diff --git a/docs/IRIS_EXTRACTION_HANDOFF.md b/docs/IRIS_EXTRACTION_HANDOFF.md index 8dc59de..2fd6c68 100644 --- a/docs/IRIS_EXTRACTION_HANDOFF.md +++ b/docs/IRIS_EXTRACTION_HANDOFF.md @@ -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