Quest Pro browser: WebXR session frame production permanently drops ~13% after 20–50s
- August 9, 2026
- 0 replies
- 41 views
GPU clocks pinned, per-frame GPU cost flat; coincides with tracking/localization activity resuming after session start; exiting and re-entering the session resets the cycle.
Environment
-----------
- **Device:** Meta Quest Pro
- **OS:** Horizon OS v207, Android build UP1A.231005.007.A1
- **Browser:** Meta Quest Browser 149.1.0.10.52.1018773329
- **Content:** WebXR immersive-vr session (three.js r185, WebGL2), OVR_multiview2\
stereo scene pass into a single-colour-attachment offscreen target, 72 Hz session,\
framebufferScaleFactor 0.75. App GPU cost ≈ 15 ms/frame at the test station (heavy\
scene, deliberately chosen --- the effect is easiest to see when frames sit near budget).
- Repro'd with eye tracking, natural facial expressions, and hand/body tracking **disabled**,\
and with the developer-mode boundary **disabled** --- the behaviour is unchanged.
Summary
-------
For the first ~20--50 s of an immersive session the browser produces XR frames at the rate\
the GPU allows (~55--62 fps for our ~15 ms frames, GPU utilization 96--98%). Then, within a\
few seconds, new-frame production drops ~13% (to ~44--52 fps) and **stays there for the\
remainder of the session**. During and after the drop:
- GPU clock level and frequency are **pinned and unchanged** (CPU4/GPU=4/4,\
1478/525MHz, DVFS=0 throughout --- no governor step).
- The app's per-frame GPU cost (App= in the VrApi stat line) is **flat across the\
drop** (e.g. 15.6 ms before → 15.4 ms after). The work per frame did not change; the\
number of frames produced did.
- GPU utilization **falls** (96--98% → 82--90%): the GPU is idling between late frames, not\
saturating.
- CPU utilization is flat (~60--67% avg, worst core ~70%), temperature flat (~23 °C),\
memory flat. No thermal event, PLS=0.
- **Exiting the immersive session and re-entering it resets the behaviour** --- another\
20--50 s of full-rate production, then the same drop. This reset-on-re-enter reproduces\
every time and is what rules out thermal/battery explanations.
The drop coincides, in all three captures, with the OS tracking stack resuming full\
activity after its session-start quiet period: LocalizationStateMachine transitions\
(Cooldown → FineLocalizationOnly → CoarseAndFineLocalization → LoadSubmapList),\
VegaMapLoader submap loads, in one capture a repeating\
VEGA:HM:MAP_TRACKING: Validation failed on Submap id = 1, DSP (Hexagon) vision-kernel\
telemetry, and IMU Update exceeded imu sampling period warnings. We note honestly that\
the tracking service logs continuously, so this correlation is circumstantial --- but the\
frame-production drop with pinned clocks and flat per-frame cost is constant across all\
three captures, and the timing (post-session-start cooldown expiry, reset on session\
re-enter) matches the localization state machine's cycle in each one.
Reproduction
------------
1. Load a WebXR app with GPU frame cost near budget (~13--16 ms at 72 Hz) in the Quest Pro\
browser. Enter immersive-vr.
2. Watch the per-second VrApi logcat line (or OVR Metrics: FPS + GPU utilization).
3. For the first ~20--50 s: fps ≈ 55--62, GPU ≈ 96--98%.
4. Observe the drop: fps → 44--52, GPU% → 82--90, App= unchanged, clocks unchanged.\
The state persists indefinitely.
5. Exit the session to the 2D page; re-enter. The full-rate window returns, then the drop\
repeats.
Evidence excerpts (capture 3 of 3 --- all three attached)
-------------------------------------------------------
Before (session seconds ~0--20):
FPS=54/72, Stale=21, CPU4/GPU=4/4, 1478/525MHz, GPU%=0.97, CPU%=0.63, App=15.05ms, Temp=23.0C, DVFS=0
After (~22 s onward, unchanged for the rest of the session):
FPS=46/72, Stale=28, CPU4/GPU=4/4, 1478/525MHz, GPU%=0.87, CPU%=0.66, App=15.21ms, Temp=23.0C, DVFS=0
Coincident with the transition (same seconds, Tracking pid):
LocalizationStateMachine updated from Cooldown to FineLocalizationOnly LocalizationStateMachine updated from FineLocalizationOnly to CoarseAndFineLocalization VegaMapLoader: BA units loaded 5/5, MapPointCount 1793 ... VEGA:HM:MAP_TRACKING: Validation failed on Submap id = 1, version = 479537200 (capture 2) IoteLogging: IMU Update exceeded imu sampling period BOLT:HexagonTelemetry: Global DSP utilization: 30.74% ... Scalar Util: 50.05%, HVX Util: 51.35%
One capture also recorded a system-wide scheduling symptom shortly after the drop:
W/Looper: Slow delivery took 98345ms android.bg h=android.os.Handler c=...VisibleActivityProcessTracker...
What we ruled out
-----------------
| Suspect | Verdict | Evidence |
|---|---|---|
| GPU clock governor / boost expiry | ruled out | levels + MHz pinned all captures, DVFS=0 |
| Thermal throttling | ruled out | 23 °C flat; reset-on-re-enter is incompatible with thermals |
| App workload change | ruled out | App= flat across the drop in all captures |
| CPU saturation in the app | ruled out | CPU ~60–67% flat, worst core ~70%, Prd stable |
| Eye/face/body tracking services | ruled out | disabled in settings; behaviour unchanged (capture 3) |
| Guardian/boundary | ruled out | developer-mode boundary disabled; behaviour unchanged (capture 3) |
Questions
---------
1. Is the browser's XR frame loop expected to lose scheduling priority (or budget) when\
the localization pipeline resumes full activity after its session-start cooldown?
2. Is there a known contention issue between Tracking/VegaMapLoader submap operations\
(including repeated submap validation failures) and foreground app frame pacing on\
Quest Pro?
3. Is there anything a WebXR page can do to avoid or mitigate this state, given that\
XRSession.visibilityState remains visible and no WebXR-visible signal accompanies\
the drop?
Attachments
-----------
Raw 120 s logcat captures (full, unfiltered):
- clockwatch-2026-08-09T07-19-41-274Z.txt --- first capture; drop at ~+10 s of capture
- clockwatch-2026-08-09T07-34-34-172Z.txt --- second; includes Validation failed on Submap
- clockwatch-2026-08-09T07-51-23-265Z.txt --- third; optional tracking features + boundary\
disabled, behaviour unchanged
