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?
0
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:
- Create a WebGL context. This provides the interface we need to render graphics.
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl2", { xrCompatible: true });
- Create an
XRWebGLLayer
using the new context. This allows WebXR to interface with our WebGL context.
const glLayer = new XRWebGLLayer(session, gl);
- Finally (this is the important part) configure the current session to use our new WebGL layer.
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
.