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:
parent
59697f9ccc
commit
ba1b0092e5
@ -12,7 +12,6 @@ use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
storage::SparseSet,
|
||||
system::{
|
||||
lifetimeless::{Read, SRes},
|
||||
*,
|
||||
@ -225,12 +224,13 @@ pub struct ExtractedBoxShadow {
|
||||
pub blur_radius: f32,
|
||||
pub size: Vec2,
|
||||
pub main_entity: MainEntity,
|
||||
pub render_entity: Entity,
|
||||
}
|
||||
|
||||
/// List of extracted shadows to be sorted and queued for rendering
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedBoxShadows {
|
||||
pub box_shadows: SparseSet<Entity, ExtractedBoxShadow>,
|
||||
pub box_shadows: Vec<ExtractedBoxShadow>,
|
||||
}
|
||||
|
||||
pub fn extract_shadows(
|
||||
@ -311,12 +311,10 @@ pub fn extract_shadows(
|
||||
bottom_right: uinode.border_radius.bottom_right * spread_ratio,
|
||||
};
|
||||
|
||||
extracted_box_shadows.box_shadows.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedBoxShadow {
|
||||
extracted_box_shadows.box_shadows.push(ExtractedBoxShadow {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix()
|
||||
* Mat4::from_translation(offset.extend(0.)),
|
||||
transform: transform.compute_matrix() * Mat4::from_translation(offset.extend(0.)),
|
||||
color: drop_shadow.color.into(),
|
||||
bounds: shadow_size + 6. * blur_radius,
|
||||
clip: clip.map(|clip| clip.clip),
|
||||
@ -325,8 +323,7 @@ pub fn extract_shadows(
|
||||
blur_radius,
|
||||
size: shadow_size,
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,7 +343,8 @@ pub fn queue_shadows(
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
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)) =
|
||||
render_views.get_mut(extracted_shadow.extracted_camera_entity)
|
||||
else {
|
||||
@ -374,13 +372,14 @@ pub fn queue_shadows(
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function,
|
||||
pipeline,
|
||||
entity: (*entity, extracted_shadow.main_entity),
|
||||
entity: (entity, extracted_shadow.main_entity),
|
||||
sort_key: (
|
||||
FloatOrd(extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW),
|
||||
entity.index(),
|
||||
),
|
||||
batch_range: 0..0,
|
||||
extra_index: PhaseItemExtraIndex::None,
|
||||
index,
|
||||
indexed: true,
|
||||
});
|
||||
}
|
||||
@ -417,11 +416,13 @@ pub fn prepare_shadows(
|
||||
let mut indices_index = 0;
|
||||
|
||||
for ui_phase in phases.values_mut() {
|
||||
let mut item_index = 0;
|
||||
|
||||
while item_index < ui_phase.items.len() {
|
||||
for item_index in 0..ui_phase.items.len() {
|
||||
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);
|
||||
|
||||
// Specify the corners of the node
|
||||
@ -532,7 +533,6 @@ pub fn prepare_shadows(
|
||||
*ui_phase.items[item_index].batch_range_mut() =
|
||||
item_index as u32..item_index as u32 + 1;
|
||||
}
|
||||
item_index += 1;
|
||||
}
|
||||
}
|
||||
ui_meta.vertices.write_buffer(&render_device, &render_queue);
|
||||
|
||||
@ -85,9 +85,8 @@ pub fn extract_debug_overlay(
|
||||
};
|
||||
|
||||
// Extract a border box to display an outline for every UI Node in the layout
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
// 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,
|
||||
color: Hsla::sequential_dispersed(entity.index()).into(),
|
||||
@ -105,14 +104,11 @@ pub fn extract_debug_overlay(
|
||||
transform: transform.compute_matrix(),
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
border: BorderRect::all(
|
||||
debug_options.line_width / uinode.inverse_scale_factor(),
|
||||
),
|
||||
border: BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor()),
|
||||
border_radius: uinode.border_radius(),
|
||||
node_type: NodeType::Border,
|
||||
},
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||
use bevy_core_pipeline::core_2d::graph::{Core2d, Node2d};
|
||||
use bevy_core_pipeline::core_3d::graph::{Core3d, Node3d};
|
||||
use bevy_core_pipeline::{core_2d::Camera2d, core_3d::Camera3d};
|
||||
use bevy_ecs::entity::hash_map::EntityHashMap;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_ecs::system::SystemParam;
|
||||
use bevy_image::prelude::*;
|
||||
@ -203,6 +202,7 @@ pub struct ExtractedUiNode {
|
||||
pub extracted_camera_entity: Entity,
|
||||
pub item: ExtractedUiItem,
|
||||
pub main_entity: MainEntity,
|
||||
pub render_entity: Entity,
|
||||
}
|
||||
|
||||
/// The type of UI node.
|
||||
@ -241,7 +241,7 @@ pub struct ExtractedGlyph {
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedUiNodes {
|
||||
pub uinodes: EntityHashMap<ExtractedUiNode>,
|
||||
pub uinodes: Vec<ExtractedUiNode>,
|
||||
pub glyphs: Vec<ExtractedGlyph>,
|
||||
}
|
||||
|
||||
@ -358,9 +358,8 @@ pub fn extract_uinode_background_colors(
|
||||
continue;
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: uinode.stack_index,
|
||||
color: background_color.0.into(),
|
||||
rect: Rect {
|
||||
@ -380,8 +379,7 @@ pub fn extract_uinode_background_colors(
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -447,9 +445,8 @@ pub fn extract_uinode_images(
|
||||
None
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: uinode.stack_index,
|
||||
color: image.color.into(),
|
||||
rect,
|
||||
@ -466,8 +463,7 @@ pub fn extract_uinode_images(
|
||||
node_type: NodeType::Rect,
|
||||
},
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -515,9 +511,7 @@ pub fn extract_uinode_borders(
|
||||
if computed_node.border() != BorderRect::ZERO {
|
||||
if let Some(border_color) = maybe_border_color.filter(|bc| !bc.0.is_fully_transparent())
|
||||
{
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
stack_index: computed_node.stack_index,
|
||||
color: border_color.0.into(),
|
||||
rect: Rect {
|
||||
@ -537,8 +531,8 @@ pub fn extract_uinode_borders(
|
||||
node_type: NodeType::Border,
|
||||
},
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -549,9 +543,8 @@ pub fn extract_uinode_borders(
|
||||
if let Some(outline) = maybe_outline.filter(|outline| !outline.color.is_fully_transparent())
|
||||
{
|
||||
let outline_size = computed_node.outlined_node_size();
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: computed_node.stack_index,
|
||||
color: outline.color.into(),
|
||||
rect: Rect {
|
||||
@ -571,8 +564,7 @@ pub fn extract_uinode_borders(
|
||||
node_type: NodeType::Border,
|
||||
},
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -788,11 +780,8 @@ pub fn extract_text_sections(
|
||||
if text_layout_info.glyphs.get(i + 1).is_none_or(|info| {
|
||||
info.span_index != current_span || info.atlas_info.texture != atlas_info.texture
|
||||
}) {
|
||||
let id = commands.spawn(TemporaryRenderEntity).id();
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
id,
|
||||
ExtractedUiNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: uinode.stack_index,
|
||||
color,
|
||||
image: atlas_info.texture.id(),
|
||||
@ -801,8 +790,7 @@ pub fn extract_text_sections(
|
||||
rect,
|
||||
item: ExtractedUiItem::Glyphs { range: start..end },
|
||||
main_entity: entity.into(),
|
||||
},
|
||||
);
|
||||
});
|
||||
start = end;
|
||||
}
|
||||
|
||||
@ -885,7 +873,8 @@ pub fn queue_uinodes(
|
||||
draw_functions: Res<DrawFunctions<TransparentUi>>,
|
||||
) {
|
||||
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)) =
|
||||
render_views.get_mut(extracted_uinode.extracted_camera_entity)
|
||||
else {
|
||||
@ -912,11 +901,12 @@ pub fn queue_uinodes(
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function,
|
||||
pipeline,
|
||||
entity: (*entity, extracted_uinode.main_entity),
|
||||
entity: (entity, extracted_uinode.main_entity),
|
||||
sort_key: (
|
||||
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE),
|
||||
entity.index(),
|
||||
),
|
||||
index,
|
||||
// batch_range will be calculated in prepare_uinodes
|
||||
batch_range: 0..0,
|
||||
extra_index: PhaseItemExtraIndex::None,
|
||||
@ -978,7 +968,11 @@ pub fn prepare_uinodes(
|
||||
|
||||
for item_index in 0..ui_phase.items.len() {
|
||||
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();
|
||||
|
||||
if batch_image_handle == AssetId::invalid()
|
||||
|
||||
@ -112,6 +112,7 @@ pub struct TransparentUi {
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_range: Range<u32>,
|
||||
pub extra_index: PhaseItemExtraIndex,
|
||||
pub index: usize,
|
||||
pub indexed: bool,
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,6 @@ use bevy_asset::*;
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
query::ROQueryItem,
|
||||
storage::SparseSet,
|
||||
system::{
|
||||
lifetimeless::{Read, SRes},
|
||||
*,
|
||||
@ -347,11 +346,12 @@ pub struct ExtractedUiMaterialNode<M: UiMaterial> {
|
||||
// Nodes with ambiguous camera will be ignored.
|
||||
pub extracted_camera_entity: Entity,
|
||||
pub main_entity: MainEntity,
|
||||
render_entity: Entity,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct ExtractedUiMaterialNodes<M: UiMaterial> {
|
||||
pub uinodes: SparseSet<Entity, ExtractedUiMaterialNode<M>>,
|
||||
pub uinodes: Vec<ExtractedUiMaterialNode<M>>,
|
||||
}
|
||||
|
||||
impl<M: UiMaterial> Default for ExtractedUiMaterialNodes<M> {
|
||||
@ -398,9 +398,8 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||
continue;
|
||||
};
|
||||
|
||||
extracted_uinodes.uinodes.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiMaterialNode {
|
||||
extracted_uinodes.uinodes.push(ExtractedUiMaterialNode {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: computed_node.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
material: handle.id(),
|
||||
@ -413,8 +412,7 @@ pub fn extract_ui_material_nodes<M: UiMaterial>(
|
||||
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() {
|
||||
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()
|
||||
.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>>();
|
||||
|
||||
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 {
|
||||
continue;
|
||||
};
|
||||
@ -660,13 +662,14 @@ pub fn queue_ui_material_nodes<M: UiMaterial>(
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function,
|
||||
pipeline,
|
||||
entity: (*entity, extracted_uinode.main_entity),
|
||||
entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
|
||||
sort_key: (
|
||||
FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::MATERIAL),
|
||||
entity.index(),
|
||||
extracted_uinode.render_entity.index(),
|
||||
),
|
||||
batch_range: 0..0,
|
||||
extra_index: PhaseItemExtraIndex::None,
|
||||
index,
|
||||
indexed: false,
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ use bevy_asset::*;
|
||||
use bevy_color::{Alpha, ColorToComponents, LinearRgba};
|
||||
use bevy_ecs::{
|
||||
prelude::Component,
|
||||
storage::SparseSet,
|
||||
system::{
|
||||
lifetimeless::{Read, SRes},
|
||||
*,
|
||||
@ -237,11 +236,12 @@ pub struct ExtractedUiTextureSlice {
|
||||
pub flip_y: bool,
|
||||
pub inverse_scale_factor: f32,
|
||||
pub main_entity: MainEntity,
|
||||
pub render_entity: Entity,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct ExtractedUiTextureSlices {
|
||||
pub slices: SparseSet<Entity, ExtractedUiTextureSlice>,
|
||||
pub slices: Vec<ExtractedUiTextureSlice>,
|
||||
}
|
||||
|
||||
pub fn extract_ui_texture_slices(
|
||||
@ -309,9 +309,8 @@ pub fn extract_ui_texture_slices(
|
||||
}
|
||||
};
|
||||
|
||||
extracted_ui_slicers.slices.insert(
|
||||
commands.spawn(TemporaryRenderEntity).id(),
|
||||
ExtractedUiTextureSlice {
|
||||
extracted_ui_slicers.slices.push(ExtractedUiTextureSlice {
|
||||
render_entity: commands.spawn(TemporaryRenderEntity).id(),
|
||||
stack_index: uinode.stack_index,
|
||||
transform: transform.compute_matrix(),
|
||||
color: image.color.into(),
|
||||
@ -328,8 +327,7 @@ pub fn extract_ui_texture_slices(
|
||||
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>>,
|
||||
) {
|
||||
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) =
|
||||
render_views.get_mut(extracted_slicer.extracted_camera_entity)
|
||||
else {
|
||||
@ -373,13 +371,14 @@ pub fn queue_ui_slices(
|
||||
transparent_phase.add(TransparentUi {
|
||||
draw_function,
|
||||
pipeline,
|
||||
entity: (*entity, extracted_slicer.main_entity),
|
||||
entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
|
||||
sort_key: (
|
||||
FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::TEXTURE_SLICE),
|
||||
entity.index(),
|
||||
extracted_slicer.render_entity.index(),
|
||||
),
|
||||
batch_range: 0..0,
|
||||
extra_index: PhaseItemExtraIndex::None,
|
||||
index,
|
||||
indexed: true,
|
||||
});
|
||||
}
|
||||
@ -434,7 +433,11 @@ pub fn prepare_ui_slices(
|
||||
|
||||
for item_index in 0..ui_phase.items.len() {
|
||||
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();
|
||||
|
||||
if batch_image_handle == AssetId::invalid()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user