diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a428b381a4..794e74ed950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ Bottom level categories: #### General +- BREAKING: Migrated from the `maxInterStageShaderComponents` limit to `maxInterStageShaderVariables`, which changes validation in a way that should not affect most programs. This follows the latest changes of the WebGPU spec. By @ErichDonGubler in [#8652](https://github.com/gfx-rs/wgpu/pull/8652). - Fixed validation of the texture format in GPUDepthStencilState when neither depth nor stencil is actually enabled. By @andyleiserson in [#8766](https://github.com/gfx-rs/wgpu/pull/8766). ### Documentation diff --git a/cts_runner/test.lst b/cts_runner/test.lst index bf25bb8979f..51427e778e3 100644 --- a/cts_runner/test.lst +++ b/cts_runner/test.lst @@ -33,6 +33,31 @@ 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,* +// NOTE: Only test some of these `maxInterStageShaderVariables` cases, because of a CTS bug (see +// below). CTS (incorrectly) only deducts once for any set of them being enabled, but it should +// deduct for _each_ built-in. +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=false;frontFacing=true;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=false;frontFacing=false;sampleIndex=true;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=true;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=true +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=true;frontFacing=true;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=true;frontFacing=false;sampleIndex=true;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=true;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=false;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=true +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=false;frontFacing=true;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=false;frontFacing=false;sampleIndex=true;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=true;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=false;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=true +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=true;frontFacing=true;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=true;frontFacing=false;sampleIndex=true;sampleMaskIn=false;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=true;sampleMaskOut=false +webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:limitTest="atDefault";testValueName="atLimit";async=true;pointList=true;frontFacing=false;sampleIndex=false;sampleMaskIn=false;sampleMaskOut=true +//FAIL: webgpu:api,validation,capability_checks,limits,maxInterStageShaderVariables:createRenderPipeline,at_over:* +// https://github.com/gpuweb/cts/issues/4538 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:* @@ -137,6 +162,9 @@ webgpu:api,validation,render_pass,render_pass_descriptor:resolveTarget,* webgpu:api,validation,render_pass,resolve:resolve_attachment:* webgpu:api,validation,render_pipeline,depth_stencil_state:format:* webgpu:api,validation,render_pipeline,depth_stencil_state:stencil_write:* +webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:isAsync=false;* +//FAIL: webgpu:api,validation,render_pipeline,inter_stage:max_shader_variable_location:isAsync=true;* +// https://github.com/gfx-rs/wgpu/pull/8712 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 5f09cf3afdb..d0a1c704c5b 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 42d24d88cab..d73fec74ea3 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 ea730cb1e20..3a610381e8b 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, )?; } } @@ -4238,17 +4237,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)?; @@ -4261,10 +4266,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(), @@ -4280,16 +4284,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)?; @@ -4302,10 +4309,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(), @@ -4319,16 +4325,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)?; @@ -4341,10 +4350,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(), @@ -4360,16 +4368,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 @@ -4386,17 +4398,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 8e73d564eda..9395d593350 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 72661a53ce5..2955f749114 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 { @@ -96,16 +102,6 @@ impl fmt::Display for NumericDimension { } } -impl NumericDimension { - fn num_components(&self) -> u32 { - match *self { - Self::Scalar => 1, - Self::Vector(size) => size as u32, - Self::Matrix(w, h) => w as u32 * h as u32, - } - } -} - #[derive(Clone, Copy, Debug)] pub struct NumericType { dim: NumericDimension, @@ -296,8 +292,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 +322,55 @@ pub enum StageError { MultipleEntryPointsFound, #[error(transparent)] InvalidResource(#[from] InvalidResourceError), + #[error( + "vertex shader output location Location[{location}] ({var}) exceeds the \ + `max_inter_stage_shader_variables` limit ({}, 0-based){}", + // 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( + "fragment shader input location Location[{location}] ({var}) exceeds the \ + `max_inter_stage_shader_variables` limit ({}, 0-based){}", + // NOTE: Remember: the limit is 0-based for indices. + limit - 1, + // NOTE: WebGPU spec. validation for fragment inputs is expressed in terms of variables + // (unlike vertex outputs), so we use `MaxFragmentShaderInputDeduction::for_variables` here + // (and not a non-existent `for_locations`). + 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})" )] @@ -371,10 +414,13 @@ impl WebGpuError for StageError { error, } => error, Self::InvalidWorkgroupSize { .. } - | Self::TooManyVaryings { .. } | Self::MissingEntryPoint(..) | Self::NoEntryPointFound | Self::MultipleEntryPointsFound + | Self::VertexOutputLocationTooLarge { .. } + | Self::TooManyUserDefinedVertexOutputs { .. } + | Self::FragmentInputLocationTooLarge { .. } + | Self::TooManyUserDefinedFragmentInputs { .. } | Self::ColorAttachmentLocationTooLarge { .. } | Self::TooManyMeshVertices { .. } | Self::TooManyMeshPrimitives { .. } @@ -1148,10 +1194,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 +1213,27 @@ 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!(), - } - } - + /// Among other things, this implements some validation logic defined by the WebGPU spec. at + /// . 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 +1355,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, @@ -1377,7 +1413,6 @@ impl Interface { } } - let mut inter_stage_components = 0; let mut this_stage_primitive_index = false; let mut has_draw_id = false; @@ -1390,38 +1425,36 @@ impl Interface { .get(&location) .ok_or(InputError::Missing) .and_then(|provided| { - let (compatible, num_components, per_primitive_correct) = - match shader_stage { - // For vertex attributes, there are defaults filled out - // by the driver if data is not provided. - naga::ShaderStage::Vertex => { - let is_compatible = - iv.ty.scalar.kind == provided.ty.scalar.kind; - // vertex inputs don't count towards inter-stage - (is_compatible, 0, !iv.per_primitive) + let (compatible, per_primitive_correct) = match shader_stage.to_naga() { + // For vertex attributes, there are defaults filled out + // by the driver if data is not provided. + naga::ShaderStage::Vertex => { + let is_compatible = + iv.ty.scalar.kind == provided.ty.scalar.kind; + // vertex inputs don't count towards inter-stage + (is_compatible, !iv.per_primitive) + } + naga::ShaderStage::Fragment => { + if iv.interpolation != provided.interpolation { + return Err(InputError::InterpolationMismatch( + provided.interpolation, + )); } - naga::ShaderStage::Fragment => { - if iv.interpolation != provided.interpolation { - return Err(InputError::InterpolationMismatch( - provided.interpolation, - )); - } - if iv.sampling != provided.sampling { - return Err(InputError::SamplingMismatch( - provided.sampling, - )); - } - ( - iv.ty.is_subtype_of(&provided.ty), - iv.ty.dim.num_components(), - iv.per_primitive == provided.per_primitive, - ) + if iv.sampling != provided.sampling { + return Err(InputError::SamplingMismatch( + provided.sampling, + )); } - // These can't have varying inputs - naga::ShaderStage::Compute - | naga::ShaderStage::Task - | naga::ShaderStage::Mesh => (false, 0, false), - }; + ( + iv.ty.is_subtype_of(&provided.ty), + iv.per_primitive == provided.per_primitive, + ) + } + // These can't have varying inputs + naga::ShaderStage::Compute + | naga::ShaderStage::Task + | naga::ShaderStage::Mesh => (false, false), + }; if !compatible { return Err(InputError::WrongType(provided.ty)); } else if !per_primitive_correct { @@ -1430,19 +1463,15 @@ impl Interface { shader: iv.per_primitive, }); } - Ok(num_components) + Ok(()) + }); + + if let Err(error) = result { + return Err(StageError::Input { + location, + var: iv.clone(), + error, }); - match result { - Ok(num_components) => { - inter_stage_components += num_components; - } - Err(error) => { - return Err(StageError::Input { - location, - var: iv.clone(), - error, - }) - } } } Varying::BuiltIn(naga::BuiltIn::PrimitiveIndex) => { @@ -1456,12 +1485,49 @@ 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() { + // NOTE: Deductions, in the current version of the spec. we implement, do not + // ever exceed the minimum variables available. + 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; + } + Varying::BuiltIn(_) => {} }; if let Some( @@ -1488,8 +1554,71 @@ 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() { + // NOTE: Deductions, in the current version of the spec. we implement, do not + // ever exceed the minimum variables available. + 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; + } + 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 +1635,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 +1657,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 +1667,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 +1697,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 +1746,36 @@ pub fn validate_color_attachment_bytes_per_sample( Ok(()) } + +pub enum ShaderStageForValidation { + Vertex { + topology: wgt::PrimitiveTopology, + compare_function: Option, + }, + Mesh, + Fragment, + Compute, + Task, +} + +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 00000000000..a7a64f86086 --- /dev/null +++ b/wgpu-core/src/validation/shader_io_deductions.rs @@ -0,0 +1,156 @@ +use core::fmt::{self, Debug, Display, Formatter}; + +#[cfg(doc)] +#[expect(unused_imports)] +use crate::validation::StageError; + +/// Max shader I/O variable deductions 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, + } + } +} + +/// Max shader I/O variable deductions for vertex shader output. Used by +/// [`StageError::TooManyUserDefinedFragmentInputs`] and +/// [`StageError::FragmentInputLocationTooLarge`]. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum MaxFragmentShaderInputDeduction { + InterStageBuiltIn(InterStageBuiltIn), +} + +impl MaxFragmentShaderInputDeduction { + pub fn for_variables(self) -> u32 { + match self { + Self::InterStageBuiltIn(builtin) => match builtin { + InterStageBuiltIn::FrontFacing + | InterStageBuiltIn::SampleIndex + | InterStageBuiltIn::SampleMask + | InterStageBuiltIn::PrimitiveIndex + | InterStageBuiltIn::SubgroupInvocationId + | InterStageBuiltIn::SubgroupSize + | InterStageBuiltIn::ViewIndex + | InterStageBuiltIn::PointCoord => 1, + InterStageBuiltIn::Barycentric => 3, + InterStageBuiltIn::Position => 4, + }, + } + } + + 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::PointCoord => InterStageBuiltIn::PointCoord, + BuiltIn::Barycentric => InterStageBuiltIn::Barycentric, + 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::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 + PointCoord, + Barycentric, + Position, + ViewIndex, +} + +pub(in crate::validation) fn display_deductions_as_optional_list( + deductions: &[T], + accessor: fn(&T) -> u32, +) -> impl Display + '_ +where + T: Debug, +{ + 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 { + writeln!(f, "\n- {deduction:?}: {}", accessor(deduction))?; + } + } + Ok(()) + }) +} diff --git a/wgpu-hal/src/dx12/adapter.rs b/wgpu-hal/src/dx12/adapter.rs index 36f30365be8..9d74e2e9663 100644 --- a/wgpu-hal/src/dx12/adapter.rs +++ b/wgpu-hal/src/dx12/adapter.rs @@ -809,7 +809,7 @@ impl super::Adapter { min_uniform_buffer_offset_alignment: Direct3D12::D3D12_CONSTANT_BUFFER_DATA_PLACEMENT_ALIGNMENT, min_storage_buffer_offset_alignment: 4, - max_inter_stage_shader_components: base.max_inter_stage_shader_components, + max_inter_stage_shader_variables: base.max_inter_stage_shader_variables, max_color_attachments, max_color_attachment_bytes_per_sample, // From: https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#18.6.6%20Inter-Thread%20Data%20Sharing diff --git a/wgpu-hal/src/gles/adapter.rs b/wgpu-hal/src/gles/adapter.rs index 971819092d6..026dc08fc15 100644 --- a/wgpu-hal/src/gles/adapter.rs +++ b/wgpu-hal/src/gles/adapter.rs @@ -755,17 +755,17 @@ impl super::Adapter { max_immediate_size: super::MAX_IMMEDIATES as u32 * 4, min_uniform_buffer_offset_alignment, min_storage_buffer_offset_alignment, - max_inter_stage_shader_components: { + max_inter_stage_shader_variables: { // MAX_VARYING_COMPONENTS may return 0, because it is deprecated since OpenGL 3.2 core, // and an OpenGL Context with the core profile and with forward-compatibility=true, // will make deprecated constants unavailable. let max_varying_components = unsafe { gl.get_parameter_i32(glow::MAX_VARYING_COMPONENTS) } as u32; if max_varying_components == 0 { - // default value for max_inter_stage_shader_components - 60 + // default value for max_inter_stage_shader_variables + 15 } else { - max_varying_components + max_varying_components / 4 } }, max_color_attachments, diff --git a/wgpu-hal/src/metal/adapter.rs b/wgpu-hal/src/metal/adapter.rs index 9ff5a04bd1b..fed8bab1685 100644 --- a/wgpu-hal/src/metal/adapter.rs +++ b/wgpu-hal/src/metal/adapter.rs @@ -1191,9 +1191,9 @@ 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: self.max_varying_components / 4, 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, max_color_attachments: self.max_color_render_targets as u32, max_color_attachment_bytes_per_sample: self.max_color_attachment_bytes_per_sample as u32, diff --git a/wgpu-hal/src/noop/mod.rs b/wgpu-hal/src/noop/mod.rs index 09b149f0997..06daa32d1d5 100644 --- a/wgpu-hal/src/noop/mod.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -181,9 +181,9 @@ 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, max_color_attachments: ALLOC_MAX_U32, max_color_attachment_bytes_per_sample: ALLOC_MAX_U32, max_compute_workgroup_storage_size: ALLOC_MAX_U32, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index 7ece600f503..796f660e34d 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -1431,11 +1431,12 @@ 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: limits + .max_vertex_output_components + .min(limits.max_fragment_input_components) + / 4, 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 - .max_vertex_output_components - .min(limits.max_fragment_input_components), max_color_attachments: limits.max_color_attachments, max_color_attachment_bytes_per_sample, max_compute_workgroup_storage_size: limits.max_compute_shared_memory_size, diff --git a/wgpu-info/src/human.rs b/wgpu-info/src/human.rs index 5145d5b83d0..48317f8d0a3 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 b50b85b49dc..5fbc6c31875 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, @@ -447,7 +447,7 @@ impl Limits { /// max_immediate_size: 0, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 60, + /// max_inter_stage_shader_variables: 15, /// max_color_attachments: 4, /// max_color_attachment_bytes_per_sample: 32, /// max_compute_workgroup_storage_size: 16352, // * @@ -487,6 +487,7 @@ impl Limits { max_texture_dimension_3d: 256, max_storage_buffers_per_shader_stage: 4, max_uniform_buffer_binding_size: 16 << 10, // (16 KiB) + max_inter_stage_shader_variables: 15, max_color_attachments: 4, // see: https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf#page=7 max_compute_workgroup_storage_size: 16352, @@ -524,7 +525,7 @@ impl Limits { /// max_immediate_size: 0, /// min_uniform_buffer_offset_alignment: 256, /// min_storage_buffer_offset_alignment: 256, - /// max_inter_stage_shader_components: 31, + /// max_inter_stage_shader_variables: 15, /// max_color_attachments: 4, /// max_color_attachment_bytes_per_sample: 32, /// max_compute_workgroup_storage_size: 0, // + @@ -573,7 +574,7 @@ impl Limits { 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 1fecb5f634f..f207660c61d 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(), @@ -820,7 +821,6 @@ fn map_wgt_limits(limits: webgpu_sys::GpuSupportedLimits) -> wgt::Limits { max_compute_workgroups_per_dimension: limits.max_compute_workgroups_per_dimension(), max_immediate_size: wgt::Limits::default().max_immediate_size, max_non_sampler_bindings: wgt::Limits::default().max_non_sampler_bindings, - max_inter_stage_shader_components: wgt::Limits::default().max_inter_stage_shader_components, max_task_mesh_workgroup_total_count: wgt::Limits::default() .max_task_mesh_workgroup_total_count, @@ -895,7 +895,7 @@ fn map_js_sys_limits(limits: &wgt::Limits) -> js_sys::Object { (maxBufferSize, max_buffer_size), (maxVertexAttributes, max_vertex_attributes), (maxVertexBufferArrayStride, max_vertex_buffer_array_stride), - // TODO: (maxInterStageShaderVariables, max_inter_stage_shader_variables), + (maxInterStageShaderVariables, max_inter_stage_shader_variables), (maxColorAttachments, max_color_attachments), (maxColorAttachmentBytesPerSample, max_color_attachment_bytes_per_sample), (maxComputeWorkgroupStorageSize, max_compute_workgroup_storage_size),