Files
ai-app/app
irisandClaude Opus 5 e65c961e1e Give every row its own layer, and stop culling during draw
The piece of a lazy list this had not rebuilt was the render node per
item. Without one, a row's glyphs are recorded into its parent's display
list, and that list is re-recorded every frame the parent is invalidated
-- which, while the list is scrolling, is every frame. With one, the row
is recorded once and afterwards moved by a transform, and the render
thread culls the ones off screen itself.

The draw-phase culling it replaces could not have won, and the two are
mutually exclusive rather than complementary: `onScreen` and `RowWindow`
read the scroll position from inside every row's and every block's
drawing, and reading a scroll position during draw invalidates the
drawing it is in. So the machinery for deciding which rows need not be
drawn was re-recording all of them, every frame, to decide it. Keeping
both would have bought the cost of the first and none of the second.

Measured on the emulator over a thirty-swipe scroll: **one** row display
list recorded across 481 frames, against 1,544 recordings for the same
gesture before, and the frame's draw phase down from 4.2ms to 2.9ms at
the median.

Two things this also corrects. `LAYOUT_MEASURE_DURATION` is the *View*
hierarchy's pass, and Compose is one view -- `AndroidComposeView.
dispatchDraw` calls `measureAndLayout()` before it records, so all of
Compose's own measurement, text shaping above all, is reported inside
DRAW_DURATION. Every "layout is 0.0ms, so nothing is being re-measured"
reading in this file's history was reading a bucket that never contained
it. And the retain window is now load-bearing for a second reason: a
retained display list costs memory, so bounding what is retained bounds
that too.

Found by the ai-app-2-6d session reading the render reports against this
code; the diagnosis and the ordering are theirs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 03:18:04 -04:00
..