Skip to content

fix(viewer): skip post-processing pipeline when WebGPU is unavailable#234

Open
b9llach wants to merge 1 commit intopascalorg:mainfrom
b9llach:fix/skip-post-processing-without-webgpu
Open

fix(viewer): skip post-processing pipeline when WebGPU is unavailable#234
b9llach wants to merge 1 commit intopascalorg:mainfrom
b9llach:fix/skip-post-processing-without-webgpu

Conversation

@b9llach
Copy link
Copy Markdown

@b9llach b9llach commented Apr 14, 2026

Problem

On a browser where navigator.gpu is undefined (Safari without the WebGPU flag, most iOS WebKit builds, older Chrome on machines without a WebGPU device), the WebGPURenderer falls back to WebGL2. When that happens, the TSL-based post-processing pipeline in post-processing.tsx can't be built — RenderPipeline, ssgi, denoise, and the MRT pass setup from three/webgpu / three/tsl are all WebGPU-only.

The existing error path in useFrame retries the pipeline up to 3 times with a 500ms delay between attempts. Each retry fights the direct-render fallback, and the net result is "scene renders for about a second, then goes black and stays black."

Fix

Add a short-circuit at the top of the pipeline-setup useEffect:

const hasWebGPU =
  typeof navigator !== 'undefined' && typeof navigator.gpu !== 'undefined'
if (!hasWebGPU) {
  console.warn('[viewer] WebGPU unavailable — rendering without post-processing (SSGI, outlines, denoise).')
  hasPipelineErrorRef.current = true
  renderPipelineRef.current = null
  return
}

When the guard fires, hasPipelineErrorRef.current is set at setup time, so useFrame's existing if (hasPipelineErrorRef.current || !renderPipelineRef.current) branch takes the direct renderer.render(scene, camera) path and never attempts the TSL pipeline. The retry loop is skipped entirely.

Scope

  • WebGPU mode: navigator.gpu is defined, the guard passes, nothing changes. SSGI, outlines, denoise all still run.
  • No WebGPU: the guard fires, direct render only, no post FX (the scene is still visible — just flat shading).
  • WebGPU API exposed but device creation fails at runtime: not handled specially — falls through the existing try { … } catch block exactly like today.

Pairs well with #233 (await renderer.init() in the gl factory), which fixes the direct-render path itself so the fallback actually works in WebGL2.

Test

On a WebGL2-only browser:

  • Before: scene blinks on, retries loop, goes permanently black, console spam.
  • After: one-time [viewer] WebGPU unavailable warning, scene renders without post FX, stays rendered.

`RenderPipeline`, SSGI, and the denoise TSL node imported from
`three/webgpu` and `three/tsl` are all WebGPU-only. On a browser
without `navigator.gpu`, the WebGPURenderer falls back to WebGL2, and
attempting to build the TSL post-processing pipeline either throws or
produces broken output — the scene renders for a few frames, then
goes black as the 3-attempt retry loop fights the direct-render
fallback path in `useFrame`.

This sets `hasPipelineErrorRef.current = true` at pipeline setup time
when `navigator.gpu` is undefined, so `useFrame` takes the existing
direct `renderer.render(scene, camera)` path exclusively and never
tries to build the broken TSL pipeline.

No behavioural change in WebGPU mode — the guard only fires when the
WebGPU API is literally not exposed by the browser. The rare edge
case of `navigator.gpu` being defined but device creation failing at
runtime still falls through the existing try/catch unchanged.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant