Record what the layout actually costs, and what of it is necessary
The random tree at depth 7 measured against the two revisions before it: the GPU pass is a tenth of a millisecond everywhere, and every difference is the CPU laying out. A repaint of one leaf costs 1313 draws and 6.5 ms where the pre-#16 code cost one draw, because `redraw` escalates to the top size reader before knowing whether the size changed at all. Drawing first and escalating only on a different size takes that to one draw and takes a resize below the pre-#16 number, but misplaces four widgets in the resize case, so it is the owner's to call. Also records the measure-by-drawing multiplier -- 1.3x the drawn widgets at depth 4, 5.2x at depth 8 -- and that scrolling is now part of the random trees. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ba275fc966
commit
38f6f9ac07
1 file changed
+55
-3
@@ -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 `4178dfb`, twelve commits, 49 tests
|
||||
passing, fmt and clippy clean. It is LAYOUT.md §2's position chain, generalised
|
||||
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
|
||||
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
|
||||
@@ -115,10 +115,62 @@ A chain is irrelevant at an example's couple of hundred primitives; a
|
||||
transcript's glyphs are tens of thousands, which is the regime `chain_cost`
|
||||
measures.
|
||||
|
||||
### How much of that work is necessary
|
||||
|
||||
Measured 2026-09-14 on a random tree at seed 1, depth 7 -- 1061 widgets, 839
|
||||
of them drawn, 10,872 primitive instances -- against the same rig at `43ce8c7`
|
||||
(the last commit before #16 sized a widget by drawing it) and at `f942385`
|
||||
(#16 itself). The rig is a `Harness` load counting widget draws, text shapes
|
||||
and instance writes per frame, plus the GPU pass through timestamp queries;
|
||||
it lives in the scratch worktrees `/home/bob/repos/iris-size-{old,new}` and is
|
||||
not in the repository, because the counters it reads are patched into
|
||||
`iris-core`.
|
||||
|
||||
| per frame | before #16 | #16 | #18 head |
|
||||
| --- | --- | --- | --- |
|
||||
| cold layout | 19.3 ms, 839 draws | 30.9 ms, 3317 | 23.6 ms, 2755 |
|
||||
| repaint one leaf | 0.000 ms, 1 draw | 8.5 ms, 1683 | 6.5 ms, 1313 |
|
||||
| scroll one scroller | 0.001 ms, 1 draw | 8.9 ms, 1683 | 6.5 ms, 1313 |
|
||||
| resize the output | 3.2 ms, 839 draws | 31.8 ms, 6940 | 25.2 ms, 5512 |
|
||||
| GPU pass | 0.129 ms | 0.114 ms | 0.115 ms |
|
||||
|
||||
**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:
|
||||
|
||||
- **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.
|
||||
- **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
|
||||
repaint still uploads 5,863 instances where the pre-#16 code uploaded 106.
|
||||
|
||||
## The random trees
|
||||
|
||||
`iris::random` grows a seeded tree -- spans in every direction holding two to
|
||||
four children, stacks, padding with each of its four sides its own number,
|
||||
four children, stacks, scrolls 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
|
||||
|
||||
Reference in new issue
Block a user