diff --git a/cts_runner/test.lst b/cts_runner/test.lst index aacb7776ed..03f7ec6738 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -33,6 +33,7 @@ webgpu:api,operation,vertex_state,correctness:setVertexBuffer_offset_and_attribu webgpu:api,validation,buffer,create:* webgpu:api,validation,buffer,destroy:* fails-if(dx12) webgpu:api,validation,capability_checks,limits,maxBindGroups:setBindGroup,* +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:* webgpu:api,validation,createBindGroup:buffer_offset_and_size_for_bind_groups_match:* webgpu:api,validation,createBindGroup:buffer,effective_buffer_binding_size:* webgpu:api,validation,createBindGroup:buffer,resource_binding_size:* @@ -135,6 +136,7 @@ webgpu:api,validation,render_pass,render_pass_descriptor:attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:color_attachments,* webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,* webgpu:api,validation,render_pass,resolve:resolve_attachment:* +webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:* webgpu:api,validation,resource_usages,buffer,in_pass_encoder:* // FAIL: 2 other cases in resource_usages,texture,in_pass_encoder. https://github.com/gfx-rs/wgpu/issues/3126 webgpu:api,validation,resource_usages,texture,in_pass_encoder:scope,* diff --git a/deno_webgpu/01_webgpu.js b/deno_webgpu/01_webgpu.js index 8c5ce5c593..682aa54939 100644 --- a/deno_webgpu/01_webgpu.js +++ b/deno_webgpu/01_webgpu.js @@ -273,8 +273,7 @@ ObjectDefineProperty(GPUSupportedLimitsPrototype, privateCustomInspect, { "maxBufferSize", "maxVertexAttributes", "maxVertexBufferArrayStride", - // TODO(@crowlKats): support max_inter_stage_shader_variables - // "maxInterStageShaderVariables", + "maxInterStageShaderVariables", "maxColorAttachments", "maxColorAttachmentBytesPerSample", "maxComputeWorkgroupStorageSize", diff --git a/deno_webgpu/adapter.rs b/deno_webgpu/adapter.rs index 42d24d88ca..d73fec74ea 100644 --- a/deno_webgpu/adapter.rs +++ b/deno_webgpu/adapter.rs @@ -339,7 +339,10 @@ impl GPUSupportedLimits { self.0.max_vertex_buffer_array_stride } - // TODO(@crowlKats): support max_inter_stage_shader_variables + #[getter] + fn maxInterStageShaderVariables(&self) -> u32 { + self.0.max_inter_stage_shader_variables + } #[getter] fn maxColorAttachments(&self) -> u32 { diff --git a/wgpu-core/src/device/resource.rs b/wgpu-core/src/device/resource.rs index 8dba743557..fbfc97df91 100644 --- a/wgpu-core/src/device/resource.rs +++ b/wgpu-core/src/device/resource.rs @@ -3743,10 +3743,10 @@ impl Device { let final_entry_point_name; { - let stage = wgt::ShaderStages::COMPUTE; + let stage = validation::ShaderStageForValidation::Compute; final_entry_point_name = shader_module.finalize_entry_point_name( - stage, + stage.to_naga(), desc.stage.entry_point.as_ref().map(|ep| ep.as_ref()), )?; @@ -3757,7 +3757,6 @@ impl Device { &final_entry_point_name, stage, io, - None, )?; } } @@ -4230,17 +4229,23 @@ impl Device { pipeline::RenderPipelineVertexProcessor::Vertex(ref vertex) => { vertex_stage = { let stage_desc = &vertex.stage; - let stage = wgt::ShaderStages::VERTEX; + let stage = validation::ShaderStageForValidation::Vertex { + topology: desc.primitive.topology, + compare_function: desc.depth_stencil.as_ref().map(|d| d.depth_compare), + }; + let stage_bit = stage.to_wgt_bit(); let vertex_shader_module = &stage_desc.module; vertex_shader_module.same_device(self)?; - let stage_err = - |error| pipeline::CreateRenderPipelineError::Stage { stage, error }; + let stage_err = |error| pipeline::CreateRenderPipelineError::Stage { + stage: stage_bit, + error, + }; _vertex_entry_point_name = vertex_shader_module .finalize_entry_point_name( - stage, + stage.to_naga(), stage_desc.entry_point.as_ref().map(|ep| ep.as_ref()), ) .map_err(stage_err)?; @@ -4253,10 +4258,9 @@ impl Device { &_vertex_entry_point_name, stage, io, - desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) .map_err(stage_err)?; - validated_stages |= stage; + validated_stages |= stage_bit; } Some(hal::ProgrammableStage { module: vertex_shader_module.raw(), @@ -4272,16 +4276,19 @@ impl Device { task_stage = if let Some(task) = task { let stage_desc = &task.stage; - let stage = wgt::ShaderStages::TASK; + let stage = validation::ShaderStageForValidation::Task; + let stage_bit = stage.to_wgt_bit(); let task_shader_module = &stage_desc.module; task_shader_module.same_device(self)?; - let stage_err = - |error| pipeline::CreateRenderPipelineError::Stage { stage, error }; + let stage_err = |error| pipeline::CreateRenderPipelineError::Stage { + stage: stage_bit, + error, + }; _task_entry_point_name = task_shader_module .finalize_entry_point_name( - stage, + stage.to_naga(), stage_desc.entry_point.as_ref().map(|ep| ep.as_ref()), ) .map_err(stage_err)?; @@ -4294,10 +4301,9 @@ impl Device { &_task_entry_point_name, stage, io, - desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) .map_err(stage_err)?; - validated_stages |= stage; + validated_stages |= stage_bit; } Some(hal::ProgrammableStage { module: task_shader_module.raw(), @@ -4311,16 +4317,19 @@ impl Device { }; mesh_stage = { let stage_desc = &mesh.stage; - let stage = wgt::ShaderStages::MESH; + let stage = validation::ShaderStageForValidation::Mesh; + let stage_bit = stage.to_wgt_bit(); let mesh_shader_module = &stage_desc.module; mesh_shader_module.same_device(self)?; - let stage_err = - |error| pipeline::CreateRenderPipelineError::Stage { stage, error }; + let stage_err = |error| pipeline::CreateRenderPipelineError::Stage { + stage: stage_bit, + error, + }; _mesh_entry_point_name = mesh_shader_module .finalize_entry_point_name( - stage, + stage.to_naga(), stage_desc.entry_point.as_ref().map(|ep| ep.as_ref()), ) .map_err(stage_err)?; @@ -4333,10 +4342,9 @@ impl Device { &_mesh_entry_point_name, stage, io, - desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) .map_err(stage_err)?; - validated_stages |= stage; + validated_stages |= stage_bit; } Some(hal::ProgrammableStage { module: mesh_shader_module.raw(), @@ -4352,16 +4360,20 @@ impl Device { let fragment_entry_point_name; let fragment_stage = match desc.fragment { Some(ref fragment_state) => { - let stage = wgt::ShaderStages::FRAGMENT; + let stage = validation::ShaderStageForValidation::Fragment; + let stage_bit = stage.to_wgt_bit(); let shader_module = &fragment_state.stage.module; shader_module.same_device(self)?; - let stage_err = |error| pipeline::CreateRenderPipelineError::Stage { stage, error }; + let stage_err = |error| pipeline::CreateRenderPipelineError::Stage { + stage: stage_bit, + error, + }; fragment_entry_point_name = shader_module .finalize_entry_point_name( - stage, + stage.to_naga(), fragment_state .stage .entry_point @@ -4378,17 +4390,16 @@ impl Device { &fragment_entry_point_name, stage, io, - desc.depth_stencil.as_ref().map(|d| d.depth_compare), ) .map_err(stage_err)?; - validated_stages |= stage; + validated_stages |= stage_bit; } if let Some(ref interface) = shader_module.interface { shader_expects_dual_source_blending = interface .fragment_uses_dual_source_blending(&fragment_entry_point_name) .map_err(|error| pipeline::CreateRenderPipelineError::Stage { - stage, + stage: stage_bit, error, })?; } diff --git a/wgpu-core/src/pipeline.rs b/wgpu-core/src/pipeline.rs index ee5bcb0798..00e400c3f7 100644 --- a/wgpu-core/src/pipeline.rs +++ b/wgpu-core/src/pipeline.rs @@ -89,11 +89,11 @@ impl ShaderModule { pub(crate) fn finalize_entry_point_name( &self, - stage_bit: wgt::ShaderStages, + stage: naga::ShaderStage, entry_point: Option<&str>, ) -> Result { match &self.interface { - Some(interface) => interface.finalize_entry_point_name(stage_bit, entry_point), + Some(interface) => interface.finalize_entry_point_name(stage, entry_point), None => entry_point .map(|ep| ep.to_string()) .ok_or(validation::StageError::NoEntryPointFound), diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index 72661a53ce..11e68182ae 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -7,13 +7,19 @@ use core::fmt; use arrayvec::ArrayVec; use hashbrown::hash_map::Entry; +use shader_io_deductions::{display_deductions_as_optional_list, MaxVertexShaderOutputDeduction}; use thiserror::Error; use wgt::{ error::{ErrorType, WebGpuError}, BindGroupLayoutEntry, BindingType, }; -use crate::{device::bgl, resource::InvalidResourceError, FastHashMap, FastHashSet}; +use crate::{ + device::bgl, resource::InvalidResourceError, + validation::shader_io_deductions::MaxFragmentShaderInputDeduction, FastHashMap, FastHashSet, +}; + +pub mod shader_io_deductions; #[derive(Debug)] enum ResourceType { @@ -296,8 +302,6 @@ pub enum StageError { per_dimension_limit: &'static str, total_limit: &'static str, }, - #[error("Shader uses {used} inter-stage components above the limit of {limit}")] - TooManyVaryings { used: u32, limit: u32 }, #[error("Unable to find entry point '{0}'")] MissingEntryPoint(String), #[error("Shader global {0:?} is not available in the pipeline layout")] @@ -328,6 +332,52 @@ pub enum StageError { MultipleEntryPointsFound, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), + #[error( + "Location[{location}]: {var}'s vertex shader output index exceeds the \ + `max_inter_stage_shader_variables` limit ({}){}", + // NOTE: Remember: the limit is 0-based for indices. + limit - 1, + display_deductions_as_optional_list(deductions, |d| d.for_location()) + )] + VertexOutputLocationTooLarge { + location: u32, + var: InterfaceVar, + limit: u32, + deductions: Vec, + }, + #[error( + "found {num_found} user-defined vertex shader output variables, which exceeds the \ + `max_inter_stage_shader_variables` limit ({limit}){}", + display_deductions_as_optional_list(deductions, |d| d.for_variables()) + )] + TooManyUserDefinedVertexOutputs { + num_found: u32, + limit: u32, + deductions: Vec, + }, + #[error( + "Location[{location}]: {var}'s fragment shader input index exceeds the \ + `max_inter_stage_shader_variables` limit ({}){}", + // NOTE: Remember: the limit is 0-based for indices. + limit - 1, + display_deductions_as_optional_list(deductions, |d| d.for_variables()) + )] + FragmentInputLocationTooLarge { + location: u32, + var: InterfaceVar, + limit: u32, + deductions: Vec, + }, + #[error( + "found {num_found} user-defined fragment shader input variables, which exceeds the \ + `max_inter_stage_shader_variables` limit ({limit}){}", + display_deductions_as_optional_list(deductions, |d| d.for_variables()) + )] + TooManyUserDefinedFragmentInputs { + num_found: u32, + limit: u32, + deductions: Vec, + }, #[error( "Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})" )] @@ -375,6 +425,10 @@ impl WebGpuError for StageError { | Self::MissingEntryPoint(..) | Self::NoEntryPointFound | Self::MultipleEntryPointsFound + | Self::VertexOutputLocationTooLarge { .. } + | Self::TooManyUserDefinedVertexOutputs { .. } + | Self::FragmentInputLocationTooLarge { .. } + | Self::TooManyUserDefinedFragmentInputs { .. } | Self::ColorAttachmentLocationTooLarge { .. } | Self::TooManyMeshVertices { .. } | Self::TooManyMeshPrimitives { .. } @@ -1148,10 +1202,9 @@ impl Interface { pub fn finalize_entry_point_name( &self, - stage_bit: wgt::ShaderStages, + stage: naga::ShaderStage, entry_point_name: Option<&str>, ) -> Result { - let stage = Self::shader_stage_from_stage_bit(stage_bit); entry_point_name .map(|ep| ep.to_string()) .map(Ok) @@ -1168,36 +1221,25 @@ impl Interface { }) } - pub(crate) fn shader_stage_from_stage_bit(stage_bit: wgt::ShaderStages) -> naga::ShaderStage { - match stage_bit { - wgt::ShaderStages::VERTEX => naga::ShaderStage::Vertex, - wgt::ShaderStages::FRAGMENT => naga::ShaderStage::Fragment, - wgt::ShaderStages::COMPUTE => naga::ShaderStage::Compute, - wgt::ShaderStages::MESH => naga::ShaderStage::Mesh, - wgt::ShaderStages::TASK => naga::ShaderStage::Task, - _ => unreachable!(), - } - } - pub fn check_stage( &self, layouts: &mut BindingLayoutSource<'_>, shader_binding_sizes: &mut FastHashMap, entry_point_name: &str, - stage_bit: wgt::ShaderStages, + shader_stage: ShaderStageForValidation, inputs: StageIo, - compare_function: Option, ) -> Result { // Since a shader module can have multiple entry points with the same name, // we need to look for one with the right execution model. - let shader_stage = Self::shader_stage_from_stage_bit(stage_bit); - let pair = (shader_stage, entry_point_name.to_string()); + let pair = (shader_stage.to_naga(), entry_point_name.to_string()); let entry_point = match self.entry_points.get(&pair) { Some(some) => some, None => return Err(StageError::MissingEntryPoint(pair.1)), }; let (_, entry_point_name) = pair; + let stage_bit = shader_stage.to_wgt_bit(); + // check resources visibility for &handle in entry_point.resources.iter() { let res = &self.resources[handle]; @@ -1319,13 +1361,13 @@ impl Interface { } // check workgroup size limits - if shader_stage.compute_like() { + if shader_stage.to_naga().compute_like() { let ( max_workgroup_size_limits, max_workgroup_size_total, per_dimension_limit, total_limit, - ) = match shader_stage { + ) = match shader_stage.to_naga() { naga::ShaderStage::Compute => ( [ self.limits.max_compute_workgroup_size_x, @@ -1391,7 +1433,7 @@ impl Interface { .ok_or(InputError::Missing) .and_then(|provided| { let (compatible, num_components, per_primitive_correct) = - match shader_stage { + match shader_stage.to_naga() { // For vertex attributes, there are defaults filled out // by the driver if data is not provided. naga::ShaderStage::Vertex => { @@ -1456,12 +1498,48 @@ impl Interface { } match shader_stage { - naga::ShaderStage::Vertex => { + ShaderStageForValidation::Vertex { + topology, + compare_function, + } => { + let mut max_vertex_shader_output_variables = + self.limits.max_inter_stage_shader_variables; + let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1; + + let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList { + Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology) + } else { + None + }; + + let deductions = point_list_deduction.into_iter(); + + for deduction in deductions.clone() { + max_vertex_shader_output_variables = max_vertex_shader_output_variables + .checked_sub(deduction.for_variables()) + .unwrap(); + max_vertex_shader_output_location = max_vertex_shader_output_location + .checked_sub(deduction.for_location()) + .unwrap(); + } + + let mut num_user_defined_outputs = 0; + for output in entry_point.outputs.iter() { - //TODO: count builtins towards the limit? - inter_stage_components += match *output { - Varying::Local { ref iv, .. } => iv.ty.dim.num_components(), - Varying::BuiltIn(_) => 0, + match *output { + Varying::Local { ref iv, location } => { + if location > max_vertex_shader_output_location { + return Err(StageError::VertexOutputLocationTooLarge { + location, + var: iv.clone(), + limit: self.limits.max_inter_stage_shader_variables, + deductions: deductions.collect(), + }); + } + num_user_defined_outputs += 1; + inter_stage_components += iv.ty.dim.num_components() + } + Varying::BuiltIn(_) => {} }; if let Some( @@ -1488,8 +1566,70 @@ impl Interface { } } } + + if num_user_defined_outputs > max_vertex_shader_output_variables { + return Err(StageError::TooManyUserDefinedVertexOutputs { + num_found: num_user_defined_outputs, + limit: self.limits.max_inter_stage_shader_variables, + deductions: deductions.collect(), + }); + } } - naga::ShaderStage::Fragment => { + ShaderStageForValidation::Fragment => { + let mut max_fragment_shader_input_variables = + self.limits.max_inter_stage_shader_variables; + + let deductions = entry_point.inputs.iter().filter_map(|output| match output { + Varying::Local { .. } => None, + Varying::BuiltIn(builtin) => { + MaxFragmentShaderInputDeduction::from_inter_stage_builtin(*builtin).or_else( + || { + unreachable!( + concat!( + "unexpected built-in provided; ", + "{:?} is not used for fragment stage input", + ), + builtin + ) + }, + ) + } + }); + + for deduction in deductions.clone() { + max_fragment_shader_input_variables = max_fragment_shader_input_variables + .checked_sub(deduction.for_variables()) + .unwrap(); + } + + let mut num_user_defined_inputs = 0; + + for output in entry_point.inputs.iter() { + match *output { + Varying::Local { ref iv, location } => { + if location >= max_fragment_shader_input_variables { + return Err(StageError::FragmentInputLocationTooLarge { + location, + var: iv.clone(), + limit: self.limits.max_inter_stage_shader_variables, + deductions: deductions.collect(), + }); + } + num_user_defined_inputs += 1; + inter_stage_components += iv.ty.dim.num_components() + } + Varying::BuiltIn(_) => {} + }; + } + + if num_user_defined_inputs > max_fragment_shader_input_variables { + return Err(StageError::TooManyUserDefinedFragmentInputs { + num_found: num_user_defined_inputs, + limit: self.limits.max_inter_stage_shader_variables, + deductions: deductions.collect(), + }); + } + for output in &entry_point.outputs { let &Varying::Local { location, ref iv } = output else { continue; @@ -1506,13 +1646,6 @@ impl Interface { _ => (), } - if inter_stage_components > self.limits.max_inter_stage_shader_components { - return Err(StageError::TooManyVaryings { - used: inter_stage_components, - limit: self.limits.max_inter_stage_shader_components, - }); - } - if let Some(ref mesh_info) = entry_point.mesh_info { if mesh_info.max_vertices > self.limits.max_mesh_output_vertices { return Err(StageError::TooManyMeshVertices { @@ -1535,7 +1668,7 @@ impl Interface { }); } } - if shader_stage == naga::ShaderStage::Mesh + if shader_stage.to_naga() == naga::ShaderStage::Mesh && entry_point.task_payload_size != inputs.task_payload_size { return Err(StageError::TaskPayloadMustMatch { @@ -1545,18 +1678,18 @@ impl Interface { } // Fragment shader primitive index is treated like a varying - if shader_stage == naga::ShaderStage::Fragment + if shader_stage.to_naga() == naga::ShaderStage::Fragment && this_stage_primitive_index && inputs.primitive_index == Some(false) { return Err(StageError::InvalidPrimitiveIndex); - } else if shader_stage == naga::ShaderStage::Fragment + } else if shader_stage.to_naga() == naga::ShaderStage::Fragment && !this_stage_primitive_index && inputs.primitive_index == Some(true) { return Err(StageError::MissingPrimitiveIndex); } - if shader_stage == naga::ShaderStage::Mesh + if shader_stage.to_naga() == naga::ShaderStage::Mesh && inputs.task_payload_size.is_some() && has_draw_id { @@ -1575,7 +1708,7 @@ impl Interface { Ok(StageIo { task_payload_size: entry_point.task_payload_size, varyings: outputs, - primitive_index: if shader_stage == naga::ShaderStage::Mesh { + primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh { Some(this_stage_primitive_index) } else { None @@ -1624,3 +1757,37 @@ pub fn validate_color_attachment_bytes_per_sample( Ok(()) } + +pub enum ShaderStageForValidation { + Vertex { + topology: wgt::PrimitiveTopology, + compare_function: Option, + }, + Mesh, + Fragment, + Compute, + Task, + // TODO: preserve ordering? +} + +impl ShaderStageForValidation { + pub fn to_naga(&self) -> naga::ShaderStage { + match self { + Self::Vertex { .. } => naga::ShaderStage::Vertex, + Self::Mesh { .. } => naga::ShaderStage::Mesh, + Self::Fragment { .. } => naga::ShaderStage::Fragment, + Self::Compute => naga::ShaderStage::Compute, + Self::Task => naga::ShaderStage::Task, + } + } + + pub fn to_wgt_bit(&self) -> wgt::ShaderStages { + match self { + Self::Vertex { .. } => wgt::ShaderStages::VERTEX, + Self::Mesh { .. } => wgt::ShaderStages::MESH, + Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT, + Self::Compute => wgt::ShaderStages::COMPUTE, + Self::Task => wgt::ShaderStages::TASK, + } + } +} diff --git a/wgpu-core/src/validation/shader_io_deductions.rs b/wgpu-core/src/validation/shader_io_deductions.rs new file mode 100644 index 0000000000..54f38c2a79 --- /dev/null +++ b/wgpu-core/src/validation/shader_io_deductions.rs @@ -0,0 +1,142 @@ +use core::fmt::{self, Debug, Display, Formatter}; + +#[cfg(doc)] +#[expect(unused_imports)] +use crate::validation::StageError; + +/// A [`ShaderIoDeduction`] for vertex shader output. Used by +/// [`StageError::TooManyUserDefinedVertexOutputs`] and +/// [`StageError::VertexOutputLocationTooLarge`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MaxVertexShaderOutputDeduction { + /// When a pipeline's [`crate::pipeline::RenderPipelineDescriptor::primitive`] is set to + /// [`wgt::PrimitiveTopology::PointList`]. + PointListPrimitiveTopology, +} + +impl MaxVertexShaderOutputDeduction { + pub fn for_variables(self) -> u32 { + match self { + Self::PointListPrimitiveTopology => 1, + } + } + + pub fn for_location(self) -> u32 { + match self { + Self::PointListPrimitiveTopology => 0, + } + } +} + +/// A [`ShaderIoDeduction`] for vertex shader output. Used by TODO. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MaxFragmentShaderInputDeduction { + InterStageBuiltIn(InterStageBuiltIn), +} + +impl MaxFragmentShaderInputDeduction { + pub fn for_variables(self) -> u32 { + match self { + Self::InterStageBuiltIn(_builtin) => 1, + } + } + + pub fn from_inter_stage_builtin(builtin: naga::BuiltIn) -> Option { + use naga::BuiltIn; + + Some(Self::InterStageBuiltIn(match builtin { + BuiltIn::FrontFacing => InterStageBuiltIn::FrontFacing, + BuiltIn::SampleIndex => InterStageBuiltIn::SampleIndex, + BuiltIn::SampleMask => InterStageBuiltIn::SampleMask, + BuiltIn::PrimitiveIndex => InterStageBuiltIn::PrimitiveIndex, + BuiltIn::SubgroupSize => InterStageBuiltIn::SubgroupSize, + BuiltIn::SubgroupInvocationId => InterStageBuiltIn::SubgroupInvocationId, + + BuiltIn::Position { .. } => InterStageBuiltIn::Position, + BuiltIn::ViewIndex => InterStageBuiltIn::ViewIndex, + + BuiltIn::BaseInstance + | BuiltIn::BaseVertex + | BuiltIn::ClipDistance + | BuiltIn::CullDistance + | BuiltIn::InstanceIndex + | BuiltIn::PointSize + | BuiltIn::VertexIndex + | BuiltIn::DrawID + | BuiltIn::FragDepth + | BuiltIn::PointCoord + | BuiltIn::Barycentric + | BuiltIn::GlobalInvocationId + | BuiltIn::LocalInvocationId + | BuiltIn::LocalInvocationIndex + | BuiltIn::WorkGroupId + | BuiltIn::WorkGroupSize + | BuiltIn::NumWorkGroups + | BuiltIn::NumSubgroups + | BuiltIn::SubgroupId + | BuiltIn::MeshTaskSize + | BuiltIn::CullPrimitive + | BuiltIn::PointIndex + | BuiltIn::LineIndices + | BuiltIn::TriangleIndices + | BuiltIn::VertexCount + | BuiltIn::Vertices + | BuiltIn::PrimitiveCount + | BuiltIn::Primitives => return None, + })) + } +} + +/// A [`naga::BuiltIn`] that counts towards +/// a [`MaxFragmentShaderInputDeduction::InterStageBuiltIn`]. +/// +/// See also . +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum InterStageBuiltIn { + // Standard for WebGPU + FrontFacing, + SampleIndex, + SampleMask, + PrimitiveIndex, + SubgroupInvocationId, + SubgroupSize, + + // Non-standard + // TODO: Is this list actually good? + Position, + ViewIndex, +} + +pub(in crate::validation) fn display_deductions_as_optional_list( + deductions: &[T], + accessor: fn(&T) -> u32, +) -> impl Display + '_ { + struct DisplayFromFn(F); + + impl Display for DisplayFromFn + where + F: Fn(&mut Formatter<'_>) -> fmt::Result, + { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let Self(inner) = self; + inner(f) + } + } + + DisplayFromFn(move |f: &mut Formatter<'_>| { + let relevant_deductions = deductions + .iter() + .map(|deduction| (deduction, accessor(deduction))) + .filter(|(_, effective_deduction)| *effective_deduction > 0); + if relevant_deductions.clone().next().is_some() { + writeln!(f, "; note that some deductions apply during validation:")?; + for deduction in deductions { + let deduction = accessor(deduction); + if deduction > 0 { + writeln!(f, "\n- {deduction:?}: {}", deduction)?; + } + } + } + Ok(()) + }) +} diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index d1389612df..c837c7bb96 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -806,6 +806,7 @@ impl super::Adapter { // // Source: https://learn.microsoft.com/en-us/windows/win32/direct3d12/root-signature-limits#memory-limits-and-costs max_immediate_size: 128, + max_inter_stage_shader_variables: 16, min_uniform_buffer_offset_alignment: Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, min_storage_buffer_offset_alignment: 4, diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index c94365a7d0..978f8dca35 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -753,6 +753,7 @@ impl super::Adapter { !0 }, max_immediate_size: super::MAX_IMMEDIATES as u32 * 4, + max_inter_stage_shader_variables: 16, min_uniform_buffer_offset_alignment, min_storage_buffer_offset_alignment, max_inter_stage_shader_components: { diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 7f49dc9cb6..ed400ad4ab 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -1182,6 +1182,7 @@ impl super::PrivateCapabilities { max_vertex_attributes: 31, max_vertex_buffer_array_stride: base.max_vertex_buffer_array_stride, max_immediate_size: 0x1000, + max_inter_stage_shader_variables: 16, min_uniform_buffer_offset_alignment: self.buffer_alignment as u32, min_storage_buffer_offset_alignment: self.buffer_alignment as u32, max_inter_stage_shader_components: self.max_varying_components, diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index a6085aa43c..ead72ade48 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -181,6 +181,7 @@ pub const CAPABILITIES: crate::Capabilities = { max_buffer_size: ALLOC_MAX_U32 as u64, max_vertex_attributes: ALLOC_MAX_U32, max_vertex_buffer_array_stride: ALLOC_MAX_U32, + max_inter_stage_shader_variables: ALLOC_MAX_U32, min_uniform_buffer_offset_alignment: 1, min_storage_buffer_offset_alignment: 1, max_inter_stage_shader_components: ALLOC_MAX_U32, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 1519e9d896..882048d6d4 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1382,6 +1382,7 @@ impl PhysicalDeviceProperties { max_vertex_attributes: limits.max_vertex_input_attributes, max_vertex_buffer_array_stride: limits.max_vertex_input_binding_stride, max_immediate_size: limits.max_push_constants_size, + max_inter_stage_shader_variables: 16, min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment as u32, min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment as u32, max_inter_stage_shader_components: limits diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index b68225b170..9cb294de8a 100644 --- a/wgpu-info/src/human.rs +++ b/wgpu-info/src/human.rs @@ -164,9 +164,9 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize max_buffer_size, max_vertex_attributes, max_vertex_buffer_array_stride, + max_inter_stage_shader_variables, min_uniform_buffer_offset_alignment, min_storage_buffer_offset_alignment, - max_inter_stage_shader_components, max_color_attachments, max_color_attachment_bytes_per_sample, max_compute_workgroup_storage_size, @@ -219,9 +219,9 @@ fn print_adapter(output: &mut impl io::Write, report: &AdapterReport, idx: usize writeln!(output, "\t\t Max Vertex Attributes: {max_vertex_attributes}")?; writeln!(output, "\t\t Max Vertex Buffer Array Stride: {max_vertex_buffer_array_stride}")?; writeln!(output, "\t\t Max Immediate data Size: {max_immediate_size}")?; + writeln!(output, "\t\t Max Inter-stage Shader Variables: {max_inter_stage_shader_variables}")?; writeln!(output, "\t\t Min Uniform Buffer Offset Alignment: {min_uniform_buffer_offset_alignment}")?; writeln!(output, "\t\t Min Storage Buffer Offset Alignment: {min_storage_buffer_offset_alignment}")?; - writeln!(output, "\t\t Max Inter-Stage Shader Component: {max_inter_stage_shader_components}")?; writeln!(output, "\t\t Max Color Attachments: {max_color_attachments}")?; writeln!(output, "\t\t Max Color Attachment Bytes per sample: {max_color_attachment_bytes_per_sample}")?; writeln!(output, "\t\t Max Compute Workgroup Storage Size: {max_compute_workgroup_storage_size}")?; diff --git a/wgpu-types/src/limits.rs b/wgpu-types/src/limits.rs index b50b85b49d..752a3ac666 100644 --- a/wgpu-types/src/limits.rs +++ b/wgpu-types/src/limits.rs @@ -46,7 +46,6 @@ macro_rules! with_limits { $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); @@ -180,6 +179,11 @@ pub struct Limits { /// Maximum value for `VertexBufferLayout::array_stride` when creating a `RenderPipeline`. /// Defaults to 2048. Higher is "better". pub max_vertex_buffer_array_stride: u32, + /// Maximum value for the number of input or output variables for inter-stage communication + /// (like vertex outputs or fragment inputs) `@location(…)`s (in WGSL parlance) + /// when creating a `RenderPipeline`. + /// Defaults to 16. Higher is "better". + pub max_inter_stage_shader_variables: u32, /// Required `BufferBindingType::Uniform` alignment for `BufferBinding::offset` /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. /// Defaults to 256. Lower is "better". @@ -188,10 +192,6 @@ pub struct Limits { /// when creating a `BindGroup`, or for `set_bind_group` `dynamicOffsets`. /// Defaults to 256. Lower is "better". pub min_storage_buffer_offset_alignment: u32, - /// Maximum allowed number of components (scalars) of input or output locations for - /// inter-stage communication (vertex outputs to fragment inputs). Defaults to 60. - /// Higher is "better". - pub max_inter_stage_shader_components: u32, /// The maximum allowed number of color attachments. pub max_color_attachments: u32, /// The maximum number of bytes necessary to hold one sample (pixel or subpixel) of render @@ -325,9 +325,9 @@ impl Limits { /// max_buffer_size: 256 << 20, // (256 MiB) /// max_vertex_attributes: 16, /// max_vertex_buffer_array_stride: 2048, + /// max_inter_stage_shader_variables: 16, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, /// max_color_attachments: 8, /// max_color_attachment_bytes_per_sample: 32, /// max_compute_workgroup_storage_size: 16384, @@ -383,9 +383,9 @@ impl Limits { max_buffer_size: 256 << 20, // (256 MiB) max_vertex_attributes: 16, max_vertex_buffer_array_stride: 2048, + max_inter_stage_shader_variables: 16, min_uniform_buffer_offset_alignment: 256, min_storage_buffer_offset_alignment: 256, - max_inter_stage_shader_components: 60, max_color_attachments: 8, max_color_attachment_bytes_per_sample: 32, max_compute_workgroup_storage_size: 16384, @@ -445,9 +445,9 @@ impl Limits { /// max_vertex_attributes: 16, /// max_vertex_buffer_array_stride: 2048, /// max_immediate_size: 0, + /// max_inter_stage_shader_variables: 16, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, /// max_color_attachments: 4, /// max_color_attachment_bytes_per_sample: 32, /// max_compute_workgroup_storage_size: 16352, // * @@ -522,9 +522,9 @@ impl Limits { /// max_vertex_attributes: 16, /// max_vertex_buffer_array_stride: 255, // + /// max_immediate_size: 0, + /// max_inter_stage_shader_variables: 16, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 31, /// max_color_attachments: 4, /// max_color_attachment_bytes_per_sample: 32, /// max_compute_workgroup_storage_size: 0, // + @@ -572,8 +572,7 @@ impl Limits { max_compute_workgroup_size_z: 0, max_compute_workgroups_per_dimension: 0, - // Value supported by Intel Celeron B830 on Windows (OpenGL 3.1) - max_inter_stage_shader_components: 31, + max_inter_stage_shader_variables: 15, // Most of the values should be the same as the downlevel defaults ..Self::downlevel_defaults() diff --git a/wgpu/src/backend/webgpu.rs b/wgpu/src/backend/webgpu.rs index 337e9ed407..6a02b05847 100644 --- a/wgpu/src/backend/webgpu.rs +++ b/wgpu/src/backend/webgpu.rs @@ -808,6 +808,7 @@ fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits { max_buffer_size: limits.max_buffer_size() as u64, max_vertex_attributes: limits.max_vertex_attributes(), max_vertex_buffer_array_stride: limits.max_vertex_buffer_array_stride(), + max_inter_stage_shader_variables: limits.max_inter_stage_shader_variables(), min_uniform_buffer_offset_alignment: limits.min_uniform_buffer_offset_alignment(), min_storage_buffer_offset_alignment: limits.min_storage_buffer_offset_alignment(), max_color_attachments: limits.max_color_attachments(),