Measure a child on the layer it draws on, not twice on two

Fixed point cost 3x in layout: `many` went from 0.179 ms a frame to 0.544,
and `scroll` from 0.011 to 0.030. The counters said why -- eight more "placed
by redrawing" a frame -- and the reason was mine rather than the grid's. A
retained drawing belongs to the layer it was made on, which `4e28f10` started
enforcing, and `Stack` measures the child that sizes it by drawing it on its
own layer and then draws it again on the child layer. So every stacked child
redrew twice a frame, forever.

`Painter::child_layer_at` addresses a child's layer rather than walking to
it, and `Stack` measures on the layer that child ends up on. The second ask
is then a reuse. Its glyphs are written once rather than once under the
background and once over it.

Measured on the same fixture: `scroll` 0.031 ms to 0.020, `many` 0.570 to
0.283, and the scroll phase's counters are back to what they were before
fixed point -- 4 widget draws and 12 draw requests a frame, exactly. What is
left above that baseline is not this.

`ReuseOutcome` could not say "another layer" or "the region-node choice
changed"; both returned without a counter, which is why the first look at
this said nothing. They have counters now.

Checked: fmt, clippy, 105 tests, five shrinker cases at 300 seeds, 100
generated seeds, and the examples byte-identical but for 36 pixels of
`random` at one level -- edges that were being drawn twice.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 04:04:31 -04:00
1 parent 95fb4f962c
commit 97cc8b32ed
5 files changed
+73 -20

No files matched your search

+4
View File
@@ -40,6 +40,8 @@ pub(crate) enum Counter {
ReuseWrongParent,
ReuseRemapped,
ReuseOutside,
ReuseWrongLayer,
ReuseWrongNode,
PlaceRedraws,
QueuePops,
DepthReads,
@@ -73,6 +75,8 @@ impl Counter {
"reuse: wrong parent",
"reuse remapped",
"reuse: outside what it holds for",
"reuse: another layer",
"reuse: region-node choice changed",
"placed by redrawing",
"redraw queue pops",
"depth reads",
+15
View File
@@ -42,6 +42,9 @@ pub struct Painter<'a> {
/// its own when opted in, otherwise the nearest ancestor's.
pub(super) move_idx: MoveIdx,
pub layer: usize,
/// The layer this widget was entered on, which its children's layers are
/// counted from however far `layer` has walked.
pub(super) own_layer: usize,
pub(super) depth: usize,
pub(super) id: WidgetId,
}
@@ -420,6 +423,18 @@ impl<'a> Painter<'a> {
self.layer = self.state.layers.child(self.layer);
}
/// The layer this widget's `n`th child draws on, addressed rather than
/// walked to. A container that measures one child by drawing it can ask
/// on the layer that child will end up on, and then the second ask is a
/// reuse rather than a second drawing on another layer.
pub fn child_layer_at(&mut self, n: usize) {
let mut at = self.state.layers.child(self.own_layer);
for _ in 0..n {
at = self.state.layers.next(at);
}
self.layer = at;
}
pub fn next_layer(&mut self) {
self.layer = self.state.layers.next(self.layer);
}
+8 -1
View File
@@ -307,6 +307,7 @@ impl UiRenderState {
region: local,
mask: info.mask,
layer: info.layer,
own_layer: info.layer,
id,
textures: Vec::new(),
primitives: Vec::new(),
@@ -349,6 +350,7 @@ impl UiRenderState {
under,
move_idx,
layer,
own_layer: _,
depth: _,
id,
} = painter;
@@ -586,6 +588,8 @@ impl UiRenderState {
}
let has_region_node = active.move_idx != active.parent_move;
if has_region_node != info.region_node {
#[cfg(feature = "layout-diagnostics")]
diag::bump(Counter::ReuseWrongNode);
return None;
}
// Drawn on another layer: the drawing sits in that layer's list and
@@ -595,7 +599,10 @@ impl UiRenderState {
// on a layer the first answer is not good for.
if active.layer != info.layer {
#[cfg(feature = "layout-diagnostics")]
diag::reuse(id, ReuseOutcome::WrongLayer);
{
diag::bump(Counter::ReuseWrongLayer);
diag::reuse(id, ReuseOutcome::WrongLayer);
}
return None;
}
// Drawn somewhere else in the tree: its box is in coordinates it no