diff --git a/CHANGELOG.md b/CHANGELOG.md index 78871071b2d..703d104d497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913). - The function you pass to `Device::on_uncaptured_error()` must now implement `Sync` in addition to `Send`, and be wrapped in `Arc` instead of `Box`. In exchange for this, it is no longer possible for calling `wgpu` functions while in that callback to cause a deadlock (not that we encourage you to actually do that). By @kpreid in [#8011](https://github.com/gfx-rs/wgpu/pull/8011). +- `wgpu` now requires that the requested device limits satisfy `min_subgroup_size <= max_subgroup_size`. By @andyleiserson in [#8085](https://github.com/gfx-rs/wgpu/pull/8085). #### Naga diff --git a/cts_runner/test.lst b/cts_runner/test.lst index 7282bef2cc1..6e4a38c2379 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -19,6 +19,7 @@ webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_w webgpu:api,operation,render_pass,storeOp:render_pass_store_op,color_attachment_only:* webgpu:api,operation,render_pass,storeOp:render_pass_store_op,multiple_color_attachments:* webgpu:api,operation,render_pass,storeOp:render_pass_store_op,depth_stencil_attachment_only:* +webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,* webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:* webgpu:api,validation,encoding,beginComputePass:* webgpu:api,validation,encoding,beginRenderPass:* diff --git a/deno_webgpu/adapter.rs b/deno_webgpu/adapter.rs index d5916fb0677..cb5cdb50309 100644 --- a/deno_webgpu/adapter.rs +++ b/deno_webgpu/adapter.rs @@ -125,8 +125,13 @@ impl GPUAdapter { return Err(CreateDeviceError::RequiredFeaturesNotASubset); } - let required_limits = - serde_json::from_value(serde_json::to_value(descriptor.required_limits)?)?; + // When support for compatibility mode is added, this will need to look + // at whether the adapter is "compatibility-defaulting" or "core-defaulting", + // and choose the appropriate set of defaults. + let required_limits = serde_json::from_value::(serde_json::to_value( + descriptor.required_limits, + )?)? + .or_better_values_from(&wgpu_types::Limits::default()); let trace = std::env::var_os("DENO_WEBGPU_TRACE") .map(|path| wgpu_types::Trace::Directory(std::path::PathBuf::from(path))) @@ -196,7 +201,7 @@ pub enum CreateDeviceError { #[class(inherit)] #[error(transparent)] Serde(#[from] serde_json::Error), - #[class(type)] + #[class("DOMExceptionOperationError")] #[error(transparent)] Device(#[from] wgpu_core::instance::RequestDeviceError), } diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index fb71e420262..a295c86caf7 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -16,6 +16,7 @@ extern crate alloc; use alloc::borrow::Cow; use alloc::{string::String, vec, vec::Vec}; +use core::cmp::Ordering; use core::{ fmt, hash::{Hash, Hasher}, @@ -459,6 +460,71 @@ impl fmt::Display for RequestAdapterError { } } +/// Invoke a macro for each of the limits. +/// +/// The supplied macro should take two arguments. The first is a limit name, as +/// an identifier, typically used to access a member of `struct Limits`. The +/// second is `Ordering::Less` if valid values are less than the limit (the +/// common case), or `Ordering::Greater` if valid values are more than the limit +/// (for limits like alignments, which are minima instead of maxima). +macro_rules! with_limits { + ($macro_name:ident) => { + $macro_name!(max_texture_dimension_1d, Ordering::Less); + $macro_name!(max_texture_dimension_1d, Ordering::Less); + $macro_name!(max_texture_dimension_2d, Ordering::Less); + $macro_name!(max_texture_dimension_3d, Ordering::Less); + $macro_name!(max_texture_array_layers, Ordering::Less); + $macro_name!(max_bind_groups, Ordering::Less); + $macro_name!(max_bindings_per_bind_group, Ordering::Less); + $macro_name!( + max_dynamic_uniform_buffers_per_pipeline_layout, + Ordering::Less + ); + $macro_name!( + max_dynamic_storage_buffers_per_pipeline_layout, + Ordering::Less + ); + $macro_name!(max_sampled_textures_per_shader_stage, Ordering::Less); + $macro_name!(max_samplers_per_shader_stage, Ordering::Less); + $macro_name!(max_storage_buffers_per_shader_stage, Ordering::Less); + $macro_name!(max_storage_textures_per_shader_stage, Ordering::Less); + $macro_name!(max_uniform_buffers_per_shader_stage, Ordering::Less); + $macro_name!(max_binding_array_elements_per_shader_stage, Ordering::Less); + $macro_name!(max_uniform_buffer_binding_size, Ordering::Less); + $macro_name!(max_storage_buffer_binding_size, Ordering::Less); + $macro_name!(max_vertex_buffers, Ordering::Less); + $macro_name!(max_buffer_size, Ordering::Less); + $macro_name!(max_vertex_attributes, Ordering::Less); + $macro_name!(max_vertex_buffer_array_stride, Ordering::Less); + $macro_name!(min_uniform_buffer_offset_alignment, Ordering::Greater); + $macro_name!(min_storage_buffer_offset_alignment, Ordering::Greater); + $macro_name!(max_inter_stage_shader_components, Ordering::Less); + $macro_name!(max_color_attachments, Ordering::Less); + $macro_name!(max_color_attachment_bytes_per_sample, Ordering::Less); + $macro_name!(max_compute_workgroup_storage_size, Ordering::Less); + $macro_name!(max_compute_invocations_per_workgroup, Ordering::Less); + $macro_name!(max_compute_workgroup_size_x, Ordering::Less); + $macro_name!(max_compute_workgroup_size_y, Ordering::Less); + $macro_name!(max_compute_workgroup_size_z, Ordering::Less); + $macro_name!(max_compute_workgroups_per_dimension, Ordering::Less); + + $macro_name!(min_subgroup_size, Ordering::Greater); + $macro_name!(max_subgroup_size, Ordering::Less); + + $macro_name!(max_push_constant_size, Ordering::Less); + $macro_name!(max_non_sampler_bindings, Ordering::Less); + + $macro_name!(max_task_workgroup_total_count, Ordering::Less); + $macro_name!(max_task_workgroups_per_dimension, Ordering::Less); + $macro_name!(max_mesh_multiview_count, Ordering::Less); + $macro_name!(max_mesh_output_layers, Ordering::Less); + + $macro_name!(max_blas_primitive_count, Ordering::Less); + $macro_name!(max_blas_geometry_count, Ordering::Less); + $macro_name!(max_tlas_instance_count, Ordering::Less); + }; +} + /// Represents the sets of limits an adapter/device supports. /// /// We provide three different defaults. @@ -1015,68 +1081,59 @@ impl Limits { fatal: bool, mut fail_fn: impl FnMut(&'static str, u64, u64), ) { - use core::cmp::Ordering; - - macro_rules! compare { - ($name:ident, $ordering:ident) => { - match self.$name.cmp(&allowed.$name) { - Ordering::$ordering | Ordering::Equal => (), - _ => { - fail_fn(stringify!($name), self.$name as u64, allowed.$name as u64); - if fatal { - return; - } + macro_rules! check_with_fail_fn { + ($name:ident, $ordering:expr) => { + let invalid_ord = $ordering.reverse(); + // In the case of `min_subgroup_size`, requesting a value of + // zero means "I'm not going to use subgroups", so we have to + // special case that. If any of our minimum limits could + // meaningfully go all the way to zero, that would conflict with + // this. + if self.$name != 0 && self.$name.cmp(&allowed.$name) == invalid_ord { + fail_fn(stringify!($name), self.$name as u64, allowed.$name as u64); + if fatal { + return; } } }; } - compare!(max_texture_dimension_1d, Less); - compare!(max_texture_dimension_2d, Less); - compare!(max_texture_dimension_3d, Less); - compare!(max_texture_array_layers, Less); - compare!(max_bind_groups, Less); - compare!(max_bindings_per_bind_group, Less); - compare!(max_dynamic_uniform_buffers_per_pipeline_layout, Less); - compare!(max_dynamic_storage_buffers_per_pipeline_layout, Less); - compare!(max_sampled_textures_per_shader_stage, Less); - compare!(max_samplers_per_shader_stage, Less); - compare!(max_storage_buffers_per_shader_stage, Less); - compare!(max_storage_textures_per_shader_stage, Less); - compare!(max_uniform_buffers_per_shader_stage, Less); - compare!(max_binding_array_elements_per_shader_stage, Less); - compare!(max_uniform_buffer_binding_size, Less); - compare!(max_storage_buffer_binding_size, Less); - compare!(max_vertex_buffers, Less); - compare!(max_buffer_size, Less); - compare!(max_vertex_attributes, Less); - compare!(max_vertex_buffer_array_stride, Less); - compare!(min_uniform_buffer_offset_alignment, Greater); - compare!(min_storage_buffer_offset_alignment, Greater); - compare!(max_inter_stage_shader_components, Less); - compare!(max_color_attachments, Less); - compare!(max_color_attachment_bytes_per_sample, Less); - compare!(max_compute_workgroup_storage_size, Less); - compare!(max_compute_invocations_per_workgroup, Less); - compare!(max_compute_workgroup_size_x, Less); - compare!(max_compute_workgroup_size_y, Less); - compare!(max_compute_workgroup_size_z, Less); - compare!(max_compute_workgroups_per_dimension, Less); - if self.min_subgroup_size > 0 && self.max_subgroup_size > 0 { - compare!(min_subgroup_size, Greater); - compare!(max_subgroup_size, Less); + if self.min_subgroup_size > self.max_subgroup_size { + fail_fn( + "max_subgroup_size", + self.min_subgroup_size as u64, + allowed.min_subgroup_size as u64, + ); + } + with_limits!(check_with_fail_fn); + } + + /// For each limit in `other` that is better than the value in `self`, + /// replace the value in `self` with the value from `other`. + /// + /// A request for a limit value less than the WebGPU-specified default must + /// be ignored. This function is used to clamp such requests to the default + /// value. + /// + /// This is not what you want to clamp a request that otherwise might be + /// asking for something beyond the supported limits. + #[must_use] + pub fn or_better_values_from(mut self, other: &Self) -> Self { + macro_rules! or_better_value_from { + ($name:ident, $ordering:expr) => { + match $ordering { + // Limits that are maximum values (most of them) + Ordering::Less => self.$name = self.$name.max(other.$name), + // Limits that are minimum values + Ordering::Greater => self.$name = self.$name.min(other.$name), + Ordering::Equal => unreachable!(), + } + }; } - compare!(max_push_constant_size, Less); - compare!(max_non_sampler_bindings, Less); - compare!(max_task_workgroup_total_count, Less); - compare!(max_task_workgroups_per_dimension, Less); - compare!(max_mesh_multiview_count, Less); - compare!(max_mesh_output_layers, Less); + with_limits!(or_better_value_from); - compare!(max_blas_primitive_count, Less); - compare!(max_blas_geometry_count, Less); - compare!(max_tlas_instance_count, Less); + self } }