diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a10bf343add996..dedf31b621ac0a 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);