Skip to content

Commit 9ed0f67

Browse files
mvaligurskyMartin Valigursky
andauthored
feat(xr): backend-aware WebXR support detection (isDeviceSupported) (#8948)
Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 4ebb89c commit 9ed0f67

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

src/framework/xr/xr-manager.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { XrAnchors } from './xr-anchors.js';
1717
import { XrMeshDetection } from './xr-mesh-detection.js';
1818
import { XrViews } from './xr-views.js';
1919
import { 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

Comments
 (0)