Carry the composed box down the draw, rather than walking back up for it

Every widget that reads its box in pixels was making `Moves` compose its
slot's chain again, a mean of 2.8 levels, about eight hundred times a frame.
A draw already descends past every one of those entries on its way in, so
`DrawInfo` carries what the slot composes to and `draw_at` steps it one box
further -- which is a select where it was a walk. `Moves::size_of` and
`compose` are left for `redraw`, which starts mid-tree with nothing above it
in flight.

Measured on the fixed-shape fixture, seed 1 depth 8, 500 frames of `many`,
medians of 25 runs, twenty-five work counters identical throughout:

| | instructions | cycles |
| --- | ---: | ---: |
| `d21a215`, before exact composition | 1,908M | 760M |
| `45a7176`, composing on the fine grid | 1,880M | 755M |
| this | **1,840M** | **735M** |

So exact composition ends up 3.6% fewer instructions and 3.3% fewer cycles
than the rounding-per-level walk it replaced, and the widening it needed was
paid for twice over by not doing the walk.

`Holds::through`'s allowance does not move: two half steps is where shrinker
seed 220 pins it, not where the arithmetic does. `Painter` still composes a
child's region into its own on the grid before asking for it in pixels, which
is the last narrow step in that path; taking it out needs the child's region
as its parent stated it, which `draw_inner` is not handed.

Checked: fmt, clippy, 83 suite tests, 17 core unit tests, the release oracle
at 100 seeds and at 1000 seeds of depth 6, all fifteen shrinker cases at 400
seeds of depth 5, and `tabs`, `view`, `minimal`, `text`, `random` and the tab
replay byte-identical at 1920x1200 against `45a7176`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
iris-aiandClaude Opus 5 committed 2026-09-16 21:49:25 -04:00
1 parent 45a717695b
commit 5f16617511
3 files changed
+92 -12

No files matched your search

+16 -5
View File
@@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter};
use crate::{
Axis, Holds, LayoutLen, Len, Px, PxVec2, RegionAlign, Rel, RenderedText, Size, StrongWidget,
TextAttrs, TextBuffer, TextData, TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight,
WidgetId, Widgets,
WideRegion, WidgetId, Widgets,
render::{
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
PrimitiveKind, TexturePrimitive,
@@ -19,6 +19,9 @@ pub struct Painter<'a> {
/// This widget's box, in the coordinates of `move_idx`.
pub(super) region: UiRegion,
/// What this widget's slot composes to, so its own box and its children's
/// are a step further rather than a walk back up the chain.
pub(super) slot_wide: WideRegion,
pub(super) mask: MaskIdx,
pub(super) textures: Vec<TextureHandle>,
pub(super) primitives: Vec<PrimitiveHandle>,
@@ -196,6 +199,7 @@ impl<'a> Painter<'a> {
mask: self.mask,
offer,
offered_px: self.px_within_offer(offer),
slot_wide: self.slot_wide,
align: align_override,
},
None,
@@ -264,7 +268,7 @@ impl<'a> Painter<'a> {
if let Some(hint) = self.size_hint(child, axis) {
return Some(hint);
}
let px = self.state.px_of(self.move_idx, within);
let px = self.px_of(within);
let (size, holds) =
self.state
.retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?;
@@ -301,6 +305,13 @@ impl<'a> Painter<'a> {
)
}
/// A box stated as a part of this widget's slot, in pixels.
fn px_of(&self, region: UiRegion) -> PxVec2 {
self.slot_wide
.select_size(&region)
.to_px(self.state.output_size())
}
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
if !self.size_deps.contains(&child.id()) {
self.size_deps.push(child.id());
@@ -388,7 +399,7 @@ impl<'a> Painter<'a> {
/// This widget's box in pixels. Reading it makes the drawing one that
/// holds for this box only, until `holds` says how far it goes.
pub fn px_size(&mut self) -> PxVec2 {
let px = self.state.px_of(self.move_idx, self.region);
let px = self.px_of(self.region);
for (own, len) in self.own.iter_mut().zip([px.x, px.y]) {
if *own == Holds::ANY {
*own = Holds::at(len);
@@ -400,7 +411,7 @@ impl<'a> Painter<'a> {
/// One axis of this widget's box in pixels. Prefer this to
/// [`Self::px_size`] when the other axis cannot affect the drawing.
pub fn px_len(&mut self, axis: Axis) -> Px {
let len = self.state.px_of(self.move_idx, self.region).axis(axis);
let len = self.px_of(self.region).axis(axis);
let own = &mut self.own[axis as usize];
if *own == Holds::ANY {
*own = Holds::at(len);
@@ -415,7 +426,7 @@ impl<'a> Painter<'a> {
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
let holds = holds.into();
debug_assert!(
holds.contains(self.state.px_of(self.move_idx, self.region).axis(axis)),
holds.contains(self.px_of(self.region).axis(axis)),
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
self.label(),
self.id