Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
9caf0b2
Added a new sub_z_layers module to bevy_ui_render
ickshonpe Oct 20, 2026
5a7e59b
Added empty bevy_ui_render\macros sub crate based on bevy_render\macros
ickshonpe Oct 20, 2026
0f278eb
Added bevy_ui_render_macros dependency to bevy_ui_render
ickshonpe Oct 20, 2026
70ef668
Added proc macro `derive_ui_sub_z_layer` to the macros subcrate
ickshonpe Oct 20, 2026
fcd67aa
Added draft migration guide
ickshonpe Oct 20, 2026
d288b8a
added define_label for UiSubZLayer, UiSubZLayerList resource to track…
ickshonpe Oct 20, 2026
0a25259
Fixed the errors in the UiSubZLayers implementation and trait path.
ickshonpe Oct 20, 2026
287750a
Removed stack_z_offsets module
ickshonpe Oct 20, 2026
ba8c3d9
Added draft docs for UiSubZLayerList
ickshonpe Oct 20, 2026
654d4ba
Updated migration guide.
ickshonpe Oct 20, 2026
62606e4
Added UiMaterial changes to the migration guide
ickshonpe Oct 20, 2026
e415ecb
Migrated all the extraction functions to use the new layers.
ickshonpe Oct 20, 2026
9c0e6c7
Added pull request # to the migration guide.
ickshonpe Oct 20, 2026
c908e02
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 20, 2026
60ce313
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 21, 2026
72018e1
Improved comments, clarifying that the z offsets occupy a clopen rang…
ickshonpe Oct 26, 2026
ad078c5
Renamed UiSubLayers to PaintOrder
ickshonpe Oct 26, 2026
d64a1bd
Renamed `UiSubZLayers` to `NodePaintSteps`
ickshonpe Oct 26, 2026
6c7e71c
More renamings:
ickshonpe Oct 26, 2026
eafa498
Comment cleanup
ickshonpe Oct 26, 2026
ea059ea
Cleaned up PaintOrder's implementation.
ickshonpe Oct 26, 2026
1ff9bee
Fixed imports
ickshonpe Oct 26, 2026
730dfc2
Added a from IntoIterator impl for PaintOrder
ickshonpe Oct 26, 2026
41ecded
Fixed more imports
ickshonpe Oct 26, 2026
f56aa0b
Replaced NodePaintOrder `all` fn with `ALL` const.
ickshonpe Oct 26, 2026
e0c418b
Renamed NodePaintLayers -> NodePaintLayer
ickshonpe Oct 26, 2026
60f3322
Renamed `UiMaterial::sub_z_layer` to `paint_layer`.
ickshonpe Oct 26, 2026
ff7f942
Fixed local variable names and comments still refering to sub z layers
ickshonpe Oct 26, 2026
d00bf66
Merge branch 'UiSubZLayer' of https://github.com/ickshonpe/bevy into …
ickshonpe Oct 26, 2026
6c291cc
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 26, 2026
6618aa7
Added PaintOrder unit tests
ickshonpe Oct 26, 2026
4896185
Added insert_top and insert_bottom methods.
ickshonpe Oct 26, 2026
1965d0b
updated test for new ordering
ickshonpe Oct 26, 2026
cf685e9
Added tests for insert_top + bottom
ickshonpe Oct 26, 2026
e8cab68
Updated migration guide.
ickshonpe Oct 26, 2026
5d20185
Added release note
ickshonpe Oct 26, 2026
c04d17c
Release content md fixes
ickshonpe Oct 26, 2026
ee17621
typo uk -> us spelling
ickshonpe Oct 26, 2026
98460d0
Removed headings from release note
ickshonpe Oct 26, 2026
1b361ff
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 27, 2026
8a85ace
Paint layers are now represents using a ` PaintLayer(SmolStr)` with n…
ickshonpe Oct 27, 2026
42f7587
Added doc comments for the PaintLayer constants.
ickshonpe Oct 27, 2026
7db7c6b
Added doc comment for DEFAULT_PAINT_LAYERS
ickshonpe Oct 27, 2026
71f823b
Made the PaintLayer implementation more consistant with bevy_feather'…
ickshonpe Oct 27, 2026
4771374
Merge branch 'UiSubZLayer' of https://github.com/ickshonpe/bevy into …
ickshonpe Oct 27, 2026
98946e5
Updated PaintLayer's doc comment
ickshonpe Oct 27, 2026
789c5cc
Updated doc comment for `PaintOrder`.
ickshonpe Oct 27, 2026
d83907e
Release content edits.
ickshonpe Oct 27, 2026
4520cac
Made DEFAULT_PAINT_LAYERS pub.
ickshonpe Oct 27, 2026
0311ce6
Removed macros dir and licenses
ickshonpe Oct 27, 2026
7359dc6
bevy_ui_render no longer uses an f32 sort key:
ickshonpe Oct 27, 2026
8a0a0d3
Updated release content
ickshonpe Oct 27, 2026
916c391
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 27, 2026
601bd9a
Removed trailing space from release note.
ickshonpe Oct 27, 2026
22afbb3
Merge branch 'main' into UiSubZLayer
ickshonpe Oct 27, 2026
b024eb9
Changed paint layer index to use a u32.
ickshonpe Oct 28, 2026
a185653
Merge branch 'UiSubZLayer' of https://github.com/ickshonpe/bevy into …
ickshonpe Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions _release-content/migration-guides/named_ui_z_layers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
title: "`stack_z_offsets` has been replaced by named z layers"
pull_requests: [26260]
---

