How WebXR works in Servo

Terminology

Servo's WebXR implementation involves three main components:

  1. The script thread (runs all JS for a page)
  2. The WebGL thread (maintains WebGL canvas data and invokes GL operations corresponding to WebGL APIs)
  3. The compositor (AKA the main thread)

Additionally, there are a number of WebXR-specific concepts:

  • The discovery object (ie. how Servo discovers if a device can provide a WebXR session)
  • The WebXR registry (the compositor's interface to WebXR)
  • The layer manager (manages WebXR layers for a given session and frame operations on those layers)
  • The layer grand manager (manages all layer managers for WebXR sessions)

Finally, there are graphics-specific concepts that are important for the low-level details of rendering with WebXR:

  • surfman is a crate that abstracts away platform-specific details of OpenGL hardware-accelerated rendering
  • surface is a hardware buffer that are tied to a specific OpenGL context
  • surface texture is an OpenGL texture that wraps a surface. Surface textures can be shared between OpenGL contexts.
  • surfman context represents a particular OpenGL context, and is backed by platform-specific implementations (such as EGL on Unix-based platforms)
  • ANGLE is an OpenGL implementation on top of Direct3D which is used in Servo to provide a consistent OpenGL backend on Windows-based platforms

How Servo's compositor starts

The embedder is responsible for creating a window and triggering the rendering context creation appropriately. Servo creates the rendering context by creating a surfman context which will be used by the compositor for all web content rendering operations.

How a session starts

When a webpage invokes navigator.xr.requestSession(..) through JS, this corresponds to the XrSystem::RequestSession method in Servo. This method sends a message to the WebXR message handler that lives on the main thread, under the control of the compositor.

The WebXR message handler iterates over all known discovery objects and attempts to request a session from each of them. The discovery objects encapsulate creating a session for each supported backend.

As of Feb 3, 2024, there are five WebXR backends:

  • magicleap - supports Magic Leap 1.0 devices
  • googlevr - supports Google VR
  • headless - supports a window-less, device-less device for automated tests
  • glwindow - supports a GL-based window for manual testing in desktop environments without real devices
  • openxr - supports devices that implement the OpenXR standard

WebXR sessions need to create a layer manager at some point in order to be able to create and render to WebXR layers. This happens in several steps:

  1. some initialization happens on the main thread
  2. the main thread sends a synchronous message to the WebGL thread
  3. the WebGL thread receives the message
  4. some backend-specific, graphics-specific initialization happens on the WebGL thread, hidden behind the layer manager factory abstraction
  5. the new layer manager is stored in the WebGL thread
  6. the main thread receives a unique identifier representing the new layer manager

This cross-thread dance is important because the device performing the rendering often has strict requirements for the compatibility of any WebGL context that is used for rendering, and most GL state is only observable on the thread that created it.

How an OpenXR session is created

The OpenXR discovery process starts at OpenXrDiscovery::request_session. The discovery object only has access to whatever state was passed in its constructor, as well as a SessionBuilder object that contains values required to create a new session.

Creating an OpenXR session first creates an OpenXR instance, which allows coniguring which extensions are in use. There are different extensions used to initialize OpenXR on different platforms; for Window the D3D extension can be used since Servo relies on ANGLE for its OpenGL implementation.

Once an OpenXR instance exists, the session builder is used to create a new WebXR session that runs in its own thread. All WebXR sessions can either run in a thread or have Servo run them on the main thread. This choice has implications for how the graphics for the WebXR session can be set up, based on what GL state must be available for sharing.

OpenXR's new session thread initializes an OpenXR device, which is responsible for creating the actual OpenXR session. This session object is created on the WebGL thread as part of creating the OpenXR layer manager, since it relies on sharing the underlying GPU device that the WebGL thread uses.

Once the session object has been created, the main thread can obtain a copy and resume initializing the remaining properties of the new device.