diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index b337df92d9687..b43c3841a127a 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -80,6 +80,7 @@ impl Plugin for SpritePlugin { .init_resource::>() .init_resource::() .init_resource::() + .init_resource::() .init_resource::() .add_render_command::() .add_systems( diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 3949fbe10d733..a546d47a9e9e4 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -333,6 +333,27 @@ pub struct ExtractedSprites { pub sprites: SparseSet, } +/// Allows extraction of multiply sprites for a single entity that all share that entity's id for visibility resolution. +/// Not designed for efficiency, extracting sprite entities individually should be more performant. +#[derive(Resource, Default)] +pub struct ExtractedSpriteBatches { + /// maps the entity id for each batch to a range of indices into `sprite_ids` + pub batches: SparseSet>, + /// set of all the extracted sprites from every batch + pub sprites: SparseSet, + /// ids of empty entities used to identify the individual sprites in each batch + pub sprite_ids: Vec, +} + +impl ExtractedSpriteBatches { + /// Clear all batches + pub fn clear(&mut self) { + self.batches.clear(); + self.sprites.clear(); + self.sprite_ids.clear(); + } +} + #[derive(Resource, Default)] pub struct SpriteAssetEvents { pub images: Vec>, @@ -505,6 +526,7 @@ pub fn queue_sprites( pipeline_cache: Res, msaa: Res, extracted_sprites: Res, + extracted_batches: Res, mut views: Query<( &mut RenderPhase, &VisibleEntities, @@ -559,7 +581,7 @@ pub fn queue_sprites( transparent_phase .items - .reserve(extracted_sprites.sprites.len()); + .reserve(extracted_sprites.sprites.len() + extracted_batches.sprites.len()); for (entity, extracted_sprite) in extracted_sprites.sprites.iter() { if !view_entities.contains(entity.index() as usize) { @@ -590,6 +612,38 @@ pub fn queue_sprites( }); } } + + for (batch_entity, sprite_entities) in extracted_batches.batches.iter() { + if !view_entities.contains(batch_entity.index() as usize) { + continue; + } + for sprite_entity in &extracted_batches.sprite_ids[sprite_entities.clone()] { + let extracted_sprite = extracted_batches.sprites.get(*sprite_entity).unwrap(); + // These items will be sorted by depth with other phase items + let sort_key = FloatOrd(extracted_sprite.transform.translation().z); + + // Add the item to the render phase + if extracted_sprite.color != Color::WHITE { + transparent_phase.add(Transparent2d { + draw_function: draw_sprite_function, + pipeline: colored_pipeline, + entity: *sprite_entity, + sort_key, + // batch_size will be calculated in prepare_glyphs + batch_size: 0, + }); + } else { + transparent_phase.add(Transparent2d { + draw_function: draw_sprite_function, + pipeline, + entity: *sprite_entity, + sort_key, + // batch_size will be calculated in prepare_glyphs + batch_size: 0, + }); + } + } + } } } @@ -605,6 +659,7 @@ pub fn prepare_sprites( mut image_bind_groups: ResMut, gpu_images: Res>, extracted_sprites: Res, + extracted_batches: Res, mut phases: Query<&mut RenderPhase>, events: Res, ) { @@ -648,7 +703,11 @@ pub fn prepare_sprites( // 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) + .or_else(|| extracted_batches.sprites.get(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_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 79fcc40094fc0..3d49da31d83d1 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -17,7 +17,7 @@ use bevy_render::{ view::{InheritedVisibility, ViewVisibility, Visibility}, Extract, }; -use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSprites, TextureAtlas}; +use bevy_sprite::{Anchor, ExtractedSprite, ExtractedSpriteBatches, TextureAtlas}; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_utils::HashSet; use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; @@ -80,11 +80,12 @@ pub struct Text2dBundle { pub fn extract_text2d_sprite( mut commands: Commands, - mut extracted_sprites: ResMut, + mut extracted_batches: ResMut, texture_atlases: Extract>>, windows: Extract>>, text2d_query: Extract< Query<( + Entity, &ViewVisibility, &Text, &TextLayoutInfo, @@ -93,6 +94,7 @@ pub fn extract_text2d_sprite( )>, >, ) { + extracted_batches.clear(); // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 let scale_factor = windows .get_single() @@ -100,18 +102,20 @@ pub fn extract_text2d_sprite( .unwrap_or(1.0); let scaling = GlobalTransform::from_scale(Vec3::splat(scale_factor.recip())); - for (view_visibility, text, text_layout_info, anchor, global_transform) in text2d_query.iter() { + for (entity, view_visibility, text, text_layout_info, anchor, global_transform) in + text2d_query.iter() + { if !view_visibility.get() { continue; } - + let start = extracted_batches.sprite_ids.len(); let text_anchor = -(anchor.as_vec() + 0.5); let alignment_translation = text_layout_info.size * text_anchor; let transform = *global_transform * scaling * GlobalTransform::from_translation(alignment_translation.extend(0.)); - let mut color = Color::WHITE; let mut current_section = usize::MAX; + let mut color = Color::WHITE; for PositionedGlyph { position, atlas_info, @@ -123,22 +127,25 @@ pub fn extract_text2d_sprite( color = text.sections[*section_index].style.color.as_rgba_linear(); current_section = *section_index; } + let atlas = texture_atlases.get(&atlas_info.texture_atlas).unwrap(); - extracted_sprites.sprites.insert( - commands.spawn_empty().id(), - ExtractedSprite { - transform: transform * GlobalTransform::from_translation(position.extend(0.)), - color, - rect: Some(atlas.textures[atlas_info.glyph_index]), - custom_size: None, - image_handle_id: atlas.texture.id(), - flip_x: false, - flip_y: false, - anchor: Anchor::Center.as_vec(), - }, - ); + let sprite_id = commands.spawn_empty().id(); + let sprite = ExtractedSprite { + transform: transform * GlobalTransform::from_translation(position.extend(0.)), + color, + rect: Some(atlas.textures[atlas_info.glyph_index]), + custom_size: None, + image_handle_id: atlas.texture.id(), + flip_x: false, + flip_y: false, + anchor: Anchor::Center.as_vec(), + }; + extracted_batches.sprite_ids.push(sprite_id); + extracted_batches.sprites.insert(sprite_id, sprite); } + let indices = start..extracted_batches.sprite_ids.len(); + extracted_batches.batches.insert(entity, indices); } }