BabylonJS/Babylon.js · error
Dynamic viewport scaling must be used during an active XR fr
Error message
Dynamic viewport scaling must be used during an active XR frame.
What it means
WebXRSessionManager._getCurrentXRView() retrieves an XRView from the current frame's viewer pose so callers can apply dynamic viewport scaling. The manager tracks whether a frame loop callback is currently executing (inXRFrameLoop) and holds the active XRFrame (currentFrame). If there is no frame in flight, there is no viewer pose to query views from, so the manager throws rather than return undefined.
Source
Thrown at packages/dev/core/src/XR/webXRSessionManager.ts:283
* This method must be called during an active XR frame.
* @param viewIndex the index of the view in the current viewer pose
* @param scale the viewport scale requested from the runtime
* @see https://playground.babylonjs.com/#BAGIIM#0
*/
public requestViewportScale(viewIndex: number, scale: Nullable<number>): void {
const view = this._getCurrentXRView(viewIndex);
if (!("recommendedViewportScale" in view) || typeof view.requestViewportScale !== "function") {
throw new Error(`Dynamic viewport scaling is not supported for XR view ${viewIndex}.`);
}
view.requestViewportScale(scale);
}
private _getCurrentXRView(viewIndex: number): XRView {
if (!this.inXRSession || !this.session) {
throw new Error("Dynamic viewport scaling requires an active XR session.");
}
if (!this.inXRFrameLoop || !this.currentFrame) {
throw new Error("Dynamic viewport scaling must be used during an active XR frame.");
}
if (!this._referenceSpaceInitialized) {
throw new Error("Dynamic viewport scaling requires an initialized XR reference space.");
}
if (!Number.isInteger(viewIndex) || viewIndex < 0) {
throw new RangeError("The XR view index must be a non-negative integer.");
}
const pose = this.currentFrame.getViewerPose(this.referenceSpace);
if (!pose || viewIndex >= pose.views.length) {
throw new RangeError(`XR view ${viewIndex} is not available in the current viewer pose.`);
}
return pose.views[viewIndex];
}
/**
* Obtains the XR graphics binding for the current session, creating it lazily.
* This is the API-agnostic seam used by WebGL and WebGPU XR features to share a binding.
View on GitHub (pinned to 0592b347b8)
Solutions
- Move the view(index) / dynamic viewport scaling call inside the XR frame callback (e.g. the scene's onBeforeRenderObservable fired during XR rendering, or sessionManager.onXRFrameloop callback).
- Verify inXRFrameLoop === true and currentFrame !== null before calling view(); skip or queue the operation when a frame is not active.
- Use scene.executeWhenReady / XR frame observables instead of external timers (setTimeout / requestAnimationFrame) to schedule viewport work.
- If work must happen outside the frame, capture the viewport data inside the frame callback and apply it later.
Example fix
// before
setInterval(() => {
const v = xr.view(0); // throws: no active XR frame
scaleViewport(v);
}, 100);
// after
xr.onXRFrameObservable.add(() => {
const v = xr.view(0); // safe: inside XR frame
scaleViewport(v);
}); Defensive patterns
Strategy: validation
Validate before calling
if (!xr.inXRFrameLoop || !xr.currentFrame) {
return; // not inside an XR frame; defer viewport work
}
const v = xr.view(0); Type guard
function canAccessXRView(xr: WebXRSessionManager): boolean {
return xr.inXRFrameLoop === true && xr.currentFrame != null;
} Try / catch
try {
const v = xr.view(0);
applyDynamicViewportScale(v);
} catch (e) {
if (e instanceof Error && e.message.includes('active XR frame')) {
scheduleOnNextXRFrame(() => applyDynamicViewportScale());
} else {
throw e;
}
} Prevention
- Only touch XR views inside the XR frame loop callbacks/observables
- Never call view() from setTimeout, setInterval or standalone requestAnimationFrame
- Assert inXRFrameLoop in debug builds when XR viewport code runs
- Use observables tied to the XR frame (onXRFrameObservable) instead of timers
When it happens
Trigger: Calling xrSessionManager.view(index) (which delegates to _getCurrentXRView) outside of an XR frame loop callback — e.g. from an event handler, setTimeout, requestAnimationFrame outside the XR render loop, or before any session has started its render loop even though inXRSession may be true.
Common situations: Developers trying to read or resize XR viewports from UI code or a game-loop callback instead of inside sessionManager.onXRFrame; storing a reference to the session manager and calling view() after the frame loop was stopped (endXRRenderLoop) while the session is still alive.
Related errors
- Invalid Space Warp framebuffer
- Multiview is not supported
- XRCompositionLayer.${control} is not supported by this XR ru
- Anchors not enabled in this environment/browser
- String(error)
AI-assisted analysis of BabylonJS/Babylon.js@0592b347b8 (2026-08-30).
Data as JSON: /api/errors/6f4e27b2e3413157.
Report an issue: GitHub.