Record retained layout optimization results
This commit is contained in:
1 parent
38f6f9ac07
commit
cb1cfff0bf
1 file changed
+53
-28
@@ -9,8 +9,8 @@ Canonical `main` is **`ca2b4b2`** (#17, the headless rig). Sixteen slices are
|
||||
in.
|
||||
|
||||
**#18 `split/18-position-chain`** is open and finished apart from one decision:
|
||||
worktree `/home/bob/repos/iris-pr18`, head `cdec293`, thirteen commits, 50
|
||||
tests passing, fmt and clippy clean. It is LAYOUT.md §2's position chain, generalised
|
||||
worktree `/home/bob/repos/iris-pr18`, head `84f589e`, fifteen commits,
|
||||
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.
|
||||
|
||||
**The one thing waiting on the owner.** `tabs` at 1920x1200 is no longer
|
||||
@@ -137,30 +137,51 @@ not in the repository, because the counters it reads are patched into
|
||||
**The GPU is not the subject.** The pass is a tenth of a millisecond at every
|
||||
revision and every phase; all of this is the CPU laying out.
|
||||
|
||||
Three separate costs, in the order they are worth fixing:
|
||||
`a640c6c` and `84f589e` remove most of the CPU work without weakening the
|
||||
retained-layout rules. Against the same seed-1/depth-7 load, widget draws are
|
||||
now 2,117 cold, 1 for a leaf repaint, 11 for a scroll, and 3,139 for a resize
|
||||
(from 2,755, 1,313, 1,313, and 5,512 respectively). Workspace tests pass; the
|
||||
five reference renders, resize render, and image-tab replay are byte-identical
|
||||
to the prior #18 head.
|
||||
|
||||
The generated tree now includes `Aligned` with every meaningful per-axis
|
||||
alignment. That exposed two retained-layout ordering bugs which `84f589e`
|
||||
fixes. The regular cold-layout equivalence suite passes. The ignored 100-seed
|
||||
sweep passed every transition through seed 59 and now gets past the former
|
||||
scroll failure at seed 52; at seed 60 it reaches the already-documented
|
||||
iterative wrapping-text defect, differing by 0.00003 px after `SwapForThree`.
|
||||
|
||||
The branches are invariants rather than widget exceptions:
|
||||
|
||||
- A dirty widget draws locally first only while its retained box has the same
|
||||
pixel size. If its returned `Size` is unchanged, no size reader can observe
|
||||
the repaint and no ancestor draws. If the size changed, the dependent path
|
||||
lays out. A changed pixel box takes the conservative path first, which is the
|
||||
condition the discarded `/tmp/escalate.patch` missed on resize.
|
||||
- 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.
|
||||
During an output resize, the resize condition stays live until these updates
|
||||
finish, so an `Aligned` ancestor chooses the new child box before a
|
||||
pixel-dependent descendant draws in it.
|
||||
- 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
|
||||
most of it away. This is the archive's faster shape, recreated on the current
|
||||
types; it often makes the measurement box the final box without caching an
|
||||
answer under different constraints.
|
||||
|
||||
A proposed `Scroll` shortcut that reused its direct child's retained size was
|
||||
discarded. “Direct child is clean” is not strong enough while a dirty
|
||||
descendant's structural change is still propagating; seed 52 demonstrated the
|
||||
stale-size failure. Re-measuring the scroll subtree costs 11 draws rather than
|
||||
1, but remains two orders of magnitude below the old 1,313 and follows the
|
||||
actual dependency invariant.
|
||||
|
||||
The remaining costs are:
|
||||
|
||||
- **A repaint escalates to the top size reader and redraws its whole
|
||||
subtree.** `redraw` asks `mark_readers` first, so a widget whose drawing
|
||||
changed but whose *size* did not still redraws everything from the highest
|
||||
widget that ever read it. At depth 8 a one-rectangle repaint costs 4825
|
||||
draws and 24 ms, which is more than building the tree from nothing. The
|
||||
measured alternative is to draw the widget in its own box first, compare
|
||||
the `Size` it returns with the one it had, and escalate only when it
|
||||
differs: repaint falls to 1 draw and 0.000 ms, a scroll to 11 draws, and a
|
||||
resize to 532 draws and 2.3 ms -- below the pre-#16 numbers on every line.
|
||||
**It is not correct as written**: the resize case in `tests/generated.rs`
|
||||
fails at seed 1 with four widgets misplaced, because drawing the widget
|
||||
clears the mark that was the only thing stopping a later parent draw from
|
||||
reusing it into a different box. The patch is kept at
|
||||
`/tmp/escalate.patch`. This is the owner's call, since it changes when a
|
||||
widget is drawn.
|
||||
- **A container measures a child by drawing it in a box it will not keep.**
|
||||
`Span::draw` places each child without an exact `size_hint` at
|
||||
`UiRegion::FULL` to read its length, then places it again at the box it
|
||||
worked out, and the second place redraws whatever the first drew. It
|
||||
compounds with nesting: draws per drawn widget are 1.3x at depth 4, 3.3x at
|
||||
7 and 5.2x at 8. Wrapping text is shaped at the trial width and then again
|
||||
at the real one -- 332 shapes for 152 text widgets on a cold frame.
|
||||
The remaining-region trial removes many mismatches, but an unknown child's
|
||||
measured length can still make its final box differ, and nested containers
|
||||
compound those redraws. This is now the largest CPU layout cost.
|
||||
- **Redundant draws rewrite primitives.** A cold frame writes 48,050
|
||||
instances for a tree that holds 10,872. What reaches the GPU is 10,872,
|
||||
since a layer uploads whole, so this is CPU cost only -- but a one-leaf
|
||||
@@ -169,8 +190,8 @@ Three separate costs, in the order they are worth fixing:
|
||||
## The random trees
|
||||
|
||||
`iris::random` grows a seeded tree -- spans in every direction holding two to
|
||||
four children, stacks, scrolls on either axis, padding with each of its four
|
||||
sides its own number,
|
||||
four children, stacks, scrolls on either axis, alignment on either axis,
|
||||
padding with each of its four sides its own number,
|
||||
rects with varying opacity, text both wrapping and overflowing, a declared size
|
||||
over half of it, stopping at a depth. `examples/random.rs` draws one
|
||||
(`IRIS_SEED`, `IRIS_DEPTH`). `tests/generated.rs` grows each seed twice -- once
|
||||
@@ -211,7 +232,7 @@ cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo test --workspace
|
||||
```
|
||||
|
||||
49 tests pass on #18's head. `--workspace` matters: `rig-input` is a crate of
|
||||
50 tests pass on #18's head. `--workspace` matters: `rig-input` is a crate of
|
||||
its own.
|
||||
|
||||
Render checks are the last pass, not the iteration loop -- the owner asked for
|
||||
@@ -329,7 +350,11 @@ Then, roughly in dependency order:
|
||||
centred content, which otherwise has to say `Redraw` because only its own
|
||||
draw knows where the middle was. Size is the harder half: a declared size
|
||||
beside the one `draw` returns is two sources of truth for one thing, so
|
||||
settle what each means before building it.
|
||||
settle what each means before building it. This is independent of #18's
|
||||
retained-update fix now that `Aligned` is in its generated coverage; land
|
||||
#18 first, then design built-in alignment and size together as the next
|
||||
structural slice rather than mixing that representation change into this
|
||||
performance correction.
|
||||
- **`OnResize::Translate`, which still does nothing.** The chain removed half
|
||||
its obstacle: a placed widget's slot holds the box it was offered while its
|
||||
drawing is a set of fractions of that box, so the two are no longer one
|
||||
|
||||
Reference in new issue
Block a user