From 55f984fcc7c33e481ab1e723937551595dd530ce Mon Sep 17 00:00:00 2001 From: Yona Appletree Date: Fri, 31 Jul 2026 11:05:03 -0700 Subject: [PATCH 1/2] test(lps-filetests): pin uniform struct-array constant indexing on all 5 targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Harvested from the composite-effects spike (PR #218): a uniform array of structs has no element address resolvable at lower time, so members must be read through constant indices and passed to helpers as scalars/vectors. This file pins the supported idiom green on rv32n, rv32c, rv32lpn, wasm, and interp — the runtime-indexed form compile-fails on 4 of the 5. One change from the spike version: the header comment cited the corpus glob `array/of-struct/*`, whose `/*` the harness's block-comment stripper (new since the spike branched) reads as a real block-comment opener, silently stripping every subsequent directive — the file passed with zero cases. Reworded to `array/of-struct/`; the stripper bug is flagged separately. Co-Authored-By: Claude Fable 5 --- .../filetests/uniform/array-of-struct.glsl | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 lp-shader/lps-filetests/filetests/uniform/array-of-struct.glsl diff --git a/lp-shader/lps-filetests/filetests/uniform/array-of-struct.glsl b/lp-shader/lps-filetests/filetests/uniform/array-of-struct.glsl new file mode 100644 index 000000000..900507d39 --- /dev/null +++ b/lp-shader/lps-filetests/filetests/uniform/array-of-struct.glsl @@ -0,0 +1,51 @@ +// test run + +// Uniform ARRAY OF STRUCTS. Distinct from `uniform/array.glsl` (uniform +// arrays of scalars/vectors, dynamically indexable) and from +// `array/of-struct/` (function-local struct arrays): a uniform struct +// array's element address must be resolvable at lower time, so members are +// read through CONSTANT indices — the idiom `examples/events/shader.glsl` +// and `examples/effects/meteor` both carry. +// +// Regression origin (2026-07-29): the meteor effect shipped a helper that +// indexed this array with a runtime parameter. It compiled on every +// Naga-frontend target and failed only on `Frontend::Lp` lowering +// ("AccessIndex: struct value behind Load: Access base has no uniform +// element addr"), so both the engine render test and CI passed while the +// browser sim failed. + +struct Particle { + uint id; + vec2 pos; + vec3 color; + float intensity; +}; + +layout(binding = 0) uniform Particle particles[4]; + +// Members pass into the helper as scalars/vectors; the helper never +// indexes the uniform array itself. +vec3 accumulate(vec3 accum, uint id, vec2 pos, vec3 color, float intensity) { + if (id == 0u) { + return accum; + } + return accum + color * intensity + vec3(pos.x, pos.y, 0.0); +} + +vec3 test_uniform_aos_constant_index_through_helper() { + vec3 accum = vec3(0.0, 0.0, 0.0); + accum = accumulate(accum, particles[0].id, particles[0].pos, particles[0].color, particles[0].intensity); + accum = accumulate(accum, particles[1].id, particles[1].pos, particles[1].color, particles[1].intensity); + accum = accumulate(accum, particles[2].id, particles[2].pos, particles[2].color, particles[2].intensity); + accum = accumulate(accum, particles[3].id, particles[3].pos, particles[3].color, particles[3].intensity); + return accum; +} + +// run: test_uniform_aos_constant_index_through_helper() ~= vec3(0.0, 0.0, 0.0) + +// Direct constant-index member reads at the top level. +float test_uniform_aos_direct_member_read() { + return particles[0].intensity + particles[3].pos.x + float(particles[2].id); +} + +// run: test_uniform_aos_direct_member_read() ~= 0.0 From 941797ec2f2964fe89c8cb78b66e81ae572a816e Mon Sep 17 00:00:00 2001 From: Yona Appletree Date: Fri, 31 Jul 2026 11:05:03 -0700 Subject: [PATCH 2/2] docs(registries): record the uniform struct-array runtime-index miss MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defect entry (2026-07-29, fixed): meteor's render shader indexed a uniform struct array with a runtime value; it compiled on the host backend and failed on 4 of 5 targets, so the engine render test and CI stayed green while the browser sim broke. Debt entry (carried): the condition behind it — nothing compile-gates examples/**/*.glsl on the non-host targets, and an engine-level render test structurally cannot. Both indexed; the debt entry is conformed to the register template (carried/since/logged) that landed after the spike branched. Co-Authored-By: Claude Fable 5 --- docs/debt/README.md | 1 + .../debt/example-shaders-not-compile-gated.md | 53 ++++++++++++++++ ...7-29-uniform-struct-array-runtime-index.md | 62 +++++++++++++++++++ docs/defects/README.md | 1 + 4 files changed, 117 insertions(+) create mode 100644 docs/debt/example-shaders-not-compile-gated.md create mode 100644 docs/defects/2026-07-29-uniform-struct-array-runtime-index.md diff --git a/docs/debt/README.md b/docs/debt/README.md index 6376e79b8..724ef8c20 100644 --- a/docs/debt/README.md +++ b/docs/debt/README.md @@ -59,3 +59,4 @@ stay in place when retired; the log is the history). | [gpu-tier-cannot-sample-led-output](gpu-tier-cannot-sample-led-output.md) | carried | 2026-07-09 | fw-browser/tier + lp-gfx-wgpu | gallery previews are dead for fixture-bearing projects; tier selection never asks what the project needs | | [firmware-capability-reporting](firmware-capability-reporting.md) | carried | 2026-07-30 | lpc-engine/nodes + lpc-wire | a gated-out node's runtime is silently inert; no device/studio signal explains why until boards genuinely differ in capability | | [firmware-partition-constants-transcribed](firmware-partition-constants-transcribed.md) | carried | 2026-07-30 | lp-fw/fw-esp32c6 | the C6 hardcodes lpfs offset/size copied by hand from partitions.csv; a verbatim port to the S3 would have erased running code | +| [example-shaders-not-compile-gated](example-shaders-not-compile-gated.md) | carried | 2026-07-29 | examples GLSL + CI + lps-filetests | an example shader can compile on the host yet fail on 4 of 5 targets; the break surfaces only when a human opens it in Studio | diff --git a/docs/debt/example-shaders-not-compile-gated.md b/docs/debt/example-shaders-not-compile-gated.md new file mode 100644 index 000000000..9a43baf45 --- /dev/null +++ b/docs/debt/example-shaders-not-compile-gated.md @@ -0,0 +1,53 @@ +--- +status: carried +since: 2026-07-29 +logged: 2026-07-29 +area: examples/**/*.glsl, CI (just check / build-ci), lps-filetests +related: + - docs/defects/2026-07-29-uniform-struct-array-runtime-index.md +--- +# Shipped example shaders are not compile-gated on non-host targets + +**Shape** — nothing in CI compiles the GLSL under `examples/` for the +device- and browser-canonical shader targets. `examples_valid.rs` loads +every example as a project (which exercises the HOST backend only), and the +filetest suite covers `lp-shader/lps-filetests/filetests/**`, never +`examples/`. An example can therefore ship a construct that compiles on the +host and fails on `rv32n` / `rv32c` / `wasm` / `interp` — the targets that +actually run on device and in the browser sim. + +The natural-looking guard does not work, which is what makes this +structural rather than one missing assertion. An engine-level render test +(load the example, tick, render, assert nonzero pixels) runs the host +backend by construction, so it cannot observe another target's lowering. +Asserting node runtime status does not help either: on the host there is no +error to report. The gate has to compile the example sources against the +other targets explicitly. + +**Carrying cost** — the failure surfaces only when a human opens that +example in Studio. It presents as a runtime shader-compile error on a node +that otherwise mounted and "runs", which is easy to read as a preview +glitch rather than broken shipped content. + +**Workarounds** +- When authoring example GLSL, copy the shape of an existing example that + is already known-good on device rather than inventing one; the uniform + struct-array idiom in `examples/events/shader.glsl` is the reference. +- After adding or editing an example shader, open it in the browser sim + once and read the node's status — that is currently the only end-to-end + check. + +**Incident log** +- 2026-07-29 — first occurrence: + [uniform-struct-array-runtime-index](../defects/2026-07-29-uniform-struct-array-runtime-index.md). + The meteor example's render shader indexed a uniform struct array with a + runtime value; the construct failed on 4 of 5 targets while every + automated gate stayed green. + +**Exit criteria** — extend the filetest runner (or add a small harness) to +compile every `examples/**/*.glsl` for `ALL_TARGETS`, run-free +(compile-only), and fail on any target that rejects a shipped example. +Compile-only keeps it cheap and needs no uniform values or expected +outputs. Note the filetest harness currently treats `compile-fail` as an +expected-failure category, so this gate must assert on it rather than +reuse that path. diff --git a/docs/defects/2026-07-29-uniform-struct-array-runtime-index.md b/docs/defects/2026-07-29-uniform-struct-array-runtime-index.md new file mode 100644 index 000000000..87e8bb300 --- /dev/null +++ b/docs/defects/2026-07-29-uniform-struct-array-runtime-index.md @@ -0,0 +1,62 @@ +--- +status: fixed +found: 2026-07-29 # how: live-debugging (Yona opened the meteor sim) +area: examples/effects/meteor (render.glsl), lps-frontend lowering, example shader coverage +class: missing-coverage +related: + - docs/debt/example-shaders-not-compile-gated.md +--- +# Meteor's render shader indexed a uniform struct array with a runtime value + +**Symptom** — opening the imported meteor effect in the browser sim: + +``` +shader compile: lower: in function 'drawMeteor': unsupported expression: +AccessIndex: struct value behind Load: Access base has no uniform element addr +path /studio.show/meteor.project/render.shader +``` + +The effect mounted and ran; only its visual failed to compile. + +**Root cause** — `drawMeteor(vec3 accum, int slot, vec2 uv)` read +`meteors[slot].…` where `slot` is a runtime parameter. A uniform array of +structs has no element address resolvable at lower time, so members must be +read through **constant** indices at the call site and passed in as +scalars/vectors. `examples/events/shader.glsl` already carries exactly that +shape (`drawEvent(color, 0, events[0].id, events[0].seq, uv)`) — the +existing example was the idiom, and the new one departed from it. + +Measured breadth (filetest probe, 2026-07-29): the runtime-indexed form +**compile-fails on 4 of 5 targets** — `rv32n.q32`, `rv32c.q32`, `wasm.q32`, +`interp.f32` — and compiles only on `rv32lpn.q32`. It is broadly +unsupported, not a single-target quirk. + +**Fix** — `examples/effects/meteor/meteor/render.glsl` now takes the +members as parameters (`uint id, vec2 head, vec3 color, float intensity`) +and indexes with constants at the call site. (The meteor example itself +lands with the composite-effects work, PR #218; the filetest and this +entry were harvested ahead of it.) + +**Regression coverage** — the miss is the interesting part, and it was two +independent holes: + +1. **The engine render test could not see it.** The new + `effect_examples_render_through_their_mirrors` (lpc-engine) renders each + effect and asserts nonzero RGB. It runs the HOST backend, whose lowering + accepts the construct — verified empirically by restoring the broken + shader, which still passed. Adding a per-node + `NodeRuntimeStatus::Error` assertion did not catch it either: on the + host path there is genuinely no error to report. An engine-level render + test is structurally incapable of gating shader-lowering support. + +2. **Example shaders are not compile-gated on the other targets.** Nothing + in CI compiles `examples/**/*.glsl` for the device/browser-canonical + targets, so any example can ship a construct that fails everywhere but + the host. That condition is broader than this defect — see the debt + entry. + +Added `lp-shader/lps-filetests/filetests/uniform/array-of-struct.glsl`, +which pins the SUPPORTED idiom (constant-index member reads, including +through a helper) green on all five targets. Note a negative test would not +gate anything: the filetest harness records `compile-fail` as an +*expected-failure*, not a failure. diff --git a/docs/defects/README.md b/docs/defects/README.md index 29913c595..eb74bb804 100644 --- a/docs/defects/README.md +++ b/docs/defects/README.md @@ -200,6 +200,7 @@ sentence (arguments in, returns out; registers and stack). | silent-drop | 2026-07-28 | [flash-progress-never-reached-the-ui](2026-07-28-flash-progress-never-reached-the-ui.md) | fixed | lpa-studio-core (actor/controller) | | unbounded-restatement | 2026-07-28 | [tick-error-restated-every-frame](2026-07-28-tick-error-restated-every-frame.md) | fixed | lpa-server (advance_frame) | | unsynchronized-shared-artifact | 2026-07-29 | [builtins-elf-uplift-race](2026-07-29-builtins-elf-uplift-race.md) | fixed | justfile `test` + lpvm-cranelift/build.rs | +| missing-coverage | 2026-07-29 | [uniform-struct-array-runtime-index](2026-07-29-uniform-struct-array-runtime-index.md) | fixed | examples/effects/meteor + lps-frontend lowering | ## Predecessor: `docs/bugs/`