Here is a minimal example which reproduces a problem I have encountered in a more complex situation:
let session;
const dummyRenderLoop = (time, frame) => {
if (!session)
return;
console.log("Render callback");
// Continue loop
session.requestAnimationFrame(dummyRenderLoop);
}
window.addEventListener("load", async () => {
if (!await navigator.xr.isSessionSupported("immersive-vr"))
return;
// Obtain a WebXR session
session = await navigator.xr.requestSession("immersive-vr");
if (!session)
return;
// Session creation succeeded
console.log("Have an XR session");
session.onend = () => {
console.log("End session");
session = undefined;
};
// Initiate animation loop
session.requestAnimationFrame(dummyRenderLoop);
});
I've tried this in Chrome 129 with both a WebXR emulator and a real VR headset. In console, I get the output Have an XR session
, but never Render callback
. According to MDN, this usage pattern should be fine. So why is the animation frame callback never being called?
An XRSession
will not call the animation frame callback if it has not been correctly configured to render. To configure the session correctly, you need to:
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl2", { xrCompatible: true });
XRWebGLLayer
using the new context. This allows WebXR to interface with our WebGL context.const glLayer = new XRWebGLLayer(session, gl);
session.updateRenderState({ baseLayer: glLayer });
Now the animation frame callback will be run. You don't even have to actually render anything!
MDN has further information about XRSession.updateRenderState
.