Skip to content

WebGPU SSAO pipeline invalid at extra-high tier → whole frame goes black #592

Open

Description

@MaximumTrainer

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):

  1. Open the row screen with any route (tested with Willowbrook / course 175 and HOCR / course 61).
  2. Before the engine attaches, set window.__VIRTUALROW_PERFORMANCE_MODE = 'extra-high' (either in DevTools or via a Playwright addInitScript).
  3. 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.wgsl and the pipeline build in src/lib/renderer/engine/postFx.ts (or wherever createPostFx mounts the SSAO pipeline). Likely a binding/layout mismatch that only exercises at the extra-high tier caps.
  • Call module.getCompilationInfo() and log any type: '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.ts that 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

  1. MaximumTrainer commented on Oct 26, 2026

    @MaximumTrainer
    OwnerAuthor

    Adding one diagnostic nuance that narrows the fix (folding in from a duplicate report):

    SSAO on its own is not broken — the high tier enables SSAO (and renders fine in live GPU capture). The black frame appears only at extra-high, which is the one tier that additionally turns DOF on (tierCaps.ts: drawSsao is true for both high and extra-high; drawDof is true only for extra-high). So although the console names the ssao pipeline, 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 in postFx.ts), rather than SSAO alone.

    Worth having the suggested per-tier postFx.test.ts build the chain at extra-high specifically (SSAO + DOF together), not just each effect in isolation.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions