{"record":{"id":"6f4e27b2e3413157","repo":"BabylonJS/Babylon.js","slug":"dynamic-viewport-scaling-must-be-used-during-an-ac","errorCode":null,"errorMessage":"Dynamic viewport scaling must be used during an active XR frame.","messagePattern":"Dynamic viewport scaling must be used during an active XR frame\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/dev/core/src/XR/webXRSessionManager.ts","lineNumber":283,"sourceCode":"     * This method must be called during an active XR frame.\r\n     * @param viewIndex the index of the view in the current viewer pose\r\n     * @param scale the viewport scale requested from the runtime\r\n     * @see https://playground.babylonjs.com/#BAGIIM#0\r\n     */\r\n    public requestViewportScale(viewIndex: number, scale: Nullable<number>): void {\r\n        const view = this._getCurrentXRView(viewIndex);\r\n        if (!(\"recommendedViewportScale\" in view) || typeof view.requestViewportScale !== \"function\") {\r\n            throw new Error(`Dynamic viewport scaling is not supported for XR view ${viewIndex}.`);\r\n        }\r\n        view.requestViewportScale(scale);\r\n    }\r\n\r\n    private _getCurrentXRView(viewIndex: number): XRView {\r\n        if (!this.inXRSession || !this.session) {\r\n            throw new Error(\"Dynamic viewport scaling requires an active XR session.\");\r\n        }\r\n        if (!this.inXRFrameLoop || !this.currentFrame) {\r\n            throw new Error(\"Dynamic viewport scaling must be used during an active XR frame.\");\r\n        }\r\n        if (!this._referenceSpaceInitialized) {\r\n            throw new Error(\"Dynamic viewport scaling requires an initialized XR reference space.\");\r\n        }\r\n        if (!Number.isInteger(viewIndex) || viewIndex < 0) {\r\n            throw new RangeError(\"The XR view index must be a non-negative integer.\");\r\n        }\r\n\r\n        const pose = this.currentFrame.getViewerPose(this.referenceSpace);\r\n        if (!pose || viewIndex >= pose.views.length) {\r\n            throw new RangeError(`XR view ${viewIndex} is not available in the current viewer pose.`);\r\n        }\r\n        return pose.views[viewIndex];\r\n    }\r\n\r\n    /**\r\n     * Obtains the XR graphics binding for the current session, creating it lazily.\r\n     * This is the API-agnostic seam used by WebGL and WebGPU XR features to share a binding.\r","sourceCodeStart":265,"sourceCodeEnd":301,"githubUrl":"https://github.com/BabylonJS/Babylon.js/blob/0592b347b8a4ee0236089ea86a749cacfdb266d8/packages/dev/core/src/XR/webXRSessionManager.ts#L265-L301","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsetInterval(() => {\n  const v = xr.view(0); // throws: no active XR frame\n  scaleViewport(v);\n}, 100);\n// after\nxr.onXRFrameObservable.add(() => {\n  const v = xr.view(0); // safe: inside XR frame\n  scaleViewport(v);\n});","handlingStrategy":"validation","validationCode":"if (!xr.inXRFrameLoop || !xr.currentFrame) {\n  return; // not inside an XR frame; defer viewport work\n}\nconst v = xr.view(0);","typeGuard":"function canAccessXRView(xr: WebXRSessionManager): boolean {\n  return xr.inXRFrameLoop === true && xr.currentFrame != null;\n}","tryCatchPattern":"try {\n  const v = xr.view(0);\n  applyDynamicViewportScale(v);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('active XR frame')) {\n    scheduleOnNextXRFrame(() => applyDynamicViewportScale());\n  } else {\n    throw e;\n  }\n}","preventionTips":["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"],"tags":["webxr","xr-frame-loop","timing"],"backgroundTag":"xr-call-outside-frame","analyzedSha":"0592b347b8a4ee0236089ea86a749cacfdb266d8","analyzedAt":"2026-08-30T15:11:20.442Z","schemaVersion":2},"datasetVersion":"2026-08-30T18:17:15.746Z"}