Skip to content

WebGPURenderer: Track frontFaceCW in needsRenderUpdate.#33780

Merged
Mugen87 merged 1 commit into
mrdoob:devfrom
LLiJJing:fix-webgpu-frontfacecw-cache
Jun 12, 2026
Merged

WebGPURenderer: Track frontFaceCW in needsRenderUpdate.#33780
Mugen87 merged 1 commit into
mrdoob:devfrom
LLiJJing:fix-webgpu-frontfacecw-cache

Conversation

@LLiJJing

Copy link
Copy Markdown
Contributor

Description

Fixes #33779.

WebGPUBackend.getRenderCacheKey includes frontFaceCW = object.isMesh && object.matrixWorld.determinant() < 0 in the pipeline cache key because the GPU frontFace enum is baked into the pre-compiled render pipeline. However, the sibling WebGPUBackend.needsRenderUpdate doesn't track the same signal, so when an existing mesh transitions between positive and negative determinant (typical case: editor toggles a mirror/flip via negative scale on an axis), Pipelines.getForRender short-circuits on the cached renderObject.pipeline and never re-evaluates the cache key. The mesh continues to draw with the stale frontFace, culling the visually-outward fragments. For lit opaque materials this results in a fully-black render.

This PR adds frontFaceCW to needsRenderUpdate's dirty check so the two sides of the cache surface stay symmetric.

Reproduction

See linked issue for the JSFiddle live demo (https://jsfiddle.net/fvpbt90h/23/). Before this patch, clicking "flip" causes the TorusKnot to disappear (closed convex geometry → all faces culled by stale frontFace) and clicking "workaround" makes it reappear. After this patch, "flip" works correctly on its own without needing the manual material.needsUpdate = true bump.

Notes

  • Two-line addition mirroring the shape of every other comparison in needsRenderUpdate.
  • Zero behavioural change for objects whose matrixWorld.determinant() never changes sign — the new comparison just stores a stable false (or stable true) in data.frontFaceCW.
  • The first frame after a sign change pays one extra pipeline cache lookup; subsequent frames at the new sign hit the cache as before.
  • npm run lint and npm run test-unit (1308 pass / 0 fail) both clean locally.
  • Related historical fix in WebGLShadowMap along the same lines: Fix: wrong face cull for shadows of freshly inserted mirrored objects. #15825.

`WebGPUBackend.getRenderCacheKey` includes `frontFaceCW = object.matrixWorld.determinant() < 0` in the pipeline cache key because the GPU `frontFace` enum is baked into the pre-compiled render pipeline. The sibling `WebGPUBackend.needsRenderUpdate` did not track the same signal, so when an existing mesh transitioned between positive and negative determinant (typical case: editor toggles a mirror/flip via negative scale on an axis) `Pipelines.getForRender` short-circuited on the cached `renderObject.pipeline` and never re-evaluated the cache key. The mesh continued to draw with the stale `frontFace`, culling the visually-outward fragments. For lit opaque materials this resulted in a fully-black render.

This adds `frontFaceCW` to `needsRenderUpdate`'s dirty check so the two sides of the cache surface stay symmetric.
@github-actions

Copy link
Copy Markdown

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 365.5
86.83
365.5
86.83
+0 B
+0 B
WebGPU 666.47
184.38
666.55
184.4
+77 B
+24 B
WebGPU Nodes 664.51
184.07
664.59
184.09
+77 B
+21 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 507.46
123.35
507.46
123.35
+0 B
+0 B
WebGPU 745.72
200.58
745.8
200.6
+77 B
+23 B
WebGPU Nodes 694.94
188.02
695.02
188.04
+77 B
+22 B

@Mugen87 Mugen87 added this to the r185 milestone Jun 12, 2026
@Mugen87

Mugen87 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

I can confirm the bug is real and that your suggested fix solves the problem. This makes the WebGPU backend in line with the WebGL backend which does not have this issue since the check is already honored correctly.

@Mugen87 Mugen87 merged commit 8254f92 into mrdoob:dev Jun 12, 2026
9 of 10 checks passed
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.

WebGPURenderer: cached pipeline keeps stale frontFace after mesh's matrixWorld determinant changes sign

2 participants