Skip to content

Commit 4d8bc61

Browse files
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` <img width="452" alt="extract_sprites" src="https://github.com/user-attachments/assets/66c60406-7c2b-4367-907d-4a71d3630296" /> `queue_sprites` <img width="463" alt="queue_sprites" src="https://github.com/user-attachments/assets/54b903bd-4137-4772-9f87-e10e1e050d69" /> --------- Co-authored-by: Alice Cecile <[email protected]>
1 parent 958c9bb commit 4d8bc61

File tree

7 files changed

+64
-59
lines changed

7 files changed

+64
-59
lines changed

crates/bevy_core_pipeline/src/core_2d/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ pub struct Transparent2d {
349349
pub pipeline: CachedRenderPipelineId,
350350
pub draw_function: DrawFunctionId,
351351
pub batch_range: Range<u32>,
352+
pub extracted_index: usize,
352353
pub extra_index: PhaseItemExtraIndex,
353354
/// Whether the mesh in question is indexed (uses an index buffer in
354355
/// addition to its vertex buffer).

crates/bevy_gizmos/src/pipeline_2d.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ fn queue_line_gizmos_2d(
341341
sort_key: FloatOrd(f32::INFINITY),
342342
batch_range: 0..1,
343343
extra_index: PhaseItemExtraIndex::None,
344+
extracted_index: usize::MAX,
344345
indexed: false,
345346
});
346347
}
@@ -362,6 +363,7 @@ fn queue_line_gizmos_2d(
362363
sort_key: FloatOrd(f32::INFINITY),
363364
batch_range: 0..1,
364365
extra_index: PhaseItemExtraIndex::None,
366+
extracted_index: usize::MAX,
365367
indexed: false,
366368
});
367369
}
@@ -421,6 +423,7 @@ fn queue_line_joint_gizmos_2d(
421423
sort_key: FloatOrd(f32::INFINITY),
422424
batch_range: 0..1,
423425
extra_index: PhaseItemExtraIndex::None,
426+
extracted_index: usize::MAX,
424427
indexed: false,
425428
});
426429
}

crates/bevy_sprite/src/mesh2d/material.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ pub fn queue_material2d_meshes<M: Material2d>(
880880
// Batching is done in batch_and_prepare_render_phase
881881
batch_range: 0..1,
882882
extra_index: PhaseItemExtraIndex::None,
883+
extracted_index: usize::MAX,
883884
indexed: mesh.indexed(),
884885
});
885886
}

crates/bevy_sprite/src/render/mod.rs

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ use bevy_ecs::{
1919
use bevy_image::{BevyDefault, Image, ImageSampler, TextureAtlasLayout, TextureFormatPixelInfo};
2020
use bevy_math::{Affine3A, FloatOrd, Quat, Rect, Vec2, Vec4};
2121
use bevy_platform_support::collections::HashMap;
22-
use bevy_render::sync_world::MainEntity;
2322
use bevy_render::view::{RenderVisibleEntities, RetainedViewEntity};
2423
use bevy_render::{
2524
render_asset::RenderAssets,
@@ -32,7 +31,7 @@ use bevy_render::{
3231
*,
3332
},
3433
renderer::{RenderDevice, RenderQueue},
35-
sync_world::{RenderEntity, TemporaryRenderEntity},
34+
sync_world::RenderEntity,
3635
texture::{DefaultImageSampler, FallbackImage, GpuImage},
3736
view::{
3837
ExtractedView, Msaa, ViewTarget, ViewUniform, ViewUniformOffset, ViewUniforms,
@@ -339,13 +338,15 @@ pub struct ExtractedSprite {
339338
pub anchor: Vec2,
340339
/// For cases where additional [`ExtractedSprites`] are created during extraction, this stores the
341340
/// entity that caused that creation for use in determining visibility.
342-
pub original_entity: Option<Entity>,
341+
pub original_entity: Entity,
343342
pub scaling_mode: Option<ScalingMode>,
343+
pub render_entity: Entity,
344344
}
345345

346346
#[derive(Resource, Default)]
347347
pub struct ExtractedSprites {
348-
pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>,
348+
//pub sprites: HashMap<(Entity, MainEntity), ExtractedSprite>,
349+
pub sprites: Vec<ExtractedSprite>,
349350
}
350351

351352
#[derive(Resource, Default)]
@@ -388,19 +389,12 @@ pub fn extract_sprites(
388389
}
389390

390391
if let Some(slices) = slices {
391-
extracted_sprites.sprites.extend(
392-
slices
393-
.extract_sprites(transform, original_entity, sprite)
394-
.map(|e| {
395-
(
396-
(
397-
commands.spawn(TemporaryRenderEntity).id(),
398-
original_entity.into(),
399-
),
400-
e,
401-
)
402-
}),
403-
);
392+
extracted_sprites.sprites.extend(slices.extract_sprites(
393+
&mut commands,
394+
transform,
395+
original_entity,
396+
sprite,
397+
));
404398
} else {
405399
let atlas_rect = sprite
406400
.texture_atlas
@@ -419,22 +413,20 @@ pub fn extract_sprites(
419413
};
420414

421415
// 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
422-
extracted_sprites.sprites.insert(
423-
(entity, original_entity.into()),
424-
ExtractedSprite {
425-
color: sprite.color.into(),
426-
transform: *transform,
427-
rect,
428-
// Pass the custom size
429-
custom_size: sprite.custom_size,
430-
flip_x: sprite.flip_x,
431-
flip_y: sprite.flip_y,
432-
image_handle_id: sprite.image.id(),
433-
anchor: sprite.anchor.as_vec(),
434-
original_entity: Some(original_entity),
435-
scaling_mode: sprite.image_mode.scale(),
436-
},
437-
);
416+
extracted_sprites.sprites.push(ExtractedSprite {
417+
render_entity: entity,
418+
color: sprite.color.into(),
419+
transform: *transform,
420+
rect,
421+
// Pass the custom size
422+
custom_size: sprite.custom_size,
423+
flip_x: sprite.flip_x,
424+
flip_y: sprite.flip_y,
425+
image_handle_id: sprite.image.id(),
426+
anchor: sprite.anchor.as_vec(),
427+
original_entity,
428+
scaling_mode: sprite.image_mode.scale(),
429+
});
438430
}
439431
}
440432
}
@@ -561,10 +553,10 @@ pub fn queue_sprites(
561553
.items
562554
.reserve(extracted_sprites.sprites.len());
563555

564-
for ((entity, main_entity), extracted_sprite) in extracted_sprites.sprites.iter() {
565-
let index = extracted_sprite.original_entity.unwrap_or(*entity).index();
556+
for (index, extracted_sprite) in extracted_sprites.sprites.iter().enumerate() {
557+
let view_index = extracted_sprite.original_entity.index();
566558

567-
if !view_entities.contains(index as usize) {
559+
if !view_entities.contains(view_index as usize) {
568560
continue;
569561
}
570562

@@ -575,11 +567,15 @@ pub fn queue_sprites(
575567
transparent_phase.add(Transparent2d {
576568
draw_function: draw_sprite_function,
577569
pipeline,
578-
entity: (*entity, *main_entity),
570+
entity: (
571+
extracted_sprite.render_entity,
572+
extracted_sprite.original_entity.into(),
573+
),
579574
sort_key,
580575
// `batch_range` is calculated in `prepare_sprite_image_bind_groups`
581576
batch_range: 0..0,
582577
extra_index: PhaseItemExtraIndex::None,
578+
extracted_index: index,
583579
indexed: true,
584580
});
585581
}
@@ -664,7 +660,12 @@ pub fn prepare_sprite_image_bind_groups(
664660
// Compatible items share the same entity.
665661
for item_index in 0..transparent_phase.items.len() {
666662
let item = &transparent_phase.items[item_index];
667-
let Some(extracted_sprite) = extracted_sprites.sprites.get(&item.entity) else {
663+
664+
let Some(extracted_sprite) = extracted_sprites
665+
.sprites
666+
.get(item.extracted_index)
667+
.filter(|extracted_sprite| extracted_sprite.render_entity == item.entity())
668+
else {
668669
// If there is a phase item that is not a sprite, then we must start a new
669670
// batch to draw the other phase item(s) and to respect draw order. This can be
670671
// done by invalidating the batch_image_handle

crates/bevy_sprite/src/texture_slice/computed_slices.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use bevy_ecs::prelude::*;
66
use bevy_image::Image;
77
use bevy_math::{Rect, Vec2};
88
use bevy_platform_support::collections::HashSet;
9+
use bevy_render::sync_world::TemporaryRenderEntity;
910
use bevy_transform::prelude::*;
1011

1112
/// Component storing texture slices for tiled or sliced sprite entities
@@ -24,12 +25,13 @@ impl ComputedTextureSlices {
2425
/// * `sprite` - The sprite component
2526
/// * `handle` - The sprite texture handle
2627
#[must_use]
27-
pub(crate) fn extract_sprites<'a>(
28+
pub(crate) fn extract_sprites<'a, 'w, 's>(
2829
&'a self,
30+
commands: &'a mut Commands<'w, 's>,
2931
transform: &'a GlobalTransform,
3032
original_entity: Entity,
3133
sprite: &'a Sprite,
32-
) -> impl ExactSizeIterator<Item = ExtractedSprite> + 'a {
34+
) -> impl ExactSizeIterator<Item = ExtractedSprite> + 'a + use<'a, 'w, 's> {
3335
let mut flip = Vec2::ONE;
3436
let [mut flip_x, mut flip_y] = [false; 2];
3537
if sprite.flip_x {
@@ -44,7 +46,8 @@ impl ComputedTextureSlices {
4446
let offset = (slice.offset * flip).extend(0.0);
4547
let transform = transform.mul_transform(Transform::from_translation(offset));
4648
ExtractedSprite {
47-
original_entity: Some(original_entity),
49+
render_entity: commands.spawn(TemporaryRenderEntity).id(),
50+
original_entity,
4851
color: sprite.color.into(),
4952
transform,
5053
rect: Some(slice.texture_rect),

crates/bevy_text/src/text2d.rs

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -204,24 +204,19 @@ pub fn extract_text2d_sprite(
204204
}
205205
let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap();
206206

207-
extracted_sprites.sprites.insert(
208-
(
209-
commands.spawn(TemporaryRenderEntity).id(),
210-
original_entity.into(),
211-
),
212-
ExtractedSprite {
213-
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
214-
color,
215-
rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()),
216-
custom_size: None,
217-
image_handle_id: atlas_info.texture.id(),
218-
flip_x: false,
219-
flip_y: false,
220-
anchor: Anchor::Center.as_vec(),
221-
original_entity: Some(original_entity),
222-
scaling_mode: None,
223-
},
224-
);
207+
extracted_sprites.sprites.push(ExtractedSprite {
208+
render_entity: commands.spawn(TemporaryRenderEntity).id(),
209+
transform: transform * GlobalTransform::from_translation(position.extend(0.)),
210+
color,
211+
rect: Some(atlas.textures[atlas_info.location.glyph_index].as_rect()),
212+
custom_size: None,
213+
image_handle_id: atlas_info.texture.id(),
214+
flip_x: false,
215+
flip_y: false,
216+
anchor: Anchor::Center.as_vec(),
217+
original_entity,
218+
scaling_mode: None,
219+
});
225220
}
226221
}
227222
}

examples/2d/mesh2d_manual.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,7 @@ pub fn queue_colored_mesh2d(
412412
// This material is not batched
413413
batch_range: 0..1,
414414
extra_index: PhaseItemExtraIndex::None,
415+
extracted_index: usize::MAX,
415416
indexed: mesh.indexed(),
416417
});
417418
}

0 commit comments

Comments
 (0)