diff --git a/CHANGELOG.md b/CHANGELOG.md index 933a34f5b..41783f270 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -198,6 +198,7 @@ By @wumpf in [#7144](https://github.com/gfx-rs/wgpu/pull/7144) #### General +- `wgpu::Instance::request_adapter()` now returns `Result` instead of `Option`; the error provides information about why no suitable adapter was returned. By @kpreid in [#7330](https://github.com/gfx-rs/wgpu/pull/7330). - Support BLAS compaction in wgpu-hal. By @Vecvec in [#7101](https://github.com/gfx-rs/wgpu/pull/7101). - Avoid using default features in many dependencies, etc. By Brody in [#7031](https://github.com/gfx-rs/wgpu/pull/7031) - Use `hashbrown` to simplify no-std support. By Brody in [#6938](https://github.com/gfx-rs/wgpu/pull/6938) & [#6925](https://github.com/gfx-rs/wgpu/pull/6925). diff --git a/examples/standalone/03_custom_backend/src/custom.rs b/examples/standalone/03_custom_backend/src/custom.rs index 556a1d29d..6a30b8f7d 100644 --- a/examples/standalone/03_custom_backend/src/custom.rs +++ b/examples/standalone/03_custom_backend/src/custom.rs @@ -43,7 +43,7 @@ impl InstanceInterface for CustomInstance { &self, _options: &wgpu::RequestAdapterOptions<'_, '_>, ) -> std::pin::Pin> { - Box::pin(std::future::ready(Some(DispatchAdapter::custom( + Box::pin(std::future::ready(Ok(DispatchAdapter::custom( CustomAdapter(self.0.clone()), )))) } diff --git a/tests/validation-tests/api/instance.rs b/tests/validation-tests/api/instance.rs new file mode 100644 index 000000000..ea3fc3567 --- /dev/null +++ b/tests/validation-tests/api/instance.rs @@ -0,0 +1,80 @@ +mod request_adapter_error { + fn id(backends: wgpu::Backends) -> wgpu::InstanceDescriptor { + wgpu::InstanceDescriptor { + backends, + flags: wgpu::InstanceFlags::default(), + backend_options: wgpu::BackendOptions::default(), + } + } + + fn adapter_error(desc: &wgpu::InstanceDescriptor) -> String { + let instance = wgpu::Instance::new(desc); + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + force_fallback_adapter: false, + compatible_surface: None, + })) + .unwrap_err() + .to_string() + } + + #[test] + fn no_backends_requested() { + assert_eq!( + adapter_error(&id(wgpu::Backends::empty())), + "No suitable graphics adapter found; \ + noop not requested, \ + vulkan not requested, \ + metal not requested, \ + dx12 not requested, \ + gl not requested, \ + webgpu not requested" + ); + } + + /// This test nominally tests the noop backend, but it also exercises the logic for other backends + /// that fail to return any adapter. + #[test] + fn noop_not_enabled() { + assert_eq!( + adapter_error(&id(wgpu::Backends::NOOP)), + "No suitable graphics adapter found; \ + noop not explicitly enabled, \ + vulkan not requested, \ + metal not requested, \ + dx12 not requested, \ + gl not requested, \ + webgpu not requested" + ); + } + + #[test] + fn no_compiled_support() { + // Whichever platform we are on, try asking for a backend that definitely will be + // cfged out regardless of feature flags. (Not that these tests run on wasm at all yet.) + + #[cfg(target_family = "wasm")] + assert_eq!( + adapter_error(&id(wgpu::Backends::METAL)), + "No suitable graphics adapter found; \ + noop not requested, \ + vulkan not requested, \ + metal support not compiled in, \ + dx12 not requested, \ + gl not requested, \ + webgpu not requested" + ); + + #[cfg(not(target_family = "wasm"))] + assert_eq!( + adapter_error(&id(wgpu::Backends::BROWSER_WEBGPU)), + "No suitable graphics adapter found; \ + noop not requested, \ + vulkan not requested, \ + metal not requested, \ + dx12 not requested, \ + gl not requested, \ + webgpu support not compiled in" + ); + } +} diff --git a/tests/validation-tests/api/mod.rs b/tests/validation-tests/api/mod.rs index 86e7a8d10..8c45d67bc 100644 --- a/tests/validation-tests/api/mod.rs +++ b/tests/validation-tests/api/mod.rs @@ -1,4 +1,5 @@ mod binding_arrays; mod buffer; mod buffer_slice; +mod instance; mod texture; diff --git a/tests/validation-tests/noop.rs b/tests/validation-tests/noop.rs index a8543eb79..39bc6e4a3 100644 --- a/tests/validation-tests/noop.rs +++ b/tests/validation-tests/noop.rs @@ -10,11 +10,8 @@ fn device_is_not_available_by_default() { ..Default::default() }); - assert_eq!( - pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())), - None, - "noop backend adapter present when it should not be" - ); + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) + .expect_err("noop backend adapter present when it should not be"); } #[test] diff --git a/wgpu-core/src/global.rs b/wgpu-core/src/global.rs index 9a4382b00..357cb2d42 100644 --- a/wgpu-core/src/global.rs +++ b/wgpu-core/src/global.rs @@ -1,5 +1,5 @@ -use alloc::{borrow::ToOwned as _, boxed::Box, sync::Arc}; -use core::{fmt, iter}; +use alloc::{borrow::ToOwned as _, sync::Arc}; +use core::fmt; use crate::{ hal_api::HalApi, @@ -47,13 +47,8 @@ impl Global { pub unsafe fn from_hal_instance(name: &str, hal_instance: A::Instance) -> Self { profiling::scope!("Global::new"); - let dyn_instance: Box = Box::new(hal_instance); Self { - instance: Instance { - name: name.to_owned(), - instance_per_backend: iter::once((A::VARIANT, dyn_instance)).collect(), - ..Default::default() - }, + instance: Instance::from_hal_instance::(name.to_owned(), hal_instance), surfaces: Registry::new(), hub: Hub::new(), } diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index e322b24ab..0bb59e239 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -3,10 +3,12 @@ use alloc::{ boxed::Box, string::String, sync::Arc, + vec, vec::Vec, }; use hashbrown::HashMap; +use thiserror::Error; use crate::{ api_log, api_log_debug, @@ -22,8 +24,6 @@ use crate::{ use wgt::{Backend, Backends, PowerPreference}; -use thiserror::Error; - pub type RequestAdapterOptions = wgt::RequestAdapterOptions; #[derive(Clone, Debug, Error)] @@ -61,64 +61,96 @@ fn downlevel_default_limits_less_than_default_limits() { #[derive(Default)] pub struct Instance { #[allow(dead_code)] - pub name: String, - /// List of instances per backend. + name: String, + + /// List of instances per `wgpu-hal` backend. /// /// The ordering in this list implies prioritization and needs to be preserved. - pub instance_per_backend: Vec<(Backend, Box)>, - pub flags: wgt::InstanceFlags, + instance_per_backend: Vec<(Backend, Box)>, + + /// The backends that were requested by the user. + requested_backends: Backends, + + /// The backends that we could have attempted to obtain from `wgpu-hal` — + /// those for which support is compiled in, currently. + /// + /// The union of this and `requested_backends` is the set of backends that would be used, + /// independent of whether accessing the drivers/hardware for them succeeds. + /// To obtain the set of backends actually in use by this instance, check + /// `instance_per_backend` instead. + supported_backends: Backends, + + flags: wgt::InstanceFlags, } impl Instance { pub fn new(name: &str, instance_desc: &wgt::InstanceDescriptor) -> Self { - fn init( - _: A, - instance_desc: &wgt::InstanceDescriptor, - instance_per_backend: &mut Vec<(Backend, Box)>, - ) { - if instance_desc.backends.contains(A::VARIANT.into()) { - let hal_desc = hal::InstanceDescriptor { - name: "wgpu", - flags: instance_desc.flags, - backend_options: instance_desc.backend_options.clone(), - }; - - use hal::Instance as _; - match unsafe { A::Instance::init(&hal_desc) } { - Ok(instance) => { - log::debug!("Instance::new: created {:?} backend", A::VARIANT); - instance_per_backend.push((A::VARIANT, Box::new(instance))); - } - Err(err) => { - log::debug!( - "Instance::new: failed to create {:?} backend: {:?}", - A::VARIANT, - err - ); - } - } - } else { - log::trace!("Instance::new: backend {:?} not requested", A::VARIANT); - } - } - - let mut instance_per_backend = Vec::new(); + let mut this = Self { + name: name.to_owned(), + instance_per_backend: Vec::new(), + requested_backends: instance_desc.backends, + supported_backends: Backends::empty(), + flags: instance_desc.flags, + }; #[cfg(vulkan)] - init(hal::api::Vulkan, instance_desc, &mut instance_per_backend); + this.try_add_hal(hal::api::Vulkan, instance_desc); #[cfg(metal)] - init(hal::api::Metal, instance_desc, &mut instance_per_backend); + this.try_add_hal(hal::api::Metal, instance_desc); #[cfg(dx12)] - init(hal::api::Dx12, instance_desc, &mut instance_per_backend); + this.try_add_hal(hal::api::Dx12, instance_desc); #[cfg(gles)] - init(hal::api::Gles, instance_desc, &mut instance_per_backend); + this.try_add_hal(hal::api::Gles, instance_desc); #[cfg(feature = "noop")] - init(hal::api::Noop, instance_desc, &mut instance_per_backend); + this.try_add_hal(hal::api::Noop, instance_desc); + this + } + + /// Helper for `Instance::new()`; attempts to add a single `wgpu-hal` backend to this instance. + fn try_add_hal(&mut self, _: A, instance_desc: &wgt::InstanceDescriptor) { + // Whether or not the backend was requested, and whether or not it succeeds, + // note that we *could* try it. + self.supported_backends |= A::VARIANT.into(); + + if !instance_desc.backends.contains(A::VARIANT.into()) { + log::trace!("Instance::new: backend {:?} not requested", A::VARIANT); + return; + } + + let hal_desc = hal::InstanceDescriptor { + name: "wgpu", + flags: self.flags, + backend_options: instance_desc.backend_options.clone(), + }; + + use hal::Instance as _; + match unsafe { A::Instance::init(&hal_desc) } { + Ok(instance) => { + log::debug!("Instance::new: created {:?} backend", A::VARIANT); + self.instance_per_backend + .push((A::VARIANT, Box::new(instance))); + } + Err(err) => { + log::debug!( + "Instance::new: failed to create {:?} backend: {:?}", + A::VARIANT, + err + ); + } + } + } + + pub(crate) fn from_hal_instance( + name: String, + hal_instance: ::Instance, + ) -> Self { Self { - name: name.to_owned(), - instance_per_backend, - flags: instance_desc.flags, + name, + instance_per_backend: vec![(A::VARIANT, Box::new(hal_instance))], + requested_backends: A::VARIANT.into(), + supported_backends: A::VARIANT.into(), + flags: wgt::InstanceFlags::default(), } } @@ -390,25 +422,40 @@ impl Instance { &self, desc: &wgt::RequestAdapterOptions<&Surface>, backends: Backends, - ) -> Result { + ) -> Result { profiling::scope!("Instance::request_adapter"); api_log!("Instance::request_adapter"); let mut adapters = Vec::new(); + let mut incompatible_surface_backends = Backends::empty(); + let mut no_fallback_backends = Backends::empty(); + let mut no_adapter_backends = Backends::empty(); - for (backend, instance) in self + for &(backend, ref instance) in self .instance_per_backend .iter() - .filter(|(backend, _)| backends.contains(Backends::from(*backend))) + .filter(|&&(backend, _)| backends.contains(Backends::from(backend))) { let compatible_hal_surface = desc .compatible_surface - .and_then(|surface| surface.raw(*backend)); + .and_then(|surface| surface.raw(backend)); + let mut backend_adapters = unsafe { instance.enumerate_adapters(compatible_hal_surface) }; + if backend_adapters.is_empty() { + no_adapter_backends |= Backends::from(backend); + // by continuing, we avoid setting the further error bits below + continue; + } + if desc.force_fallback_adapter { backend_adapters.retain(|exposed| exposed.info.device_type == wgt::DeviceType::Cpu); + if backend_adapters.is_empty() { + no_fallback_backends |= Backends::from(backend); + continue; + } } + if let Some(surface) = desc.compatible_surface { backend_adapters.retain(|exposed| { let capabilities = surface.get_capabilities_with_raw(exposed); @@ -418,11 +465,16 @@ impl Instance { exposed.info, err ); + incompatible_surface_backends |= Backends::from(backend); false } else { true } }); + if backend_adapters.is_empty() { + incompatible_surface_backends |= Backends::from(backend); + continue; + } } adapters.extend(backend_adapters); } @@ -482,9 +534,23 @@ impl Instance { let adapter = Adapter::new(adapter); Ok(adapter) } else { - Err(RequestAdapterError::NotFound) + Err(wgt::RequestAdapterError::NotFound { + supported_backends: self.supported_backends, + requested_backends: self.requested_backends, + active_backends: self.active_backends(), + no_fallback_backends, + no_adapter_backends, + incompatible_surface_backends, + }) } } + + fn active_backends(&self) -> Backends { + self.instance_per_backend + .iter() + .map(|&(backend, _)| Backends::from(backend)) + .collect() + } } pub struct Surface { @@ -783,14 +849,6 @@ pub enum RequestDeviceError { UnsupportedFeature(wgt::Features), } -#[derive(Clone, Debug, Error)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[non_exhaustive] -pub enum RequestAdapterError { - #[error("No suitable adapter found")] - NotFound, -} - #[derive(Clone, Debug, Error)] #[non_exhaustive] pub enum CreateSurfaceError { @@ -949,7 +1007,7 @@ impl Global { desc: &RequestAdapterOptions, backends: Backends, id_in: Option, - ) -> Result { + ) -> Result { let compatible_surface = desc.compatible_surface.map(|id| self.surfaces.get(id)); let desc = wgt::RequestAdapterOptions { power_preference: desc.power_preference, diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index 10efab680..208304991 100644 --- a/wgpu-types/src/instance.rs +++ b/wgpu-types/src/instance.rs @@ -56,7 +56,7 @@ impl InstanceDescriptor { bitflags::bitflags! { /// Instance debugging flags. /// - /// These are not part of the webgpu standard. + /// These are not part of the WebGPU standard. /// /// Defaults to enabling debugging-related flags if the build configuration has `debug_assertions`. #[repr(transparent)] diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index c6dc50c5a..0daf42f9e 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -16,7 +16,9 @@ extern crate alloc; use alloc::{string::String, vec, vec::Vec}; use core::{ + fmt, hash::{Hash, Hasher}, + mem, num::NonZeroU32, ops::Range, }; @@ -146,6 +148,16 @@ pub enum Backend { } impl Backend { + /// Array of all [`Backend`] values, corresponding to [`Backends::all()`]. + pub const ALL: [Backend; Backends::all().bits().count_ones() as usize] = [ + Self::Noop, + Self::Vulkan, + Self::Metal, + Self::Dx12, + Self::Gl, + Self::BrowserWebGpu, + ]; + /// Returns the string name of the backend. #[must_use] pub const fn to_str(self) -> &'static str { @@ -348,6 +360,88 @@ impl Default for RequestAdapterOptions { } } +/// Error when [`Instance::request_adapter()`] fails. +/// +/// This type is not part of the WebGPU standard, where `requestAdapter()` would simply return null. +/// +/// [`Instance::request_adapter()`]: ../wgpu/struct.Instance.html#method.request_adapter +#[derive(Clone, Debug, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[non_exhaustive] +pub enum RequestAdapterError { + /// No adapter available via the instance’s backends matched the request’s adapter criteria. + NotFound { + // These fields must be set by wgpu-core and wgpu, but are not intended to be stable API, + // only data for the production of the error message. + #[doc(hidden)] + active_backends: Backends, + #[doc(hidden)] + requested_backends: Backends, + #[doc(hidden)] + supported_backends: Backends, + #[doc(hidden)] + no_fallback_backends: Backends, + #[doc(hidden)] + no_adapter_backends: Backends, + #[doc(hidden)] + incompatible_surface_backends: Backends, + }, + + /// Attempted to obtain adapter specified by environment variable, but the environment variable + /// was not set. + EnvNotSet, +} + +impl core::error::Error for RequestAdapterError {} +impl fmt::Display for RequestAdapterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RequestAdapterError::NotFound { + active_backends, + requested_backends, + supported_backends, + no_fallback_backends, + no_adapter_backends, + incompatible_surface_backends, + } => { + write!(f, "No suitable graphics adapter found; ")?; + let mut first = true; + for backend in Backend::ALL { + let bit = Backends::from(backend); + let comma = if mem::take(&mut first) { "" } else { ", " }; + let explanation = if !requested_backends.contains(bit) { + // We prefer reporting this, because it makes the error most stable with + // respect to what is directly controllable by the caller, as opposed to + // compilation options or the run-time environment. + "not requested" + } else if !supported_backends.contains(bit) { + "support not compiled in" + } else if no_adapter_backends.contains(bit) { + "found no adapters" + } else if incompatible_surface_backends.contains(bit) { + "not compatible with provided surface" + } else if no_fallback_backends.contains(bit) { + "had no fallback adapters" + } else if !active_backends.contains(bit) { + // Backend requested but not active in this instance + if backend == Backend::Noop { + "not explicitly enabled" + } else { + "drivers/libraries could not be loaded" + } + } else { + // This path should be unreachable, but don't crash. + "[unknown reason]" + }; + write!(f, "{comma}{backend} {explanation}")?; + } + } + RequestAdapterError::EnvNotSet => f.write_str("WGPU_ADAPTER_NAME not set")?, + } + Ok(()) + } +} + /// Represents the sets of limits an adapter/device supports. /// /// We provide three different defaults. diff --git a/wgpu/src/api/instance.rs b/wgpu/src/api/instance.rs index b5ea8a99f..c27e72927 100644 --- a/wgpu/src/api/instance.rs +++ b/wgpu/src/api/instance.rs @@ -236,13 +236,14 @@ impl Instance { /// /// Some options are "soft", so treated as non-mandatory. Others are "hard". /// - /// If no adapters are found that suffice all the "hard" options, `None` is returned. + /// If no adapters are found that satisfy all the "hard" options, an error is returned. /// - /// A `compatible_surface` is required when targeting WebGL2. + /// When targeting WebGL2, a [`compatible_surface`](RequestAdapterOptions::compatible_surface) + /// must be specified; using `RequestAdapterOptions::default()` will not succeed. pub fn request_adapter( &self, options: &RequestAdapterOptions<'_, '_>, - ) -> impl Future> + WasmNotSend { + ) -> impl Future> + WasmNotSend { let future = self.inner.request_adapter(options); async move { future.await.map(|adapter| Adapter { inner: adapter }) } } diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 710cc38cc..d7a76384f 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -21,6 +21,7 @@ use core::{ pin::Pin, task::{self, Poll}, }; +use wgt::Backends; use js_sys::Promise; use wasm_bindgen::{prelude::*, JsCast}; @@ -50,6 +51,10 @@ pub struct ContextWebGpu { gpu: Option>, /// Unique identifier for this context. ident: crate::cmp::Identifier, + /// Backends requested in the [`crate::InstanceDescriptor`]. + /// Remembered for error reporting even though this itself is strictly + /// [`Backends::BROWSER_WEBGPU`]. + requested_backends: Backends, } impl fmt::Debug for ContextWebGpu { @@ -195,36 +200,6 @@ impl MakeSendFuture { #[cfg(send_sync)] unsafe impl Send for MakeSendFuture {} -/// Wraps a future that returns `Option` and adds the ability to immediately -/// return None. -pub(crate) struct OptionFuture(Option); - -impl>, T> Future for OptionFuture { - type Output = Option; - - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - // This is safe because we have no Drop implementation to violate the Pin requirements and - // do not provide any means of moving the inner future. - unsafe { - let this = self.get_unchecked_mut(); - match &mut this.0 { - Some(future) => Pin::new_unchecked(future).poll(cx), - None => task::Poll::Ready(None), - } - } - } -} - -impl OptionFuture { - fn some(future: F) -> Self { - Self(Some(future)) - } - - fn none() -> Self { - Self(None) - } -} - fn map_texture_format(texture_format: wgt::TextureFormat) -> webgpu_sys::GpuTextureFormat { use webgpu_sys::GpuTextureFormat as tf; use wgt::TextureFormat; @@ -900,16 +875,35 @@ fn map_js_sys_limits(limits: &wgt::Limits) -> js_sys::Object { type JsFutureResult = Result; -fn future_request_adapter(result: JsFutureResult) -> Option { +fn future_request_adapter( + result: JsFutureResult, + requested_backends: Backends, +) -> Result { let web_adapter: Option = result.and_then(wasm_bindgen::JsCast::dyn_into).ok(); - web_adapter.map(|adapter| { - WebAdapter { - inner: adapter, - ident: crate::cmp::Identifier::create(), - } - .into() - }) + web_adapter + .map(|adapter| { + WebAdapter { + inner: adapter, + ident: crate::cmp::Identifier::create(), + } + .into() + }) + .ok_or_else(|| request_adapter_null_error(requested_backends)) +} + +// Translate WebGPU’s null return into our error. +fn request_adapter_null_error(requested_backends: Backends) -> wgt::RequestAdapterError { + wgt::RequestAdapterError::NotFound { + active_backends: Backends::BROWSER_WEBGPU, + requested_backends, + // TODO: supported_backends should also include wgpu-core-based backends, + // if they were compiled in. + supported_backends: Backends::BROWSER_WEBGPU, + no_fallback_backends: Backends::empty(), + no_adapter_backends: Backends::BROWSER_WEBGPU, + incompatible_surface_backends: Backends::empty(), + } } fn future_request_device( @@ -1444,7 +1438,7 @@ crate::cmp::impl_eq_ord_hash_proxy!(WebQueueWriteBuffer => .ident); crate::cmp::impl_eq_ord_hash_proxy!(WebBufferMappedRange => .ident); impl dispatch::InstanceInterface for ContextWebGpu { - fn new(_desc: &crate::InstanceDescriptor) -> Self + fn new(desc: &crate::InstanceDescriptor) -> Self where Self: Sized, { @@ -1456,6 +1450,7 @@ impl dispatch::InstanceInterface for ContextWebGpu { ContextWebGpu { gpu, + requested_backends: desc.backends, ident: crate::cmp::Identifier::create(), } } @@ -1512,10 +1507,25 @@ impl dispatch::InstanceInterface for ContextWebGpu { &self, options: &crate::RequestAdapterOptions<'_, '_>, ) -> Pin> { + let requested_backends = self.requested_backends; + //TODO: support this check, return `None` if the flag is not set. // It's not trivial, since we need the Future logic to have this check, // and currently the Future here has no room for extra parameter `backends`. - //assert!(backends.contains(wgt::Backends::BROWSER_WEBGPU)); + if !(requested_backends.contains(wgt::Backends::BROWSER_WEBGPU)) { + return Box::pin(core::future::ready(Err( + wgt::RequestAdapterError::NotFound { + active_backends: Backends::BROWSER_WEBGPU, + requested_backends, + // TODO: supported_backends should also include wgpu-core-based backends, + // if they were compiled in. + supported_backends: Backends::BROWSER_WEBGPU, + no_fallback_backends: Backends::default(), + no_adapter_backends: Backends::default(), + incompatible_surface_backends: Backends::default(), + }, + ))); + } let mapped_options = webgpu_sys::GpuRequestAdapterOptions::new(); let mapped_power_preference = match options.power_preference { wgt::PowerPreference::None => None, @@ -1527,18 +1537,20 @@ impl dispatch::InstanceInterface for ContextWebGpu { if let Some(mapped_pref) = mapped_power_preference { mapped_options.set_power_preference(mapped_pref); } - let future = if let Some(gpu) = &self.gpu { + + if let Some(gpu) = &self.gpu { let adapter_promise = gpu.request_adapter_with_options(&mapped_options); - OptionFuture::some(MakeSendFuture::new( + Box::pin(MakeSendFuture::new( wasm_bindgen_futures::JsFuture::from(adapter_promise), - future_request_adapter, + move |result| future_request_adapter(result, requested_backends), )) } else { // Gpu is undefined; WebGPU is not supported in this browser. - OptionFuture::none() - }; - - Box::pin(future) + // Treat this exactly like requestAdapter() returned null. + Box::pin(core::future::ready(Err(request_adapter_null_error( + requested_backends, + )))) + } } fn poll_all_devices(&self, _force_wait: bool) -> bool { diff --git a/wgpu/src/backend/wgpu_core.rs b/wgpu/src/backend/wgpu_core.rs index 53717bea2..e81d02c24 100644 --- a/wgpu/src/backend/wgpu_core.rs +++ b/wgpu/src/backend/wgpu_core.rs @@ -850,7 +850,7 @@ impl dispatch::InstanceInterface for ContextWgpuCore { let generic: dispatch::DispatchAdapter = core.into(); generic }); - Box::pin(ready(adapter.ok())) + Box::pin(ready(adapter)) } fn poll_all_devices(&self, force_wait: bool) -> bool { diff --git a/wgpu/src/dispatch.rs b/wgpu/src/dispatch.rs index 6c47eb48c..07924d914 100644 --- a/wgpu/src/dispatch.rs +++ b/wgpu/src/dispatch.rs @@ -35,7 +35,7 @@ macro_rules! trait_alias { } // Various return futures in the API. -trait_alias!(RequestAdapterFuture: Future> + WasmNotSend + 'static); +trait_alias!(RequestAdapterFuture: Future> + WasmNotSend + 'static); trait_alias!(RequestDeviceFuture: Future> + WasmNotSend + 'static); trait_alias!(PopErrorScopeFuture: Future> + WasmNotSend + 'static); trait_alias!(ShaderCompilationInfoFuture: Future + WasmNotSend + 'static); diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 7dfa47f22..d1ec8a17f 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -80,13 +80,13 @@ pub use wgt::{ InstanceFlags, InternalCounters, Limits, MemoryHints, MultisampleState, NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PollError, PollStatus, PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, - PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, SamplerBindingType, - SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, ShaderStages, - StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, SurfaceCapabilities, - SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, TextureFormat, - TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, TextureTransition, - TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, VertexFormat, - VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, + PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, RequestAdapterError, + SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel, ShaderRuntimeChecks, + ShaderStages, StencilFaceState, StencilOperation, StencilState, StorageTextureAccess, + SurfaceCapabilities, SurfaceStatus, TexelCopyBufferLayout, TextureAspect, TextureDimension, + TextureFormat, TextureFormatFeatureFlags, TextureFormatFeatures, TextureSampleType, + TextureTransition, TextureUsages, TextureUses, TextureViewDimension, Trace, VertexAttribute, + VertexFormat, VertexStepMode, WasmNotSend, WasmNotSendSync, WasmNotSync, COPY_BUFFER_ALIGNMENT, COPY_BYTES_PER_ROW_ALIGNMENT, MAP_ALIGNMENT, PUSH_CONSTANT_ALIGNMENT, QUERY_RESOLVE_BUFFER_ALIGNMENT, QUERY_SET_MAX_QUERIES, QUERY_SIZE, VERTEX_STRIDE_ALIGNMENT, }; diff --git a/wgpu/src/util/init.rs b/wgpu/src/util/init.rs index 1eb17870e..be5c41986 100644 --- a/wgpu/src/util/init.rs +++ b/wgpu/src/util/init.rs @@ -8,11 +8,11 @@ use crate::Backends; pub fn initialize_adapter_from_env( instance: &Instance, compatible_surface: Option<&Surface<'_>>, -) -> Option { +) -> Result { let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME") .as_deref() .map(str::to_lowercase) - .ok()?; + .map_err(|_| wgt::RequestAdapterError::EnvNotSet)?; let adapters = instance.enumerate_adapters(crate::Backends::all()); @@ -32,7 +32,7 @@ pub fn initialize_adapter_from_env( } } - Some(chosen_adapter.expect("WGPU_ADAPTER_NAME set but no matching adapter found!")) + Ok(chosen_adapter.expect("WGPU_ADAPTER_NAME set but no matching adapter found!")) } /// Initialize the adapter obeying the `WGPU_ADAPTER_NAME` environment variable. @@ -40,18 +40,18 @@ pub fn initialize_adapter_from_env( pub fn initialize_adapter_from_env( _instance: &Instance, _compatible_surface: Option<&Surface<'_>>, -) -> Option { - None +) -> Result { + Err(wgt::RequestAdapterError::EnvNotSet) } /// Initialize the adapter obeying the `WGPU_ADAPTER_NAME` environment variable and if it doesn't exist fall back on a default adapter. pub async fn initialize_adapter_from_env_or_default( instance: &Instance, compatible_surface: Option<&Surface<'_>>, -) -> Option { +) -> Result { match initialize_adapter_from_env(instance, compatible_surface) { - Some(a) => Some(a), - None => { + Ok(a) => Ok(a), + Err(_) => { instance .request_adapter(&RequestAdapterOptions { power_preference: crate::PowerPreference::from_env().unwrap_or_default(),