Instead of using explicit `f32` z-offsets, `bevy_ui_render` now has named paint layers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should include an example of how to migrate GlobalZIndex components.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GlobalZIndex's behaviour is unchanged, it only affects the stack ordering.

The z offset constants in the `stack_z_offsets` module have been replaced by named `PaintLayer` constants.
The list of layers is stored in back-to-front order in the `PaintOrder` resource.

`TransparentUi::sort_key` now uses `UiSortKey` instead of `FloatOrd`. UI phase items are ordered first by stack index, then by paint layer index. Use `PaintOrder::get_layer_index` to look up a layer's index, then construct a `UiSortKey` with `UiSortKey::new(stack_index, paint_layer)`

`ExtractedUiNode::z_order` has been replaced by `stack_index: ComputedStackIndex` and `paint_layer: usize`.
The `stack_index` fields on `ExtractedBoxShadow`, `ExtractedGradient`, `ExtractedUiMaterialNode`, and `ExtractedUiTextureSlice` now take `ComputedStackIndex` instead of `u32`.

The `UiMaterial` trait's `stack_z_offset` method has been replaced by `paint_layer`, which should return the `PaintLayer` the material is intended to be drawn to.
36 changes: 36 additions & 0 deletions _release-content/release-notes/ui_paint_layers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
title: Paint layers
authors: ["@Ickshonpe"]
pull_requests: [26260]
---

`bevy_ui_render` now has paint layers.

The `PaintOrder` resource holds the list of paint layers, in the order they’re drawn from back to front.

The built-in layers are identified by named `PaintLayer` constants such as `BOX_SHADOW_PAINT_LAYER` and `TEXT_PAINT_LAYER`.

`UiRenderPlugin` initializes `PaintOrder` with all the builtin layers in the correct order.

To create a new `PaintLayer`, give it a string name:

```rust
pub const MY_PAINT_LAYER: PaintLayer = PaintLayer::new_static("my_plugin.overlay");
```

The `PaintOrder` resource exposes `IndexSet`’s methods through dereferencing, plus these helpers:

- `insert_before_layer` / `insert_after_layer` place a layer directly below or above an existing layer.
- `insert_top` / `insert_bottom` place a layer above or below all the other layers.
- `remove_layer` removes a layer. A built-in element whose layer is removed isn't drawn.

The insert methods move a layer if it is already present.

The `UiMaterial` trait has a new `paint_layer()` method that chooses the layer the layer the material is drawn to.
It defaults to `MATERIAL_PAINT_LAYER`.

UI render items are sorted using `UiSortKey`, first by `ComputedStackIndex` and then by paint-layer index.

Extraction functions should look up their layer index with `PaintOrder::get_layer_index` once at the start of the function. Extracted items store `ComputedStackIndex` directly, and `ExtractedUiNode` also stores the paint-layer index. Queue functions construct the sort key with `UiSortKey::new(stack_index, paint_layer)`. The `PaintOrder` resource is also extracted separately, so some queue functions look up the layer index there instead.

Changing `PaintOrder` at all is expensive, as it triggers a reextraction of all elements.
4 changes: 3 additions & 1 deletion crates/bevy_ui/src/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ use core::ops::Range;
/// Nodes with a higher stack index are drawn on top of and receive interactions before nodes with lower stack indices.
///
/// Automatically calculated in [`UiSystems::Stack`](`super::UiSystems::Stack`).
#[derive(Component, Default, PartialEq, Eq, Deref, DerefMut, Reflect)]
#[derive(
Component, Default, Clone, Copy, Ord, Debug, PartialEq, PartialOrd, Eq, Deref, DerefMut, Reflect,
)]
#[reflect(Component, Default)]
pub struct ComputedStackIndex(pub u32);

