The defect P1a's screenshots found, and the one that mattered: `Rect::is_size_independent()` answered `true`. A `Rect` fills whatever region it is handed, so its content *is* the region -- and `draw_inner`'s fast path, which rewrites a widget's primitives with `r.outside(&from).within(®ion)` instead of redrawing it, cannot reproduce that once a region carries both `rel` and `abs`. What it looked like: a fenced code block's background kept the height of the provisional full-region draw `Span` does in its first phase, so one fence's panel covered every block below it and every row below that, with the text underneath laid out correctly. Likely the same cause as RUST.md's older "the composer bar's grey background is not drawn". Also here: a quote's bar is a `Stack` background behind padded text rather than a two-child `Span(Dir::RIGHT)` (one widget fewer and no provisional pass), and `transcript-ui`'s `transcript` example gains a row holding one of every block kind -- the fixture's own heading, paragraph, fence and table source, plus a list and a quote, which the fixture has neither of. docs/bench/p1a-2026-09-06/ has the pairs and docs/RUST.md's P1a box names what still differs. The iris half is from the desktop backend because this emulator cannot draw iris's glyphs at all (solid boxes, reproduced on the previous commit, with Compose drawing text correctly on the same AVD); both routes to Vulkan on this AVD were tried and both fail. Bench stream phase, assertions live, no abort: p50 53.0ms p90 108.6ms p99 132.0ms against 52.8/108.1/137.3 before -- unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
406 lines
26 KiB
Markdown
406 lines
26 KiB
Markdown
# Decisions taken for Iris to review
|
|
|
|
Short list of design choices made by the design agent without asking, so
|
|
they can be judged and reversed later. Detail lives in RUST.md (and IRIS.md
|
|
for iris API changes); this file is only the summary. Newest first. Items
|
|
marked **DEFERRED** are ones the agent chose not to decide alone.
|
|
|
|
## 2026-09-06 (how a markdown block looks, P1a)
|
|
|
|
- **A table is drawn as padded monospace columns, not as a grid.** Your
|
|
call to reverse. Compose draws a real grid: cells on a tint, each
|
|
column with a 136dp floor, scrolling sideways when there are too many.
|
|
iris has no grid widget, and building one would be a widget per
|
|
markdown feature -- which is the thing the block model exists to avoid.
|
|
In a monospace face a character count *is* a pixel width, so padding
|
|
each cell to its column's width is alignment, the widths are still
|
|
measured from the cells, and a table that is too wide pans sideways
|
|
through the same mechanism a code fence already uses. The header is
|
|
bold with a rule under it, and a long cell wraps inside its column
|
|
(capped at 28 characters, which is what fits three columns across a
|
|
phone). **What it trades:** no cell borders, and a table looks like
|
|
code rather than like a table. If you want the grid, it is a new widget
|
|
and it is a day's work.
|
|
- **Three block frames, and only three.** A heading, paragraph and list
|
|
are plain text with spans; a fence and a table are a rounded panel that
|
|
does not wrap; a quote is a bar with the text padded past it.
|
|
Everything else markdown says is expressed in span styles, which cost
|
|
no widgets and no layout nodes. So a new markdown feature is a span,
|
|
not a widget.
|
|
- **A list's marker is part of the text, so a wrapped item's second line
|
|
returns to the left margin.** Compose keeps it indented by giving the
|
|
marker its own column. Doing the same here needs per-line indent in
|
|
iris's text attributes; it is written down rather than done, because
|
|
the list items in a real reply are usually one line.
|
|
- **A link opens on a tap and not on the end of a drag.** A press that
|
|
panned the transcript past a link, or that held long enough to start a
|
|
selection, does not follow it -- decided by the same gesture machine
|
|
that decides pan-versus-select, so there is one rule rather than two
|
|
that can disagree.
|
|
|
|
## 2026-09-06 (composer scroll and the streaming block model)
|
|
|
|
- **A streamed message becomes a column of per-block widgets.** Decided by
|
|
the design agent; recorded here because it is the shape of every message
|
|
on screen. A transcript row is one `TextEdit` today, so a streamed delta
|
|
re-shapes the entire message through parley on every event -- the stream
|
|
phase is the one place iris is behind Compose on your phone (p50 18.2ms
|
|
vs 13.4ms). A row becomes a column of one widget per markdown block
|
|
(paragraph, heading, fence, list, table) and a delta replaces only the
|
|
last block, keeping every earlier block's layout. **Rejected:** splitting
|
|
parley's layout at block boundaries inside one text widget (couples
|
|
iris's text widget to markdown structure, and parley has no incremental
|
|
API), and caching shaped runs per paragraph inside `TextEdit` (a second
|
|
cache with its own invalidation beside the glyph cache). Chosen because
|
|
P1's markdown block model is needed anyway, so the split happens once, in
|
|
`client-core`, and iris stays a text renderer. **Status: designed, not
|
|
built** -- this pass spent its budget on the composer's three layout
|
|
defects; docs/RUST.md has the design and the pass conditions.
|
|
- **The composer's overflowing text now scrolls on a finger**, capped at
|
|
six lines and clipped to the bar. Reverses the "still does not scroll"
|
|
item below.
|
|
- **A widget may not report a `dp` length** (see IRIS.md). A rule for
|
|
widget authors, enforced by a `debug_assert!`; nothing changes for app
|
|
code.
|
|
|
|
## 2026-09-06 (stale-primitives and touch-scroll pass)
|
|
|
|
- **A vertical drag inside a focused composer now scrolls rather than
|
|
selects.** Android's own `EditText` does this -- a vertical drag scrolls
|
|
the field, and only a long press starts a selection -- so the platform
|
|
decided it. What it costs: you can no longer drag straight down inside
|
|
the composer to select several lines of what you typed; use a long press
|
|
and then drag, or drag sideways. Say if that trade is wrong for you.
|
|
- **`Scroll` gets a finger pan but no fling.** `List` flings; a scroll area
|
|
does not, because it has no per-frame tick to animate one and the areas
|
|
it wraps are at most a screenful (Android does not fling a six-line text
|
|
box either). Easy to add later if a scroll area ever wraps something long.
|
|
- **The composer still does not scroll its overflowed text**, though the
|
|
mechanism it needs is now in place. Wrapping the field in `.scrollable()`
|
|
was tried and reverted the same day: `Scroll` measures its content and
|
|
container against the *window*, so inside the `MaxSize` that caps the
|
|
composer at six lines the two are in different spaces and the field pans
|
|
itself entirely out of the bar (measured on the emulator with 474
|
|
characters in it -- the bar collapsed to its padding). Fixing that means
|
|
`Scroll` measuring against its own offered box, which is a change to a
|
|
widget the transcript and the bench shell both use, so it is its own
|
|
piece of work rather than a rider on this one.
|
|
|
|
## 2026-09-06 (defect pass)
|
|
|
|
- **The keyboard-open diagnostics overlay is gone; the capture only
|
|
logs now.** It was added when `on_insets_changed` was not firing at all
|
|
and there was no way to get a report off the phone. It fires reliably
|
|
since the activity went edge-to-edge -- and what that looks like in
|
|
use is a full-screen report covering the app **every time the keyboard
|
|
opens**, with its own Copy/Close buttons sitting underneath the
|
|
keyboard, so it cannot be dismissed (reproduced on the emulator this
|
|
pass: two `tap 'CLOSE'` runs left it up). An interruption for something
|
|
nobody asked for, over the app you are trying to type into. The named
|
|
`Diagnostics` button still shows the same text on demand, and the new
|
|
`iris surface:`/`iris insets:` log lines carry the lifecycle a `logcat`
|
|
pull needs. Reversible: `capture_keyboard_diagnostics` is still the one
|
|
place this is decided, and `PlatformHandle::show_diagnostics_overlay`
|
|
is still there.
|
|
|
|
- **The bench shell's report pane is sized to its report, not to a share
|
|
of the window.** It held `.height(rest(1))` beside the transcript's
|
|
`rest(2)`, so an *empty* `TextEdit` reserved a third of every screen --
|
|
which is what Iris's "the app does not start with keyboard spacing
|
|
correct" screenshot was showing, with the composer two thirds down and
|
|
black below it. It is `.max_height(dp(260))` now and sits above the
|
|
transcript rather than under the composer, where it was eating the
|
|
navigation-bar clearance. Cost: a filled report is clipped at 260dp
|
|
rather than scrolling (a `Scroll` there drew itself off the top of the
|
|
screen, since `Scroll` pins to the end of its content and reports its
|
|
content's full length to the parent -- worth fixing in `Scroll`, not
|
|
worked around here). "Copy report" and `logcat` still have the whole
|
|
thing.
|
|
|
|
## 2026-09-05
|
|
|
|
- **iris no longer asks every device for compute-shader limits it never
|
|
uses.** `adapter.request_device` (both `iris/src/android/render.rs` and
|
|
`iris/src/default/render.rs`) used `Limits::default()` plus an override
|
|
for `max_buffer_size`, and `Limits::default()` unconditionally requests
|
|
desktop-tier compute limits (`max_compute_workgroups_per_dimension:
|
|
65535`, per `wgpu_types`) even though nothing in `iris`/`iris-core`
|
|
creates a `ComputePipeline` or writes a `@compute` shader stage —
|
|
confirmed by grepping the whole tree, not assumed. That crashed
|
|
`request_device` outright on the Android emulator's software GL path
|
|
(`EMU_GPU=software`, `--features force-gles`): SwiftShader's GL reports
|
|
itself as OpenGL ES 3.0, which has no compute shaders at all, so the
|
|
adapter's real limit is 0 against the unconditional request for 65535 —
|
|
`RUST.md`'s "Software mode ... crashes for a third, different reason,"
|
|
2026-09-05, earlier today. The same would happen on any real
|
|
GLES-3.0-only Android device, not just the emulator. Fixed by a new
|
|
`iris_core::device_limits()` (`iris/core/src/render/mod.rs`), shared by
|
|
both platform backends so the two requests cannot drift, that zeros the
|
|
six `max_compute_*` fields explicitly rather than switching to a
|
|
downlevel `Limits` preset — `Limits::downlevel_webgl2_defaults()` was
|
|
considered and rejected: it also zeros
|
|
`max_storage_buffers_per_shader_stage`, and `shader.wgsl`'s vertex stage
|
|
reads four `var<storage>` buffers (rects, glyphs, masks, move_offsets),
|
|
so that preset would trade the compute crash for a bind-group-layout
|
|
one on the same downlevel hardware this is meant to support. No
|
|
capability check or fallback path was needed since nothing is being
|
|
disabled — the request is simply narrowed to what the pipeline actually
|
|
uses. `rigs/gpu-probe`'s own mirrored limits (it is deliberately its own
|
|
crate, not a workspace member, so it cannot call `device_limits()`
|
|
directly) were updated to match, and confirm `IRIS DEVICE: ok` against
|
|
this VM's own Vulkan and GL adapters. **Not verified this pass**: the
|
|
specific SwiftShader-ES-3.0 crash this fixes, on-device — the
|
|
`EMU_GPU=software` cold boot this needs would have force-restarted this
|
|
checkout's emulator while another session was actively running its own
|
|
app on it (`com.example.aiapp` had window focus at the time), so it was
|
|
left for a pass when the emulator is free rather than disrupting that
|
|
session. Everything reachable without the emulator is clean: `cargo
|
|
fmt`/`clippy --workspace --all-targets`/`test --workspace`, `cargo ndk
|
|
build`/`clippy` for `iris-android-app` with `force-gles`, and
|
|
`gpu-probe` against this VM's own Vulkan and GL(ES 3.2, which still has
|
|
compute and so would not have reproduced the crash even before this
|
|
fix — not a substitute for the real ES-3.0 test).
|
|
|
|
- **P0's Compose half is built and smoke-tested on the emulator** — the
|
|
`bench` build type, the shared `app/bench-fixture/` transcript, and an
|
|
in-process fake backend (`BenchFixture.kt`/`BenchNetwork.kt`) that
|
|
answers `TranscriptSource`/`EventStream` from an in-memory event log
|
|
instead of a real server, so the fold and paging under test are the real
|
|
ones. Full account, the smoke run's report, and what is deliberately
|
|
left (the iris half, the real on-phone runs) are in RUST.md's P0 box.
|
|
Not a decision to review so much as the gate itself now being runnable —
|
|
flagged here because it is the first half of something Iris explicitly
|
|
asked to see before P1.
|
|
|
|
- **P0's iris half is also built and smoke-tested on the emulator,
|
|
2026-09-05.** A new `bench` Cargo feature on `iris-android-app`, on top
|
|
of `transcript-screen`: the same checked-in fixture (`include_str!`, no
|
|
asset pipeline needed), the same 24-swipe scroll loop animated through
|
|
`List::scroll` and the same 400-event/20s streaming phase through
|
|
`fold_event`, "Run benchmark"/"Copy report" as named accessible
|
|
controls, and the same three added report fields (process CPU time,
|
|
peak RSS, battery current) via direct JNI calls
|
|
(`bench_jni.rs::PlatformHandle`) since `android_view` has no
|
|
`BatteryManager`/`ClipboardManager` wrapper of its own. One small public
|
|
API addition to get there: `AndroidAppState::platform_ready` (`IRIS.md`),
|
|
a default-no-op lifecycle hook handing an implementor a `JavaVM` +
|
|
`GlobalRef` it can call Java through from any thread. Packaged with a
|
|
new `release` build type on `iris-android-app`'s own Gradle project
|
|
(there was previously only `debug`), signed with the same key
|
|
`app/build-apk.sh` generates. Smoke run and the full report are in
|
|
RUST.md's P0 box; not attempted this pass: the real on-phone runs and
|
|
Iris's pass/fail call, which is the actual gate.
|
|
|
|
- **The intermittent touch-scroll dropout is root-caused and fixed: a
|
|
missed `ACTION_DOWN` hit-test, not the previously-suspected coalesced
|
|
first `ACTION_MOVE`.** Diagnosed by temporary logcat tracing of every
|
|
touch event, `DragArbiter` state transition and `Selection::drag`
|
|
dispatch (removed once confirmed), reproduced on this checkout's own
|
|
emulator against a real sandbox session. The trace showed the actual
|
|
mechanism: a gesture's `ACTION_DOWN` lands wherever the finger actually
|
|
is, which is not guaranteed to fall inside the same row-local sensor
|
|
region a later `ACTION_MOVE` in the same gesture lands in (a row's own
|
|
padding/gap, or its non-selectable sender-name header, is
|
|
pointer-transparent to `iris::sense::CursorSense`). When that happens,
|
|
the widget that ends up handling the gesture never saw `PressStart`, so
|
|
`DragArbiter` sits in `Idle` — which answers every subsequent frame with
|
|
`Undecided` and has no way to tell "no press is happening" from "a press
|
|
is happening but I missed its start," so it never recovers on its own
|
|
for the rest of that gesture. One real trace showed exactly this: touch
|
|
`Down`/`Move`/`Up` all delivered correctly, but zero `PressStart`
|
|
reaching the arbiter, `state=Idle` unchanged from first frame to last.
|
|
Fixed at the call site that has the context to recover
|
|
(`iris::transcript_ui::selection::Selection::drag`,
|
|
`iris/transcript-ui/src/selection.rs`): a new `DragArbiter::is_idle()`
|
|
(`iris/src/sense.rs`) lets it notice a `Pressing` frame arriving with the
|
|
arbiter still `Idle` — which can only mean a missed `PressStart`, since a
|
|
`Pressing` sense requires the button to genuinely be down — and start the
|
|
press there instead of where it was missed. Three new unit tests in
|
|
`sense.rs`'s `drag_arbiter_tests` and one in `transcript-ui`'s
|
|
`selection::tests` (the latter fails on the code before this fix).
|
|
Commit follows. Not the same failure the earlier pass's `DECISIONS.md`
|
|
DEFERRED item speculated about (a coalesced first `ACTION_MOVE` skipping
|
|
slop detection) — that hypothesis is now ruled out; the arbiter's own
|
|
slop/long-press logic was never wrong. RUST.md's I5 box,
|
|
"Touch-scroll dropout root-caused, 2026-09-05" has the full trace.
|
|
- **P0, a phone benchmark gate before any porting, asked for by Iris
|
|
2026-09-05**: "before P1 I'd like to see benchmarks & also maybe stress
|
|
test on my own phone ... If it doesn't match compose reasonably well then
|
|
I don't think I'd wanna continue." Design (RUST.md's P0 box has the
|
|
detail): the same embedded synthetic fixture in both apps with no server
|
|
needed; the same scripted scroll loop then a streaming phase, run
|
|
programmatically since the phone has no usable system tracing and no
|
|
agent can drive it; the same report from both (frames, janky %, p50/p90/
|
|
p99, process CPU time, peak RSS, battery current where readable) with a
|
|
copy button; the iris app under its own id and the Compose one as a new
|
|
`bench` build type with an id suffix, so neither replaces her production
|
|
install; two arm64 APKs plus instructions delivered under `~/host/bench/`.
|
|
The gate is hers: iris within a reasonable margin of Compose release on
|
|
p50, p99 and CPU time, no crashes, no visible stutter. If it fails, the
|
|
port stops.
|
|
- **The rest of the port is one UI crate, `iris/app-ui`, grown out of
|
|
`iris/transcript-ui` rather than started beside it.** It holds a
|
|
`Screen` enum plus a back stack — the Rust equivalent of `AppRoot.kt`'s
|
|
`when` — and `iris/desktop-app`/`iris/android-app` become thin entry
|
|
points over it. Chosen over a fresh crate because `transcript-ui`
|
|
already has the right generic shape (`Rsc: HasEvents` +
|
|
`Rsc::State: FocusHost`) and the `client-core`/`event-model` path
|
|
dependencies every later screen needs, so growing it in place is the
|
|
smaller diff. Platform-only code (notification service, share target,
|
|
QR scanner, Keystore token, deep-link enrolment) stays in the E3/E5
|
|
Java shell (`android-shell/` + `app/shellApp`) rather than moving into
|
|
this crate, since none of it is a screen. The Android APK is built by
|
|
`cargo xtask apk` (E5), merging the app-ui cdylib into the E3 shell so
|
|
there is one app rather than a demo shell plus a service shell.
|
|
`app/androidApp` (the Compose app) stays untouched and is the baseline
|
|
every step is measured against, until parity is reached (P7 decides
|
|
the switch, and is itself a load-bearing decision left to Iris). Order
|
|
is by risk to the daily-use path: session screen first (P1, where
|
|
every hard behaviour already lives), then the shell merge and a real
|
|
phone install (P2), then root tabs (P3), the explorer (P4),
|
|
settings/enrolment (P5), desktop parity (P6), and the cutover itself
|
|
(P7). Full plan: RUST.md's "The port, in order (decided 2026-09-05)".
|
|
- **iris gets its own measured frame report, rather than waiting on a
|
|
`dumpsys`/`gfxinfo` answer that cannot see a `SurfaceView`'s GPU-drawn
|
|
frames.** `iris_core::FrameReport` (`iris/core/src/render/frame_report.rs`)
|
|
times each frame's wall clock from the same point `render()`'s redraw
|
|
starts to just after `queue.submit` + `present()` — the span Compose's
|
|
own render report and `gfxinfo` both count — into a fixed 4096-entry
|
|
ring (no allocation per frame; `report()` is the only place that
|
|
allocates, and only on a button tap). The report gives total frames,
|
|
janky % over the same 16.7ms budget `gfxinfo` uses, P50/P90/P99 and the
|
|
worst, plus a reset. Exposed the way the Compose app's copy-button
|
|
report already is: two named controls ("Frame report", "Reset frame
|
|
report") on the transcript screen, tappable by accessibility name via
|
|
`ui-trace`, logging under this crate's fixed `android_logger` tag
|
|
(`iris-android-app`) so a script can grep `"iris frame report"` the way
|
|
`transcript-bench.sh` greps `"ai-app render report"`. The report's own
|
|
`Display` line says plainly that it measures up to the `present()` call
|
|
returning, not GPU/compositor completion — wgpu's `present()` is not
|
|
fenced against either, so presenting that span as "time to reach the
|
|
screen" would be a measured-looking number that is actually inferred,
|
|
which the standing UI rule forbids.
|
|
- **`ui-trace` gains a hold-then-drag gesture, additive, in
|
|
`emulator-tools`.** Neither of its two existing actions can produce
|
|
"hold stationary for `LONG_PRESS`, then move without lifting" — `tap`
|
|
has no hold and `swipe X1 Y1 X2 Y2 MS` interpolates motion across its
|
|
whole duration from t=0. A new action presses, waits, then moves to a
|
|
second point and releases as one continuous touch (raw
|
|
`sendevent`/`MotionEvent` injection, extending whatever mechanism the
|
|
existing `swipe` already uses), so `DragArbiter`'s pan-vs-select rule
|
|
(`iris/src/sense.rs`, already covered by 8 unit tests against a
|
|
synthetic clock) can finally be driven on a real device instead of only
|
|
in a test harness.
|
|
- **Touch drag on a transcript row follows Android's own rule**: a vertical
|
|
drag pans the list immediately; a stationary press held 500 ms starts a
|
|
text selection which further dragging extends; a horizontal drag while
|
|
something is already selected extends that selection without the wait.
|
|
One `DragArbiter` per list decides it (`iris/src/sense.rs`). Chosen over a
|
|
"text layer always wins" or "list always wins" rule because either loses
|
|
one of the two gestures a reader expects.
|
|
- **E4's desktop shape is a new `iris/desktop-app` crate**: a winit window
|
|
holding `transcript-ui`'s screen beside a session list, talking to a real
|
|
`ai-server` through `client-core`. It enrols by pasting the same
|
|
`aiapp://enroll?…` link a phone scans (`client-core::config::EnrolledServer`)
|
|
and keeps it owner-only under `$XDG_CONFIG_HOME/ai-app-desktop/`. The
|
|
pinned CA is a path given on the command line, not baked in. Chosen so
|
|
the phone and desktop share one enrolment format and no second one is
|
|
invented.
|
|
- **I5's Android integration extends `iris-android-app` (I2's shell)
|
|
behind a Cargo feature (`transcript-screen`), rather than a third
|
|
shell crate.** That project already has the Gradle module, the
|
|
`IrisView`/`MainActivity` Java, and the JNI registration; the only
|
|
thing a second screen needs on top is a different `AndroidAppState`,
|
|
the same axis `tabs_ui::build`/`transcript_ui::build` already vary
|
|
along on the winit side. `tabs-screen`/`transcript-screen` are
|
|
mutually exclusive and each pulls in only its own deps, so the plain
|
|
tabs build (I2/I4) is untouched.
|
|
- **Order of remaining work, updated 2026-09-05**: the two in-flight
|
|
pieces and I5's Android integration are all done; next is giving iris
|
|
its own frame-timing report so item 3 below can be decided by a number.
|
|
- **DECIDED by Iris, 2026-09-05: iris is the app's framework; Masonry was
|
|
the calibration.** Her words: "I think iris definitely makes more sense
|
|
based on the limitations we've found." The limitations: Masonry has no
|
|
touch scroll on Android (E2), no per-span rich text and no cross-row
|
|
selection on the pinned commit (E2), and its keyboard bridge is a TODO
|
|
(E1); iris carries the same screen under the Compose baseline on the
|
|
host GPU (p50 15.0 ms against Compose's 20.0 ms, RUST.md's I5 box). What
|
|
follows: the E-steps are closed as calibration, and the port proceeds
|
|
on iris — screens, the shell (E3/E5), and `client-core` underneath.
|
|
The item below is kept as the record of what she decided from.
|
|
- **Was DEFERRED — whether to commit to iris over Masonry for `ai-app`.**
|
|
Updated 2026-09-05 with the clean comparison the recommendation wanted:
|
|
same sandbox session content, same emulator, `EMU_GPU=software`, one
|
|
session. Headline numbers (RUST.md's I5 box, "Clean scroll comparison,
|
|
2026-09-05," has the full table and every caveat):
|
|
|
|
| app | build | frames | janky % | p50 | p90 | p99 | worst |
|
|
|---|---|---|---|---|---|---|---|
|
|
| Compose (in-app report) | debug | 1102 | 99.0% late | 33.8ms | 50.6ms | 79.5ms | -- |
|
|
| Compose (`dumpsys gfxinfo`) | debug | 1499 | 21.15% (95.66% legacy) | 32ms | 48ms | 150ms (p99) | -- |
|
|
| iris (`FrameReport`) | **release** | 299 | 94.65% | 79.1ms | 98.6ms | 117.8ms | 212.6ms |
|
|
| iris (`FrameReport`, repeat) | **release** | 233 | 94.42% | 109.3ms | 130.8ms | 147.1ms | 150.5ms |
|
|
|
|
**Not a clean apples-to-apples reading, stated plainly rather than
|
|
smoothed over**: iris had to be built **release** (debug `SIGSEGV`s on
|
|
this emulator's Vulkan loader, I4's finding) against Compose's mandated
|
|
**debug** build, so this asymmetry likely *understates* iris's gap
|
|
rather than the reverse; the three frame-time sources measure different
|
|
things (Compose's own phase accounting vs. Android's HWUI deadline-miss
|
|
definition vs. iris's redraw-start-to-present window, the last of which
|
|
`dumpsys gfxinfo` cannot see at all for iris's `SurfaceView`); and both
|
|
figures are emulator numbers under software rasterisation, which
|
|
Compose's *own* in-app report shows already costs 20-34ms/frame in
|
|
`swap`+`gpu` alone under this GPU mode, so a same-mode iris number well
|
|
above 16.7ms was expected going in for either app. A second pair under
|
|
`-gpu host` was not taken this pass. The earlier session's suspected
|
|
intermittent touch-delivery dropout was **not reproduced** this pass —
|
|
the zero-frame results this time traced to this pass's own script bug
|
|
(a `cd` that changed which emulator `ui-trace` targeted), not the
|
|
emulator; a CPU-load rise during the gesture was observed by a sampler
|
|
running throughout, but did not correlate with any failure, so the
|
|
original candidate is neither confirmed nor ruled out.
|
|
The choice in front of Iris, updated: decide now on the
|
|
structural-plus-functional case already made (iris works end-to-end
|
|
where Masonry's scroll gesture doesn't exist at all on Android) plus
|
|
this table — reading the two build profiles and three jank definitions
|
|
with the caveats above rather than as a single number — or ask for a
|
|
same-profile, same-GPU-mode rerun first. RUST.md's I5 box has the full
|
|
account.
|
|
|
|
**Updated 2026-09-05, the `-gpu host` pair taken.** Real GPU rendering
|
|
(`force-gles` -- the default Vulkan backend has no adapter at all under
|
|
plain host-GPU boot, confirmed by the exact `wgpu` error) reverses the
|
|
software-mode shape:
|
|
|
|
| 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`), **best of three, 2026-09-05** | release, `force-gles` | host (virgl) | 439 | 46.24% | 15.7ms | 23.3ms | 31.2ms | 57.4ms | 1.2ms | 13.2ms |
|
|
|
|
Under real GPU rendering iris's median frame is *faster* than
|
|
Compose's, not the 2-3x-slower shape the software-mode table shows. A
|
|
new split inside `FrameReport` (redraw-to-submit vs. submit-to-present,
|
|
commit `e2a1fad`) says why: iris's own CPU work per frame is a median
|
|
~1ms -- almost the entire frame is time spent handing the frame to the
|
|
driver, not in iris's layout/text/primitive code. This is consistent
|
|
with the earlier software-mode gap being mostly SwiftShader's CPU
|
|
rasterisation cost rather than an iris-specific slowness. **Still not
|
|
proof, and now closed as unanswerable rather than merely untaken**: a
|
|
same-mode software `force-gles` run to isolate the backend was retried
|
|
2026-09-05 after fixing the compute-limit crash the first attempt hit,
|
|
and hit a second, structural wall instead — SwiftShader's ES 3.0 GL
|
|
path has no storage-buffer capacity at all, and `shader.wgsl` reads
|
|
`var<storage>` buffers unconditionally, so reaching that path needs a
|
|
shader rewrite, not a limits fix (RUST.md's I5 box, "The three
|
|
remaining I5 verifications, closed 2026-09-05," item 2). The
|
|
intermittent touch-scroll dropout this pass also reproduced is
|
|
root-caused and fixed as of the same date (a missed `ACTION_DOWN` on a
|
|
row's padding/header left `DragArbiter` stuck in `Idle`); three clean
|
|
`iris-scroll.sh` runs post-fix each scrolled all 24/24 swipes, replacing
|
|
the single-attempt 62-frame reading this table used to carry. RUST.md's
|
|
I5 box, "Where iris's frame time goes, 2026-09-05, the `-gpu host`
|
|
pass," and "The three remaining I5 verifications, closed 2026-09-05,"
|
|
have the full account. The iris-vs-Masonry choice itself is still
|
|
Iris's to make.
|