From e30e95f14c0597a12f5b9e0cd31c373fd9ad8c69 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Thu, 7 Jul 2022 18:29:14 +0200 Subject: [PATCH 1/9] Add a Prepare stage for sprite rendering --- crates/bevy_sprite/src/lib.rs | 11 +- crates/bevy_sprite/src/render/mod.rs | 324 ++++++++++++++------------- 2 files changed, 182 insertions(+), 153 deletions(-) diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index a7be1a4ab983b..f1646d1c9f187 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -48,6 +48,8 @@ pub const SPRITE_SHADER_HANDLE: HandleUntyped = #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] pub enum SpriteSystem { ExtractSprites, + PrepareSprites, + QueueSprites, } impl Plugin for SpritePlugin { @@ -75,7 +77,14 @@ impl Plugin for SpritePlugin { render::extract_sprites.label(SpriteSystem::ExtractSprites), ) .add_system_to_stage(RenderStage::Extract, render::extract_sprite_events) - .add_system_to_stage(RenderStage::Queue, queue_sprites); + .add_system_to_stage( + RenderStage::Prepare, + render::prepare_sprites.label(SpriteSystem::PrepareSprites), + ) + .add_system_to_stage( + RenderStage::Queue, + render::queue_sprites.label(SpriteSystem::QueueSprites), + ); }; } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index ead7f71895f8f..5129b87d520c9 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,4 +1,5 @@ use std::cmp::Ordering; +use std::ops::Range; use crate::{ texture_atlas::{TextureAtlas, TextureAtlasSprite}, @@ -170,7 +171,6 @@ impl SpecializedRenderPipeline for SpritePipeline { } } -#[derive(Component, Clone, Copy)] pub struct ExtractedSprite { pub transform: GlobalTransform, pub color: Color, @@ -232,6 +232,7 @@ pub fn extract_sprites( )>, >, ) { + let mut extracted_sprites = render_world.resource_mut::(); extracted_sprites.sprites.clear(); for (visibility, sprite, transform, handle) in sprite_query.iter() { if !visibility.is_visible { @@ -320,10 +321,149 @@ const QUAD_UVS: [Vec2; 4] = [ Vec2::new(0., 0.), ]; -#[derive(Component, Eq, PartialEq, Copy, Clone)] +#[derive(Component)] pub struct SpriteBatch { + range: Range, image_handle_id: HandleId, colored: bool, + z_order: f32, +} + +pub fn prepare_sprites( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut sprite_meta: ResMut, + gpu_images: Res>, + mut extracted_sprites: ResMut, +) { + // Clear the vertex buffers + sprite_meta.vertices.clear(); + sprite_meta.colored_vertices.clear(); + + // We divide the sprites according to their color + let [mut white_sprites, mut colored_sprites] = extracted_sprites.sprites.drain(..).fold( + [vec![], vec![]], + |[mut colored, mut white], sprite| { + if sprite.color == Color::WHITE { + white.push(sprite); + } else { + colored.push(sprite) + } + [colored, white] + }, + ); + + // Sort sprites by z for correct transparency and then by handle to improve batching + let sort = |vec: &mut Vec| { + vec.sort_unstable_by(|a, b| { + match a + .transform + .translation + .z + .partial_cmp(&b.transform.translation.z) + { + Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), + Some(other) => other, + } + }); + }; + sort(&mut white_sprites); + sort(&mut colored_sprites); + + for (colored, sprites) in [(false, white_sprites), (true, colored_sprites)] { + // Impossible starting values that will be replaced on the first iteration + let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); + let mut current_image_size = Vec2::ZERO; + let mut current_z_order = 0.0; + + // Vertex buffer indices + let mut index_start = 0; + let mut index_end = 0; + + for extracted_sprite in sprites { + if extracted_sprite.image_handle_id != current_batch_handle + || extracted_sprite.transform.translation.z != current_z_order + { + if let Some(gpu_image) = + gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) + { + current_image_size = gpu_image.size; + current_batch_handle = extracted_sprite.image_handle_id; + current_z_order = extracted_sprite.transform.translation.z; + if index_start != index_end { + commands.spawn().insert(SpriteBatch { + range: index_start..index_end, + image_handle_id: current_batch_handle, + colored, + z_order: current_z_order, + }); + index_start = index_end; + } + } else { + // We skip loading images + continue; + } + } + // Calculate vertex data for this item + let mut uvs = QUAD_UVS; + if extracted_sprite.flip_x { + uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; + } + if extracted_sprite.flip_y { + uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; + } + + // By default, the size of the quad is the size of the texture + let mut quad_size = current_image_size; + + // If a rect is specified, adjust UVs and the size of the quad + if let Some(rect) = extracted_sprite.rect { + let rect_size = rect.size(); + for uv in &mut uvs { + *uv = (rect.min + *uv * rect_size) / current_image_size; + } + quad_size = rect_size; + } + + // Override the size if a custom one is specified + if let Some(custom_size) = extracted_sprite.custom_size { + quad_size = custom_size; + } + + // Apply size and global transform + let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { + extracted_sprite + .transform + .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) + .into() + }); + + if colored { + for i in QUAD_INDICES { + sprite_meta.colored_vertices.push(ColoredSpriteVertex { + position: positions[i], + uv: uvs[i].into(), + color: extracted_sprite.color.as_linear_rgba_f32(), + }); + } + } else { + for i in QUAD_INDICES { + sprite_meta.vertices.push(SpriteVertex { + position: positions[i], + uv: uvs[i].into(), + }); + } + } + index_end += QUAD_INDICES.len() as u32; + } + } + sprite_meta + .vertices + .write_buffer(&render_device, &render_queue); + sprite_meta + .colored_vertices + .write_buffer(&render_device, &render_queue); } #[derive(Default)] @@ -333,10 +473,8 @@ pub struct ImageBindGroups { #[allow(clippy::too_many_arguments)] pub fn queue_sprites( - mut commands: Commands, draw_functions: Res>, render_device: Res, - render_queue: Res, mut sprite_meta: ResMut, view_uniforms: Res, sprite_pipeline: Res, @@ -345,7 +483,7 @@ pub fn queue_sprites( mut image_bind_groups: ResMut, gpu_images: Res>, msaa: Res, - mut extracted_sprites: ResMut, + sprite_batches: Query<(Entity, &SpriteBatch)>, mut views: Query<&mut RenderPhase>, events: Res, ) { @@ -360,12 +498,6 @@ pub fn queue_sprites( } if let Some(view_binding) = view_uniforms.uniforms.binding() { - let sprite_meta = &mut sprite_meta; - - // Clear the vertex buffers - sprite_meta.vertices.clear(); - sprite_meta.colored_vertices.clear(); - sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { entries: &[BindGroupEntry { binding: 0, @@ -384,168 +516,56 @@ pub fn queue_sprites( key | SpritePipelineKey::COLORED, ); - // Vertex buffer indices - let mut index = 0; - let mut colored_index = 0; - // FIXME: VisibleEntities is ignored for mut transparent_phase in &mut views { - let extracted_sprites = &mut extracted_sprites.sprites; let image_bind_groups = &mut *image_bind_groups; - - transparent_phase.items.reserve(extracted_sprites.len()); - - // Sort sprites by z for correct transparency and then by handle to improve batching - extracted_sprites.sort_unstable_by(|a, b| { - match a - .transform - .translation - .z - .partial_cmp(&b.transform.translation.z) - { - Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), - Some(other) => other, - } - }); - - // Impossible starting values that will be replaced on the first iteration - let mut current_batch = SpriteBatch { - image_handle_id: HandleId::Id(Uuid::nil(), u64::MAX), - colored: false, - }; - let mut current_batch_entity = Entity::from_raw(u32::MAX); - let mut current_image_size = Vec2::ZERO; - // Add a phase item for each sprite, and detect when succesive items can be batched. - // Spawn an entity with a `SpriteBatch` component for each possible batch. - // Compatible items share the same entity. - // Batches are merged later (in `batch_phase_system()`), so that they can be interrupted - // by any other phase item (and they can interrupt other items from batching). - for extracted_sprite in extracted_sprites { - let new_batch = SpriteBatch { - image_handle_id: extracted_sprite.image_handle_id, - colored: extracted_sprite.color != Color::WHITE, - }; - if new_batch != current_batch { - // Set-up a new possible batch - if let Some(gpu_image) = - gpu_images.get(&Handle::weak(new_batch.image_handle_id)) - { - current_batch = new_batch; - current_image_size = Vec2::new(gpu_image.size.x, gpu_image.size.y); - current_batch_entity = commands.spawn_bundle((current_batch,)).id(); - - image_bind_groups - .values - .entry(Handle::weak(current_batch.image_handle_id)) - .or_insert_with(|| { - render_device.create_bind_group(&BindGroupDescriptor { - entries: &[ - BindGroupEntry { - binding: 0, - resource: BindingResource::TextureView( - &gpu_image.texture_view, - ), - }, - BindGroupEntry { - binding: 1, - resource: BindingResource::Sampler(&gpu_image.sampler), - }, - ], - label: Some("sprite_material_bind_group"), - layout: &sprite_pipeline.material_layout, - }) - }); - } else { - // Skip this item if the texture is not ready - continue; - } - } - - // Calculate vertex data for this item - - let mut uvs = QUAD_UVS; - if extracted_sprite.flip_x { - uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; - } - if extracted_sprite.flip_y { - uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; - } - - // By default, the size of the quad is the size of the texture - let mut quad_size = current_image_size; - - // If a rect is specified, adjust UVs and the size of the quad - if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - for uv in &mut uvs { - *uv = (rect.min + *uv * rect_size) / current_image_size; - } - quad_size = rect_size; - } - - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; - } - - // Apply size and global transform - let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { - extracted_sprite - .transform - .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) - .into() - }); + for (entity, sprite_batch) in sprite_batches.iter() { + image_bind_groups + .values + .entry(Handle::weak(sprite_batch.image_handle_id)) + .or_insert_with(|| { + let gpu_image = gpu_images + .get(&Handle::weak(sprite_batch.image_handle_id)) + .unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("sprite_material_bind_group"), + layout: &sprite_pipeline.material_layout, + }) + }); // These items will be sorted by depth with other phase items - let sort_key = FloatOrd(extracted_sprite.transform.translation.z); + let sort_key = FloatOrd(sprite_batch.z_order); // Store the vertex data and add the item to the render phase - if current_batch.colored { - for i in QUAD_INDICES { - sprite_meta.colored_vertices.push(ColoredSpriteVertex { - position: positions[i], - uv: uvs[i].into(), - color: extracted_sprite.color.as_linear_rgba_f32(), - }); - } - let item_start = colored_index; - colored_index += QUAD_INDICES.len() as u32; - let item_end = colored_index; - + if sprite_batch.colored { transparent_phase.add(Transparent2d { draw_function: draw_sprite_function, pipeline: colored_pipeline, - entity: current_batch_entity, + entity, sort_key, - batch_range: Some(item_start..item_end), + batch_range: Some(sprite_batch.range.clone()), }); } else { - for i in QUAD_INDICES { - sprite_meta.vertices.push(SpriteVertex { - position: positions[i], - uv: uvs[i].into(), - }); - } - let item_start = index; - index += QUAD_INDICES.len() as u32; - let item_end = index; - transparent_phase.add(Transparent2d { draw_function: draw_sprite_function, pipeline, - entity: current_batch_entity, + entity, sort_key, - batch_range: Some(item_start..item_end), + batch_range: Some(sprite_batch.range.clone()), }); } } } - sprite_meta - .vertices - .write_buffer(&render_device, &render_queue); - sprite_meta - .colored_vertices - .write_buffer(&render_device, &render_queue); } } From 39ff89577fedd96e19ca96aa7c993d07b8cb5b55 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Thu, 7 Jul 2022 18:38:18 +0200 Subject: [PATCH 2/9] Fixed issue with sprites --- crates/bevy_sprite/src/render/mod.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 5129b87d520c9..26b09cdaf968b 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -375,28 +375,25 @@ pub fn prepare_sprites( // Impossible starting values that will be replaced on the first iteration let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); let mut current_image_size = Vec2::ZERO; - let mut current_z_order = 0.0; + let mut z_order = 0.0; // Vertex buffer indices let mut index_start = 0; let mut index_end = 0; for extracted_sprite in sprites { - if extracted_sprite.image_handle_id != current_batch_handle - || extracted_sprite.transform.translation.z != current_z_order - { + if extracted_sprite.image_handle_id != current_batch_handle { if let Some(gpu_image) = gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) { current_image_size = gpu_image.size; current_batch_handle = extracted_sprite.image_handle_id; - current_z_order = extracted_sprite.transform.translation.z; if index_start != index_end { commands.spawn().insert(SpriteBatch { range: index_start..index_end, image_handle_id: current_batch_handle, colored, - z_order: current_z_order, + z_order, }); index_start = index_end; } @@ -456,6 +453,16 @@ pub fn prepare_sprites( } } index_end += QUAD_INDICES.len() as u32; + z_order = extracted_sprite.transform.translation.z; + } + // if start != end, there is one last batch to process + if index_start != index_end { + commands.spawn().insert(SpriteBatch { + range: index_start..index_end, + image_handle_id: current_batch_handle, + colored, + z_order, + }); } } sprite_meta From 7eed020afc28d22f43ab85fce7c8644875a03dea Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Thu, 7 Jul 2022 20:16:23 +0200 Subject: [PATCH 3/9] no more performance regression --- crates/bevy_sprite/src/render/mod.rs | 222 +++++++++++++-------------- 1 file changed, 110 insertions(+), 112 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 26b09cdaf968b..772e0c0e81703 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -341,130 +341,128 @@ pub fn prepare_sprites( sprite_meta.vertices.clear(); sprite_meta.colored_vertices.clear(); - // We divide the sprites according to their color - let [mut white_sprites, mut colored_sprites] = extracted_sprites.sprites.drain(..).fold( - [vec![], vec![]], - |[mut colored, mut white], sprite| { - if sprite.color == Color::WHITE { - white.push(sprite); - } else { - colored.push(sprite) - } - [colored, white] - }, - ); - // Sort sprites by z for correct transparency and then by handle to improve batching - let sort = |vec: &mut Vec| { - vec.sort_unstable_by(|a, b| { - match a - .transform - .translation - .z - .partial_cmp(&b.transform.translation.z) + extracted_sprites.sprites.sort_unstable_by(|a, b| { + match a + .transform + .translation + .z + .partial_cmp(&b.transform.translation.z) + { + Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), + Some(other) => other, + } + }); + + // Impossible starting values that will be replaced on the first iteration + let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); + let mut current_image_size = Vec2::ZERO; + let mut current_batch_colored = false; + let mut z_order = 0.0; + + // Vertex buffer indices + let [mut white_start, mut white_end] = [0, 0]; + let [mut colored_start, mut colored_end] = [0, 0]; + + for extracted_sprite in extracted_sprites.sprites.drain(..) { + let colored = extracted_sprite.color != Color::WHITE; + if extracted_sprite.image_handle_id != current_batch_handle + || colored != current_batch_colored + { + if let Some(gpu_image) = gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) { - Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), - Some(other) => other, - } - }); - }; - sort(&mut white_sprites); - sort(&mut colored_sprites); - - for (colored, sprites) in [(false, white_sprites), (true, colored_sprites)] { - // Impossible starting values that will be replaced on the first iteration - let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); - let mut current_image_size = Vec2::ZERO; - let mut z_order = 0.0; - - // Vertex buffer indices - let mut index_start = 0; - let mut index_end = 0; - - for extracted_sprite in sprites { - if extracted_sprite.image_handle_id != current_batch_handle { - if let Some(gpu_image) = - gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) - { - current_image_size = gpu_image.size; - current_batch_handle = extracted_sprite.image_handle_id; - if index_start != index_end { - commands.spawn().insert(SpriteBatch { - range: index_start..index_end, - image_handle_id: current_batch_handle, - colored, - z_order, - }); - index_start = index_end; - } - } else { - // We skip loading images - continue; + current_image_size = gpu_image.size; + current_batch_handle = extracted_sprite.image_handle_id; + current_batch_colored = colored; + let [start, end] = match colored { + true => [&mut colored_start, &mut colored_end], + false => [&mut white_start, &mut white_end], + }; + if *start != *end { + commands.spawn().insert(SpriteBatch { + range: *start..*end, + image_handle_id: current_batch_handle, + colored: current_batch_colored, + z_order, + }); + *start = *end; } + } else { + // We skip loading images + continue; } - // Calculate vertex data for this item - let mut uvs = QUAD_UVS; - if extracted_sprite.flip_x { - uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; - } - if extracted_sprite.flip_y { - uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; - } - - // By default, the size of the quad is the size of the texture - let mut quad_size = current_image_size; + } + // Calculate vertex data for this item + let mut uvs = QUAD_UVS; + if extracted_sprite.flip_x { + uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; + } + if extracted_sprite.flip_y { + uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; + } - // If a rect is specified, adjust UVs and the size of the quad - if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - for uv in &mut uvs { - *uv = (rect.min + *uv * rect_size) / current_image_size; - } - quad_size = rect_size; - } + // By default, the size of the quad is the size of the texture + let mut quad_size = current_image_size; - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; + // If a rect is specified, adjust UVs and the size of the quad + if let Some(rect) = extracted_sprite.rect { + let rect_size = rect.size(); + for uv in &mut uvs { + *uv = (rect.min + *uv * rect_size) / current_image_size; } + quad_size = rect_size; + } - // Apply size and global transform - let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { - extracted_sprite - .transform - .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) - .into() - }); + // Override the size if a custom one is specified + if let Some(custom_size) = extracted_sprite.custom_size { + quad_size = custom_size; + } - if colored { - for i in QUAD_INDICES { - sprite_meta.colored_vertices.push(ColoredSpriteVertex { - position: positions[i], - uv: uvs[i].into(), - color: extracted_sprite.color.as_linear_rgba_f32(), - }); - } - } else { - for i in QUAD_INDICES { - sprite_meta.vertices.push(SpriteVertex { - position: positions[i], - uv: uvs[i].into(), - }); - } + // Apply size and global transform + let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { + extracted_sprite + .transform + .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) + .into() + }); + if colored { + for i in QUAD_INDICES { + sprite_meta.colored_vertices.push(ColoredSpriteVertex { + position: positions[i], + uv: uvs[i].into(), + color: extracted_sprite.color.as_linear_rgba_f32(), + }); } - index_end += QUAD_INDICES.len() as u32; - z_order = extracted_sprite.transform.translation.z; - } - // if start != end, there is one last batch to process - if index_start != index_end { - commands.spawn().insert(SpriteBatch { - range: index_start..index_end, - image_handle_id: current_batch_handle, - colored, - z_order, - }); + colored_end += QUAD_INDICES.len() as u32; + } else { + for i in QUAD_INDICES { + sprite_meta.vertices.push(SpriteVertex { + position: positions[i], + uv: uvs[i].into(), + }); + } + white_end += QUAD_INDICES.len() as u32; } + z_order = extracted_sprite.transform.translation.z; } + // if start != end, there is one last batch to process + if white_start != white_end { + commands.spawn().insert(SpriteBatch { + range: white_start..white_end, + image_handle_id: current_batch_handle, + colored: false, + z_order, + }); + } + if colored_start != colored_end { + commands.spawn().insert(SpriteBatch { + range: colored_start..colored_end, + image_handle_id: current_batch_handle, + colored: true, + z_order, + }); + } + sprite_meta .vertices .write_buffer(&render_device, &render_queue); From 7941f7391bd9bf3ee26da56019532fd237f08d20 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Fri, 8 Jul 2022 11:23:04 +0200 Subject: [PATCH 4/9] Added documentation --- crates/bevy_sprite/src/render/mod.rs | 32 +++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 772e0c0e81703..1b64853b1a770 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -274,24 +274,35 @@ pub fn extract_sprites( } } +/// Single sprite vertex data #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] struct SpriteVertex { + /// 3D vertex position pub position: [f32; 3], + /// vertex UV coordinates pub uv: [f32; 2], } +/// Single sprite colored vertex data #[repr(C)] #[derive(Copy, Clone, Pod, Zeroable)] struct ColoredSpriteVertex { + /// 3D vertex position pub position: [f32; 3], + /// Vertex UV coordinates pub uv: [f32; 2], + /// Vertex color as linear RGBA pub color: [f32; 4], } +/// Sprite rendering information, stored as a resource pub struct SpriteMeta { + /// Non colored vertices buffer vertices: BufferVec, + /// Colored vertices buffer colored_vertices: BufferVec, + /// The associated pipeline's bind group view_bind_group: Option, } @@ -305,27 +316,46 @@ impl Default for SpriteMeta { } } -const QUAD_INDICES: [usize; 6] = [0, 2, 3, 0, 1, 2]; +/// Sprite quad triangles vertex indices +const QUAD_INDICES: [usize; 6] = [ + 0, 2, 3, // Bottom left triangle + 0, 1, 2, // Top right triangle +]; +/// Base Quad vertices 2D positions const QUAD_VERTEX_POSITIONS: [Vec2; 4] = [ + // Top left Vec2::new(-0.5, -0.5), + // Top right Vec2::new(0.5, -0.5), + // Bottom right Vec2::new(0.5, 0.5), + // Bottom left Vec2::new(-0.5, 0.5), ]; +/// Base Quad vertices UV coordinates const QUAD_UVS: [Vec2; 4] = [ + // Top left Vec2::new(0., 1.), + // Top right Vec2::new(1., 1.), + // Bottom right Vec2::new(1., 0.), + // Bottom left Vec2::new(0., 0.), ]; +/// Component defining a batch of sprites for the render world #[derive(Component)] pub struct SpriteBatch { + /// The [`SpriteMeta`] vertex data indices range: Range, + /// The texture handle id image_handle_id: HandleId, + /// Defines if the `range` targets [`SpriteMeta::vertices`] or [`SpriteMeta::colored_vertices`] colored: bool, + /// Sort key of the batch z_order: f32, } From 15924b92bf887b6ce6b06aff7608f2530eb2b912 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Fri, 8 Jul 2022 13:42:48 +0200 Subject: [PATCH 5/9] small code improvement --- crates/bevy_sprite/src/render/mod.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 1b64853b1a770..5454fd0b898a4 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -476,19 +476,15 @@ pub fn prepare_sprites( z_order = extracted_sprite.transform.translation.z; } // if start != end, there is one last batch to process - if white_start != white_end { + let [start, end] = match current_batch_colored { + true => [&mut colored_start, &mut colored_end], + false => [&mut white_start, &mut white_end], + }; + if *start != *end { commands.spawn().insert(SpriteBatch { - range: white_start..white_end, + range: *start..*end, image_handle_id: current_batch_handle, - colored: false, - z_order, - }); - } - if colored_start != colored_end { - commands.spawn().insert(SpriteBatch { - range: colored_start..colored_end, - image_handle_id: current_batch_handle, - colored: true, + colored: current_batch_colored, z_order, }); } From 06680cf4aaf0bf1915b18917f93b1eedb9e70000 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Sat, 9 Jul 2022 12:53:13 +0200 Subject: [PATCH 6/9] rebased on main --- crates/bevy_sprite/src/render/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 5454fd0b898a4..58434234a8277 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -232,8 +232,6 @@ pub fn extract_sprites( )>, >, ) { - let mut extracted_sprites = render_world.resource_mut::(); - extracted_sprites.sprites.clear(); for (visibility, sprite, transform, handle) in sprite_query.iter() { if !visibility.is_visible { continue; From db279a06e6c7b4922dfec35b6b2ee68e4780b628 Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Sat, 9 Jul 2022 13:17:10 +0200 Subject: [PATCH 7/9] cleaner code and improvement performances --- crates/bevy_sprite/src/render/mod.rs | 235 ++++++++++++++------------- 1 file changed, 124 insertions(+), 111 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 58434234a8277..20afef59d7561 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -171,8 +171,11 @@ impl SpecializedRenderPipeline for SpritePipeline { } } +/// Render world sprite data pub struct ExtractedSprite { + /// Sprite global translation, rotation and scale pub transform: GlobalTransform, + /// Sprite color tint pub color: Color, /// Select an area of the texture pub rect: Option, @@ -181,14 +184,21 @@ pub struct ExtractedSprite { /// Handle to the `Image` of this sprite /// PERF: storing a `HandleId` instead of `Handle` enables some optimizations (`ExtractedSprite` becomes `Copy` and doesn't need to be dropped) pub image_handle_id: HandleId, + /// Is the texture flipped horizontally pub flip_x: bool, + /// Is the texture flipped vertically pub flip_y: bool, + /// Sprite anchor offset pub anchor: Vec2, } +/// Container for all [`ExtractedSprite`] #[derive(Default)] pub struct ExtractedSprites { + /// White extracted sprites pub sprites: Vec, + /// Color tinted extracted sprites + pub colored_sprites: Vec, } #[derive(Default)] @@ -232,12 +242,14 @@ pub fn extract_sprites( )>, >, ) { + extracted_sprites.sprites.clear(); + extracted_sprites.colored_sprites.clear(); for (visibility, sprite, transform, handle) in sprite_query.iter() { if !visibility.is_visible { continue; } // 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.alloc().init(ExtractedSprite { + let sprite = ExtractedSprite { color: sprite.color, transform: *transform, // Use the full texture @@ -248,7 +260,12 @@ pub fn extract_sprites( flip_y: sprite.flip_y, image_handle_id: handle.id, anchor: sprite.anchor.as_vec(), - }); + }; + if sprite.color == Color::WHITE { + extracted_sprites.sprites.alloc().init(sprite); + } else { + extracted_sprites.colored_sprites.alloc().init(sprite); + } } for (visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { if !visibility.is_visible { @@ -256,7 +273,7 @@ pub fn extract_sprites( } if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { let rect = Some(texture_atlas.textures[atlas_sprite.index as usize]); - extracted_sprites.sprites.alloc().init(ExtractedSprite { + let sprite = ExtractedSprite { color: atlas_sprite.color, transform: *transform, // Select the area in the texture atlas @@ -267,7 +284,12 @@ pub fn extract_sprites( flip_y: atlas_sprite.flip_y, image_handle_id: texture_atlas.texture.id, anchor: atlas_sprite.anchor.as_vec(), - }); + }; + if sprite.color == Color::WHITE { + extracted_sprites.sprites.alloc().init(sprite); + } else { + extracted_sprites.colored_sprites.alloc().init(sprite); + } } } } @@ -370,123 +392,114 @@ pub fn prepare_sprites( sprite_meta.colored_vertices.clear(); // Sort sprites by z for correct transparency and then by handle to improve batching - extracted_sprites.sprites.sort_unstable_by(|a, b| { - match a - .transform - .translation - .z - .partial_cmp(&b.transform.translation.z) - { - Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), - Some(other) => other, - } - }); - - // Impossible starting values that will be replaced on the first iteration - let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); - let mut current_image_size = Vec2::ZERO; - let mut current_batch_colored = false; - let mut z_order = 0.0; - - // Vertex buffer indices - let [mut white_start, mut white_end] = [0, 0]; - let [mut colored_start, mut colored_end] = [0, 0]; - - for extracted_sprite in extracted_sprites.sprites.drain(..) { - let colored = extracted_sprite.color != Color::WHITE; - if extracted_sprite.image_handle_id != current_batch_handle - || colored != current_batch_colored - { - if let Some(gpu_image) = gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) - { - current_image_size = gpu_image.size; - current_batch_handle = extracted_sprite.image_handle_id; - current_batch_colored = colored; - let [start, end] = match colored { - true => [&mut colored_start, &mut colored_end], - false => [&mut white_start, &mut white_end], - }; - if *start != *end { - commands.spawn().insert(SpriteBatch { - range: *start..*end, - image_handle_id: current_batch_handle, - colored: current_batch_colored, - z_order, - }); - *start = *end; + let sort = |a: &ExtractedSprite, b: &ExtractedSprite| match a + .transform + .translation + .z + .partial_cmp(&b.transform.translation.z) + { + Some(Ordering::Equal) | None => a.image_handle_id.cmp(&b.image_handle_id), + Some(other) => other, + }; + extracted_sprites.sprites.sort_unstable_by(sort); + extracted_sprites.colored_sprites.sort_unstable_by(sort); + + for (colored, sprites) in [ + (false, &extracted_sprites.sprites), + (true, &extracted_sprites.colored_sprites), + ] { + // Impossible starting values that will be replaced on the first iteration + let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); + let mut current_image_size = Vec2::ZERO; + let mut z_order = 0.0; + + let mut start = 0; + let mut end = 0; + + for extracted_sprite in sprites { + if extracted_sprite.image_handle_id != current_batch_handle { + if let Some(gpu_image) = + gpu_images.get(&Handle::weak(extracted_sprite.image_handle_id)) + { + current_image_size = gpu_image.size; + current_batch_handle = extracted_sprite.image_handle_id; + if start != end { + commands.spawn().insert(SpriteBatch { + range: start..end, + image_handle_id: current_batch_handle, + colored, + z_order, + }); + start = end; + } + } else { + // We skip loading images + continue; } - } else { - // We skip loading images - continue; } - } - // Calculate vertex data for this item - let mut uvs = QUAD_UVS; - if extracted_sprite.flip_x { - uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; - } - if extracted_sprite.flip_y { - uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; - } + // Calculate vertex data for this item + let mut uvs = QUAD_UVS; + if extracted_sprite.flip_x { + uvs = [uvs[1], uvs[0], uvs[3], uvs[2]]; + } + if extracted_sprite.flip_y { + uvs = [uvs[3], uvs[2], uvs[1], uvs[0]]; + } - // By default, the size of the quad is the size of the texture - let mut quad_size = current_image_size; + // By default, the size of the quad is the size of the texture + let mut quad_size = current_image_size; - // If a rect is specified, adjust UVs and the size of the quad - if let Some(rect) = extracted_sprite.rect { - let rect_size = rect.size(); - for uv in &mut uvs { - *uv = (rect.min + *uv * rect_size) / current_image_size; + // If a rect is specified, adjust UVs and the size of the quad + if let Some(rect) = extracted_sprite.rect { + let rect_size = rect.size(); + for uv in &mut uvs { + *uv = (rect.min + *uv * rect_size) / current_image_size; + } + quad_size = rect_size; } - quad_size = rect_size; - } - // Override the size if a custom one is specified - if let Some(custom_size) = extracted_sprite.custom_size { - quad_size = custom_size; - } - - // Apply size and global transform - let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { - extracted_sprite - .transform - .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) - .into() - }); - if colored { - for i in QUAD_INDICES { - sprite_meta.colored_vertices.push(ColoredSpriteVertex { - position: positions[i], - uv: uvs[i].into(), - color: extracted_sprite.color.as_linear_rgba_f32(), - }); + // Override the size if a custom one is specified + if let Some(custom_size) = extracted_sprite.custom_size { + quad_size = custom_size; } - colored_end += QUAD_INDICES.len() as u32; - } else { - for i in QUAD_INDICES { - sprite_meta.vertices.push(SpriteVertex { - position: positions[i], - uv: uvs[i].into(), - }); + + // Apply size and global transform + let positions = QUAD_VERTEX_POSITIONS.map(|quad_pos| { + extracted_sprite + .transform + .mul_vec3(((quad_pos - extracted_sprite.anchor) * quad_size).extend(0.)) + .into() + }); + if colored { + for i in QUAD_INDICES { + sprite_meta.colored_vertices.push(ColoredSpriteVertex { + position: positions[i], + uv: uvs[i].into(), + color: extracted_sprite.color.as_linear_rgba_f32(), + }); + } + } else { + for i in QUAD_INDICES { + sprite_meta.vertices.push(SpriteVertex { + position: positions[i], + uv: uvs[i].into(), + }); + } } - white_end += QUAD_INDICES.len() as u32; + end += QUAD_INDICES.len() as u32; + z_order = extracted_sprite.transform.translation.z; } - z_order = extracted_sprite.transform.translation.z; - } - // if start != end, there is one last batch to process - let [start, end] = match current_batch_colored { - true => [&mut colored_start, &mut colored_end], - false => [&mut white_start, &mut white_end], - }; - if *start != *end { - commands.spawn().insert(SpriteBatch { - range: *start..*end, - image_handle_id: current_batch_handle, - colored: current_batch_colored, - z_order, - }); - } + // if start != end, there is one last batch to process + if start != end { + commands.spawn().insert(SpriteBatch { + range: start..end, + image_handle_id: current_batch_handle, + colored, + z_order, + }); + } + } sprite_meta .vertices .write_buffer(&render_device, &render_queue); From 29d076d5afa981b35fc73597802d81ec40bcc6fb Mon Sep 17 00:00:00 2001 From: Felix de Maneville Date: Sat, 9 Jul 2022 13:27:32 +0200 Subject: [PATCH 8/9] const handle value --- crates/bevy_sprite/src/render/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 20afef59d7561..d83b49681e5b3 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -387,6 +387,9 @@ pub fn prepare_sprites( gpu_images: Res>, mut extracted_sprites: ResMut, ) { + // Impossible handle id value + const NULL_HANDLE: HandleId = HandleId::Id(Uuid::nil(), u64::MAX); + // Clear the vertex buffers sprite_meta.vertices.clear(); sprite_meta.colored_vertices.clear(); @@ -409,10 +412,11 @@ pub fn prepare_sprites( (true, &extracted_sprites.colored_sprites), ] { // Impossible starting values that will be replaced on the first iteration - let mut current_batch_handle = HandleId::Id(Uuid::nil(), u64::MAX); + let mut current_batch_handle = NULL_HANDLE; let mut current_image_size = Vec2::ZERO; let mut z_order = 0.0; + // Vertex buffer indices let mut start = 0; let mut end = 0; @@ -490,7 +494,7 @@ pub fn prepare_sprites( z_order = extracted_sprite.transform.translation.z; } - // if start != end, there is one last batch to process + // if start != end, there is one remaining batch to process if start != end { commands.spawn().insert(SpriteBatch { range: start..end, From fdf09f37fd9a40a47b0e9996f19a7112630607e4 Mon Sep 17 00:00:00 2001 From: felix de Maneville Date: Sat, 9 Jul 2022 18:02:39 +0200 Subject: [PATCH 9/9] Renaming --- crates/bevy_sprite/src/render/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index d83b49681e5b3..90002a235fc0a 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -437,7 +437,7 @@ pub fn prepare_sprites( start = end; } } else { - // We skip loading images + // Skip this item if the texture is not ready continue; } }