diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index 45a2ade..cef0361 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -7,6 +7,48 @@ marked **DEFERRED** are ones the agent chose not to decide alone. ## 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` 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 diff --git a/docs/IRIS.md b/docs/IRIS.md index a4fd1eb..cb1a2af 100644 --- a/docs/IRIS.md +++ b/docs/IRIS.md @@ -31,6 +31,31 @@ platform handle. `vm`/`view` are independent handles from the ones `new_peer` keeps for its own `RequestRedraw` (a fresh `get_java_vm`/ `new_global_ref` each), so storing them has no effect on that mechanism. +## 2026-09-05 (later still): `iris_core::device_limits()`, and iris no longer requests compute-shader limits + +New public function, `iris_core::device_limits() -> wgpu::Limits`. Why: +`adapter.request_device`'s `required_limits` was `Limits::default()` plus +a `max_buffer_size` override in both platform backends, and +`Limits::default()` requests desktop-tier compute-shader limits +unconditionally (`max_compute_workgroups_per_dimension: 65535`) even +though nothing in `iris`/`iris-core` uses a `ComputePipeline` — that +crashed device creation outright on a downlevel GL adapter reporting +OpenGL ES 3.0 (no compute shaders at all: the Android emulator's +`EMU_GPU=software` path, and any real GLES-3.0-only Android device). +`device_limits()` is what both `android::render::AndroidRenderer::new` +and `default::render::UiRenderer::new` now build their `required_limits` +from, so the request cannot drift between the two backends. + +Before: `Limits { max_buffer_size: 1 << 30, ..Default::default() }` +inlined in each backend. After: `iris_core::device_limits()`, which is +the same thing with the six `max_compute_*` fields additionally zeroed. +A caller building its own `DeviceDescriptor` outside these two backends +(there are none today, but a third platform backend would want this) +should call `device_limits()` rather than reaching for +`Limits::default()` directly, unless it genuinely adds a compute pass — +in which case it wants the specific compute limits that pass needs, not +the desktop-tier default for everything. + ## 2026-09-05 (later the same day): `iris_core::FrameReport` (RUST.md's I5 box) New public type, `iris_core::FrameReport` (re-exported from `iris_core`'s diff --git a/docs/IRIS_TODO.md b/docs/IRIS_TODO.md index 13222be..6a2c03f 100644 --- a/docs/IRIS_TODO.md +++ b/docs/IRIS_TODO.md @@ -7,6 +7,29 @@ order and what "done" looks like. Tick and date them in place. ## Fix +- [x] **`request_device` asked for compute-shader limits it never uses + (2026-09-05).** `Limits::default()` (both `iris/src/android/render.rs` + and `iris/src/default/render.rs`) requests desktop-tier compute limits + unconditionally, 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 device creation + 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, so the adapter's real limit is 0 + against the unconditional request for 65535 — the same would happen on + any real GLES-3.0-only Android device. Fixed by a new, shared + `iris_core::device_limits()` (`iris/core/src/render/mod.rs`) that zeros + exactly the six `max_compute_*` fields rather than switching to a + downlevel `Limits` preset — `downlevel_webgl2_defaults()` also zeros + `max_storage_buffers_per_shader_stage`, which `shader.wgsl`'s vertex + stage needs (four `var` buffers), so that preset would trade + this crash for a bind-group-layout one on the same hardware. + `rigs/gpu-probe`'s own hand-mirrored `Limits` (it is deliberately its + own crate, not able to call `device_limits()` directly) was updated to + match. See `DECISIONS.md` and RUST.md's I5 box for the account, + including what could not be re-verified on-device this pass (the + emulator was in concurrent use by another session). + - [x] **Input does not fall through by input type (2026-09-04).** `SensorUi::run_sensors` (`src/default/sense.rs`) used to set "consumed, stop checking lower layers" from mere hover — a widget registered for diff --git a/docs/RUST.md b/docs/RUST.md index 9839e7d..5f96ad8 100644 --- a/docs/RUST.md +++ b/docs/RUST.md @@ -59,6 +59,21 @@ session spending an afternoon on them again. end-to-end this pass. The fix itself is verified by direct, targeted logcat traces taken before that interference began, not by the aggregate script. +- **iris no longer requests compute-shader limits it never uses, + 2026-09-05.** `adapter.request_device`'s `Limits::default()` asks for + desktop-tier compute limits unconditionally even though nothing in + `iris`/`iris-core` uses a `ComputePipeline` -- confirmed by grep, not + assumed -- which is what crashed `request_device` outright under + `EMU_GPU=software`'s `force-gles` path (SwiftShader's GL reports OpenGL + ES 3.0, no compute at all). New shared `iris_core::device_limits()` + zeros exactly the six compute fields; `rigs/gpu-probe`'s own mirrored + limits were updated and confirm `IRIS DEVICE: ok` on this VM's own + Vulkan and GL adapters. **The specific SwiftShader-ES-3.0 crash this + fixes was not re-verified on-device this pass** -- the cold boot needed + would have force-restarted this checkout's emulator while another + session had its own app focused on it, so it was left rather than + disrupted. See this box's "Fixed, 2026-09-05, later the same day" + subsection (under the software-mode crash it fixes) and `DECISIONS.md`. - **Decided 2026-09-05: iris over Masonry**, by Iris, from the host-GPU numbers in I5's box and E1/E2's findings. See the Recommendation's item 3 and `DECISIONS.md`. Next: the remaining screens and the app on iris — @@ -3211,6 +3226,45 @@ silently on real hardware. general) explains the ~80-150ms software-mode numbers, since no GLES number under software mode could be taken at all. + **Fixed, 2026-09-05, later the same day.** Not "requesting compute + limits only when the adapter reports them" (a capability check with + a fallback) -- simpler than that, because iris has no code path that + needs compute at all: grepped the whole `iris`/`iris-core` tree for + `ComputePipeline`/`@compute` and found none, so the right fix is to + stop asking for compute limits, full stop, rather than to build a + fallback for a capability nothing uses. `iris_core::device_limits()` + (`iris/core/src/render/mod.rs`) is the one place both platform + backends now build their `required_limits` from: `Limits::default()` + with the six `max_compute_*` fields zeroed and `max_buffer_size` + still raised, as before. `Limits::downlevel_webgl2_defaults()` was + the first thing tried and rejected -- it also zeros + `max_storage_buffers_per_shader_stage`, and `shader.wgsl`'s vertex + stage reads four `var` buffers, so it would have traded + this crash for a bind-group-layout one on the same hardware. + `rigs/gpu-probe`'s own `Limits` (necessarily a hand-mirrored copy -- + that rig is deliberately its own crate, not a workspace member) was + updated to match and re-run: `IRIS DEVICE: ok` against this VM's own + Vulkan (Venus) and GL (virgl, reports OpenGL ES 3.2) adapters. + **Not verified against the actual SwiftShader-ES-3.0 failure this + pass**: the `EMU_GPU=software` cold boot needed to reproduce it would + have force-restarted this checkout's shared emulator while another + session had `com.example.aiapp` focused and running on it (`adb + shell dumpsys window`), so this pass left that measurement rather + than disrupting concurrent work -- matching AGENTS.md's "coordinate + with peer agents" guidance rather than contending for the emulator. + Everything else: `cargo fmt --all`/`clippy --workspace --all-targets`/ + `test --workspace` clean, `cargo ndk build`/`clippy` for + `iris-android-app --features transcript-screen,force-gles` clean + (only the pre-existing unused-`tabs-ui`-dependency warning, unrelated + to this change). This also means the software-mode question two + boxes up is still open, for the same original reason plus this new + one: a GLES number under `EMU_GPU=software` still has not been + taken, now blocked on emulator availability rather than on the + crash. A future pass should cold-boot `EMU_GPU=software` once the + emulator is free, confirm `dev.iris.android.demo` no longer aborts + on `request_device`, and take the `iris-scroll.sh` FrameReport row + that pairs with this box's host-GPU one. + **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 diff --git a/iris/core/src/render/mod.rs b/iris/core/src/render/mod.rs index feac4d8..ee30733 100644 --- a/iris/core/src/render/mod.rs +++ b/iris/core/src/render/mod.rs @@ -23,6 +23,48 @@ pub use primitive::*; const SHAPE_SHADER: &str = include_str!("./shader.wgsl"); +/// The `wgpu::Limits` both platform backends (`android::render:: +/// AndroidRenderer::new`, `default::render::UiRenderer::new`) ask +/// `Adapter::request_device` for -- shared so the two copies cannot drift, +/// per AGENTS.md's "write the logic once." +/// +/// Built from `Limits::default()`, **not** a downlevel variant: the shader +/// (`shader.wgsl`) reads four `var` buffers (rects, glyphs, masks, +/// move_offsets) from the vertex stage, and `Limits::downlevel_webgl2_defaults()` +/// zeroes `max_storage_buffers_per_shader_stage` along with the compute +/// limits below -- switching to it would trade one `request_device` crash +/// for a bind-group-layout one on the same downlevel hardware this is meant +/// to support. `max_buffer_size` is raised for the growing instance/atlas +/// buffers (`ArrBuf`, `GpuTextures`); everything else is `default()`'s +/// desktop-tier value, unchanged. +/// +/// The six `max_compute_*` fields are zeroed because nothing in this crate +/// creates a `ComputePipeline` or writes a `@compute` shader stage -- +/// grepped for both across `iris`/`iris-core` before writing this, found +/// none. `Limits::default()` requests desktop-tier compute limits +/// unconditionally (`max_compute_workgroups_per_dimension: 65535`) even +/// though nothing asks a device to actually support compute, which is what +/// crashed `request_device` on the Android emulator's software GL path +/// (`EMU_GPU=software`, `force-gles`): SwiftShader's GL reports itself as +/// OpenGL ES 3.0, which has no compute shaders, so the adapter's real limit +/// is 0 and the unconditional request fails outright +/// (`RUST.md`'s "Software mode ... crashes for a third, different reason"). +/// The same would happen on a real GLES-3.0-only Android device. If a +/// future change adds a compute pass, request the specific limits it needs +/// here rather than reverting to the desktop-tier default for everything. +pub fn device_limits() -> Limits { + Limits { + max_buffer_size: 1 << 30, + max_compute_workgroup_storage_size: 0, + max_compute_invocations_per_workgroup: 0, + max_compute_workgroup_size_x: 0, + max_compute_workgroup_size_y: 0, + max_compute_workgroup_size_z: 0, + max_compute_workgroups_per_dimension: 0, + ..Default::default() + } +} + pub struct UiRenderNode { uniform_group: BindGroup, primitive_layout: BindGroupLayout, diff --git a/iris/src/android/render.rs b/iris/src/android/render.rs index 25649e2..05a1f2c 100644 --- a/iris/src/android/render.rs +++ b/iris/src/android/render.rs @@ -86,12 +86,11 @@ impl AndroidRenderer { // Same request as the winit backend's `UiRenderer::new` -- no // binding-array features, see TEXTURES.md's "Recommended shape". + // `iris_core::device_limits()` is shared between the two backends; + // see its own doc for why it is not simply `Limits::default()`. let (device, queue) = adapter .request_device(&DeviceDescriptor { - required_limits: Limits { - max_buffer_size: 1 << 30, - ..Default::default() - }, + required_limits: iris_core::device_limits(), ..Default::default() }) .block_on() diff --git a/iris/src/default/render.rs b/iris/src/default/render.rs index 74ffd6c..1da993f 100644 --- a/iris/src/default/render.rs +++ b/iris/src/default/render.rs @@ -102,13 +102,12 @@ impl UiRenderer { // needs descriptor indexing. See TEXTURES.md's "Recommended shape" // for why the old binding array asked for // VK_EXT_descriptor_indexing unconditionally and did not survive a - // real share of Android GPUs. + // real share of Android GPUs. `iris_core::device_limits()` is + // shared with the Android backend; see its own doc for why it is + // not simply `Limits::default()`. let (device, queue) = adapter .request_device(&DeviceDescriptor { - required_limits: Limits { - max_buffer_size: 1 << 30, - ..Default::default() - }, + required_limits: iris_core::device_limits(), ..Default::default() }) .block_on() diff --git a/rigs/gpu-probe/src/main.rs b/rigs/gpu-probe/src/main.rs index a15f977..ed803b4 100644 --- a/rigs/gpu-probe/src/main.rs +++ b/rigs/gpu-probe/src/main.rs @@ -35,6 +35,34 @@ fn iris_features() -> Features { /// kept for the big storage buffers behind rects/glyphs. const IRIS_MAX_BUFFER_SIZE: u64 = 1 << 30; +/// Mirrors `iris_core::device_limits()` (`iris/core/src/render/mod.rs`) -- +/// cannot call it directly, since this rig is deliberately its own crate, +/// not a workspace member (this file's own Cargo.toml comment). Keep the +/// two in sync by hand when one changes; this rig's whole purpose is "does +/// the device iris actually builds come back," so a stale copy here would +/// silently stop answering that question. Zeroed rather than left at +/// `Limits::default()`'s desktop-tier values because nothing in iris +/// creates a `ComputePipeline` or a `@compute` shader stage -- found by +/// grepping the whole `iris`/`iris-core` tree before this rig's comment was +/// written -- and the unconditional default request is what crashed +/// `request_device` on the Android emulator's software GL path +/// (`EMU_GPU=software`, `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). The same would happen on a real GLES-3.0-only Android +/// device. +fn iris_limits() -> Limits { + Limits { + max_buffer_size: IRIS_MAX_BUFFER_SIZE, + max_compute_workgroup_storage_size: 0, + max_compute_invocations_per_workgroup: 0, + max_compute_workgroup_size_x: 0, + max_compute_workgroup_size_y: 0, + max_compute_workgroup_size_z: 0, + max_compute_workgroups_per_dimension: 0, + ..Default::default() + } +} + fn main() { vk::report(); @@ -104,10 +132,7 @@ fn main() { // limits requested, this is expected to succeed everywhere -- this rig // is what turned that from an assumption into a measurement, first on // this emulator's software Vulkan. - let wanted = Limits { - max_buffer_size: IRIS_MAX_BUFFER_SIZE, - ..Default::default() - }; + let wanted = iris_limits(); match pollster::block_on(adapter.request_device(&DeviceDescriptor { required_features: iris_features(), required_limits: wanted,