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.