Extract UI nodes into a Vec (#17618)

# Objective

Extract UI nodes into a `Vec` instead of an `EntityHashMap`.

## Solution

Extract UI nodes into a `Vec` instead of an `EntityHashMap`.
Store an index into the `Vec` in each transparent UI item.
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_buttons --release --features trace_tracy
```

`extract_uinode_background_colors`
<img width="448" alt="extract_uinode_background_colors"
src="https://github.com/user-attachments/assets/09c0f434-ab4f-4c0f-956a-cf31e9060061"
/>

`extract_uinode_images`
<img width="587" alt="extract_uinode_images"
src="https://github.com/user-attachments/assets/43246d7f-d22c-46d0-9a07-7e13d5379f56"
/>

`prepare_uinodes`
<img width="441" alt="prepare_uinodes_vec"
src="https://github.com/user-attachments/assets/cc9a7eac-60e9-42fa-8093-bce833a1c153"
/>
This commit is contained in:
ickshonpe 2025-01-30 23:25:07 +00:00 committed by GitHub
parent 59697f9ccc
commit ba1b0092e5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 207 additions and 210 deletions

View File

@ -12,7 +12,6 @@ use bevy_color::{Alpha, ColorToComponents, LinearRgba};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::{ use bevy_ecs::{
prelude::Component, prelude::Component,
storage::SparseSet,
system::{ system::{
lifetimeless::{Read, SRes}, lifetimeless::{Read, SRes},
*, *,
@ -225,12 +224,13 @@ pub struct ExtractedBoxShadow {
pub blur_radius: f32, pub blur_radius: f32,
pub size: Vec2, pub size: Vec2,
pub main_entity: MainEntity, pub main_entity: MainEntity,
pub render_entity: Entity,
} }
/// List of extracted shadows to be sorted and queued for rendering /// List of extracted shadows to be sorted and queued for rendering
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct ExtractedBoxShadows { pub struct ExtractedBoxShadows {
pub box_shadows: SparseSet<Entity, ExtractedBoxShadow>, pub box_shadows: Vec<ExtractedBoxShadow>,
} }
pub fn extract_shadows( pub fn extract_shadows(
@ -311,22 +311,19 @@ pub fn extract_shadows(
bottom_right: uinode.border_radius.bottom_right * spread_ratio, bottom_right: uinode.border_radius.bottom_right * spread_ratio,
}; };
extracted_box_shadows.box_shadows.insert( extracted_box_shadows.box_shadows.push(ExtractedBoxShadow {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedBoxShadow { stack_index: uinode.stack_index,
stack_index: uinode.stack_index, transform: transform.compute_matrix() * Mat4::from_translation(offset.extend(0.)),
transform: transform.compute_matrix() color: drop_shadow.color.into(),
* Mat4::from_translation(offset.extend(0.)), bounds: shadow_size + 6. * blur_radius,
color: drop_shadow.color.into(), clip: clip.map(|clip| clip.clip),
bounds: shadow_size + 6. * blur_radius, extracted_camera_entity,
clip: clip.map(|clip| clip.clip), radius,
extracted_camera_entity, blur_radius,
radius, size: shadow_size,
blur_radius, main_entity: entity.into(),
size: shadow_size, });
main_entity: entity.into(),
},
);
} }
} }
} }
@ -346,7 +343,8 @@ pub fn queue_shadows(
draw_functions: Res<DrawFunctions<TransparentUi>>, draw_functions: Res<DrawFunctions<TransparentUi>>,
) { ) {
let draw_function = draw_functions.read().id::<DrawBoxShadows>(); let draw_function = draw_functions.read().id::<DrawBoxShadows>();
for (entity, extracted_shadow) in extracted_box_shadows.box_shadows.iter() { for (index, extracted_shadow) in extracted_box_shadows.box_shadows.iter().enumerate() {
let entity = extracted_shadow.render_entity;
let Ok((default_camera_view, shadow_samples)) = let Ok((default_camera_view, shadow_samples)) =
render_views.get_mut(extracted_shadow.extracted_camera_entity) render_views.get_mut(extracted_shadow.extracted_camera_entity)
else { else {
@ -374,13 +372,14 @@ pub fn queue_shadows(
transparent_phase.add(TransparentUi { transparent_phase.add(TransparentUi {
draw_function, draw_function,
pipeline, pipeline,
entity: (*entity, extracted_shadow.main_entity), entity: (entity, extracted_shadow.main_entity),
sort_key: ( sort_key: (
FloatOrd(extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW), FloatOrd(extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW),
entity.index(), entity.index(),
), ),
batch_range: 0..0, batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None, extra_index: PhaseItemExtraIndex::None,
index,
indexed: true, indexed: true,
}); });
} }
@ -417,11 +416,13 @@ pub fn prepare_shadows(
let mut indices_index = 0; let mut indices_index = 0;
for ui_phase in phases.values_mut() { for ui_phase in phases.values_mut() {
let mut item_index = 0; for item_index in 0..ui_phase.items.len() {
while item_index < ui_phase.items.len() {
let item = &mut ui_phase.items[item_index]; let item = &mut ui_phase.items[item_index];
if let Some(box_shadow) = extracted_shadows.box_shadows.get(item.entity()) { if let Some(box_shadow) = extracted_shadows
.box_shadows
.get(item.index)
.filter(|n| item.entity() == n.render_entity)
{
let rect_size = box_shadow.bounds.extend(1.0); let rect_size = box_shadow.bounds.extend(1.0);
// Specify the corners of the node // Specify the corners of the node
@ -532,7 +533,6 @@ pub fn prepare_shadows(
*ui_phase.items[item_index].batch_range_mut() = *ui_phase.items[item_index].batch_range_mut() =
item_index as u32..item_index as u32 + 1; item_index as u32..item_index as u32 + 1;
} }
item_index += 1;
} }
} }
ui_meta.vertices.write_buffer(&render_device, &render_queue); ui_meta.vertices.write_buffer(&render_device, &render_queue);

View File

@ -85,34 +85,30 @@ pub fn extract_debug_overlay(
}; };
// Extract a border box to display an outline for every UI Node in the layout // Extract a border box to display an outline for every UI Node in the layout
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiNode {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiNode { // Add a large number to the UI node's stack index so that the overlay is always drawn on top
// Add a large number to the UI node's stack index so that the overlay is always drawn on top stack_index: uinode.stack_index + u32::MAX / 2,
stack_index: uinode.stack_index + u32::MAX / 2, color: Hsla::sequential_dispersed(entity.index()).into(),
color: Hsla::sequential_dispersed(entity.index()).into(), rect: Rect {
rect: Rect { min: Vec2::ZERO,
min: Vec2::ZERO, max: uinode.size,
max: uinode.size,
},
clip: maybe_clip
.filter(|_| !debug_options.show_clipped)
.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: BorderRect::all(
debug_options.line_width / uinode.inverse_scale_factor(),
),
border_radius: uinode.border_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
}, },
); clip: maybe_clip
.filter(|_| !debug_options.show_clipped)
.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
border_radius: uinode.border_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
});
} }
} }

View File

@ -18,7 +18,6 @@ use bevy_color::{Alpha, ColorToComponents, LinearRgba};
use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d}; use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d}; use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d}; use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
use bevy_ecs::entity::hash_map::EntityHashMap;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_ecs::system::SystemParam; use bevy_ecs::system::SystemParam;
use bevy_image::prelude::*; use bevy_image::prelude::*;
@ -203,6 +202,7 @@ pub struct ExtractedUiNode {
pub extracted_camera_entity: Entity, pub extracted_camera_entity: Entity,
pub item: ExtractedUiItem, pub item: ExtractedUiItem,
pub main_entity: MainEntity, pub main_entity: MainEntity,
pub render_entity: Entity,
} }
/// The type of UI node. /// The type of UI node.
@ -241,7 +241,7 @@ pub struct ExtractedGlyph {
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct ExtractedUiNodes { pub struct ExtractedUiNodes {
pub uinodes: EntityHashMap<ExtractedUiNode>, pub uinodes: Vec<ExtractedUiNode>,
pub glyphs: Vec<ExtractedGlyph>, pub glyphs: Vec<ExtractedGlyph>,
} }
@ -358,30 +358,28 @@ pub fn extract_uinode_background_colors(
continue; continue;
}; };
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiNode {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiNode { stack_index: uinode.stack_index,
stack_index: uinode.stack_index, color: background_color.0.into(),
color: background_color.0.into(), rect: Rect {
rect: Rect { min: Vec2::ZERO,
min: Vec2::ZERO, max: uinode.size,
max: uinode.size,
},
clip: clip.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: uinode.border(),
border_radius: uinode.border_radius(),
node_type: NodeType::Rect,
},
main_entity: entity.into(),
}, },
); clip: clip.map(|clip| clip.clip),
image: AssetId::default(),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: uinode.border(),
border_radius: uinode.border_radius(),
node_type: NodeType::Rect,
},
main_entity: entity.into(),
});
} }
} }
@ -447,27 +445,25 @@ pub fn extract_uinode_images(
None None
}; };
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiNode {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiNode { stack_index: uinode.stack_index,
stack_index: uinode.stack_index, color: image.color.into(),
color: image.color.into(), rect,
rect, clip: clip.map(|clip| clip.clip),
clip: clip.map(|clip| clip.clip), image: image.image.id(),
image: image.image.id(), extracted_camera_entity,
extracted_camera_entity, item: ExtractedUiItem::Node {
item: ExtractedUiItem::Node { atlas_scaling,
atlas_scaling, transform: transform.compute_matrix(),
transform: transform.compute_matrix(), flip_x: image.flip_x,
flip_x: image.flip_x, flip_y: image.flip_y,
flip_y: image.flip_y, border: uinode.border,
border: uinode.border, border_radius: uinode.border_radius,
border_radius: uinode.border_radius, node_type: NodeType::Rect,
node_type: NodeType::Rect,
},
main_entity: entity.into(),
}, },
); main_entity: entity.into(),
});
} }
} }
@ -515,30 +511,28 @@ pub fn extract_uinode_borders(
if computed_node.border() != BorderRect::ZERO { if computed_node.border() != BorderRect::ZERO {
if let Some(border_color) = maybe_border_color.filter(|bc| !bc.0.is_fully_transparent()) if let Some(border_color) = maybe_border_color.filter(|bc| !bc.0.is_fully_transparent())
{ {
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiNode {
commands.spawn(TemporaryRenderEntity).id(), stack_index: computed_node.stack_index,
ExtractedUiNode { color: border_color.0.into(),
stack_index: computed_node.stack_index, rect: Rect {
color: border_color.0.into(), max: computed_node.size(),
rect: Rect { ..Default::default()
max: computed_node.size(),
..Default::default()
},
image,
clip: maybe_clip.map(|clip| clip.clip),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: global_transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: computed_node.border(),
border_radius: computed_node.border_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
}, },
); image,
clip: maybe_clip.map(|clip| clip.clip),
extracted_camera_entity,
item: ExtractedUiItem::Node {
atlas_scaling: None,
transform: global_transform.compute_matrix(),
flip_x: false,
flip_y: false,
border: computed_node.border(),
border_radius: computed_node.border_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
render_entity: commands.spawn(TemporaryRenderEntity).id(),
});
} }
} }
@ -549,30 +543,28 @@ pub fn extract_uinode_borders(
if let Some(outline) = maybe_outline.filter(|outline| !outline.color.is_fully_transparent()) if let Some(outline) = maybe_outline.filter(|outline| !outline.color.is_fully_transparent())
{ {
let outline_size = computed_node.outlined_node_size(); let outline_size = computed_node.outlined_node_size();
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiNode {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiNode { stack_index: computed_node.stack_index,
stack_index: computed_node.stack_index, color: outline.color.into(),
color: outline.color.into(), rect: Rect {
rect: Rect { max: outline_size,
max: outline_size, ..Default::default()
..Default::default()
},
image,
clip: maybe_clip.map(|clip| clip.clip),
extracted_camera_entity,
item: ExtractedUiItem::Node {
transform: global_transform.compute_matrix(),
atlas_scaling: None,
flip_x: false,
flip_y: false,
border: BorderRect::all(computed_node.outline_width()),
border_radius: computed_node.outline_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
}, },
); image,
clip: maybe_clip.map(|clip| clip.clip),
extracted_camera_entity,
item: ExtractedUiItem::Node {
transform: global_transform.compute_matrix(),
atlas_scaling: None,
flip_x: false,
flip_y: false,
border: BorderRect::all(computed_node.outline_width()),
border_radius: computed_node.outline_radius(),
node_type: NodeType::Border,
},
main_entity: entity.into(),
});
} }
} }
} }
@ -788,21 +780,17 @@ pub fn extract_text_sections(
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| { if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
info.span_index != current_span || info.atlas_info.texture != atlas_info.texture info.span_index != current_span || info.atlas_info.texture != atlas_info.texture
}) { }) {
let id = commands.spawn(TemporaryRenderEntity).id(); extracted_uinodes.uinodes.push(ExtractedUiNode {
render_entity: commands.spawn(TemporaryRenderEntity).id(),
extracted_uinodes.uinodes.insert( stack_index: uinode.stack_index,
id, color,
ExtractedUiNode { image: atlas_info.texture.id(),
stack_index: uinode.stack_index, clip: clip.map(|clip| clip.clip),
color, extracted_camera_entity,
image: atlas_info.texture.id(), rect,
clip: clip.map(|clip| clip.clip), item: ExtractedUiItem::Glyphs { range: start..end },
extracted_camera_entity, main_entity: entity.into(),
rect, });
item: ExtractedUiItem::Glyphs { range: start..end },
main_entity: entity.into(),
},
);
start = end; start = end;
} }
@ -885,7 +873,8 @@ pub fn queue_uinodes(
draw_functions: Res<DrawFunctions<TransparentUi>>, draw_functions: Res<DrawFunctions<TransparentUi>>,
) { ) {
let draw_function = draw_functions.read().id::<DrawUi>(); let draw_function = draw_functions.read().id::<DrawUi>();
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { for (index, extracted_uinode) in extracted_uinodes.uinodes.iter().enumerate() {
let entity = extracted_uinode.render_entity;
let Ok((default_camera_view, ui_anti_alias)) = let Ok((default_camera_view, ui_anti_alias)) =
render_views.get_mut(extracted_uinode.extracted_camera_entity) render_views.get_mut(extracted_uinode.extracted_camera_entity)
else { else {
@ -912,11 +901,12 @@ pub fn queue_uinodes(
transparent_phase.add(TransparentUi { transparent_phase.add(TransparentUi {
draw_function, draw_function,
pipeline, pipeline,
entity: (*entity, extracted_uinode.main_entity), entity: (entity, extracted_uinode.main_entity),
sort_key: ( sort_key: (
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE), FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE),
entity.index(), entity.index(),
), ),
index,
// batch_range will be calculated in prepare_uinodes // batch_range will be calculated in prepare_uinodes
batch_range: 0..0, batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None, extra_index: PhaseItemExtraIndex::None,
@ -978,7 +968,11 @@ pub fn prepare_uinodes(
for item_index in 0..ui_phase.items.len() { for item_index in 0..ui_phase.items.len() {
let item = &mut ui_phase.items[item_index]; let item = &mut ui_phase.items[item_index];
if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(&item.entity()) { if let Some(extracted_uinode) = extracted_uinodes
.uinodes
.get(item.index)
.filter(|n| item.entity() == n.render_entity)
{
let mut existing_batch = batches.last_mut(); let mut existing_batch = batches.last_mut();
if batch_image_handle == AssetId::invalid() if batch_image_handle == AssetId::invalid()

View File

@ -112,6 +112,7 @@ pub struct TransparentUi {
pub draw_function: DrawFunctionId, pub draw_function: DrawFunctionId,
pub batch_range: Range<u32>, pub batch_range: Range<u32>,
pub extra_index: PhaseItemExtraIndex, pub extra_index: PhaseItemExtraIndex,
pub index: usize,
pub indexed: bool, pub indexed: bool,
} }

View File

@ -5,7 +5,6 @@ use bevy_asset::*;
use bevy_ecs::{ use bevy_ecs::{
prelude::Component, prelude::Component,
query::ROQueryItem, query::ROQueryItem,
storage::SparseSet,
system::{ system::{
lifetimeless::{Read, SRes}, lifetimeless::{Read, SRes},
*, *,
@ -347,11 +346,12 @@ pub struct ExtractedUiMaterialNode<M: UiMaterial> {
// Nodes with ambiguous camera will be ignored. // Nodes with ambiguous camera will be ignored.
pub extracted_camera_entity: Entity, pub extracted_camera_entity: Entity,
pub main_entity: MainEntity, pub main_entity: MainEntity,
render_entity: Entity,
} }
#[derive(Resource)] #[derive(Resource)]
pub struct ExtractedUiMaterialNodes<M: UiMaterial> { pub struct ExtractedUiMaterialNodes<M: UiMaterial> {
pub uinodes: SparseSet<Entity, ExtractedUiMaterialNode<M>>, pub uinodes: Vec<ExtractedUiMaterialNode<M>>,
} }
impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> { impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> {
@ -398,23 +398,21 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
continue; continue;
}; };
extracted_uinodes.uinodes.insert( extracted_uinodes.uinodes.push(ExtractedUiMaterialNode {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiMaterialNode { stack_index: computed_node.stack_index,
stack_index: computed_node.stack_index, transform: transform.compute_matrix(),
transform: transform.compute_matrix(), material: handle.id(),
material: handle.id(), rect: Rect {
rect: Rect { min: Vec2::ZERO,
min: Vec2::ZERO, max: computed_node.size(),
max: computed_node.size(),
},
border: computed_node.border(),
border_radius: computed_node.border_radius(),
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
main_entity: entity.into(),
}, },
); border: computed_node.border(),
border_radius: computed_node.border_radius(),
clip: clip.map(|clip| clip.clip),
extracted_camera_entity,
main_entity: entity.into(),
});
} }
} }
@ -450,7 +448,11 @@ pub fn prepare_uimaterial_nodes<M: UiMaterial>(
for item_index in 0..ui_phase.items.len() { for item_index in 0..ui_phase.items.len() {
let item = &mut ui_phase.items[item_index]; let item = &mut ui_phase.items[item_index];
if let Some(extracted_uinode) = extracted_uinodes.uinodes.get(item.entity()) { if let Some(extracted_uinode) = extracted_uinodes
.uinodes
.get(item.index)
.filter(|n| item.entity() == n.render_entity)
{
let mut existing_batch = batches let mut existing_batch = batches
.last_mut() .last_mut()
.filter(|_| batch_shader_handle == extracted_uinode.material); .filter(|_| batch_shader_handle == extracted_uinode.material);
@ -624,7 +626,7 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
{ {
let draw_function = draw_functions.read().id::<DrawUiMaterial<M>>(); let draw_function = draw_functions.read().id::<DrawUiMaterial<M>>();
for (entity, extracted_uinode) in extracted_uinodes.uinodes.iter() { for (index, extracted_uinode) in extracted_uinodes.uinodes.iter().enumerate() {
let Some(material) = render_materials.get(extracted_uinode.material) else { let Some(material) = render_materials.get(extracted_uinode.material) else {
continue; continue;
}; };
@ -660,13 +662,14 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
transparent_phase.add(TransparentUi { transparent_phase.add(TransparentUi {
draw_function, draw_function,
pipeline, pipeline,
entity: (*entity, extracted_uinode.main_entity), entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
sort_key: ( sort_key: (
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::MATERIAL), FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::MATERIAL),
entity.index(), extracted_uinode.render_entity.index(),
), ),
batch_range: 0..0, batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None, extra_index: PhaseItemExtraIndex::None,
index,
indexed: false, indexed: false,
}); });
} }

View File

@ -5,7 +5,6 @@ use bevy_asset::*;
use bevy_color::{Alpha, ColorToComponents, LinearRgba}; use bevy_color::{Alpha, ColorToComponents, LinearRgba};
use bevy_ecs::{ use bevy_ecs::{
prelude::Component, prelude::Component,
storage::SparseSet,
system::{ system::{
lifetimeless::{Read, SRes}, lifetimeless::{Read, SRes},
*, *,
@ -237,11 +236,12 @@ pub struct ExtractedUiTextureSlice {
pub flip_y: bool, pub flip_y: bool,
pub inverse_scale_factor: f32, pub inverse_scale_factor: f32,
pub main_entity: MainEntity, pub main_entity: MainEntity,
pub render_entity: Entity,
} }
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct ExtractedUiTextureSlices { pub struct ExtractedUiTextureSlices {
pub slices: SparseSet<Entity, ExtractedUiTextureSlice>, pub slices: Vec<ExtractedUiTextureSlice>,
} }
pub fn extract_ui_texture_slices( pub fn extract_ui_texture_slices(
@ -309,27 +309,25 @@ pub fn extract_ui_texture_slices(
} }
}; };
extracted_ui_slicers.slices.insert( extracted_ui_slicers.slices.push(ExtractedUiTextureSlice {
commands.spawn(TemporaryRenderEntity).id(), render_entity: commands.spawn(TemporaryRenderEntity).id(),
ExtractedUiTextureSlice { stack_index: uinode.stack_index,
stack_index: uinode.stack_index, transform: transform.compute_matrix(),
transform: transform.compute_matrix(), color: image.color.into(),
color: image.color.into(), rect: Rect {
rect: Rect { min: Vec2::ZERO,
min: Vec2::ZERO, max: uinode.size,
max: uinode.size,
},
clip: clip.map(|clip| clip.clip),
image: image.image.id(),
extracted_camera_entity,
image_scale_mode,
atlas_rect,
flip_x: image.flip_x,
flip_y: image.flip_y,
inverse_scale_factor: uinode.inverse_scale_factor,
main_entity: entity.into(),
}, },
); clip: clip.map(|clip| clip.clip),
image: image.image.id(),
extracted_camera_entity,
image_scale_mode,
atlas_rect,
flip_x: image.flip_x,
flip_y: image.flip_y,
inverse_scale_factor: uinode.inverse_scale_factor,
main_entity: entity.into(),
});
} }
} }
@ -348,7 +346,7 @@ pub fn queue_ui_slices(
draw_functions: Res<DrawFunctions<TransparentUi>>, draw_functions: Res<DrawFunctions<TransparentUi>>,
) { ) {
let draw_function = draw_functions.read().id::<DrawUiTextureSlices>(); let draw_function = draw_functions.read().id::<DrawUiTextureSlices>();
for (entity, extracted_slicer) in extracted_ui_slicers.slices.iter() { for (index, extracted_slicer) in extracted_ui_slicers.slices.iter().enumerate() {
let Ok(default_camera_view) = let Ok(default_camera_view) =
render_views.get_mut(extracted_slicer.extracted_camera_entity) render_views.get_mut(extracted_slicer.extracted_camera_entity)
else { else {
@ -373,13 +371,14 @@ pub fn queue_ui_slices(
transparent_phase.add(TransparentUi { transparent_phase.add(TransparentUi {
draw_function, draw_function,
pipeline, pipeline,
entity: (*entity, extracted_slicer.main_entity), entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
sort_key: ( sort_key: (
FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::TEXTURE_SLICE), FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::TEXTURE_SLICE),
entity.index(), extracted_slicer.render_entity.index(),
), ),
batch_range: 0..0, batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None, extra_index: PhaseItemExtraIndex::None,
index,
indexed: true, indexed: true,
}); });
} }
@ -434,7 +433,11 @@ pub fn prepare_ui_slices(
for item_index in 0..ui_phase.items.len() { for item_index in 0..ui_phase.items.len() {
let item = &mut ui_phase.items[item_index]; let item = &mut ui_phase.items[item_index];
if let Some(texture_slices) = extracted_slices.slices.get(item.entity()) { if let Some(texture_slices) = extracted_slices
.slices
.get(item.index)
.filter(|n| item.entity() == n.render_entity)
{
let mut existing_batch = batches.last_mut(); let mut existing_batch = batches.last_mut();
if batch_image_handle == AssetId::invalid() if batch_image_handle == AssetId::invalid()