RUST.md, DECISIONS.md, IRIS.md: iris's host-GPU frame time, 2026-09-05

Takes the -gpu host pair the earlier software-mode comparison flagged as
missing. Under real GPU rendering (--features force-gles: the default
Vulkan backend has no adapter at all under plain host-GPU boot, confirmed
by the exact wgpu error), iris's median frame (15.0ms) is faster than
Compose's (20.0ms) on the same session content -- the opposite shape from
the software-mode table. The new redraw-to-submit/submit-to-present split
shows iris's own CPU work is a median 0.2ms per frame; almost the whole
frame is time handing off to the driver, consistent with (but not proof
of) the software-mode gap being mostly SwiftShader's CPU rasterisation
cost rather than iris-specific slowness.

A same-mode software force-gles run, meant to isolate the backend, hit a
third distinct crash instead (SwiftShader's GL path reports itself as
OpenGL ES 3.0, which has no compute shaders, and iris's device request
assumes them unconditionally) -- real scope to fix, not done here, so the
software-mode question stays open. A real intermittent touch-scroll
dropout was also reproduced (six consecutive swipes produced zero
redraws while taps kept working; an identical retry then succeeded) and
is not explained. The idle-redraw and virtualised-culling findings from
the software-mode pass were confirmed to hold under real GPU rendering
too.

DECISIONS.md's DEFERRED item carries the updated table; the iris-vs-
Masonry choice itself is still Iris's to make. IRIS.md records the
FrameReport::record_split/FrameStats::cpu_p50/gpu_wait_p50 API from the
prior commit (e2a1fad), which this pass's measurement used.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Fable 5.1 committed 2026-09-05 20:07:20 -04:00
1 parent e2a1fadbec
commit e49d0e606f
3 files changed
+226

No files matched your search

