From 4d8bc6161b1c80c8b6929a11739a57e2f8a3176d Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Tue, 18 Mar 2025 00:48:33 +0000 Subject: [PATCH] Extract sprites into a `Vec` (#17619) # Objective Extract sprites into a `Vec` instead of a `HashMap`. ## Solution Extract UI nodes into a `Vec` instead of an `EntityHashMap`. Add an index into the `Vec` to `Transparent2d`. Compare both the index and render entity in prepare so there aren't any collisions. ## Showcase yellow this PR, red main ``` cargo run --example many_sprites --release --features "trace_tracy" ``` `extract_sprites` extract_sprites `queue_sprites` queue_sprites --------- Co-authored-by: Alice Cecile --- crates/bevy_core_pipeline/src/core_2d/mod.rs | 1 + crates/bevy_gizmos/src/pipeline_2d.rs | 3 + crates/bevy_sprite/src/mesh2d/material.rs | 1 + crates/bevy_sprite/src/render/mod.rs | 77 ++++++++++--------- .../src/texture_slice/computed_slices.rs | 9 ++- crates/bevy_text/src/text2d.rs | 31 ++++---- examples/2d/mesh2d_manual.rs | 1 + 7 files changed, 64 insertions(+), 59 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index f327757277..3261ef5a61 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -349,6 +349,7 @@ pub struct Transparent2d { pub pipeline: CachedRenderPipelineId, pub draw_function: DrawFunctionId, pub batch_range: Range, + pub extracted_index: usize, pub extra_index: PhaseItemExtraIndex, /// Whether the mesh in question is indexed (uses an index buffer in /// addition to its vertex buffer). diff --git a/crates/bevy_gizmos/src/pipeline_2d.rs b/crates/bevy_gizmos/src/pipeline_2d.rs index d81c55ecac..3a43055491 100644 --- a/crates/bevy_gizmos/src/pipeline_2d.rs +++ b/crates/bevy_gizmos/src/pipeline_2d.rs @@ -341,6 +341,7 @@ fn queue_line_gizmos_2d( sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + extracted_index: usize::MAX, indexed: false, }); } @@ -362,6 +363,7 @@ fn queue_line_gizmos_2d( sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + extracted_index: usize::MAX, indexed: false, }); } @@ -421,6 +423,7 @@ fn queue_line_joint_gizmos_2d( sort_key: FloatOrd(f32::INFINITY), batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + extracted_index: usize::MAX, indexed: false, }); } diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9192dce4e1..81ad7a9e3e 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -880,6 +880,7 @@ pub fn queue_material2d_meshes( // Batching is done in batch_and_prepare_render_phase batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + extracted_index: usize::MAX, indexed: mesh.indexed(), }); } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index b188ebbc1a..e06d20b3c6 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -19,7 +19,6 @@ use bevy_ecs::{ use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo}; use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4}; use bevy_platform_support::collections::HashMap; -use bevy_render::sync_world::MainEntity; use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity}; use bevy_render::{ render_asset::RenderAssets, @@ -32,7 +31,7 @@ use bevy_render::{ *, }, renderer::{RenderDevice, RenderQueue}, - sync_world::{RenderEntity, TemporaryRenderEntity}, + sync_world::RenderEntity, texture::{DefaultImageSampler, FallbackImage, GpuImage}, view::{ ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms, @@ -339,13 +338,15 @@ pub struct ExtractedSprite { pub anchor: Vec2, /// 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, + pub original_entity: Entity, pub scaling_mode: Option, + pub render_entity: Entity, } #[derive(Resource, Default)] pub struct ExtractedSprites { - pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>, + //pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>, + pub sprites: Vec, } #[derive(Resource, Default)] @@ -388,19 +389,12 @@ 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, - ) - }), - ); + extracted_sprites.sprites.extend(slices.extract_sprites( + &mut commands, + transform, + original_entity, + sprite, + )); } else { let atlas_rect = sprite .texture_atlas @@ -419,22 +413,20 @@ 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()), - ExtractedSprite { - color: sprite.color.into(), - transform: *transform, - rect, - // Pass the custom size - custom_size: sprite.custom_size, - flip_x: sprite.flip_x, - flip_y: sprite.flip_y, - image_handle_id: sprite.image.id(), - anchor: sprite.anchor.as_vec(), - original_entity: Some(original_entity), - scaling_mode: sprite.image_mode.scale(), - }, - ); + extracted_sprites.sprites.push(ExtractedSprite { + render_entity: entity, + color: sprite.color.into(), + transform: *transform, + rect, + // Pass the custom size + custom_size: sprite.custom_size, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + image_handle_id: sprite.image.id(), + anchor: sprite.anchor.as_vec(), + original_entity, + scaling_mode: sprite.image_mode.scale(), + }); } } } @@ -561,10 +553,10 @@ 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 (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() { + let view_index = extracted_sprite.original_entity.index(); - if !view_entities.contains(index as usize) { + if !view_entities.contains(view_index as usize) { continue; } @@ -575,11 +567,15 @@ pub fn queue_sprites( transparent_phase.add(Transparent2d { draw_function: draw_sprite_function, pipeline, - entity: (*entity, *main_entity), + entity: ( + extracted_sprite.render_entity, + extracted_sprite.original_entity.into(), + ), sort_key, // `batch_range` is calculated in `prepare_sprite_image_bind_groups` batch_range: 0..0, extra_index: PhaseItemExtraIndex::None, + extracted_index: index, indexed: true, }); } @@ -664,7 +660,12 @@ 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.extracted_index) + .filter(|extracted_sprite| extracted_sprite.render_entity == item.entity()) + 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 diff --git a/crates/bevy_sprite/src/texture_slice/computed_slices.rs b/crates/bevy_sprite/src/texture_slice/computed_slices.rs index c258e5f652..78b84d2977 100644 --- a/crates/bevy_sprite/src/texture_slice/computed_slices.rs +++ b/crates/bevy_sprite/src/texture_slice/computed_slices.rs @@ -6,6 +6,7 @@ use bevy_ecs::prelude::*; use bevy_image::Image; use bevy_math::{Rect, Vec2}; use bevy_platform_support::collections::HashSet; +use bevy_render::sync_world::TemporaryRenderEntity; use bevy_transform::prelude::*; /// Component storing texture slices for tiled or sliced sprite entities @@ -24,12 +25,13 @@ impl ComputedTextureSlices { /// * `sprite` - The sprite component /// * `handle` - The sprite texture handle #[must_use] - pub(crate) fn extract_sprites<'a>( + pub(crate) fn extract_sprites<'a, 'w, 's>( &'a self, + commands: &'a mut Commands<'w, 's>, transform: &'a GlobalTransform, original_entity: Entity, sprite: &'a Sprite, - ) -> impl ExactSizeIterator + 'a { + ) -> impl ExactSizeIterator + 'a + use<'a, 'w, 's> { let mut flip = Vec2::ONE; let [mut flip_x, mut flip_y] = [false; 2]; if sprite.flip_x { @@ -44,7 +46,8 @@ impl ComputedTextureSlices { let offset = (slice.offset * flip).extend(0.0); let transform = transform.mul_transform(Transform::from_translation(offset)); ExtractedSprite { - original_entity: Some(original_entity), + render_entity: commands.spawn(TemporaryRenderEntity).id(), + original_entity, color: sprite.color.into(), transform, rect: Some(slice.texture_rect), diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index baf5700a3c..fc750c5ec1 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -204,24 +204,19 @@ 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(), - ), - ExtractedSprite { - transform: transform * GlobalTransform::from_translation(position.extend(0.)), - color, - rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()), - custom_size: None, - image_handle_id: atlas_info.texture.id(), - flip_x: false, - flip_y: false, - anchor: Anchor::Center.as_vec(), - original_entity: Some(original_entity), - scaling_mode: None, - }, - ); + extracted_sprites.sprites.push(ExtractedSprite { + render_entity: commands.spawn(TemporaryRenderEntity).id(), + transform: transform * GlobalTransform::from_translation(position.extend(0.)), + color, + rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()), + custom_size: None, + image_handle_id: atlas_info.texture.id(), + flip_x: false, + flip_y: false, + anchor: Anchor::Center.as_vec(), + original_entity, + scaling_mode: None, + }); } } } diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index e203a3668a..b6c27a2e62 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -412,6 +412,7 @@ pub fn queue_colored_mesh2d( // This material is not batched batch_range: 0..1, extra_index: PhaseItemExtraIndex::None, + extracted_index: usize::MAX, indexed: mesh.indexed(), }); }