Require token for experimental features (#8163)

This commit is contained in:
Connor Fitzgerald 2025-08-29 15:43:27 -04:00 committed by GitHub
parent e58223bedf
commit 8d1f4bb5f2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 191 additions and 3 deletions

View File

@ -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

View File

@ -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,
}))

View File

@ -146,6 +146,7 @@ impl GPUAdapter {
descriptor.required_features,
),
required_limits,
experimental_features: wgpu_types::ExperimentalFeatures::disabled(),
memory_hints: Default::default(),
trace,
};

View File

@ -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()),

View File

@ -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,
})

View File

@ -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,
})

View File

@ -74,6 +74,7 @@ async fn run(event_loop: EventLoop<()>, viewports: Vec<(Arc<Window>, 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,
})

View File

@ -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,
})

View File

@ -20,6 +20,7 @@ async fn run(_path: Option<String>) {
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,
})

View File

@ -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,
})

View File

@ -34,6 +34,7 @@ async fn run(_path: Option<String>) {
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,
})

View File

@ -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,
})

View File

@ -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,
})

View File

@ -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,
}))

View File

@ -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,
},

View File

@ -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,
})

View File

@ -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}");

View File

@ -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());
}

View File

@ -2,6 +2,7 @@ mod binding_arrays;
mod buffer;
mod buffer_slice;
mod device;
mod experimental;
mod external_texture;
mod instance;
mod texture;

View File

@ -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)]

View File

@ -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<VertexFormat> {
@ -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);
}
}
}

View File

@ -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<L> {
/// 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<L> DeviceDescriptor<L> {
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(),
}

43
wgpu-types/src/tokens.rs Normal file
View File

@ -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
}
}

View File

@ -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,