How JSAR Manages XR Sessions Using WebXR: A Deep Dive into the JavaScript AR Runtime
JSAR manages XR sessions by wrapping the standard WebXR API with a thin abstraction layer that delegates framebuffer creation to the host engine while extending XRSession with deferred-composition helpers for coordinate handedness, collision boxes, and multiview rendering.
The m-creativelab/jsar-runtime repository implements a JavaScript Augmented-Reality runtime that simplifies immersive AR development. By building on top of the browser's native WebXR implementation, JSAR allows developers to launch XR sessions with minimal boilerplate while providing host engines like Unity or Unreal with low-level control over rendering and hit-testing.
The WebXR Session Lifecycle in JSAR
JSAR concentrates its XR session management logic in the splinedesign viewer, which demonstrates the complete lifecycle from request to render loop.
Requesting an Immersive-AR Session
The process begins with a standard WebXR session request. In lib/runtime2/viewers/splinedesign.ts, JSAR calls the browser's XR system to initiate an AR session:
navigator.xr.requestSession('immersive-ar', {})
.then(session => {
// Session initialization continues...
})
.catch(err => console.warn('Failed to start XR session:', err));
This returns a standard XRSession object that JSAR subsequently extends with its own runtime capabilities.
Binding the Host GL Context to XRWebGLLayer
Unlike typical web-based WebXR applications that create their own WebGL contexts, JSAR receives a pre-configured GL context from the host engine through navigator.gl. The runtime wraps this context in an XRWebGLLayer and attaches it to the session:
const baseLayer = new XRWebGLLayer(session, navigator.gl);
session.updateRenderState({ baseLayer });
This delegation pattern allows the host engine to maintain control over framebuffer allocation while the JavaScript client handles the rendering commands.
Configuring Three.js for XR Rendering
Once the WebXR layer is established, JSAR configures Three.js to use the host-provided canvas and context. The renderer is set up with XR enabled and attached to the session:
const renderer = new THREE.WebGLRenderer({
canvas: navigator.gl._screenCanvas,
context: navigator.gl,
});
renderer.preserveDrawingBuffer = true;
renderer.autoClear = false;
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType('local');
renderer.xr.setSession(session);
function animate() {
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
The setAnimationLoop method drives the render loop, automatically handling the XR pose updates each frame.
JSAR's Extended WebXR API for Deferred Composition
Behind the scenes, JSAR extends standard WebXR interfaces with deferred-composition helpers. These extensions, declared in types/transmute-webapis.d.ts, allow the host engine to optimize rendering and hit-testing without breaking the standard WebXR contract.
Coordinate Handedness Management
JSAR adds setDefaultCoordHandedness to XRSession, allowing the host to specify whether the rendering pipeline uses left- or right-handed coordinates:
interface XRSession {
setDefaultCoordHandedness?(handedness: 'left' | 'right'): void;
}
This ensures correct view-projection matrix construction when the host engine uses a different coordinate system than standard WebGL.
Host-Side Hit-Testing with Collision Boxes
To reduce per-frame latency, JSAR allows the client to send collision boundaries to the host via updateCollisionBox:
interface XRSession {
updateCollisionBox?(min: number[], max: number[]): void;
}
The host performs hit-testing against these bounds natively, returning results without requiring the JavaScript runtime to process every frame.
Multiview Rendering Support
For stereoscopic rendering optimization, XRWebGLLayer exposes a multiviewRequired property:
interface XRWebGLLayer {
readonly multiviewRequired: boolean;
}
When true, the host framebuffer expects multiview rendering (e.g., glFramebufferTextureMultiviewOVR), allowing the client to adjust shaders accordingly for single-pass stereo rendering.
Architecture Overview: Client-Host Delegation
JSAR's architecture delegates heavy graphics work to the host engine while maintaining a standard WebXR surface for developers:
+-------------------+ navigator.xr.requestSession('immersive-ar')
| Web Browser | ------------------------------> XRSession (JSAR wrapper)
+-------------------+ |
+------------------------+------------------------+
| | |
XRWebGLLayer (host GL) XRSession extensions (handedness, hit-test) Three.js Renderer (xr.enabled)
| | |
renderer.xr.setSession(session) | render loop
+------------------------+------------------------+
|
JSAR host (Unity / Unreal / custom engine)
This delegation allows JSAR to keep the XR session lightweight while the host manages framebuffer creation, hit-testing, and coordinate system transformations.
Implementation Examples
Minimal XR-Enabled Viewer
The splinedesign.ts viewer demonstrates the complete setup:
// Request an immersive-AR session and bind it to the host's GL context
navigator.xr.requestSession('immersive-ar', {})
.then(session => {
const baseLayer = new XRWebGLLayer(session, navigator.gl);
session.updateRenderState({ baseLayer });
const renderer = new THREE.WebGLRenderer({
canvas: navigator.gl._screenCanvas,
context: navigator.gl,
});
renderer.preserveDrawingBuffer = true;
renderer.autoClear = false;
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType('local');
renderer.xr.setSession(session);
function animate() {
renderer.render(scene, camera);
}
renderer.setAnimationLoop(animate);
})
.catch(err => console.warn('Failed to start XR session:', err));
Source: [lib/runtime2/viewers/splinedesign.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/lib/runtime2/viewers/splinedesign.ts) – lines 54‑67.
Extended WebXR Type Definitions
The type declarations reveal the extended API surface:
// These methods are declared in the JSAR type layer
declare global {
interface XRSession {
/** Set coordinate handedness (left/right) for deferred composition */
setDefaultCoordHandedness?(handedness: 'left' | 'right'): void;
/** Send a collision box to the host for hit-testing */
updateCollisionBox?(min: number[], max: number[]): void;
}
interface XRWebGLLayer {
/** True when the host framebuffer expects multiview rendering */
readonly multiviewRequired: boolean;
}
}
Source: [types/transmute-webapis.d.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/types/transmute-webapis.d.ts) – XRSession (lines 55‑68) and XRWebGLLayer (lines 74‑96).
Native Session Types
The private types reveal the host-side implementation:
// Native side of the XR session, used by the host engine
type XRNativeSession = {
// opaque id used to reference the session on the host
sessionId: number;
// ... other host-specific fields
};
Source: [types/transmute-private.d.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/types/transmute-private.d.ts) – line 145.
Key Files and Their Roles
| File | Role |
|---|---|
[lib/runtime2/viewers/splinedesign.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/lib/runtime2/viewers/splinedesign.ts) |
Demonstrates the full XR session creation, WebGL layer binding, and Three.js XR setup. |
[types/transmute-webapis.d.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/types/transmute-webapis.d.ts) |
Declares the extended XRSession and XRWebGLLayer interfaces that JSAR adds to the standard WebXR API. |
[types/transmute-private.d.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/types/transmute-private.d.ts) |
Contains internal native-session types (XRNativeSession, XRNativeInputSource) used by the host engine. |
[lib/main.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/lib/main.ts) |
Entry point that sets up the JSAR environment, patches global objects (e.g., navigator.gl), and loads the appropriate viewer. |
[lib/bindings/renderer.ts](https://github.com/m-creativelab/jsar-runtime/blob/main/lib/bindings/renderer.ts) |
Provides the bridge between the host’s GL context and the client-side Three.js renderer. |
Summary
- JSAR wraps the standard WebXR API to provide a lightweight abstraction for AR sessions, implemented primarily in
lib/runtime2/viewers/splinedesign.ts. - Host delegation allows JSAR to use a pre-existing GL context via
navigator.gl, creating anXRWebGLLayerthat renders into a host-managed framebuffer. - Extended WebXR interfaces in
types/transmute-webapis.d.tsadd deferred-composition helpers for coordinate handedness, collision-box hit-testing, and multiview rendering. - Three.js integration requires enabling
renderer.xr.enabled, setting the reference space to'local', and attaching the session viarenderer.xr.setSession(session).
Frequently Asked Questions
How does JSAR differ from standard browser WebXR implementations?
JSAR acts as a thin runtime that delegates heavy graphics operations to a native host engine (such as Unity or Unreal) while exposing the standard WebXR API to JavaScript developers. Unlike browsers that manage their own framebuffers and hit-testing, JSAR uses navigator.gl to bind to a host-provided GL context and extends XRSession with methods like updateCollisionBox to offload computations to the host.
What is the purpose of the XRWebGLLayer extensions in JSAR?
The XRWebGLLayer extensions, specifically the multiviewRequired property declared in types/transmute-webapis.d.ts, allow the host engine to signal whether it expects multiview rendering (such as glFramebufferTextureMultiviewOVR). This enables the JavaScript client to adjust its shaders for single-pass stereo rendering when running on host engines that support multiview framebuffers, optimizing performance for AR headsets.
How does JSAR handle coordinate system differences between the host engine and WebGL?
JSAR addresses coordinate system mismatches through the setDefaultCoordHandedness method on XRSession. This extension allows the host to inform the JavaScript runtime whether it uses left-handed or right-handed coordinates. According to the type definitions in types/transmute-webapis.d.ts, this ensures that view-projection matrices are constructed correctly for the host's rendering pipeline, preventing mirroring or orientation errors in the final AR output.
Can JSAR run without a host engine providing navigator.gl?
No, JSAR requires a host engine to function. The runtime depends on navigator.gl being injected by the host environment, as seen in lib/main.ts and utilized in lib/runtime2/viewers/splinedesign.ts. Without this host-provided GL context, JSAR cannot create the XRWebGLLayer or bind to the framebuffer, as the runtime is specifically designed as a client-host architecture where heavy graphics operations and session management are delegated to native engines like Unity or Unreal.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →