Repository navigation
WebGPU SSAO pipeline invalid at extra-high tier → whole frame goes black #592
Description
Repro
On claude/project-thread-lddi4g (also reproduces on main tip, so this is NOT caused by the #591 / 9st41b scenery changes — the SSAO pipeline fails before any of those run):
- Open the row screen with any route (tested with Willowbrook / course 175 and HOCR / course 61).
- Before the engine attaches, set
window.__VIRTUALROW_PERFORMANCE_MODE = 'extra-high'(either in DevTools or via a PlaywrightaddInitScript). - Click "Try a demo row".
The 3D canvas comes up black. HUD and route overlay render normally (they're HTML), the engine's frame counter keeps advancing (__ROWER3D_FRAME_STATS.frame climbs), __VIRTUALROW_WEBGPU_HEALTH reports {adapter: "intel / gen-9", isSoftware: false, deviceLostCount: 0} — so the device didn't go away. The frame is black because the SSAO render pass fails pipeline validation and the whole command buffer gets invalidated for every submit.
Chrome console (filtered to the relevant warnings, repeats every frame)
[Invalid RenderPipeline "ssao"] is invalid due to a previous error.
- While Validating GetBindGroupLayout (0) on [Invalid RenderPipeline "ssao"]
[Invalid BindGroupLayout (unlabeled)] is invalid due to a previous error.
- While validating [BindGroupDescriptor] against [Invalid BindGroupLayout (unlabeled)]
- While calling [Device].CreateBindGroup([BindGroupDescriptor]).
[Invalid RenderPipeline "ssao"] is invalid due to a previous error.
- While encoding [RenderPassEncoder "ssao"].SetPipeline([Invalid RenderPipeline "ssao"]).
- While finishing [CommandEncoder "engine-frame"].
[Invalid CommandBuffer from CommandEncoder "engine-frame"] is invalid due to a previous error.
- While calling [Queue].Submit([[Invalid CommandBuffer from CommandEncoder "engine-frame"]])
The actual WGSL compilation error isn't surfaced in the console — the invalidation is reported later when the frame tries to use the pipeline. getCompilationInfo() on ssao.wgsl would say which line is wrong; the pipeline-build site in postFx.ts could log that eagerly on construction to make this easier to catch next time.
Why it's visible now
The engine was previously only ever handed tier = undefined from the row page, which defaulted to 'medium' in every tier-cap predicate — SSAO wasn't mounted on medium so the broken pipeline was never built. #591 follow-up (branch claude/project-thread-9st41b) wires the real resolvePerformanceMode() through, which means a user who sets __VIRTUALROW_PERFORMANCE_MODE='extra-high' (or saves 'extra-high' through GraphicsQualityStore once the picker reaches /row) now trips the SSAO path on first attach and gets a black screen.
Impact
Any user whose tier resolves to extra-high sees a black 3D scene. basic / low / medium / high are fine. Dan's Intel Gen-9 probes to medium by default so this didn't surface during the #591 parity captures, but is reachable with one localStorage.graphics-quality = 'extra-high' or one window override.
Suggested fix shape
- Inspect
src/lib/renderer/passes/ssao.wgsland the pipeline build insrc/lib/renderer/engine/postFx.ts(or wherevercreatePostFxmounts the SSAO pipeline). Likely a binding/layout mismatch that only exercises at the extra-high tier caps. - Call
module.getCompilationInfo()and log anytype: 'error'messages at pipeline-build time so a future broken shader fails loudly on attach instead of silently per-frame. - Add a parse+build sanity test under
src/lib/renderer/__tests__/postFx.test.tsthat constructs the post-fx chain at every tier against the mock device, so this is caught without needing a real GPU.
Related
Discovered during #591 follow-up tier-plumbing + biome-density grass work on claude/project-thread-9st41b (parity captures parity-shots/run5/). Not blocking that PR — the regular tier path renders fine; only the forced extra-high shot was unusable.
Activity
extra-hightier (invalidssaoRenderPipeline) #593 as a duplicate of this issueMaximumTrainer commentedon Oct 26, 2026
Adding one diagnostic nuance that narrows the fix (folding in from a duplicate report):
SSAO on its own is not broken — the
hightier enables SSAO (and renders fine in live GPU capture). The black frame appears only atextra-high, which is the one tier that additionally turns DOF on (tierCaps.ts:drawSsaois true for bothhighandextra-high;drawDofis true only forextra-high). So although the console names thessaopipeline, the trigger is likely the extra-high/DOF combination or an ordering/target-format interaction in the post-FX chain (bloom → SSAO → composite → DOF, per the header inpostFx.ts), rather than SSAO alone.Worth having the suggested per-tier
postFx.test.tsbuild the chain atextra-highspecifically (SSAO + DOF together), not just each effect in isolation.Generated by Claude Code