+173
View File
@@ -58,6 +58,26 @@ session spending an afternoon on them again.
still hers to make, not decided here.
I5's own box, "Update, 2026-09-05, later the same day" has the full
account.
- **The `-gpu host` pair this box's own DEFERRED item flagged as missing
is now taken, 2026-09-05, and it changes the picture.** Under real GPU
rendering (`--features force-gles`, since the default Vulkan backend has
no adapter under plain host-GPU boot -- confirmed by the exact crash
message), iris's median frame (15.0ms, `FrameReport`) is *faster* than
Compose's (20.0ms, in-app report) on the same session content, the
opposite shape from the software-mode table. The new CPU/GPU split
(`FrameReport::record_split`, `iris/core/src/render/frame_report.rs`,
commit `e2a1fad`) shows why: iris's own redraw-to-submit work is a
median 0.2ms; almost the whole frame is time handing off to the driver.
Software-mode `force-gles` crashes for a third, distinct reason
(SwiftShader's GL path reports itself as ES 3.0, which has no compute
shaders, and iris's device request assumes them unconditionally), so
this pass could not isolate SwiftShader-Vulkan as the sole cause of the
software-mode gap. A real intermittent touch-scroll dropout was also
reproduced and left unexplained (not the same as the earlier pass's
script-bug dropout). I5's box, "Where iris's frame time goes,
2026-09-05, the `-gpu host` pass" has the full account, all four
findings, and what verification did and did not re-run.
`DECISIONS.md`'s DEFERRED item has the updated table.
- **I5's Android integration is done and measured, 2026-09-05.** The
transcript screen runs on-device against a real `ai-server`, with real
scrolling, real touch-drag panning and tap-by-name accessibility all
@@ -3012,6 +3032,159 @@ silently on real hardware.
this table's headline numbers below rather than a decision made
here.
**Where iris's frame time goes, 2026-09-05, the `-gpu host` pass this
box's own "not attempted" flagged.** New code first: `FrameReport`
(`iris/core/src/render/frame_report.rs`) now splits each sample at
`queue.submit` into `cpu_p50` (redraw-start to submit -- iris's own
layout/text/primitive-building work) and `gpu_wait_p50` (submit
through `present()` -- wherever a driver/compositor wait would show
up), and a new `force-gles` Cargo feature
(`iris/Cargo.toml`/`android-app/Cargo.toml`) switches the Android
`wgpu::Instance` from `Backends::PRIMARY` to `Backends::GL` at
compile time -- there is no way to hand an environment variable to an
already-launched Android process on this machine, so a runtime
switch was not an option. `app/iris-scroll.sh` extracts
`transcript-bench.sh`'s exact 24-swipe/6-cycle loop for iris's own
demo app. Commit `e2a1fad`.
**Host GPU, default (Vulkan) backend -- crashes immediately, exactly
as the "Vulkan in the emulator" section already predicted.** Cold
boot (AVD snapshot cleared by hand -- `emu`'s wrapper has no flag for
this, matching the documented GPU-config-switch trap) under `emu
up`'s own default `GPU_HOST_FEATURES=-feature -Vulkan` (Vulkan
explicitly *off* under plain host-GPU boot, confirmed by reading
`emulator-tools/bin/emu` itself), release build, `transcript-screen`.
`dev.iris.android.demo` aborts on `surface_changed` before a single
frame:
Abort message: 'Could not get adapter!: NotFound { active_backends: Backends(VULKAN),
requested_backends: Backends(VULKAN | METAL | DX12 | BROWSER_WEBGPU),
supported_backends: Backends(VULKAN | GL), no_fallback_backends: Backends(0x0),
no_adapter_backends: Backends(VULKAN), incompatible_surface_backends: Backends(0x0) }
i.e. this boot mode offers a GL device only, and `wgpu`'s default
`Backends::PRIMARY` never tries it. Rebuilt and reinstalled with
`--features transcript-screen,force-gles`: no crash, real content on
screen (`wgpu_hal::gles::egl` picks up virgl/the real host GPU, same
harmless `D2`/`D2Array` heuristic warning I2 already found benign).
**Host GPU, `force-gles` -- a real number, and it changes the
picture.** Same 24-swipe/6-cycle loop (`app/iris-scroll.sh`), same
sandbox session content class as the earlier pass (a fresh session,
`fda668c4d7e60dd9`, 30 identical sent messages -- heading/bold/
italic/inline-code/link/list/fenced-code -- since the earlier pass's
sandbox data does not persist across a server rebuild and had been
wiped by the time this pass started). Compose (debug) via
`transcript-bench.sh -s benchsession2` on the same session, same
emulator boot:
| app | build | GPU mode | frames | janky % | p50 | p90 | p99 | worst | cpu p50 | gpu-wait p50 |
|---|---|---|---|---|---|---|---|---|---|---|
| Compose (in-app report) | debug | host (virgl) | 1268 | 96.4% late | 20.0ms | 28.4ms | 37.7ms | -- | -- | -- |
| iris (`FrameReport`) | **release**, `force-gles` | host (virgl) | 62 | 41.94% | 15.0ms | 21.8ms | 37.1ms | 37.1ms | 0.2ms | 12.9ms |
**Under real GPU rendering, iris's median frame is faster than
Compose's, not 2-3x slower** -- the opposite shape from the
software-mode table above. And the CPU/GPU split says why: iris's
own redraw-to-submit work is a median 0.2ms, essentially free: almost
the entire 15.0ms median frame is `gpu_wait_p50` (submit through
`present()`), i.e. time spent on the driver/compositor side, not in
iris's layout or primitive-building code. That is consistent with
the software-mode number being dominated by SwiftShader's CPU
rasterisation cost rather than by anything iris itself does slowly --
the leading candidate the software-mode box above named but could
not confirm directly. It is **not proof**: `gpu_wait_p50` is "how
long the CPU was blocked handing the frame to the driver," per
`FrameReport::record_split`'s own doc, not a fenced GPU-completion
time, and the two apps' frame populations still differ in kind the
same way the software-mode table's caveats describe (Compose
free-runs its own Choreographer-driven count over 36.8s including
settle time; iris's 62 are real redraws only, matching this box's
"does not redraw while idle" finding below) -- so "15.0ms vs. 20.0ms"
should be read as "the same order of magnitude, on real GPU
hardware," not as a precise ranking.
**A real, reproduced instance of the previously-suspected
intermittent touch-scroll dropout**, distinct from the earlier
pass's script-bug explanation for its own dropout. After a fresh
`am start`, six consecutive swipes (`ui-trace record --do "swipe ..."`,
matching `iris-scroll.sh`'s own gesture exactly) produced **zero**
`render():` log lines and a screenshot confirming the list had not
moved, while a `tap 'Message'` immediately before and after each
block of swipes reliably produced `render()` calls -- so touch
delivery and the render loop were both alive throughout; only the
drag-to-pan gesture failed to register. A later, otherwise-identical
retry (same coordinates, same session, same app process still
running) succeeded and produced 120 `render()` calls with `active`
climbing smoothly 63->113 across the gesture (see below). Not
root-caused this pass -- `iris::sense::DragArbiter` (`iris/src/
sense.rs`) requires a `dy`/`dx` past `DRAG_SLOP` on an early frame of
the gesture to leave `Undecided`, so a dropped or coalesced initial
`ACTION_MOVE` under emulator input-injection load is the leading
candidate, but this pass did not instrument that path to confirm it.
Practical effect on the table above: the 62-frame iris run was the
one attempt this pass that worked on the first try, so it stands as
the number, but a next pass should budget for retries rather than
treating a single `iris-scroll.sh` invocation as reliable.
**Redundant-work check (no optimising, as instructed), host GPU,
`force-gles`.** (1) **Idle redraw: zero**, confirmed fresh this pass
-- `adb logcat -c` then a 5s settled wait with nothing on screen
touched produced no `render():` lines, matching the software-mode
pass's earlier finding on the same code path. (2) **A scrolling
frame does not relayout the whole list**: during the successful
120-call run, `active=` climbed 63, 68, 73, 78, 83, 88, 93, 98, 103,
108, 113 -- one small step per frame-or-two, not a jump to the full
148-widget count (`widgets=148` in the same log lines), consistent
with I3's virtualised culling doing its job under real GPU rendering
the same way the software-mode pass found under SwiftShader. Neither
check isolates further than the software-mode pass already did; both
are restated here because this pass had a live device to check them
against a different backend, and they held.
**Software mode (`EMU_GPU=software`), `force-gles` -- crashes for a
third, different reason, so this pass could not isolate
SwiftShader-Vulkan as the sole cause of the software-mode gap.** Cold
boot under `EMU_GPU=software`, same release build with `--features
transcript-screen,force-gles`. `wgpu_hal::gles::adapter` finds a real
adapter (`Renderer: Android Emulator OpenGL ES Translator (Google
SwiftShader)`, `Version: OpenGL ES 3.0`), further than the plain
host-GPU/default-backend attempt got -- but `AndroidRenderer::new`'s
device request then aborts:
Abort message: 'Could not get device!: RequestDeviceError { inner: Core(LimitsExceeded(
FailedLimit { name: "max_compute_workgroups_per_dimension", requested: 65535, allowed: 0 } )) }'
i.e. iris's device descriptor asks for compute-shader limits
unconditionally, and SwiftShader's software GL path reports itself
as OpenGL ES 3.0 -- compute shaders are an ES 3.1+ feature, so the
allowed limit is 0. This is a different failure from both the host-
GPU/default-backend crash above (no adapter at all) and the earlier
Venus/gfxstream failure "Vulkan in the emulator" documents (a
different Vulkan implementation's external-memory gap) -- three
distinct emulator/backend incompatibilities found across this
project's Android work, not one recurring bug. **Not fixed this
pass**: making iris's device request tolerant of a downlevel GL
adapter (requesting compute limits only when the adapter actually
reports them) is real scope, not a measurement task. Consequence for
the software-mode question this step was meant to answer: it remains
open whether SwiftShader-Vulkan specifically (rather than GLES in
general) explains the ~80-150ms software-mode numbers, since no GLES
number under software mode could be taken at all.
**Verification, this update.** `cargo fmt --all` (no diff),
`cargo clippy --workspace --all-targets` (no warnings from the new
code; pre-existing `wgpu`/`winit`/`naga` future-incompat notices
only) both re-run and clean this pass. `cargo test --workspace` and
`cargo ndk ... test`/`clippy` for `iris-android-app` were **not**
re-run this pass -- the previous pass on this identical diff had
already run and reported them clean, and this pass's host was
disk-pressure-limited (93% full, a concurrent `ai-server` rebuild in
progress) when the repeat attempt was made, so it was stopped rather
than left to spend 50+ minutes doing no useful work; see commit
`e2a1fad`'s own message. `docs/DECISIONS.md`'s DEFERRED item is
updated with this section's host-GPU table below.
## For the next agent
What to do when you pick this up, in order, so nothing here has to be