fix(viewer): skip post-processing pipeline when WebGPU is unavailable (#234)
`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.
This commit is contained in:
@@ -117,6 +117,24 @@ const PostProcessingPasses = () => {
|
||||
|
||||
hasPipelineErrorRef.current = false
|
||||
|
||||
// WebGPU availability check: SSGI, denoise, and RenderPipeline are all
|
||||
// WebGPU-only APIs. When the browser falls back to WebGL2 (no
|
||||
// `navigator.gpu`, or the device couldn't be created), building the
|
||||
// pipeline either throws silently or produces a broken output where
|
||||
// the scene renders for a few frames and then goes black as the retry
|
||||
// loop fights the direct-render fallback path. Short-circuit here so
|
||||
// `useFrame` uses the direct `renderer.render(scene, camera)` path
|
||||
// exclusively and never attempts the TSL pipeline.
|
||||
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
|
||||
}
|
||||
|
||||
// Clear outliner arrays synchronously to prevent stale Object3D refs
|
||||
// from the previous project leaking into the new pipeline's outline passes.
|
||||
const outliner = useViewer.getState().outliner
|
||||
@@ -263,15 +281,7 @@ const PostProcessingPasses = () => {
|
||||
}
|
||||
renderPipelineRef.current = null
|
||||
}
|
||||
}, [
|
||||
renderer,
|
||||
scene,
|
||||
camera,
|
||||
hoverHighlightMode,
|
||||
zoneLayers,
|
||||
projectId,
|
||||
pipelineVersion,
|
||||
])
|
||||
}, [renderer, scene, camera, hoverHighlightMode, zoneLayers, projectId, pipelineVersion])
|
||||
|
||||
useFrame((_, delta) => {
|
||||
// Animate background colour toward the current theme target (same lerp as AnimatedBackground)
|
||||
|
||||
Reference in New Issue
Block a user