diff --git a/CHANGELOG.md b/CHANGELOG.md index c9560fc4e..ffca24947 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -73,6 +73,21 @@ Difference for SPIR-V passthrough: ``` This allows using precompiled shaders without manually checking which backend's code to pass, for example if you have shaders precompiled for both DXIL and SPIR-V. +#### `EXPERIMENTAL_*` features now require unsafe code to enable + +We want to be able to expose potentially experimental features to our users before we have ensured that they are fully sound to use. +As such, we now require any feature that is prefixed with `EXPERIMENTAL` to have a special unsafe token enabled in the device descriptor +acknowledging that the features may still have bugs in them and to report any they find. + +```rust +adapter.request_device(&wgpu::DeviceDescriptor { + features: wgpu::Features::EXPERIMENTAL_MESH_SHADER, + experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() } + .. +}) +``` + +By @cwfitzgerald in [#8163](https://github.com/gfx-rs/wgpu/pull/8163). #### Multi-draw indirect is now unconditionally supported when indirect draws are supported diff --git a/benches/benches/wgpu-benchmark/main.rs b/benches/benches/wgpu-benchmark/main.rs index c087f1f84..091ba8d0e 100644 --- a/benches/benches/wgpu-benchmark/main.rs +++ b/benches/benches/wgpu-benchmark/main.rs @@ -47,6 +47,7 @@ impl DeviceState { required_features: adapter.features(), required_limits: adapter.limits(), memory_hints: wgpu::MemoryHints::Performance, + experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, label: Some("Compute/RenderPass Device"), trace: wgpu::Trace::Off, })) diff --git a/deno_webgpu/adapter.rs b/deno_webgpu/adapter.rs index a97f7ddb3..fe0b6de4f 100644 --- a/deno_webgpu/adapter.rs +++ b/deno_webgpu/adapter.rs @@ -146,6 +146,7 @@ impl GPUAdapter { descriptor.required_features, ), required_limits, + experimental_features: wgpu_types::ExperimentalFeatures::disabled(), memory_hints: Default::default(), trace, }; diff --git a/examples/features/src/framework.rs b/examples/features/src/framework.rs index 7a3017848..994eba874 100644 --- a/examples/features/src/framework.rs +++ b/examples/features/src/framework.rs @@ -287,6 +287,7 @@ impl ExampleContext { required_features: (E::optional_features() & adapter.features()) | E::required_features(), required_limits: needed_limits, + experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, memory_hints: wgpu::MemoryHints::MemoryUsage, trace: match std::env::var_os("WGPU_TRACE") { Some(path) => wgpu::Trace::Directory(path.into()), diff --git a/examples/features/src/hello_synchronization/mod.rs b/examples/features/src/hello_synchronization/mod.rs index da2f103f2..e70610047 100644 --- a/examples/features/src/hello_synchronization/mod.rs +++ b/examples/features/src/hello_synchronization/mod.rs @@ -16,6 +16,7 @@ async fn run() { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::Performance, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/hello_triangle/mod.rs b/examples/features/src/hello_triangle/mod.rs index 89ef9864f..eca99f6bb 100644 --- a/examples/features/src/hello_triangle/mod.rs +++ b/examples/features/src/hello_triangle/mod.rs @@ -31,6 +31,7 @@ async fn run(event_loop: EventLoop<()>, window: Window) { // Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain. required_limits: wgpu::Limits::downlevel_webgl2_defaults() .using_resolution(adapter.limits()), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/hello_windows/mod.rs b/examples/features/src/hello_windows/mod.rs index 9885d2402..fbd69f0bf 100644 --- a/examples/features/src/hello_windows/mod.rs +++ b/examples/features/src/hello_windows/mod.rs @@ -74,6 +74,7 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc, wgpu::Color label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/hello_workgroups/mod.rs b/examples/features/src/hello_workgroups/mod.rs index 5a8e7ffab..ee2c6fe59 100644 --- a/examples/features/src/hello_workgroups/mod.rs +++ b/examples/features/src/hello_workgroups/mod.rs @@ -31,6 +31,7 @@ async fn run() { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/render_to_texture/mod.rs b/examples/features/src/render_to_texture/mod.rs index 3460864b0..7981f6655 100644 --- a/examples/features/src/render_to_texture/mod.rs +++ b/examples/features/src/render_to_texture/mod.rs @@ -20,6 +20,7 @@ async fn run(_path: Option) { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/repeated_compute/mod.rs b/examples/features/src/repeated_compute/mod.rs index f6104d948..77e5f9c51 100644 --- a/examples/features/src/repeated_compute/mod.rs +++ b/examples/features/src/repeated_compute/mod.rs @@ -164,6 +164,7 @@ impl WgpuContext { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::Performance, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/storage_texture/mod.rs b/examples/features/src/storage_texture/mod.rs index 754e8406b..07c3d48f7 100644 --- a/examples/features/src/storage_texture/mod.rs +++ b/examples/features/src/storage_texture/mod.rs @@ -34,6 +34,7 @@ async fn run(_path: Option) { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/timestamp_queries/mod.rs b/examples/features/src/timestamp_queries/mod.rs index 23746a6ee..2531cc4e6 100644 --- a/examples/features/src/timestamp_queries/mod.rs +++ b/examples/features/src/timestamp_queries/mod.rs @@ -210,6 +210,7 @@ async fn run() { label: None, required_features: features, required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/features/src/uniform_values/mod.rs b/examples/features/src/uniform_values/mod.rs index 7215b7238..6a42f2635 100644 --- a/examples/features/src/uniform_values/mod.rs +++ b/examples/features/src/uniform_values/mod.rs @@ -114,6 +114,7 @@ impl WgpuContext { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/examples/standalone/01_hello_compute/src/main.rs b/examples/standalone/01_hello_compute/src/main.rs index 4a1f03465..e9035c034 100644 --- a/examples/standalone/01_hello_compute/src/main.rs +++ b/examples/standalone/01_hello_compute/src/main.rs @@ -70,6 +70,7 @@ fn main() { label: None, required_features: wgpu::Features::empty(), required_limits: wgpu::Limits::downlevel_defaults(), + experimental_features: wgpu::ExperimentalFeatures::disabled(), memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, })) diff --git a/player/tests/player/main.rs b/player/tests/player/main.rs index 1e16c3f03..e50e4368d 100644 --- a/player/tests/player/main.rs +++ b/player/tests/player/main.rs @@ -95,6 +95,7 @@ impl Test<'_> { label: None, required_features: self.features, required_limits: wgt::Limits::default(), + experimental_features: unsafe { wgt::ExperimentalFeatures::enabled() }, memory_hints: wgt::MemoryHints::default(), trace: wgt::Trace::Off, }, diff --git a/tests/src/init.rs b/tests/src/init.rs index 0a33f969f..38073fa1b 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -163,6 +163,7 @@ pub async fn initialize_device( label: None, required_features: features, required_limits: limits, + experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, memory_hints: wgpu::MemoryHints::MemoryUsage, trace: wgpu::Trace::Off, }) diff --git a/tests/tests/wgpu-gpu/device.rs b/tests/tests/wgpu-gpu/device.rs index 002ce316a..5accb3d33 100644 --- a/tests/tests/wgpu-gpu/device.rs +++ b/tests/tests/wgpu-gpu/device.rs @@ -156,7 +156,7 @@ async fn request_device_error_message() { let expected = "TypeError"; } else { // This message appears whenever wgpu-core is used as the implementation. - let expected = "Unsupported features were requested: Features {"; + let expected = "Unsupported features were requested:"; } } assert!(device_error.contains(expected), "{device_error}"); diff --git a/tests/tests/wgpu-validation/api/experimental.rs b/tests/tests/wgpu-validation/api/experimental.rs new file mode 100644 index 000000000..23cdc8ea4 --- /dev/null +++ b/tests/tests/wgpu-validation/api/experimental.rs @@ -0,0 +1,70 @@ +fn noop_adapter() -> wgpu::Adapter { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::NOOP, + backend_options: wgpu::BackendOptions { + noop: wgpu::NoopBackendOptions { enable: true }, + ..Default::default() + }, + ..Default::default() + }); + + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) + .expect("noop backend adapter absent when it should be") +} + +#[test] +fn request_no_experimental_features() { + let adapter = noop_adapter(); + + let dq = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + // Not experimental + required_features: wgpu::Features::FLOAT32_FILTERABLE, + experimental_features: wgpu::ExperimentalFeatures::disabled(), + ..Default::default() + })); + + assert!(dq.is_ok()); +} + +#[test] +fn request_experimental_features() { + let adapter = noop_adapter(); + + let dq = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + // Experimental + required_features: wgpu::Features::EXPERIMENTAL_MESH_SHADER, + experimental_features: unsafe { wgpu::ExperimentalFeatures::enabled() }, + ..Default::default() + })); + + assert!(dq.is_ok()); +} + +#[test] +fn request_experimental_features_when_not_enabled() { + let adapter = noop_adapter(); + + let dq = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + // Experimental + required_features: wgpu::Features::EXPERIMENTAL_MESH_SHADER, + experimental_features: wgpu::ExperimentalFeatures::disabled(), + ..Default::default() + })); + + assert!(dq.is_err()); +} + +#[test] +fn request_multiple_experimental_features_when_not_enabled() { + let adapter = noop_adapter(); + + let dq = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor { + // Experimental + required_features: wgpu::Features::EXPERIMENTAL_MESH_SHADER + | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS, + experimental_features: wgpu::ExperimentalFeatures::disabled(), + ..Default::default() + })); + + assert!(dq.is_err()); +} diff --git a/tests/tests/wgpu-validation/api/mod.rs b/tests/tests/wgpu-validation/api/mod.rs index 383e86d30..1266113fa 100644 --- a/tests/tests/wgpu-validation/api/mod.rs +++ b/tests/tests/wgpu-validation/api/mod.rs @@ -2,6 +2,7 @@ mod binding_arrays; mod buffer; mod buffer_slice; mod device; +mod experimental; mod external_texture; mod instance; mod texture; diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 99f967f03..434462231 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -797,6 +797,18 @@ impl Adapter { )); } + // Check if experimental features are permitted to be enabled. + if desc + .required_features + .intersects(wgt::Features::all_experimental_mask()) + && !desc.experimental_features.is_enabled() + { + return Err(RequestDeviceError::ExperimentalFeaturesNotEnabled( + desc.required_features + .intersection(wgt::Features::all_experimental_mask()), + )); + } + let caps = &self.raw.capabilities; if Backends::PRIMARY.contains(Backends::from(self.backend())) && !caps.downlevel.is_webgpu_compliant() @@ -857,8 +869,12 @@ pub enum RequestDeviceError { LimitsExceeded(#[from] FailedLimit), #[error("Failed to initialize Timestamp Normalizer")] TimestampNormalizerInitFailed(#[from] TimestampNormalizerInitError), - #[error("Unsupported features were requested: {0:?}")] + #[error("Unsupported features were requested: {0}")] UnsupportedFeature(wgt::Features), + #[error( + "Some experimental features, {0}, were requested, but experimental features are not enabled" + )] + ExperimentalFeaturesNotEnabled(wgt::Features), } #[derive(Clone, Debug, Error)] diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index d3db2264b..94a9a91cb 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -1498,6 +1498,19 @@ impl Features { ])) } + /// Mask of all features which are experimental. + #[must_use] + pub const fn all_experimental_mask() -> Self { + Self::from_bits_truncate(FeatureBits([ + FeaturesWGPU::EXPERIMENTAL_MESH_SHADER.bits() + | FeaturesWGPU::EXPERIMENTAL_MESH_SHADER_MULTIVIEW.bits() + | FeaturesWGPU::EXPERIMENTAL_RAY_QUERY.bits() + | FeaturesWGPU::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN.bits() + | FeaturesWGPU::EXPERIMENTAL_PASSTHROUGH_SHADERS.bits(), + FeaturesWebGPU::empty().bits(), + ])) + } + /// Vertex formats allowed for creating and building BLASes #[must_use] pub fn allowed_vertex_formats_for_blas(&self) -> Vec { @@ -1624,4 +1637,13 @@ mod tests { ) ); } + + #[test] + fn experimental_features_part_of_experimental_mask() { + for (name, feature) in Features::all().iter_names() { + let prefixed_with_experimental = name.starts_with("EXPERIMENTAL_"); + let in_experimental_mask = Features::all_experimental_mask().contains(feature); + assert_eq!(in_experimental_mask, prefixed_with_experimental); + } + } } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index 457b1b9f6..7674c0a95 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -41,11 +41,13 @@ pub mod error; mod features; pub mod instance; pub mod math; +mod tokens; mod transfers; pub use counters::*; pub use features::*; pub use instance::*; +pub use tokens::*; pub use transfers::*; /// Integral type used for [`Buffer`] offsets and sizes. @@ -1468,6 +1470,9 @@ pub struct DeviceDescriptor { /// Exactly the specified limits, and no better or worse, /// will be allowed in validation of API calls on the resulting device. pub required_limits: Limits, + /// Specifies whether `self.required_features` is allowed to contain experimental features. + #[cfg_attr(feature = "serde", serde(skip))] + pub experimental_features: ExperimentalFeatures, /// Hints for memory allocation strategies. pub memory_hints: MemoryHints, /// Whether API tracing for debugging is enabled, @@ -1483,6 +1488,7 @@ impl DeviceDescriptor { label: fun(&self.label), required_features: self.required_features, required_limits: self.required_limits.clone(), + experimental_features: self.experimental_features, memory_hints: self.memory_hints.clone(), trace: self.trace.clone(), } diff --git a/wgpu-types/src/tokens.rs b/wgpu-types/src/tokens.rs new file mode 100644 index 000000000..ecf100ddb --- /dev/null +++ b/wgpu-types/src/tokens.rs @@ -0,0 +1,43 @@ +/// Token of the user agreeing to access experimental features. +#[derive(Debug, Default, Copy, Clone)] +pub struct ExperimentalFeatures { + enabled: bool, +} + +impl ExperimentalFeatures { + /// Uses of [`Features`] prefixed with "EXPERIMENTAL" are disallowed. + /// + /// [`Features`]: ../wgpu/struct.Features.html + pub const fn disabled() -> Self { + Self { enabled: false } + } + + /// Uses of [`Features`] prefixed with "EXPERIMENTAL" may result + /// in undefined behavior when used incorrectly. The exact bounds + /// of these issues varies by the feature. These instances are + /// inherently bugs in our implementation that we will eventually fix. + /// + /// By giving access to still work-in-progress APIs, users can get + /// access to newer technology sooner, and we can work with users + /// to fix bugs quicker. + /// + /// Look inside our repo at the [`api-specs`] for more information + /// on various experimental apis. + /// + /// # Safety + /// + /// - You acknowledge that there may be UB-containing bugs in these + /// apis and those may be hit by calling otherwise safe code. + /// - You agree to report any such bugs to us, if you find them. + /// + /// [`Features`]: ../wgpu/struct.Features.html + /// [`api-specs`]: https://github.com/gfx-rs/wgpu/tree/trunk/docs/api-specs + pub const unsafe fn enabled() -> Self { + Self { enabled: true } + } + + /// Returns true if the user has agreed to access experimental features. + pub const fn is_enabled(&self) -> bool { + self.enabled + } +} diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index dcfc19338..85d9b901b 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -91,7 +91,7 @@ pub use wgt::{ CommandBufferDescriptor, CompareFunction, CompositeAlphaMode, CopyExternalImageDestInfo, CoreCounters, DepthBiasState, DepthStencilState, DeviceLostReason, DeviceType, DownlevelCapabilities, DownlevelFlags, DownlevelLimits, Dx12BackendOptions, Dx12Compiler, - DxcShaderModel, DynamicOffset, Extent3d, ExternalTextureFormat, + DxcShaderModel, DynamicOffset, ExperimentalFeatures, Extent3d, ExternalTextureFormat, ExternalTextureTransferFunction, Face, Features, FeaturesWGPU, FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, GlFenceBehavior, Gles3MinorVersion, HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters,