Describe the bug
On Meta Quest 3 (Meta Quest Browser), an immersive WebXR session hangs within ~2 seconds and never recovers (the page's rAF loop stops permanently; the OS itself stays responsive) when all three of the following are active:
new THREE.WebGLRenderer({ antialias: true }) (MSAA)
renderer.shadowMap.enabled = true with a shadow-casting light rendering a per-frame shadow depth pass
- A Spark
SplatMesh being rendered
Removing any one of the three avoids the hang. Verified matrix (each condition tested in isolation on device):
| antialias |
shadow pass |
SplatMesh |
result |
| on |
off |
on |
OK |
| off |
on |
on |
OK |
| on |
on |
off |
OK |
| on |
on |
on |
hangs within seconds |
| on |
on (baked once via shadowMap.autoUpdate = false) |
on |
OK |
Additional observations:
- Adding the
SplatMesh mid-session vs. before entering VR makes no difference.
- Not reproducible on desktop, PICO 4, or Apple Vision Pro — Quest Browser only.
- WASM + SIMD are available on the device (verified in-page); sorting runs in the worker normally, so this is not a WASM-fallback issue.
- Throttling with
minSortIntervalMs was not required for the workaround; the deciding factor is whether the shadow depth pass runs every frame.
Environment
Reproduction
Single-file repro (CDN imports only, all variables toggled via URL params):
https://github.com/sawa-zen/xrift-world-3dgs-musium/tree/7fab1f8/tools/quest-msaa-repro
Serve the folder over HTTPS, put any .spz next to it as model.spz, open on Quest, enter VR (a spinning cube + frame counter make the hang obvious):
/?aa=1&shadow=0 → OK
/?aa=0&shadow=1 → OK
/?aa=1&shadow=1&splat=0 → OK
/?aa=1&shadow=1 → hangs
/?aa=1&shadow=1&shadowstatic=1 (shadow map baked once) → OK
Suspected mechanism
The per-frame shadow depth pass (render-target switch to a depth texture), the MSAA resolve of the XR framebuffer, and Spark's out-of-band GPU work (update pass scheduled via setTimeout + fence-based async depth readback for sorting) appear to deadlock the Adreno driver when interleaved every frame. We could not pinpoint the exact GL interaction without a GPU debugger.
Workaround for other users
renderer.shadowMap.autoUpdate = false (bake shadows once / on demand) allows MSAA + shadows + Spark to coexist on Quest.
Happy to test patches or provide more traces.
Describe the bug
On Meta Quest 3 (Meta Quest Browser), an immersive WebXR session hangs within ~2 seconds and never recovers (the page's rAF loop stops permanently; the OS itself stays responsive) when all three of the following are active:
new THREE.WebGLRenderer({ antialias: true })(MSAA)renderer.shadowMap.enabled = truewith a shadow-casting light rendering a per-frame shadow depth passSplatMeshbeing renderedRemoving any one of the three avoids the hang. Verified matrix (each condition tested in isolation on device):
shadowMap.autoUpdate = false)Additional observations:
SplatMeshmid-session vs. before entering VR makes no difference.minSortIntervalMswas not required for the workaround; the deciding factor is whether the shadow depth pass runs every frame.Environment
@sparkjsdev/spark2.1.0 (includes Ensure only one update timeout is scheduled at a time and callgl.flush()to encourage eager execution #156), three0.185.1(same behavior observed in an app on three 0.183.x)Reproduction
Single-file repro (CDN imports only, all variables toggled via URL params):
https://github.com/sawa-zen/xrift-world-3dgs-musium/tree/7fab1f8/tools/quest-msaa-repro
Serve the folder over HTTPS, put any
.spznext to it asmodel.spz, open on Quest, enter VR (a spinning cube + frame counter make the hang obvious):/?aa=1&shadow=0→ OK/?aa=0&shadow=1→ OK/?aa=1&shadow=1&splat=0→ OK/?aa=1&shadow=1→ hangs/?aa=1&shadow=1&shadowstatic=1(shadow map baked once) → OKSuspected mechanism
The per-frame shadow depth pass (render-target switch to a depth texture), the MSAA resolve of the XR framebuffer, and Spark's out-of-band GPU work (update pass scheduled via
setTimeout+ fence-based async depth readback for sorting) appear to deadlock the Adreno driver when interleaved every frame. We could not pinpoint the exact GL interaction without a GPU debugger.Workaround for other users
renderer.shadowMap.autoUpdate = false(bake shadows once / on demand) allows MSAA + shadows + Spark to coexist on Quest.Happy to test patches or provide more traces.