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.
|
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 `cdec293`, thirteen commits, 50
|
worktree `/home/bob/repos/iris-pr18`, head `84f589e`, fifteen commits,
|
||||||
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.
|
||||||
|
|
||||||
**The one thing waiting on the owner.** `tabs` at 1920x1200 is no longer
|
**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
|
**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.
|
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.**
|
- **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
|
The remaining-region trial removes many mismatches, but an unknown child's
|
||||||
`UiRegion::FULL` to read its length, then places it again at the box it
|
measured length can still make its final box differ, and nested containers
|
||||||
worked out, and the second place redraws whatever the first drew. It
|
compound those redraws. This is now the largest CPU layout cost.
|
||||||
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.
|
|
||||||
- **Redundant draws rewrite primitives.** A cold frame writes 48,050
|
- **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,
|
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
|
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
|
## The random trees
|
||||||
|
|
||||||
`iris::random` grows a seeded tree -- spans in every direction holding two to
|
`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
|
four children, stacks, scrolls on either axis, alignment on either axis,
|
||||||
sides its own number,
|
padding with each of its four sides its own number,
|
||||||
rects with varying opacity, text both wrapping and overflowing, a declared size
|
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
|
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
|
(`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
|
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.
|
its own.
|
||||||
|
|
||||||
Render checks are the last pass, not the iteration loop -- the owner asked for
|
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
|
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
|
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
|
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
|
- **`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
|
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
|
drawing is a set of fractions of that box, so the two are no longer one
|
||||||
|
|||||||
Reference in new issue
Block a user