Fix sprite performance regression since retained render world (#17078)
# Objective - Fix sprite rendering performance regression since retained render world changes - The retained render world changes moved `ExtractedSprites` from using the highly-optimised `EntityHasher` with an `Entity` to using `FixedHasher` with `(Entity, MainEntity)`. This was enough to regress framerate in bevymark by 25%. ## Solution - Move the render world entity into a member of `ExtractedSprite` and change `ExtractedSprites` to use `MainEntityHashMap` for its storage - Disable sprite picking in bevymark ## Testing M4 Max. `bevymark --waves 100 --per-wave 1000 --benchmark`. main in yellow vs PR in red: <img width="590" alt="Screenshot 2025-01-01 at 16 36 22" src="https://github.com/user-attachments/assets/1e4ed6ec-3811-4abf-8b30-336153737f89" /> 20.2% median frame time reduction. <img width="594" alt="Screenshot 2025-01-01 at 16 38 37" src="https://github.com/user-attachments/assets/157c2022-cda6-4cf2-bc63-d0bc40528cf0" /> 49.7% median extract_sprites execution time reduction. Comparing 0.14.2 yellow vs PR red: <img width="593" alt="Screenshot 2025-01-01 at 16 40 06" src="https://github.com/user-attachments/assets/abd59b6f-290a-4eb6-8835-ed110af995f3" /> ~6.1% median frame time reduction. --- ## Migration Guide - `ExtractedSprites` is now using `MainEntityHashMap` for storage, which is keyed on `MainEntity`. - The render world entity corresponding to an `ExtractedSprite` is now stored in the `render_entity` member of it.
This commit is contained in:
parent
0141bd01b3
commit
fd330c834f
@ -19,7 +19,6 @@ use bevy_ecs::{
|
||||
};
|
||||
use bevy_image::{BevyDefault, Image, ImageSampler, TextureFormatPixelInfo};
|
||||
use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
|
||||
use bevy_render::sync_world::MainEntity;
|
||||
use bevy_render::view::RenderVisibleEntities;
|
||||
use bevy_render::{
|
||||
render_asset::RenderAssets,
|
||||
@ -32,7 +31,7 @@ use bevy_render::{
|
||||
*,
|
||||
},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
sync_world::{RenderEntity, TemporaryRenderEntity},
|
||||
sync_world::{MainEntityHashMap, RenderEntity, TemporaryRenderEntity},
|
||||
texture::{DefaultImageSampler, FallbackImage, GpuImage},
|
||||
view::{
|
||||
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
|
||||
@ -341,11 +340,12 @@ pub struct ExtractedSprite {
|
||||
/// For cases where additional [`ExtractedSprites`] are created during extraction, this stores the
|
||||
/// entity that caused that creation for use in determining visibility.
|
||||
pub original_entity: Option<Entity>,
|
||||
pub render_entity: Entity,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedSprites {
|
||||
pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>,
|
||||
pub sprites: MainEntityHashMap<ExtractedSprite>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
@ -390,16 +390,13 @@ pub fn extract_sprites(
|
||||
if let Some(slices) = slices {
|
||||
extracted_sprites.sprites.extend(
|
||||
slices
|
||||
.extract_sprites(transform, original_entity, sprite)
|
||||
.map(|e| {
|
||||
(
|
||||
(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
original_entity.into(),
|
||||
),
|
||||
e,
|
||||
)
|
||||
}),
|
||||
.extract_sprites(
|
||||
transform,
|
||||
original_entity,
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
sprite,
|
||||
)
|
||||
.map(|e| (original_entity.into(), e)),
|
||||
);
|
||||
} else {
|
||||
let atlas_rect = sprite
|
||||
@ -420,7 +417,7 @@ pub fn extract_sprites(
|
||||
|
||||
// PERF: we don't check in this function that the `Image` asset is ready, since it should be in most cases and hashing the handle is expensive
|
||||
extracted_sprites.sprites.insert(
|
||||
(entity, original_entity.into()),
|
||||
original_entity.into(),
|
||||
ExtractedSprite {
|
||||
color: sprite.color.into(),
|
||||
transform: *transform,
|
||||
@ -432,6 +429,7 @@ pub fn extract_sprites(
|
||||
image_handle_id: sprite.image.id(),
|
||||
anchor: sprite.anchor.as_vec(),
|
||||
original_entity: Some(original_entity),
|
||||
render_entity: entity,
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -558,8 +556,11 @@ pub fn queue_sprites(
|
||||
.items
|
||||
.reserve(extracted_sprites.sprites.len());
|
||||
|
||||
for ((entity, main_entity), extracted_sprite) in extracted_sprites.sprites.iter() {
|
||||
let index = extracted_sprite.original_entity.unwrap_or(*entity).index();
|
||||
for (main_entity, extracted_sprite) in extracted_sprites.sprites.iter() {
|
||||
let index = extracted_sprite
|
||||
.original_entity
|
||||
.unwrap_or(extracted_sprite.render_entity)
|
||||
.index();
|
||||
|
||||
if !view_entities.contains(index as usize) {
|
||||
continue;
|
||||
@ -572,7 +573,7 @@ pub fn queue_sprites(
|
||||
transparent_phase.add(Transparent2d {
|
||||
draw_function: draw_sprite_function,
|
||||
pipeline,
|
||||
entity: (*entity, *main_entity),
|
||||
entity: (extracted_sprite.render_entity, *main_entity),
|
||||
sort_key,
|
||||
// batch_range and dynamic_offset will be calculated in prepare_sprites
|
||||
batch_range: 0..0,
|
||||
@ -662,7 +663,7 @@ pub fn prepare_sprite_image_bind_groups(
|
||||
// Compatible items share the same entity.
|
||||
for item_index in 0..transparent_phase.items.len() {
|
||||
let item = &transparent_phase.items[item_index];
|
||||
let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else {
|
||||
let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity.1) else {
|
||||
// If there is a phase item that is not a sprite, then we must start a new
|
||||
// batch to draw the other phase item(s) and to respect draw order. This can be
|
||||
// done by invalidating the batch_image_handle
|
||||
|
@ -28,6 +28,7 @@ impl ComputedTextureSlices {
|
||||
&'a self,
|
||||
transform: &'a GlobalTransform,
|
||||
original_entity: Entity,
|
||||
render_entity: Entity,
|
||||
sprite: &'a Sprite,
|
||||
) -> impl ExactSizeIterator<Item = ExtractedSprite> + 'a {
|
||||
let mut flip = Vec2::ONE;
|
||||
@ -53,6 +54,7 @@ impl ComputedTextureSlices {
|
||||
flip_y,
|
||||
image_handle_id: sprite.image.id(),
|
||||
anchor: Self::redepend_anchor_from_sprite_to_slice(sprite, slice),
|
||||
render_entity,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -206,10 +206,7 @@ pub fn extract_text2d_sprite(
|
||||
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
|
||||
|
||||
extracted_sprites.sprites.insert(
|
||||
(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
original_entity.into(),
|
||||
),
|
||||
original_entity.into(),
|
||||
ExtractedSprite {
|
||||
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
|
||||
color,
|
||||
@ -220,6 +217,7 @@ pub fn extract_text2d_sprite(
|
||||
flip_y: false,
|
||||
anchor: Anchor::Center.as_vec(),
|
||||
original_entity: Some(original_entity),
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ use bevy::{
|
||||
render_asset::RenderAssetUsages,
|
||||
render_resource::{Extent3d, TextureDimension, TextureFormat},
|
||||
},
|
||||
sprite::AlphaMode2d,
|
||||
sprite::{AlphaMode2d, SpritePlugin},
|
||||
utils::Duration,
|
||||
window::{PresentMode, WindowResolution},
|
||||
winit::{UpdateMode, WinitSettings},
|
||||
@ -132,16 +132,21 @@ fn main() {
|
||||
|
||||
App::new()
|
||||
.add_plugins((
|
||||
DefaultPlugins.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "BevyMark".into(),
|
||||
resolution: WindowResolution::new(1920.0, 1080.0)
|
||||
.with_scale_factor_override(1.0),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
DefaultPlugins
|
||||
.set(WindowPlugin {
|
||||
primary_window: Some(Window {
|
||||
title: "BevyMark".into(),
|
||||
resolution: WindowResolution::new(1920.0, 1080.0)
|
||||
.with_scale_factor_override(1.0),
|
||||
present_mode: PresentMode::AutoNoVsync,
|
||||
..default()
|
||||
}),
|
||||
..default()
|
||||
})
|
||||
.set(SpritePlugin {
|
||||
#[cfg(feature = "bevy_sprite_picking_backend")]
|
||||
add_picking: false,
|
||||
}),
|
||||
..default()
|
||||
}),
|
||||
FrameTimeDiagnosticsPlugin,
|
||||
LogDiagnosticsPlugin::default(),
|
||||
))
|
||||
|
Loading…
Reference in New Issue
Block a user