diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fdb08584..7e72f1c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -89,6 +89,7 @@ By @bradwerth [#6216](https://github.com/gfx-rs/wgpu/pull/6216). #### General - Add `VideoFrame` to `ExternalImageSource` enum. By @jprochazk in [#6170](https://github.com/gfx-rs/wgpu/pull/6170) +- Add `wgpu::util::new_instance_with_webgpu_detection` & `wgpu::util::is_browser_webgpu_supported` to make it easier to support WebGPU & WebGL in the same binary. By @wumpf in [#6371](https://github.com/gfx-rs/wgpu/pull/6371) #### Vulkan diff --git a/wgpu/src/api/instance.rs b/wgpu/src/api/instance.rs index af6775b86..b21c9f70e 100644 --- a/wgpu/src/api/instance.rs +++ b/wgpu/src/api/instance.rs @@ -93,10 +93,15 @@ impl Instance { /// during instantiation, and which [DX12 shader compiler][Dx12Compiler] wgpu will use. /// /// [`Backends::BROWSER_WEBGPU`] takes a special role: - /// If it is set and WebGPU support is detected, this instance will *only* be able to create - /// WebGPU adapters. If you instead want to force use of WebGL, either - /// disable the `webgpu` compile-time feature or don't add the [`Backends::BROWSER_WEBGPU`] - /// flag to the the `instance_desc`'s `backends` field. + /// If it is set and a [`navigator.gpu`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/gpu) + /// object is present, this instance will *only* be able to create WebGPU adapters. + /// + /// ⚠️ On some browsers this check is insufficient to determine whether WebGPU is supported, + /// as the browser may define the `navigator.gpu` object, but be unable to create any WebGPU adapters. + /// For targeting _both_ WebGPU & WebGL is recommended to use [`crate::util::new_instance_with_webgpu_detection`]. + /// + /// If you instead want to force use of WebGL, either disable the `webgpu` compile-time feature + /// or don't add the [`Backends::BROWSER_WEBGPU`] flag to the the `instance_desc`'s `backends` field. /// If it is set and WebGPU support is *not* detected, the instance will use wgpu-core /// to create adapters. Meaning that if the `webgl` feature is enabled, it is able to create /// a WebGL adapter. diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 16172b614..e0cf006e6 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -1087,8 +1087,12 @@ pub struct BrowserGpuPropertyInaccessible; /// Returns the browser's gpu object or `Err(BrowserGpuPropertyInaccessible)` if /// the current context is neither the main thread nor a dedicated worker. /// -/// If WebGPU is not supported, the Gpu property is `undefined`, and so this -/// function will return `Ok(None)`. +/// If WebGPU is not supported, the Gpu property may (!) be `undefined`, +/// and so this function will return `Ok(None)`. +/// Note that this check is insufficient to determine whether WebGPU is +/// supported, as the browser may define the Gpu property, but be unable to +/// create any WebGPU adapters. +/// To detect whether WebGPU is supported, use the [`crate::utils::is_browser_webgpu_supported`] function. /// /// See: /// * diff --git a/wgpu/src/util/init.rs b/wgpu/src/util/init.rs index 55154242e..573fe3bd8 100644 --- a/wgpu/src/util/init.rs +++ b/wgpu/src/util/init.rs @@ -135,3 +135,61 @@ pub fn gles_minor_version_from_env() -> Option { }, ) } + +/// Determines whether the [`Backends::BROWSER_WEBGPU`] backend is supported. +/// +/// The result can only be true if this is called from the main thread or a dedicated worker. +/// For convenience, this is also supported on non-wasm targets, always returning false there. +pub async fn is_browser_webgpu_supported() -> bool { + #[cfg(webgpu)] + { + // In theory it should be enough to check for the presence of the `gpu` property... + let gpu = crate::backend::get_browser_gpu_property(); + let Ok(Some(gpu)) = gpu else { + return false; + }; + + // ...but in practice, we also have to try to create an adapter, since as of writing + // Chrome on Linux has the `gpu` property but doesn't support WebGPU. + let adapter_promise = gpu.request_adapter(); + wasm_bindgen_futures::JsFuture::from(adapter_promise) + .await + .map_or(false, |adapter| { + !adapter.is_undefined() && !adapter.is_null() + }) + } + #[cfg(not(webgpu))] + { + false + } +} + +/// Create an new instance of wgpu, but disabling [`Backends::BROWSER_WEBGPU`] if no WebGPU support was detected. +/// +/// If the instance descriptor enables [`Backends::BROWSER_WEBGPU`], +/// this checks via [`is_browser_webgpu_supported`] for WebGPU support before forwarding +/// the descriptor with or without [`Backends::BROWSER_WEBGPU`] respecitively to [`Instance::new`]. +/// +/// You should prefer this method over [`Instance::new`] if you want to target WebGPU and automatically +/// fall back to WebGL if WebGPU is not available. +/// This is because WebGPU support has to be decided upon instance creation and [`Instance::new`] +/// (being a `sync` function) can't establish WebGPU support (details see [`is_browser_webgpu_supported`]). +/// +/// # Panics +/// +/// If no backend feature for the active target platform is enabled, +/// this method will panic, see [`Instance::enabled_backend_features()`]. +#[allow(unused_mut)] +pub async fn new_instance_with_webgpu_detection( + mut instance_desc: wgt::InstanceDescriptor, +) -> crate::Instance { + if instance_desc + .backends + .contains(wgt::Backends::BROWSER_WEBGPU) + && !is_browser_webgpu_supported().await + { + instance_desc.backends.remove(wgt::Backends::BROWSER_WEBGPU); + } + + crate::Instance::new(instance_desc) +}