Quest WebXR Performance
A desktop world that feels effortless can miss every headset frame. Sparksbud taught us to stop treating one scene counter as a performance verdict. Geometry, draw calls and textures help locate a problem, but the actual question is whether the headset presents both eyes on time while the visitor turns, opens the dock and changes worlds.
At a requested 72 Hz, a frame has about 13.9 milliseconds before the next display interval. Treat that as a target, then measure on the headset. Sparksbud tracks triangles, draw calls and geometry counts, builds reduced detail for selected worlds and checks two-eye rendering in an emulator. Those checks do not establish physical Quest frame pacing or thermal behavior.
Begin with the display interval
Divide one second by the refresh rate to get the interval: 72 Hz is about 13.9 ms, 90 Hz about 11.1 ms and 120 Hz about 8.3 ms. That interval is a target for the complete frame, including application work, rendering and the browser's XR path. It is not a promise that an app requesting a rate will get it. Sparksbud asks for 72 Hz when the session reports that rate as supported; the actual rate still needs a headset readout.
The renderer draws each eye from a different view. A desktop screenshot shows one view and does not exercise that stereo path. On Sparksbud we check that both eyes render, that the XR dock remains usable and that scene counters stay within each world's own budget. A Quest emulator is useful for geometry, control and rendering regressions. It runs on a desktop GPU, so its frame timing cannot stand in for a mobile headset.
| Signal | Useful for | Cannot prove |
|---|---|---|
| Rendered triangles | Finding an unexpectedly dense scene or quality profile | GPU time on a headset |
| Draw calls | Spotting too many separate objects or materials | Smooth frame pacing by itself |
| Geometry and texture counts | Catching growth across switches or long sessions | That every allocation has been released |
| Desktop frame intervals | Comparing local builds under the same conditions | Quest FPS or battery and heat behavior |
| Physical headset frame time | Acceptance on that device, build and scene | Performance on every other headset |
Three.js exposes render calls, triangles and allocated geometry through renderer.info. Sparksbud copies those counters onto its canvas for diagnostics. We reset the render counters at each draw because a stereo frame and a postprocessing frame are not comparable unless we know what was counted. We also record the number of XR eye cameras. That makes a report more useful than “it looked smooth on my laptop.”
Write down the refresh rate reported by the headset before judging a frame budget. Requesting 72 Hz in code doesn't prove the session runs at 72 Hz.
Spend less on what does not change the scene
Sparksbud shares geometry through instancing, merges some static shapes and keeps scene motion in bounded pools. The cheap version of a world is authored, not an emergency blur filter. Its quality choice can cut geometry, particle counts or shader work while retaining the composition. Kaleidoscope has a separate Quest profile because the full desktop optics were too heavy in headset testing. The desktop version stays intact, while the headset takes the lighter build.
Some Sparksbud worlds also lower desktop pixel ratio after sustained slow frames. That path runs only outside XR. The headset path uses the browser's XR framebuffer and a world-specific scale setting before the session begins. Three.js documents that setFramebufferScaleFactor() cannot be changed during an XR session, so it is not a control to turn reactively after each slow headset frame.
Foveated rendering also has a visual cost. Sparksbud currently sets foveation to zero because a view-locked edge showed a contrast change in tiny additive particles as the head tilted. That is a tradeoff specific to these scenes, not a general recommendation to disable foveation. A developer should compare the actual peripheral result and headset frame time before deciding.
Make the budget repeatable
- Capture a named build, device, browser version, scene and quality profile. “Quest profile” in a desktop browser is only an emulation of scene choices.
- Use the same viewpoint and interaction. A world seen toward open sky may cost less than a frame facing the densest geometry or the XR dock.
- Record frame time over more than the first few seconds. Shader compilation, scene changes and heat can change the result.
- Keep CPU-side and GPU-side hypotheses separate. Fewer triangles will not fix a long JavaScript build on the main thread; fewer objects will not fix an expensive full-screen shader.
- Recheck the visual result. An optimization that removes the world's main focal point is a failed design change even if the counter improves.
| Stage | Evidence | Decision |
|---|---|---|
| Build | Finite geometry, bounded instances, no broken shaders | The scene is structurally safe to render |
| Browser | Desktop, narrow phone view and two-eye emulator | Layout, controls and stereo path behave |
| Headset | Physical frame time while turning and interacting | This device can present the world acceptably |
| Long session | Repeated switches and time in scene | Heat, memory and frame pacing stay acceptable |
The first two stages are good at catching mistakes cheaply. They cannot close the last two. Our QA records mark physical headset pacing as unmeasured where it has not been read. The world lifecycle note covers the repeated-switch side of the budget.
Keep the camera on the densest part of the scene and repeat once with the dock visible and once hidden. Record those as separate runs instead of averaging them together.
Where the next gains may be
The largest apparent meshes are not always the slowest pass. A full-screen shader runs over pixels in both eyes, while an instanced scene can submit many triangles in few calls. If a world misses its target, profile the problem before cutting art. Our next measurement pass should use physical Quest frame time in the densest view, with the dock shown and hidden, then decide whether to change geometry, overdraw, textures, framebuffer scale or the update loop.
Questions developers ask
Is 13.9 ms a guaranteed Quest budget?
No. It is the interval for 72 Hz, not a measured application allotment. Browser, compositor and device behavior must be measured on the target headset.
Can I use a desktop Quest profile as a headset benchmark?
No. It can check which content and quality path loads. It does not reproduce the headset GPU, thermals or frame timing.
Does a lower triangle count always improve VR performance?
No. Shader cost, overdraw, textures, draw calls and JavaScript can dominate a frame. Change the measured bottleneck and check the visual result.
Can framebuffer scale change mid-session in Three.js?
Three.js says setFramebufferScaleFactor() cannot be used during an XR session. Set it before entry, then verify the actual result on the device.
Why is foveation disabled in Sparksbud?
Tiny additive particles showed a view-locked edge artifact as the head tilted. That observation is specific to Sparksbud's art; other scenes may benefit from foveation.


