Document coalesced resize layout
This commit is contained in:
1 parent
986f98380d
commit
974d6c7a52
2 files changed
+59
-39
No files matched your search
@@ -9,7 +9,7 @@ Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are
|
|||||||
in.
|
in.
|
||||||
|
|
||||||
**#18 `split/18-position-chain`** is open and finished apart from one decision:
|
**#18 `split/18-position-chain`** is open and finished apart from one decision:
|
||||||
worktree `/home/bob/repos/iris-pr18`, head `480f0bc`, sixteen commits,
|
worktree `/home/bob/repos/iris-pr18`, head `82fa6c1`, seventeen commits,
|
||||||
workspace tests passing, fmt and clippy clean. It is LAYOUT.md §2's position chain, generalised
|
workspace tests passing, fmt and clippy clean. It is LAYOUT.md §2's position chain, generalised
|
||||||
to boxes. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean.
|
to boxes. `/home/bob/repos/ai-app-2` is on `rustify`, worktree clean.
|
||||||
|
|
||||||
@@ -183,24 +183,51 @@ IRIS_PHASE=resize IRIS_DEPTH=8 IRIS_FRAMES=1000 \
|
|||||||
--test layout_diagnostics -- --ignored --nocapture
|
--test layout_diagnostics -- --ignored --nocapture
|
||||||
```
|
```
|
||||||
|
|
||||||
The first depth-8 resize run makes the amplification concrete. A 260-widget
|
`82fa6c1` adds targeted tracing to the same feature. Call
|
||||||
tree has 215 active widgets, but a resize averages 1,395 widget draws, 913
|
`layout_diagnostics::trace_widget(id)` before a frame; `take().traces()` then
|
||||||
|
returns the selected widgets' ordered draw requests and pixel boxes, reuse
|
||||||
|
outcomes, placements, hints, size reads and reported sizes, and text widths.
|
||||||
|
The selection is a set, survives `take()`, and is removed with
|
||||||
|
`untrace_widget` or `clear_traced_widgets`. This replaces temporary
|
||||||
|
text-specific logging without imposing anything on normal builds.
|
||||||
|
|
||||||
|
The first depth-8 resize run made the amplification concrete. A 260-widget
|
||||||
|
tree has 215 active widgets, but a resize averaged 1,395 widget draws, 913
|
||||||
placement calls, 1,313 reads of drawn sizes and 34,844 primitive writes. Only
|
placement calls, 1,313 reads of drawn sizes and 34,844 primitive writes. Only
|
||||||
12 distinct text widgets render, yet they render and reshape 282 times per
|
12 distinct text widgets rendered, yet they rendered and reshaped 282 times
|
||||||
frame with no shape-cache hits. Text rendering accounts for 11.6 of 13.2 ms,
|
per frame with no shape-cache hits. Text rendering accounted for 11.6 of 13.2
|
||||||
including 9.9 ms shaping and 1.7 ms placing glyphs. The hottest two text
|
ms, including 9.9 ms shaping and 1.7 ms placing glyphs. The hottest two text
|
||||||
widgets each draw 96 times per frame at 22 distinct widths across two resize
|
widgets each drew 96 times below nested `Span`, `Aligned`, `Scroll`, `Pad`, and
|
||||||
frames, below nested `Span`, `Aligned`, `Scroll`, `Pad`, and `SetSize`
|
`SetSize` ancestors.
|
||||||
ancestors. This identifies repeated constraint discovery, rather than resize
|
|
||||||
marking (0.001 ms), as the next subject; it does not yet choose between
|
Tracing one of them showed that 96 was two multipliers, not 96 necessary
|
||||||
per-axis retained size validity and coalescing the resize dependency frontier.
|
layout iterations. One traversal of the nested positioning chain shaped it 32
|
||||||
|
times at 12 widths; three resize-dependent descendants then caused that whole
|
||||||
|
traversal to run three times through the same highest size reader. Resize
|
||||||
|
marking had queued each pixel reader independently, and each leaf discovered
|
||||||
|
and redrew the same reader path in turn.
|
||||||
|
|
||||||
|
`82fa6c1` coalesces that frontier by marking every resize-dependent leaf and
|
||||||
|
its size-reader chain first, then settling the shallowest shared reader once.
|
||||||
|
Ordinary content and size changes remain deepest-first. On the same depth-8
|
||||||
|
load, an actual resize now averages about 694 widget draws, 435 placement
|
||||||
|
calls, 649 drawn-size reads, 15,343 primitive writes, and 116 text shapes; the
|
||||||
|
hottest texts draw 32 times. Instrumented layout is about 5.5 ms instead of
|
||||||
|
13.2 ms. An uninstrumented 1,000-frame resize run measured a 5.61 ms median,
|
||||||
|
60.79B instructions total (60.8M/frame), and 23.85B cycles. This is about 58%
|
||||||
|
fewer instructions than the roughly 138M/frame #18 path before coalescing,
|
||||||
|
but still about 1.8x the old 3.14 ms resize median. The remaining multiplier
|
||||||
|
is within one constraint traversal, so per-axis retained size validity is now
|
||||||
|
the useful next question. Re-sending the existing output size also no longer
|
||||||
|
starts a resize.
|
||||||
|
|
||||||
The generated tree now includes `Aligned` with every meaningful per-axis
|
The generated tree now includes `Aligned` with every meaningful per-axis
|
||||||
alignment. That exposed two retained-layout ordering bugs which `84f589e`
|
alignment. That exposed two retained-layout ordering bugs which `84f589e`
|
||||||
fixes. The regular cold-layout equivalence suite passes. The ignored 100-seed
|
fixes. The regular cold-layout equivalence suite and the ignored 100-seed
|
||||||
sweep passed every transition through seed 59 and now gets past the former
|
sweep pass. The latter previously stopped at seed 60 on `140.0` versus
|
||||||
scroll failure at seed 52; at seed 60 it reaches the already-documented
|
`139.99996948242188`: exactly two `f32` ULPs from an equivalent composition
|
||||||
iterative wrapping-text defect, differing by 0.00003 px after `SwapForThree`.
|
order, not a visible layout difference. The oracle now keeps draw presence
|
||||||
|
exact and allows at most four ULPs per pixel coordinate.
|
||||||
|
|
||||||
The branches are invariants rather than widget exceptions:
|
The branches are invariants rather than widget exceptions:
|
||||||
|
|
||||||
@@ -211,9 +238,10 @@ The branches are invariants rather than widget exceptions:
|
|||||||
condition the discarded `/tmp/escalate.patch` missed on resize.
|
condition the discarded `/tmp/escalate.patch` missed on resize.
|
||||||
- Dirty widgets settle deepest-first. A changed size queues only its immediate
|
- Dirty widgets settle deepest-first. A changed size queues only its immediate
|
||||||
reader; propagation stops as soon as a reader's own answer stays unchanged.
|
reader; propagation stops as soon as a reader's own answer stays unchanged.
|
||||||
During an output resize, the resize condition stays live until these updates
|
Output resize is the distinct invariant: all pixel-dependent leaves and
|
||||||
finish, so an `Aligned` ancestor chooses the new child box before a
|
their reader chains are marked together, then shared reader roots settle
|
||||||
pixel-dependent descendant draws in it.
|
shallowest-first under the new output. This both chooses the parent box
|
||||||
|
before drawing its descendant and coalesces overlapping resize paths.
|
||||||
- A span measures an unknown child in the part of its axis still available,
|
- A span measures an unknown child in the part of its axis still available,
|
||||||
rather than giving every child the whole container and immediately taking
|
rather than giving every child the whole container and immediately taking
|
||||||
most of it away. This is the archive's faster shape, recreated on the current
|
most of it away. This is the archive's faster shape, recreated on the current
|
||||||
|
|||||||
+13
-21
@@ -102,27 +102,19 @@ narrowings apply, and both are real, measured properties of the code as it
|
|||||||
stands rather than new machinery:
|
stands rather than new machinery:
|
||||||
|
|
||||||
**(a) A window resize does not, by itself, require touching most widgets.**
|
**(a) A window resize does not, by itself, require touching most widgets.**
|
||||||
`shader.wgsl:105-106` recomputes every primitive's pixel position from
|
`shader.wgsl` recomputes every primitive's pixel position from `window.dim`
|
||||||
`window.dim` and the primitive's stored `rel`/`abs` pair *every frame,
|
and the primitive's stored `rel`/`abs` pair every frame, already, on the GPU.
|
||||||
already, on the GPU*. A widget laid out purely in `rel`/`abs` terms (no
|
A widget laid out purely in `rel`/`abs` terms is therefore already correct
|
||||||
call to `px_size()`, `output_size()`, or anything else that reads a
|
after a resize with zero CPU work. Calls to `Painter::px_size` and
|
||||||
concrete pixel count) is therefore already correct after a resize with zero
|
`Painter::output_size` mark the active widget as reading concrete pixels;
|
||||||
CPU work — the shader did it. `UiRenderState::needs_redraw_all`
|
only those widgets become dirty when the output actually changes.
|
||||||
(`render_state.rs:229-231`) currently ignores this and redraws the entire
|
|
||||||
tree on every `resized`, which was the safe default while sizing and
|
All pixel-dependent leaves are marked before layout begins, along with every
|
||||||
drawing were two passes; it should be narrowed to only the widgets that
|
chain of parents that read their sizes. Resize then settles the shallowest
|
||||||
*do* read a concrete pixel value. Track this the same way `needs_redraw`
|
shared readers first, under the new output, so overlapping dependency paths
|
||||||
already tracks per-widget dirtiness (`Widgets::needs_redraw`,
|
are drawn once. Ordinary content changes use the opposite order: deepest
|
||||||
`core/src/widget/widgets.rs:9`): a widget's `draw` call marks itself
|
dirty widgets first, with a changed returned size propagated one reader edge
|
||||||
pixel-dependent by calling through `Painter` methods that read
|
at a time. Re-reporting the current output size is a no-op.
|
||||||
`output_size`/`px_size` (both already funnel through `Painter`, so the
|
|
||||||
marking is one line at each), and `resize()` (`render_state.rs:32-35`)
|
|
||||||
walks only that set instead of unconditionally setting `resized = true`
|
|
||||||
for a full `redraw_all`. This turns "every resize redraws everything" into
|
|
||||||
"every resize redraws what depends on pixels" — a real behavior change
|
|
||||||
beyond what was asked, so verify it against the I0b `pre_present_notify`
|
|
||||||
resize regression (that fix depended on `redraw_all`'s completeness)
|
|
||||||
before narrowing this.
|
|
||||||
|
|
||||||
**(b) A widget's `available` (its parent's offered region) can change
|
**(b) A widget's `available` (its parent's offered region) can change
|
||||||
without the widget's *content* changing — this is what
|
without the widget's *content* changing — this is what
|
||||||
|
|||||||
Reference in new issue
Block a user