Understanding XR Frames and WebXR Rendering Loops
This article provides an overview of XR frames and explains how
JavaScript coordinates rendering loops for virtual reality (VR) using
the WebXR Device API. You will learn what an XRFrame
represents, how the VR rendering loop differs from traditional
screen-based rendering, and the mechanisms JavaScript uses to
synchronize frame updates with high-refresh VR headsets to eliminate
latency and motion sickness.
What is an XR Frame?
In the WebXR Device API, an XRFrame represents a
snapshot of the tracking and presentation state for an immersive session
at a single moment in time. Rather than representing an actual image or
pixel buffer, the XRFrame provides access to positional
data, device orientations, and spatial tracking metrics.
Each XRFrame is transient and only valid during the
execution of the frame callback function. It serves as the primary
gateway to query: - Viewer Poses: The current position
and orientation of the user’s head (XRViewerPose). -
Input Sources: Hand controller positions, rotations,
and button states. - Reference Spaces: Coordinate
systems used to place virtual objects relative to the real world or the
user.
The WebXR Rendering Loop
Traditional web animations rely on
window.requestAnimationFrame() to update visual elements at
the standard monitor refresh rate (typically 60Hz). Virtual reality
requires much higher and more consistent frame rates (usually 72Hz,
90Hz, or 120Hz) to prevent visual stutter and motion sickness.
To handle these hardware demands, WebXR introduces
XRSession.prototype.requestAnimationFrame(). This method
synchronizes JavaScript execution directly with the VR headset’s native
display compositor rather than the desktop browser’s refresh rate.
function onXRFrame(time, frame) {
const session = frame.session;
// Schedule the next frame
session.requestAnimationFrame(onXRFrame);
// Retrieve pose and render scene
const pose = frame.getViewerPose(referenceSpace);
if (pose) {
// Render the scene for each eye
}
}How JavaScript Synchronizes VR Rendering
Synchronizing the render loop with VR hardware involves several coordinated steps between JavaScript, WebGL, the browser engine, and the underlying VR runtime (such as OpenXR).
1. Timing and Callbacks
When an immersive session begins, JavaScript registers an animation
callback using session.requestAnimationFrame. The browser
coordinates with the XR compositor to invoke this callback at the exact
moment the hardware requires a new frame. The callback receives a
high-precision timestamp and the current XRFrame.
2. Pose Prediction and Querying
To account for the latency between CPU calculation and display
illumination, the XR runtime predicts where the user’s head will be when
the photons actually hit the display. Within the render loop, calling
frame.getViewerPose(referenceSpace) provides this predicted
pose.
3. Multi-View Rendering
A typical VR headset contains two displays or splits a single display
into two views (one for each eye). Inside the loop, the
XRViewerPose.views array contains an XRView
for each eye: - JavaScript binds the WebGL framebuffer provided by the
session’s XRWebGLLayer. - For each view, the developer
adjusts the WebGL viewport to match the eye’s assigned region on the
framebuffer. - The projection matrix and view matrix are applied to
render the 3D scene from the perspective of each eye.
4. Frame Submission and Reprojection
Once JavaScript finishes drawing the scene and the frame callback completes, the browser automatically submits the WebGL canvas to the XR compositor.
If a JavaScript execution spike causes the frame to miss the hardware refresh deadline, the XR runtime applies techniques like Asynchronous Timewarp (ATW) or reprojection. These techniques take the previous frame and warp it based on the latest head movement, maintaining visual stability until the next JavaScript frame arrives.