From 8c9258d1573c2d730a467bf944db23d3dd1a5978 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 11:35:24 +0200 Subject: [PATCH 1/9] add `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` --- CHANGELOG.md | 1 + wgpu-types/src/instance.rs | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43d403b74d0..ebb0d80b110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -132,6 +132,7 @@ By @beholdnec in [#8505](https://github.com/gfx-rs/wgpu/pull/8505). ``` By @AdrianEddy in [#9496](https://github.com/gfx-rs/wgpu/pull/9496). - Extend `copy_texture_to_texture` to allow copying a single plane of a multi-planar source (NV12, P010) into a single-plane destination of the matching format (e.g. NV12 `Plane0` → `R8Unorm`, NV12 `Plane1` → `Rg8Unorm`). `copy_size` is interpreted in plane texels, not luma texels. By @AdrianEddy in [#9551](https://github.com/gfx-rs/wgpu/pull/9551). +- Added `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` flag, which restricts the available feature set to the one defined by the WebGPU specification. By @teoxoy in [#9586](https://github.com/gfx-rs/wgpu/pull/9586). #### Metal diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index 7b475767a07..25deb8158b4 100644 --- a/wgpu-types/src/instance.rs +++ b/wgpu-types/src/instance.rs @@ -177,6 +177,11 @@ bitflags::bitflags! { /// This mainly applies to a Vulkan driver's compliance version. If the major compliance version /// is `0`, then the driver is ignored. This flag allows that driver to be enabled for testing. /// + /// This flag controls whether adapters that don't meet the *native* + /// API's own compliance requirements are returned, while + /// [`Self::STRICT_WEBGPU_COMPLIANCE`] controls whether adapters that + /// can't fully satisfy the *WebGPU v1* spec are excluded. + /// /// When `Self::from_env()` is used takes value from `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` environment variable. const ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER = 1 << 3; /// Enable GPU-based validation. Implies [`Self::VALIDATION`]. Currently, this only changes @@ -228,6 +233,11 @@ bitflags::bitflags! { /// #[doc = link_to_wgpu_docs!(["rqs"]: "struct.CommandEncoder.html#method.resolve_query_set")] const AUTOMATIC_TIMESTAMP_NORMALIZATION = 1 << 6; + + /// Restrict the available feature set to the one defined by the WebGPU specification. + /// + /// When `Self::from_env()` is used takes value from `WGPU_STRICT_WEBGPU_COMPLIANCE` environment variable. + const STRICT_WEBGPU_COMPLIANCE = 1 << 7; } } @@ -285,6 +295,7 @@ impl InstanceFlags { /// - `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER` /// - `WGPU_GPU_BASED_VALIDATION` /// - `WGPU_VALIDATION_INDIRECT_CALL` + /// - `WGPU_STRICT_WEBGPU_COMPLIANCE` #[must_use] pub fn with_env(mut self) -> Self { fn env(key: &str) -> Option { @@ -313,6 +324,9 @@ impl InstanceFlags { if let Some(bit) = env("WGPU_VALIDATION_INDIRECT_CALL") { self.set(Self::VALIDATION_INDIRECT_CALL, bit); } + if let Some(bit) = env("WGPU_STRICT_WEBGPU_COMPLIANCE") { + self.set(Self::STRICT_WEBGPU_COMPLIANCE, bit); + } self } From b8c2644670e1acb740bcfe0f6f11af645a7580b1 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 11:58:45 +0200 Subject: [PATCH 2/9] disallow `LoadOp::DontCare` under `STRICT_WEBGPU_COMPLIANCE` --- tests/tests/wgpu-gpu/pass_ops/mod.rs | 149 ++++++++++++++++++++++++++- wgpu-core/src/command/render.rs | 32 ++++-- 2 files changed, 170 insertions(+), 11 deletions(-) diff --git a/tests/tests/wgpu-gpu/pass_ops/mod.rs b/tests/tests/wgpu-gpu/pass_ops/mod.rs index e6fc4960605..c1655913605 100644 --- a/tests/tests/wgpu-gpu/pass_ops/mod.rs +++ b/tests/tests/wgpu-gpu/pass_ops/mod.rs @@ -1,18 +1,23 @@ use wgpu_test::{ - gpu_test, image::ReadbackBuffers, GpuTestConfiguration, GpuTestInitializer, TestParameters, - TestingContext, + fail, gpu_test, image::ReadbackBuffers, GpuTestConfiguration, GpuTestInitializer, + TestParameters, TestingContext, }; pub fn all_tests(vec: &mut Vec) { - vec.push(DONT_CARE); + vec.extend([ + DONT_CARE, + DONT_CARE_COLOR_STRICT_WEBGPU_COMPLIANCE, + DONT_CARE_DEPTH_STRICT_WEBGPU_COMPLIANCE, + DONT_CARE_STENCIL_STRICT_WEBGPU_COMPLIANCE, + ]); } #[gpu_test] static DONT_CARE: GpuTestConfiguration = GpuTestConfiguration::new() .parameters(TestParameters::default()) - .run_async(run_test); + .run_async(dont_care); -async fn run_test(ctx: TestingContext) { +async fn dont_care(ctx: TestingContext) { let shader_src = " const triangles = array(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0)); @@ -109,3 +114,137 @@ async fn run_test(ctx: TestingContext) { .assert_buffer_contents(&ctx, &[127, 127, 127, 127]) .await; } + +#[gpu_test] +static DONT_CARE_COLOR_STRICT_WEBGPU_COMPLIANCE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .instance_flags(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + .enable_noop(), + ) + .run_sync(|ctx| { + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() }), + store: wgpu::StoreOp::Store, + }, + })], + ..Default::default() + }); + fail( + &ctx.device, + || encoder.finish(), + Some("STRICT_WEBGPU_COMPLIANCE"), + ); + }); + +#[gpu_test] +static DONT_CARE_DEPTH_STRICT_WEBGPU_COMPLIANCE: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .instance_flags(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + .enable_noop(), + ) + .run_sync(|ctx| { + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth16Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() }), + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + ..Default::default() + }); + fail( + &ctx.device, + || encoder.finish(), + Some("STRICT_WEBGPU_COMPLIANCE"), + ); + }); + +#[gpu_test] +static DONT_CARE_STENCIL_STRICT_WEBGPU_COMPLIANCE: GpuTestConfiguration = + GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .instance_flags(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + .enable_noop(), + ) + .run_sync(|ctx| { + let tex = ctx.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: 1, + height: 1, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Stencil8, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }); + let view = tex.create_view(&wgpu::TextureViewDescriptor::default()); + let mut encoder = ctx + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor::default()); + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &view, + depth_ops: None, + stencil_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::DontCare(unsafe { wgpu::LoadOpDontCare::enabled() }), + store: wgpu::StoreOp::Store, + }), + }), + ..Default::default() + }); + fail( + &ctx.device, + || encoder.finish(), + Some("STRICT_WEBGPU_COMPLIANCE"), + ); + }); diff --git a/wgpu-core/src/command/render.rs b/wgpu-core/src/command/render.rs index 279e9593091..fbbf0bd858b 100644 --- a/wgpu-core/src/command/render.rs +++ b/wgpu-core/src/command/render.rs @@ -6,8 +6,8 @@ use arrayvec::ArrayVec; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, - BufferAddress, BufferSize, BufferUsages, Color, DynamicOffset, IndexFormat, TextureSelector, - TextureUsages, TextureViewDimension, VertexStepMode, + BufferAddress, BufferSize, BufferUsages, Color, DynamicOffset, IndexFormat, InstanceFlags, + TextureSelector, TextureUsages, TextureViewDimension, VertexStepMode, }; use crate::{ @@ -109,6 +109,7 @@ pub struct PassChannel { impl PassChannel> { fn resolve( &self, + instance_flags: InstanceFlags, handle_clear: impl Fn(Option) -> Result, ) -> Result, AttachmentError> { if self.read_only { @@ -123,7 +124,12 @@ impl PassChannel> { Ok(ResolvedPassChannel::Operational(wgt::Operations { load: match self.load_op.ok_or(AttachmentError::NoLoad)? { LoadOp::Clear(clear_value) => LoadOp::Clear(handle_clear(clear_value)?), - LoadOp::DontCare(token) => LoadOp::DontCare(token), + LoadOp::DontCare(token) => { + if instance_flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) { + return Err(AttachmentError::LoadOpDontCareUnderStrictWebgpuCompliance); + } + LoadOp::DontCare(token) + } LoadOp::Load => LoadOp::Load, }, store: self.store_op.ok_or(AttachmentError::NoStore)?, @@ -803,6 +809,8 @@ pub enum ColorAttachmentError { }, #[error("Color attachment's usage contains {0:?}. This can only be used with StoreOp::{1:?}, but StoreOp::{2:?} was provided")] InvalidUsageForStoreOp(TextureUsages, StoreOp, StoreOp), + #[error("Color attachment's load op is `LoadOp::DontCare` but `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` is set")] + LoadOpDontCareUnderStrictWebgpuCompliance, } impl WebGpuError for ColorAttachmentError { @@ -838,6 +846,8 @@ pub enum AttachmentError { NoClearValue, #[error("Clear value ({0}) must be between 0.0 and 1.0, inclusive")] ClearValueOutOfRange(f32), + #[error("Load op is `DontCare` but `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` is set")] + LoadOpDontCareUnderStrictWebgpuCompliance, } impl WebGpuError for AttachmentError { @@ -1688,7 +1698,7 @@ impl RenderPassInfo { raw: &mut dyn hal::DynCommandEncoder, snatch_guard: &SnatchGuard, scope: &mut UsageScope<'_>, - instance_flags: wgt::InstanceFlags, + instance_flags: InstanceFlags, ) -> Result<(), RenderPassErrorInner> { profiling::scope!("RenderPassInfo::finish"); unsafe { @@ -1811,6 +1821,16 @@ impl Global { let view = texture_views.get(*view_id).get()?; view.same_device(device)?; + if matches!(*load_op, LoadOp::DontCare(..)) + && device + .instance_flags + .contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + { + return Err(RenderPassErrorInner::ColorAttachment( + ColorAttachmentError::LoadOpDontCareUnderStrictWebgpuCompliance, + )); + } + if view.desc.usage.contains(TextureUsages::TRANSIENT) && *store_op != StoreOp::Discard { @@ -1862,7 +1882,7 @@ impl Global { Some(ResolvedRenderPassDepthStencilAttachment { view, depth: if format.has_depth_aspect() { - depth_stencil_attachment.depth.resolve(|clear| if let Some(clear) = clear { + depth_stencil_attachment.depth.resolve(device.instance_flags, |clear| if let Some(clear) = clear { // If this.depthLoadOp is "clear", this.depthClearValue must be provided and must be between 0.0 and 1.0, inclusive. if !(0.0..=1.0).contains(&clear) { Err(AttachmentError::ClearValueOutOfRange(clear)) @@ -1882,7 +1902,7 @@ impl Global { ResolvedPassChannel::ReadOnly }, stencil: if format.has_stencil_aspect() { - depth_stencil_attachment.stencil.resolve(|clear| { + depth_stencil_attachment.stencil.resolve(device.instance_flags, |clear| { Ok(convert_stencil_value(clear.unwrap_or_default(), Some(format))) })? } else { From d7735e2c13b58b37f9de52af21e0cdfacebe2b8b Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 12:54:01 +0200 Subject: [PATCH 3/9] filter out adapters with missing downlevel flags under `STRICT_WEBGPU_COMPLIANCE` --- wgpu-core/src/instance.rs | 41 ++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 1ee81054c62..97f4169d5d4 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -17,7 +17,7 @@ use crate::{ DOWNLEVEL_WARNING_MESSAGE, }; -use wgt::{Backend, Backends, PowerPreference}; +use wgt::{Backend, Backends, InstanceFlags, PowerPreference}; pub type RequestAdapterOptions = wgt::RequestAdapterOptions; @@ -51,7 +51,7 @@ pub struct Instance { /// `instance_per_backend` instead. supported_backends: Backends, - pub flags: wgt::InstanceFlags, + pub flags: InstanceFlags, /// Non-lifetimed [`raw_window_handle::DisplayHandle`], for keepalive and validation purposes in /// [`Self::create_surface()`]. @@ -153,7 +153,7 @@ impl Instance { instance_per_backend: vec![(A::VARIANT, Box::new(hal_instance))], requested_backends: A::VARIANT.into(), supported_backends: A::VARIANT.into(), - flags: wgt::InstanceFlags::default(), + flags: InstanceFlags::default(), display: None, // TODO: Extract display from HAL instance if available? } } @@ -414,6 +414,20 @@ impl Instance { }) } + fn adapter_allowed(&self, raw: &hal::DynExposedAdapter) -> bool { + let allowed = !self.flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + || raw.capabilities.downlevel.is_webgpu_compliant(); + if !allowed { + let missing_flags = wgt::DownlevelFlags::compliant() - raw.capabilities.downlevel.flags; + log::debug!( + "Adapter {:?} is not WebGPU compliant due to missing downlevel flags: {:?}", + raw.info, + missing_flags + ); + } + allowed + } + pub fn enumerate_adapters( &self, backends: Backends, @@ -441,6 +455,7 @@ impl Instance { self.adjust_limits_for_indirect_validation(&mut raw.capabilities.limits); raw }) + .filter(|raw| self.adapter_allowed(raw)) .filter_map(|raw| { if apply_limit_buckets { limits::apply_limit_buckets(raw) @@ -526,10 +541,13 @@ impl Instance { } } - let backend_adapters = backend_adapters.into_iter().map(|mut raw| { - self.adjust_limits_for_indirect_validation(&mut raw.capabilities.limits); - raw - }); + let backend_adapters = backend_adapters + .into_iter() + .map(|mut raw| { + self.adjust_limits_for_indirect_validation(&mut raw.capabilities.limits); + raw + }) + .filter(|raw| self.adapter_allowed(raw)); if desc.apply_limit_buckets { adapters.extend(backend_adapters.filter_map(limits::apply_limit_buckets)); @@ -607,10 +625,7 @@ impl Instance { fn adjust_limits_for_indirect_validation(&self, limits: &mut wgt::Limits) { // Indirect draw validation can't support u64 offsets, // lower max buffer and binding size to fit in an u32. - if self - .flags - .contains(wgt::InstanceFlags::VALIDATION_INDIRECT_CALL) - { + if self.flags.contains(InstanceFlags::VALIDATION_INDIRECT_CALL) { limits.max_buffer_size = limits.max_buffer_size.min(u32::MAX as u64); limits.max_uniform_buffer_binding_size = limits.max_uniform_buffer_binding_size.min(u32::MAX as u64); @@ -820,7 +835,7 @@ impl Adapter { self: &Arc, hal_device: hal::DynOpenDevice, desc: &DeviceDescriptor, - instance_flags: wgt::InstanceFlags, + instance_flags: InstanceFlags, ) -> Result<(Arc, Arc), RequestDeviceError> { api_log!("Adapter::create_device"); @@ -839,7 +854,7 @@ impl Adapter { pub fn create_device_and_queue( self: &Arc, desc: &DeviceDescriptor, - instance_flags: wgt::InstanceFlags, + instance_flags: InstanceFlags, ) -> Result<(Arc, Arc), RequestDeviceError> { // Verify all features were exposed by the adapter if !self.raw.features.contains(desc.required_features) { From b90cac3ebdc240d639e24887ffa62c316f3aaed9 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 13:13:39 +0200 Subject: [PATCH 4/9] check alignment limits are powers of 2 --- wgpu-core/src/instance.rs | 43 +++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 97f4169d5d4..32489ba442c 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -414,18 +414,53 @@ impl Instance { }) } + /// This function checks that the adapter obeys WebGPU's adapter capability + /// guarantees. Most of the limits are adjusted in wgpu-hal's + /// `adjust_raw_limits` fn. So we only check the remaining properties here. + /// See . fn adapter_allowed(&self, raw: &hal::DynExposedAdapter) -> bool { - let allowed = !self.flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) - || raw.capabilities.downlevel.is_webgpu_compliant(); - if !allowed { + // Check "All alignment-class limits must be powers of 2." + // + // Even if the application has not requested strict WebGPU compliance, + // non-power-of-two alignment limits are nonsensical, so don't attempt + // to use such a device. + let min_uniform_buffer_offset_alignment = + raw.capabilities.limits.min_uniform_buffer_offset_alignment; + if !min_uniform_buffer_offset_alignment.is_power_of_two() { + log::error!( + "Adapter {:?} min_uniform_buffer_offset_alignment limit is not a power of 2: {:?}", + raw.info, + min_uniform_buffer_offset_alignment + ); + return false; + } + let min_storage_buffer_offset_alignment = + raw.capabilities.limits.min_storage_buffer_offset_alignment; + if !min_storage_buffer_offset_alignment.is_power_of_two() { + log::error!( + "Adapter {:?} min_storage_buffer_offset_alignment limit is not a power of 2: {:?}", + raw.info, + min_storage_buffer_offset_alignment + ); + return false; + } + + // Following checks are only enabled if `STRICT_WEBGPU_COMPLIANCE` is set. + if !self.flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) { + return true; + } + + if !raw.capabilities.downlevel.is_webgpu_compliant() { let missing_flags = wgt::DownlevelFlags::compliant() - raw.capabilities.downlevel.flags; log::debug!( "Adapter {:?} is not WebGPU compliant due to missing downlevel flags: {:?}", raw.info, missing_flags ); + return false; } - allowed + + true } pub fn enumerate_adapters( From 1df433b0dc3f499adf6473853654badb0c2926f6 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 13:23:08 +0200 Subject: [PATCH 5/9] check that all limits are at least as high as default limits under `STRICT_WEBGPU_COMPLIANCE` --- wgpu-core/src/instance.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 32489ba442c..7325199325c 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -450,6 +450,17 @@ impl Instance { return true; } + // Check "All supported limits must be either the default value or better." + let failed_limits = check_limits(&wgt::Limits::defaults(), &raw.capabilities.limits); + if !failed_limits.is_empty() { + log::debug!( + "Adapter {:?} is not WebGPU compliant due to limits: {:?}", + raw.info, + failed_limits + ); + return false; + } + if !raw.capabilities.downlevel.is_webgpu_compliant() { let missing_flags = wgt::DownlevelFlags::compliant() - raw.capabilities.downlevel.flags; log::debug!( From b7dc045aaacd6986861304edb54678526dffaffb Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 13:37:42 +0200 Subject: [PATCH 6/9] add downlevel flag for `BC || (ETC2 && ASTC)` --- wgpu-hal/src/gles/adapter.rs | 9 +++++++++ wgpu-hal/src/metal/adapter.rs | 4 ++++ wgpu-hal/src/vulkan/adapter.rs | 9 +++++++++ wgpu-types/src/limits.rs | 6 ++++++ 4 files changed, 28 insertions(+) diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 246384b804a..236e71bd031 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -574,6 +574,15 @@ impl super::Adapter { ); } + downlevel_flags.set( + wgt::DownlevelFlags::TEXTURE_COMPRESSION, + features.contains(wgt::Features::TEXTURE_COMPRESSION_BC) + || features.contains( + wgt::Features::TEXTURE_COMPRESSION_ETC2 + | wgt::Features::TEXTURE_COMPRESSION_ASTC, + ), + ); + features.set( wgt::Features::FLOAT32_FILTERABLE, extensions.contains("GL_ARB_color_buffer_float") diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index ef4ddcdd214..5b2f5bb18c8 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -1314,6 +1314,10 @@ impl super::CapabilitiesQuery { wgt::DownlevelFlags::MSL2_1, self.msl_version >= MTLLanguageVersion::Version2_1, ); + downlevel.flags.set( + wgt::DownlevelFlags::TEXTURE_COMPRESSION, + self.format_bc || (self.format_eac_etc && self.format_astc), + ); let limits = crate::auxil::adjust_raw_limits(wgt::Limits { // diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 211e53e3731..c88f3b2c161 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -2270,6 +2270,15 @@ impl super::Instance { ); } + downlevel_flags.set( + wgt::DownlevelFlags::TEXTURE_COMPRESSION, + available_features.contains(wgt::Features::TEXTURE_COMPRESSION_BC) + || available_features.contains( + wgt::Features::TEXTURE_COMPRESSION_ETC2 + | wgt::Features::TEXTURE_COMPRESSION_ASTC, + ), + ); + let has_robust_buffer_access2 = phd_features .robustness2 .as_ref() diff --git a/wgpu-types/src/limits.rs b/wgpu-types/src/limits.rs index e0961362a33..e8e599c8460 100644 --- a/wgpu-types/src/limits.rs +++ b/wgpu-types/src/limits.rs @@ -1082,6 +1082,12 @@ bitflags::bitflags! { /// Supports features introduced in MSL 2.1. const MSL2_1 = 1 << 24; + + /// The adapter supports the WebGPU texture compression requirement: + /// BC || (ETC2 && ASTC). + /// + /// See . + const TEXTURE_COMPRESSION = 1 << 25; } } From 15d57b5bb5900968cd2add2610ba668fc3ddfadf Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Mon, 25 May 2026 15:43:21 +0200 Subject: [PATCH 7/9] add test to ensure returned adapters are WebGPU compliant if `STRICT_WEBGPU_COMPLIANCE` is set --- tests/src/init.rs | 36 +++++++++++++++++++++++---------- tests/src/run.rs | 7 +++++-- tests/tests/wgpu-gpu/adapter.rs | 20 ++++++++++++++++++ tests/tests/wgpu-gpu/device.rs | 4 +++- tests/tests/wgpu-gpu/main.rs | 2 ++ 5 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 tests/tests/wgpu-gpu/adapter.rs diff --git a/tests/src/init.rs b/tests/src/init.rs index 9ed3c5df95f..77476b836f6 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -105,10 +105,12 @@ pub fn initialize_instance(backends: wgpu::Backends, params: &TestParameters) -> } /// Initialize a wgpu adapter, using the given adapter report to match the adapter. +/// +/// Returns `None` if the adapter from the report is not returned by `enumerate_adapters` due to `InstanceFlags::STRICT_WEBGPU_COMPLIANCE` being set. pub async fn initialize_adapter( adapter_report: Option<&AdapterReport>, params: &TestParameters, -) -> (Instance, Adapter, Option) { +) -> Option<(Instance, Adapter, Option)> { let backends = adapter_report .map(|report| Backends::from(report.info.backend)) .unwrap_or_default(); @@ -157,24 +159,36 @@ pub async fn initialize_adapter( } else { true }); - let Some(adapter) = adapter else { - panic!( - "Could not find adapter with info {:#?} in {:#?}", - adapter_report.map(|r| &r.info), - instance.enumerate_adapters(backends).await.into_iter().map(|a| a.get_info()).collect::>(), - ); - }; } else { let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions { compatible_surface: surface.as_ref(), ..Default::default() - }).await.unwrap(); + }).await.ok(); } } - log::info!("Testing using adapter: {:#?}", adapter.get_info()); + let Some(adapter) = adapter else { + if params + .required_instance_flags + .contains(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + { + return None; + } else { + panic!( + "Could not find adapter with info {:#?} in {:#?}", + adapter_report.map(|r| &r.info), + instance + .enumerate_adapters(backends) + .await + .into_iter() + .map(|a| a.get_info()) + .collect::>(), + ); + } + }; - (instance, adapter, surface_guard) + log::info!("Testing using adapter: {:#?}", adapter.get_info()); + Some((instance, adapter, surface_guard)) } /// Initialize a wgpu device from a given adapter. diff --git a/tests/src/run.rs b/tests/src/run.rs index 16dcc7ecbf1..e0914dd5403 100644 --- a/tests/src/run.rs +++ b/tests/src/run.rs @@ -43,8 +43,11 @@ pub async fn execute_test( let _test_guard = isolation::OneTestPerProcessGuard::new(); - let (instance, adapter, _surface_guard) = - initialize_adapter(adapter_report, &config.params).await; + let Some((instance, adapter, _surface_guard)) = + initialize_adapter(adapter_report, &config.params).await + else { + return; + }; let adapter_info = adapter.get_info(); let adapter_downlevel_capabilities = adapter.get_downlevel_capabilities(); diff --git a/tests/tests/wgpu-gpu/adapter.rs b/tests/tests/wgpu-gpu/adapter.rs new file mode 100644 index 00000000000..6af48360500 --- /dev/null +++ b/tests/tests/wgpu-gpu/adapter.rs @@ -0,0 +1,20 @@ +use wgpu_test::{gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters}; + +pub fn all_tests(vec: &mut Vec) { + vec.push(STRICT_WEBGPU_COMPLIANCE_ADAPTER); +} + +#[gpu_test] +static STRICT_WEBGPU_COMPLIANCE_ADAPTER: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters( + TestParameters::default() + .instance_flags(wgpu::InstanceFlags::STRICT_WEBGPU_COMPLIANCE) + .enable_noop(), + ) + .run_sync(|ctx| { + assert!(ctx + .adapter + .get_downlevel_capabilities() + .is_webgpu_compliant()); + assert!(wgpu::Limits::defaults().check_limits(&ctx.adapter.limits())); + }); diff --git a/tests/tests/wgpu-gpu/device.rs b/tests/tests/wgpu-gpu/device.rs index 1d76536aa5b..0250493a939 100644 --- a/tests/tests/wgpu-gpu/device.rs +++ b/tests/tests/wgpu-gpu/device.rs @@ -135,7 +135,9 @@ async fn request_device_error_message() { // Not using initialize_test() because that doesn't let us catch the error // nor .await anything let (_instance, adapter, _surface_guard) = - wgpu_test::initialize_adapter(None, &TestParameters::default()).await; + wgpu_test::initialize_adapter(None, &TestParameters::default()) + .await + .unwrap(); let device_error = adapter .request_device(&wgpu::DeviceDescriptor { diff --git a/tests/tests/wgpu-gpu/main.rs b/tests/tests/wgpu-gpu/main.rs index 8383e1d8a43..604f328c42b 100644 --- a/tests/tests/wgpu-gpu/main.rs +++ b/tests/tests/wgpu-gpu/main.rs @@ -13,6 +13,7 @@ mod regression { pub mod issue_9115; } +mod adapter; mod bgra8unorm_storage; mod bind_group_layout_dedup; mod bind_groups; @@ -86,6 +87,7 @@ mod zero_init; fn all_tests() -> Vec { let mut tests = Vec::new(); + adapter::all_tests(&mut tests); bgra8unorm_storage::all_tests(&mut tests); bind_group_layout_dedup::all_tests(&mut tests); bind_groups::all_tests(&mut tests); From c3c5a55f69e2b7c9dca4ebd0e568a7be1ef48a47 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:48:51 +0200 Subject: [PATCH 8/9] fix typo in comment --- wgpu-hal/src/dx12/adapter.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index ade2715767e..4707298aa78 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -918,7 +918,7 @@ impl super::Adapter { // 65536 max_uniform_buffer_binding_size: Direct3D12::D3D12_REQ_CONSTANT_BUFFER_ELEMENT_COUNT as u64 * 16, - // 254 + // 256 min_uniform_buffer_offset_alignment: Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, // 16 From 38ed05600f8f956d3a90cfa834481329d0e63818 Mon Sep 17 00:00:00 2001 From: teoxoy <28601907+teoxoy@users.noreply.github.com> Date: Wed, 3 Jun 2026 22:43:08 +0200 Subject: [PATCH 9/9] add tests for `adapter_allowed` --- wgpu-core/src/instance.rs | 231 ++++++++++++++++++++++++++++---------- 1 file changed, 174 insertions(+), 57 deletions(-) diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 7325199325c..07fe0f0a48f 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -1,4 +1,5 @@ use alloc::{borrow::ToOwned as _, boxed::Box, string::String, sync::Arc, vec, vec::Vec}; +use core::fmt; use hashbrown::HashMap; use thiserror::Error; @@ -414,64 +415,13 @@ impl Instance { }) } - /// This function checks that the adapter obeys WebGPU's adapter capability - /// guarantees. Most of the limits are adjusted in wgpu-hal's - /// `adjust_raw_limits` fn. So we only check the remaining properties here. - /// See . fn adapter_allowed(&self, raw: &hal::DynExposedAdapter) -> bool { - // Check "All alignment-class limits must be powers of 2." - // - // Even if the application has not requested strict WebGPU compliance, - // non-power-of-two alignment limits are nonsensical, so don't attempt - // to use such a device. - let min_uniform_buffer_offset_alignment = - raw.capabilities.limits.min_uniform_buffer_offset_alignment; - if !min_uniform_buffer_offset_alignment.is_power_of_two() { - log::error!( - "Adapter {:?} min_uniform_buffer_offset_alignment limit is not a power of 2: {:?}", - raw.info, - min_uniform_buffer_offset_alignment - ); - return false; - } - let min_storage_buffer_offset_alignment = - raw.capabilities.limits.min_storage_buffer_offset_alignment; - if !min_storage_buffer_offset_alignment.is_power_of_two() { - log::error!( - "Adapter {:?} min_storage_buffer_offset_alignment limit is not a power of 2: {:?}", - raw.info, - min_storage_buffer_offset_alignment - ); - return false; - } - - // Following checks are only enabled if `STRICT_WEBGPU_COMPLIANCE` is set. - if !self.flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) { - return true; - } - - // Check "All supported limits must be either the default value or better." - let failed_limits = check_limits(&wgt::Limits::defaults(), &raw.capabilities.limits); - if !failed_limits.is_empty() { - log::debug!( - "Adapter {:?} is not WebGPU compliant due to limits: {:?}", - raw.info, - failed_limits - ); - return false; - } - - if !raw.capabilities.downlevel.is_webgpu_compliant() { - let missing_flags = wgt::DownlevelFlags::compliant() - raw.capabilities.downlevel.flags; - log::debug!( - "Adapter {:?} is not WebGPU compliant due to missing downlevel flags: {:?}", - raw.info, - missing_flags - ); - return false; - } - - true + adapter_allowed( + self.flags, + &raw.info, + &raw.capabilities.limits, + &raw.capabilities.downlevel, + ) } pub fn enumerate_adapters( @@ -1315,3 +1265,170 @@ impl Global { Ok((device_id, queue_id)) } } + +/// This function checks that the adapter obeys WebGPU's adapter capability +/// guarantees. Most of the limits are adjusted in wgpu-hal's +/// `adjust_raw_limits` fn. So we only check the remaining properties here. +/// See . +fn adapter_allowed( + flags: InstanceFlags, + info: &impl fmt::Debug, + limits: &wgt::Limits, + downlevel: &wgt::DownlevelCapabilities, +) -> bool { + // Check "All alignment-class limits must be powers of 2." + // + // Even if the application has not requested strict WebGPU compliance, + // non-power-of-two alignment limits are nonsensical, so don't attempt + // to use such a device. + let min_uniform_buffer_offset_alignment = limits.min_uniform_buffer_offset_alignment; + if !min_uniform_buffer_offset_alignment.is_power_of_two() { + log::error!( + "Adapter {:?} min_uniform_buffer_offset_alignment limit is not a power of 2: {:?}", + info, + min_uniform_buffer_offset_alignment + ); + return false; + } + let min_storage_buffer_offset_alignment = limits.min_storage_buffer_offset_alignment; + if !min_storage_buffer_offset_alignment.is_power_of_two() { + log::error!( + "Adapter {:?} min_storage_buffer_offset_alignment limit is not a power of 2: {:?}", + info, + min_storage_buffer_offset_alignment + ); + return false; + } + + // Following checks are only enabled if `STRICT_WEBGPU_COMPLIANCE` is set. + if !flags.contains(InstanceFlags::STRICT_WEBGPU_COMPLIANCE) { + return true; + } + + // Check "All supported limits must be either the default value or better." + let failed_limits = check_limits(&wgt::Limits::defaults(), limits); + if !failed_limits.is_empty() { + log::debug!( + "Adapter {:?} is not WebGPU compliant due to limits: {:?}", + info, + failed_limits + ); + return false; + } + + if !downlevel.is_webgpu_compliant() { + let missing_flags = wgt::DownlevelFlags::compliant() - downlevel.flags; + log::debug!( + "Adapter {:?} is not WebGPU compliant due to missing downlevel flags: {:?}", + info, + missing_flags + ); + return false; + } + + true +} + +#[cfg(test)] +mod tests { + use super::*; + + fn compliant_downlevel() -> wgt::DownlevelCapabilities { + wgt::DownlevelCapabilities { + flags: wgt::DownlevelFlags::compliant(), + ..Default::default() + } + } + + #[test] + fn non_power_of_two_uniform_alignment_always_rejected() { + let limits = wgt::Limits { + min_uniform_buffer_offset_alignment: 3, + ..wgt::Limits::defaults() + }; + assert!(!adapter_allowed( + InstanceFlags::empty(), + &"", + &limits, + &compliant_downlevel() + )); + assert!(!adapter_allowed( + InstanceFlags::STRICT_WEBGPU_COMPLIANCE, + &"", + &limits, + &compliant_downlevel() + )); + } + + #[test] + fn non_power_of_two_storage_alignment_always_rejected() { + let limits = wgt::Limits { + min_storage_buffer_offset_alignment: 96, + ..wgt::Limits::defaults() + }; + assert!(!adapter_allowed( + InstanceFlags::empty(), + &"", + &limits, + &compliant_downlevel() + )); + assert!(!adapter_allowed( + InstanceFlags::STRICT_WEBGPU_COMPLIANCE, + &"", + &limits, + &compliant_downlevel() + )); + } + + #[test] + fn low_limits_allowed_without_strict_compliance() { + let limits = wgt::Limits { + max_texture_dimension_1d: 1, + ..wgt::Limits::defaults() + }; + assert!(adapter_allowed( + InstanceFlags::empty(), + &"", + &limits, + &wgt::DownlevelCapabilities::default() + )); + } + + #[test] + fn low_limits_rejected_with_strict_compliance() { + let limits = wgt::Limits { + max_texture_dimension_1d: 1, + ..wgt::Limits::defaults() + }; + assert!(!adapter_allowed( + InstanceFlags::STRICT_WEBGPU_COMPLIANCE, + &"", + &limits, + &compliant_downlevel() + )); + } + + #[test] + fn missing_downlevel_flags_rejected_with_strict_compliance() { + let downlevel = wgt::DownlevelCapabilities { + flags: wgt::DownlevelFlags::empty(), + ..Default::default() + }; + assert!(!adapter_allowed( + InstanceFlags::STRICT_WEBGPU_COMPLIANCE, + &"", + &wgt::Limits::defaults(), + &downlevel + )); + } + + #[test] + fn fully_compliant_adapter_always_allowed() { + assert!(adapter_allowed( + InstanceFlags::STRICT_WEBGPU_COMPLIANCE, + &"", + &wgt::Limits::defaults(), + &compliant_downlevel() + )); + } +}