Skip to content

Commit a949867

Browse files
authored
UI z-ordering fix (#19691)
# Objective During the migration to required components a lot of things were changed around and somehow the draw order for some UI elements ended up depending on the system ordering in `RenderSystems::Queue`, which can sometimes result in the elements being drawn in the wrong order. Fixes #19674 ## Solution * Added some more `stack_z_offsets` constants and used them to enforce an explicit ordering. * Removed the `stack_index: u32` field from `ExtractedUiNodes` and replaced it with a `z_order: f32` field. These changes should fix all the ordering problems. ## Testing I added a nine-patched bordered node with a navy background color to the slice section of the `testbed_ui` example. The border should always be drawn above the background color.
1 parent 5e8aa79 commit a949867

File tree

6 files changed

+93
-27
lines changed

6 files changed

+93
-27
lines changed

crates/bevy_ui/src/render/debug_overlay.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
1+
use super::ExtractedUiItem;
2+
use super::ExtractedUiNode;
3+
use super::ExtractedUiNodes;
4+
use super::NodeType;
5+
use super::UiCameraMap;
16
use crate::shader_flags;
27
use crate::ui_node::ComputedNodeTarget;
38
use crate::ui_transform::UiGlobalTransform;
49
use crate::CalculatedClip;
510
use crate::ComputedNode;
11+
use crate::UiStack;
612
use bevy_asset::AssetId;
713
use bevy_color::Hsla;
814
use bevy_ecs::entity::Entity;
@@ -18,12 +24,6 @@ use bevy_render::view::InheritedVisibility;
1824
use bevy_render::Extract;
1925
use bevy_sprite::BorderRect;
2026

21-
use super::ExtractedUiItem;
22-
use super::ExtractedUiNode;
23-
use super::ExtractedUiNodes;
24-
use super::NodeType;
25-
use super::UiCameraMap;
26-
2727
/// Configuration for the UI debug overlay
2828
#[derive(Resource)]
2929
pub struct UiDebugOptions {
@@ -68,6 +68,7 @@ pub fn extract_debug_overlay(
6868
&ComputedNodeTarget,
6969
)>,
7070
>,
71+
ui_stack: Extract<Res<UiStack>>,
7172
camera_map: Extract<UiCameraMap>,
7273
) {
7374
if !debug_options.enabled {
@@ -89,7 +90,7 @@ pub fn extract_debug_overlay(
8990
extracted_uinodes.uinodes.push(ExtractedUiNode {
9091
render_entity: commands.spawn(TemporaryRenderEntity).id(),
9192
// Add a large number to the UI node's stack index so that the overlay is always drawn on top
92-
stack_index: uinode.stack_index + u32::MAX / 2,
93+
z_order: (ui_stack.uinodes.len() as u32 + uinode.stack_index()) as f32,
9394
color: Hsla::sequential_dispersed(entity.index()).into(),
9495
rect: Rect {
9596
min: Vec2::ZERO,

crates/bevy_ui/src/render/gradient.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,11 @@ pub fn extract_gradients(
408408
if let Some(color) = gradient.get_single() {
409409
// With a single color stop there's no gradient, fill the node with the color
410410
extracted_uinodes.uinodes.push(ExtractedUiNode {
411-
stack_index: uinode.stack_index,
411+
z_order: uinode.stack_index as f32
412+
+ match node_type {
413+
NodeType::Rect => stack_z_offsets::GRADIENT,
414+
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
415+
},
412416
color: color.into(),
413417
rect: Rect {
414418
min: Vec2::ZERO,
@@ -629,7 +633,13 @@ pub fn queue_gradient(
629633
draw_function,
630634
pipeline,
631635
entity: (gradient.render_entity, gradient.main_entity),
632-
sort_key: FloatOrd(gradient.stack_index as f32 + stack_z_offsets::GRADIENT),
636+
sort_key: FloatOrd(
637+
gradient.stack_index as f32
638+
+ match gradient.node_type {
639+
NodeType::Rect => stack_z_offsets::GRADIENT,
640+
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
641+
},
642+
),
633643
batch_range: 0..0,
634644
extra_index: PhaseItemExtraIndex::None,
635645
index,

crates/bevy_ui/src/render/mod.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ pub mod graph {
8181
}
8282
}
8383

84-
/// Z offsets of "extracted nodes" for a given entity. These exist to allow rendering multiple "extracted nodes"
84+
/// Local Z offsets of "extracted nodes" for a given entity. These exist to allow rendering multiple "extracted nodes"
8585
/// for a given source entity (ex: render both a background color _and_ a custom material for a given node).
8686
///
8787
/// When possible these offsets should be defined in _this_ module to ensure z-index coordination across contexts.
@@ -97,10 +97,13 @@ pub mod graph {
9797
/// a positive offset on a node below.
9898
pub mod stack_z_offsets {
9999
pub const BOX_SHADOW: f32 = -0.1;
100-
pub const TEXTURE_SLICE: f32 = 0.0;
101-
pub const NODE: f32 = 0.0;
102-
pub const GRADIENT: f32 = 0.1;
103-
pub const MATERIAL: f32 = 0.18267;
100+
pub const BACKGROUND_COLOR: f32 = 0.0;
101+
pub const BORDER: f32 = 0.01;
102+
pub const GRADIENT: f32 = 0.02;
103+
pub const BORDER_GRADIENT: f32 = 0.03;
104+
pub const IMAGE: f32 = 0.04;
105+
pub const MATERIAL: f32 = 0.05;
106+
pub const TEXT: f32 = 0.06;
104107
}
105108

106109
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
@@ -213,7 +216,7 @@ fn get_ui_graph(render_app: &mut SubApp) -> RenderGraph {
213216
}
214217

215218
pub struct ExtractedUiNode {
216-
pub stack_index: u32,
219+
pub z_order: f32,
217220
pub color: LinearRgba,
218221
pub rect: Rect,
219222
pub image: AssetId<Image>,
@@ -374,7 +377,7 @@ pub fn extract_uinode_background_colors(
374377

375378
extracted_uinodes.uinodes.push(ExtractedUiNode {
376379
render_entity: commands.spawn(TemporaryRenderEntity).id(),
377-
stack_index: uinode.stack_index,
380+
z_order: uinode.stack_index as f32 + stack_z_offsets::BACKGROUND_COLOR,
378381
color: background_color.0.into(),
379382
rect: Rect {
380383
min: Vec2::ZERO,
@@ -460,8 +463,8 @@ pub fn extract_uinode_images(
460463
};
461464

462465
extracted_uinodes.uinodes.push(ExtractedUiNode {
466+
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
463467
render_entity: commands.spawn(TemporaryRenderEntity).id(),
464-
stack_index: uinode.stack_index,
465468
color: image.color.into(),
466469
rect,
467470
clip: clip.map(|clip| clip.clip),
@@ -558,7 +561,7 @@ pub fn extract_uinode_borders(
558561
completed_flags |= border_flags;
559562

560563
extracted_uinodes.uinodes.push(ExtractedUiNode {
561-
stack_index: computed_node.stack_index,
564+
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
562565
color,
563566
rect: Rect {
564567
max: computed_node.size(),
@@ -591,8 +594,8 @@ pub fn extract_uinode_borders(
591594
{
592595
let outline_size = computed_node.outlined_node_size();
593596
extracted_uinodes.uinodes.push(ExtractedUiNode {
597+
z_order: computed_node.stack_index as f32 + stack_z_offsets::BORDER,
594598
render_entity: commands.spawn(TemporaryRenderEntity).id(),
595-
stack_index: computed_node.stack_index,
596599
color: outline.color.into(),
597600
rect: Rect {
598601
max: outline_size,
@@ -782,8 +785,8 @@ pub fn extract_viewport_nodes(
782785
};
783786

784787
extracted_uinodes.uinodes.push(ExtractedUiNode {
788+
z_order: uinode.stack_index as f32 + stack_z_offsets::IMAGE,
785789
render_entity: commands.spawn(TemporaryRenderEntity).id(),
786-
stack_index: uinode.stack_index,
787790
color: LinearRgba::WHITE,
788791
rect: Rect {
789792
min: Vec2::ZERO,
@@ -885,8 +888,8 @@ pub fn extract_text_sections(
885888
.map(|text_color| LinearRgba::from(text_color.0))
886889
.unwrap_or_default();
887890
extracted_uinodes.uinodes.push(ExtractedUiNode {
891+
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
888892
render_entity: commands.spawn(TemporaryRenderEntity).id(),
889-
stack_index: uinode.stack_index,
890893
color,
891894
image: atlas_info.texture.id(),
892895
clip: clip.map(|clip| clip.clip),
@@ -966,8 +969,8 @@ pub fn extract_text_shadows(
966969
info.span_index != *span_index || info.atlas_info.texture != atlas_info.texture
967970
}) {
968971
extracted_uinodes.uinodes.push(ExtractedUiNode {
972+
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
969973
render_entity: commands.spawn(TemporaryRenderEntity).id(),
970-
stack_index: uinode.stack_index,
971974
color: shadow.color.into(),
972975
image: atlas_info.texture.id(),
973976
clip: clip.map(|clip| clip.clip),
@@ -1023,8 +1026,8 @@ pub fn extract_text_background_colors(
10231026
};
10241027

10251028
extracted_uinodes.uinodes.push(ExtractedUiNode {
1029+
z_order: uinode.stack_index as f32 + stack_z_offsets::TEXT,
10261030
render_entity: commands.spawn(TemporaryRenderEntity).id(),
1027-
stack_index: uinode.stack_index,
10281031
color: text_background_color.0.to_linear(),
10291032
rect: Rect {
10301033
min: Vec2::ZERO,
@@ -1167,7 +1170,7 @@ pub fn queue_uinodes(
11671170
draw_function,
11681171
pipeline,
11691172
entity: (extracted_uinode.render_entity, extracted_uinode.main_entity),
1170-
sort_key: FloatOrd(extracted_uinode.stack_index as f32 + stack_z_offsets::NODE),
1173+
sort_key: FloatOrd(extracted_uinode.z_order),
11711174
index,
11721175
// batch_range will be calculated in prepare_uinodes
11731176
batch_range: 0..0,

crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,9 +366,7 @@ pub fn queue_ui_slices(
366366
draw_function,
367367
pipeline,
368368
entity: (extracted_slicer.render_entity, extracted_slicer.main_entity),
369-
sort_key: FloatOrd(
370-
extracted_slicer.stack_index as f32 + stack_z_offsets::TEXTURE_SLICE,
371-
),
369+
sort_key: FloatOrd(extracted_slicer.stack_index as f32 + stack_z_offsets::IMAGE),
372370
batch_range: 0..0,
373371
extra_index: PhaseItemExtraIndex::None,
374372
index,

examples/testbed/ui.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,26 @@ mod slice {
484484
},
485485
));
486486
}
487+
488+
parent.spawn((
489+
ImageNode {
490+
image: asset_server
491+
.load("textures/fantasy_ui_borders/panel-border-010.png"),
492+
image_mode: NodeImageMode::Sliced(TextureSlicer {
493+
border: BorderRect::all(22.0),
494+
center_scale_mode: SliceScaleMode::Stretch,
495+
sides_scale_mode: SliceScaleMode::Stretch,
496+
max_corner_scale: 1.0,
497+
}),
498+
..Default::default()
499+
},
500+
Node {
501+
width: Val::Px(100.),
502+
height: Val::Px(100.),
503+
..default()
504+
},
505+
BackgroundColor(bevy::color::palettes::css::NAVY.into()),
506+
));
487507
});
488508
}
489509
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
title: Fixed UI draw order and `stack_z_offsets` changes
3+
pull_requests: [19691]
4+
---
5+
6+
The draw order of some renderable UI elements relative to others wasn't fixed and depended on system ordering.
7+
In particular the ordering of background colors and texture sliced images was sometimes swapped.
8+
9+
The UI draw order is now fixed.
10+
The new order is (back-to-front):
11+
12+
1. Box shadows
13+
14+
2. Node background colors
15+
16+
3. Node borders
17+
18+
4. Gradients
19+
20+
5. Border Gradients
21+
22+
6. Images (including texture-sliced images)
23+
24+
7. Materials
25+
26+
8. Text (including text shadows)
27+
28+
The values of the `stack_z_offsets` constants have been updated to enforce the new ordering. Other changes:
29+
30+
* `NODE` is renamed to `BACKGROUND_COLOR`
31+
32+
* `TEXTURE_SLICE` is removed, use `IMAGE`.
33+
34+
* New `BORDER`, `BORDER_GRADIENT` and `TEXT` constants.

0 commit comments

Comments
 (0)