fix: recover post-processing after duplicate scene mutations (#228)

This commit is contained in:
Pascal
2026-04-13 23:49:45 -04:00
committed by GitHub
parent a205e4f778
commit 3afa1cea8b
@@ -52,6 +52,7 @@ const PostProcessingPasses = () => {
const renderPipelineRef = useRef<RenderPipeline | null>(null) const renderPipelineRef = useRef<RenderPipeline | null>(null)
const hasPipelineErrorRef = useRef(false) const hasPipelineErrorRef = useRef(false)
const retryCountRef = useRef(0) const retryCountRef = useRef(0)
const rebuildTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const [isInitialized, setIsInitialized] = useState(false) const [isInitialized, setIsInitialized] = useState(false)
// Background color uniform — updated every frame via lerp, read by the TSL pipeline. // Background color uniform — updated every frame via lerp, read by the TSL pipeline.
@@ -76,6 +77,11 @@ const PostProcessingPasses = () => {
const [pipelineVersion, setPipelineVersion] = useState(0) const [pipelineVersion, setPipelineVersion] = useState(0)
const requestPipelineRebuild = useCallback(() => { const requestPipelineRebuild = useCallback(() => {
if (rebuildTimeoutRef.current !== null) {
clearTimeout(rebuildTimeoutRef.current)
rebuildTimeoutRef.current = null
}
setPipelineVersion((v) => v + 1) setPipelineVersion((v) => v + 1)
}, []) }, [])
@@ -108,13 +114,33 @@ const PostProcessingPasses = () => {
} }
}, [renderer]) }, [renderer])
// Reset retry count when project changes // Reset retry state when project changes
useEffect(() => { useEffect(() => {
// Intentionally touch projectId so the effect reruns on project switches.
void projectId
retryCountRef.current = 0 retryCountRef.current = 0
if (rebuildTimeoutRef.current !== null) {
clearTimeout(rebuildTimeoutRef.current)
rebuildTimeoutRef.current = null
}
}, [projectId])
useEffect(() => {
return () => {
if (rebuildTimeoutRef.current !== null) {
clearTimeout(rebuildTimeoutRef.current)
rebuildTimeoutRef.current = null
}
}
}, []) }, [])
// Build / rebuild the post-processing pipeline // Build / rebuild the post-processing pipeline
useEffect(() => { useEffect(() => {
// Intentionally touch these so React/biome treat project switches and retry bumps
// as explicit rebuild triggers instead of accidental extra dependencies.
void projectId
void pipelineVersion
if (!(renderer && scene && camera && isInitialized)) { if (!(renderer && scene && camera && isInitialized)) {
return return
} }
@@ -248,6 +274,7 @@ const PostProcessingPasses = () => {
const renderPipeline = new RenderPipeline(renderer as unknown as WebGPURenderer) const renderPipeline = new RenderPipeline(renderer as unknown as WebGPURenderer)
renderPipeline.outputNode = finalOutput renderPipeline.outputNode = finalOutput
renderPipelineRef.current = renderPipeline renderPipelineRef.current = renderPipeline
retryCountRef.current = 0
} catch (error) { } catch (error) {
hasPipelineErrorRef.current = true hasPipelineErrorRef.current = true
console.error( console.error(
@@ -266,7 +293,16 @@ const PostProcessingPasses = () => {
} }
renderPipelineRef.current = null renderPipelineRef.current = null
} }
}, [renderer, scene, camera, hoverHighlightMode, isInitialized, zoneLayers]) }, [
renderer,
scene,
camera,
hoverHighlightMode,
isInitialized,
zoneLayers,
projectId,
pipelineVersion,
])
useFrame((_, delta) => { useFrame((_, delta) => {
// Animate background colour toward the current theme target (same lerp as AnimatedBackground) // Animate background colour toward the current theme target (same lerp as AnimatedBackground)
@@ -275,6 +311,14 @@ const PostProcessingPasses = () => {
bgUniform.current.value.copy(bgCurrent.current) bgUniform.current.value.copy(bgCurrent.current)
if (hasPipelineErrorRef.current || !renderPipelineRef.current) { if (hasPipelineErrorRef.current || !renderPipelineRef.current) {
try {
if ((renderer as any).setClearAlpha) {
;(renderer as any).setClearAlpha(1)
}
;(renderer as any).render(scene, camera)
} catch (fallbackError) {
console.error('[viewer] Fallback render failed.', fallbackError)
}
return return
} }
@@ -297,7 +341,10 @@ const PostProcessingPasses = () => {
console.warn( console.warn(
`[viewer] Scheduling post-processing rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`, `[viewer] Scheduling post-processing rebuild (attempt ${retryCountRef.current}/${MAX_PIPELINE_RETRIES})`,
) )
setTimeout(requestPipelineRebuild, RETRY_DELAY_MS) if (rebuildTimeoutRef.current !== null) {
clearTimeout(rebuildTimeoutRef.current)
}
rebuildTimeoutRef.current = setTimeout(requestPipelineRebuild, RETRY_DELAY_MS)
} else { } else {
console.error( console.error(
'[viewer] Post-processing retries exhausted. Rendering without post FX for this session.', '[viewer] Post-processing retries exhausted. Rendering without post FX for this session.',