@@ -17,6 +17,7 @@ import { XrAnchors } from './xr-anchors.js';
1717import { XrMeshDetection } from './xr-mesh-detection.js' ;
1818import { XrViews } from './xr-views.js' ;
1919import { XrBridge } from '../../platform/graphics/xr-bridge.js' ;
20+ import { DEVICETYPE_WEBGPU } from '../../platform/graphics/constants.js' ;
2021
2122/**
2223 * @import { AppBase } from '../app-base.js'
@@ -324,6 +325,55 @@ class XrManager extends EventHandler {
324325 }
325326 }
326327
328+ /**
329+ * Tests whether an immersive WebXR session of the given type can run on the specified graphics
330+ * backend. Unlike {@link XrManager#isAvailable}, this is a static method that can be called
331+ * before a graphics device (or the {@link AppBase}) is created, which makes it useful for
332+ * deciding which device type to create for XR - for example WebGPU vs WebGL2.
333+ *
334+ * This is a best-effort preflight check. The only authoritative test remains a successful
335+ * {@link XrManager#start}, so a fallback path should always be kept.
336+ *
337+ * @param {string } deviceType - The graphics device type the session would run on. Can be
338+ * {@link DEVICETYPE_WEBGPU} or {@link DEVICETYPE_WEBGL2}.
339+ * @param {string } type - The session type. Can be:
340+ *
341+ * - {@link XRTYPE_VR}: Immersive VR session.
342+ * - {@link XRTYPE_AR}: Immersive AR session.
343+ *
344+ * @returns {Promise<boolean> } Promise that resolves to true if a session of the given type is
345+ * reported supported on the given backend, false otherwise.
346+ * @example
347+ * const supported = await pc.XrManager.isDeviceSupported(pc.DEVICETYPE_WEBGPU, pc.XRTYPE_VR);
348+ * if (supported) {
349+ * // a WebGPU device can be created and used to offer VR
350+ * }
351+ */
352+ static async isDeviceSupported ( deviceType , type ) {
353+ if ( ! platform . browser || ! navigator . xr || ! XrManager . _backendSupportsXr ( deviceType ) ) {
354+ return false ;
355+ }
356+
357+ try {
358+ return await navigator . xr . isSessionSupported ( type ) ;
359+ } catch {
360+ return false ;
361+ }
362+ }
363+
364+ /**
365+ * Returns whether the given graphics backend meets the WebXR binding requirement. A WebGPU
366+ * backend can only host an XR session when the browser exposes `XRGPUBinding`; a WebGL backend
367+ * uses the classic `XRWebGLLayer` and needs no additional binding.
368+ *
369+ * @param {string } [deviceType] - The graphics device type, see DEVICETYPE_*.
370+ * @returns {boolean } True if the backend can host a WebXR session.
371+ * @private
372+ */
373+ static _backendSupportsXr ( deviceType ) {
374+ return deviceType !== DEVICETYPE_WEBGPU || typeof globalThis . XRGPUBinding !== 'undefined' ;
375+ }
376+
327377 /**
328378 * Destroys the XrManager instance.
329379 *
@@ -678,6 +728,13 @@ class XrManager extends EventHandler {
678728 */
679729 _sessionSupportCheck ( type ) {
680730 navigator . xr . isSessionSupported ( type ) . then ( ( available ) => {
731+ // A session reported as supported by the browser can still be unusable on the current
732+ // graphics backend (a WebGPU device requires `XRGPUBinding`). Reflect that requirement
733+ // so availability matches what can actually be started on this device.
734+ if ( available && ! XrManager . _backendSupportsXr ( this . app ?. graphicsDevice ?. deviceType ) ) {
735+ available = false ;
736+ }
737+
681738 if ( this . _available [ type ] === available ) {
682739 return ;
683740 }
0 commit comments