Expand Down
9 changes: 8 additions & 1 deletion crates/bevy_ui_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,17 @@ derive_more = { version = "2", default-features = false, features = ["from"] }
tracing = { version = "0.1", default-features = false, features = ["std"] }
indexmap = { version = "2" }
smallvec = { version = "1", default-features = false }
smol_str = { version = "0.2", default-features = false }
serde = { version = "1", features = ["derive"], optional = true }

[features]
default = ["bevy_ui_debug"]
serialize = ["bevy_math/serialize", "bevy_platform/serialize"]
serialize = [
"bevy_math/serialize",
"bevy_platform/serialize",
"dep:serde",
"smol_str/serde",
]
bevy_ui_debug = []

[lints]
Expand Down
20 changes: 12 additions & 8 deletions crates/bevy_ui_render/src/box_shadow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bevy_ecs::{
*,
},
};
use bevy_math::{vec2, Affine2, FloatOrd, Vec2};
use bevy_math::{vec2, Affine2, Vec2};
use bevy_mesh::VertexBufferLayout;
use bevy_render::sync_world::{MainEntity, MainEntityHashMap, MainEntityHashSet};
use bevy_render::{
Expand All @@ -34,11 +34,13 @@ use bevy_ui::{
use bevy_utils::default;
use bytemuck::{Pod, Zeroable};

use crate::UiSortKey;
use crate::{
clipping::clip_polygon, BoxShadowSamples, RenderUiSystems, TransparentUi, UiCameraMap,
};

use super::{stack_z_offsets, UiCameraView, QUAD_VERTEX_POSITIONS};
use super::{UiCameraView, QUAD_VERTEX_POSITIONS};
use crate::paint_layers::{PaintOrder, BOX_SHADOW_PAINT_LAYER};

/// A plugin that enables the rendering of box shadows.
pub struct BoxShadowPlugin;
Expand Down Expand Up @@ -186,7 +188,7 @@ impl SpecializedRenderPipeline for BoxShadowPipeline {

/// Description of a shadow to be sorted and queued for rendering
pub struct ExtractedBoxShadow {
pub stack_index: u32,
pub stack_index: ComputedStackIndex,
pub transform: Affine2,
pub bounds: Vec2,
pub clip: Option<CalculatedClip>,
Expand Down Expand Up @@ -358,7 +360,7 @@ pub fn extract_shadows(
.insert(
commands.spawn_empty().id(),
ExtractedBoxShadow {
stack_index: stack_index.0,
stack_index: *stack_index,
transform: Affine2::from(transform) * Affine2::from_translation(offset),
color: drop_shadow.color.into(),
bounds: shadow_size + 6. * blur_radius,
Expand Down Expand Up @@ -401,6 +403,7 @@ pub fn extract_shadows(
reason = "it's a system that needs a lot of them"
)]
pub fn queue_shadows(
paint_order: Res<PaintOrder>,
extracted_box_shadows: ResMut<ExtractedBoxShadows>,
box_shadow_pipeline: Res<BoxShadowPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<BoxShadowPipeline>>,
Expand All @@ -410,6 +413,10 @@ pub fn queue_shadows(
pipeline_cache: Res<PipelineCache>,
draw_functions: Res<DrawFunctions<TransparentUi>>,
) {
let Ok(paint_layer) = paint_order.get_layer_index(BOX_SHADOW_PAINT_LAYER) else {
return;
};

let draw_function = draw_functions.read().id::<DrawBoxShadows>();
let mut current_camera_entity = Entity::PLACEHOLDER;
let mut current_phase = None;
Expand Down Expand Up @@ -451,10 +458,7 @@ pub fn queue_shadows(
draw_function,
pipeline: *pipeline,
entity: (*entity, *main_entity),
sort_key: FloatOrd(
extracted_shadow.stack_index as f32 + stack_z_offsets::BOX_SHADOW,
),

sort_key: UiSortKey::new(extracted_shadow.stack_index, paint_layer),
batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_ui_render/src/debug_overlay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ pub fn extract_debug_overlay(
let color = debug_options
.line_color_override
.unwrap_or_else(|| Hsla::sequential_dispersed(entity.index_u32()).into());
let z_order = (ui_stack.uinodes.len() as u32 + stack_index.0) as f32;
let stack_index = ComputedStackIndex(ui_stack.uinodes.len() as u32 + stack_index.0);
let border = BorderRect::all(debug_options.line_width / uinode.inverse_scale_factor());
let transform = transform.affine();

Expand All @@ -229,8 +229,8 @@ pub fn extract_debug_overlay(
.insert(
commands.spawn_empty().id(),
ExtractedUiNode {
// Keep all overlays above UI, and nudge each type slightly in Z so ordering is stable.
z_order,
stack_index,
paint_layer: 0,
clip: maybe_clip.filter(|_| !debug_options.show_clipped).cloned(),
image: AssetId::default(),
transform: transform * Affine2::from_translation(rect.center()),
Expand Down
27 changes: 15 additions & 12 deletions crates/bevy_ui_render/src/gradient.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use core::{

use super::shader_flags::BORDER_ALL;
use crate::clipping::clip_polygon;
use crate::paint_layers::{BORDER_GRADIENT_PAINT_LAYER, GRADIENT_PAINT_LAYER};
use crate::*;
use bevy_asset::*;
use bevy_color::{ColorToComponents, Hsla, Hsva, LinearRgba, Okhsla, Oklaba, Oklcha, Srgba};
Expand Down Expand Up @@ -231,7 +232,7 @@ pub enum ResolvedGradient {
}

pub struct ExtractedGradient {
pub stack_index: u32,
pub stack_index: ComputedStackIndex,
pub transform: Affine2,
pub rect: Rect,
pub clip: Option<CalculatedClip>,
Expand Down Expand Up @@ -486,7 +487,7 @@ pub fn extract_gradients(
.insert(
commands.spawn_empty().id(),
ExtractedGradient {
stack_index: stack_index.0,
stack_index: *stack_index,
transform: transform.into(),
stops: extracted_stops,
rect: Rect {
Expand Down Expand Up @@ -529,7 +530,7 @@ pub fn extract_gradients(
.insert(
commands.spawn_empty().id(),
ExtractedGradient {
stack_index: stack_index.0,
stack_index: *stack_index,
transform: transform.into(),
stops: extracted_stops,
rect: Rect {
Expand Down Expand Up @@ -588,7 +589,7 @@ pub fn extract_gradients(
.insert(
commands.spawn_empty().id(),
ExtractedGradient {
stack_index: stack_index.0,
stack_index: *stack_index,
transform: transform.into(),
stops: computed_stops,
rect: Rect {
Expand Down Expand Up @@ -649,7 +650,7 @@ pub fn extract_gradients(
.insert(
commands.spawn_empty().id(),
ExtractedGradient {
stack_index: stack_index.0,
stack_index: *stack_index,
transform: transform.into(),
stops: extracted_color_stops,
rect: Rect {
Expand Down Expand Up @@ -704,6 +705,7 @@ pub fn extract_gradients(
reason = "it's a system that needs a lot of them"
)]
pub fn queue_gradient(
paint_order: Res<PaintOrder>,
extracted_gradients: ResMut<ExtractedGradients>,
gradients_pipeline: Res<GradientPipeline>,
mut pipelines: ResMut<SpecializedRenderPipelines<GradientPipeline>>,
Expand Down Expand Up @@ -741,6 +743,13 @@ pub fn queue_gradient(
continue;
};
for (render_entity, gradient) in sub_gradients.iter() {
let Ok(paint_layer) = paint_order.get_layer_index(match gradient.node_type {
NodeType::Rect | NodeType::Inverted => GRADIENT_PAINT_LAYER,
NodeType::Border(_) => BORDER_GRADIENT_PAINT_LAYER,
}) else {
continue;
};

let pipeline = pipelines.specialize(
&pipeline_cache,
&gradients_pipeline,
Expand All @@ -755,13 +764,7 @@ pub fn queue_gradient(
draw_function,
pipeline,
entity: (*render_entity, *main_entity),
sort_key: FloatOrd(
gradient.stack_index as f32
+ match gradient.node_type {
NodeType::Rect | NodeType::Inverted => stack_z_offsets::GRADIENT,
NodeType::Border(_) => stack_z_offsets::BORDER_GRADIENT,
},
),
sort_key: UiSortKey::new(gradient.stack_index, paint_layer),
batch_range: 0..0,
extra_index: PhaseItemExtraIndex::None,
indexed: true,
Expand Down
Loading
Loading