From 1c90d193f48a6968855562062236c6d907a744e7 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Thu, 14 Aug 2025 12:53:07 -0500 Subject: [PATCH 001/110] Initial commit --- docs/api-specs/mesh_shading.md | 32 ++-- naga-cli/src/bin/naga.rs | 25 +++ naga/src/back/dot/mod.rs | 19 +++ naga/src/back/glsl/features.rs | 1 + naga/src/back/glsl/mod.rs | 23 ++- naga/src/back/hlsl/conv.rs | 3 + naga/src/back/hlsl/mod.rs | 3 +- naga/src/back/hlsl/writer.rs | 19 ++- naga/src/back/msl/mod.rs | 5 + naga/src/back/msl/writer.rs | 20 ++- naga/src/back/pipeline_constants.rs | 45 ++++++ naga/src/back/wgsl/writer.rs | 5 +- naga/src/common/wgsl/to_wgsl.rs | 8 +- naga/src/compact/mod.rs | 56 +++++++ naga/src/compact/statements.rs | 34 ++++ naga/src/front/glsl/functions.rs | 4 + naga/src/front/glsl/mod.rs | 2 +- naga/src/front/glsl/variables.rs | 1 + naga/src/front/interpolator.rs | 1 + naga/src/front/spv/function.rs | 2 + naga/src/front/spv/mod.rs | 4 + naga/src/ir/mod.rs | 76 ++++++++- naga/src/proc/mod.rs | 3 + naga/src/proc/terminator.rs | 1 + naga/src/valid/analyzer.rs | 102 +++++++++++- naga/src/valid/function.rs | 42 +++++ naga/src/valid/handles.rs | 16 ++ naga/src/valid/interface.rs | 232 ++++++++++++++++++++++++++-- naga/src/valid/mod.rs | 2 + naga/src/valid/type.rs | 9 +- wgpu-core/src/validation.rs | 4 +- wgpu-hal/src/vulkan/adapter.rs | 3 + 32 files changed, 754 insertions(+), 48 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 8c979890b78..ee14f99e757 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -80,32 +80,36 @@ This shader stage can be selected by marking a function with `@task`. Task shade The output of this determines how many workgroups of mesh shaders will be dispatched. Once dispatched, global id variables will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup. -If task shaders are marked with `@payload(someVar)`, where `someVar` is global variable declared like `var someVar: `, task shaders may write to `someVar`. This payload is passed to the mesh shader workgroup that is invoked. The mesh shader can skip declaring `@payload` to ignore this input. +If task shaders are marked with `@payload(someVar)`, where `someVar` is global variable declared like `var someVar: `, task shaders may use `someVar` as if it is a read-write workgroup storage variable. This payload is passed to the mesh shader workgroup that is invoked. The mesh shader can skip declaring `@payload` to ignore this input. ### Mesh shader This shader stage can be selected by marking a function with `@mesh`. Mesh shaders must not return anything. -Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, mesh shaders cannot write to this workgroup memory. Declaring `@payload` in a pipeline with no task shader, in a pipeline with a task shader that doesn't declare `@payload`, or in a task shader with an `@payload` that is statically sized and smaller than the mesh shader payload is illegal. +Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, mesh shaders cannot write to this memory. Declaring `@payload` in a pipeline with no task shader, in a pipeline with a task shader that doesn't declare `@payload`, or in a task shader with an `@payload` that is statically sized and smaller than the mesh shader payload is illegal. -Mesh shaders must be marked with `@vertex_output(OutputType, numOutputs)`, where `numOutputs` is the maximum number of vertices to be output by a mesh shader, and `OutputType` is the data associated with vertices, similar to a standard vertex shader output. +Mesh shaders must be marked with `@vertex_output(OutputType, numOutputs)`, where `numOutputs` is the maximum number of vertices to be output by a mesh shader, and `OutputType` is the data associated with vertices, similar to a standard vertex shader output, and must be a struct. Mesh shaders must also be marked with `@primitive_output(OutputType, numOutputs)`, which is similar to `@vertex_output` except it describes the primitive outputs. ### Mesh shader outputs -Primitive outputs from mesh shaders have some additional builtins they can set. These include `@builtin(cull_primitive)`, which must be a boolean value. If this is set to true, then the primitive is skipped during rendering. +Vertex outputs from mesh shaders function identically to outputs of vertex shaders, and as such must have a field with `@builtin(position)`. + +Primitive outputs from mesh shaders have some additional builtins they can set. These include `@builtin(cull_primitive)`, which must be a boolean value. If this is set to true, then the primitive is skipped during rendering. All non-builtin primitive outputs must be decorated with `@per_primitive`. Mesh shader primitive outputs must also specify exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`. This determines the output topology of the mesh shader, and must match the output topology of the pipeline descriptor the mesh shader is used with. These must be of type `vec3`, `vec2`, and `u32` respectively. When setting this, each of the indices must be less than the number of vertices declared in `setMeshOutputs`. Additionally, the `@location` attributes from the vertex and primitive outputs can't overlap. -Before setting any vertices or indices, or exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly these numbers of vertices and primitives. +Before setting any vertices or indices, or exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly these numbers of vertices and primitives. A varying member with `@per_primitive` cannot be used in function interfaces except as the primitive output for mesh shaders or as input for fragment shaders. The mesh shader can write to vertices using the `setVertex(idx: u32, vertex: VertexOutput)` where `VertexOutput` is replaced with the vertex type declared in `@vertex_output`, and `idx` is the index of the vertex to write. Similarly, the mesh shader can write to vertices using `setPrimitive(idx: u32, primitive: PrimitiveOutput)`. These can be written to multiple times, however unsynchronized writes are undefined behavior. The primitives and indices are shared across the entire mesh shader workgroup. ### Fragment shader -Fragment shaders may now be passed the primitive info from a mesh shader the same was as they are passed vertex inputs, for example `fn fs_main(vertex: VertexOutput, primitive: PrimitiveOutput)`. The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. +Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` attribute can be applied to a value directly, such as `@per_primitive @location(1) value: vec4`, to a struct such as `@per_primitive primitive_input: PrimitiveInput` where `PrimitiveInput` is a struct containing fields decorated with `@location` and `@builtin`, or to members of a struct that are themselves decorated with `@location` or `@builtin`. + +The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. Using `@per_primitive` also requires enabling the mesh shader extension. Additionally, the locations of vertex and primitive input cannot overlap. ### Full example @@ -115,9 +119,9 @@ The following is a full example of WGSL shaders that could be used to create a m enable mesh_shading; const positions = array( - vec4(0.,-1.,0.,1.), - vec4(-1.,1.,0.,1.), - vec4(1.,1.,0.,1.) + vec4(0.,1.,0.,1.), + vec4(-1.,-1.,0.,1.), + vec4(1.,-1.,0.,1.) ); const colors = array( vec4(0.,1.,0.,1.), @@ -128,7 +132,7 @@ struct TaskPayload { colorMask: vec4, visible: bool, } -var taskPayload: TaskPayload; +var taskPayload: TaskPayload; var workgroupData: f32; struct VertexOutput { @builtin(position) position: vec4, @@ -137,14 +141,12 @@ struct VertexOutput { struct PrimitiveOutput { @builtin(triangle_indices) index: vec3, @builtin(cull_primitive) cull: bool, - @location(1) colorMask: vec4, + @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { - @location(1) colorMask: vec4, + @per_primitive @location(1) colorMask: vec4, } -fn test_function(input: u32) { -} @task @payload(taskPayload) @workgroup_size(1) @@ -163,8 +165,6 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati workgroupData = 2.0; var v: VertexOutput; - test_function(1); - v.position = positions[0]; v.color = colors[0] * taskPayload.colorMask; setVertex(0, v); diff --git a/naga-cli/src/bin/naga.rs b/naga-cli/src/bin/naga.rs index 44369e9df7d..171d970166e 100644 --- a/naga-cli/src/bin/naga.rs +++ b/naga-cli/src/bin/naga.rs @@ -64,6 +64,12 @@ struct Args { #[argh(option)] shader_model: Option, + /// the SPIR-V version to use if targeting SPIR-V + /// + /// For example, 1.0, 1.4, etc + #[argh(option)] + spirv_version: Option, + /// the shader stage, for example 'frag', 'vert', or 'compute'. /// if the shader stage is unspecified it will be derived from /// the file extension. @@ -189,6 +195,22 @@ impl FromStr for ShaderModelArg { } } +#[derive(Debug, Clone)] +struct SpirvVersionArg(u8, u8); + +impl FromStr for SpirvVersionArg { + type Err = String; + + fn from_str(s: &str) -> Result { + let dot = s + .find(".") + .ok_or_else(|| "Missing dot separator".to_owned())?; + let major = s[..dot].parse::().map_err(|e| e.to_string())?; + let minor = s[dot + 1..].parse::().map_err(|e| e.to_string())?; + Ok(Self(major, minor)) + } +} + /// Newtype so we can implement [`FromStr`] for `ShaderSource`. #[derive(Debug, Clone, Copy)] struct ShaderStage(naga::ShaderStage); @@ -465,6 +487,9 @@ fn run() -> anyhow::Result<()> { if let Some(ref version) = args.metal_version { params.msl.lang_version = version.0; } + if let Some(ref version) = args.spirv_version { + params.spv_out.lang_version = (version.0, version.1); + } params.keep_coordinate_space = args.keep_coordinate_space; params.dot.cfg_only = args.dot_cfg_only; diff --git a/naga/src/back/dot/mod.rs b/naga/src/back/dot/mod.rs index 826dad1c219..1f1396eccff 100644 --- a/naga/src/back/dot/mod.rs +++ b/naga/src/back/dot/mod.rs @@ -307,6 +307,25 @@ impl StatementGraph { crate::RayQueryFunction::Terminate => "RayQueryTerminate", } } + S::MeshFunction(crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + }) => { + self.dependencies.push((id, vertex_count, "vertex_count")); + self.dependencies + .push((id, primitive_count, "primitive_count")); + "SetMeshOutputs" + } + S::MeshFunction(crate::MeshFunction::SetVertex { index, value }) => { + self.dependencies.push((id, index, "index")); + self.dependencies.push((id, value, "value")); + "SetVertex" + } + S::MeshFunction(crate::MeshFunction::SetPrimitive { index, value }) => { + self.dependencies.push((id, index, "index")); + self.dependencies.push((id, value, "value")); + "SetPrimitive" + } S::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.dependencies.push((id, predicate, "predicate")); diff --git a/naga/src/back/glsl/features.rs b/naga/src/back/glsl/features.rs index a6dfe4e3100..b884f08ac39 100644 --- a/naga/src/back/glsl/features.rs +++ b/naga/src/back/glsl/features.rs @@ -610,6 +610,7 @@ impl Writer<'_, W> { interpolation, sampling, blend_src, + per_primitive: _, } => { if interpolation == Some(Interpolation::Linear) { self.features.request(Features::NOPERSPECTIVE_QUALIFIER); diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index e78af74c844..1af18528944 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -139,7 +139,8 @@ impl crate::AddressSpace { | crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } | crate::AddressSpace::Handle - | crate::AddressSpace::PushConstant => false, + | crate::AddressSpace::PushConstant + | crate::AddressSpace::TaskPayload => false, } } } @@ -1300,6 +1301,9 @@ impl<'a, W: Write> Writer<'a, W> { crate::AddressSpace::Storage { .. } => { self.write_interface_block(handle, global)?; } + crate::AddressSpace::TaskPayload => { + self.write_interface_block(handle, global)?; + } // A global variable in the `Function` address space is a // contradiction in terms. crate::AddressSpace::Function => unreachable!(), @@ -1614,6 +1618,7 @@ impl<'a, W: Write> Writer<'a, W> { interpolation, sampling, blend_src, + per_primitive: _, } => (location, interpolation, sampling, blend_src), crate::Binding::BuiltIn(built_in) => { match built_in { @@ -1732,6 +1737,7 @@ impl<'a, W: Write> Writer<'a, W> { interpolation: None, sampling: None, blend_src, + per_primitive: false, }, stage: self.entry_point.stage, options: VaryingOptions::from_writer_options(self.options, output), @@ -2669,6 +2675,11 @@ impl<'a, W: Write> Writer<'a, W> { self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)? } Statement::RayQuery { .. } => unreachable!(), + Statement::MeshFunction( + crate::MeshFunction::SetMeshOutputs { .. } + | crate::MeshFunction::SetVertex { .. } + | crate::MeshFunction::SetPrimitive { .. }, + ) => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); @@ -5247,6 +5258,15 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s Bi::SubgroupId => "gl_SubgroupID", Bi::SubgroupSize => "gl_SubgroupSize", Bi::SubgroupInvocationId => "gl_SubgroupInvocationID", + // mesh + // TODO: figure out how to map these to glsl things as glsl treats them as arrays + Bi::CullPrimitive + | Bi::PointIndex + | Bi::LineIndices + | Bi::TriangleIndices + | Bi::MeshTaskSize => { + unimplemented!() + } } } @@ -5262,6 +5282,7 @@ const fn glsl_storage_qualifier(space: crate::AddressSpace) -> Option<&'static s As::Handle => Some("uniform"), As::WorkGroup => Some("shared"), As::PushConstant => Some("uniform"), + As::TaskPayload => unreachable!(), } } diff --git a/naga/src/back/hlsl/conv.rs b/naga/src/back/hlsl/conv.rs index ed40cbe5102..d6ccc5ec6e4 100644 --- a/naga/src/back/hlsl/conv.rs +++ b/naga/src/back/hlsl/conv.rs @@ -183,6 +183,9 @@ impl crate::BuiltIn { Self::PointSize | Self::ViewIndex | Self::PointCoord | Self::DrawID => { return Err(Error::Custom(format!("Unsupported builtin {self:?}"))) } + Self::CullPrimitive => "SV_CullPrimitive", + Self::PointIndex | Self::LineIndices | Self::TriangleIndices => unimplemented!(), + Self::MeshTaskSize => unreachable!(), }) } } diff --git a/naga/src/back/hlsl/mod.rs b/naga/src/back/hlsl/mod.rs index 8df06cf1323..f357c02bb3f 100644 --- a/naga/src/back/hlsl/mod.rs +++ b/naga/src/back/hlsl/mod.rs @@ -283,7 +283,8 @@ impl crate::ShaderStage { Self::Vertex => "vs", Self::Fragment => "ps", Self::Compute => "cs", - Self::Task | Self::Mesh => unreachable!(), + Self::Task => "ts", + Self::Mesh => "ms", } } } diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index 357b8597521..9401766448f 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -507,7 +507,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_wrapped_functions(module, &ctx)?; - if ep.stage == ShaderStage::Compute { + if ep.stage.compute_like() { // HLSL is calling workgroup size "num threads" let num_threads = ep.workgroup_size; writeln!( @@ -967,6 +967,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { self.write_type(module, global.ty)?; "" } + crate::AddressSpace::TaskPayload => unimplemented!(), crate::AddressSpace::Uniform => { // constant buffer declarations are expected to be inlined, e.g. // `cbuffer foo: register(b0) { field1: type1; }` @@ -2599,6 +2600,19 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { writeln!(self.out, ".Abort();")?; } }, + Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + }) => { + write!(self.out, "{level}SetMeshOutputCounts(")?; + self.write_expr(module, vertex_count, func_ctx)?; + write!(self.out, ", ")?; + self.write_expr(module, primitive_count, func_ctx)?; + write!(self.out, ");")?; + } + Statement::MeshFunction( + crate::MeshFunction::SetVertex { .. } | crate::MeshFunction::SetPrimitive { .. }, + ) => unimplemented!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = Baked(result).to_string(); @@ -3076,7 +3090,8 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup - | crate::AddressSpace::PushConstant, + | crate::AddressSpace::PushConstant + | crate::AddressSpace::TaskPayload, ) | None => true, Some(crate::AddressSpace::Uniform) => { diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index 7bc8289b9b8..8a2e07635b8 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -494,6 +494,7 @@ impl Options { interpolation, sampling, blend_src, + per_primitive: _, } => match mode { LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)), LocationMode::FragmentOutput => { @@ -651,6 +652,10 @@ impl ResolvedBinding { Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => { return Err(Error::UnsupportedBuiltIn(built_in)) } + Bi::CullPrimitive => "primitive_culled", + // TODO: figure out how to make this written as a function call + Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(), + Bi::MeshTaskSize => unreachable!(), }; write!(out, "{name}")?; } diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs index 2525855cd70..a6b80a2dd27 100644 --- a/naga/src/back/msl/writer.rs +++ b/naga/src/back/msl/writer.rs @@ -578,7 +578,8 @@ impl crate::AddressSpace { | Self::Private | Self::WorkGroup | Self::PushConstant - | Self::Handle => true, + | Self::Handle + | Self::TaskPayload => true, Self::Function => false, } } @@ -591,6 +592,7 @@ impl crate::AddressSpace { // may end up with "const" even if the binding is read-write, // and that should be OK. Self::Storage { .. } => true, + Self::TaskPayload => unimplemented!(), // These should always be read-write. Self::Private | Self::WorkGroup => false, // These translate to `constant` address space, no need for qualifiers. @@ -607,6 +609,7 @@ impl crate::AddressSpace { Self::Storage { .. } => Some("device"), Self::Private | Self::Function => Some("thread"), Self::WorkGroup => Some("threadgroup"), + Self::TaskPayload => Some("object_data"), } } } @@ -4020,6 +4023,14 @@ impl Writer { } } } + // TODO: write emitters for these + crate::Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { .. }) => { + unimplemented!() + } + crate::Statement::MeshFunction( + crate::MeshFunction::SetVertex { .. } + | crate::MeshFunction::SetPrimitive { .. }, + ) => unimplemented!(), crate::Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = self.namer.call(""); @@ -6169,7 +6180,7 @@ template LocationMode::Uniform, false, ), - crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(), + crate::ShaderStage::Task | crate::ShaderStage::Mesh => unimplemented!(), }; // Should this entry point be modified to do vertex pulling? @@ -6232,6 +6243,9 @@ template break; } } + crate::AddressSpace::TaskPayload => { + unimplemented!() + } crate::AddressSpace::Function | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => {} @@ -7159,7 +7173,7 @@ mod workgroup_mem_init { fun_info: &valid::FunctionInfo, ) -> bool { options.zero_initialize_workgroup_memory - && ep.stage == crate::ShaderStage::Compute + && ep.stage.compute_like() && module.global_variables.iter().any(|(handle, var)| { !fun_info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }) diff --git a/naga/src/back/pipeline_constants.rs b/naga/src/back/pipeline_constants.rs index d2b3ed70eda..c009082a3c9 100644 --- a/naga/src/back/pipeline_constants.rs +++ b/naga/src/back/pipeline_constants.rs @@ -39,6 +39,8 @@ pub enum PipelineConstantError { ValidationError(#[from] WithSpan), #[error("workgroup_size override isn't strictly positive")] NegativeWorkgroupSize, + #[error("max vertices or max primitives is negative")] + NegativeMeshOutputMax, } /// Compact `module` and replace all overrides with constants. @@ -243,6 +245,7 @@ pub fn process_overrides<'a>( for ep in entry_points.iter_mut() { process_function(&mut module, &override_map, &mut layouter, &mut ep.function)?; process_workgroup_size_override(&mut module, &adjusted_global_expressions, ep)?; + process_mesh_shader_overrides(&mut module, &adjusted_global_expressions, ep)?; } module.entry_points = entry_points; module.overrides = overrides; @@ -296,6 +299,28 @@ fn process_workgroup_size_override( Ok(()) } +fn process_mesh_shader_overrides( + module: &mut Module, + adjusted_global_expressions: &HandleVec>, + ep: &mut crate::EntryPoint, +) -> Result<(), PipelineConstantError> { + if let Some(ref mut mesh_info) = ep.mesh_info { + if let Some(r#override) = mesh_info.max_vertices_override { + mesh_info.max_vertices = module + .to_ctx() + .eval_expr_to_u32(adjusted_global_expressions[r#override]) + .map_err(|_| PipelineConstantError::NegativeWorkgroupSize)?; + } + if let Some(r#override) = mesh_info.max_primitives_override { + mesh_info.max_primitives = module + .to_ctx() + .eval_expr_to_u32(adjusted_global_expressions[r#override]) + .map_err(|_| PipelineConstantError::NegativeWorkgroupSize)?; + } + } + Ok(()) +} + /// Add a [`Constant`] to `module` for the override `old_h`. /// /// Add the new `Constant` to `override_map` and `adjusted_constant_initializers`. @@ -835,6 +860,26 @@ fn adjust_stmt(new_pos: &HandleVec>, stmt: &mut S crate::RayQueryFunction::Terminate => {} } } + Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { + ref mut vertex_count, + ref mut primitive_count, + }) => { + adjust(vertex_count); + adjust(primitive_count); + } + Statement::MeshFunction( + crate::MeshFunction::SetVertex { + ref mut index, + ref mut value, + } + | crate::MeshFunction::SetPrimitive { + ref mut index, + ref mut value, + }, + ) => { + adjust(index); + adjust(value); + } Statement::Break | Statement::Continue | Statement::Kill diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index 8982242daca..245bc40dd5d 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -207,7 +207,7 @@ impl Writer { Attribute::Stage(ShaderStage::Compute), Attribute::WorkGroupSize(ep.workgroup_size), ], - ShaderStage::Task | ShaderStage::Mesh => unreachable!(), + ShaderStage::Mesh | ShaderStage::Task => unreachable!(), }; self.write_attributes(&attributes)?; @@ -856,6 +856,7 @@ impl Writer { } } Statement::RayQuery { .. } => unreachable!(), + Statement::MeshFunction(..) => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); @@ -1822,6 +1823,7 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { interpolation, sampling, blend_src: None, + per_primitive: _, } => vec![ Attribute::Location(location), Attribute::Interpolate(interpolation, sampling), @@ -1831,6 +1833,7 @@ fn map_binding_to_attribute(binding: &crate::Binding) -> Vec { interpolation, sampling, blend_src: Some(blend_src), + per_primitive: _, } => vec![ Attribute::Location(location), Attribute::BlendSrc(blend_src), diff --git a/naga/src/common/wgsl/to_wgsl.rs b/naga/src/common/wgsl/to_wgsl.rs index 035c4eafb32..dc891aa5a3f 100644 --- a/naga/src/common/wgsl/to_wgsl.rs +++ b/naga/src/common/wgsl/to_wgsl.rs @@ -188,7 +188,12 @@ impl TryToWgsl for crate::BuiltIn { | Bi::PointSize | Bi::DrawID | Bi::PointCoord - | Bi::WorkGroupSize => return None, + | Bi::WorkGroupSize + | Bi::CullPrimitive + | Bi::TriangleIndices + | Bi::LineIndices + | Bi::MeshTaskSize + | Bi::PointIndex => return None, }) } } @@ -352,6 +357,7 @@ pub const fn address_space_str( As::WorkGroup => "workgroup", As::Handle => return (None, None), As::Function => "function", + As::TaskPayload => return (None, None), }), None, ) diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs index d059ba21e4f..a7d3d463f11 100644 --- a/naga/src/compact/mod.rs +++ b/naga/src/compact/mod.rs @@ -221,6 +221,45 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { } } + for entry in &module.entry_points { + if let Some(task_payload) = entry.task_payload { + module_tracer.global_variables_used.insert(task_payload); + } + if let Some(ref mesh_info) = entry.mesh_info { + module_tracer + .types_used + .insert(mesh_info.vertex_output_type); + module_tracer + .types_used + .insert(mesh_info.primitive_output_type); + if let Some(max_vertices_override) = mesh_info.max_vertices_override { + module_tracer + .global_expressions_used + .insert(max_vertices_override); + } + if let Some(max_primitives_override) = mesh_info.max_primitives_override { + module_tracer + .global_expressions_used + .insert(max_primitives_override); + } + } + if entry.stage == crate::ShaderStage::Task || entry.stage == crate::ShaderStage::Mesh { + // u32 should always be there if the module is valid, as it is e.g. the type of some expressions + let u32_type = module + .types + .iter() + .find_map(|tuple| { + if tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32) { + Some(tuple.0) + } else { + None + } + }) + .unwrap(); + module_tracer.types_used.insert(u32_type); + } + } + module_tracer.type_expression_tandem(); // Now that we know what is used and what is never touched, @@ -342,6 +381,23 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { &module_map, &mut reused_named_expressions, ); + if let Some(ref mut task_payload) = entry.task_payload { + module_map.globals.adjust(task_payload); + } + if let Some(ref mut mesh_info) = entry.mesh_info { + module_map.types.adjust(&mut mesh_info.vertex_output_type); + module_map + .types + .adjust(&mut mesh_info.primitive_output_type); + if let Some(ref mut max_vertices_override) = mesh_info.max_vertices_override { + module_map.global_expressions.adjust(max_vertices_override); + } + if let Some(ref mut max_primitives_override) = mesh_info.max_primitives_override { + module_map + .global_expressions + .adjust(max_primitives_override); + } + } } } diff --git a/naga/src/compact/statements.rs b/naga/src/compact/statements.rs index 39d6065f5f0..b370501baca 100644 --- a/naga/src/compact/statements.rs +++ b/naga/src/compact/statements.rs @@ -117,6 +117,20 @@ impl FunctionTracer<'_> { self.expressions_used.insert(query); self.trace_ray_query_function(fun); } + St::MeshFunction(crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + }) => { + self.expressions_used.insert(vertex_count); + self.expressions_used.insert(primitive_count); + } + St::MeshFunction( + crate::MeshFunction::SetPrimitive { index, value } + | crate::MeshFunction::SetVertex { index, value }, + ) => { + self.expressions_used.insert(index); + self.expressions_used.insert(value); + } St::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.expressions_used.insert(predicate); @@ -335,6 +349,26 @@ impl FunctionMap { adjust(query); self.adjust_ray_query_function(fun); } + St::MeshFunction(crate::MeshFunction::SetMeshOutputs { + ref mut vertex_count, + ref mut primitive_count, + }) => { + adjust(vertex_count); + adjust(primitive_count); + } + St::MeshFunction( + crate::MeshFunction::SetVertex { + ref mut index, + ref mut value, + } + | crate::MeshFunction::SetPrimitive { + ref mut index, + ref mut value, + }, + ) => { + adjust(index); + adjust(value); + } St::SubgroupBallot { ref mut result, ref mut predicate, diff --git a/naga/src/front/glsl/functions.rs b/naga/src/front/glsl/functions.rs index 7de7364cd40..ba096a82b3b 100644 --- a/naga/src/front/glsl/functions.rs +++ b/naga/src/front/glsl/functions.rs @@ -1377,6 +1377,8 @@ impl Frontend { result: ty.map(|ty| FunctionResult { ty, binding: None }), ..Default::default() }, + mesh_info: None, + task_payload: None, }); Ok(()) @@ -1446,6 +1448,7 @@ impl Context<'_> { interpolation, sampling: None, blend_src: None, + per_primitive: false, }; location += 1; @@ -1482,6 +1485,7 @@ impl Context<'_> { interpolation, sampling: None, blend_src: None, + per_primitive: false, }; location += 1; binding diff --git a/naga/src/front/glsl/mod.rs b/naga/src/front/glsl/mod.rs index 876add46a1c..e5eda6b3ad9 100644 --- a/naga/src/front/glsl/mod.rs +++ b/naga/src/front/glsl/mod.rs @@ -107,7 +107,7 @@ impl ShaderMetadata { self.version = 0; self.profile = Profile::Core; self.stage = stage; - self.workgroup_size = [u32::from(stage == ShaderStage::Compute); 3]; + self.workgroup_size = [u32::from(stage.compute_like()); 3]; self.early_fragment_tests = false; self.extensions.clear(); } diff --git a/naga/src/front/glsl/variables.rs b/naga/src/front/glsl/variables.rs index ef98143b769..98871bd2f81 100644 --- a/naga/src/front/glsl/variables.rs +++ b/naga/src/front/glsl/variables.rs @@ -465,6 +465,7 @@ impl Frontend { interpolation, sampling, blend_src, + per_primitive: false, }, handle, storage, diff --git a/naga/src/front/interpolator.rs b/naga/src/front/interpolator.rs index e23cae0e7c2..126e860426c 100644 --- a/naga/src/front/interpolator.rs +++ b/naga/src/front/interpolator.rs @@ -44,6 +44,7 @@ impl crate::Binding { interpolation: ref mut interpolation @ None, ref mut sampling, blend_src: _, + per_primitive: _, } = *self { match ty.scalar_kind() { diff --git a/naga/src/front/spv/function.rs b/naga/src/front/spv/function.rs index 67cbf05f04f..48b23e7c4c4 100644 --- a/naga/src/front/spv/function.rs +++ b/naga/src/front/spv/function.rs @@ -596,6 +596,8 @@ impl> super::Frontend { workgroup_size: ep.workgroup_size, workgroup_size_overrides: None, function, + mesh_info: None, + task_payload: None, }); Ok(()) diff --git a/naga/src/front/spv/mod.rs b/naga/src/front/spv/mod.rs index 960437ece58..396318f14dc 100644 --- a/naga/src/front/spv/mod.rs +++ b/naga/src/front/spv/mod.rs @@ -263,6 +263,7 @@ impl Decoration { interpolation, sampling, blend_src: None, + per_primitive: false, }), _ => Err(Error::MissingDecoration(spirv::Decoration::Location)), } @@ -4613,6 +4614,7 @@ impl> Frontend { | S::Atomic { .. } | S::ImageAtomic { .. } | S::RayQuery { .. } + | S::MeshFunction(..) | S::SubgroupBallot { .. } | S::SubgroupCollectiveOperation { .. } | S::SubgroupGather { .. } => {} @@ -4894,6 +4896,8 @@ impl> Frontend { spirv::ExecutionModel::Vertex => crate::ShaderStage::Vertex, spirv::ExecutionModel::Fragment => crate::ShaderStage::Fragment, spirv::ExecutionModel::GLCompute => crate::ShaderStage::Compute, + spirv::ExecutionModel::TaskEXT => crate::ShaderStage::Task, + spirv::ExecutionModel::MeshEXT => crate::ShaderStage::Mesh, _ => return Err(Error::UnsupportedExecutionModel(exec_model as u32)), }, name, diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 257445952b8..a182bf0e064 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -329,6 +329,16 @@ pub enum ShaderStage { Mesh, } +impl ShaderStage { + // TODO: make more things respect this + pub const fn compute_like(self) -> bool { + match self { + Self::Vertex | Self::Fragment => false, + Self::Compute | Self::Task | Self::Mesh => true, + } + } +} + /// Addressing space of variables. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] @@ -363,6 +373,8 @@ pub enum AddressSpace { /// /// [`SHADER_FLOAT16`]: crate::valid::Capabilities::SHADER_FLOAT16 PushConstant, + /// Task shader to mesh shader payload + TaskPayload, } /// Built-in inputs and outputs. @@ -373,7 +385,7 @@ pub enum AddressSpace { pub enum BuiltIn { Position { invariant: bool }, ViewIndex, - // vertex + // vertex (and often mesh) BaseInstance, BaseVertex, ClipDistance, @@ -386,10 +398,10 @@ pub enum BuiltIn { FragDepth, PointCoord, FrontFacing, - PrimitiveIndex, + PrimitiveIndex, // Also for mesh output SampleIndex, SampleMask, - // compute + // compute (and task/mesh) GlobalInvocationId, LocalInvocationId, LocalInvocationIndex, @@ -401,6 +413,12 @@ pub enum BuiltIn { SubgroupId, SubgroupSize, SubgroupInvocationId, + // mesh + MeshTaskSize, + CullPrimitive, + PointIndex, + LineIndices, + TriangleIndices, } /// Number of bytes per scalar. @@ -966,6 +984,7 @@ pub enum Binding { /// Optional `blend_src` index used for dual source blending. /// See blend_src: Option, + per_primitive: bool, }, } @@ -1935,7 +1954,9 @@ pub enum Statement { /// [`Loop`] statement. /// /// [`Loop`]: Statement::Loop - Return { value: Option> }, + Return { + value: Option>, + }, /// Aborts the current shader execution. /// @@ -2141,6 +2162,7 @@ pub enum Statement { /// The specific operation we're performing on `query`. fun: RayQueryFunction, }, + MeshFunction(MeshFunction), /// Calculate a bitmask using a boolean from each active thread in the subgroup SubgroupBallot { /// The [`SubgroupBallotResult`] expression representing this load's result. @@ -2314,6 +2336,9 @@ pub struct EntryPoint { pub workgroup_size_overrides: Option<[Option>; 3]>, /// The entrance function. pub function: Function, + /// The information relating to a mesh shader + pub mesh_info: Option, + pub task_payload: Option>, } /// Return types predeclared for the frexp, modf, and atomicCompareExchangeWeak built-in functions. @@ -2578,3 +2603,46 @@ pub struct Module { /// Doc comments. pub doc_comments: Option>, } + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum MeshOutputTopology { + Points, + Lines, + Triangles, +} +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +#[allow(dead_code)] +pub struct MeshStageInfo { + pub topology: MeshOutputTopology, + pub max_vertices: u32, + pub max_vertices_override: Option>, + pub max_primitives: u32, + pub max_primitives_override: Option>, + pub vertex_output_type: Handle, + pub primitive_output_type: Handle, +} + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum MeshFunction { + SetMeshOutputs { + vertex_count: Handle, + primitive_count: Handle, + }, + SetVertex { + index: Handle, + value: Handle, + }, + SetPrimitive { + index: Handle, + value: Handle, + }, +} diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 413e49c1eed..434c6e3f724 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -177,6 +177,9 @@ impl super::AddressSpace { crate::AddressSpace::Storage { access } => access, crate::AddressSpace::Handle => Sa::LOAD, crate::AddressSpace::PushConstant => Sa::LOAD, + // TaskPayload isn't always writable, but this is checked for elsewhere, + // when not using multiple payloads and matching the entry payload is checked. + crate::AddressSpace::TaskPayload => Sa::LOAD | Sa::STORE, } } } diff --git a/naga/src/proc/terminator.rs b/naga/src/proc/terminator.rs index b29ccb054a3..f76d4c06a3b 100644 --- a/naga/src/proc/terminator.rs +++ b/naga/src/proc/terminator.rs @@ -36,6 +36,7 @@ pub fn ensure_block_returns(block: &mut crate::Block) { | S::ImageStore { .. } | S::Call { .. } | S::RayQuery { .. } + | S::MeshFunction(..) | S::Atomic { .. } | S::ImageAtomic { .. } | S::WorkGroupUniformLoad { .. } diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 95ae40dcdb4..101ea046487 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -85,6 +85,16 @@ struct FunctionUniformity { exit: ExitFlags, } +/// Mesh shader related characteristics of a function. +#[derive(Debug, Clone, Default)] +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] +#[cfg_attr(test, derive(PartialEq))] +pub struct FunctionMeshShaderInfo { + pub vertex_type: Option<(Handle, Handle)>, + pub primitive_type: Option<(Handle, Handle)>, +} + impl ops::BitOr for FunctionUniformity { type Output = Self; fn bitor(self, other: Self) -> Self { @@ -302,6 +312,8 @@ pub struct FunctionInfo { /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. diagnostic_filter_leaf: Option>, + + pub mesh_shader_info: FunctionMeshShaderInfo, } impl FunctionInfo { @@ -372,6 +384,14 @@ impl FunctionInfo { info.uniformity.non_uniform_result } + pub fn insert_global_use( + &mut self, + global_use: GlobalUse, + global: Handle, + ) { + self.global_uses[global.index()] |= global_use; + } + /// Record a use of `expr` for its value. /// /// This is used for almost all expression references. Anything @@ -482,6 +502,8 @@ impl FunctionInfo { *mine |= *other; } + self.try_update_mesh_info(&callee.mesh_shader_info)?; + Ok(FunctionUniformity { result: callee.uniformity.clone(), exit: if callee.may_kill { @@ -635,7 +657,8 @@ impl FunctionInfo { // local data is non-uniform As::Function | As::Private => false, // workgroup memory is exclusively accessed by the group - As::WorkGroup => true, + // task payload memory is very similar to workgroup memory + As::WorkGroup | As::TaskPayload => true, // uniform data As::Uniform | As::PushConstant => true, // storage data is only uniform when read-only @@ -1113,6 +1136,34 @@ impl FunctionInfo { } FunctionUniformity::new() } + S::MeshFunction(func) => match &func { + // TODO: double check all of this uniformity stuff. I frankly don't fully understand all of it. + &crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + } => { + let _ = self.add_ref(vertex_count); + let _ = self.add_ref(primitive_count); + FunctionUniformity::new() + } + &crate::MeshFunction::SetVertex { index, value } + | &crate::MeshFunction::SetPrimitive { index, value } => { + let _ = self.add_ref(index); + let _ = self.add_ref(value); + let ty = + self.expressions[value.index()].ty.clone().handle().ok_or( + FunctionError::InvalidMeshShaderOutputType(value).with_span(), + )?; + + if matches!(func, crate::MeshFunction::SetVertex { .. }) { + self.try_update_mesh_vertex_type(ty, value)?; + } else { + self.try_update_mesh_primitive_type(ty, value)?; + }; + + FunctionUniformity::new() + } + }, S::SubgroupBallot { result: _, predicate, @@ -1158,6 +1209,53 @@ impl FunctionInfo { } Ok(combined_uniformity) } + + fn try_update_mesh_vertex_type( + &mut self, + ty: Handle, + value: Handle, + ) -> Result<(), WithSpan> { + if let &Some(ref existing) = &self.mesh_shader_info.vertex_type { + if existing.0 != ty { + return Err( + FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span() + ); + } + } else { + self.mesh_shader_info.vertex_type = Some((ty, value)); + } + Ok(()) + } + + fn try_update_mesh_primitive_type( + &mut self, + ty: Handle, + value: Handle, + ) -> Result<(), WithSpan> { + if let &Some(ref existing) = &self.mesh_shader_info.primitive_type { + if existing.0 != ty { + return Err( + FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span() + ); + } + } else { + self.mesh_shader_info.primitive_type = Some((ty, value)); + } + Ok(()) + } + + fn try_update_mesh_info( + &mut self, + other: &FunctionMeshShaderInfo, + ) -> Result<(), WithSpan> { + if let &Some(ref other_vertex) = &other.vertex_type { + self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?; + } + if let &Some(ref other_primitive) = &other.vertex_type { + self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?; + } + Ok(()) + } } impl ModuleInfo { @@ -1193,6 +1291,7 @@ impl ModuleInfo { sampling: crate::FastHashSet::default(), dual_source_blending: false, diagnostic_filter_leaf: fun.diagnostic_filter_leaf, + mesh_shader_info: FunctionMeshShaderInfo::default(), }; let resolve_context = ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments); @@ -1326,6 +1425,7 @@ fn uniform_control_flow() { sampling: crate::FastHashSet::default(), dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: FunctionMeshShaderInfo::default(), }; let resolve_context = ResolveContext { constants: &Arena::new(), diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index dc19e191764..0ae2ffdb54f 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -217,6 +217,14 @@ pub enum FunctionError { EmitResult(Handle), #[error("Expression not visited by the appropriate statement")] UnvisitedExpression(Handle), + #[error("Expression {0:?} should be u32, but isn't")] + InvalidMeshFunctionCall(Handle), + #[error("Mesh output types differ from {0:?} to {1:?}")] + ConflictingMeshOutputTypes(Handle, Handle), + #[error("Task payload variables differ from {0:?} to {1:?}")] + ConflictingTaskPayloadVariables(Handle, Handle), + #[error("Mesh shader output at {0:?} is not a user-defined struct")] + InvalidMeshShaderOutputType(Handle), } bitflags::bitflags! { @@ -1539,6 +1547,40 @@ impl super::Validator { crate::RayQueryFunction::Terminate => {} } } + S::MeshFunction(func) => { + let ensure_u32 = + |expr: Handle| -> Result<(), WithSpan> { + let u32_ty = TypeResolution::Value(Ti::Scalar(crate::Scalar::U32)); + let ty = context + .resolve_type_impl(expr, &self.valid_expression_set) + .map_err_inner(|source| { + FunctionError::Expression { + source, + handle: expr, + } + .with_span_handle(expr, context.expressions) + })?; + if !context.compare_types(&u32_ty, ty) { + return Err(FunctionError::InvalidMeshFunctionCall(expr) + .with_span_handle(expr, context.expressions)); + } + Ok(()) + }; + match func { + crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + } => { + ensure_u32(vertex_count)?; + ensure_u32(primitive_count)?; + } + crate::MeshFunction::SetVertex { index, value: _ } + | crate::MeshFunction::SetPrimitive { index, value: _ } => { + ensure_u32(index)?; + // TODO: ensure it is correct for the value + } + } + } S::SubgroupBallot { result, predicate } => { stages &= self.subgroup_stages; if !self.capabilities.contains(super::Capabilities::SUBGROUP) { diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs index e8a69013434..a0153e9398c 100644 --- a/naga/src/valid/handles.rs +++ b/naga/src/valid/handles.rs @@ -801,6 +801,22 @@ impl super::Validator { } Ok(()) } + crate::Statement::MeshFunction(func) => match func { + crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + } => { + validate_expr(vertex_count)?; + validate_expr(primitive_count)?; + Ok(()) + } + crate::MeshFunction::SetVertex { index, value } + | crate::MeshFunction::SetPrimitive { index, value } => { + validate_expr(index)?; + validate_expr(value)?; + Ok(()) + } + }, crate::Statement::SubgroupBallot { result, predicate } => { validate_expr_opt(predicate)?; validate_expr(result)?; diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 7c8cc903139..51167a4810d 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -92,6 +92,10 @@ pub enum VaryingError { }, #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")] InvalidMultiDimensionalSubgroupBuiltIn, + #[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")] + InvalidPerPrimitive, + #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] + MissingPerPrimitive, } #[derive(Clone, Debug, thiserror::Error)] @@ -123,6 +127,26 @@ pub enum EntryPointError { InvalidIntegerInterpolation { location: u32 }, #[error(transparent)] Function(#[from] FunctionError), + #[error("Non mesh shader entry point cannot have mesh shader attributes")] + UnexpectedMeshShaderAttributes, + #[error("Non mesh/task shader entry point cannot have task payload attribute")] + UnexpectedTaskPayload, + #[error("Task payload must be declared with `var`")] + TaskPayloadWrongAddressSpace, + #[error("For a task payload to be used, it must be declared with @payload")] + WrongTaskPayloadUsed, + #[error("A function can only set vertex and primitive types that correspond to the mesh shader attributes")] + WrongMeshOutputType, + #[error("Only mesh shader entry points can write to mesh output vertices and primitives")] + UnexpectedMeshShaderOutput, + #[error("Mesh shader entry point cannot have a return type")] + UnexpectedMeshShaderEntryResult, + #[error("Task shader entry point must return @builtin(mesh_task_size) vec3")] + WrongTaskShaderEntryResult, + #[error("Mesh output type must be a user-defined struct.")] + InvalidMeshOutputType, + #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] + InvalidMeshPrimitiveOutputType, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -139,6 +163,13 @@ fn storage_usage(access: crate::StorageAccess) -> GlobalUse { storage_usage } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum MeshOutputType { + None, + VertexOutput, + PrimitiveOutput, +} + struct VaryingContext<'a> { stage: crate::ShaderStage, output: bool, @@ -149,6 +180,7 @@ struct VaryingContext<'a> { built_ins: &'a mut crate::FastHashSet, capabilities: Capabilities, flags: super::ValidationFlags, + mesh_output_type: MeshOutputType, } impl VaryingContext<'_> { @@ -236,10 +268,9 @@ impl VaryingContext<'_> { ), Bi::Position { .. } => ( match self.stage { - St::Vertex => self.output, + St::Vertex | St::Mesh => self.output, St::Fragment => !self.output, - St::Compute => false, - St::Task | St::Mesh => unreachable!(), + St::Compute | St::Task => false, }, *ty_inner == Ti::Vector { @@ -276,7 +307,7 @@ impl VaryingContext<'_> { *ty_inner == Ti::Scalar(crate::Scalar::U32), ), Bi::LocalInvocationIndex => ( - self.stage == St::Compute && !self.output, + self.stage.compute_like() && !self.output, *ty_inner == Ti::Scalar(crate::Scalar::U32), ), Bi::GlobalInvocationId @@ -284,7 +315,7 @@ impl VaryingContext<'_> { | Bi::WorkGroupId | Bi::WorkGroupSize | Bi::NumWorkGroups => ( - self.stage == St::Compute && !self.output, + self.stage.compute_like() && !self.output, *ty_inner == Ti::Vector { size: Vs::Tri, @@ -292,17 +323,48 @@ impl VaryingContext<'_> { }, ), Bi::NumSubgroups | Bi::SubgroupId => ( - self.stage == St::Compute && !self.output, + self.stage.compute_like() && !self.output, *ty_inner == Ti::Scalar(crate::Scalar::U32), ), Bi::SubgroupSize | Bi::SubgroupInvocationId => ( match self.stage { - St::Compute | St::Fragment => !self.output, + St::Compute | St::Fragment | St::Task | St::Mesh => !self.output, St::Vertex => false, - St::Task | St::Mesh => unreachable!(), }, *ty_inner == Ti::Scalar(crate::Scalar::U32), ), + Bi::CullPrimitive => ( + self.mesh_output_type == MeshOutputType::PrimitiveOutput, + *ty_inner == Ti::Scalar(crate::Scalar::BOOL), + ), + Bi::PointIndex => ( + self.mesh_output_type == MeshOutputType::PrimitiveOutput, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), + Bi::LineIndices => ( + self.mesh_output_type == MeshOutputType::PrimitiveOutput, + *ty_inner + == Ti::Vector { + size: Vs::Bi, + scalar: crate::Scalar::U32, + }, + ), + Bi::TriangleIndices => ( + self.mesh_output_type == MeshOutputType::PrimitiveOutput, + *ty_inner + == Ti::Vector { + size: Vs::Tri, + scalar: crate::Scalar::U32, + }, + ), + Bi::MeshTaskSize => ( + self.stage == St::Task && self.output, + *ty_inner + == Ti::Vector { + size: Vs::Tri, + scalar: crate::Scalar::U32, + }, + ), }; if !visible { @@ -318,6 +380,7 @@ impl VaryingContext<'_> { interpolation, sampling, blend_src, + per_primitive, } => { // Only IO-shareable types may be stored in locations. if !self.type_info[ty.index()] @@ -326,6 +389,14 @@ impl VaryingContext<'_> { { return Err(VaryingError::NotIOShareableType(ty)); } + if !per_primitive && self.mesh_output_type == MeshOutputType::PrimitiveOutput { + return Err(VaryingError::MissingPerPrimitive); + } else if per_primitive + && ((self.stage != crate::ShaderStage::Fragment || self.output) + && self.mesh_output_type != MeshOutputType::PrimitiveOutput) + { + return Err(VaryingError::InvalidPerPrimitive); + } if let Some(blend_src) = blend_src { // `blend_src` is only valid if dual source blending was explicitly enabled, @@ -390,11 +461,12 @@ impl VaryingContext<'_> { } } + // TODO: update this to reflect the fact that per-primitive outputs aren't interpolated for fragment and mesh stages let needs_interpolation = match self.stage { crate::ShaderStage::Vertex => self.output, crate::ShaderStage::Fragment => !self.output, - crate::ShaderStage::Compute => false, - crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(), + crate::ShaderStage::Compute | crate::ShaderStage::Task => false, + crate::ShaderStage::Mesh => self.output, }; // It doesn't make sense to specify a sampling when `interpolation` is `Flat`, but @@ -595,7 +667,9 @@ impl super::Validator { TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED, false, ), - crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false), + crate::AddressSpace::WorkGroup | crate::AddressSpace::TaskPayload => { + (TypeFlags::DATA | TypeFlags::SIZED, false) + } crate::AddressSpace::PushConstant => { if !self.capabilities.contains(Capabilities::PUSH_CONSTANT) { return Err(GlobalVariableError::UnsupportedCapability( @@ -671,7 +745,7 @@ impl super::Validator { } } - if ep.stage == crate::ShaderStage::Compute { + if ep.stage.compute_like() { if ep .workgroup_size .iter() @@ -683,10 +757,30 @@ impl super::Validator { return Err(EntryPointError::UnexpectedWorkgroupSize.with_span()); } + if ep.stage != crate::ShaderStage::Mesh && ep.mesh_info.is_some() { + return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span()); + } + let mut info = self .validate_function(&ep.function, module, mod_info, true) .map_err(WithSpan::into_other)?; + if let Some(handle) = ep.task_payload { + if ep.stage != crate::ShaderStage::Task && ep.stage != crate::ShaderStage::Mesh { + return Err(EntryPointError::UnexpectedTaskPayload.with_span()); + } + if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { + return Err(EntryPointError::TaskPayloadWrongAddressSpace.with_span()); + } + // Make sure that this is always present in the outputted shader + let uses = if ep.stage == crate::ShaderStage::Mesh { + GlobalUse::READ + } else { + GlobalUse::READ | GlobalUse::WRITE + }; + info.insert_global_use(uses, handle); + } + { use super::ShaderStages; @@ -694,7 +788,8 @@ impl super::Validator { crate::ShaderStage::Vertex => ShaderStages::VERTEX, crate::ShaderStage::Fragment => ShaderStages::FRAGMENT, crate::ShaderStage::Compute => ShaderStages::COMPUTE, - crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(), + crate::ShaderStage::Mesh => ShaderStages::MESH, + crate::ShaderStage::Task => ShaderStages::TASK, }; if !info.available_stages.contains(stage_bit) { @@ -716,6 +811,7 @@ impl super::Validator { built_ins: &mut argument_built_ins, capabilities: self.capabilities, flags: self.flags, + mesh_output_type: MeshOutputType::None, }; ctx.validate(ep, fa.ty, fa.binding.as_ref()) .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?; @@ -734,6 +830,7 @@ impl super::Validator { built_ins: &mut result_built_ins, capabilities: self.capabilities, flags: self.flags, + mesh_output_type: MeshOutputType::None, }; ctx.validate(ep, fr.ty, fr.binding.as_ref()) .map_err_inner(|e| EntryPointError::Result(e).with_span())?; @@ -742,11 +839,26 @@ impl super::Validator { { return Err(EntryPointError::MissingVertexOutputPosition.with_span()); } + if ep.stage == crate::ShaderStage::Mesh + && (!result_built_ins.is_empty() || !self.location_mask.is_empty()) + { + return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span()); + } + // Cannot have any other built-ins or @location outputs as those are per-vertex or per-primitive + if ep.stage == crate::ShaderStage::Task + && (!result_built_ins.contains(&crate::BuiltIn::MeshTaskSize) + || result_built_ins.len() != 1 + || !self.location_mask.is_empty()) + { + return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); + } if !self.blend_src_mask.is_empty() { info.dual_source_blending = true; } } else if ep.stage == crate::ShaderStage::Vertex { return Err(EntryPointError::MissingVertexOutputPosition.with_span()); + } else if ep.stage == crate::ShaderStage::Task { + return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); } { @@ -764,6 +876,13 @@ impl super::Validator { } } + if let Some(task_payload) = ep.task_payload { + if module.global_variables[task_payload].space != crate::AddressSpace::TaskPayload { + return Err(EntryPointError::TaskPayloadWrongAddressSpace + .with_span_handle(task_payload, &module.global_variables)); + } + } + self.ep_resource_bindings.clear(); for (var_handle, var) in module.global_variables.iter() { let usage = info[var_handle]; @@ -771,6 +890,13 @@ impl super::Validator { continue; } + if var.space == crate::AddressSpace::TaskPayload { + if ep.task_payload != Some(var_handle) { + return Err(EntryPointError::WrongTaskPayloadUsed + .with_span_handle(var_handle, &module.global_variables)); + } + } + let allowed_usage = match var.space { crate::AddressSpace::Function => unreachable!(), crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY, @@ -792,6 +918,15 @@ impl super::Validator { crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => { GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY } + crate::AddressSpace::TaskPayload => { + GlobalUse::READ + | GlobalUse::QUERY + | if ep.stage == crate::ShaderStage::Task { + GlobalUse::WRITE + } else { + GlobalUse::empty() + } + } crate::AddressSpace::PushConstant => GlobalUse::READ, }; if !allowed_usage.contains(usage) { @@ -811,6 +946,77 @@ impl super::Validator { } } + if let &Some(ref mesh_info) = &ep.mesh_info { + // Technically it is allowed to not output anything + // TODO: check that only the allowed builtins are used here + if let Some(used_vertex_type) = info.mesh_shader_info.vertex_type { + if used_vertex_type.0 != mesh_info.vertex_output_type { + return Err(EntryPointError::WrongMeshOutputType + .with_span_handle(mesh_info.vertex_output_type, &module.types)); + } + } + if let Some(used_primitive_type) = info.mesh_shader_info.primitive_type { + if used_primitive_type.0 != mesh_info.primitive_output_type { + return Err(EntryPointError::WrongMeshOutputType + .with_span_handle(mesh_info.primitive_output_type, &module.types)); + } + } + + for (ty, mesh_output_type) in [ + (mesh_info.vertex_output_type, MeshOutputType::VertexOutput), + ( + mesh_info.primitive_output_type, + MeshOutputType::PrimitiveOutput, + ), + ] { + if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) { + return Err( + EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types) + ); + } + let mut result_built_ins = crate::FastHashSet::default(); + let mut ctx = VaryingContext { + stage: ep.stage, + output: true, + types: &module.types, + type_info: &self.types, + location_mask: &mut self.location_mask, + blend_src_mask: &mut self.blend_src_mask, + built_ins: &mut result_built_ins, + capabilities: self.capabilities, + flags: self.flags, + mesh_output_type, + }; + ctx.validate(ep, ty, None) + .map_err_inner(|e| EntryPointError::Result(e).with_span())?; + if mesh_output_type == MeshOutputType::PrimitiveOutput { + let mut num_indices_builtins = 0; + if result_built_ins.contains(&crate::BuiltIn::PointIndex) { + num_indices_builtins += 1; + } + if result_built_ins.contains(&crate::BuiltIn::LineIndices) { + num_indices_builtins += 1; + } + if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) { + num_indices_builtins += 1; + } + if num_indices_builtins != 1 { + return Err(EntryPointError::InvalidMeshPrimitiveOutputType + .with_span_handle(ty, &module.types)); + } + } else if mesh_output_type == MeshOutputType::VertexOutput + && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) + { + return Err(EntryPointError::MissingVertexOutputPosition + .with_span_handle(ty, &module.types)); + } + } + } else if info.mesh_shader_info.vertex_type.is_some() + || info.mesh_shader_info.primitive_type.is_some() + { + return Err(EntryPointError::UnexpectedMeshShaderOutput.with_span()); + } + Ok(info) } } diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index fe45d3bfb07..babea985244 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -240,6 +240,8 @@ bitflags::bitflags! { const VERTEX = 0x1; const FRAGMENT = 0x2; const COMPUTE = 0x4; + const MESH = 0x8; + const TASK = 0x10; } } diff --git a/naga/src/valid/type.rs b/naga/src/valid/type.rs index e8b83ff08f3..aa0633e1852 100644 --- a/naga/src/valid/type.rs +++ b/naga/src/valid/type.rs @@ -220,9 +220,12 @@ const fn ptr_space_argument_flag(space: crate::AddressSpace) -> TypeFlags { use crate::AddressSpace as As; match space { As::Function | As::Private => TypeFlags::ARGUMENT, - As::Uniform | As::Storage { .. } | As::Handle | As::PushConstant | As::WorkGroup => { - TypeFlags::empty() - } + As::Uniform + | As::Storage { .. } + | As::Handle + | As::PushConstant + | As::WorkGroup + | As::TaskPayload => TypeFlags::empty(), } } diff --git a/wgpu-core/src/validation.rs b/wgpu-core/src/validation.rs index 2c2f4b36c44..ae199f2c703 100644 --- a/wgpu-core/src/validation.rs +++ b/wgpu-core/src/validation.rs @@ -1085,6 +1085,8 @@ impl Interface { 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!(), } } @@ -1229,7 +1231,7 @@ impl Interface { } // check workgroup size limits - if shader_stage == naga::ShaderStage::Compute { + if shader_stage.compute_like() { let max_workgroup_size_limits = [ self.limits.max_compute_workgroup_size_x, self.limits.max_compute_workgroup_size_y, diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index bb4e2a9d4ae..51381ce4f75 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -2099,6 +2099,9 @@ impl super::Adapter { if features.contains(wgt::Features::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN) { capabilities.push(spv::Capability::RayQueryPositionFetchKHR) } + if features.contains(wgt::Features::EXPERIMENTAL_MESH_SHADER) { + capabilities.push(spv::Capability::MeshShadingEXT); + } if self.private_caps.shader_integer_dot_product { // See . capabilities.extend(&[ From 8c3e550d30ba44eec07f9bc0b3c0301e33a38f29 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Thu, 14 Aug 2025 12:53:21 -0500 Subject: [PATCH 002/110] Other initial changes --- naga/src/back/spv/block.rs | 1 + naga/src/back/spv/helpers.rs | 1 + naga/src/back/spv/writer.rs | 6 ++++++ naga/src/front/wgsl/lower/mod.rs | 3 +++ 4 files changed, 11 insertions(+) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 0cd414bfbeb..148626ce6bd 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -3654,6 +3654,7 @@ impl BlockContext<'_> { } => { self.write_subgroup_gather(mode, argument, result, &mut block)?; } + Statement::MeshFunction(_) => unreachable!(), } } diff --git a/naga/src/back/spv/helpers.rs b/naga/src/back/spv/helpers.rs index 84e130efaa3..f6d26794e70 100644 --- a/naga/src/back/spv/helpers.rs +++ b/naga/src/back/spv/helpers.rs @@ -54,6 +54,7 @@ pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::Stor crate::AddressSpace::Uniform => spirv::StorageClass::Uniform, crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup, crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant, + crate::AddressSpace::TaskPayload => unreachable!(), } } diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 0688eb6c975..2a294a92275 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1927,6 +1927,7 @@ impl Writer { interpolation, sampling, blend_src, + per_primitive: _, } => { self.decorate(id, Decoration::Location, &[location]); @@ -2076,6 +2077,11 @@ impl Writer { )?; BuiltIn::SubgroupLocalInvocationId } + Bi::MeshTaskSize + | Bi::CullPrimitive + | Bi::PointIndex + | Bi::LineIndices + | Bi::TriangleIndices => unreachable!(), }; self.decorate(id, Decoration::BuiltIn, &[built_in as u32]); diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index e90d7eab0a8..2066d7cf2c8 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1527,6 +1527,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { workgroup_size, workgroup_size_overrides, function, + mesh_info: None, + task_payload: None, }); Ok(LoweredGlobalDecl::EntryPoint( ctx.module.entry_points.len() - 1, @@ -4069,6 +4071,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { interpolation, sampling, blend_src, + per_primitive: false, }; binding.apply_default_interpolation(&ctx.module.types[ty].inner); Some(binding) From 85bbc5a0bbb8958e0d2d8bf977e7dd00effafaeb Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 14 Aug 2025 13:24:44 -0500 Subject: [PATCH 003/110] Updated shader snapshots --- naga/tests/out/analysis/spv-shadow.info.ron | 18 ++- naga/tests/out/analysis/wgsl-access.info.ron | 114 +++++++++++++++--- naga/tests/out/analysis/wgsl-collatz.info.ron | 12 +- .../out/analysis/wgsl-overrides.info.ron | 6 +- .../analysis/wgsl-storage-textures.info.ron | 12 +- naga/tests/out/ir/spv-fetch_depth.compact.ron | 2 + naga/tests/out/ir/spv-fetch_depth.ron | 2 + naga/tests/out/ir/spv-shadow.compact.ron | 5 + naga/tests/out/ir/spv-shadow.ron | 5 + .../out/ir/spv-spec-constants.compact.ron | 6 + naga/tests/out/ir/spv-spec-constants.ron | 6 + naga/tests/out/ir/wgsl-access.compact.ron | 7 ++ naga/tests/out/ir/wgsl-access.ron | 7 ++ naga/tests/out/ir/wgsl-collatz.compact.ron | 2 + naga/tests/out/ir/wgsl-collatz.ron | 2 + .../out/ir/wgsl-const_assert.compact.ron | 2 + naga/tests/out/ir/wgsl-const_assert.ron | 2 + .../out/ir/wgsl-diagnostic-filter.compact.ron | 2 + naga/tests/out/ir/wgsl-diagnostic-filter.ron | 2 + .../out/ir/wgsl-index-by-value.compact.ron | 2 + naga/tests/out/ir/wgsl-index-by-value.ron | 2 + .../tests/out/ir/wgsl-local-const.compact.ron | 2 + naga/tests/out/ir/wgsl-local-const.ron | 2 + naga/tests/out/ir/wgsl-must-use.compact.ron | 2 + naga/tests/out/ir/wgsl-must-use.ron | 2 + ...ides-atomicCompareExchangeWeak.compact.ron | 2 + ...sl-overrides-atomicCompareExchangeWeak.ron | 2 + .../ir/wgsl-overrides-ray-query.compact.ron | 2 + .../tests/out/ir/wgsl-overrides-ray-query.ron | 2 + naga/tests/out/ir/wgsl-overrides.compact.ron | 2 + naga/tests/out/ir/wgsl-overrides.ron | 2 + .../out/ir/wgsl-storage-textures.compact.ron | 4 + naga/tests/out/ir/wgsl-storage-textures.ron | 4 + ...l-template-list-trailing-comma.compact.ron | 2 + .../ir/wgsl-template-list-trailing-comma.ron | 2 + .../out/ir/wgsl-texture-external.compact.ron | 7 ++ naga/tests/out/ir/wgsl-texture-external.ron | 7 ++ .../ir/wgsl-types_with_comments.compact.ron | 2 + .../tests/out/ir/wgsl-types_with_comments.ron | 2 + 39 files changed, 241 insertions(+), 27 deletions(-) diff --git a/naga/tests/out/analysis/spv-shadow.info.ron b/naga/tests/out/analysis/spv-shadow.info.ron index 6ddda61f5c6..b08a28438ed 100644 --- a/naga/tests/out/analysis/spv-shadow.info.ron +++ b/naga/tests/out/analysis/spv-shadow.info.ron @@ -18,7 +18,7 @@ functions: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(1), requirements: (""), @@ -413,10 +413,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(1), requirements: (""), @@ -1591,12 +1595,16 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], entry_points: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(1), requirements: (""), @@ -1685,6 +1693,10 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-access.info.ron b/naga/tests/out/analysis/wgsl-access.info.ron index 319f62bdf13..d297b09a404 100644 --- a/naga/tests/out/analysis/wgsl-access.info.ron +++ b/naga/tests/out/analysis/wgsl-access.info.ron @@ -42,7 +42,7 @@ functions: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -1197,10 +1197,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -2523,10 +2527,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -2563,10 +2571,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -2612,10 +2624,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -2655,10 +2671,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -2749,10 +2769,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -2870,10 +2894,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -2922,10 +2950,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -2977,10 +3009,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -3029,10 +3065,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -3084,10 +3124,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -3148,10 +3192,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(2), requirements: (""), @@ -3221,10 +3269,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(2), requirements: (""), @@ -3297,10 +3349,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -3397,10 +3453,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(1), requirements: (""), @@ -3593,12 +3653,16 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], entry_points: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -4290,10 +4354,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -4742,10 +4810,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(0), requirements: (""), @@ -4812,6 +4884,10 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-collatz.info.ron b/naga/tests/out/analysis/wgsl-collatz.info.ron index 7ec5799d758..2796f544510 100644 --- a/naga/tests/out/analysis/wgsl-collatz.info.ron +++ b/naga/tests/out/analysis/wgsl-collatz.info.ron @@ -8,7 +8,7 @@ functions: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(3), requirements: (""), @@ -275,12 +275,16 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], entry_points: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: Some(3), requirements: (""), @@ -430,6 +434,10 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], const_expression_types: [], diff --git a/naga/tests/out/analysis/wgsl-overrides.info.ron b/naga/tests/out/analysis/wgsl-overrides.info.ron index 0e0ae318042..a76c9c89c9b 100644 --- a/naga/tests/out/analysis/wgsl-overrides.info.ron +++ b/naga/tests/out/analysis/wgsl-overrides.info.ron @@ -8,7 +8,7 @@ entry_points: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -201,6 +201,10 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-storage-textures.info.ron b/naga/tests/out/analysis/wgsl-storage-textures.info.ron index fbbf7206c33..35b5a7e320c 100644 --- a/naga/tests/out/analysis/wgsl-storage-textures.info.ron +++ b/naga/tests/out/analysis/wgsl-storage-textures.info.ron @@ -11,7 +11,7 @@ entry_points: [ ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -184,10 +184,14 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), uniformity: ( non_uniform_result: None, requirements: (""), @@ -396,6 +400,10 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), ), ], const_expression_types: [], diff --git a/naga/tests/out/ir/spv-fetch_depth.compact.ron b/naga/tests/out/ir/spv-fetch_depth.compact.ron index 1fbee2deb35..98f4426c3eb 100644 --- a/naga/tests/out/ir/spv-fetch_depth.compact.ron +++ b/naga/tests/out/ir/spv-fetch_depth.compact.ron @@ -196,6 +196,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/spv-fetch_depth.ron b/naga/tests/out/ir/spv-fetch_depth.ron index 186f78354ad..104de852c17 100644 --- a/naga/tests/out/ir/spv-fetch_depth.ron +++ b/naga/tests/out/ir/spv-fetch_depth.ron @@ -266,6 +266,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/spv-shadow.compact.ron b/naga/tests/out/ir/spv-shadow.compact.ron index b49cd9b55be..bed86a5334d 100644 --- a/naga/tests/out/ir/spv-shadow.compact.ron +++ b/naga/tests/out/ir/spv-shadow.compact.ron @@ -974,6 +974,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), ), ( @@ -984,6 +985,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), ), ], @@ -994,6 +996,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -1032,6 +1035,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/spv-shadow.ron b/naga/tests/out/ir/spv-shadow.ron index e1f0f60b6bb..bdda1d18566 100644 --- a/naga/tests/out/ir/spv-shadow.ron +++ b/naga/tests/out/ir/spv-shadow.ron @@ -1252,6 +1252,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), ), ( @@ -1262,6 +1263,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), ), ], @@ -1272,6 +1274,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -1310,6 +1313,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/spv-spec-constants.compact.ron b/naga/tests/out/ir/spv-spec-constants.compact.ron index 3fa6ffef4ff..67eb29c2475 100644 --- a/naga/tests/out/ir/spv-spec-constants.compact.ron +++ b/naga/tests/out/ir/spv-spec-constants.compact.ron @@ -151,6 +151,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), offset: 0, ), @@ -510,6 +511,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ( @@ -520,6 +522,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ( @@ -530,6 +533,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ], @@ -613,6 +617,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/spv-spec-constants.ron b/naga/tests/out/ir/spv-spec-constants.ron index 94c90aa78f9..51686aa20eb 100644 --- a/naga/tests/out/ir/spv-spec-constants.ron +++ b/naga/tests/out/ir/spv-spec-constants.ron @@ -242,6 +242,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), offset: 0, ), @@ -616,6 +617,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ( @@ -626,6 +628,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ( @@ -636,6 +639,7 @@ interpolation: None, sampling: None, blend_src: None, + per_primitive: false, )), ), ], @@ -719,6 +723,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-access.compact.ron b/naga/tests/out/ir/wgsl-access.compact.ron index 30e88984f3c..c3df0c8c500 100644 --- a/naga/tests/out/ir/wgsl-access.compact.ron +++ b/naga/tests/out/ir/wgsl-access.compact.ron @@ -2655,6 +2655,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "foo_frag", @@ -2672,6 +2674,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -2848,6 +2851,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "foo_compute", @@ -2907,6 +2912,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-access.ron b/naga/tests/out/ir/wgsl-access.ron index 30e88984f3c..c3df0c8c500 100644 --- a/naga/tests/out/ir/wgsl-access.ron +++ b/naga/tests/out/ir/wgsl-access.ron @@ -2655,6 +2655,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "foo_frag", @@ -2672,6 +2674,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -2848,6 +2851,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "foo_compute", @@ -2907,6 +2912,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-collatz.compact.ron b/naga/tests/out/ir/wgsl-collatz.compact.ron index f72c652d032..fc4daaa1296 100644 --- a/naga/tests/out/ir/wgsl-collatz.compact.ron +++ b/naga/tests/out/ir/wgsl-collatz.compact.ron @@ -334,6 +334,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-collatz.ron b/naga/tests/out/ir/wgsl-collatz.ron index f72c652d032..fc4daaa1296 100644 --- a/naga/tests/out/ir/wgsl-collatz.ron +++ b/naga/tests/out/ir/wgsl-collatz.ron @@ -334,6 +334,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-const_assert.compact.ron b/naga/tests/out/ir/wgsl-const_assert.compact.ron index 2816364f88b..648f4ff9bc9 100644 --- a/naga/tests/out/ir/wgsl-const_assert.compact.ron +++ b/naga/tests/out/ir/wgsl-const_assert.compact.ron @@ -34,6 +34,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-const_assert.ron b/naga/tests/out/ir/wgsl-const_assert.ron index 2816364f88b..648f4ff9bc9 100644 --- a/naga/tests/out/ir/wgsl-const_assert.ron +++ b/naga/tests/out/ir/wgsl-const_assert.ron @@ -34,6 +34,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron b/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron index c5746696d52..9a2bf193f30 100644 --- a/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron +++ b/naga/tests/out/ir/wgsl-diagnostic-filter.compact.ron @@ -73,6 +73,8 @@ ], diagnostic_filter_leaf: Some(0), ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [ diff --git a/naga/tests/out/ir/wgsl-diagnostic-filter.ron b/naga/tests/out/ir/wgsl-diagnostic-filter.ron index c5746696d52..9a2bf193f30 100644 --- a/naga/tests/out/ir/wgsl-diagnostic-filter.ron +++ b/naga/tests/out/ir/wgsl-diagnostic-filter.ron @@ -73,6 +73,8 @@ ], diagnostic_filter_leaf: Some(0), ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [ diff --git a/naga/tests/out/ir/wgsl-index-by-value.compact.ron b/naga/tests/out/ir/wgsl-index-by-value.compact.ron index a4f84a7a6b2..addd0e5871c 100644 --- a/naga/tests/out/ir/wgsl-index-by-value.compact.ron +++ b/naga/tests/out/ir/wgsl-index-by-value.compact.ron @@ -465,6 +465,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-index-by-value.ron b/naga/tests/out/ir/wgsl-index-by-value.ron index a4f84a7a6b2..addd0e5871c 100644 --- a/naga/tests/out/ir/wgsl-index-by-value.ron +++ b/naga/tests/out/ir/wgsl-index-by-value.ron @@ -465,6 +465,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-local-const.compact.ron b/naga/tests/out/ir/wgsl-local-const.compact.ron index 512972657ed..0e4e2e4d40e 100644 --- a/naga/tests/out/ir/wgsl-local-const.compact.ron +++ b/naga/tests/out/ir/wgsl-local-const.compact.ron @@ -100,6 +100,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-local-const.ron b/naga/tests/out/ir/wgsl-local-const.ron index 512972657ed..0e4e2e4d40e 100644 --- a/naga/tests/out/ir/wgsl-local-const.ron +++ b/naga/tests/out/ir/wgsl-local-const.ron @@ -100,6 +100,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-must-use.compact.ron b/naga/tests/out/ir/wgsl-must-use.compact.ron index a701a6805da..16e925f2fb8 100644 --- a/naga/tests/out/ir/wgsl-must-use.compact.ron +++ b/naga/tests/out/ir/wgsl-must-use.compact.ron @@ -201,6 +201,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-must-use.ron b/naga/tests/out/ir/wgsl-must-use.ron index a701a6805da..16e925f2fb8 100644 --- a/naga/tests/out/ir/wgsl-must-use.ron +++ b/naga/tests/out/ir/wgsl-must-use.ron @@ -201,6 +201,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron index 640ee25ca49..28a824bb035 100644 --- a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.compact.ron @@ -128,6 +128,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron index 640ee25ca49..28a824bb035 100644 --- a/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron +++ b/naga/tests/out/ir/wgsl-overrides-atomicCompareExchangeWeak.ron @@ -128,6 +128,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron b/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron index f65e8f186db..152a45008c5 100644 --- a/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides-ray-query.compact.ron @@ -263,6 +263,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides-ray-query.ron b/naga/tests/out/ir/wgsl-overrides-ray-query.ron index f65e8f186db..152a45008c5 100644 --- a/naga/tests/out/ir/wgsl-overrides-ray-query.ron +++ b/naga/tests/out/ir/wgsl-overrides-ray-query.ron @@ -263,6 +263,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides.compact.ron b/naga/tests/out/ir/wgsl-overrides.compact.ron index 81221ff7941..fe136e71e4d 100644 --- a/naga/tests/out/ir/wgsl-overrides.compact.ron +++ b/naga/tests/out/ir/wgsl-overrides.compact.ron @@ -221,6 +221,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-overrides.ron b/naga/tests/out/ir/wgsl-overrides.ron index 81221ff7941..fe136e71e4d 100644 --- a/naga/tests/out/ir/wgsl-overrides.ron +++ b/naga/tests/out/ir/wgsl-overrides.ron @@ -221,6 +221,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-storage-textures.compact.ron b/naga/tests/out/ir/wgsl-storage-textures.compact.ron index ec63fecac27..68c867a19e2 100644 --- a/naga/tests/out/ir/wgsl-storage-textures.compact.ron +++ b/naga/tests/out/ir/wgsl-storage-textures.compact.ron @@ -218,6 +218,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "csStore", @@ -315,6 +317,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-storage-textures.ron b/naga/tests/out/ir/wgsl-storage-textures.ron index ec63fecac27..68c867a19e2 100644 --- a/naga/tests/out/ir/wgsl-storage-textures.ron +++ b/naga/tests/out/ir/wgsl-storage-textures.ron @@ -218,6 +218,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "csStore", @@ -315,6 +317,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron b/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron index a8208c09b86..db619dff836 100644 --- a/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron +++ b/naga/tests/out/ir/wgsl-template-list-trailing-comma.compact.ron @@ -190,6 +190,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron b/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron index a8208c09b86..db619dff836 100644 --- a/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron +++ b/naga/tests/out/ir/wgsl-template-list-trailing-comma.ron @@ -190,6 +190,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-texture-external.compact.ron b/naga/tests/out/ir/wgsl-texture-external.compact.ron index dbffbddcdc7..379e76566c5 100644 --- a/naga/tests/out/ir/wgsl-texture-external.compact.ron +++ b/naga/tests/out/ir/wgsl-texture-external.compact.ron @@ -360,6 +360,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -382,6 +383,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "vertex_main", @@ -418,6 +421,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "compute_main", @@ -449,6 +454,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-texture-external.ron b/naga/tests/out/ir/wgsl-texture-external.ron index dbffbddcdc7..379e76566c5 100644 --- a/naga/tests/out/ir/wgsl-texture-external.ron +++ b/naga/tests/out/ir/wgsl-texture-external.ron @@ -360,6 +360,7 @@ interpolation: Some(Perspective), sampling: Some(Center), blend_src: None, + per_primitive: false, )), )), local_variables: [], @@ -382,6 +383,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "vertex_main", @@ -418,6 +421,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ( name: "compute_main", @@ -449,6 +454,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-types_with_comments.compact.ron b/naga/tests/out/ir/wgsl-types_with_comments.compact.ron index 7186209f00e..7c0d856946f 100644 --- a/naga/tests/out/ir/wgsl-types_with_comments.compact.ron +++ b/naga/tests/out/ir/wgsl-types_with_comments.compact.ron @@ -116,6 +116,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], diff --git a/naga/tests/out/ir/wgsl-types_with_comments.ron b/naga/tests/out/ir/wgsl-types_with_comments.ron index 480b0d2337f..34e44cb9653 100644 --- a/naga/tests/out/ir/wgsl-types_with_comments.ron +++ b/naga/tests/out/ir/wgsl-types_with_comments.ron @@ -172,6 +172,8 @@ ], diagnostic_filter_leaf: None, ), + mesh_info: None, + task_payload: None, ), ], diagnostic_filters: [], From ccf84676ce22129a3199c022e24cd46591e71284 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Sat, 16 Aug 2025 20:09:18 -0500 Subject: [PATCH 004/110] Added new HLSL limitation --- naga/src/valid/interface.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 51167a4810d..0e2a2583f0f 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -147,6 +147,8 @@ pub enum EntryPointError { InvalidMeshOutputType, #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] InvalidMeshPrimitiveOutputType, + #[error("Task payload must not be zero-sized")] + ZeroSizedTaskPayload, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -881,6 +883,13 @@ impl super::Validator { return Err(EntryPointError::TaskPayloadWrongAddressSpace .with_span_handle(task_payload, &module.global_variables)); } + let var = &module.global_variables[task_payload]; + let ty = &module.types[var.ty].inner; + // HLSL doesn't allow zero sized payloads. + if ty.try_size(module.to_ctx()) == Some(0) { + return Err(EntryPointError::ZeroSizedTaskPayload + .with_span_handle(task_payload, &module.global_variables)); + } } self.ep_resource_bindings.clear(); From e55c02f2e3d75ba607f9f9b1886b69eb0c65cea9 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Sat, 16 Aug 2025 20:20:36 -0500 Subject: [PATCH 005/110] Moved error to global variable error --- naga/src/valid/interface.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 0e2a2583f0f..16c09f6dc7c 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -43,6 +43,8 @@ pub enum GlobalVariableError { StorageAddressSpaceWriteOnlyNotSupported, #[error("Type is not valid for use as a push constant")] InvalidPushConstantType(#[source] PushConstantError), + #[error("Task payload must not be zero-sized")] + ZeroSizedTaskPayload, } #[derive(Clone, Debug, thiserror::Error)] @@ -147,8 +149,6 @@ pub enum EntryPointError { InvalidMeshOutputType, #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] InvalidMeshPrimitiveOutputType, - #[error("Task payload must not be zero-sized")] - ZeroSizedTaskPayload, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -704,6 +704,14 @@ impl super::Validator { } } + if var.space == crate::AddressSpace::TaskPayload { + let ty = &gctx.types[var.ty].inner; + // HLSL doesn't allow zero sized payloads. + if ty.try_size(gctx) == Some(0) { + return Err(GlobalVariableError::ZeroSizedTaskPayload); + } + } + if let Some(init) = var.init { match var.space { crate::AddressSpace::Private | crate::AddressSpace::Function => {} @@ -883,13 +891,6 @@ impl super::Validator { return Err(EntryPointError::TaskPayloadWrongAddressSpace .with_span_handle(task_payload, &module.global_variables)); } - let var = &module.global_variables[task_payload]; - let ty = &module.types[var.ty].inner; - // HLSL doesn't allow zero sized payloads. - if ty.try_size(module.to_ctx()) == Some(0) { - return Err(EntryPointError::ZeroSizedTaskPayload - .with_span_handle(task_payload, &module.global_variables)); - } } self.ep_resource_bindings.clear(); From 0f6da753722c1585ecc2089f5e4d121f03a02cd3 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 20 Aug 2025 10:46:27 -0500 Subject: [PATCH 006/110] Added docs to per_primitive --- naga/src/ir/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index a182bf0e064..12a0fecf5c8 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -984,6 +984,13 @@ pub enum Binding { /// Optional `blend_src` index used for dual source blending. /// See blend_src: Option, + /// Whether the binding is a per-primitive binding for use with mesh shaders. + /// This is required to match for mesh and fragment shader stages. + /// This is merely an extra attribute on a binding. You still may not have + /// a per-vertex and per-primitive input with the same location. + /// + /// Per primitive values are not interpolated at all and are not dependent on the vertices + /// or pixel location. For example, it may be used to store a non-interpolated normal vector. per_primitive: bool, }, } From 3017214d9bb12b6021d561045d9fb9ea3485f70c Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 20 Aug 2025 11:08:34 -0500 Subject: [PATCH 007/110] Added a little bit more docs here and there in IR --- naga/src/ir/mod.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 12a0fecf5c8..2856872db27 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -325,6 +325,7 @@ pub enum ShaderStage { Vertex, Fragment, Compute, + // Mesh shader stages Task, Mesh, } @@ -1961,9 +1962,7 @@ pub enum Statement { /// [`Loop`] statement. /// /// [`Loop`]: Statement::Loop - Return { - value: Option>, - }, + Return { value: Option> }, /// Aborts the current shader execution. /// @@ -2169,6 +2168,7 @@ pub enum Statement { /// The specific operation we're performing on `query`. fun: RayQueryFunction, }, + /// A mesh shader intrinsic MeshFunction(MeshFunction), /// Calculate a bitmask using a boolean from each active thread in the subgroup SubgroupBallot { @@ -2345,6 +2345,7 @@ pub struct EntryPoint { pub function: Function, /// The information relating to a mesh shader pub mesh_info: Option, + /// The unique global variable used as a task payload from task shader to mesh shader pub task_payload: Option>, } @@ -2620,6 +2621,7 @@ pub enum MeshOutputTopology { Lines, Triangles, } + #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] @@ -2635,6 +2637,7 @@ pub struct MeshStageInfo { pub primitive_output_type: Handle, } +/// Mesh shader intrinsics #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] From 198437b71d2bb39756c5a5133b8e19235553a1f6 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Wed, 20 Aug 2025 12:37:38 -0500 Subject: [PATCH 008/110] Adding validation to ensure that task shaders have a task payload --- naga/src/valid/interface.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 16c09f6dc7c..1fed0fda529 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -149,6 +149,8 @@ pub enum EntryPointError { InvalidMeshOutputType, #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] InvalidMeshPrimitiveOutputType, + #[error("Task shaders must declare a task payload output")] + ExpectedTaskPayload, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -891,6 +893,8 @@ impl super::Validator { return Err(EntryPointError::TaskPayloadWrongAddressSpace .with_span_handle(task_payload, &module.global_variables)); } + } else if ep.stage == crate::ShaderStage::Task { + return Err(EntryPointError::ExpectedTaskPayload.with_span()); } self.ep_resource_bindings.clear(); From 64000e4d976edb7397bdd2e71e940a4db0a19c39 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Wed, 20 Aug 2025 12:42:01 -0500 Subject: [PATCH 009/110] Updated spec to reflect the change to payload variables --- docs/api-specs/mesh_shading.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index ee14f99e757..e1f28d43e91 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -80,12 +80,12 @@ This shader stage can be selected by marking a function with `@task`. Task shade The output of this determines how many workgroups of mesh shaders will be dispatched. Once dispatched, global id variables will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup. -If task shaders are marked with `@payload(someVar)`, where `someVar` is global variable declared like `var someVar: `, task shaders may use `someVar` as if it is a read-write workgroup storage variable. This payload is passed to the mesh shader workgroup that is invoked. The mesh shader can skip declaring `@payload` to ignore this input. +Task shaders must be marked with `@payload(someVar)`, where `someVar` is global variable declared like `var someVar: `. Task shaders may use `someVar` as if it is a read-write workgroup storage variable. This payload is passed to the mesh shader workgroup that is invoked. ### Mesh shader This shader stage can be selected by marking a function with `@mesh`. Mesh shaders must not return anything. -Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, mesh shaders cannot write to this memory. Declaring `@payload` in a pipeline with no task shader, in a pipeline with a task shader that doesn't declare `@payload`, or in a task shader with an `@payload` that is statically sized and smaller than the mesh shader payload is illegal. +Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, this is optional, and mesh shaders cannot write to this memory. Declaring `@payload` in a pipeline with no task shader or in a task shader with an `@payload` that is statically sized and differently than the mesh shader payload is illegal. The `@payload` attribute can only be ignored in pipelines that don't have a task shader. Mesh shaders must be marked with `@vertex_output(OutputType, numOutputs)`, where `numOutputs` is the maximum number of vertices to be output by a mesh shader, and `OutputType` is the data associated with vertices, similar to a standard vertex shader output, and must be a struct. From b572ec7e231d466457aec0d17aa7a11ceffd313d Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Sat, 23 Aug 2025 20:08:16 -0500 Subject: [PATCH 010/110] Updated the mesh shading spec because it was goofy --- docs/api-specs/mesh_shading.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index e1f28d43e91..e9b6df3710d 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -2,8 +2,8 @@ 🧪Experimental🧪 -`wgpu` supports an experimental version of mesh shading. The extensions allow for acceleration structures to be created and built (with -`Features::EXPERIMENTAL_MESH_SHADER` enabled) and interacted with in shaders. Currently `naga` has no support for mesh shaders beyond recognizing the additional shader stages. +`wgpu` supports an experimental version of mesh shading when `Features::EXPERIMENTAL_MESH_SHADER` is enabled. +Currently `naga` has no support for parsing or writing mesh shaders. For this reason, all shaders must be created with `Device::create_shader_module_passthrough`. **Note**: The features documented here may have major bugs in them and are expected to be subject From 7bec4dd3fed42a01b2a6f3ecb35dd965a23ccbb0 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Sun, 24 Aug 2025 17:36:41 -0700 Subject: [PATCH 011/110] some doc tweaks --- wgpu/src/api/render_pass.rs | 22 ++++++++++++++++++- wgpu/src/api/render_pipeline.rs | 38 +++++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index 8163b4261f0..5779d1a0ff3 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -226,7 +226,27 @@ impl RenderPass<'_> { self.inner.draw_indexed(indices, base_vertex, instances); } - /// Draws using a mesh shader pipeline + /// Draws using a mesh shader pipeline. + /// + /// The current pipeline must be a mesh shader pipeline. + /// + /// If the current pipeline has a task shader, run it with an invocation for + /// every `vec3(i, j, k)` where `i`, `j`, and `k` are between `0` and + /// `group_count_x`, `group_count_y`, and `group_count_z`. Each invocation's + /// return value indicates a set of mesh shaders to invoke, and passes + /// payload values for them to consume. TODO: provide specifics on return value + /// + /// If the current pipeline lacks a task shader, run its mesh shader with an + /// invocation for every `vec3(i, j, k)` where `i`, `j`, and `k` are + /// between `0` and `group_count_x`, `group_count_y`, and `group_count_z`. + /// + /// Each mesh shader invocation's return value produces a list of primitives + /// to draw. TODO: provide specifics on return value + /// + /// Each primitive is then rendered with the current pipeline's fragment + /// shader, if present. Otherwise, [No Color Output mode] is used. + /// + /// [No Color Output mode]: https://www.w3.org/TR/webgpu/#no-color-output pub fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32) { self.inner .draw_mesh_tasks(group_count_x, group_count_y, group_count_z); diff --git a/wgpu/src/api/render_pipeline.rs b/wgpu/src/api/render_pipeline.rs index e887bb4b97e..07ec909b28c 100644 --- a/wgpu/src/api/render_pipeline.rs +++ b/wgpu/src/api/render_pipeline.rs @@ -238,7 +238,41 @@ static_assertions::assert_impl_all!(RenderPipelineDescriptor<'_>: Send, Sync); /// Describes a mesh shader (graphics) pipeline. /// -/// For use with [`Device::create_mesh_pipeline`]. +/// For use with [`Device::create_mesh_pipeline`]. A mesh pipeline is very much +/// like a render pipeline, except that instead of [`RenderPass::draw`] it is +/// invoked with [`RenderPass::draw_mesh_tasks`], and instead of a vertex shader +/// and a fragment shader: +/// +/// - [`task`] specifies an optional task shader entry point, which generates +/// groups of mesh shaders to dispatch. +/// +/// - [`mesh`] specifies a mesh shader entry point, which generates groups of +/// primitives to draw +/// +/// - [`fragment`] specifies as fragment shader for drawing those primitive, +/// just like in an ordinary render pipeline. +/// +/// The key difference is that, whereas a vertex shader is invoked on the +/// elements of vertex buffers, the task shader gets to decide how many mesh +/// shader invocations to make, and then each mesh shader invocation gets to +/// decide which primitives it wants to generate, and what their vertex +/// attributes are. Task and mesh shaders can use whatever they please as +/// inputs, like a compute shader. (Fancy [vertex formats] are up to the mesh +/// shader to implement itself.) +/// +/// A mesh pipeline is invoked by [`RenderPass::draw_mesh_tasks`], which looks +/// like a compute shader dispatch with [`ComputePass::dispatch_workgroups`]: +/// you pass `x`, `y`, and `z` values indicating the number of task shaders to +/// invoke in parallel. TODO: what is the output of a task shader? +/// +/// If the task shader is omitted, then the (`x`, `y`, `z`) parameters to +/// `draw_mesh_tasks` are used to decide how many invocations of the mesh shader +/// to invoke directly. +/// +/// [vertex formats]: wgpu_types::VertexFormat +/// [`task`]: Self::task +/// [`mesh`]: Self::mesh +/// [`fragment`]: Self::fragment #[derive(Clone, Debug)] pub struct MeshPipelineDescriptor<'a> { /// Debug label of the pipeline. This will show up in graphics debuggers for easy identification. @@ -263,7 +297,7 @@ pub struct MeshPipelineDescriptor<'a> { /// /// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout pub layout: Option<&'a PipelineLayout>, - /// The compiled task stage, its entry point, and the color targets. + /// The compiled task stage and its entry point. pub task: Option>, /// The compiled mesh stage and its entry point pub mesh: MeshState<'a>, From 2fcb8539c2d6e22c10e769035c185442cfe23226 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 25 Aug 2025 01:27:22 -0500 Subject: [PATCH 012/110] Tried to clarify docs a little --- wgpu/src/api/render_pass.rs | 32 +++++++++++++++++++------------- wgpu/src/api/render_pipeline.rs | 18 ++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index 5779d1a0ff3..a832e380fbf 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -228,23 +228,29 @@ impl RenderPass<'_> { /// Draws using a mesh shader pipeline. /// - /// The current pipeline must be a mesh shader pipeline. + /// The current pipeline must be a mesh shader pipeline. /// - /// If the current pipeline has a task shader, run it with an invocation for + /// If the current pipeline has a task shader, run it with an workgroup for /// every `vec3(i, j, k)` where `i`, `j`, and `k` are between `0` and - /// `group_count_x`, `group_count_y`, and `group_count_z`. Each invocation's - /// return value indicates a set of mesh shaders to invoke, and passes - /// payload values for them to consume. TODO: provide specifics on return value - /// - /// If the current pipeline lacks a task shader, run its mesh shader with an - /// invocation for every `vec3(i, j, k)` where `i`, `j`, and `k` are + /// `group_count_x`, `group_count_y`, and `group_count_z`. The invocation with + /// index zero in each group is responsible for determining the mesh shader dispatch. + /// Its return value indicates the number of workgroups of mesh shaders to invoke. It also + /// passes a payload value for them to consume. Because each task workgroup is essentially + /// a mesh shader draw call, mesh workgroups dispatched by different task workgroups + /// cannot interact in any way, and `workgroup_id` corresponds to its location in the + /// calling specific task shader's dispatch group. + /// + /// If the current pipeline lacks a task shader, run its mesh shader with a + /// workgroup for every `vec3(i, j, k)` where `i`, `j`, and `k` are /// between `0` and `group_count_x`, `group_count_y`, and `group_count_z`. /// - /// Each mesh shader invocation's return value produces a list of primitives - /// to draw. TODO: provide specifics on return value - /// - /// Each primitive is then rendered with the current pipeline's fragment - /// shader, if present. Otherwise, [No Color Output mode] is used. + /// Each mesh shader workgroup outputs a set of vertices and indices for primitives. + /// The indices outputted correspond to the vertices outputted by that same workgroup; + /// there is no global vertex buffer. These primitives are passed to the rasterizer and + /// essentially treated like a vertex shader output, except that the mesh shader may + /// choose to cull specific primitives or pass per-primitive non-interpolated values + /// to the mesh shader. As such, each primitive is then rendered with the current + /// pipeline's fragment shader, if present. Otherwise, [No Color Output mode] is used. /// /// [No Color Output mode]: https://www.w3.org/TR/webgpu/#no-color-output pub fn draw_mesh_tasks(&mut self, group_count_x: u32, group_count_y: u32, group_count_z: u32) { diff --git a/wgpu/src/api/render_pipeline.rs b/wgpu/src/api/render_pipeline.rs index 07ec909b28c..be16d91f27a 100644 --- a/wgpu/src/api/render_pipeline.rs +++ b/wgpu/src/api/render_pipeline.rs @@ -243,31 +243,33 @@ static_assertions::assert_impl_all!(RenderPipelineDescriptor<'_>: Send, Sync); /// invoked with [`RenderPass::draw_mesh_tasks`], and instead of a vertex shader /// and a fragment shader: /// -/// - [`task`] specifies an optional task shader entry point, which generates -/// groups of mesh shaders to dispatch. +/// - [`task`] specifies an optional task shader entry point, which determines how +/// many groups of mesh shaders to dispatch. /// /// - [`mesh`] specifies a mesh shader entry point, which generates groups of /// primitives to draw /// -/// - [`fragment`] specifies as fragment shader for drawing those primitive, +/// - [`fragment`] specifies as fragment shader for drawing those primitives, /// just like in an ordinary render pipeline. /// /// The key difference is that, whereas a vertex shader is invoked on the /// elements of vertex buffers, the task shader gets to decide how many mesh -/// shader invocations to make, and then each mesh shader invocation gets to +/// shader workgroups to make, and then each mesh shader workgroup gets to /// decide which primitives it wants to generate, and what their vertex /// attributes are. Task and mesh shaders can use whatever they please as -/// inputs, like a compute shader. (Fancy [vertex formats] are up to the mesh -/// shader to implement itself.) +/// inputs, like a compute shader. However, they cannot use specialized vertex +/// or index buffers. /// /// A mesh pipeline is invoked by [`RenderPass::draw_mesh_tasks`], which looks /// like a compute shader dispatch with [`ComputePass::dispatch_workgroups`]: /// you pass `x`, `y`, and `z` values indicating the number of task shaders to -/// invoke in parallel. TODO: what is the output of a task shader? +/// invoke in parallel. The output value of the first thread in a task shader +/// workgroup determines how many mesh workgroups should be dispatched from there. +/// Those mesh workgroups also get a special payload passed from the task shader. /// /// If the task shader is omitted, then the (`x`, `y`, `z`) parameters to /// `draw_mesh_tasks` are used to decide how many invocations of the mesh shader -/// to invoke directly. +/// to invoke directly, without a task payload. /// /// [vertex formats]: wgpu_types::VertexFormat /// [`task`]: Self::task From 8bfe1067e8658f728166d2a84d8be6dc64e47476 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 25 Aug 2025 02:10:41 -0500 Subject: [PATCH 013/110] Tried to update spec --- docs/api-specs/mesh_shading.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index e9b6df3710d..24a4cde2cda 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -11,6 +11,31 @@ to breaking changes, suggestions for the API exposed by this should be posted on ***This is not*** a thorough explanation of mesh shading and how it works. Those wishing to understand mesh shading more broadly should look elsewhere first. +## Mesh shaders overview + +### What are mesh shaders +Mesh shaders are a new kind of rasterization pipeline intended to address some of the shortfalls with the vertex shader pipeline. The core idea of mesh shaders is that the GPU decides how to render the many small parts of a scene instead of the CPU issuing a draw call for every small part or issuing an inefficient monolithic draw call for a large part of the scene. + +Mesh shaders are specifically designed to be used with **meshlet rendering**, a technique where every object is split into many subobjects called meshlets that are each rendered with their own parameters. With the standard vertex pipeline, each draw call specifies an exact number of primitives to render and the same parameters for all vertex shaders on an entire object (or even multiple objects). This doesn't leave room for different LODs for different parts of an object, for example a closer part having more detail, nor does it allow culling smaller sections (or primitives) of objects. With mesh shaders, each task workgroup might get assigned to a single object. It can then analyze the different meshlets(sections) of that object, determine which are visible and should actually be rendered, and for those meshlets determine what LOD to use based on the distance from the camera. It can then dispatch a mesh workgroup for each meshlet, with each mesh workgroup then reading the data for that specific LOD of its meshlet, determining which and how many vertices and primitives to output, determining which remaining primitives need to be culled, and passing the resulting primitives to the rasterizer. + +Mesh shaders are most effective in scenes with many polygons. They can allow skipping processing of entire groups of primitives that are facing away from the camera or otherwise occluded, which reduces the number of primitives that need to be processed by more than half in most cases, and they can reduce the number of primitives that need to be processed for more distant objects. Scenes that are not bottlenecked by geometry (perhaps instead by fragment processing or post processing) will not see much benefit from using them. + +Mesh shaders were first shown off in [NVIDIA's asteroids demo](https://www.youtube.com/watch?v=CRfZYJ_sk5E). Now, they form the basis for [Unreal Engine's Nanite](https://www.unrealengine.com/en-US/blog/unreal-engine-5-is-now-available-in-preview#Nanite). + +### Mesh shader pipeline +A mesh shader pipeline is just like a standard render pipeline, except that the vertex shader stage is replaced by a mesh shader stage (and optionally a task shader stage). This functions as follows: + +* If there is a task shader stage, task shader workgroups are invoked first, with the number of workgroups determined by the draw call. Each task shader workgroup outputs a workgroup size and a task payload. A dispatch group of mesh shaders with the given workgroup size is then invoked with the task payload as a parameter. +* Otherwise, a single dispatch group of mesh shaders with workgroup size given by the draw call is invoked. +* Each mesh shader dispatch group functions exactly as a compute dispatch group, except that it has special outputs and may take a task payload as input. Mesh dispatch groups invoked by different task shader workgroups cannot interact. +* Each workgroup within the mesh shader dispatch group can output vertices and primitives + * It determines how many vertices and primitives to write and then sets those vertices and primitives. + * Primitives have an indices field which determines the indices of the vertices of that primitive. The indices are based on the output of that mesh shader workgroup only; there is no sharing of vertices across workgroups (no vertex or index buffer equivalents). + * Primitives can then be culled by setting the appropriate builtin + * Each vertex output functions exactly as the output from a vertex shader would + * There can also be per-primitive outputs passed to fragment shaders; these are not interpolated or based on the vertices of the primitive in any way. +* Once all of the primitives are written, those that weren't culled are are rasterized. From this point forward, the only difference from a standard render pipeline is that there may be some per-primitive inputs passed to fragment shaders. + ## `wgpu` API ### New `wgpu` functions @@ -101,7 +126,7 @@ Mesh shader primitive outputs must also specify exactly one of `@builtin(triangl Additionally, the `@location` attributes from the vertex and primitive outputs can't overlap. -Before setting any vertices or indices, or exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly these numbers of vertices and primitives. A varying member with `@per_primitive` cannot be used in function interfaces except as the primitive output for mesh shaders or as input for fragment shaders. +Before exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly this range of vertices and primitives. A varying member with `@per_primitive` cannot be used in function interfaces except as a primitive output for mesh shaders or as input for fragment shaders. The mesh shader can write to vertices using the `setVertex(idx: u32, vertex: VertexOutput)` where `VertexOutput` is replaced with the vertex type declared in `@vertex_output`, and `idx` is the index of the vertex to write. Similarly, the mesh shader can write to vertices using `setPrimitive(idx: u32, primitive: PrimitiveOutput)`. These can be written to multiple times, however unsynchronized writes are undefined behavior. The primitives and indices are shared across the entire mesh shader workgroup. From 6ccaeec5e96e50abc136acffda6841d92d52036d Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 25 Aug 2025 02:14:45 -0500 Subject: [PATCH 014/110] Removed a warning --- docs/api-specs/mesh_shading.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 24a4cde2cda..df0a5149f9f 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -9,8 +9,6 @@ For this reason, all shaders must be created with `Device::create_shader_module_ **Note**: The features documented here may have major bugs in them and are expected to be subject to breaking changes, suggestions for the API exposed by this should be posted on [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). -***This is not*** a thorough explanation of mesh shading and how it works. Those wishing to understand mesh shading more broadly should look elsewhere first. - ## Mesh shaders overview ### What are mesh shaders From 5b7ba116b70380827a14bf1da4e67a023529703e Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Mon, 25 Aug 2025 13:34:27 -0500 Subject: [PATCH 015/110] Addressed comment about docs mistake --- wgpu/src/api/render_pass.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index a832e380fbf..0c3acad7ac8 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -249,7 +249,7 @@ impl RenderPass<'_> { /// there is no global vertex buffer. These primitives are passed to the rasterizer and /// essentially treated like a vertex shader output, except that the mesh shader may /// choose to cull specific primitives or pass per-primitive non-interpolated values - /// to the mesh shader. As such, each primitive is then rendered with the current + /// to the fragment shader. As such, each primitive is then rendered with the current /// pipeline's fragment shader, if present. Otherwise, [No Color Output mode] is used. /// /// [No Color Output mode]: https://www.w3.org/TR/webgpu/#no-color-output From 46576462ebd75cbf0d25f2f5a1a4d79cb2ec8af5 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Tue, 2 Sep 2025 08:11:38 -0700 Subject: [PATCH 016/110] Review in progress - Extensive revisions to `docs/api-specs/mesh_shading.md`. - Doc comments. - Ensure `Module` stays at the bottom of `ir/mod.rs`. - Avoid a clone. - Rename some arguments to be more specific. - Minor readability tweaks. --- docs/api-specs/mesh_shading.md | 113 +++++++++++++++++++++++-------- naga/src/ir/mod.rs | 115 ++++++++++++++++++-------------- naga/src/valid/analyzer.rs | 9 +-- naga/src/valid/interface.rs | 19 +++--- wgpu/src/api/render_pass.rs | 6 +- wgpu/src/api/render_pipeline.rs | 21 ++++-- 6 files changed, 184 insertions(+), 99 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index df0a5149f9f..fcead0898bb 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -11,7 +11,8 @@ to breaking changes, suggestions for the API exposed by this should be posted on ## Mesh shaders overview -### What are mesh shaders +### What are mesh shaders? + Mesh shaders are a new kind of rasterization pipeline intended to address some of the shortfalls with the vertex shader pipeline. The core idea of mesh shaders is that the GPU decides how to render the many small parts of a scene instead of the CPU issuing a draw call for every small part or issuing an inefficient monolithic draw call for a large part of the scene. Mesh shaders are specifically designed to be used with **meshlet rendering**, a technique where every object is split into many subobjects called meshlets that are each rendered with their own parameters. With the standard vertex pipeline, each draw call specifies an exact number of primitives to render and the same parameters for all vertex shaders on an entire object (or even multiple objects). This doesn't leave room for different LODs for different parts of an object, for example a closer part having more detail, nor does it allow culling smaller sections (or primitives) of objects. With mesh shaders, each task workgroup might get assigned to a single object. It can then analyze the different meshlets(sections) of that object, determine which are visible and should actually be rendered, and for those meshlets determine what LOD to use based on the distance from the camera. It can then dispatch a mesh workgroup for each meshlet, with each mesh workgroup then reading the data for that specific LOD of its meshlet, determining which and how many vertices and primitives to output, determining which remaining primitives need to be culled, and passing the resulting primitives to the rasterizer. @@ -21,18 +22,51 @@ Mesh shaders are most effective in scenes with many polygons. They can allow ski Mesh shaders were first shown off in [NVIDIA's asteroids demo](https://www.youtube.com/watch?v=CRfZYJ_sk5E). Now, they form the basis for [Unreal Engine's Nanite](https://www.unrealengine.com/en-US/blog/unreal-engine-5-is-now-available-in-preview#Nanite). ### Mesh shader pipeline -A mesh shader pipeline is just like a standard render pipeline, except that the vertex shader stage is replaced by a mesh shader stage (and optionally a task shader stage). This functions as follows: - -* If there is a task shader stage, task shader workgroups are invoked first, with the number of workgroups determined by the draw call. Each task shader workgroup outputs a workgroup size and a task payload. A dispatch group of mesh shaders with the given workgroup size is then invoked with the task payload as a parameter. -* Otherwise, a single dispatch group of mesh shaders with workgroup size given by the draw call is invoked. -* Each mesh shader dispatch group functions exactly as a compute dispatch group, except that it has special outputs and may take a task payload as input. Mesh dispatch groups invoked by different task shader workgroups cannot interact. -* Each workgroup within the mesh shader dispatch group can output vertices and primitives - * It determines how many vertices and primitives to write and then sets those vertices and primitives. - * Primitives have an indices field which determines the indices of the vertices of that primitive. The indices are based on the output of that mesh shader workgroup only; there is no sharing of vertices across workgroups (no vertex or index buffer equivalents). - * Primitives can then be culled by setting the appropriate builtin - * Each vertex output functions exactly as the output from a vertex shader would - * There can also be per-primitive outputs passed to fragment shaders; these are not interpolated or based on the vertices of the primitive in any way. -* Once all of the primitives are written, those that weren't culled are are rasterized. From this point forward, the only difference from a standard render pipeline is that there may be some per-primitive inputs passed to fragment shaders. + +With the current pipeline set to a mesh pipeline, a draw command like +`render_pass.draw_mesh_tasks(x, y, z)` takes the following steps: + +* If the pipeline has a task shader stage: + + * Dispatch a grid of task shader workgroups, where `x`, `y`, and `z` give + the number of workgroups along each axis of the grid. Each task shader + workgroup produces a mesh shader workgroup grid size `(mx, my, mz)` and a + task payload value `mp`. + + * For each task shader workgroup, dispatch a grid of mesh shader workgroups, + where `mx`, `my`, and `mz` give the number of workgroups along each axis + of the grid. Pass `mp` to each of these workgroup's mesh shader + invocations. + +* Alternatively, if the pipeline does not have a task shader stage: + + * Dispatch a single grid of mesh shader workgroups, where `x`, `y`, and `z` + give the number of workgroups along each axis of the grid. These mesh + shaders receive no task payload value. + +* Each mesh shader workgroup produces a list of output vertices, and a list of + primitives built from those vertices. The workgroup can supply per-primitive + values as well, if needed. Each primitive selects its vertices by index, like + an indexed draw call, from among the vertices generated by this workgroup. + + Unlike a grid of ordinary compute shader workgroups collaborating to build + vertex and index data in common storage buffers, the vertices and primitives + produced by a mesh shader workgroup are entirely private to that workgroup, + and are not accessible by other workgroups. + +* Primitives produced by a mesh shader workgroup can have a culling flag. If a + primitive's culling flag is false, it is skipped during rasterization. + +* The primitives produced by all mesh shader workgroups are then rasterized in + the usual way, with each fragment shader invocation handling one pixel. + + Attributes from the vertices produced by the mesh shader workgroup are + provided to the fragment shader with interpolation applied as appropriate. + + If the mesh shader workgroup supplied per-primitive values, these are + available to each primitive's fragment shader invocations. Per-primitive + values are never interpolated; fragment shaders simply receive the values + the mesh shader workgroup associated with their primitive. ## `wgpu` API @@ -99,34 +133,57 @@ Using any of these features in a `wgsl` program will require adding the `enable Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-specific functionality, such as subgroup operations. ### Task shader -This shader stage can be selected by marking a function with `@task`. Task shaders must return a `vec3` as their output type. Similar to compute shaders, task shaders run in a workgroup. The output must be uniform across all threads in a workgroup. -The output of this determines how many workgroups of mesh shaders will be dispatched. Once dispatched, global id variables will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup. +A function with the `@task` attribute is a **task shader entry point**. A mesh shader pipeline may optionally specify a task shader entry point, and if it does, mesh draw commands using that pipeline dispatch a **task shader grid** of workgroups running the task shader entry point. Like compute shader dispatches, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the task shader grid as the number of workgroups along each of the grid's three axes. -Task shaders must be marked with `@payload(someVar)`, where `someVar` is global variable declared like `var someVar: `. Task shaders may use `someVar` as if it is a read-write workgroup storage variable. This payload is passed to the mesh shader workgroup that is invoked. +A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. + +A task shader entry point must return a `vec3` value. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section. + +If a task shader entry point has a `@payload(G)` property, then `G` must be the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. + +Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid; +and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload. ### Mesh shader -This shader stage can be selected by marking a function with `@mesh`. Mesh shaders must not return anything. -Mesh shaders can be marked with `@payload(someVar)` similar to task shaders. Unlike task shaders, this is optional, and mesh shaders cannot write to this memory. Declaring `@payload` in a pipeline with no task shader or in a task shader with an `@payload` that is statically sized and differently than the mesh shader payload is illegal. The `@payload` attribute can only be ignored in pipelines that don't have a task shader. +A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh shaders must not return anything. + +Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes. + +A mesh shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. + +If the mesh shader pipeline has a task shader entry point with a `@payload(G)` attribute, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. + +If the mesh shader pipeline does not have a task shader entry point, or the task shader entry point does not have a `@payload(G)` attribute, then the mesh shader entry point must not have any `@payload` attribute. + +A mesh shader entry point must have the following attributes: + +- `@vertex_output(V, NV)`: This indicates that the mesh shader workgroup will generate at most `NV` vertex values, each of type `V`. + +- `@primitive_output(P, NP)`: This indicates that the mesh shader workgroup will generate at most `NP` primitives, each of type `P`. + +Each mesh shader entry point invocation must call the `setMeshOutputs(numVertices: u32, numPrimitives: u32)` builtin function exactly once, in uniform control flow. The values passed by each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) determine how many vertices (values of type `V`) and primitives (values of type `P`) the workgroup must produce. This call essentially establishes two implicit arrays of vertex and primitive values, shared across the workgroup, for invocations to populate. + +The `numVertices` and `numPrimitives` arguments must be no greater than `NV` and `NP` from the `@vertex_output` and `@primitive_output` attributes. -Mesh shaders must be marked with `@vertex_output(OutputType, numOutputs)`, where `numOutputs` is the maximum number of vertices to be output by a mesh shader, and `OutputType` is the data associated with vertices, similar to a standard vertex shader output, and must be a struct. +To produce vertex data, the workgroup as a whole must make `numVertices` calls to the `setVertex(i: u32, vertex: V)` builtin function. This establishes `vertex` as the value of the `i`'th vertex. `V` is the type given in the `@vertex_output` attribute. `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a '@builtin(position)`, and so on. An invocation may only call `setVertex` after its call to `setMeshOutputs`. -Mesh shaders must also be marked with `@primitive_output(OutputType, numOutputs)`, which is similar to `@vertex_output` except it describes the primitive outputs. +To produce primitives, the workgroup as a whole must make `numPrimitives` calls to the `setPrimitive(i: u32, primitive: P)` builtin function. This establishes `primitive` as the value of the `i`'th primitive. `P` is the type given in the `@primitive_output` attribute. `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. The following `@builtin` attributes are allowed: -### Mesh shader outputs +- `triangle_indices`, `line_indices`, or `point_index`: The annotated member must be of type `vec3`, `vec2`, or `u32`. -Vertex outputs from mesh shaders function identically to outputs of vertex shaders, and as such must have a field with `@builtin(position)`. + The member's components are indices (or, its value is an index) into the list of vertices generated by this workgroup, identifying the vertices of the primitive to be drawn. These indices must be less than the value of `numVertices` passed to `setMeshOutputs`. -Primitive outputs from mesh shaders have some additional builtins they can set. These include `@builtin(cull_primitive)`, which must be a boolean value. If this is set to true, then the primitive is skipped during rendering. All non-builtin primitive outputs must be decorated with `@per_primitive`. + The type `P` must contain exactly one member with one of these attributes, determining what sort of primitives the mesh shader generates. -Mesh shader primitive outputs must also specify exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`. This determines the output topology of the mesh shader, and must match the output topology of the pipeline descriptor the mesh shader is used with. These must be of type `vec3`, `vec2`, and `u32` respectively. When setting this, each of the indices must be less than the number of vertices declared in `setMeshOutputs`. +- `cull_primitive`: The annotated member must be of type `bool`. If it is true, then the primitive is skipped during rendering. -Additionally, the `@location` attributes from the vertex and primitive outputs can't overlap. +Every member of `P` with a `@location` attribute must either have a `@per_primitive` attribute, or be part of a struct type that appears in the primitive data as a struct member with the `@per_primitive` attribute. -Before exiting, the mesh shader must call `setMeshOutputs(numVertices: u32, numIndices: u32)`, which declares the number of vertices and indices that will be written to. These must be less than the corresponding maximums set in `@vertex_output` and `@primitive_output`. The mesh shader must then write to exactly this range of vertices and primitives. A varying member with `@per_primitive` cannot be used in function interfaces except as a primitive output for mesh shaders or as input for fragment shaders. +The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader. -The mesh shader can write to vertices using the `setVertex(idx: u32, vertex: VertexOutput)` where `VertexOutput` is replaced with the vertex type declared in `@vertex_output`, and `idx` is the index of the vertex to write. Similarly, the mesh shader can write to vertices using `setPrimitive(idx: u32, primitive: PrimitiveOutput)`. These can be written to multiple times, however unsynchronized writes are undefined behavior. The primitives and indices are shared across the entire mesh shader workgroup. +It is possible to write to the same vertex or primitive index repeatedly. Since the implicit arrays written by `setVertex` and `setPrimitive` are shared by the workgroup, data races on writes to the same index for a given type are undefined behavior. ### Fragment shader @@ -210,4 +267,4 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; } -``` \ No newline at end of file +``` diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 2856872db27..94159ae7bf6 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -320,14 +320,21 @@ pub enum ConservativeDepth { #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -#[allow(missing_docs)] // The names are self evident pub enum ShaderStage { + /// A vertex shader, in a render pipeline. Vertex, - Fragment, - Compute, - // Mesh shader stages + + /// A task shader, in a mesh render pipeline. Task, + + /// A mesh shader, in a mesh render pipeline. Mesh, + + /// A fragment shader, in a render pipeline. + Fragment, + + /// Compute pipeline shader. + Compute, } impl ShaderStage { @@ -964,6 +971,9 @@ pub enum Binding { /// Indexed location. /// + /// This is a value passed to a [`Fragment`] shader from a [`Vertex`] or + /// [`Mesh`] shader. + /// /// Values passed from the [`Vertex`] stage to the [`Fragment`] stage must /// have their `interpolation` defaulted (i.e. not `None`) by the front end /// as appropriate for that language. @@ -977,6 +987,7 @@ pub enum Binding { /// interpolation must be `Flat`. /// /// [`Vertex`]: crate::ShaderStage::Vertex + /// [`Mesh`]: crate::ShaderStage::Mesh /// [`Fragment`]: crate::ShaderStage::Fragment Location { location: u32, @@ -1751,10 +1762,12 @@ pub enum Expression { query: Handle, committed: bool, }, + /// Result of a [`SubgroupBallot`] statement. /// /// [`SubgroupBallot`]: Statement::SubgroupBallot SubgroupBallotResult, + /// Result of a [`SubgroupCollectiveOperation`] or [`SubgroupGather`] statement. /// /// [`SubgroupCollectiveOperation`]: Statement::SubgroupCollectiveOperation @@ -2343,7 +2356,9 @@ pub struct EntryPoint { pub workgroup_size_overrides: Option<[Option>; 3]>, /// The entrance function. pub function: Function, - /// The information relating to a mesh shader + /// Information for [`Mesh`] shaders. + /// + /// [`Mesh`]: ShaderStage::Mesh pub mesh_info: Option, /// The unique global variable used as a task payload from task shader to mesh shader pub task_payload: Option>, @@ -2523,6 +2538,51 @@ pub struct DocComments { pub module: Vec, } +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum MeshOutputTopology { + Points, + Lines, + Triangles, +} + +#[derive(Debug, Clone)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +#[allow(dead_code)] +pub struct MeshStageInfo { + pub topology: MeshOutputTopology, + pub max_vertices: u32, + pub max_vertices_override: Option>, + pub max_primitives: u32, + pub max_primitives_override: Option>, + pub vertex_output_type: Handle, + pub primitive_output_type: Handle, +} + +/// Mesh shader intrinsics +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serialize", derive(Serialize))] +#[cfg_attr(feature = "deserialize", derive(Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] +pub enum MeshFunction { + SetMeshOutputs { + vertex_count: Handle, + primitive_count: Handle, + }, + SetVertex { + index: Handle, + value: Handle, + }, + SetPrimitive { + index: Handle, + value: Handle, + }, +} + /// Shader module. /// /// A module is a set of constants, global variables and functions, as well as @@ -2611,48 +2671,3 @@ pub struct Module { /// Doc comments. pub doc_comments: Option>, } - -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "serialize", derive(Serialize))] -#[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -pub enum MeshOutputTopology { - Points, - Lines, - Triangles, -} - -#[derive(Debug, Clone)] -#[cfg_attr(feature = "serialize", derive(Serialize))] -#[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -#[allow(dead_code)] -pub struct MeshStageInfo { - pub topology: MeshOutputTopology, - pub max_vertices: u32, - pub max_vertices_override: Option>, - pub max_primitives: u32, - pub max_primitives_override: Option>, - pub vertex_output_type: Handle, - pub primitive_output_type: Handle, -} - -/// Mesh shader intrinsics -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "serialize", derive(Serialize))] -#[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -pub enum MeshFunction { - SetMeshOutputs { - vertex_count: Handle, - primitive_count: Handle, - }, - SetVertex { - index: Handle, - value: Handle, - }, - SetPrimitive { - index: Handle, - value: Handle, - }, -} diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 101ea046487..6d9fd7f6a08 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -1151,7 +1151,7 @@ impl FunctionInfo { let _ = self.add_ref(index); let _ = self.add_ref(value); let ty = - self.expressions[value.index()].ty.clone().handle().ok_or( + self.expressions[value.index()].ty.handle().ok_or( FunctionError::InvalidMeshShaderOutputType(value).with_span(), )?; @@ -1244,14 +1244,15 @@ impl FunctionInfo { Ok(()) } + /// Update this function's mesh shader info, given that it calls `callee`. fn try_update_mesh_info( &mut self, - other: &FunctionMeshShaderInfo, + callee: &FunctionMeshShaderInfo, ) -> Result<(), WithSpan> { - if let &Some(ref other_vertex) = &other.vertex_type { + if let &Some(ref other_vertex) = &callee.vertex_type { self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?; } - if let &Some(ref other_primitive) = &other.vertex_type { + if let &Some(ref other_primitive) = &callee.vertex_type { self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?; } Ok(()) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 1fed0fda529..9f5cb278330 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -856,13 +856,14 @@ impl super::Validator { { return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span()); } - // Cannot have any other built-ins or @location outputs as those are per-vertex or per-primitive - if ep.stage == crate::ShaderStage::Task - && (!result_built_ins.contains(&crate::BuiltIn::MeshTaskSize) - || result_built_ins.len() != 1 - || !self.location_mask.is_empty()) - { - return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); + // Task shaders must have a single `MeshTaskSize` output, and nothing else. + if ep.stage == crate::ShaderStage::Task { + let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize) + && result_built_ins.len() == 1 + && self.location_mask.is_empty(); + if !ok { + return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); + } } if !self.blend_src_mask.is_empty() { info.dual_source_blending = true; @@ -960,8 +961,10 @@ impl super::Validator { } } + // If this is a `Mesh` entry point, check its interface. if let &Some(ref mesh_info) = &ep.mesh_info { - // Technically it is allowed to not output anything + // Mesh shaders don't return any value. All their results are supplied through + // [`SetVertex`] and [`SetPrimitive`] calls. // TODO: check that only the allowed builtins are used here if let Some(used_vertex_type) = info.mesh_shader_info.vertex_type { if used_vertex_type.0 != mesh_info.vertex_output_type { diff --git a/wgpu/src/api/render_pass.rs b/wgpu/src/api/render_pass.rs index 9103264eed9..c73394db261 100644 --- a/wgpu/src/api/render_pass.rs +++ b/wgpu/src/api/render_pass.rs @@ -231,9 +231,9 @@ impl RenderPass<'_> { self.inner.draw_indexed(indices, base_vertex, instances); } - /// Draws using a mesh shader pipeline. + /// Draws using a mesh pipeline. /// - /// The current pipeline must be a mesh shader pipeline. + /// The current pipeline must be a mesh pipeline. /// /// If the current pipeline has a task shader, run it with an workgroup for /// every `vec3(i, j, k)` where `i`, `j`, and `k` are between `0` and @@ -290,7 +290,7 @@ impl RenderPass<'_> { .draw_indexed_indirect(&indirect_buffer.inner, indirect_offset); } - /// Draws using a mesh shader pipeline, + /// Draws using a mesh pipeline, /// based on the contents of the `indirect_buffer` /// /// This is like calling [`RenderPass::draw_mesh_tasks`] but the contents of the call are specified in the `indirect_buffer`. diff --git a/wgpu/src/api/render_pipeline.rs b/wgpu/src/api/render_pipeline.rs index be16d91f27a..35b74100d00 100644 --- a/wgpu/src/api/render_pipeline.rs +++ b/wgpu/src/api/render_pipeline.rs @@ -152,13 +152,15 @@ static_assertions::assert_impl_all!(FragmentState<'_>: Send, Sync); pub struct TaskState<'a> { /// The compiled shader module for this stage. pub module: &'a ShaderModule, - /// The name of the entry point in the compiled shader to use. + + /// The name of the task shader entry point in the shader module to use. /// - /// If [`Some`], there must be a vertex-stage shader entry point with this name in `module`. - /// Otherwise, expect exactly one vertex-stage entry point in `module`, which will be - /// selected. + /// If [`Some`], there must be a task shader entry point with the given name + /// in `module`. Otherwise, there must be exactly one task shader entry + /// point in `module`, which will be selected. pub entry_point: Option<&'a str>, - /// Advanced options for when this pipeline is compiled + + /// Advanced options for when this pipeline is compiled. /// /// This implements `Default`, and for most users can be set to `Default::default()` pub compilation_options: PipelineCompilationOptions<'a>, @@ -299,8 +301,15 @@ pub struct MeshPipelineDescriptor<'a> { /// /// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout pub layout: Option<&'a PipelineLayout>, - /// The compiled task stage and its entry point. + + /// The mesh pipeline's task shader. + /// + /// If this is `None`, the mesh pipeline has no task shader. Executing a + /// mesh drawing command simply dispatches a grid of mesh shaders directly. + /// + /// [`draw_mesh_tasks`]: RenderPass::draw_mesh_tasks pub task: Option>, + /// The compiled mesh stage and its entry point pub mesh: MeshState<'a>, /// The properties of the pipeline at the primitive assembly and rasterization level. From 41b654ce811f9b88b95c83d7f9b8d88af48bff17 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Thu, 2 Oct 2025 00:28:47 -0700 Subject: [PATCH 017/110] mesh_shading.md: more tweaks --- docs/api-specs/mesh_shading.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index fcead0898bb..5990e63e871 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -151,19 +151,19 @@ A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh sha Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes. -A mesh shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. - If the mesh shader pipeline has a task shader entry point with a `@payload(G)` attribute, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. If the mesh shader pipeline does not have a task shader entry point, or the task shader entry point does not have a `@payload(G)` attribute, then the mesh shader entry point must not have any `@payload` attribute. A mesh shader entry point must have the following attributes: +- `@workgroup_size`: this has the same meaning as when it appears on a compute shader entry point. + - `@vertex_output(V, NV)`: This indicates that the mesh shader workgroup will generate at most `NV` vertex values, each of type `V`. - `@primitive_output(P, NP)`: This indicates that the mesh shader workgroup will generate at most `NP` primitives, each of type `P`. -Each mesh shader entry point invocation must call the `setMeshOutputs(numVertices: u32, numPrimitives: u32)` builtin function exactly once, in uniform control flow. The values passed by each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) determine how many vertices (values of type `V`) and primitives (values of type `P`) the workgroup must produce. This call essentially establishes two implicit arrays of vertex and primitive values, shared across the workgroup, for invocations to populate. +Before generating any results, each mesh shader entry point invocation must call the `setMeshOutputs(numVertices: u32, numPrimitives: u32)` builtin function exactly once, in uniform control flow. The values passed by each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) determine how many vertices (values of type `V`) and primitives (values of type `P`) the workgroup must produce. This call essentially establishes two implicit arrays of vertex and primitive values, shared across the workgroup, for invocations to populate. The `numVertices` and `numPrimitives` arguments must be no greater than `NV` and `NP` from the `@vertex_output` and `@primitive_output` attributes. From 33ed0a66f4baf09b9692631e8b36140daee238f5 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 2 Oct 2025 12:22:11 -0500 Subject: [PATCH 018/110] Ran cargo fmt --- naga/src/valid/analyzer.rs | 8 ++++---- naga/src/valid/interface.rs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 6d9fd7f6a08..84390c3e5cd 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -1150,10 +1150,10 @@ impl FunctionInfo { | &crate::MeshFunction::SetPrimitive { index, value } => { let _ = self.add_ref(index); let _ = self.add_ref(value); - let ty = - self.expressions[value.index()].ty.handle().ok_or( - FunctionError::InvalidMeshShaderOutputType(value).with_span(), - )?; + let ty = self.expressions[value.index()] + .ty + .handle() + .ok_or(FunctionError::InvalidMeshShaderOutputType(value).with_span())?; if matches!(func, crate::MeshFunction::SetVertex { .. }) { self.try_update_mesh_vertex_type(ty, value)?; diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 9f5cb278330..d40db4b45f8 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -859,8 +859,8 @@ impl super::Validator { // Task shaders must have a single `MeshTaskSize` output, and nothing else. if ep.stage == crate::ShaderStage::Task { let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize) - && result_built_ins.len() == 1 - && self.location_mask.is_empty(); + && result_built_ins.len() == 1 + && self.location_mask.is_empty(); if !ok { return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); } From 53ecb39b7171bfa13a153efc6233d3bb9e6e9adb Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 2 Oct 2025 13:03:04 -0500 Subject: [PATCH 019/110] Small tweaks --- docs/api-specs/mesh_shading.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 5990e63e871..c3f80e79a67 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -138,9 +138,9 @@ A function with the `@task` attribute is a **task shader entry point**. A mesh s A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. -A task shader entry point must return a `vec3` value. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section. +A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. -If a task shader entry point has a `@payload(G)` property, then `G` must be the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. +A task shader entry point must return a `vec3` value. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section. Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid; and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload. @@ -151,9 +151,9 @@ A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh sha Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes. -If the mesh shader pipeline has a task shader entry point with a `@payload(G)` attribute, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. +If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable, and the sizes must match. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. -If the mesh shader pipeline does not have a task shader entry point, or the task shader entry point does not have a `@payload(G)` attribute, then the mesh shader entry point must not have any `@payload` attribute. +If the mesh shader pipeline does not have a task shader entry point, then the mesh shader entry point must not have any `@payload` attribute. A mesh shader entry point must have the following attributes: @@ -167,7 +167,7 @@ Before generating any results, each mesh shader entry point invocation must call The `numVertices` and `numPrimitives` arguments must be no greater than `NV` and `NP` from the `@vertex_output` and `@primitive_output` attributes. -To produce vertex data, the workgroup as a whole must make `numVertices` calls to the `setVertex(i: u32, vertex: V)` builtin function. This establishes `vertex` as the value of the `i`'th vertex. `V` is the type given in the `@vertex_output` attribute. `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a '@builtin(position)`, and so on. An invocation may only call `setVertex` after its call to `setMeshOutputs`. +To produce vertex data, the workgroup as a whole must make `numVertices` calls to the `setVertex(i: u32, vertex: V)` builtin function. This establishes `vertex` as the value of the `i`'th vertex. `V` is the type given in the `@vertex_output` attribute. `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on. An invocation may only call `setVertex` after its call to `setMeshOutputs`. To produce primitives, the workgroup as a whole must make `numPrimitives` calls to the `setPrimitive(i: u32, primitive: P)` builtin function. This establishes `primitive` as the value of the `i`'th primitive. `P` is the type given in the `@primitive_output` attribute. `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. The following `@builtin` attributes are allowed: From c4e3eefe014ff92e5a362e226b606dee5587a27c Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 15 Oct 2025 16:24:35 -0700 Subject: [PATCH 020/110] [naga] Move definition of `ShaderStage::compute_like` to `proc`. Move the definition of `naga::ShaderStage::compute_like` from `naga::ir` into `naga::proc`. We generally want ot keep methods out of `naga::ir`, since the IR itself is complicated enough already. --- naga/src/ir/mod.rs | 10 ---------- naga/src/proc/mod.rs | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 94159ae7bf6..ad03f542d09 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -337,16 +337,6 @@ pub enum ShaderStage { Compute, } -impl ShaderStage { - // TODO: make more things respect this - pub const fn compute_like(self) -> bool { - match self { - Self::Vertex | Self::Fragment => false, - Self::Compute | Self::Task | Self::Mesh => true, - } - } -} - /// Addressing space of variables. #[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] #[cfg_attr(feature = "serialize", derive(Serialize))] diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 5743e96a33e..7b90aa35512 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -631,6 +631,16 @@ pub fn flatten_compose<'arenas>( .take(size) } +impl super::ShaderStage { + // TODO: make more things respect this + pub const fn compute_like(self) -> bool { + match self { + Self::Vertex | Self::Fragment => false, + Self::Compute | Self::Task | Self::Mesh => true, + } + } +} + #[test] fn test_matrix_size() { let module = crate::Module::default(); From 8c9287d634f13fd47ec709406d83e952a54a496c Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 15 Oct 2025 17:37:59 -0700 Subject: [PATCH 021/110] Replace TODO comment with followup issue. --- naga/src/valid/interface.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index d40db4b45f8..f33e8fc8133 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -965,7 +965,6 @@ impl super::Validator { if let &Some(ref mesh_info) = &ep.mesh_info { // Mesh shaders don't return any value. All their results are supplied through // [`SetVertex`] and [`SetPrimitive`] calls. - // TODO: check that only the allowed builtins are used here if let Some(used_vertex_type) = info.mesh_shader_info.vertex_type { if used_vertex_type.0 != mesh_info.vertex_output_type { return Err(EntryPointError::WrongMeshOutputType From 3a8399de7ca78521606c6180cee5d217c4fc70e3 Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Wed, 15 Oct 2025 22:24:56 -0500 Subject: [PATCH 022/110] Update analyzer.rs Co-authored-by: Jim Blandy --- naga/src/valid/analyzer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 84390c3e5cd..5ce80f20fb9 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -1252,7 +1252,7 @@ impl FunctionInfo { if let &Some(ref other_vertex) = &callee.vertex_type { self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?; } - if let &Some(ref other_primitive) = &callee.vertex_type { + if let &Some(ref other_primitive) = &callee.primitive_type { self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?; } Ok(()) From d92fe673e65a91e5aee86539e16ce2248bbb5721 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 15 Oct 2025 23:08:59 -0500 Subject: [PATCH 023/110] Removed stuff in accordance with Jim's recommendation --- Cargo.lock | 4 ++-- naga/src/valid/interface.rs | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 992defc7d5f..d8c550ff796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3997,8 +3997,8 @@ dependencies = [ "fastrand", "getrandom 0.3.3", "once_cell", - "rustix 1.0.8", - "windows-sys 0.61.0", + "rustix 1.1.2", + "windows-sys 0.52.0", ] [[package]] diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index f33e8fc8133..6aebd33a64e 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -851,9 +851,7 @@ impl super::Validator { { return Err(EntryPointError::MissingVertexOutputPosition.with_span()); } - if ep.stage == crate::ShaderStage::Mesh - && (!result_built_ins.is_empty() || !self.location_mask.is_empty()) - { + if ep.stage == crate::ShaderStage::Mesh { return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span()); } // Task shaders must have a single `MeshTaskSize` output, and nothing else. From 2dc409028517c9da3bfb5852fca04f5b33296e6d Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 15 Oct 2025 20:14:08 -0700 Subject: [PATCH 024/110] minor changes for readability --- naga/src/valid/interface.rs | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 6aebd33a64e..550f200150a 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -393,13 +393,23 @@ impl VaryingContext<'_> { { return Err(VaryingError::NotIOShareableType(ty)); } - if !per_primitive && self.mesh_output_type == MeshOutputType::PrimitiveOutput { - return Err(VaryingError::MissingPerPrimitive); - } else if per_primitive - && ((self.stage != crate::ShaderStage::Fragment || self.output) - && self.mesh_output_type != MeshOutputType::PrimitiveOutput) - { - return Err(VaryingError::InvalidPerPrimitive); + + // Check whether `per_primitive` is appropriate for this stage and direction. + if self.mesh_output_type == MeshOutputType::PrimitiveOutput { + // All mesh shader `Location` outputs must be `per_primitive`. + if !per_primitive { + return Err(VaryingError::MissingPerPrimitive); + } + } else if self.stage == crate::ShaderStage::Fragment && !self.output { + // Fragment stage inputs may be `per_primitive`. We'll only + // know if these are correct when the whole mesh pipeline is + // created and we're paired with a specific mesh or vertex + // shader. + } else { + // All other `Location` bindings must not be `per_primitive`. + if per_primitive { + return Err(VaryingError::InvalidPerPrimitive); + } } if let Some(blend_src) = blend_src { @@ -959,18 +969,18 @@ impl super::Validator { } } - // If this is a `Mesh` entry point, check its interface. + // If this is a `Mesh` entry point, check the bindings of its vertex and primitive output types. if let &Some(ref mesh_info) = &ep.mesh_info { // Mesh shaders don't return any value. All their results are supplied through // [`SetVertex`] and [`SetPrimitive`] calls. - if let Some(used_vertex_type) = info.mesh_shader_info.vertex_type { - if used_vertex_type.0 != mesh_info.vertex_output_type { + if let Some((used_vertex_type, _)) = info.mesh_shader_info.vertex_type { + if used_vertex_type != mesh_info.vertex_output_type { return Err(EntryPointError::WrongMeshOutputType .with_span_handle(mesh_info.vertex_output_type, &module.types)); } } - if let Some(used_primitive_type) = info.mesh_shader_info.primitive_type { - if used_primitive_type.0 != mesh_info.primitive_output_type { + if let Some((used_primitive_type, _)) = info.mesh_shader_info.primitive_type { + if used_primitive_type != mesh_info.primitive_output_type { return Err(EntryPointError::WrongMeshOutputType .with_span_handle(mesh_info.primitive_output_type, &module.types)); } From 1ec734b3528b08c69bcf425f2c953274d5ea812a Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Wed, 15 Oct 2025 20:38:11 -0700 Subject: [PATCH 025/110] Pull mesh shader output type validation out into its own function. --- naga/src/valid/interface.rs | 113 ++++++++++++++++++++---------------- 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 550f200150a..891a87c5cbf 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -747,6 +747,58 @@ impl super::Validator { Ok(()) } + /// Validate the mesh shader output type `ty`, used as `mesh_output_type`. + fn validate_mesh_output_type( + &mut self, + ep: &crate::EntryPoint, + module: &crate::Module, + ty: Handle, + mesh_output_type: MeshOutputType, + ) -> Result<(), WithSpan> { + if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) { + return Err(EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types)); + } + let mut result_built_ins = crate::FastHashSet::default(); + let mut ctx = VaryingContext { + stage: ep.stage, + output: true, + types: &module.types, + type_info: &self.types, + location_mask: &mut self.location_mask, + blend_src_mask: &mut self.blend_src_mask, + built_ins: &mut result_built_ins, + capabilities: self.capabilities, + flags: self.flags, + mesh_output_type, + }; + ctx.validate(ep, ty, None) + .map_err_inner(|e| EntryPointError::Result(e).with_span())?; + if mesh_output_type == MeshOutputType::PrimitiveOutput { + let mut num_indices_builtins = 0; + if result_built_ins.contains(&crate::BuiltIn::PointIndex) { + num_indices_builtins += 1; + } + if result_built_ins.contains(&crate::BuiltIn::LineIndices) { + num_indices_builtins += 1; + } + if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) { + num_indices_builtins += 1; + } + if num_indices_builtins != 1 { + return Err(EntryPointError::InvalidMeshPrimitiveOutputType + .with_span_handle(ty, &module.types)); + } + } else if mesh_output_type == MeshOutputType::VertexOutput + && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) + { + return Err( + EntryPointError::MissingVertexOutputPosition.with_span_handle(ty, &module.types) + ); + } + + Ok(()) + } + pub(super) fn validate_entry_point( &mut self, ep: &crate::EntryPoint, @@ -986,55 +1038,18 @@ impl super::Validator { } } - for (ty, mesh_output_type) in [ - (mesh_info.vertex_output_type, MeshOutputType::VertexOutput), - ( - mesh_info.primitive_output_type, - MeshOutputType::PrimitiveOutput, - ), - ] { - if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) { - return Err( - EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types) - ); - } - let mut result_built_ins = crate::FastHashSet::default(); - let mut ctx = VaryingContext { - stage: ep.stage, - output: true, - types: &module.types, - type_info: &self.types, - location_mask: &mut self.location_mask, - blend_src_mask: &mut self.blend_src_mask, - built_ins: &mut result_built_ins, - capabilities: self.capabilities, - flags: self.flags, - mesh_output_type, - }; - ctx.validate(ep, ty, None) - .map_err_inner(|e| EntryPointError::Result(e).with_span())?; - if mesh_output_type == MeshOutputType::PrimitiveOutput { - let mut num_indices_builtins = 0; - if result_built_ins.contains(&crate::BuiltIn::PointIndex) { - num_indices_builtins += 1; - } - if result_built_ins.contains(&crate::BuiltIn::LineIndices) { - num_indices_builtins += 1; - } - if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) { - num_indices_builtins += 1; - } - if num_indices_builtins != 1 { - return Err(EntryPointError::InvalidMeshPrimitiveOutputType - .with_span_handle(ty, &module.types)); - } - } else if mesh_output_type == MeshOutputType::VertexOutput - && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) - { - return Err(EntryPointError::MissingVertexOutputPosition - .with_span_handle(ty, &module.types)); - } - } + self.validate_mesh_output_type( + ep, + module, + mesh_info.vertex_output_type, + MeshOutputType::VertexOutput, + )?; + self.validate_mesh_output_type( + ep, + module, + mesh_info.primitive_output_type, + MeshOutputType::PrimitiveOutput, + )?; } else if info.mesh_shader_info.vertex_type.is_some() || info.mesh_shader_info.primitive_type.is_some() { From 9ef0ed580e8cd21cba47f22ac7aad6b490339cff Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Thu, 16 Oct 2025 08:08:06 -0700 Subject: [PATCH 026/110] doc fixes --- naga/src/ir/mod.rs | 17 ++++++++++++----- naga/src/valid/analyzer.rs | 29 +++++++++++++++++++++++++++++ naga/src/valid/interface.rs | 15 ++++++++++----- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index ad03f542d09..a8a5d220463 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -983,16 +983,23 @@ pub enum Binding { location: u32, interpolation: Option, sampling: Option, + /// Optional `blend_src` index used for dual source blending. /// See blend_src: Option, + /// Whether the binding is a per-primitive binding for use with mesh shaders. - /// This is required to match for mesh and fragment shader stages. - /// This is merely an extra attribute on a binding. You still may not have - /// a per-vertex and per-primitive input with the same location. /// - /// Per primitive values are not interpolated at all and are not dependent on the vertices - /// or pixel location. For example, it may be used to store a non-interpolated normal vector. + /// This must be `true` if this binding is a mesh shader primitive output, or such + /// an output's corresponding fragment shader input. It must be `false` otherwise. + /// + /// A stage's outputs must all have unique `location` numbers, regardless of + /// whether they are per-primitive; a mesh shader's per-vertex and per-primitive + /// outputs share the same location numbering space. + /// + /// Per primitive values are not interpolated at all and are not dependent on the + /// vertices or pixel location. For example, it may be used to store a + /// non-interpolated normal vector. per_primitive: bool, }, } diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 5ce80f20fb9..bbf00508e00 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -91,7 +91,16 @@ struct FunctionUniformity { #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[cfg_attr(test, derive(PartialEq))] pub struct FunctionMeshShaderInfo { + /// The type of value this function passes to [`SetVertex`], and the + /// expression that first established it. + /// + /// [`SetVertex`]: crate::ir::MeshFunction::SetVertex pub vertex_type: Option<(Handle, Handle)>, + + /// The type of value this function passes to [`SetPrimitive`], and the + /// expression that first established it. + /// + /// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive pub primitive_type: Option<(Handle, Handle)>, } @@ -313,6 +322,7 @@ pub struct FunctionInfo { /// validation. diagnostic_filter_leaf: Option>, + /// Mesh shader info for this function and its callees. pub mesh_shader_info: FunctionMeshShaderInfo, } @@ -502,6 +512,7 @@ impl FunctionInfo { *mine |= *other; } + // Inherit mesh output types from our callees. self.try_update_mesh_info(&callee.mesh_shader_info)?; Ok(FunctionUniformity { @@ -1210,6 +1221,15 @@ impl FunctionInfo { Ok(combined_uniformity) } + /// Note the type of value passed to [`SetVertex`]. + /// + /// Record that this function passed a value of type `ty` as the second + /// argument to the [`SetVertex`] builtin function. All calls to + /// `SetVertex` must pass the same type, and this must match the + /// function's [`vertex_output_type`]. + /// + /// [`SetVertex`]: crate::ir::MeshFunction::SetVertex + /// [`vertex_output_type`]: crate::ir::MeshStageInfo::vertex_output_type fn try_update_mesh_vertex_type( &mut self, ty: Handle, @@ -1227,6 +1247,15 @@ impl FunctionInfo { Ok(()) } + /// Note the type of value passed to [`SetPrimitive`]. + /// + /// Record that this function passed a value of type `ty` as the second + /// argument to the [`SetPrimitive`] builtin function. All calls to + /// `SetPrimitive` must pass the same type, and this must match the + /// function's [`primitive_output_type`]. + /// + /// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive + /// [`primitive_output_type`]: crate::ir::MeshStageInfo::primitive_output_type fn try_update_mesh_primitive_type( &mut self, ty: Handle, diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 891a87c5cbf..5768f56e641 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -1021,7 +1021,8 @@ impl super::Validator { } } - // If this is a `Mesh` entry point, check the bindings of its vertex and primitive output types. + // If this is a `Mesh` entry point, check its vertex and primitive output types. + // We verified previously that only mesh shaders can have `mesh_info`. if let &Some(ref mesh_info) = &ep.mesh_info { // Mesh shaders don't return any value. All their results are supplied through // [`SetVertex`] and [`SetPrimitive`] calls. @@ -1050,10 +1051,14 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; - } else if info.mesh_shader_info.vertex_type.is_some() - || info.mesh_shader_info.primitive_type.is_some() - { - return Err(EntryPointError::UnexpectedMeshShaderOutput.with_span()); + } else { + // This is not a `Mesh` entry point, so ensure that it never tries to produce + // vertices or primitives. + if info.mesh_shader_info.vertex_type.is_some() + || info.mesh_shader_info.primitive_type.is_some() + { + return Err(EntryPointError::UnexpectedMeshShaderOutput.with_span()); + } } Ok(info) From 1173b0f578da4921a530f755f4cd85bb9b42cf62 Mon Sep 17 00:00:00 2001 From: Jim Blandy Date: Thu, 16 Oct 2025 10:01:21 -0700 Subject: [PATCH 027/110] remove duplicated task payload validation --- naga/src/valid/interface.rs | 51 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 5768f56e641..db6d800bd31 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -839,20 +839,38 @@ impl super::Validator { .validate_function(&ep.function, module, mod_info, true) .map_err(WithSpan::into_other)?; - if let Some(handle) = ep.task_payload { - if ep.stage != crate::ShaderStage::Task && ep.stage != crate::ShaderStage::Mesh { - return Err(EntryPointError::UnexpectedTaskPayload.with_span()); + // Validate the task shader payload. + match ep.stage { + // Task shaders must produce a payload. + crate::ShaderStage::Task => { + let Some(handle) = ep.task_payload else { + return Err(EntryPointError::ExpectedTaskPayload.with_span()); + }; + if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { + return Err(EntryPointError::TaskPayloadWrongAddressSpace + .with_span_handle(handle, &module.global_variables)); + } + info.insert_global_use(GlobalUse::READ | GlobalUse::WRITE, handle); } - if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { - return Err(EntryPointError::TaskPayloadWrongAddressSpace.with_span()); + + // Mesh shaders may accept a payload. + crate::ShaderStage::Mesh => { + if let Some(handle) = ep.task_payload { + if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { + return Err(EntryPointError::TaskPayloadWrongAddressSpace + .with_span_handle(handle, &module.global_variables)); + } + info.insert_global_use(GlobalUse::READ, handle); + } + } + + // Other stages must not have a payload. + _ => { + if let Some(handle) = ep.task_payload { + return Err(EntryPointError::UnexpectedTaskPayload + .with_span_handle(handle, &module.global_variables)); + } } - // Make sure that this is always present in the outputted shader - let uses = if ep.stage == crate::ShaderStage::Mesh { - GlobalUse::READ - } else { - GlobalUse::READ | GlobalUse::WRITE - }; - info.insert_global_use(uses, handle); } { @@ -949,15 +967,6 @@ impl super::Validator { } } - if let Some(task_payload) = ep.task_payload { - if module.global_variables[task_payload].space != crate::AddressSpace::TaskPayload { - return Err(EntryPointError::TaskPayloadWrongAddressSpace - .with_span_handle(task_payload, &module.global_variables)); - } - } else if ep.stage == crate::ShaderStage::Task { - return Err(EntryPointError::ExpectedTaskPayload.with_span()); - } - self.ep_resource_bindings.clear(); for (var_handle, var) in module.global_variables.iter() { let usage = info[var_handle]; From 258e7e642ab414a318843e76877a12b9911bf72d Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 15:43:01 -0500 Subject: [PATCH 028/110] Quick little changes --- naga/src/back/glsl/mod.rs | 2 +- naga/src/back/hlsl/writer.rs | 2 +- naga/src/back/mod.rs | 6 +++--- naga/src/back/pipeline_constants.rs | 4 ++-- naga/src/back/spv/writer.rs | 5 ++++- 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 6376b39c58b..37bf318c4f8 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -1879,7 +1879,7 @@ impl<'a, W: Write> Writer<'a, W> { writeln!(self.out, ") {{")?; if self.options.zero_initialize_workgroup_memory - && ctx.ty.is_compute_entry_point(self.module) + && ctx.ty.is_compute_like_entry_point(self.module) { self.write_workgroup_variables_initialization(&ctx)?; } diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index 8d1aabded61..6f0ba814a52 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -1765,7 +1765,7 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { module: &Module, ) -> bool { self.options.zero_initialize_workgroup_memory - && func_ctx.ty.is_compute_entry_point(module) + && func_ctx.ty.is_compute_like_entry_point(module) && module.global_variables.iter().any(|(handle, var)| { !func_ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup }) diff --git a/naga/src/back/mod.rs b/naga/src/back/mod.rs index 0d13d63dd9b..8be763234e7 100644 --- a/naga/src/back/mod.rs +++ b/naga/src/back/mod.rs @@ -139,11 +139,11 @@ pub enum FunctionType { } impl FunctionType { - /// Returns true if the function is an entry point for a compute shader. - pub fn is_compute_entry_point(&self, module: &crate::Module) -> bool { + /// Returns true if the function is an entry point for a compute-like shader. + pub fn is_compute_like_entry_point(&self, module: &crate::Module) -> bool { match *self { FunctionType::EntryPoint(index) => { - module.entry_points[index as usize].stage == crate::ShaderStage::Compute + module.entry_points[index as usize].stage.compute_like() } FunctionType::Function(_) => false, } diff --git a/naga/src/back/pipeline_constants.rs b/naga/src/back/pipeline_constants.rs index c009082a3c9..109cc591e74 100644 --- a/naga/src/back/pipeline_constants.rs +++ b/naga/src/back/pipeline_constants.rs @@ -309,13 +309,13 @@ fn process_mesh_shader_overrides( mesh_info.max_vertices = module .to_ctx() .eval_expr_to_u32(adjusted_global_expressions[r#override]) - .map_err(|_| PipelineConstantError::NegativeWorkgroupSize)?; + .map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?; } if let Some(r#override) = mesh_info.max_primitives_override { mesh_info.max_primitives = module .to_ctx() .eval_expr_to_u32(adjusted_global_expressions[r#override]) - .map_err(|_| PipelineConstantError::NegativeWorkgroupSize)?; + .map_err(|_| PipelineConstantError::NegativeMeshOutputMax)?; } } Ok(()) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 85d575cb9af..1e207fc7002 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1094,7 +1094,10 @@ impl Writer { super::ZeroInitializeWorkgroupMemoryMode::Polyfill, Some( ref mut interface @ FunctionInterface { - stage: crate::ShaderStage::Compute, + stage: + crate::ShaderStage::Compute + | crate::ShaderStage::Mesh + | crate::ShaderStage::Task, .. }, ), From 8885c5def0e8b23150bbc54a7e9baa41b2ff2f28 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 15:49:24 -0500 Subject: [PATCH 029/110] Another quick fix --- naga/src/valid/interface.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index db6d800bd31..8346e1e4ba9 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -405,11 +405,9 @@ impl VaryingContext<'_> { // know if these are correct when the whole mesh pipeline is // created and we're paired with a specific mesh or vertex // shader. - } else { + } else if per_primitive { // All other `Location` bindings must not be `per_primitive`. - if per_primitive { - return Err(VaryingError::InvalidPerPrimitive); - } + return Err(VaryingError::InvalidPerPrimitive); } if let Some(blend_src) = blend_src { From 1cc3e8516f691cd166c4906c993c60a0e02af9c0 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 16:01:35 -0500 Subject: [PATCH 030/110] Quick fix --- naga/src/valid/interface.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 8346e1e4ba9..6d122a8b2c5 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -473,10 +473,9 @@ impl VaryingContext<'_> { } } - // TODO: update this to reflect the fact that per-primitive outputs aren't interpolated for fragment and mesh stages let needs_interpolation = match self.stage { crate::ShaderStage::Vertex => self.output, - crate::ShaderStage::Fragment => !self.output, + crate::ShaderStage::Fragment => !self.output && !per_primitive, crate::ShaderStage::Compute | crate::ShaderStage::Task => false, crate::ShaderStage::Mesh => self.output, }; From 3be2c256ce3f5330ea2cf200b88ca4f2c9b34700 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 16:04:36 -0500 Subject: [PATCH 031/110] Removed unnecessary TODO statement --- naga/src/valid/function.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index 0ae2ffdb54f..4dca52b4687 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -1577,7 +1577,8 @@ impl super::Validator { crate::MeshFunction::SetVertex { index, value: _ } | crate::MeshFunction::SetPrimitive { index, value: _ } => { ensure_u32(index)?; - // TODO: ensure it is correct for the value + // Value is validated elsewhere (since the value type isn't known ahead of time but must match for a function + // and all functions it calls) } } } From 21d3cc703c127b40e52175c43d4d0110d975353b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 16:05:16 -0500 Subject: [PATCH 032/110] A --- naga/src/valid/function.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index 4dca52b4687..4caa6ffc451 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -1577,8 +1577,8 @@ impl super::Validator { crate::MeshFunction::SetVertex { index, value: _ } | crate::MeshFunction::SetPrimitive { index, value: _ } => { ensure_u32(index)?; - // Value is validated elsewhere (since the value type isn't known ahead of time but must match for a function - // and all functions it calls) + // Value is validated elsewhere (since the value type isn't known ahead of time but must match for all calls + // in a function or the function's called functions) } } } From d5c11d3b594a5aa8cdaea5f9c73934a3ba59f1c7 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 16:09:59 -0500 Subject: [PATCH 033/110] Tried to be more expressive --- naga/src/valid/function.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index 4caa6ffc451..0216c6ef7f6 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -217,7 +217,7 @@ pub enum FunctionError { EmitResult(Handle), #[error("Expression not visited by the appropriate statement")] UnvisitedExpression(Handle), - #[error("Expression {0:?} should be u32, but isn't")] + #[error("Expression {0:?} in mesh shader intrinsic call should be `u32` (is the expression a signed integer?)")] InvalidMeshFunctionCall(Handle), #[error("Mesh output types differ from {0:?} to {1:?}")] ConflictingMeshOutputTypes(Handle, Handle), From e7faff660c927c075b409ada6c0b7c217ba77fe2 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 20:36:59 -0500 Subject: [PATCH 034/110] Made functions only work in mesh shader entry points --- naga/src/valid/analyzer.rs | 56 ++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index bbf00508e00..14554573c9f 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -1147,34 +1147,36 @@ impl FunctionInfo { } FunctionUniformity::new() } - S::MeshFunction(func) => match &func { - // TODO: double check all of this uniformity stuff. I frankly don't fully understand all of it. - &crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - } => { - let _ = self.add_ref(vertex_count); - let _ = self.add_ref(primitive_count); - FunctionUniformity::new() - } - &crate::MeshFunction::SetVertex { index, value } - | &crate::MeshFunction::SetPrimitive { index, value } => { - let _ = self.add_ref(index); - let _ = self.add_ref(value); - let ty = self.expressions[value.index()] - .ty - .handle() - .ok_or(FunctionError::InvalidMeshShaderOutputType(value).with_span())?; - - if matches!(func, crate::MeshFunction::SetVertex { .. }) { - self.try_update_mesh_vertex_type(ty, value)?; - } else { - self.try_update_mesh_primitive_type(ty, value)?; - }; - - FunctionUniformity::new() + S::MeshFunction(func) => { + self.available_stages |= ShaderStages::MESH; + match &func { + // TODO: double check all of this uniformity stuff. I frankly don't fully understand all of it. + &crate::MeshFunction::SetMeshOutputs { + vertex_count, + primitive_count, + } => { + let _ = self.add_ref(vertex_count); + let _ = self.add_ref(primitive_count); + FunctionUniformity::new() + } + &crate::MeshFunction::SetVertex { index, value } + | &crate::MeshFunction::SetPrimitive { index, value } => { + let _ = self.add_ref(index); + let _ = self.add_ref(value); + let ty = self.expressions[value.index()].ty.handle().ok_or( + FunctionError::InvalidMeshShaderOutputType(value).with_span(), + )?; + + if matches!(func, crate::MeshFunction::SetVertex { .. }) { + self.try_update_mesh_vertex_type(ty, value)?; + } else { + self.try_update_mesh_primitive_type(ty, value)?; + }; + + FunctionUniformity::new() + } } - }, + } S::SubgroupBallot { result: _, predicate, From 385535a8d0045fd8fec7e7a454924491824e6a83 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 16 Oct 2025 23:25:14 -0500 Subject: [PATCH 035/110] Various validation fix attempts --- naga/src/valid/handles.rs | 14 ++++++++++++++ naga/src/valid/interface.rs | 16 ++++++++++++++++ naga/src/valid/mod.rs | 4 +++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs index a0153e9398c..adb9f355c11 100644 --- a/naga/src/valid/handles.rs +++ b/naga/src/valid/handles.rs @@ -233,6 +233,20 @@ impl super::Validator { validate_const_expr(size)?; } } + if let Some(task_payload) = entry_point.task_payload { + Self::validate_global_variable_handle(task_payload, global_variables)?; + } + if let Some(ref mesh_info) = entry_point.mesh_info { + validate_type(mesh_info.vertex_output_type)?; + validate_type(mesh_info.primitive_output_type)?; + for ov in mesh_info + .max_vertices_override + .iter() + .chain(mesh_info.max_primitives_override.iter()) + { + validate_const_expr(*ov)?; + } + } } for (function_handle, function) in functions.iter() { diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 6d122a8b2c5..04c5d99babb 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -98,6 +98,8 @@ pub enum VaryingError { InvalidPerPrimitive, #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] MissingPerPrimitive, + #[error("The `MESH_SHADER` capability must be enabled to use per-primitive fragment inputs.")] + PerPrimitiveNotAllowed, } #[derive(Clone, Debug, thiserror::Error)] @@ -151,6 +153,10 @@ pub enum EntryPointError { InvalidMeshPrimitiveOutputType, #[error("Task shaders must declare a task payload output")] ExpectedTaskPayload, + #[error( + "The `MESH_SHADER` capability must be enabled to compile mesh shaders and task shaders." + )] + MeshShaderCapabilityDisabled, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -386,6 +392,9 @@ impl VaryingContext<'_> { blend_src, per_primitive, } => { + if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) { + return Err(VaryingError::PerPrimitiveNotAllowed); + } // Only IO-shareable types may be stored in locations. if !self.type_info[ty.index()] .flags @@ -802,6 +811,13 @@ impl super::Validator { module: &crate::Module, mod_info: &ModuleInfo, ) -> Result> { + if matches!( + ep.stage, + crate::ShaderStage::Task | crate::ShaderStage::Mesh + ) && !self.capabilities.contains(Capabilities::MESH_SHADER) + { + return Err(EntryPointError::MeshShaderCapabilityDisabled.with_span()); + } if ep.early_depth_test.is_some() { let required = Capabilities::EARLY_DEPTH_TEST; if !self.capabilities.contains(required) { diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index eb707bcb383..d47d878ed4e 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -83,7 +83,7 @@ bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Capabilities: u32 { + pub struct Capabilities: u64 { /// Support for [`AddressSpace::PushConstant`][1]. /// /// [1]: crate::AddressSpace::PushConstant @@ -186,6 +186,8 @@ bitflags::bitflags! { /// Support for `quantizeToF16`, `pack2x16float`, and `unpack2x16float`, which store /// `f16`-precision values in `f32`s. const SHADER_FLOAT16_IN_FLOAT32 = 1 << 28; + /// Support for task shaders, mesh shaders, and per-primitive fragment inputs + const MESH_SHADER = 1 << 29; } } From c3f9acd8427e2961de5b68014a9a347f8fbdc415 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 17 Oct 2025 13:27:30 -0500 Subject: [PATCH 036/110] Undid capabilities resize --- naga/src/valid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index d47d878ed4e..2460a46df4b 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -83,7 +83,7 @@ bitflags::bitflags! { #[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] #[derive(Clone, Copy, Debug, Eq, PartialEq)] - pub struct Capabilities: u64 { + pub struct Capabilities: u32 { /// Support for [`AddressSpace::PushConstant`][1]. /// /// [1]: crate::AddressSpace::PushConstant From d15ba19aa097ecaf52bbf1496a64032e69d97738 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 17 Oct 2025 13:33:32 -0500 Subject: [PATCH 037/110] WGSL PR is up :) --- naga/src/front/wgsl/error.rs | 34 + naga/src/front/wgsl/lower/mod.rs | 220 ++- naga/src/front/wgsl/parse/ast.rs | 11 + naga/src/front/wgsl/parse/conv.rs | 7 + .../wgsl/parse/directive/enable_extension.rs | 9 + naga/src/front/wgsl/parse/mod.rs | 81 +- naga/tests/in/wgsl/mesh-shader.toml | 19 + naga/tests/in/wgsl/mesh-shader.wgsl | 71 + .../out/analysis/wgsl-mesh-shader.info.ron | 1211 +++++++++++++++++ .../tests/out/ir/wgsl-mesh-shader.compact.ron | 846 ++++++++++++ naga/tests/out/ir/wgsl-mesh-shader.ron | 846 ++++++++++++ 11 files changed, 3313 insertions(+), 42 deletions(-) create mode 100644 naga/tests/in/wgsl/mesh-shader.toml create mode 100644 naga/tests/in/wgsl/mesh-shader.wgsl create mode 100644 naga/tests/out/analysis/wgsl-mesh-shader.info.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader.compact.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader.ron diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 17dab5cb0ea..5fc69382447 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -406,6 +406,19 @@ pub(crate) enum Error<'a> { accept_span: Span, accept_type: String, }, + MissingMeshShaderInfo { + mesh_attribute_span: Span, + }, + OneMeshShaderAttribute { + attribute_span: Span, + }, + ExpectedGlobalVariable { + name_span: Span, + }, + MeshPrimitiveNoDefinedTopology { + attribute_span: Span, + struct_span: Span, + }, StructMemberTooLarge { member_name_span: Span, }, @@ -1370,6 +1383,27 @@ impl<'a> Error<'a> { ], notes: vec![], }, + Error::MissingMeshShaderInfo { mesh_attribute_span} => ParseError { + message: "mesh shader entry point is missing @vertex_output or @primitive_output".into(), + labels: vec![(mesh_attribute_span, "mesh shader entry declared here".into())], + notes: vec![], + }, + Error::OneMeshShaderAttribute { attribute_span } => ParseError { + message: "only one of @vertex_output or @primitive_output was given".into(), + labels: vec![(attribute_span, "only one of @vertex_output or @primitive_output is provided".into())], + notes: vec![], + }, + Error::ExpectedGlobalVariable { name_span } => ParseError { + message: "expected global variable".to_string(), + // TODO: I would like to also include the global declaration span + labels: vec![(name_span, "variable used here".into())], + notes: vec![], + }, + Error::MeshPrimitiveNoDefinedTopology { struct_span, attribute_span } => ParseError { + message: "mesh primitive struct must have exactly one of point indices, line indices, or triangle indices".to_string(), + labels: vec![(attribute_span, "primitive type declared here".into()), (struct_span, "primitive struct declared here".into())], + notes: vec![] + }, Error::StructMemberTooLarge { member_name_span } => ParseError { message: "struct member is too large".into(), labels: vec![(member_name_span, "this member exceeds the maximum size".into())], diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index 2066d7cf2c8..ef63e6aaea7 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1479,47 +1479,147 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { .collect(); if let Some(ref entry) = f.entry_point { - let workgroup_size_info = if let Some(workgroup_size) = entry.workgroup_size { - // TODO: replace with try_map once stabilized - let mut workgroup_size_out = [1; 3]; - let mut workgroup_size_overrides_out = [None; 3]; - for (i, size) in workgroup_size.into_iter().enumerate() { - if let Some(size_expr) = size { - match self.const_u32(size_expr, &mut ctx.as_const()) { - Ok(value) => { - workgroup_size_out[i] = value.0; - } - Err(err) => { - if let Error::ConstantEvaluatorError(ref ty, _) = *err { - match **ty { - proc::ConstantEvaluatorError::OverrideExpr => { - workgroup_size_overrides_out[i] = - Some(self.workgroup_size_override( - size_expr, - &mut ctx.as_override(), - )?); - } - _ => { - return Err(err); + let (workgroup_size, workgroup_size_overrides) = + if let Some(workgroup_size) = entry.workgroup_size { + // TODO: replace with try_map once stabilized + let mut workgroup_size_out = [1; 3]; + let mut workgroup_size_overrides_out = [None; 3]; + for (i, size) in workgroup_size.into_iter().enumerate() { + if let Some(size_expr) = size { + match self.const_u32(size_expr, &mut ctx.as_const()) { + Ok(value) => { + workgroup_size_out[i] = value.0; + } + Err(err) => { + if let Error::ConstantEvaluatorError(ref ty, _) = *err { + match **ty { + proc::ConstantEvaluatorError::OverrideExpr => { + workgroup_size_overrides_out[i] = + Some(self.workgroup_size_override( + size_expr, + &mut ctx.as_override(), + )?); + } + _ => { + return Err(err); + } } + } else { + return Err(err); } - } else { - return Err(err); } } } } - } - if workgroup_size_overrides_out.iter().all(|x| x.is_none()) { - (workgroup_size_out, None) + if workgroup_size_overrides_out.iter().all(|x| x.is_none()) { + (workgroup_size_out, None) + } else { + (workgroup_size_out, Some(workgroup_size_overrides_out)) + } } else { - (workgroup_size_out, Some(workgroup_size_overrides_out)) + ([0; 3], None) + }; + + let mesh_info = if let Some(mesh_info) = entry.mesh_shader_info { + let mut const_u32 = |expr| match self.const_u32(expr, &mut ctx.as_const()) { + Ok(value) => Ok((value.0, None)), + Err(err) => { + if let Error::ConstantEvaluatorError(ref ty, _) = *err { + match **ty { + proc::ConstantEvaluatorError::OverrideExpr => Ok(( + 0, + Some( + // This is dubious but it seems the code isn't workgroup size specific + self.workgroup_size_override(expr, &mut ctx.as_override())?, + ), + )), + _ => Err(err), + } + } else { + Err(err) + } + } + }; + let (max_vertices, max_vertices_override) = const_u32(mesh_info.vertex_count)?; + let (max_primitives, max_primitives_override) = + const_u32(mesh_info.primitive_count)?; + let vertex_output_type = + self.resolve_ast_type(mesh_info.vertex_type.0, &mut ctx.as_const())?; + let primitive_output_type = + self.resolve_ast_type(mesh_info.primitive_type.0, &mut ctx.as_const())?; + + let mut topology = None; + let struct_span = ctx.module.types.get_span(primitive_output_type); + match &ctx.module.types[primitive_output_type].inner { + &ir::TypeInner::Struct { + ref members, + span: _, + } => { + for member in members { + let out_topology = match member.binding { + Some(ir::Binding::BuiltIn(ir::BuiltIn::TriangleIndices)) => { + Some(ir::MeshOutputTopology::Triangles) + } + Some(ir::Binding::BuiltIn(ir::BuiltIn::LineIndices)) => { + Some(ir::MeshOutputTopology::Lines) + } + _ => None, + }; + if out_topology.is_some() { + if topology.is_some() { + return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { + attribute_span: mesh_info.primitive_type.1, + struct_span, + })); + } + topology = out_topology; + } + } + } + _ => { + return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { + attribute_span: mesh_info.primitive_type.1, + struct_span, + })) + } } + let topology = if let Some(t) = topology { + t + } else { + return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { + attribute_span: mesh_info.primitive_type.1, + struct_span, + })); + }; + + Some(ir::MeshStageInfo { + max_vertices, + max_vertices_override, + max_primitives, + max_primitives_override, + + vertex_output_type, + primitive_output_type, + topology, + }) + } else { + None + }; + + let task_payload = if let Some((var_name, var_span)) = entry.task_payload { + Some(match ctx.globals.get(var_name) { + Some(&LoweredGlobalDecl::Var(handle)) => handle, + Some(_) => { + return Err(Box::new(Error::ExpectedGlobalVariable { + name_span: var_span, + })) + } + None => return Err(Box::new(Error::UnknownIdent(var_span, var_name))), + }) } else { - ([0; 3], None) + None }; - let (workgroup_size, workgroup_size_overrides) = workgroup_size_info; ctx.module.entry_points.push(ir::EntryPoint { name: f.name.name.to_string(), stage: entry.stage, @@ -1527,8 +1627,8 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { workgroup_size, workgroup_size_overrides, function, - mesh_info: None, - task_payload: None, + mesh_info, + task_payload, }); Ok(LoweredGlobalDecl::EntryPoint( ctx.module.entry_points.len() - 1, @@ -3132,6 +3232,59 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ); return Ok(Some(result)); } + + "setMeshOutputs" | "setVertex" | "setPrimitive" => { + let mut args = ctx.prepare_args(arguments, 2, span); + let arg1 = args.next()?; + let arg2 = args.next()?; + args.finish()?; + + let mut cast_u32 = |arg| { + // Try to convert abstract values to the known argument types + let expr = self.expression_for_abstract(arg, ctx)?; + let goal_ty = + ctx.ensure_type_exists(ir::TypeInner::Scalar(ir::Scalar::U32)); + ctx.try_automatic_conversions( + expr, + &proc::TypeResolution::Handle(goal_ty), + ctx.ast_expressions.get_span(arg), + ) + }; + + let arg1 = cast_u32(arg1)?; + let arg2 = if function.name == "setMeshOutputs" { + cast_u32(arg2)? + } else { + self.expression(arg2, ctx)? + }; + + let rctx = ctx.runtime_expression_ctx(span)?; + + // Emit all previous expressions, even if not used directly + rctx.block + .extend(rctx.emitter.finish(&rctx.function.expressions)); + rctx.block.push( + crate::Statement::MeshFunction(match function.name { + "setMeshOutputs" => crate::MeshFunction::SetMeshOutputs { + vertex_count: arg1, + primitive_count: arg2, + }, + "setVertex" => crate::MeshFunction::SetVertex { + index: arg1, + value: arg2, + }, + "setPrimitive" => crate::MeshFunction::SetPrimitive { + index: arg1, + value: arg2, + }, + _ => unreachable!(), + }), + span, + ); + rctx.emitter.start(&rctx.function.expressions); + + return Ok(None); + } _ => { return Err(Box::new(Error::UnknownIdent(function.span, function.name))) } @@ -4059,6 +4212,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { interpolation, sampling, blend_src, + per_primitive, }) => { let blend_src = if let Some(blend_src) = blend_src { Some(self.const_u32(blend_src, &mut ctx.as_const())?.0) @@ -4071,7 +4225,7 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { interpolation, sampling, blend_src, - per_primitive: false, + per_primitive, }; binding.apply_default_interpolation(&ctx.module.types[ty].inner); Some(binding) diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs index 345e9c4c486..49ecddfdee5 100644 --- a/naga/src/front/wgsl/parse/ast.rs +++ b/naga/src/front/wgsl/parse/ast.rs @@ -128,6 +128,16 @@ pub struct EntryPoint<'a> { pub stage: crate::ShaderStage, pub early_depth_test: Option, pub workgroup_size: Option<[Option>>; 3]>, + pub mesh_shader_info: Option>, + pub task_payload: Option<(&'a str, Span)>, +} + +#[derive(Debug, Clone, Copy)] +pub struct EntryPointMeshShaderInfo<'a> { + pub vertex_count: Handle>, + pub primitive_count: Handle>, + pub vertex_type: (Handle>, Span), + pub primitive_type: (Handle>, Span), } #[cfg(doc)] @@ -152,6 +162,7 @@ pub enum Binding<'a> { interpolation: Option, sampling: Option, blend_src: Option>>, + per_primitive: bool, }, } diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 30d0eb2d598..2bde001804e 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -16,6 +16,7 @@ pub fn map_address_space(word: &str, span: Span) -> Result<'_, crate::AddressSpa }), "push_constant" => Ok(crate::AddressSpace::PushConstant), "function" => Ok(crate::AddressSpace::Function), + "task_payload" => Ok(crate::AddressSpace::TaskPayload), _ => Err(Box::new(Error::UnknownAddressSpace(span))), } } @@ -49,6 +50,12 @@ pub fn map_built_in( "subgroup_id" => crate::BuiltIn::SubgroupId, "subgroup_size" => crate::BuiltIn::SubgroupSize, "subgroup_invocation_id" => crate::BuiltIn::SubgroupInvocationId, + // mesh + "cull_primitive" => crate::BuiltIn::CullPrimitive, + "point_index" => crate::BuiltIn::PointIndex, + "line_indices" => crate::BuiltIn::LineIndices, + "triangle_indices" => crate::BuiltIn::TriangleIndices, + "mesh_task_size" => crate::BuiltIn::MeshTaskSize, _ => return Err(Box::new(Error::UnknownBuiltin(span))), }; match built_in { diff --git a/naga/src/front/wgsl/parse/directive/enable_extension.rs b/naga/src/front/wgsl/parse/directive/enable_extension.rs index 38d6d6719ca..d376c114ff0 100644 --- a/naga/src/front/wgsl/parse/directive/enable_extension.rs +++ b/naga/src/front/wgsl/parse/directive/enable_extension.rs @@ -10,6 +10,7 @@ use alloc::boxed::Box; /// Tracks the status of every enable-extension known to Naga. #[derive(Clone, Debug, Eq, PartialEq)] pub struct EnableExtensions { + mesh_shader: bool, dual_source_blending: bool, /// Whether `enable f16;` was written earlier in the shader module. f16: bool, @@ -19,6 +20,7 @@ pub struct EnableExtensions { impl EnableExtensions { pub(crate) const fn empty() -> Self { Self { + mesh_shader: false, f16: false, dual_source_blending: false, clip_distances: false, @@ -28,6 +30,7 @@ impl EnableExtensions { /// Add an enable-extension to the set requested by a module. pub(crate) fn add(&mut self, ext: ImplementedEnableExtension) { let field = match ext { + ImplementedEnableExtension::MeshShader => &mut self.mesh_shader, ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending, ImplementedEnableExtension::F16 => &mut self.f16, ImplementedEnableExtension::ClipDistances => &mut self.clip_distances, @@ -38,6 +41,7 @@ impl EnableExtensions { /// Query whether an enable-extension tracked here has been requested. pub(crate) const fn contains(&self, ext: ImplementedEnableExtension) -> bool { match ext { + ImplementedEnableExtension::MeshShader => self.mesh_shader, ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending, ImplementedEnableExtension::F16 => self.f16, ImplementedEnableExtension::ClipDistances => self.clip_distances, @@ -70,6 +74,7 @@ impl EnableExtension { const F16: &'static str = "f16"; const CLIP_DISTANCES: &'static str = "clip_distances"; const DUAL_SOURCE_BLENDING: &'static str = "dual_source_blending"; + const MESH_SHADER: &'static str = "mesh_shading"; const SUBGROUPS: &'static str = "subgroups"; const PRIMITIVE_INDEX: &'static str = "primitive_index"; @@ -81,6 +86,7 @@ impl EnableExtension { Self::DUAL_SOURCE_BLENDING => { Self::Implemented(ImplementedEnableExtension::DualSourceBlending) } + Self::MESH_SHADER => Self::Implemented(ImplementedEnableExtension::MeshShader), Self::SUBGROUPS => Self::Unimplemented(UnimplementedEnableExtension::Subgroups), Self::PRIMITIVE_INDEX => { Self::Unimplemented(UnimplementedEnableExtension::PrimitiveIndex) @@ -93,6 +99,7 @@ impl EnableExtension { pub const fn to_ident(self) -> &'static str { match self { Self::Implemented(kind) => match kind { + ImplementedEnableExtension::MeshShader => Self::MESH_SHADER, ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING, ImplementedEnableExtension::F16 => Self::F16, ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, @@ -126,6 +133,8 @@ pub enum ImplementedEnableExtension { /// /// [`enable clip_distances;`]: https://www.w3.org/TR/WGSL/#extension-clip_distances ClipDistances, + /// Enables the `mesh_shader` extension, native only + MeshShader, } /// A variant of [`EnableExtension::Unimplemented`]. diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index c01ba4de30f..29376614d6e 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -178,6 +178,7 @@ struct BindingParser<'a> { sampling: ParsedAttribute, invariant: ParsedAttribute, blend_src: ParsedAttribute>>, + per_primitive: ParsedAttribute<()>, } impl<'a> BindingParser<'a> { @@ -238,6 +239,9 @@ impl<'a> BindingParser<'a> { lexer.skip(Token::Separator(',')); lexer.expect(Token::Paren(')'))?; } + "per_primitive" => { + self.per_primitive.set((), name_span)?; + } _ => return Err(Box::new(Error::UnknownAttribute(name_span))), } Ok(()) @@ -251,9 +255,10 @@ impl<'a> BindingParser<'a> { self.sampling.value, self.invariant.value.unwrap_or_default(), self.blend_src.value, + self.per_primitive.value, ) { - (None, None, None, None, false, None) => Ok(None), - (Some(location), None, interpolation, sampling, false, blend_src) => { + (None, None, None, None, false, None, None) => Ok(None), + (Some(location), None, interpolation, sampling, false, blend_src, per_primitive) => { // Before handing over the completed `Module`, we call // `apply_default_interpolation` to ensure that the interpolation and // sampling have been explicitly specified on all vertex shader output and fragment @@ -263,17 +268,18 @@ impl<'a> BindingParser<'a> { interpolation, sampling, blend_src, + per_primitive: per_primitive.is_some(), })) } - (None, Some(crate::BuiltIn::Position { .. }), None, None, invariant, None) => { + (None, Some(crate::BuiltIn::Position { .. }), None, None, invariant, None, None) => { Ok(Some(ast::Binding::BuiltIn(crate::BuiltIn::Position { invariant, }))) } - (None, Some(built_in), None, None, false, None) => { + (None, Some(built_in), None, None, false, None, None) => { Ok(Some(ast::Binding::BuiltIn(built_in))) } - (_, _, _, _, _, _) => Err(Box::new(Error::InconsistentBinding(span))), + (_, _, _, _, _, _, _) => Err(Box::new(Error::InconsistentBinding(span))), } } } @@ -2790,12 +2796,15 @@ impl Parser { // read attributes let mut binding = None; let mut stage = ParsedAttribute::default(); - let mut compute_span = Span::new(0, 0); + let mut compute_like_span = Span::new(0, 0); let mut workgroup_size = ParsedAttribute::default(); let mut early_depth_test = ParsedAttribute::default(); let (mut bind_index, mut bind_group) = (ParsedAttribute::default(), ParsedAttribute::default()); let mut id = ParsedAttribute::default(); + let mut payload = ParsedAttribute::default(); + let mut vertex_output = ParsedAttribute::default(); + let mut primitive_output = ParsedAttribute::default(); let mut must_use: ParsedAttribute = ParsedAttribute::default(); @@ -2854,7 +2863,35 @@ impl Parser { } "compute" => { stage.set(ShaderStage::Compute, name_span)?; - compute_span = name_span; + compute_like_span = name_span; + } + "task" => { + stage.set(ShaderStage::Task, name_span)?; + compute_like_span = name_span; + } + "mesh" => { + stage.set(ShaderStage::Mesh, name_span)?; + compute_like_span = name_span; + } + "payload" => { + lexer.expect(Token::Paren('('))?; + payload.set(lexer.next_ident_with_span()?, name_span)?; + lexer.expect(Token::Paren(')'))?; + } + "vertex_output" | "primitive_output" => { + lexer.expect(Token::Paren('('))?; + let type_span = lexer.peek().1; + let r#type = self.type_decl(lexer, &mut ctx)?; + let type_span = lexer.span_from(type_span.to_range().unwrap().start); + lexer.expect(Token::Separator(','))?; + let max_output = self.general_expression(lexer, &mut ctx)?; + let end_span = lexer.expect_span(Token::Paren(')'))?; + let total_span = name_span.until(&end_span); + if name == "vertex_output" { + vertex_output.set((r#type, type_span, max_output), total_span)?; + } else if name == "primitive_output" { + primitive_output.set((r#type, type_span, max_output), total_span)?; + } } "workgroup_size" => { lexer.expect(Token::Paren('('))?; @@ -3020,13 +3057,39 @@ impl Parser { )?; Some(ast::GlobalDeclKind::Fn(ast::Function { entry_point: if let Some(stage) = stage.value { - if stage == ShaderStage::Compute && workgroup_size.value.is_none() { - return Err(Box::new(Error::MissingWorkgroupSize(compute_span))); + if stage.compute_like() && workgroup_size.value.is_none() { + return Err(Box::new(Error::MissingWorkgroupSize(compute_like_span))); } + if stage == ShaderStage::Mesh + && (vertex_output.value.is_none() || primitive_output.value.is_none()) + { + return Err(Box::new(Error::MissingMeshShaderInfo { + mesh_attribute_span: compute_like_span, + })); + } + let mesh_shader_info = match (vertex_output.value, primitive_output.value) { + (Some(vertex_output), Some(primitive_output)) => { + Some(ast::EntryPointMeshShaderInfo { + vertex_count: vertex_output.2, + primitive_count: primitive_output.2, + vertex_type: (vertex_output.0, vertex_output.1), + primitive_type: (primitive_output.0, primitive_output.1), + }) + } + (None, None) => None, + (Some(v), None) | (None, Some(v)) => { + return Err(Box::new(Error::OneMeshShaderAttribute { + attribute_span: v.1, + })) + } + }; + Some(ast::EntryPoint { stage, early_depth_test: early_depth_test.value, workgroup_size: workgroup_size.value, + mesh_shader_info, + task_payload: payload.value, }) } else { None diff --git a/naga/tests/in/wgsl/mesh-shader.toml b/naga/tests/in/wgsl/mesh-shader.toml new file mode 100644 index 00000000000..1f8b4e23baa --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader.toml @@ -0,0 +1,19 @@ +# Stolen from ray-query.toml + +god_mode = true +targets = "IR | ANALYSIS" + +[msl] +fake_missing_bindings = true +lang_version = [2, 4] +spirv_cross_compatibility = false +zero_initialize_workgroup_memory = false + +[hlsl] +shader_model = "V6_5" +fake_missing_bindings = true +zero_initialize_workgroup_memory = true + +[spv] +version = [1, 4] +capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl new file mode 100644 index 00000000000..70fc2aec333 --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -0,0 +1,71 @@ +enable mesh_shading; + +const positions = array( + vec4(0.,1.,0.,1.), + vec4(-1.,-1.,0.,1.), + vec4(1.,-1.,0.,1.) +); +const colors = array( + vec4(0.,1.,0.,1.), + vec4(0.,0.,1.,1.), + vec4(1.,0.,0.,1.) +); +struct TaskPayload { + colorMask: vec4, + visible: bool, +} +var taskPayload: TaskPayload; +var workgroupData: f32; +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} +struct PrimitiveOutput { + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, +} +struct PrimitiveInput { + @per_primitive @location(1) colorMask: vec4, +} + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + workgroupData = 1.0; + taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); + taskPayload.visible = true; + return vec3(3, 1, 1); +} +@mesh +@payload(taskPayload) +@vertex_output(VertexOutput, 3) @primitive_output(PrimitiveOutput, 1) +@workgroup_size(1) +fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { + setMeshOutputs(3, 1); + workgroupData = 2.0; + var v: VertexOutput; + + v.position = positions[0]; + v.color = colors[0] * taskPayload.colorMask; + setVertex(0, v); + + v.position = positions[1]; + v.color = colors[1] * taskPayload.colorMask; + setVertex(1, v); + + v.position = positions[2]; + v.color = colors[2] * taskPayload.colorMask; + setVertex(2, v); + + var p: PrimitiveOutput; + p.index = vec3(0, 1, 2); + p.cull = !taskPayload.visible; + p.colorMask = vec4(1.0, 0.0, 1.0, 1.0); + setPrimitive(0, p); +} +@fragment +fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { + return vertex.color * primitive.colorMask; +} diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron new file mode 100644 index 00000000000..208e0aac84e --- /dev/null +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -0,0 +1,1211 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ], + functions: [], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ | WRITE"), + ("WRITE"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 0, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 1, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 2, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Bool, + width: 1, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(6), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ"), + ("WRITE"), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(0), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(5), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 0, + assignable_global: None, + ty: Handle(6), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(1), + ty: Value(Pointer( + base: 0, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 9, + assignable_global: None, + ty: Value(Pointer( + base: 4, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 1, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 1, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 1, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(6), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(61), + requirements: (""), + ), + ref_count: 4, + assignable_global: None, + ty: Value(Pointer( + base: 7, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: Some(61), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 6, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(6), + ), + ( + uniformity: ( + non_uniform_result: Some(61), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 2, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 3, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(0), + ty: Value(Pointer( + base: 2, + space: TaskPayload, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(2), + ), + ( + uniformity: ( + non_uniform_result: Some(61), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Pointer( + base: 1, + space: Function, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: Some(61), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(7), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: Some((4, 24)), + primitive_type: Some((7, 79)), + ), + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: Some(0), + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + (""), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: Some(0), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(4), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(8), + ), + ( + uniformity: ( + non_uniform_result: Some(0), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(1), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ( + uniformity: ( + non_uniform_result: Some(0), + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(1), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + mesh_shader_info: ( + vertex_type: None, + primitive_type: None, + ), + ), + ], + const_expression_types: [], +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron new file mode 100644 index 00000000000..38c79cba451 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -0,0 +1,846 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Bool, + width: 1, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 1, + binding: None, + offset: 0, + ), + ( + name: Some("visible"), + ty: 2, + binding: None, + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 1, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ( + name: Some("color"), + ty: 1, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("index"), + ty: 6, + binding: Some(BuiltIn(TriangleIndices)), + offset: 0, + ), + ( + name: Some("cull"), + ty: 2, + binding: Some(BuiltIn(CullPrimitive)), + offset: 12, + ), + ( + name: Some("colorMask"), + ty: 1, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("PrimitiveInput"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 1, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 0, + ), + ], + span: 16, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 3, + init: None, + ), + ( + name: Some("workgroupData"), + space: WorkGroup, + binding: None, + ty: 0, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 6, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + GlobalVariable(1), + Literal(F32(1.0)), + GlobalVariable(0), + AccessIndex( + base: 2, + index: 0, + ), + Literal(F32(1.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 4, + 5, + 6, + 7, + ], + ), + GlobalVariable(0), + AccessIndex( + base: 9, + index: 1, + ), + Literal(Bool(true)), + Literal(U32(3)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 6, + components: [ + 12, + 13, + 14, + ], + ), + ], + named_expressions: {}, + body: [ + Store( + pointer: 0, + value: 1, + ), + Emit(( + start: 3, + end: 4, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 8, + end: 9, + )), + Store( + pointer: 3, + value: 8, + ), + Emit(( + start: 10, + end: 11, + )), + Store( + pointer: 10, + value: 11, + ), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 15, + end: 16, + )), + Return( + value: Some(15), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [ + ( + name: Some("index"), + ty: 5, + binding: Some(BuiltIn(LocalInvocationIndex)), + ), + ( + name: Some("id"), + ty: 6, + binding: Some(BuiltIn(GlobalInvocationId)), + ), + ], + result: None, + local_variables: [ + ( + name: Some("v"), + ty: 4, + init: None, + ), + ( + name: Some("p"), + ty: 7, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + FunctionArgument(1), + Literal(U32(3)), + Literal(U32(1)), + GlobalVariable(1), + Literal(F32(2.0)), + LocalVariable(0), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 8, + 9, + 10, + 11, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 14, + index: 0, + ), + Load( + pointer: 15, + ), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 17, + 18, + 19, + 20, + ], + ), + Binary( + op: Multiply, + left: 21, + right: 16, + ), + Literal(U32(0)), + Load( + pointer: 6, + ), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(-1.0)), + Literal(F32(-1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 26, + 27, + 28, + 29, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 32, + index: 0, + ), + Load( + pointer: 33, + ), + Literal(F32(0.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 35, + 36, + 37, + 38, + ], + ), + Binary( + op: Multiply, + left: 39, + right: 34, + ), + Literal(U32(1)), + Load( + pointer: 6, + ), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(1.0)), + Literal(F32(-1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 44, + 45, + 46, + 47, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 51, + ), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 53, + 54, + 55, + 56, + ], + ), + Binary( + op: Multiply, + left: 57, + right: 52, + ), + Literal(U32(2)), + Load( + pointer: 6, + ), + LocalVariable(1), + AccessIndex( + base: 61, + index: 0, + ), + Literal(U32(0)), + Literal(U32(1)), + Literal(U32(2)), + Compose( + ty: 6, + components: [ + 63, + 64, + 65, + ], + ), + AccessIndex( + base: 61, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 68, + index: 1, + ), + Load( + pointer: 69, + ), + Unary( + op: LogicalNot, + expr: 70, + ), + AccessIndex( + base: 61, + index: 2, + ), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 73, + 74, + 75, + 76, + ], + ), + Literal(U32(0)), + Load( + pointer: 61, + ), + ], + named_expressions: { + 0: "index", + 1: "id", + }, + body: [ + MeshFunction(SetMeshOutputs( + vertex_count: 2, + primitive_count: 3, + )), + Store( + pointer: 4, + value: 5, + ), + Emit(( + start: 7, + end: 8, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 12, + end: 13, + )), + Store( + pointer: 7, + value: 12, + ), + Emit(( + start: 13, + end: 14, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 15, + end: 17, + )), + Emit(( + start: 21, + end: 23, + )), + Store( + pointer: 13, + value: 22, + ), + Emit(( + start: 24, + end: 25, + )), + MeshFunction(SetVertex( + index: 23, + value: 24, + )), + Emit(( + start: 25, + end: 26, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 30, + end: 31, + )), + Store( + pointer: 25, + value: 30, + ), + Emit(( + start: 31, + end: 32, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 33, + end: 35, + )), + Emit(( + start: 39, + end: 41, + )), + Store( + pointer: 31, + value: 40, + ), + Emit(( + start: 42, + end: 43, + )), + MeshFunction(SetVertex( + index: 41, + value: 42, + )), + Emit(( + start: 43, + end: 44, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 48, + end: 49, + )), + Store( + pointer: 43, + value: 48, + ), + Emit(( + start: 49, + end: 50, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 51, + end: 53, + )), + Emit(( + start: 57, + end: 59, + )), + Store( + pointer: 49, + value: 58, + ), + Emit(( + start: 60, + end: 61, + )), + MeshFunction(SetVertex( + index: 59, + value: 60, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 66, + end: 67, + )), + Store( + pointer: 62, + value: 66, + ), + Emit(( + start: 67, + end: 68, + )), + Emit(( + start: 69, + end: 72, + )), + Store( + pointer: 67, + value: 71, + ), + Emit(( + start: 72, + end: 73, + )), + Emit(( + start: 77, + end: 78, + )), + Store( + pointer: 72, + value: 77, + ), + Emit(( + start: 79, + end: 80, + )), + MeshFunction(SetPrimitive( + index: 78, + value: 79, + )), + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 4, + primitive_output_type: 7, + )), + task_payload: Some(0), + ), + ( + name: "fs_main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + workgroup_size_overrides: None, + function: ( + name: Some("fs_main"), + arguments: [ + ( + name: Some("vertex"), + ty: 4, + binding: None, + ), + ( + name: Some("primitive"), + ty: 8, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + FunctionArgument(1), + AccessIndex( + base: 0, + index: 1, + ), + AccessIndex( + base: 1, + index: 0, + ), + Binary( + op: Multiply, + left: 2, + right: 3, + ), + ], + named_expressions: { + 0: "vertex", + 1: "primitive", + }, + body: [ + Emit(( + start: 2, + end: 5, + )), + Return( + value: Some(4), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: None, + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron new file mode 100644 index 00000000000..38c79cba451 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -0,0 +1,846 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Bool, + width: 1, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 1, + binding: None, + offset: 0, + ), + ( + name: Some("visible"), + ty: 2, + binding: None, + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 1, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ( + name: Some("color"), + ty: 1, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("index"), + ty: 6, + binding: Some(BuiltIn(TriangleIndices)), + offset: 0, + ), + ( + name: Some("cull"), + ty: 2, + binding: Some(BuiltIn(CullPrimitive)), + offset: 12, + ), + ( + name: Some("colorMask"), + ty: 1, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("PrimitiveInput"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 1, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 0, + ), + ], + span: 16, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 3, + init: None, + ), + ( + name: Some("workgroupData"), + space: WorkGroup, + binding: None, + ty: 0, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 6, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + GlobalVariable(1), + Literal(F32(1.0)), + GlobalVariable(0), + AccessIndex( + base: 2, + index: 0, + ), + Literal(F32(1.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 4, + 5, + 6, + 7, + ], + ), + GlobalVariable(0), + AccessIndex( + base: 9, + index: 1, + ), + Literal(Bool(true)), + Literal(U32(3)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 6, + components: [ + 12, + 13, + 14, + ], + ), + ], + named_expressions: {}, + body: [ + Store( + pointer: 0, + value: 1, + ), + Emit(( + start: 3, + end: 4, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 8, + end: 9, + )), + Store( + pointer: 3, + value: 8, + ), + Emit(( + start: 10, + end: 11, + )), + Store( + pointer: 10, + value: 11, + ), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 15, + end: 16, + )), + Return( + value: Some(15), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [ + ( + name: Some("index"), + ty: 5, + binding: Some(BuiltIn(LocalInvocationIndex)), + ), + ( + name: Some("id"), + ty: 6, + binding: Some(BuiltIn(GlobalInvocationId)), + ), + ], + result: None, + local_variables: [ + ( + name: Some("v"), + ty: 4, + init: None, + ), + ( + name: Some("p"), + ty: 7, + init: None, + ), + ], + expressions: [ + FunctionArgument(0), + FunctionArgument(1), + Literal(U32(3)), + Literal(U32(1)), + GlobalVariable(1), + Literal(F32(2.0)), + LocalVariable(0), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 8, + 9, + 10, + 11, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 14, + index: 0, + ), + Load( + pointer: 15, + ), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 17, + 18, + 19, + 20, + ], + ), + Binary( + op: Multiply, + left: 21, + right: 16, + ), + Literal(U32(0)), + Load( + pointer: 6, + ), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(-1.0)), + Literal(F32(-1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 26, + 27, + 28, + 29, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 32, + index: 0, + ), + Load( + pointer: 33, + ), + Literal(F32(0.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 35, + 36, + 37, + 38, + ], + ), + Binary( + op: Multiply, + left: 39, + right: 34, + ), + Literal(U32(1)), + Load( + pointer: 6, + ), + AccessIndex( + base: 6, + index: 0, + ), + Literal(F32(1.0)), + Literal(F32(-1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 44, + 45, + 46, + 47, + ], + ), + AccessIndex( + base: 6, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 50, + index: 0, + ), + Load( + pointer: 51, + ), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 53, + 54, + 55, + 56, + ], + ), + Binary( + op: Multiply, + left: 57, + right: 52, + ), + Literal(U32(2)), + Load( + pointer: 6, + ), + LocalVariable(1), + AccessIndex( + base: 61, + index: 0, + ), + Literal(U32(0)), + Literal(U32(1)), + Literal(U32(2)), + Compose( + ty: 6, + components: [ + 63, + 64, + 65, + ], + ), + AccessIndex( + base: 61, + index: 1, + ), + GlobalVariable(0), + AccessIndex( + base: 68, + index: 1, + ), + Load( + pointer: 69, + ), + Unary( + op: LogicalNot, + expr: 70, + ), + AccessIndex( + base: 61, + index: 2, + ), + Literal(F32(1.0)), + Literal(F32(0.0)), + Literal(F32(1.0)), + Literal(F32(1.0)), + Compose( + ty: 1, + components: [ + 73, + 74, + 75, + 76, + ], + ), + Literal(U32(0)), + Load( + pointer: 61, + ), + ], + named_expressions: { + 0: "index", + 1: "id", + }, + body: [ + MeshFunction(SetMeshOutputs( + vertex_count: 2, + primitive_count: 3, + )), + Store( + pointer: 4, + value: 5, + ), + Emit(( + start: 7, + end: 8, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 12, + end: 13, + )), + Store( + pointer: 7, + value: 12, + ), + Emit(( + start: 13, + end: 14, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 15, + end: 17, + )), + Emit(( + start: 21, + end: 23, + )), + Store( + pointer: 13, + value: 22, + ), + Emit(( + start: 24, + end: 25, + )), + MeshFunction(SetVertex( + index: 23, + value: 24, + )), + Emit(( + start: 25, + end: 26, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 30, + end: 31, + )), + Store( + pointer: 25, + value: 30, + ), + Emit(( + start: 31, + end: 32, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 33, + end: 35, + )), + Emit(( + start: 39, + end: 41, + )), + Store( + pointer: 31, + value: 40, + ), + Emit(( + start: 42, + end: 43, + )), + MeshFunction(SetVertex( + index: 41, + value: 42, + )), + Emit(( + start: 43, + end: 44, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 48, + end: 49, + )), + Store( + pointer: 43, + value: 48, + ), + Emit(( + start: 49, + end: 50, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 51, + end: 53, + )), + Emit(( + start: 57, + end: 59, + )), + Store( + pointer: 49, + value: 58, + ), + Emit(( + start: 60, + end: 61, + )), + MeshFunction(SetVertex( + index: 59, + value: 60, + )), + Emit(( + start: 62, + end: 63, + )), + Emit(( + start: 66, + end: 67, + )), + Store( + pointer: 62, + value: 66, + ), + Emit(( + start: 67, + end: 68, + )), + Emit(( + start: 69, + end: 72, + )), + Store( + pointer: 67, + value: 71, + ), + Emit(( + start: 72, + end: 73, + )), + Emit(( + start: 77, + end: 78, + )), + Store( + pointer: 72, + value: 77, + ), + Emit(( + start: 79, + end: 80, + )), + MeshFunction(SetPrimitive( + index: 78, + value: 79, + )), + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 4, + primitive_output_type: 7, + )), + task_payload: Some(0), + ), + ( + name: "fs_main", + stage: Fragment, + early_depth_test: None, + workgroup_size: (0, 0, 0), + workgroup_size_overrides: None, + function: ( + name: Some("fs_main"), + arguments: [ + ( + name: Some("vertex"), + ty: 4, + binding: None, + ), + ( + name: Some("primitive"), + ty: 8, + binding: None, + ), + ], + result: Some(( + ty: 1, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + )), + local_variables: [], + expressions: [ + FunctionArgument(0), + FunctionArgument(1), + AccessIndex( + base: 0, + index: 1, + ), + AccessIndex( + base: 1, + index: 0, + ), + Binary( + op: Multiply, + left: 2, + right: 3, + ), + ], + named_expressions: { + 0: "vertex", + 1: "primitive", + }, + body: [ + Emit(( + start: 2, + end: 5, + )), + Return( + value: Some(4), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: None, + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file From f14e0f0b5cee3439b348f8be1f64d65691e475f4 Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:14:21 -0500 Subject: [PATCH 038/110] Update naga/src/ir/mod.rs Co-authored-by: Erich Gubler --- naga/src/ir/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index a8a5d220463..151bd36b694 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -2178,7 +2178,7 @@ pub enum Statement { /// The specific operation we're performing on `query`. fun: RayQueryFunction, }, - /// A mesh shader intrinsic + /// A mesh shader intrinsic. MeshFunction(MeshFunction), /// Calculate a bitmask using a boolean from each active thread in the subgroup SubgroupBallot { From 7e12d30c0b29f65bdce3985eb910c2f5e6aad89e Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:14:28 -0500 Subject: [PATCH 039/110] Update naga/src/front/wgsl/error.rs Co-authored-by: Erich Gubler --- naga/src/front/wgsl/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 5fc69382447..26505c20478 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1384,12 +1384,12 @@ impl<'a> Error<'a> { notes: vec![], }, Error::MissingMeshShaderInfo { mesh_attribute_span} => ParseError { - message: "mesh shader entry point is missing @vertex_output or @primitive_output".into(), + message: "mesh shader entry point is missing `@vertex_output` or `@primitive_output`".into(), labels: vec![(mesh_attribute_span, "mesh shader entry declared here".into())], notes: vec![], }, Error::OneMeshShaderAttribute { attribute_span } => ParseError { - message: "only one of @vertex_output or @primitive_output was given".into(), + message: "only one of `@vertex_output` or `@primitive_output` was given".into(), labels: vec![(attribute_span, "only one of @vertex_output or @primitive_output is provided".into())], notes: vec![], }, From ce517bb48c99f21be2214b1c01b2501e04c41342 Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:14:40 -0500 Subject: [PATCH 040/110] Update naga/src/ir/mod.rs Co-authored-by: Erich Gubler --- naga/src/ir/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 151bd36b694..6f5857861a8 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -997,7 +997,7 @@ pub enum Binding { /// whether they are per-primitive; a mesh shader's per-vertex and per-primitive /// outputs share the same location numbering space. /// - /// Per primitive values are not interpolated at all and are not dependent on the + /// Per-primitive values are not interpolated at all and are not dependent on the /// vertices or pixel location. For example, it may be used to store a /// non-interpolated normal vector. per_primitive: bool, From 083959e4129b4088d71c205320afde601ce9f327 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 17 Oct 2025 14:16:12 -0500 Subject: [PATCH 041/110] Other Erich suggestion --- naga/src/front/wgsl/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 26505c20478..004528dbe91 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1384,7 +1384,7 @@ impl<'a> Error<'a> { notes: vec![], }, Error::MissingMeshShaderInfo { mesh_attribute_span} => ParseError { - message: "mesh shader entry point is missing `@vertex_output` or `@primitive_output`".into(), + message: "mesh shader entry point is missing both `@vertex_output` and `@primitive_output`".into(), labels: vec![(mesh_attribute_span, "mesh shader entry declared here".into())], notes: vec![], }, From 16aa7d059926ca7f8f47bdfc5c7c27cc717b09c5 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 17 Oct 2025 14:50:22 -0500 Subject: [PATCH 042/110] Updated docs & validation for some builtins --- naga/src/ir/mod.rs | 43 +++++++++++++++++++++++++++++++------ naga/src/valid/interface.rs | 25 +++++++++++++-------- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 6f5857861a8..3c2d1942d7c 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -381,41 +381,72 @@ pub enum AddressSpace { #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum BuiltIn { + /// Written in vertex/mesh shaders, read in fragment shaders Position { invariant: bool }, + /// Read in task, mesh, vertex, and fragment shaders ViewIndex, - // vertex (and often mesh) + + /// Read in vertex shaders BaseInstance, + /// Read in vertex shaders BaseVertex, + /// Written in vertex & mesh shaders ClipDistance, + /// Written in vertex & mesh shaders CullDistance, + /// Read in vertex shaders InstanceIndex, + /// Written in vertex & mesh shaders PointSize, + /// Read in vertex shaders VertexIndex, + /// Read in vertex & task shaders, or mesh shaders in pipelines without task shaders DrawID, - // fragment + + /// Written in fragment shaders FragDepth, + /// Read in fragment shaders PointCoord, + /// Read in fragment shaders FrontFacing, - PrimitiveIndex, // Also for mesh output + /// Read in fragment shaders, in the future may written in mesh shaders + PrimitiveIndex, + /// Read in fragment shaders SampleIndex, + /// Read or written in fragment shaders SampleMask, - // compute (and task/mesh) + + /// Read in compute, task, and mesh shaders GlobalInvocationId, + /// Read in compute, task, and mesh shaders LocalInvocationId, + /// Read in compute, task, and mesh shaders LocalInvocationIndex, + /// Read in compute, task, and mesh shaders WorkGroupId, + /// Read in compute, task, and mesh shaders WorkGroupSize, + /// Read in compute, task, and mesh shaders NumWorkGroups, - // subgroup + + /// Read in compute, task, and mesh shaders NumSubgroups, + /// Read in compute, task, and mesh shaders SubgroupId, + /// Read in compute, fragment, task, and mesh shaders SubgroupSize, + /// Read in compute, fragment, task, and mesh shaders SubgroupInvocationId, - // mesh + + /// Written in task shaders MeshTaskSize, + /// Written in mesh shaders CullPrimitive, + /// Written in mesh shaders PointIndex, + /// Written in mesh shaders LineIndices, + /// Written in mesh shaders TriangleIndices, } diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 04c5d99babb..a4e0af99ccc 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -191,6 +191,7 @@ struct VaryingContext<'a> { capabilities: Capabilities, flags: super::ValidationFlags, mesh_output_type: MeshOutputType, + has_task_payload: bool, } impl VaryingContext<'_> { @@ -243,16 +244,20 @@ impl VaryingContext<'_> { } let (visible, type_good) = match built_in { - Bi::BaseInstance - | Bi::BaseVertex - | Bi::InstanceIndex - | Bi::VertexIndex - | Bi::DrawID => ( + Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => ( self.stage == St::Vertex && !self.output, *ty_inner == Ti::Scalar(crate::Scalar::U32), ), + Bi::DrawID => ( + // Always allowed in task/vertex stage. Allowed in mesh stage if there is no task stage in the pipeline. + (self.stage == St::Vertex + || self.stage == St::Task + || (self.stage == St::Mesh && !self.has_task_payload)) + && !self.output, + *ty_inner == Ti::Scalar(crate::Scalar::U32), + ), Bi::ClipDistance | Bi::CullDistance => ( - self.stage == St::Vertex && self.output, + (self.stage == St::Vertex || self.stage == St::Mesh) && self.output, match *ty_inner { Ti::Array { base, size, .. } => { self.types[base].inner == Ti::Scalar(crate::Scalar::F32) @@ -265,7 +270,7 @@ impl VaryingContext<'_> { }, ), Bi::PointSize => ( - self.stage == St::Vertex && self.output, + (self.stage == St::Vertex || self.stage == St::Mesh) && self.output, *ty_inner == Ti::Scalar(crate::Scalar::F32), ), Bi::PointCoord => ( @@ -290,9 +295,8 @@ impl VaryingContext<'_> { ), Bi::ViewIndex => ( match self.stage { - St::Vertex | St::Fragment => !self.output, + St::Vertex | St::Fragment | St::Task | St::Mesh => !self.output, St::Compute => false, - St::Task | St::Mesh => unreachable!(), }, *ty_inner == Ti::Scalar(crate::Scalar::I32), ), @@ -776,6 +780,7 @@ impl super::Validator { capabilities: self.capabilities, flags: self.flags, mesh_output_type, + has_task_payload: ep.task_payload.is_some(), }; ctx.validate(ep, ty, None) .map_err_inner(|e| EntryPointError::Result(e).with_span())?; @@ -917,6 +922,7 @@ impl super::Validator { capabilities: self.capabilities, flags: self.flags, mesh_output_type: MeshOutputType::None, + has_task_payload: ep.task_payload.is_some(), }; ctx.validate(ep, fa.ty, fa.binding.as_ref()) .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?; @@ -936,6 +942,7 @@ impl super::Validator { capabilities: self.capabilities, flags: self.flags, mesh_output_type: MeshOutputType::None, + has_task_payload: ep.task_payload.is_some(), }; ctx.validate(ep, fr.ty, fr.binding.as_ref()) .map_err_inner(|e| EntryPointError::Result(e).with_span())?; From 76bfca00a1170673c45e9f07b57a59d259324bbd Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 17 Oct 2025 15:05:55 -0500 Subject: [PATCH 043/110] Added some docs & removed contentious "// TODO" --- naga/src/ir/mod.rs | 15 +++++++++++++++ naga/src/proc/mod.rs | 1 - 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 3c2d1942d7c..4b0769c2803 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -2566,28 +2566,40 @@ pub struct DocComments { pub module: Vec, } +/// The output topology for a mesh shader. Note that mesh shaders don't allow things like triangle-strips. #[derive(Debug, Clone, Copy)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum MeshOutputTopology { + /// Outputs individual vertices to be rendered as points. Points, + /// Outputs groups of 2 vertices to be renderedas lines . Lines, + /// Outputs groups of 3 vertices to be rendered as triangles. Triangles, } +/// Information specific to mesh shader entry points. #[derive(Debug, Clone)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] #[allow(dead_code)] pub struct MeshStageInfo { + /// The type of primitive outputted. pub topology: MeshOutputTopology, + /// The maximum number of vertices a mesh shader may output. pub max_vertices: u32, + /// If pipeline constants are used, the expressions that override `max_vertices` pub max_vertices_override: Option>, + /// The maximum number of primitives a mesh shader may output. pub max_primitives: u32, + /// If pipeline constants are used, the expressions that override `max_primitives` pub max_primitives_override: Option>, + /// The type used by vertex outputs, i.e. what is passed to `setVertex`. pub vertex_output_type: Handle, + /// The type used by primitive outputs, i.e. what is passed to `setPrimitive`. pub primitive_output_type: Handle, } @@ -2597,14 +2609,17 @@ pub struct MeshStageInfo { #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] pub enum MeshFunction { + /// Sets the number of vertices and primitives that will be outputted. SetMeshOutputs { vertex_count: Handle, primitive_count: Handle, }, + /// Sets the output vertex at a given index. SetVertex { index: Handle, value: Handle, }, + /// Sets the output primitive at a given index. SetPrimitive { index: Handle, value: Handle, diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 7b90aa35512..eca63ee4fb5 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -632,7 +632,6 @@ pub fn flatten_compose<'arenas>( } impl super::ShaderStage { - // TODO: make more things respect this pub const fn compute_like(self) -> bool { match self { Self::Vertex | Self::Fragment => false, From e100034614c00009f13e51346cbc1ad10b55b551 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 30 Oct 2025 17:02:07 -0500 Subject: [PATCH 044/110] Fixed bad validation, formatted mesh shader wgsl --- naga/src/valid/interface.rs | 1 + naga/tests/in/wgsl/mesh-shader.wgsl | 72 ++++++++++++++--------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index f13dba1e584..f3f5a43c060 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -870,6 +870,7 @@ impl super::Validator { (crate::ShaderStage::Mesh, &None) => { return Err(EntryPointError::ExpectedMeshShaderAttributes.with_span()); } + (crate::ShaderStage::Mesh, &Some(..)) => {} (_, &Some(_)) => { return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span()); } diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index 70fc2aec333..7f094a82f81 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -1,71 +1,71 @@ enable mesh_shading; const positions = array( - vec4(0.,1.,0.,1.), - vec4(-1.,-1.,0.,1.), - vec4(1.,-1.,0.,1.) + vec4(0., 1., 0., 1.), + vec4(-1., -1., 0., 1.), + vec4(1., -1., 0., 1.) ); const colors = array( - vec4(0.,1.,0.,1.), - vec4(0.,0.,1.,1.), - vec4(1.,0.,0.,1.) + vec4(0., 1., 0., 1.), + vec4(0., 0., 1., 1.), + vec4(1., 0., 0., 1.) ); struct TaskPayload { - colorMask: vec4, - visible: bool, + colorMask: vec4, + visible: bool, } var taskPayload: TaskPayload; var workgroupData: f32; struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, + @builtin(position) position: vec4, + @location(0) color: vec4, } struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, - @builtin(cull_primitive) cull: bool, - @per_primitive @location(1) colorMask: vec4, + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { - @per_primitive @location(1) colorMask: vec4, + @per_primitive @location(1) colorMask: vec4, } @task @payload(taskPayload) @workgroup_size(1) fn ts_main() -> @builtin(mesh_task_size) vec3 { - workgroupData = 1.0; - taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); - taskPayload.visible = true; - return vec3(3, 1, 1); + workgroupData = 1.0; + taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); + taskPayload.visible = true; + return vec3(3, 1, 1); } @mesh @payload(taskPayload) @vertex_output(VertexOutput, 3) @primitive_output(PrimitiveOutput, 1) @workgroup_size(1) fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { - setMeshOutputs(3, 1); - workgroupData = 2.0; - var v: VertexOutput; + setMeshOutputs(3, 1); + workgroupData = 2.0; + var v: VertexOutput; - v.position = positions[0]; - v.color = colors[0] * taskPayload.colorMask; - setVertex(0, v); + v.position = positions[0]; + v.color = colors[0] * taskPayload.colorMask; + setVertex(0, v); - v.position = positions[1]; - v.color = colors[1] * taskPayload.colorMask; - setVertex(1, v); + v.position = positions[1]; + v.color = colors[1] * taskPayload.colorMask; + setVertex(1, v); - v.position = positions[2]; - v.color = colors[2] * taskPayload.colorMask; - setVertex(2, v); + v.position = positions[2]; + v.color = colors[2] * taskPayload.colorMask; + setVertex(2, v); - var p: PrimitiveOutput; - p.index = vec3(0, 1, 2); - p.cull = !taskPayload.visible; - p.colorMask = vec4(1.0, 0.0, 1.0, 1.0); - setPrimitive(0, p); + var p: PrimitiveOutput; + p.index = vec3(0, 1, 2); + p.cull = !taskPayload.visible; + p.colorMask = vec4(1.0, 0.0, 1.0, 1.0); + setPrimitive(0, p); } @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { - return vertex.color * primitive.colorMask; + return vertex.color * primitive.colorMask; } From edea07e16c6d2979dbfab910bf7ab25ac9e7c2fb Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 30 Oct 2025 19:31:42 -0500 Subject: [PATCH 045/110] Rewrote the IR and parser significantly --- naga/src/back/dot/mod.rs | 19 - naga/src/back/glsl/mod.rs | 11 +- naga/src/back/hlsl/conv.rs | 6 +- naga/src/back/hlsl/writer.rs | 13 - naga/src/back/msl/mod.rs | 6 +- naga/src/back/msl/writer.rs | 8 - naga/src/back/pipeline_constants.rs | 20 - naga/src/back/spv/block.rs | 1 - naga/src/back/spv/writer.rs | 6 +- naga/src/back/wgsl/writer.rs | 1 - naga/src/common/wgsl/to_wgsl.rs | 6 +- naga/src/compact/statements.rs | 34 -- naga/src/front/spv/mod.rs | 1 - naga/src/front/wgsl/error.rs | 25 - naga/src/front/wgsl/lower/mod.rs | 153 +----- naga/src/front/wgsl/parse/ast.rs | 10 +- naga/src/front/wgsl/parse/conv.rs | 7 +- naga/src/front/wgsl/parse/mod.rs | 47 +- naga/src/ir/mod.rs | 40 +- naga/src/proc/mod.rs | 148 ++++++ naga/src/proc/terminator.rs | 1 - naga/src/valid/analyzer.rs | 30 -- naga/src/valid/function.rs | 35 -- naga/src/valid/handles.rs | 16 - naga/src/valid/interface.rs | 75 ++- naga/tests/in/wgsl/mesh-shader.wgsl | 39 +- .../out/analysis/wgsl-mesh-shader.info.ron | 422 ++++++++++++--- .../tests/out/ir/wgsl-mesh-shader.compact.ron | 480 +++++++++++------- naga/tests/out/ir/wgsl-mesh-shader.ron | 480 +++++++++++------- 29 files changed, 1256 insertions(+), 884 deletions(-) diff --git a/naga/src/back/dot/mod.rs b/naga/src/back/dot/mod.rs index 1f1396eccff..826dad1c219 100644 --- a/naga/src/back/dot/mod.rs +++ b/naga/src/back/dot/mod.rs @@ -307,25 +307,6 @@ impl StatementGraph { crate::RayQueryFunction::Terminate => "RayQueryTerminate", } } - S::MeshFunction(crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - }) => { - self.dependencies.push((id, vertex_count, "vertex_count")); - self.dependencies - .push((id, primitive_count, "primitive_count")); - "SetMeshOutputs" - } - S::MeshFunction(crate::MeshFunction::SetVertex { index, value }) => { - self.dependencies.push((id, index, "index")); - self.dependencies.push((id, value, "value")); - "SetVertex" - } - S::MeshFunction(crate::MeshFunction::SetPrimitive { index, value }) => { - self.dependencies.push((id, index, "index")); - self.dependencies.push((id, value, "value")); - "SetPrimitive" - } S::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.dependencies.push((id, predicate, "predicate")); diff --git a/naga/src/back/glsl/mod.rs b/naga/src/back/glsl/mod.rs index 716bc8049e0..f29504010d5 100644 --- a/naga/src/back/glsl/mod.rs +++ b/naga/src/back/glsl/mod.rs @@ -2675,11 +2675,6 @@ impl<'a, W: Write> Writer<'a, W> { self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)? } Statement::RayQuery { .. } => unreachable!(), - Statement::MeshFunction( - crate::MeshFunction::SetMeshOutputs { .. } - | crate::MeshFunction::SetVertex { .. } - | crate::MeshFunction::SetPrimitive { .. }, - ) => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); @@ -5265,7 +5260,11 @@ const fn glsl_built_in(built_in: crate::BuiltIn, options: VaryingOptions) -> &'s | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices - | Bi::MeshTaskSize => { + | Bi::MeshTaskSize + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => { unimplemented!() } } diff --git a/naga/src/back/hlsl/conv.rs b/naga/src/back/hlsl/conv.rs index 5cd43e14297..ce7f0bc3dc7 100644 --- a/naga/src/back/hlsl/conv.rs +++ b/naga/src/back/hlsl/conv.rs @@ -186,7 +186,11 @@ impl crate::BuiltIn { } Self::CullPrimitive => "SV_CullPrimitive", Self::PointIndex | Self::LineIndices | Self::TriangleIndices => unimplemented!(), - Self::MeshTaskSize => unreachable!(), + Self::MeshTaskSize + | Self::VertexCount + | Self::PrimitiveCount + | Self::Vertices + | Self::Primitives => unreachable!(), }) } } diff --git a/naga/src/back/hlsl/writer.rs b/naga/src/back/hlsl/writer.rs index 6f0ba814a52..8806137d65a 100644 --- a/naga/src/back/hlsl/writer.rs +++ b/naga/src/back/hlsl/writer.rs @@ -2600,19 +2600,6 @@ impl<'a, W: fmt::Write> super::Writer<'a, W> { writeln!(self.out, ".Abort();")?; } }, - Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - }) => { - write!(self.out, "{level}SetMeshOutputCounts(")?; - self.write_expr(module, vertex_count, func_ctx)?; - write!(self.out, ", ")?; - self.write_expr(module, primitive_count, func_ctx)?; - write!(self.out, ");")?; - } - Statement::MeshFunction( - crate::MeshFunction::SetVertex { .. } | crate::MeshFunction::SetPrimitive { .. }, - ) => unimplemented!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = Baked(result).to_string(); diff --git a/naga/src/back/msl/mod.rs b/naga/src/back/msl/mod.rs index c8e5f68be9a..abb596020f8 100644 --- a/naga/src/back/msl/mod.rs +++ b/naga/src/back/msl/mod.rs @@ -707,7 +707,11 @@ impl ResolvedBinding { Bi::CullPrimitive => "primitive_culled", // TODO: figure out how to make this written as a function call Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(), - Bi::MeshTaskSize => unreachable!(), + Bi::MeshTaskSize + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => unreachable!(), }; write!(out, "{name}")?; } diff --git a/naga/src/back/msl/writer.rs b/naga/src/back/msl/writer.rs index 484142630d2..ca7da02a930 100644 --- a/naga/src/back/msl/writer.rs +++ b/naga/src/back/msl/writer.rs @@ -4063,14 +4063,6 @@ impl Writer { } } } - // TODO: write emitters for these - crate::Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { .. }) => { - unimplemented!() - } - crate::Statement::MeshFunction( - crate::MeshFunction::SetVertex { .. } - | crate::MeshFunction::SetPrimitive { .. }, - ) => unimplemented!(), crate::Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let name = self.namer.call(""); diff --git a/naga/src/back/pipeline_constants.rs b/naga/src/back/pipeline_constants.rs index 109cc591e74..de643b82fab 100644 --- a/naga/src/back/pipeline_constants.rs +++ b/naga/src/back/pipeline_constants.rs @@ -860,26 +860,6 @@ fn adjust_stmt(new_pos: &HandleVec>, stmt: &mut S crate::RayQueryFunction::Terminate => {} } } - Statement::MeshFunction(crate::MeshFunction::SetMeshOutputs { - ref mut vertex_count, - ref mut primitive_count, - }) => { - adjust(vertex_count); - adjust(primitive_count); - } - Statement::MeshFunction( - crate::MeshFunction::SetVertex { - ref mut index, - ref mut value, - } - | crate::MeshFunction::SetPrimitive { - ref mut index, - ref mut value, - }, - ) => { - adjust(index); - adjust(value); - } Statement::Break | Statement::Continue | Statement::Kill diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index d0556acdc53..dd9a3811687 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -3655,7 +3655,6 @@ impl BlockContext<'_> { } => { self.write_subgroup_gather(mode, argument, result, &mut block)?; } - Statement::MeshFunction(_) => unreachable!(), } } diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 1beb86577c8..ee1ea847739 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -2156,7 +2156,11 @@ impl Writer { | Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices - | Bi::TriangleIndices => unreachable!(), + | Bi::TriangleIndices + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => unreachable!(), }; self.decorate(id, Decoration::BuiltIn, &[built_in as u32]); diff --git a/naga/src/back/wgsl/writer.rs b/naga/src/back/wgsl/writer.rs index d1ebf62e6ee..daf32a7116f 100644 --- a/naga/src/back/wgsl/writer.rs +++ b/naga/src/back/wgsl/writer.rs @@ -856,7 +856,6 @@ impl Writer { } } Statement::RayQuery { .. } => unreachable!(), - Statement::MeshFunction(..) => unreachable!(), Statement::SubgroupBallot { result, predicate } => { write!(self.out, "{level}")?; let res_name = Baked(result).to_string(); diff --git a/naga/src/common/wgsl/to_wgsl.rs b/naga/src/common/wgsl/to_wgsl.rs index 25847a5df7b..5e6178c049c 100644 --- a/naga/src/common/wgsl/to_wgsl.rs +++ b/naga/src/common/wgsl/to_wgsl.rs @@ -194,7 +194,11 @@ impl TryToWgsl for crate::BuiltIn { | Bi::TriangleIndices | Bi::LineIndices | Bi::MeshTaskSize - | Bi::PointIndex => return None, + | Bi::PointIndex + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => return None, }) } } diff --git a/naga/src/compact/statements.rs b/naga/src/compact/statements.rs index b370501baca..39d6065f5f0 100644 --- a/naga/src/compact/statements.rs +++ b/naga/src/compact/statements.rs @@ -117,20 +117,6 @@ impl FunctionTracer<'_> { self.expressions_used.insert(query); self.trace_ray_query_function(fun); } - St::MeshFunction(crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - }) => { - self.expressions_used.insert(vertex_count); - self.expressions_used.insert(primitive_count); - } - St::MeshFunction( - crate::MeshFunction::SetPrimitive { index, value } - | crate::MeshFunction::SetVertex { index, value }, - ) => { - self.expressions_used.insert(index); - self.expressions_used.insert(value); - } St::SubgroupBallot { result, predicate } => { if let Some(predicate) = predicate { self.expressions_used.insert(predicate); @@ -349,26 +335,6 @@ impl FunctionMap { adjust(query); self.adjust_ray_query_function(fun); } - St::MeshFunction(crate::MeshFunction::SetMeshOutputs { - ref mut vertex_count, - ref mut primitive_count, - }) => { - adjust(vertex_count); - adjust(primitive_count); - } - St::MeshFunction( - crate::MeshFunction::SetVertex { - ref mut index, - ref mut value, - } - | crate::MeshFunction::SetPrimitive { - ref mut index, - ref mut value, - }, - ) => { - adjust(index); - adjust(value); - } St::SubgroupBallot { ref mut result, ref mut predicate, diff --git a/naga/src/front/spv/mod.rs b/naga/src/front/spv/mod.rs index 2a3a971a8bf..ac9eaf8306f 100644 --- a/naga/src/front/spv/mod.rs +++ b/naga/src/front/spv/mod.rs @@ -4661,7 +4661,6 @@ impl> Frontend { | S::Atomic { .. } | S::ImageAtomic { .. } | S::RayQuery { .. } - | S::MeshFunction(..) | S::SubgroupBallot { .. } | S::SubgroupCollectiveOperation { .. } | S::SubgroupGather { .. } => {} diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index 004528dbe91..a8958525ad1 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -406,19 +406,9 @@ pub(crate) enum Error<'a> { accept_span: Span, accept_type: String, }, - MissingMeshShaderInfo { - mesh_attribute_span: Span, - }, - OneMeshShaderAttribute { - attribute_span: Span, - }, ExpectedGlobalVariable { name_span: Span, }, - MeshPrimitiveNoDefinedTopology { - attribute_span: Span, - struct_span: Span, - }, StructMemberTooLarge { member_name_span: Span, }, @@ -1383,27 +1373,12 @@ impl<'a> Error<'a> { ], notes: vec![], }, - Error::MissingMeshShaderInfo { mesh_attribute_span} => ParseError { - message: "mesh shader entry point is missing both `@vertex_output` and `@primitive_output`".into(), - labels: vec![(mesh_attribute_span, "mesh shader entry declared here".into())], - notes: vec![], - }, - Error::OneMeshShaderAttribute { attribute_span } => ParseError { - message: "only one of `@vertex_output` or `@primitive_output` was given".into(), - labels: vec![(attribute_span, "only one of @vertex_output or @primitive_output is provided".into())], - notes: vec![], - }, Error::ExpectedGlobalVariable { name_span } => ParseError { message: "expected global variable".to_string(), // TODO: I would like to also include the global declaration span labels: vec![(name_span, "variable used here".into())], notes: vec![], }, - Error::MeshPrimitiveNoDefinedTopology { struct_span, attribute_span } => ParseError { - message: "mesh primitive struct must have exactly one of point indices, line indices, or triangle indices".to_string(), - labels: vec![(attribute_span, "primitive type declared here".into()), (struct_span, "primitive struct declared here".into())], - notes: vec![] - }, Error::StructMemberTooLarge { member_name_span } => ParseError { message: "struct member is too large".into(), labels: vec![(member_name_span, "this member exceeds the maximum size".into())], diff --git a/naga/src/front/wgsl/lower/mod.rs b/naga/src/front/wgsl/lower/mod.rs index ef63e6aaea7..33a1de6d579 100644 --- a/naga/src/front/wgsl/lower/mod.rs +++ b/naga/src/front/wgsl/lower/mod.rs @@ -1520,88 +1520,34 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ([0; 3], None) }; - let mesh_info = if let Some(mesh_info) = entry.mesh_shader_info { - let mut const_u32 = |expr| match self.const_u32(expr, &mut ctx.as_const()) { - Ok(value) => Ok((value.0, None)), - Err(err) => { - if let Error::ConstantEvaluatorError(ref ty, _) = *err { - match **ty { - proc::ConstantEvaluatorError::OverrideExpr => Ok(( - 0, - Some( - // This is dubious but it seems the code isn't workgroup size specific - self.workgroup_size_override(expr, &mut ctx.as_override())?, - ), - )), - _ => Err(err), - } - } else { - Err(err) - } - } - }; - let (max_vertices, max_vertices_override) = const_u32(mesh_info.vertex_count)?; - let (max_primitives, max_primitives_override) = - const_u32(mesh_info.primitive_count)?; - let vertex_output_type = - self.resolve_ast_type(mesh_info.vertex_type.0, &mut ctx.as_const())?; - let primitive_output_type = - self.resolve_ast_type(mesh_info.primitive_type.0, &mut ctx.as_const())?; - - let mut topology = None; - let struct_span = ctx.module.types.get_span(primitive_output_type); - match &ctx.module.types[primitive_output_type].inner { - &ir::TypeInner::Struct { - ref members, - span: _, - } => { - for member in members { - let out_topology = match member.binding { - Some(ir::Binding::BuiltIn(ir::BuiltIn::TriangleIndices)) => { - Some(ir::MeshOutputTopology::Triangles) - } - Some(ir::Binding::BuiltIn(ir::BuiltIn::LineIndices)) => { - Some(ir::MeshOutputTopology::Lines) - } - _ => None, - }; - if out_topology.is_some() { - if topology.is_some() { - return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { - attribute_span: mesh_info.primitive_type.1, - struct_span, - })); - } - topology = out_topology; - } - } - } - _ => { - return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { - attribute_span: mesh_info.primitive_type.1, - struct_span, + let mesh_info = if let Some((var_name, var_span)) = entry.mesh_output_variable { + let var = match ctx.globals.get(var_name) { + Some(&LoweredGlobalDecl::Var(handle)) => handle, + Some(_) => { + return Err(Box::new(Error::ExpectedGlobalVariable { + name_span: var_span, })) } - } - let topology = if let Some(t) = topology { - t - } else { - return Err(Box::new(Error::MeshPrimitiveNoDefinedTopology { - attribute_span: mesh_info.primitive_type.1, - struct_span, - })); + None => return Err(Box::new(Error::UnknownIdent(var_span, var_name))), }; - Some(ir::MeshStageInfo { - max_vertices, - max_vertices_override, - max_primitives, - max_primitives_override, + let mut info = ctx.module.analyze_mesh_shader_info(var); + if let Some(h) = info.1[0] { + info.0.max_vertices_override = Some( + ctx.module + .global_expressions + .append(crate::Expression::Override(h), Span::UNDEFINED), + ); + } + if let Some(h) = info.1[1] { + info.0.max_primitives_override = Some( + ctx.module + .global_expressions + .append(crate::Expression::Override(h), Span::UNDEFINED), + ); + } - vertex_output_type, - primitive_output_type, - topology, - }) + Some(info.0) } else { None }; @@ -3232,59 +3178,6 @@ impl<'source, 'temp> Lowerer<'source, 'temp> { ); return Ok(Some(result)); } - - "setMeshOutputs" | "setVertex" | "setPrimitive" => { - let mut args = ctx.prepare_args(arguments, 2, span); - let arg1 = args.next()?; - let arg2 = args.next()?; - args.finish()?; - - let mut cast_u32 = |arg| { - // Try to convert abstract values to the known argument types - let expr = self.expression_for_abstract(arg, ctx)?; - let goal_ty = - ctx.ensure_type_exists(ir::TypeInner::Scalar(ir::Scalar::U32)); - ctx.try_automatic_conversions( - expr, - &proc::TypeResolution::Handle(goal_ty), - ctx.ast_expressions.get_span(arg), - ) - }; - - let arg1 = cast_u32(arg1)?; - let arg2 = if function.name == "setMeshOutputs" { - cast_u32(arg2)? - } else { - self.expression(arg2, ctx)? - }; - - let rctx = ctx.runtime_expression_ctx(span)?; - - // Emit all previous expressions, even if not used directly - rctx.block - .extend(rctx.emitter.finish(&rctx.function.expressions)); - rctx.block.push( - crate::Statement::MeshFunction(match function.name { - "setMeshOutputs" => crate::MeshFunction::SetMeshOutputs { - vertex_count: arg1, - primitive_count: arg2, - }, - "setVertex" => crate::MeshFunction::SetVertex { - index: arg1, - value: arg2, - }, - "setPrimitive" => crate::MeshFunction::SetPrimitive { - index: arg1, - value: arg2, - }, - _ => unreachable!(), - }), - span, - ); - rctx.emitter.start(&rctx.function.expressions); - - return Ok(None); - } _ => { return Err(Box::new(Error::UnknownIdent(function.span, function.name))) } diff --git a/naga/src/front/wgsl/parse/ast.rs b/naga/src/front/wgsl/parse/ast.rs index 49ecddfdee5..04964e7ba5f 100644 --- a/naga/src/front/wgsl/parse/ast.rs +++ b/naga/src/front/wgsl/parse/ast.rs @@ -128,18 +128,10 @@ pub struct EntryPoint<'a> { pub stage: crate::ShaderStage, pub early_depth_test: Option, pub workgroup_size: Option<[Option>>; 3]>, - pub mesh_shader_info: Option>, + pub mesh_output_variable: Option<(&'a str, Span)>, pub task_payload: Option<(&'a str, Span)>, } -#[derive(Debug, Clone, Copy)] -pub struct EntryPointMeshShaderInfo<'a> { - pub vertex_count: Handle>, - pub primitive_count: Handle>, - pub vertex_type: (Handle>, Span), - pub primitive_type: (Handle>, Span), -} - #[cfg(doc)] use crate::front::wgsl::lower::{LocalExpressionContext, StatementContext}; diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 3b96bde7c9e..16e814f56f5 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -53,10 +53,15 @@ pub fn map_built_in( "subgroup_invocation_id" => crate::BuiltIn::SubgroupInvocationId, // mesh "cull_primitive" => crate::BuiltIn::CullPrimitive, - "point_index" => crate::BuiltIn::PointIndex, + "vertex_indices" => crate::BuiltIn::PointIndex, "line_indices" => crate::BuiltIn::LineIndices, "triangle_indices" => crate::BuiltIn::TriangleIndices, "mesh_task_size" => crate::BuiltIn::MeshTaskSize, + // mesh global variable + "vertex_count" => crate::BuiltIn::VertexCount, + "vertices" => crate::BuiltIn::Vertices, + "primitive_count" => crate::BuiltIn::PrimitiveCount, + "primitives" => crate::BuiltIn::Primitives, _ => return Err(Box::new(Error::UnknownBuiltin(span))), }; match built_in { diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 29376614d6e..94df933a6a9 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -2803,8 +2803,7 @@ impl Parser { (ParsedAttribute::default(), ParsedAttribute::default()); let mut id = ParsedAttribute::default(); let mut payload = ParsedAttribute::default(); - let mut vertex_output = ParsedAttribute::default(); - let mut primitive_output = ParsedAttribute::default(); + let mut mesh_output = ParsedAttribute::default(); let mut must_use: ParsedAttribute = ParsedAttribute::default(); @@ -2872,27 +2871,16 @@ impl Parser { "mesh" => { stage.set(ShaderStage::Mesh, name_span)?; compute_like_span = name_span; + + lexer.expect(Token::Paren('('))?; + mesh_output.set(lexer.next_ident_with_span()?, name_span)?; + lexer.expect(Token::Paren(')'))?; } "payload" => { lexer.expect(Token::Paren('('))?; payload.set(lexer.next_ident_with_span()?, name_span)?; lexer.expect(Token::Paren(')'))?; } - "vertex_output" | "primitive_output" => { - lexer.expect(Token::Paren('('))?; - let type_span = lexer.peek().1; - let r#type = self.type_decl(lexer, &mut ctx)?; - let type_span = lexer.span_from(type_span.to_range().unwrap().start); - lexer.expect(Token::Separator(','))?; - let max_output = self.general_expression(lexer, &mut ctx)?; - let end_span = lexer.expect_span(Token::Paren(')'))?; - let total_span = name_span.until(&end_span); - if name == "vertex_output" { - vertex_output.set((r#type, type_span, max_output), total_span)?; - } else if name == "primitive_output" { - primitive_output.set((r#type, type_span, max_output), total_span)?; - } - } "workgroup_size" => { lexer.expect(Token::Paren('('))?; let mut new_workgroup_size = [None; 3]; @@ -3060,35 +3048,12 @@ impl Parser { if stage.compute_like() && workgroup_size.value.is_none() { return Err(Box::new(Error::MissingWorkgroupSize(compute_like_span))); } - if stage == ShaderStage::Mesh - && (vertex_output.value.is_none() || primitive_output.value.is_none()) - { - return Err(Box::new(Error::MissingMeshShaderInfo { - mesh_attribute_span: compute_like_span, - })); - } - let mesh_shader_info = match (vertex_output.value, primitive_output.value) { - (Some(vertex_output), Some(primitive_output)) => { - Some(ast::EntryPointMeshShaderInfo { - vertex_count: vertex_output.2, - primitive_count: primitive_output.2, - vertex_type: (vertex_output.0, vertex_output.1), - primitive_type: (primitive_output.0, primitive_output.1), - }) - } - (None, None) => None, - (Some(v), None) | (None, Some(v)) => { - return Err(Box::new(Error::OneMeshShaderAttribute { - attribute_span: v.1, - })) - } - }; Some(ast::EntryPoint { stage, early_depth_test: early_depth_test.value, workgroup_size: workgroup_size.value, - mesh_shader_info, + mesh_output_variable: mesh_output.value, task_payload: payload.value, }) } else { diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 4093d823b4b..097220a46bb 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -450,6 +450,15 @@ pub enum BuiltIn { LineIndices, /// Written in mesh shaders TriangleIndices, + + /// Written to a workgroup variable in mesh shaders + VertexCount, + /// Written to a workgroup variable in mesh shaders + Vertices, + /// Written to a workgroup variable in mesh shaders + PrimitiveCount, + /// Written to a workgroup variable in mesh shaders + Primitives, } /// Number of bytes per scalar. @@ -2211,8 +2220,6 @@ pub enum Statement { /// The specific operation we're performing on `query`. fun: RayQueryFunction, }, - /// A mesh shader intrinsic. - MeshFunction(MeshFunction), /// Calculate a bitmask using a boolean from each active thread in the subgroup SubgroupBallot { /// The [`SubgroupBallotResult`] expression representing this load's result. @@ -2569,7 +2576,7 @@ pub struct DocComments { } /// The output topology for a mesh shader. Note that mesh shaders don't allow things like triangle-strips. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -2583,7 +2590,7 @@ pub enum MeshOutputTopology { } /// Information specific to mesh shader entry points. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serialize", derive(Serialize))] #[cfg_attr(feature = "deserialize", derive(Deserialize))] #[cfg_attr(feature = "arbitrary", derive(Arbitrary))] @@ -2603,29 +2610,8 @@ pub struct MeshStageInfo { pub vertex_output_type: Handle, /// The type used by primitive outputs, i.e. what is passed to `setPrimitive`. pub primitive_output_type: Handle, -} - -/// Mesh shader intrinsics -#[derive(Debug, Clone, Copy)] -#[cfg_attr(feature = "serialize", derive(Serialize))] -#[cfg_attr(feature = "deserialize", derive(Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(Arbitrary))] -pub enum MeshFunction { - /// Sets the number of vertices and primitives that will be outputted. - SetMeshOutputs { - vertex_count: Handle, - primitive_count: Handle, - }, - /// Sets the output vertex at a given index. - SetVertex { - index: Handle, - value: Handle, - }, - /// Sets the output primitive at a given index. - SetPrimitive { - index: Handle, - value: Handle, - }, + /// The global variable holding the outputted vertices, primitives, and counts + pub output_variable: Handle, } /// Shader module. diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index eca63ee4fb5..dd2ae459373 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -27,6 +27,8 @@ use thiserror::Error; pub use type_methods::min_max_float_representable_by; pub use typifier::{compare_types, ResolveContext, ResolveError, TypeResolution}; +use crate::non_max_u32::NonMaxU32; + impl From for super::Scalar { fn from(format: super::StorageFormat) -> Self { use super::{ScalarKind as Sk, StorageFormat as Sf}; @@ -653,3 +655,149 @@ fn test_matrix_size() { 48, ); } + +impl crate::Module { + /// Extracts mesh shader info from a mesh output global variable. Used in frontends + /// and by validators. This only validates the output variable itself, and not the + /// vertex and primitive output types. + #[allow(clippy::type_complexity)] + pub fn analyze_mesh_shader_info( + &self, + gv: crate::Handle, + ) -> ( + crate::MeshStageInfo, + [Option>; 2], + Option>, + ) { + use crate::span::AddSpan; + use crate::valid::EntryPointError; + let null_type = crate::Handle::new(NonMaxU32::new(0).unwrap()); + let mut output = crate::MeshStageInfo { + topology: crate::MeshOutputTopology::Triangles, + max_vertices: 0, + max_vertices_override: None, + max_primitives: 0, + max_primitives_override: None, + vertex_output_type: null_type, + primitive_output_type: null_type, + output_variable: gv, + }; + let mut error = None; + let typ = &self.types[self.global_variables[gv].ty].inner; + + let mut topology = output.topology; + // Max, max override, type + let mut vertex_info = (0, None, null_type); + let mut primitive_info = (0, None, null_type); + + match typ { + &crate::TypeInner::Struct { ref members, .. } => { + let mut builtins = crate::FastHashSet::default(); + for member in members { + match member.binding { + Some(crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) => { + if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { + error = Some(EntryPointError::BadMeshOutputVariableField); + } + if builtins.contains(&crate::BuiltIn::VertexCount) { + error = Some(EntryPointError::BadMeshOutputVarableType); + } + builtins.insert(crate::BuiltIn::VertexCount); + } + Some(crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) => { + if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { + error = Some(EntryPointError::BadMeshOutputVariableField); + } + if builtins.contains(&crate::BuiltIn::PrimitiveCount) { + error = Some(EntryPointError::BadMeshOutputVarableType); + } + builtins.insert(crate::BuiltIn::PrimitiveCount); + } + Some(crate::Binding::BuiltIn( + crate::BuiltIn::Vertices | crate::BuiltIn::Primitives, + )) => { + let ty = &self.types[member.ty].inner; + let (a, b, c) = match ty { + &crate::TypeInner::Array { base, size, .. } => { + let ty = base; + let (max, max_override) = match size { + crate::ArraySize::Constant(a) => (a.get(), None), + crate::ArraySize::Pending(o) => (0, Some(o)), + crate::ArraySize::Dynamic => { + error = + Some(EntryPointError::BadMeshOutputVariableField); + (0, None) + } + }; + (max, max_override, ty) + } + _ => { + error = Some(EntryPointError::BadMeshOutputVariableField); + (0, None, null_type) + } + }; + if matches!( + member.binding, + Some(crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + ) { + primitive_info = (a, b, c); + match self.types[c].inner { + crate::TypeInner::Struct { ref members, .. } => { + for member in members { + match member.binding { + Some(crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex, + )) => { + topology = crate::MeshOutputTopology::Points; + } + Some(crate::Binding::BuiltIn( + crate::BuiltIn::LineIndices, + )) => { + topology = crate::MeshOutputTopology::Lines; + } + Some(crate::Binding::BuiltIn( + crate::BuiltIn::TriangleIndices, + )) => { + topology = crate::MeshOutputTopology::Triangles; + } + _ => (), + } + } + } + _ => (), + } + if builtins.contains(&crate::BuiltIn::Primitives) { + error = Some(EntryPointError::BadMeshOutputVarableType); + } + builtins.insert(crate::BuiltIn::Primitives); + } else { + vertex_info = (a, b, c); + if builtins.contains(&crate::BuiltIn::Vertices) { + error = Some(EntryPointError::BadMeshOutputVarableType); + } + builtins.insert(crate::BuiltIn::Vertices); + } + } + _ => error = Some(EntryPointError::BadMeshOutputVarableType), + } + } + output = crate::MeshStageInfo { + topology, + max_vertices: vertex_info.0, + max_vertices_override: None, + vertex_output_type: vertex_info.2, + max_primitives: primitive_info.0, + max_primitives_override: None, + primitive_output_type: primitive_info.2, + ..output + } + } + _ => error = Some(EntryPointError::BadMeshOutputVarableType), + } + ( + output, + [vertex_info.1, primitive_info.1], + error.map(|a| a.with_span_handle(self.global_variables[gv].ty, &self.types)), + ) + } +} diff --git a/naga/src/proc/terminator.rs b/naga/src/proc/terminator.rs index f76d4c06a3b..b29ccb054a3 100644 --- a/naga/src/proc/terminator.rs +++ b/naga/src/proc/terminator.rs @@ -36,7 +36,6 @@ pub fn ensure_block_returns(block: &mut crate::Block) { | S::ImageStore { .. } | S::Call { .. } | S::RayQuery { .. } - | S::MeshFunction(..) | S::Atomic { .. } | S::ImageAtomic { .. } | S::WorkGroupUniformLoad { .. } diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 6ef2ca0988d..5befdfe22a6 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -1155,36 +1155,6 @@ impl FunctionInfo { } FunctionUniformity::new() } - S::MeshFunction(func) => { - self.available_stages |= ShaderStages::MESH; - match &func { - // TODO: double check all of this uniformity stuff. I frankly don't fully understand all of it. - &crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - } => { - let _ = self.add_ref(vertex_count); - let _ = self.add_ref(primitive_count); - FunctionUniformity::new() - } - &crate::MeshFunction::SetVertex { index, value } - | &crate::MeshFunction::SetPrimitive { index, value } => { - let _ = self.add_ref(index); - let _ = self.add_ref(value); - let ty = self.expressions[value.index()].ty.handle().ok_or( - FunctionError::InvalidMeshShaderOutputType(value).with_span(), - )?; - - if matches!(func, crate::MeshFunction::SetVertex { .. }) { - self.try_update_mesh_vertex_type(ty, value)?; - } else { - self.try_update_mesh_primitive_type(ty, value)?; - }; - - FunctionUniformity::new() - } - } - } S::SubgroupBallot { result: _, predicate, diff --git a/naga/src/valid/function.rs b/naga/src/valid/function.rs index 0216c6ef7f6..abf6bc430a6 100644 --- a/naga/src/valid/function.rs +++ b/naga/src/valid/function.rs @@ -1547,41 +1547,6 @@ impl super::Validator { crate::RayQueryFunction::Terminate => {} } } - S::MeshFunction(func) => { - let ensure_u32 = - |expr: Handle| -> Result<(), WithSpan> { - let u32_ty = TypeResolution::Value(Ti::Scalar(crate::Scalar::U32)); - let ty = context - .resolve_type_impl(expr, &self.valid_expression_set) - .map_err_inner(|source| { - FunctionError::Expression { - source, - handle: expr, - } - .with_span_handle(expr, context.expressions) - })?; - if !context.compare_types(&u32_ty, ty) { - return Err(FunctionError::InvalidMeshFunctionCall(expr) - .with_span_handle(expr, context.expressions)); - } - Ok(()) - }; - match func { - crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - } => { - ensure_u32(vertex_count)?; - ensure_u32(primitive_count)?; - } - crate::MeshFunction::SetVertex { index, value: _ } - | crate::MeshFunction::SetPrimitive { index, value: _ } => { - ensure_u32(index)?; - // Value is validated elsewhere (since the value type isn't known ahead of time but must match for all calls - // in a function or the function's called functions) - } - } - } S::SubgroupBallot { result, predicate } => { stages &= self.subgroup_stages; if !self.capabilities.contains(super::Capabilities::SUBGROUP) { diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs index adb9f355c11..7fe6fa8803d 100644 --- a/naga/src/valid/handles.rs +++ b/naga/src/valid/handles.rs @@ -815,22 +815,6 @@ impl super::Validator { } Ok(()) } - crate::Statement::MeshFunction(func) => match func { - crate::MeshFunction::SetMeshOutputs { - vertex_count, - primitive_count, - } => { - validate_expr(vertex_count)?; - validate_expr(primitive_count)?; - Ok(()) - } - crate::MeshFunction::SetVertex { index, value } - | crate::MeshFunction::SetPrimitive { index, value } => { - validate_expr(index)?; - validate_expr(value)?; - Ok(()) - } - }, crate::Statement::SubgroupBallot { result, predicate } => { validate_expr_opt(predicate)?; validate_expr(result)?; diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index f3f5a43c060..e5e7b6997b1 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -141,24 +141,31 @@ pub enum EntryPointError { TaskPayloadWrongAddressSpace, #[error("For a task payload to be used, it must be declared with @payload")] WrongTaskPayloadUsed, - #[error("A function can only set vertex and primitive types that correspond to the mesh shader attributes")] - WrongMeshOutputType, - #[error("Only mesh shader entry points can write to mesh output vertices and primitives")] - UnexpectedMeshShaderOutput, - #[error("Mesh shader entry point cannot have a return type")] - UnexpectedMeshShaderEntryResult, #[error("Task shader entry point must return @builtin(mesh_task_size) vec3")] WrongTaskShaderEntryResult, - #[error("Mesh output type must be a user-defined struct.")] - InvalidMeshOutputType, - #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] - InvalidMeshPrimitiveOutputType, #[error("Task shaders must declare a task payload output")] ExpectedTaskPayload, #[error( - "The `MESH_SHADER` capability must be enabled to compile mesh shaders and task shaders." + "The `MESH_SHADER` capability must be enabled to compile mesh shaders and task shaders" )] MeshShaderCapabilityDisabled, + + #[error( + "Mesh shader output variable must be a struct with fields that are all allowed builtins" + )] + BadMeshOutputVarableType, + #[error("Mesh shader output variable fields must have types that are in accordance with the mesh shader spec")] + BadMeshOutputVariableField, + #[error("Mesh shader entry point cannot have a return type")] + UnexpectedMeshShaderEntryResult, + #[error( + "Mesh output type must be a user-defined struct with fields in alignment with the mesh shader spec" + )] + InvalidMeshOutputType, + #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] + InvalidMeshPrimitiveOutputType, + #[error("Mesh output global variable must live in the workgroup address space")] + WrongMeshOutputAddressSpace, } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -390,6 +397,10 @@ impl VaryingContext<'_> { scalar: crate::Scalar::U32, }, ), + // Validated elsewhere + Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives => { + (true, true) + } }; if !visible { @@ -1074,24 +1085,44 @@ impl super::Validator { } } + // TODO: validate mesh entry point info + // If this is a `Mesh` entry point, check its vertex and primitive output types. // We verified previously that only mesh shaders can have `mesh_info`. if let &Some(ref mesh_info) = &ep.mesh_info { - // Mesh shaders don't return any value. All their results are supplied through - // [`SetVertex`] and [`SetPrimitive`] calls. - if let Some((used_vertex_type, _)) = info.mesh_shader_info.vertex_type { - if used_vertex_type != mesh_info.vertex_output_type { - return Err(EntryPointError::WrongMeshOutputType - .with_span_handle(mesh_info.vertex_output_type, &module.types)); + // TODO: validate global variable + if module.global_variables[mesh_info.output_variable].space + != crate::AddressSpace::WorkGroup + { + return Err(EntryPointError::WrongMeshOutputAddressSpace.with_span()); + } + + let mut implied = module.analyze_mesh_shader_info(mesh_info.output_variable); + if let Some(e) = implied.2 { + return Err(e); + } + + if let Some(e) = mesh_info.max_vertices_override { + if let crate::Expression::Override(o) = module.global_expressions[e] { + if implied.1[0] != Some(o) { + return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + } } } - if let Some((used_primitive_type, _)) = info.mesh_shader_info.primitive_type { - if used_primitive_type != mesh_info.primitive_output_type { - return Err(EntryPointError::WrongMeshOutputType - .with_span_handle(mesh_info.primitive_output_type, &module.types)); + if let Some(e) = mesh_info.max_primitives_override { + if let crate::Expression::Override(o) = module.global_expressions[e] { + if implied.1[1] != Some(o) { + return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + } } } + implied.0.max_vertices_override = mesh_info.max_vertices_override; + implied.0.max_primitives_override = mesh_info.max_primitives_override; + if implied.0 != *mesh_info { + return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + } + self.validate_mesh_output_type( ep, module, @@ -1110,7 +1141,7 @@ impl super::Validator { if info.mesh_shader_info.vertex_type.is_some() || info.mesh_shader_info.primitive_type.is_some() { - return Err(EntryPointError::UnexpectedMeshShaderOutput.with_span()); + return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span()); } } diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index 7f094a82f81..cdc7366b415 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -38,32 +38,35 @@ fn ts_main() -> @builtin(mesh_task_size) vec3 { taskPayload.visible = true; return vec3(3, 1, 1); } -@mesh + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; +@mesh(mesh_output) @payload(taskPayload) -@vertex_output(VertexOutput, 3) @primitive_output(PrimitiveOutput, 1) @workgroup_size(1) fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { - setMeshOutputs(3, 1); + mesh_output.vertex_count = 3; + mesh_output.primitive_count = 1; workgroupData = 2.0; - var v: VertexOutput; - v.position = positions[0]; - v.color = colors[0] * taskPayload.colorMask; - setVertex(0, v); + mesh_output.vertices[0].position = positions[0]; + mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; - v.position = positions[1]; - v.color = colors[1] * taskPayload.colorMask; - setVertex(1, v); + mesh_output.vertices[1].position = positions[1]; + mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; - v.position = positions[2]; - v.color = colors[2] * taskPayload.colorMask; - setVertex(2, v); + mesh_output.vertices[2].position = positions[2]; + mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; - var p: PrimitiveOutput; - p.index = vec3(0, 1, 2); - p.cull = !taskPayload.visible; - p.colorMask = vec4(1.0, 0.0, 1.0, 1.0); - setPrimitive(0, p); + mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 208e0aac84e..9ba7187ac69 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -9,6 +9,9 @@ ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ], functions: [], entry_points: [ @@ -24,6 +27,7 @@ global_uses: [ ("READ | WRITE"), ("WRITE"), + (""), ], expressions: [ ( @@ -233,6 +237,7 @@ global_uses: [ ("READ"), ("WRITE"), + ("WRITE"), ], expressions: [ ( @@ -253,6 +258,30 @@ assignable_global: None, ty: Handle(6), ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: WorkGroup, + )), + ), ( uniformity: ( non_uniform_result: None, @@ -265,6 +294,30 @@ width: 4, ))), ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: WorkGroup, + )), + ), ( uniformity: ( non_uniform_result: None, @@ -303,26 +356,50 @@ ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), - ref_count: 9, - assignable_global: None, + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 4, - space: Function, + space: WorkGroup, )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -384,14 +461,50 @@ ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 4, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -499,31 +612,46 @@ requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Handle(4), + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 4, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -585,14 +713,50 @@ ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 4, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -700,31 +864,46 @@ requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Handle(4), + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 4, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -786,14 +965,50 @@ ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 9, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 4, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 1, - space: Function, + space: WorkGroup, )), ), ( @@ -901,43 +1116,46 @@ requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(6), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Handle(4), + assignable_global: Some(2), + ty: Value(Pointer( + base: 10, + space: WorkGroup, + )), ), ( uniformity: ( - non_uniform_result: Some(61), + non_uniform_result: None, requirements: (""), ), - ref_count: 4, - assignable_global: None, + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 7, - space: Function, + space: WorkGroup, )), ), ( uniformity: ( - non_uniform_result: Some(61), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), ty: Value(Pointer( base: 6, - space: Function, + space: WorkGroup, )), ), ( @@ -987,14 +1205,50 @@ ), ( uniformity: ( - non_uniform_result: Some(61), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 10, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 7, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), ty: Value(Pointer( base: 2, - space: Function, + space: WorkGroup, )), ), ( @@ -1041,14 +1295,14 @@ ), ( uniformity: ( - non_uniform_result: Some(61), + non_uniform_result: None, requirements: (""), ), ref_count: 1, - assignable_global: None, + assignable_global: Some(2), ty: Value(Pointer( - base: 1, - space: Function, + base: 11, + space: WorkGroup, )), ), ( @@ -1057,11 +1311,11 @@ requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), + assignable_global: Some(2), + ty: Value(Pointer( + base: 10, + space: WorkGroup, + )), ), ( uniformity: ( @@ -1069,11 +1323,23 @@ requirements: (""), ), ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), + assignable_global: Some(2), + ty: Value(Pointer( + base: 7, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 1, + space: WorkGroup, + )), ), ( uniformity: ( @@ -1106,7 +1372,10 @@ ), ref_count: 1, assignable_global: None, - ty: Handle(1), + ty: Value(Scalar(( + kind: Float, + width: 4, + ))), ), ( uniformity: ( @@ -1116,26 +1385,26 @@ ref_count: 1, assignable_global: None, ty: Value(Scalar(( - kind: Uint, + kind: Float, width: 4, ))), ), ( uniformity: ( - non_uniform_result: Some(61), + non_uniform_result: None, requirements: (""), ), ref_count: 1, assignable_global: None, - ty: Handle(7), + ty: Handle(1), ), ], sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, mesh_shader_info: ( - vertex_type: Some((4, 24)), - primitive_type: Some((7, 79)), + vertex_type: None, + primitive_type: None, ), ), ( @@ -1150,6 +1419,7 @@ global_uses: [ (""), (""), + (""), ], expressions: [ ( diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron index 38c79cba451..1147b017f5c 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -141,6 +141,54 @@ span: 16, ), ), + ( + name: None, + inner: Array( + base: 4, + size: Constant(3), + stride: 32, + ), + ), + ( + name: None, + inner: Array( + base: 7, + size: Constant(1), + stride: 32, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 9, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 10, + binding: Some(BuiltIn(Primitives)), + offset: 96, + ), + ( + name: Some("vertex_count"), + ty: 5, + binding: Some(BuiltIn(VertexCount)), + offset: 128, + ), + ( + name: Some("primitive_count"), + ty: 5, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 132, + ), + ], + span: 144, + ), + ), ], special_types: ( ray_desc: None, @@ -167,6 +215,13 @@ ty: 0, init: None, ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 11, + init: None, + ), ], global_expressions: [], functions: [], @@ -292,28 +347,35 @@ ), ], result: None, - local_variables: [ - ( - name: Some("v"), - ty: 4, - init: None, - ), - ( - name: Some("p"), - ty: 7, - init: None, - ), - ], + local_variables: [], expressions: [ FunctionArgument(0), FunctionArgument(1), + GlobalVariable(2), + AccessIndex( + base: 2, + index: 2, + ), Literal(U32(3)), + GlobalVariable(2), + AccessIndex( + base: 5, + index: 3, + ), Literal(U32(1)), GlobalVariable(1), Literal(F32(2.0)), - LocalVariable(0), + GlobalVariable(2), + AccessIndex( + base: 10, + index: 0, + ), + AccessIndex( + base: 11, + index: 0, + ), AccessIndex( - base: 6, + base: 12, index: 0, ), Literal(F32(0.0)), @@ -323,23 +385,32 @@ Compose( ty: 1, components: [ - 8, - 9, - 10, - 11, + 14, + 15, + 16, + 17, ], ), + GlobalVariable(2), + AccessIndex( + base: 19, + index: 0, + ), + AccessIndex( + base: 20, + index: 0, + ), AccessIndex( - base: 6, + base: 21, index: 1, ), GlobalVariable(0), AccessIndex( - base: 14, + base: 23, index: 0, ), Load( - pointer: 15, + pointer: 24, ), Literal(F32(0.0)), Literal(F32(1.0)), @@ -348,23 +419,28 @@ Compose( ty: 1, components: [ - 17, - 18, - 19, - 20, + 26, + 27, + 28, + 29, ], ), Binary( op: Multiply, - left: 21, - right: 16, + left: 30, + right: 25, ), - Literal(U32(0)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 32, + index: 0, + ), + AccessIndex( + base: 33, + index: 1, ), AccessIndex( - base: 6, + base: 34, index: 0, ), Literal(F32(-1.0)), @@ -374,23 +450,32 @@ Compose( ty: 1, components: [ - 26, - 27, - 28, - 29, + 36, + 37, + 38, + 39, ], ), + GlobalVariable(2), AccessIndex( - base: 6, + base: 41, + index: 0, + ), + AccessIndex( + base: 42, + index: 1, + ), + AccessIndex( + base: 43, index: 1, ), GlobalVariable(0), AccessIndex( - base: 32, + base: 45, index: 0, ), Load( - pointer: 33, + pointer: 46, ), Literal(F32(0.0)), Literal(F32(0.0)), @@ -399,23 +484,28 @@ Compose( ty: 1, components: [ - 35, - 36, - 37, - 38, + 48, + 49, + 50, + 51, ], ), Binary( op: Multiply, - left: 39, - right: 34, + left: 52, + right: 47, ), - Literal(U32(1)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 55, + index: 2, ), AccessIndex( - base: 6, + base: 56, index: 0, ), Literal(F32(1.0)), @@ -425,23 +515,32 @@ Compose( ty: 1, components: [ - 44, - 45, - 46, - 47, + 58, + 59, + 60, + 61, ], ), + GlobalVariable(2), + AccessIndex( + base: 63, + index: 0, + ), AccessIndex( - base: 6, + base: 64, + index: 2, + ), + AccessIndex( + base: 65, index: 1, ), GlobalVariable(0), AccessIndex( - base: 50, + base: 67, index: 0, ), Load( - pointer: 51, + pointer: 68, ), Literal(F32(1.0)), Literal(F32(0.0)), @@ -450,24 +549,28 @@ Compose( ty: 1, components: [ - 53, - 54, - 55, - 56, + 70, + 71, + 72, + 73, ], ), Binary( op: Multiply, - left: 57, - right: 52, + left: 74, + right: 69, ), - Literal(U32(2)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 76, + index: 1, + ), + AccessIndex( + base: 77, + index: 0, ), - LocalVariable(1), AccessIndex( - base: 61, + base: 78, index: 0, ), Literal(U32(0)), @@ -476,29 +579,47 @@ Compose( ty: 6, components: [ - 63, - 64, - 65, + 80, + 81, + 82, ], ), + GlobalVariable(2), AccessIndex( - base: 61, + base: 84, + index: 1, + ), + AccessIndex( + base: 85, + index: 0, + ), + AccessIndex( + base: 86, index: 1, ), GlobalVariable(0), AccessIndex( - base: 68, + base: 88, index: 1, ), Load( - pointer: 69, + pointer: 89, ), Unary( op: LogicalNot, - expr: 70, + expr: 90, + ), + GlobalVariable(2), + AccessIndex( + base: 92, + index: 1, + ), + AccessIndex( + base: 93, + index: 0, ), AccessIndex( - base: 61, + base: 94, index: 2, ), Literal(F32(1.0)), @@ -508,33 +629,45 @@ Compose( ty: 1, components: [ - 73, - 74, - 75, - 76, + 96, + 97, + 98, + 99, ], ), - Literal(U32(0)), - Load( - pointer: 61, - ), ], named_expressions: { 0: "index", 1: "id", }, body: [ - MeshFunction(SetMeshOutputs( - vertex_count: 2, - primitive_count: 3, + Emit(( + start: 3, + end: 4, )), Store( - pointer: 4, - value: 5, + pointer: 3, + value: 4, + ), + Emit(( + start: 6, + end: 7, + )), + Store( + pointer: 6, + value: 7, + ), + Store( + pointer: 8, + value: 9, ), Emit(( - start: 7, - end: 8, + start: 11, + end: 12, + )), + Emit(( + start: 12, + end: 14, )), Emit(( start: 0, @@ -549,16 +682,20 @@ end: 0, )), Emit(( - start: 12, - end: 13, + start: 18, + end: 19, )), Store( - pointer: 7, - value: 12, + pointer: 13, + value: 18, ), Emit(( - start: 13, - end: 14, + start: 20, + end: 21, + )), + Emit(( + start: 21, + end: 23, )), Emit(( start: 0, @@ -573,28 +710,24 @@ end: 0, )), Emit(( - start: 15, - end: 17, + start: 24, + end: 26, )), Emit(( - start: 21, - end: 23, + start: 30, + end: 32, )), Store( - pointer: 13, - value: 22, + pointer: 22, + value: 31, ), Emit(( - start: 24, - end: 25, - )), - MeshFunction(SetVertex( - index: 23, - value: 24, + start: 33, + end: 34, )), Emit(( - start: 25, - end: 26, + start: 34, + end: 36, )), Emit(( start: 0, @@ -609,16 +742,20 @@ end: 0, )), Emit(( - start: 30, - end: 31, + start: 40, + end: 41, )), Store( - pointer: 25, - value: 30, + pointer: 35, + value: 40, ), Emit(( - start: 31, - end: 32, + start: 42, + end: 43, + )), + Emit(( + start: 43, + end: 45, )), Emit(( start: 0, @@ -633,28 +770,24 @@ end: 0, )), Emit(( - start: 33, - end: 35, + start: 46, + end: 48, )), Emit(( - start: 39, - end: 41, + start: 52, + end: 54, )), Store( - pointer: 31, - value: 40, + pointer: 44, + value: 53, ), Emit(( - start: 42, - end: 43, - )), - MeshFunction(SetVertex( - index: 41, - value: 42, + start: 55, + end: 56, )), Emit(( - start: 43, - end: 44, + start: 56, + end: 58, )), Emit(( start: 0, @@ -669,16 +802,20 @@ end: 0, )), Emit(( - start: 48, - end: 49, + start: 62, + end: 63, )), Store( - pointer: 43, - value: 48, + pointer: 57, + value: 62, ), Emit(( - start: 49, - end: 50, + start: 64, + end: 65, + )), + Emit(( + start: 65, + end: 67, )), Emit(( start: 0, @@ -693,69 +830,65 @@ end: 0, )), Emit(( - start: 51, - end: 53, + start: 68, + end: 70, )), Emit(( - start: 57, - end: 59, + start: 74, + end: 76, )), Store( - pointer: 49, - value: 58, + pointer: 66, + value: 75, ), Emit(( - start: 60, - end: 61, - )), - MeshFunction(SetVertex( - index: 59, - value: 60, + start: 77, + end: 78, )), Emit(( - start: 62, - end: 63, + start: 78, + end: 80, )), Emit(( - start: 66, - end: 67, + start: 83, + end: 84, )), Store( - pointer: 62, - value: 66, + pointer: 79, + value: 83, ), Emit(( - start: 67, - end: 68, + start: 85, + end: 86, + )), + Emit(( + start: 86, + end: 88, )), Emit(( - start: 69, - end: 72, + start: 89, + end: 92, )), Store( - pointer: 67, - value: 71, + pointer: 87, + value: 91, ), Emit(( - start: 72, - end: 73, + start: 93, + end: 94, )), Emit(( - start: 77, - end: 78, + start: 94, + end: 96, )), - Store( - pointer: 72, - value: 77, - ), Emit(( - start: 79, - end: 80, - )), - MeshFunction(SetPrimitive( - index: 78, - value: 79, + start: 100, + end: 101, )), + Store( + pointer: 95, + value: 100, + ), Return( value: None, ), @@ -770,6 +903,7 @@ max_primitives_override: None, vertex_output_type: 4, primitive_output_type: 7, + output_variable: 2, )), task_payload: Some(0), ), diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron index 38c79cba451..1147b017f5c 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -141,6 +141,54 @@ span: 16, ), ), + ( + name: None, + inner: Array( + base: 4, + size: Constant(3), + stride: 32, + ), + ), + ( + name: None, + inner: Array( + base: 7, + size: Constant(1), + stride: 32, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 9, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 10, + binding: Some(BuiltIn(Primitives)), + offset: 96, + ), + ( + name: Some("vertex_count"), + ty: 5, + binding: Some(BuiltIn(VertexCount)), + offset: 128, + ), + ( + name: Some("primitive_count"), + ty: 5, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 132, + ), + ], + span: 144, + ), + ), ], special_types: ( ray_desc: None, @@ -167,6 +215,13 @@ ty: 0, init: None, ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 11, + init: None, + ), ], global_expressions: [], functions: [], @@ -292,28 +347,35 @@ ), ], result: None, - local_variables: [ - ( - name: Some("v"), - ty: 4, - init: None, - ), - ( - name: Some("p"), - ty: 7, - init: None, - ), - ], + local_variables: [], expressions: [ FunctionArgument(0), FunctionArgument(1), + GlobalVariable(2), + AccessIndex( + base: 2, + index: 2, + ), Literal(U32(3)), + GlobalVariable(2), + AccessIndex( + base: 5, + index: 3, + ), Literal(U32(1)), GlobalVariable(1), Literal(F32(2.0)), - LocalVariable(0), + GlobalVariable(2), + AccessIndex( + base: 10, + index: 0, + ), + AccessIndex( + base: 11, + index: 0, + ), AccessIndex( - base: 6, + base: 12, index: 0, ), Literal(F32(0.0)), @@ -323,23 +385,32 @@ Compose( ty: 1, components: [ - 8, - 9, - 10, - 11, + 14, + 15, + 16, + 17, ], ), + GlobalVariable(2), + AccessIndex( + base: 19, + index: 0, + ), + AccessIndex( + base: 20, + index: 0, + ), AccessIndex( - base: 6, + base: 21, index: 1, ), GlobalVariable(0), AccessIndex( - base: 14, + base: 23, index: 0, ), Load( - pointer: 15, + pointer: 24, ), Literal(F32(0.0)), Literal(F32(1.0)), @@ -348,23 +419,28 @@ Compose( ty: 1, components: [ - 17, - 18, - 19, - 20, + 26, + 27, + 28, + 29, ], ), Binary( op: Multiply, - left: 21, - right: 16, + left: 30, + right: 25, ), - Literal(U32(0)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 32, + index: 0, + ), + AccessIndex( + base: 33, + index: 1, ), AccessIndex( - base: 6, + base: 34, index: 0, ), Literal(F32(-1.0)), @@ -374,23 +450,32 @@ Compose( ty: 1, components: [ - 26, - 27, - 28, - 29, + 36, + 37, + 38, + 39, ], ), + GlobalVariable(2), AccessIndex( - base: 6, + base: 41, + index: 0, + ), + AccessIndex( + base: 42, + index: 1, + ), + AccessIndex( + base: 43, index: 1, ), GlobalVariable(0), AccessIndex( - base: 32, + base: 45, index: 0, ), Load( - pointer: 33, + pointer: 46, ), Literal(F32(0.0)), Literal(F32(0.0)), @@ -399,23 +484,28 @@ Compose( ty: 1, components: [ - 35, - 36, - 37, - 38, + 48, + 49, + 50, + 51, ], ), Binary( op: Multiply, - left: 39, - right: 34, + left: 52, + right: 47, ), - Literal(U32(1)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 54, + index: 0, + ), + AccessIndex( + base: 55, + index: 2, ), AccessIndex( - base: 6, + base: 56, index: 0, ), Literal(F32(1.0)), @@ -425,23 +515,32 @@ Compose( ty: 1, components: [ - 44, - 45, - 46, - 47, + 58, + 59, + 60, + 61, ], ), + GlobalVariable(2), + AccessIndex( + base: 63, + index: 0, + ), AccessIndex( - base: 6, + base: 64, + index: 2, + ), + AccessIndex( + base: 65, index: 1, ), GlobalVariable(0), AccessIndex( - base: 50, + base: 67, index: 0, ), Load( - pointer: 51, + pointer: 68, ), Literal(F32(1.0)), Literal(F32(0.0)), @@ -450,24 +549,28 @@ Compose( ty: 1, components: [ - 53, - 54, - 55, - 56, + 70, + 71, + 72, + 73, ], ), Binary( op: Multiply, - left: 57, - right: 52, + left: 74, + right: 69, ), - Literal(U32(2)), - Load( - pointer: 6, + GlobalVariable(2), + AccessIndex( + base: 76, + index: 1, + ), + AccessIndex( + base: 77, + index: 0, ), - LocalVariable(1), AccessIndex( - base: 61, + base: 78, index: 0, ), Literal(U32(0)), @@ -476,29 +579,47 @@ Compose( ty: 6, components: [ - 63, - 64, - 65, + 80, + 81, + 82, ], ), + GlobalVariable(2), AccessIndex( - base: 61, + base: 84, + index: 1, + ), + AccessIndex( + base: 85, + index: 0, + ), + AccessIndex( + base: 86, index: 1, ), GlobalVariable(0), AccessIndex( - base: 68, + base: 88, index: 1, ), Load( - pointer: 69, + pointer: 89, ), Unary( op: LogicalNot, - expr: 70, + expr: 90, + ), + GlobalVariable(2), + AccessIndex( + base: 92, + index: 1, + ), + AccessIndex( + base: 93, + index: 0, ), AccessIndex( - base: 61, + base: 94, index: 2, ), Literal(F32(1.0)), @@ -508,33 +629,45 @@ Compose( ty: 1, components: [ - 73, - 74, - 75, - 76, + 96, + 97, + 98, + 99, ], ), - Literal(U32(0)), - Load( - pointer: 61, - ), ], named_expressions: { 0: "index", 1: "id", }, body: [ - MeshFunction(SetMeshOutputs( - vertex_count: 2, - primitive_count: 3, + Emit(( + start: 3, + end: 4, )), Store( - pointer: 4, - value: 5, + pointer: 3, + value: 4, + ), + Emit(( + start: 6, + end: 7, + )), + Store( + pointer: 6, + value: 7, + ), + Store( + pointer: 8, + value: 9, ), Emit(( - start: 7, - end: 8, + start: 11, + end: 12, + )), + Emit(( + start: 12, + end: 14, )), Emit(( start: 0, @@ -549,16 +682,20 @@ end: 0, )), Emit(( - start: 12, - end: 13, + start: 18, + end: 19, )), Store( - pointer: 7, - value: 12, + pointer: 13, + value: 18, ), Emit(( - start: 13, - end: 14, + start: 20, + end: 21, + )), + Emit(( + start: 21, + end: 23, )), Emit(( start: 0, @@ -573,28 +710,24 @@ end: 0, )), Emit(( - start: 15, - end: 17, + start: 24, + end: 26, )), Emit(( - start: 21, - end: 23, + start: 30, + end: 32, )), Store( - pointer: 13, - value: 22, + pointer: 22, + value: 31, ), Emit(( - start: 24, - end: 25, - )), - MeshFunction(SetVertex( - index: 23, - value: 24, + start: 33, + end: 34, )), Emit(( - start: 25, - end: 26, + start: 34, + end: 36, )), Emit(( start: 0, @@ -609,16 +742,20 @@ end: 0, )), Emit(( - start: 30, - end: 31, + start: 40, + end: 41, )), Store( - pointer: 25, - value: 30, + pointer: 35, + value: 40, ), Emit(( - start: 31, - end: 32, + start: 42, + end: 43, + )), + Emit(( + start: 43, + end: 45, )), Emit(( start: 0, @@ -633,28 +770,24 @@ end: 0, )), Emit(( - start: 33, - end: 35, + start: 46, + end: 48, )), Emit(( - start: 39, - end: 41, + start: 52, + end: 54, )), Store( - pointer: 31, - value: 40, + pointer: 44, + value: 53, ), Emit(( - start: 42, - end: 43, - )), - MeshFunction(SetVertex( - index: 41, - value: 42, + start: 55, + end: 56, )), Emit(( - start: 43, - end: 44, + start: 56, + end: 58, )), Emit(( start: 0, @@ -669,16 +802,20 @@ end: 0, )), Emit(( - start: 48, - end: 49, + start: 62, + end: 63, )), Store( - pointer: 43, - value: 48, + pointer: 57, + value: 62, ), Emit(( - start: 49, - end: 50, + start: 64, + end: 65, + )), + Emit(( + start: 65, + end: 67, )), Emit(( start: 0, @@ -693,69 +830,65 @@ end: 0, )), Emit(( - start: 51, - end: 53, + start: 68, + end: 70, )), Emit(( - start: 57, - end: 59, + start: 74, + end: 76, )), Store( - pointer: 49, - value: 58, + pointer: 66, + value: 75, ), Emit(( - start: 60, - end: 61, - )), - MeshFunction(SetVertex( - index: 59, - value: 60, + start: 77, + end: 78, )), Emit(( - start: 62, - end: 63, + start: 78, + end: 80, )), Emit(( - start: 66, - end: 67, + start: 83, + end: 84, )), Store( - pointer: 62, - value: 66, + pointer: 79, + value: 83, ), Emit(( - start: 67, - end: 68, + start: 85, + end: 86, + )), + Emit(( + start: 86, + end: 88, )), Emit(( - start: 69, - end: 72, + start: 89, + end: 92, )), Store( - pointer: 67, - value: 71, + pointer: 87, + value: 91, ), Emit(( - start: 72, - end: 73, + start: 93, + end: 94, )), Emit(( - start: 77, - end: 78, + start: 94, + end: 96, )), - Store( - pointer: 72, - value: 77, - ), Emit(( - start: 79, - end: 80, - )), - MeshFunction(SetPrimitive( - index: 78, - value: 79, + start: 100, + end: 101, )), + Store( + pointer: 95, + value: 100, + ), Return( value: None, ), @@ -770,6 +903,7 @@ max_primitives_override: None, vertex_output_type: 4, primitive_output_type: 7, + output_variable: 2, )), task_payload: Some(0), ), From 3905ae8201ce20ba32b8d6b98297acf470aecd6b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 30 Oct 2025 19:42:50 -0500 Subject: [PATCH 046/110] Improved validation slightly, remvoed obselete crap, fixed bug in compaction, made clippy happy --- naga/src/compact/mod.rs | 4 + naga/src/proc/mod.rs | 16 ++-- naga/src/valid/analyzer.rs | 93 ------------------- naga/src/valid/handles.rs | 1 + naga/src/valid/interface.rs | 19 +--- naga/tests/out/analysis/spv-shadow.info.ron | 12 --- naga/tests/out/analysis/wgsl-access.info.ron | 76 --------------- naga/tests/out/analysis/wgsl-collatz.info.ron | 8 -- .../out/analysis/wgsl-mesh-shader.info.ron | 12 --- .../out/analysis/wgsl-overrides.info.ron | 4 - .../analysis/wgsl-storage-textures.info.ron | 8 -- 11 files changed, 17 insertions(+), 236 deletions(-) diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs index a7d3d463f11..2761c7cfaf8 100644 --- a/naga/src/compact/mod.rs +++ b/naga/src/compact/mod.rs @@ -226,6 +226,9 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { module_tracer.global_variables_used.insert(task_payload); } if let Some(ref mesh_info) = entry.mesh_info { + module_tracer + .global_variables_used + .insert(mesh_info.output_variable); module_tracer .types_used .insert(mesh_info.vertex_output_type); @@ -385,6 +388,7 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { module_map.globals.adjust(task_payload); } if let Some(ref mut mesh_info) = entry.mesh_info { + module_map.globals.adjust(&mut mesh_info.output_variable); module_map.types.adjust(&mut mesh_info.vertex_output_type); module_map .types diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index dd2ae459373..4271db391c5 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -683,14 +683,14 @@ impl crate::Module { output_variable: gv, }; let mut error = None; - let typ = &self.types[self.global_variables[gv].ty].inner; + let r#type = &self.types[self.global_variables[gv].ty].inner; let mut topology = output.topology; // Max, max override, type let mut vertex_info = (0, None, null_type); let mut primitive_info = (0, None, null_type); - match typ { + match r#type { &crate::TypeInner::Struct { ref members, .. } => { let mut builtins = crate::FastHashSet::default(); for member in members { @@ -700,7 +700,7 @@ impl crate::Module { error = Some(EntryPointError::BadMeshOutputVariableField); } if builtins.contains(&crate::BuiltIn::VertexCount) { - error = Some(EntryPointError::BadMeshOutputVarableType); + error = Some(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::VertexCount); } @@ -709,7 +709,7 @@ impl crate::Module { error = Some(EntryPointError::BadMeshOutputVariableField); } if builtins.contains(&crate::BuiltIn::PrimitiveCount) { - error = Some(EntryPointError::BadMeshOutputVarableType); + error = Some(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::PrimitiveCount); } @@ -767,18 +767,18 @@ impl crate::Module { _ => (), } if builtins.contains(&crate::BuiltIn::Primitives) { - error = Some(EntryPointError::BadMeshOutputVarableType); + error = Some(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Primitives); } else { vertex_info = (a, b, c); if builtins.contains(&crate::BuiltIn::Vertices) { - error = Some(EntryPointError::BadMeshOutputVarableType); + error = Some(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Vertices); } } - _ => error = Some(EntryPointError::BadMeshOutputVarableType), + _ => error = Some(EntryPointError::BadMeshOutputVariableType), } } output = crate::MeshStageInfo { @@ -792,7 +792,7 @@ impl crate::Module { ..output } } - _ => error = Some(EntryPointError::BadMeshOutputVarableType), + _ => error = Some(EntryPointError::BadMeshOutputVariableType), } ( output, diff --git a/naga/src/valid/analyzer.rs b/naga/src/valid/analyzer.rs index 5befdfe22a6..e01a7b0b735 100644 --- a/naga/src/valid/analyzer.rs +++ b/naga/src/valid/analyzer.rs @@ -85,25 +85,6 @@ struct FunctionUniformity { exit: ExitFlags, } -/// Mesh shader related characteristics of a function. -#[derive(Debug, Clone, Default)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] -#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))] -#[cfg_attr(test, derive(PartialEq))] -pub struct FunctionMeshShaderInfo { - /// The type of value this function passes to [`SetVertex`], and the - /// expression that first established it. - /// - /// [`SetVertex`]: crate::ir::MeshFunction::SetVertex - pub vertex_type: Option<(Handle, Handle)>, - - /// The type of value this function passes to [`SetPrimitive`], and the - /// expression that first established it. - /// - /// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive - pub primitive_type: Option<(Handle, Handle)>, -} - impl ops::BitOr for FunctionUniformity { type Output = Self; fn bitor(self, other: Self) -> Self { @@ -321,9 +302,6 @@ pub struct FunctionInfo { /// See [`DiagnosticFilterNode`] for details on how the tree is represented and used in /// validation. diagnostic_filter_leaf: Option>, - - /// Mesh shader info for this function and its callees. - pub mesh_shader_info: FunctionMeshShaderInfo, } impl FunctionInfo { @@ -520,9 +498,6 @@ impl FunctionInfo { *mine |= *other; } - // Inherit mesh output types from our callees. - self.try_update_mesh_info(&callee.mesh_shader_info)?; - Ok(FunctionUniformity { result: callee.uniformity.clone(), exit: if callee.may_kill { @@ -1200,72 +1175,6 @@ impl FunctionInfo { } Ok(combined_uniformity) } - - /// Note the type of value passed to [`SetVertex`]. - /// - /// Record that this function passed a value of type `ty` as the second - /// argument to the [`SetVertex`] builtin function. All calls to - /// `SetVertex` must pass the same type, and this must match the - /// function's [`vertex_output_type`]. - /// - /// [`SetVertex`]: crate::ir::MeshFunction::SetVertex - /// [`vertex_output_type`]: crate::ir::MeshStageInfo::vertex_output_type - fn try_update_mesh_vertex_type( - &mut self, - ty: Handle, - value: Handle, - ) -> Result<(), WithSpan> { - if let &Some(ref existing) = &self.mesh_shader_info.vertex_type { - if existing.0 != ty { - return Err( - FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span() - ); - } - } else { - self.mesh_shader_info.vertex_type = Some((ty, value)); - } - Ok(()) - } - - /// Note the type of value passed to [`SetPrimitive`]. - /// - /// Record that this function passed a value of type `ty` as the second - /// argument to the [`SetPrimitive`] builtin function. All calls to - /// `SetPrimitive` must pass the same type, and this must match the - /// function's [`primitive_output_type`]. - /// - /// [`SetPrimitive`]: crate::ir::MeshFunction::SetPrimitive - /// [`primitive_output_type`]: crate::ir::MeshStageInfo::primitive_output_type - fn try_update_mesh_primitive_type( - &mut self, - ty: Handle, - value: Handle, - ) -> Result<(), WithSpan> { - if let &Some(ref existing) = &self.mesh_shader_info.primitive_type { - if existing.0 != ty { - return Err( - FunctionError::ConflictingMeshOutputTypes(existing.1, value).with_span() - ); - } - } else { - self.mesh_shader_info.primitive_type = Some((ty, value)); - } - Ok(()) - } - - /// Update this function's mesh shader info, given that it calls `callee`. - fn try_update_mesh_info( - &mut self, - callee: &FunctionMeshShaderInfo, - ) -> Result<(), WithSpan> { - if let &Some(ref other_vertex) = &callee.vertex_type { - self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?; - } - if let &Some(ref other_primitive) = &callee.primitive_type { - self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?; - } - Ok(()) - } } impl ModuleInfo { @@ -1301,7 +1210,6 @@ impl ModuleInfo { sampling: crate::FastHashSet::default(), dual_source_blending: false, diagnostic_filter_leaf: fun.diagnostic_filter_leaf, - mesh_shader_info: FunctionMeshShaderInfo::default(), }; let resolve_context = ResolveContext::with_locals(module, &fun.local_variables, &fun.arguments); @@ -1435,7 +1343,6 @@ fn uniform_control_flow() { sampling: crate::FastHashSet::default(), dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: FunctionMeshShaderInfo::default(), }; let resolve_context = ResolveContext { constants: &Arena::new(), diff --git a/naga/src/valid/handles.rs b/naga/src/valid/handles.rs index 7fe6fa8803d..5b7fb3fab75 100644 --- a/naga/src/valid/handles.rs +++ b/naga/src/valid/handles.rs @@ -237,6 +237,7 @@ impl super::Validator { Self::validate_global_variable_handle(task_payload, global_variables)?; } if let Some(ref mesh_info) = entry_point.mesh_info { + Self::validate_global_variable_handle(mesh_info.output_variable, global_variables)?; validate_type(mesh_info.vertex_output_type)?; validate_type(mesh_info.primitive_output_type)?; for ov in mesh_info diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index e5e7b6997b1..4d437477ca1 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -153,7 +153,7 @@ pub enum EntryPointError { #[error( "Mesh shader output variable must be a struct with fields that are all allowed builtins" )] - BadMeshOutputVarableType, + BadMeshOutputVariableType, #[error("Mesh shader output variable fields must have types that are in accordance with the mesh shader spec")] BadMeshOutputVariableField, #[error("Mesh shader entry point cannot have a return type")] @@ -1085,12 +1085,9 @@ impl super::Validator { } } - // TODO: validate mesh entry point info - // If this is a `Mesh` entry point, check its vertex and primitive output types. // We verified previously that only mesh shaders can have `mesh_info`. if let &Some(ref mesh_info) = &ep.mesh_info { - // TODO: validate global variable if module.global_variables[mesh_info.output_variable].space != crate::AddressSpace::WorkGroup { @@ -1105,14 +1102,14 @@ impl super::Validator { if let Some(e) = mesh_info.max_vertices_override { if let crate::Expression::Override(o) = module.global_expressions[e] { if implied.1[0] != Some(o) { - return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + return Err(EntryPointError::BadMeshOutputVariableType.with_span()); } } } if let Some(e) = mesh_info.max_primitives_override { if let crate::Expression::Override(o) = module.global_expressions[e] { if implied.1[1] != Some(o) { - return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + return Err(EntryPointError::BadMeshOutputVariableType.with_span()); } } } @@ -1120,7 +1117,7 @@ impl super::Validator { implied.0.max_vertices_override = mesh_info.max_vertices_override; implied.0.max_primitives_override = mesh_info.max_primitives_override; if implied.0 != *mesh_info { - return Err(EntryPointError::BadMeshOutputVarableType.with_span()); + return Err(EntryPointError::BadMeshOutputVariableType.with_span()); } self.validate_mesh_output_type( @@ -1135,14 +1132,6 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; - } else { - // This is not a `Mesh` entry point, so ensure that it never tries to produce - // vertices or primitives. - if info.mesh_shader_info.vertex_type.is_some() - || info.mesh_shader_info.primitive_type.is_some() - { - return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span()); - } } Ok(info) diff --git a/naga/tests/out/analysis/spv-shadow.info.ron b/naga/tests/out/analysis/spv-shadow.info.ron index b08a28438ed..381f841d5d9 100644 --- a/naga/tests/out/analysis/spv-shadow.info.ron +++ b/naga/tests/out/analysis/spv-shadow.info.ron @@ -413,10 +413,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -1595,10 +1591,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], entry_points: [ @@ -1693,10 +1685,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-access.info.ron b/naga/tests/out/analysis/wgsl-access.info.ron index d297b09a404..c22cd768f2e 100644 --- a/naga/tests/out/analysis/wgsl-access.info.ron +++ b/naga/tests/out/analysis/wgsl-access.info.ron @@ -1197,10 +1197,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2527,10 +2523,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2571,10 +2563,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2624,10 +2612,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2671,10 +2655,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2769,10 +2749,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2894,10 +2870,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -2950,10 +2922,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3009,10 +2977,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3065,10 +3029,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3124,10 +3084,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3192,10 +3148,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3269,10 +3221,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3349,10 +3297,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3453,10 +3397,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -3653,10 +3593,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], entry_points: [ @@ -4354,10 +4290,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4810,10 +4742,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -4884,10 +4812,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-collatz.info.ron b/naga/tests/out/analysis/wgsl-collatz.info.ron index 2796f544510..219e016f8d7 100644 --- a/naga/tests/out/analysis/wgsl-collatz.info.ron +++ b/naga/tests/out/analysis/wgsl-collatz.info.ron @@ -275,10 +275,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], entry_points: [ @@ -434,10 +430,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [], diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 9ba7187ac69..9422d07107d 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -220,10 +220,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -1402,10 +1398,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -1471,10 +1463,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [], diff --git a/naga/tests/out/analysis/wgsl-overrides.info.ron b/naga/tests/out/analysis/wgsl-overrides.info.ron index a76c9c89c9b..92e99112e53 100644 --- a/naga/tests/out/analysis/wgsl-overrides.info.ron +++ b/naga/tests/out/analysis/wgsl-overrides.info.ron @@ -201,10 +201,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [ diff --git a/naga/tests/out/analysis/wgsl-storage-textures.info.ron b/naga/tests/out/analysis/wgsl-storage-textures.info.ron index 35b5a7e320c..8bb298a6450 100644 --- a/naga/tests/out/analysis/wgsl-storage-textures.info.ron +++ b/naga/tests/out/analysis/wgsl-storage-textures.info.ron @@ -184,10 +184,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), @@ -400,10 +396,6 @@ sampling: [], dual_source_blending: false, diagnostic_filter_leaf: None, - mesh_shader_info: ( - vertex_type: None, - primitive_type: None, - ), ), ], const_expression_types: [], From f557cadfe662a505c6e9f197aff4ac89baf9cb49 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 31 Oct 2025 16:41:29 -0500 Subject: [PATCH 047/110] Some initial changes to adapt to new system --- naga/src/back/spv/block.rs | 435 ++++++++++- naga/src/back/spv/helpers.rs | 14 +- naga/src/back/spv/mod.rs | 41 + naga/src/back/spv/writer.rs | 723 ++++++++++++++++-- naga/tests/in/wgsl/mesh-shader.toml | 2 +- naga/tests/out/spv/wgsl-access.spvasm | 2 +- naga/tests/out/spv/wgsl-boids.spvasm | 2 +- ...l-bounds-check-image-restrict-depth.spvasm | 2 +- .../wgsl-bounds-check-image-restrict.spvasm | 2 +- .../wgsl-bounds-check-image-rzsw-depth.spvasm | 2 +- .../spv/wgsl-bounds-check-image-rzsw.spvasm | 2 +- naga/tests/out/spv/wgsl-collatz.spvasm | 2 +- .../spv/wgsl-debug-symbol-large-source.spvasm | 2 +- .../out/spv/wgsl-debug-symbol-simple.spvasm | 2 +- .../out/spv/wgsl-debug-symbol-terrain.spvasm | 2 +- naga/tests/out/spv/wgsl-f16-native.spvasm | 2 +- naga/tests/out/spv/wgsl-f16-polyfill.spvasm | 2 +- naga/tests/out/spv/wgsl-image.spvasm | 2 +- .../out/spv/wgsl-interface.fragment.spvasm | 2 +- .../out/spv/wgsl-interface.vertex.spvasm | 2 +- .../wgsl-interface.vertex_two_structs.spvasm | 2 +- naga/tests/out/spv/wgsl-interpolate.spvasm | 2 +- .../out/spv/wgsl-interpolate_compat.spvasm | 2 +- naga/tests/out/spv/wgsl-mesh-shader.spvasm | 299 ++++++++ naga/tests/out/spv/wgsl-padding.spvasm | 2 +- naga/tests/out/spv/wgsl-pointers.spvasm | 2 +- naga/tests/out/spv/wgsl-policy-mix.spvasm | 2 +- naga/tests/out/spv/wgsl-quad.spvasm | 2 +- naga/tests/out/spv/wgsl-shadow.spvasm | 2 +- naga/tests/out/spv/wgsl-texture-arg.spvasm | 2 +- .../out/spv/wgsl-workgroup-var-init.spvasm | 2 +- 31 files changed, 1472 insertions(+), 92 deletions(-) create mode 100644 naga/tests/out/spv/wgsl-mesh-shader.spvasm diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index dd9a3811687..06a0b2dd7f8 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -221,8 +221,12 @@ impl Writer { ir_result: &crate::FunctionResult, result_members: &[ResultMember], body: &mut Vec, - ) -> Result<(), Error> { + task_payload: Option, + ) -> Result { for (index, res_member) in result_members.iter().enumerate() { + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + continue; + } let member_value_id = match ir_result.binding { Some(_) => value_id, None => { @@ -253,6 +257,411 @@ impl Writer { _ => {} } } + // OpEmitMeshTasksEXT must be called right before exiting (after setting other + // output variables if there are any) + for (index, res_member) in result_members.iter().enumerate() { + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + let member_value_id = match ir_result.binding { + Some(_) => value_id, + None => { + let member_value_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + res_member.type_id, + member_value_id, + value_id, + &[index as Word], + )); + member_value_id + } + }; + + let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; + for (i, &value) in values.iter().enumerate() { + let instruction = Instruction::composite_extract( + self.get_u32_type_id(), + value, + member_value_id, + &[i as Word], + ); + body.push(instruction); + } + let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); + for id in values { + instruction.add_operand(id); + } + if let Some(task_payload) = task_payload { + instruction.add_operand(task_payload); + } + return Ok(instruction); + } + } + Ok(Instruction::return_void()) + } + + /// Writes the return call for a mesh shader, which involves copying previously + /// written vertices/primitives into the actual output location. + fn write_mesh_shader_return( + &mut self, + return_info: &super::MeshReturnInfo, + body: &mut Vec, + ) -> Result<(), Error> { + let output_value_id = self.id_gen.next(); + body.push(Instruction::load( + return_info.out_type_id, + output_value_id, + return_info.out_variable_id, + None, + )); + // Load the actual vertex and primitive counts + let vert_count_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_u32_type_id(), + vert_count_id, + output_value_id, + &[return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) + .unwrap() as u32], + )); + let prim_count_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_u32_type_id(), + prim_count_id, + output_value_id, + &[return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) + .unwrap() as u32], + )); + let vert_array_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.vertex_array_type_id, + spirv::StorageClass::Workgroup, + ), + vert_array_ptr, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() as u32, + ))], + )); + let prim_array_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.primitive_array_type_id, + spirv::StorageClass::Workgroup, + ), + prim_array_ptr, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() as u32, + ))], + )); + + // Call this. It must be called exactly once, which the user shouldn't be assumed + // to have done correctly. + { + let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); + ins.add_operand(vert_count_id); + ins.add_operand(prim_count_id); + body.push(ins); + } + + // All this for a `for i in 0..num_vertices` lol + // This is basically just a memcpy but the result is split up to multiple places + let u32_type_id = self.get_u32_type_id(); + let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); + let vertex_loop_header = self.id_gen.next(); + let prim_loop_header = self.id_gen.next(); + let in_between_loops = self.id_gen.next(); + let func_end = self.id_gen.next(); + let index_var = return_info.function_variable; + + body.push(Instruction::store( + index_var, + return_info.local_invocation_index_id, + None, + )); + body.push(Instruction::branch(vertex_loop_header)); + + // Vertex copies + let vertex_copy_body = { + let mut body = Vec::new(); + // Current index to copy + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let vert_to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.vertex_type_id, + spirv::StorageClass::Workgroup, + ), + vert_to_copy_ptr, + vert_array_ptr, + &[val_i], + )); + + // Load the entire vertex value + let vert_to_copy = self.id_gen.next(); + body.push(Instruction::load( + return_info.vertex_type_id, + vert_to_copy, + vert_to_copy_ptr, + None, + )); + + let mut builtin_index = 0; + let mut binding_index = 0; + // Write individual members of the vertex + for (member_id, member) in return_info.vertex_members.iter().enumerate() { + let val_to_copy = self.id_gen.next(); + let mut needs_y_flip = false; + body.push(Instruction::composite_extract( + member.ty_id, + val_to_copy, + vert_to_copy, + &[member_id as u32], + )); + let ptr_to_copy_to = self.id_gen.next(); + // Get the variable that holds it and indexed pointer, which points to + // the value and not a wrapper struct + match member.binding { + crate::Binding::BuiltIn(bi) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.vertex_builtin_block.as_ref().unwrap().var_id, + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) + && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.vertex_bindings[binding_index].var_id, + &[val_i, zero_u32], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + // Can't use epilogue flip because can't read from this storage class I believe + if needs_y_flip { + let prev_y = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_f32_type_id(), + prev_y, + val_to_copy, + &[1], + )); + let new_y = self.id_gen.next(); + body.push(Instruction::unary( + spirv::Op::FNegate, + self.get_f32_type_id(), + new_y, + prev_y, + )); + let new_ptr_to_copy_to = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_f32_pointer_type_id(spirv::StorageClass::Output), + new_ptr_to_copy_to, + ptr_to_copy_to, + &[self.get_constant_scalar(crate::Literal::U32(1))], + )); + body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); + } + } + body + }; + + // Primitive copies + let primitive_copy_body = { + // See comments in `vertex_copy_body` + let mut body = Vec::new(); + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let prim_to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.primitive_type_id, + spirv::StorageClass::Workgroup, + ), + prim_to_copy_ptr, + prim_array_ptr, + &[val_i], + )); + let prim_to_copy = self.id_gen.next(); + body.push(Instruction::load( + return_info.primitive_type_id, + prim_to_copy, + prim_to_copy_ptr, + None, + )); + + let mut builtin_index = 0; + let mut binding_index = 0; + for (member_id, member) in return_info.primitive_members.iter().enumerate() { + let val_to_copy = self.id_gen.next(); + body.push(Instruction::composite_extract( + member.ty_id, + val_to_copy, + prim_to_copy, + &[member_id as u32], + )); + let ptr_to_copy_to = self.id_gen.next(); + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_indices.as_ref().unwrap().var_id, + &[val_i], + )); + } + crate::Binding::BuiltIn(_) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_builtin_block.as_ref().unwrap().var_id, + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_bindings[binding_index].var_id, + &[val_i, zero_u32], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + } + body + }; + + // This writes the actual loop + let mut get_loop_continue_id = |body: &mut Vec, + mut loop_body_block, + loop_header, + loop_merge, + count_id, + index_var| { + let condition_check = self.id_gen.next(); + let loop_continue = self.id_gen.next(); + let loop_body = self.id_gen.next(); + + // Loop header + { + body.push(Instruction::label(loop_header)); + body.push(Instruction::loop_merge( + loop_merge, + loop_continue, + spirv::SelectionControl::empty(), + )); + body.push(Instruction::branch(condition_check)); + } + // Condition check - check if i is less than num vertices to copy + { + body.push(Instruction::label(condition_check)); + + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let cond = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::ULessThan, + self.get_bool_type_id(), + cond, + val_i, + count_id, + )); + body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); + } + // Loop body + { + body.push(Instruction::label(loop_body)); + body.append(&mut loop_body_block); + body.push(Instruction::branch(loop_continue)); + } + // Loop continue - increment i + { + body.push(Instruction::label(loop_continue)); + + let prev_val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, prev_val_i, index_var, None)); + let new_val_i = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::IAdd, + u32_type_id, + new_val_i, + prev_val_i, + return_info.workgroup_size, + )); + body.push(Instruction::store(index_var, new_val_i, None)); + + body.push(Instruction::branch(loop_header)); + } + }; + // Write vertex copy loop + get_loop_continue_id( + body, + vertex_copy_body, + vertex_loop_header, + in_between_loops, + vert_count_id, + index_var, + ); + // In between loops, reset the initial index + { + body.push(Instruction::label(in_between_loops)); + + body.push(Instruction::store( + index_var, + return_info.local_invocation_index_id, + None, + )); + + body.push(Instruction::branch(prim_loop_header)); + } + // Write primitive copy loop + get_loop_continue_id( + body, + primitive_copy_body, + prim_loop_header, + func_end, + prim_count_id, + index_var, + ); + + body.push(Instruction::label(func_end)); Ok(()) } } @@ -3227,21 +3636,27 @@ impl BlockContext<'_> { let instruction = match self.function.entry_point_context { // If this is an entry point, and we need to return anything, // let's instead store the output variables and return `void`. - Some(ref context) => { - self.writer.write_entry_point_return( - value_id, - self.ir_function.result.as_ref().unwrap(), - &context.results, - &mut block.body, - )?; - Instruction::return_void() - } + Some(ref context) => self.writer.write_entry_point_return( + value_id, + self.ir_function.result.as_ref().unwrap(), + &context.results, + &mut block.body, + context.task_payload, + )?, None => Instruction::return_value(value_id), }; self.function.consume(block, instruction); return Ok(BlockExitDisposition::Discarded); } Statement::Return { value: None } => { + if let Some(super::EntryPointContext { + mesh_state: Some(ref mesh_state), + .. + }) = self.function.entry_point_context + { + self.writer + .write_mesh_shader_return(mesh_state, &mut block.body)?; + }; self.function.consume(block, Instruction::return_void()); return Ok(BlockExitDisposition::Discarded); } diff --git a/naga/src/back/spv/helpers.rs b/naga/src/back/spv/helpers.rs index 6522a0970d7..c0c8406b3ff 100644 --- a/naga/src/back/spv/helpers.rs +++ b/naga/src/back/spv/helpers.rs @@ -1,5 +1,6 @@ use alloc::{vec, vec::Vec}; +use arrayvec::ArrayVec; use spirv::Word; use crate::{Handle, UniqueArena}; @@ -54,7 +55,7 @@ pub(super) const fn map_storage_class(space: crate::AddressSpace) -> spirv::Stor crate::AddressSpace::Uniform => spirv::StorageClass::Uniform, crate::AddressSpace::WorkGroup => spirv::StorageClass::Workgroup, crate::AddressSpace::PushConstant => spirv::StorageClass::PushConstant, - crate::AddressSpace::TaskPayload => unreachable!(), + crate::AddressSpace::TaskPayload => spirv::StorageClass::TaskPayloadWorkgroupEXT, } } @@ -153,3 +154,14 @@ impl StrUnstable for str { } } } + +pub enum BindingDecorations { + BuiltIn(spirv::BuiltIn, ArrayVec), + Location { + location: Word, + others: ArrayVec, + /// If this is `Some`, use Decoration::Index with blend_src as an operand + blend_src: Option, + }, + None, +} diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index 4690dc71951..4bd3150addd 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -52,6 +52,7 @@ struct LogicalLayout { function_definitions: Vec, } +#[derive(Clone)] struct Instruction { op: spirv::Op, wc: u32, @@ -78,6 +79,8 @@ pub enum Error { Override, #[error(transparent)] ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError), + #[error("module requires SPIRV-{0}.{1}, which isn't supported")] + SpirvVersionTooLow(u8, u8), #[error("mapping of {0:?} is missing")] MissingBinding(crate::ResourceBinding), } @@ -141,9 +144,47 @@ struct ResultMember { built_in: Option, } +struct MeshReturnGlobalVariable { + _inner_ty: u32, + var_id: u32, +} + +#[derive(Clone)] +struct MeshReturnMember { + pub ty_id: u32, + pub binding: crate::Binding, +} +struct MeshReturnInfo { + out_variable_id: u32, + out_type_id: u32, + out_members: Vec, + + vertex_type_id: u32, + vertex_array_type_id: u32, + vertex_members: Vec, + primitive_type_id: u32, + primitive_array_type_id: u32, + primitive_members: Vec, + // In vulkan, all builtins must be in the same block. + // All bindings must be in their own unique block. + // Also, the primitive indices builtin family needs its own block. + // Also also, cull primitive doesn't care about having its own block. + vertex_builtin_block: Option, + vertex_bindings: Vec, + primitive_builtin_block: Option, + primitive_bindings: Vec, + primitive_indices: Option, + local_invocation_index_id: u32, + workgroup_size: u32, + /// The id of a function variable in the entry point for a u32 + function_variable: u32, +} + struct EntryPointContext { argument_ids: Vec, results: Vec, + task_payload: Option, + mesh_state: Option, } #[derive(Default)] diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index ee1ea847739..4bfc7e48108 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1,5 +1,6 @@ use alloc::{string::String, vec, vec::Vec}; +use arrayvec::ArrayVec; use hashbrown::hash_map::Entry; use spirv::Word; @@ -13,7 +14,8 @@ use super::{ }; use crate::{ arena::{Handle, HandleVec, UniqueArena}, - back::spv::{BindingInfo, WrappedFunction}, + back::spv::{helpers::BindingDecorations, BindingInfo, WrappedFunction}, + non_max_u32::NonMaxU32, proc::{Alignment, TypeResolution}, valid::{FunctionInfo, ModuleInfo}, }; @@ -21,6 +23,9 @@ use crate::{ struct FunctionInterface<'a> { varying_ids: &'a mut Vec, stage: crate::ShaderStage, + task_payload: Option>, + mesh_info: Option, + workgroup_size: [u32; 3], } impl Function { @@ -726,6 +731,397 @@ impl Writer { Ok(()) } + fn write_mesh_return_global_variable( + &mut self, + ty: u32, + array_size_id: u32, + ) -> Result { + let array_ty = self.id_gen.next(); + Instruction::type_array(array_ty, ty, array_size_id) + .to_words(&mut self.logical_layout.declarations); + let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); + let var_id = self.id_gen.next(); + Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) + .to_words(&mut self.logical_layout.declarations); + Ok(super::MeshReturnGlobalVariable { + _inner_ty: ty, + var_id, + }) + } + + /// This does various setup things to allow mesh shader entry points + /// to be properly written, such as creating the output variables + fn write_entry_point_mesh_shader_info( + &mut self, + iface: &mut FunctionInterface, + local_invocation_index_id: Option, + ir_module: &crate::Module, + prelude: &mut Block, + ep_context: &mut EntryPointContext, + ) -> Result<(), Error> { + if let Some(ref mesh_info) = iface.mesh_info { + // Collect the members in the output structs + let out_members: Vec = + match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let vertex_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() + .ty_id; + let primitive_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() + .ty_id; + let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + // In the final return, we do a giant memcpy, for which this is helpful + let local_invocation_index_id = match local_invocation_index_id { + Some(a) => a, + None => { + let u32_id = self.get_u32_type_id(); + let var = self.id_gen.next(); + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), + var, + spirv::StorageClass::Input, + None, + ) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate( + var, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationIndex as u32], + ) + .to_words(&mut self.logical_layout.annotations); + + let loaded_value = self.id_gen.next(); + prelude + .body + .push(Instruction::load(u32_id, loaded_value, var, None)); + loaded_value + } + }; + let u32_id = self.get_u32_type_id(); + // A general function variable that we guarantee to allow in the final return. It must be + // declared at the top of the function. Currently it is used in the memcpy part to keep + // index to copy track of the current + let function_variable = self.id_gen.next(); + prelude.body.insert( + 0, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), + function_variable, + spirv::StorageClass::Function, + None, + ), + ); + // This is the information that is passed to the function writer + // so that it can write the final return logic + + let mut mesh_return_info = super::MeshReturnInfo { + out_variable_id: self.global_variables[mesh_info.output_variable].var_id, + out_type_id: self + .get_handle_type_id(ir_module.global_variables[mesh_info.output_variable].ty), + out_members, + + vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), + vertex_array_type_id, + vertex_members, + primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), + primitive_array_type_id, + primitive_members, + vertex_bindings: Vec::new(), + vertex_builtin_block: None, + primitive_bindings: Vec::new(), + primitive_builtin_block: None, + primitive_indices: None, + local_invocation_index_id, + workgroup_size: self.get_constant_scalar(crate::Literal::U32( + iface.workgroup_size.iter().product(), + )), + function_variable, + }; + let vert_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); + let prim_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); + // Create the actual output variables and types. + // According to SPIR-V, + // * All builtins must be in the same output `Block` + // * Each member with `location` must be in its own `Block`. + // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this!) + // * Some builtins like the indices ones need to be in their + // own output variable without a struct wrapper + if mesh_return_info + .vertex_members + .iter() + .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.vertex_members { + if let crate::Binding::BuiltIn(_) = member.binding { + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = self + .write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; + iface.varying_ids.push(v.var_id); + if self.flags.contains(WriterFlags::DEBUG) { + Instruction::name(v.var_id, "naga_vertex_builtin_outputs") + .to_words(&mut self.logical_layout.debugs); + } + mesh_return_info.vertex_builtin_block = Some(v); + } + if mesh_return_info.primitive_members.iter().any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.primitive_members { + if let crate::Binding::BuiltIn(bi) = member.binding { + if matches!( + bi, + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) { + continue; + } + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = self + .write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; + Instruction::decorate(v.var_id, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v.var_id); + if self.flags.contains(WriterFlags::DEBUG) { + Instruction::name(v.var_id, "naga_primitive_builtin_outputs") + .to_words(&mut self.logical_layout.debugs); + } + mesh_return_info.primitive_builtin_block = Some(v); + } + { + for member in &mesh_return_info.vertex_members { + match member.binding { + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = + self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; + iface.varying_ids.push(v.var_id); + mesh_return_info.vertex_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + for member in &mesh_return_info.primitive_members { + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + let v = self.write_mesh_return_global_variable( + member.ty_id, + prim_array_size_id, + )?; + Instruction::decorate( + v.var_id, + spirv::Decoration::PerPrimitiveEXT, + &[], + ) + .to_words(&mut self.logical_layout.annotations); + Instruction::decorate( + v.var_id, + spirv::Decoration::BuiltIn, + &[match member.binding.to_built_in().unwrap() { + crate::BuiltIn::PointIndex => { + spirv::BuiltIn::PrimitivePointIndicesEXT + } + crate::BuiltIn::LineIndices => { + spirv::BuiltIn::PrimitiveLineIndicesEXT + } + crate::BuiltIn::TriangleIndices => { + spirv::BuiltIn::PrimitiveTriangleIndicesEXT + } + _ => unreachable!(), + } as Word], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v.var_id); + if self.flags.contains(WriterFlags::DEBUG) { + Instruction::name(v.var_id, "naga_primitive_indices_outputs") + .to_words(&mut self.logical_layout.debugs); + } + mesh_return_info.primitive_indices = Some(v); + } + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = + self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; + Instruction::decorate( + v.var_id, + spirv::Decoration::PerPrimitiveEXT, + &[], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v.var_id); + mesh_return_info.primitive_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + } + ep_context.mesh_state = Some(mesh_return_info); + } + Ok(()) + } + fn write_function( &mut self, ir_function: &crate::Function, @@ -744,11 +1140,20 @@ impl Writer { let mut ep_context = EntryPointContext { argument_ids: Vec::new(), results: Vec::new(), + task_payload: if let Some(ref i) = interface { + i.task_payload.map(|a| self.global_variables[a].var_id) + } else { + None + }, + mesh_state: None, }; let mut local_invocation_id = None; let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); + + let mut local_invocation_index_id = None; + for argument in ir_function.arguments.iter() { let class = spirv::StorageClass::Input; let handle_ty = ir_module.types[argument.ty].inner.is_handle(); @@ -779,6 +1184,10 @@ impl Writer { if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); } id @@ -806,6 +1215,10 @@ impl Writer { if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); } } prelude.body.push(Instruction::composite_construct( @@ -854,15 +1267,21 @@ impl Writer { has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); let type_id = self.get_handle_type_id(result.ty); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - None, - result.ty, - binding, - )?; - iface.varying_ids.push(varying_id); + let varying_id = + if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + None, + result.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; ep_context.results.push(ResultMember { id: varying_id, type_id, @@ -877,15 +1296,25 @@ impl Writer { let binding = member.binding.as_ref().unwrap(); has_point_size |= *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - member.ty, - binding, - )?; - iface.varying_ids.push(varying_id); + // This isn't an actual builtin in SPIR-V. It can only appear as the + // output of a task shader and the output is used when writing the + // entry point return, in which case the id is ignored anyway. + let varying_id = if *binding + == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) + { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; ep_context.results.push(ResultMember { id: varying_id, type_id, @@ -925,6 +1354,21 @@ impl Writer { None => self.void_type, }; + if let Some(ref mut iface) = interface { + if let Some(task_payload) = iface.task_payload { + iface + .varying_ids + .push(self.global_variables[task_payload].var_id); + } + self.write_entry_point_mesh_shader_info( + iface, + local_invocation_index_id, + ir_module, + &mut prelude, + &mut ep_context, + )?; + } + let lookup_function_type = LookupFunctionType { parameter_type_ids, return_type_id, @@ -961,7 +1405,7 @@ impl Writer { let mut gv = self.global_variables[handle].clone(); if let Some(ref mut iface) = interface { // Have to include global variables in the interface - if self.physical_layout.version >= 0x10400 { + if self.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) { iface.varying_ids.push(gv.var_id); } } @@ -1160,6 +1604,9 @@ impl Writer { Some(FunctionInterface { varying_ids: &mut interface_ids, stage: entry_point.stage, + task_payload: entry_point.task_payload, + mesh_info: entry_point.mesh_info.clone(), + workgroup_size: entry_point.workgroup_size, }), debug_info, )?; @@ -1223,7 +1670,53 @@ impl Writer { .to_words(&mut self.logical_layout.execution_modes); spirv::ExecutionModel::GLCompute } - crate::ShaderStage::Task | crate::ShaderStage::Mesh => unreachable!(), + crate::ShaderStage::Task => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::TaskEXT + } + crate::ShaderStage::Mesh => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + let mesh_info = entry_point.mesh_info.as_ref().unwrap(); + Instruction::execution_mode( + function_id, + match mesh_info.topology { + crate::MeshOutputTopology::Points => spirv::ExecutionMode::OutputPoints, + crate::MeshOutputTopology::Lines => spirv::ExecutionMode::OutputLinesEXT, + crate::MeshOutputTopology::Triangles => { + spirv::ExecutionMode::OutputTrianglesEXT + } + }, + &[], + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputVertices, + core::slice::from_ref(&mesh_info.max_vertices), + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputPrimitivesEXT, + core::slice::from_ref(&mesh_info.max_primitives), + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::MeshEXT + } }; //self.check(exec_model.required_capabilities())?; @@ -1954,8 +2447,6 @@ impl Writer { ty: Handle, binding: &crate::Binding, ) -> Result { - use crate::TypeInner; - let id = self.id_gen.next(); let ty_inner = &ir_module.types[ty].inner; let needs_polyfill = self.needs_f16_polyfill(ty_inner); @@ -1986,17 +2477,111 @@ impl Writer { } } - use spirv::{BuiltIn, Decoration}; + let binding = self.map_binding(ir_module, stage, class, ty, binding)?; + self.write_binding(id, binding); + Ok(id) + } + + pub fn write_binding(&mut self, id: Word, binding: BindingDecorations) { + match binding { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.decorate(id, spirv::Decoration::BuiltIn, &[bi as u32]); + for other in others { + self.decorate(id, other, &[]); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.decorate(id, spirv::Decoration::Location, &[location]); + for other in others { + self.decorate(id, other, &[]); + } + if let Some(blend_src) = blend_src { + self.decorate(id, spirv::Decoration::Index, &[blend_src]); + } + } + } + } + + pub fn write_binding_struct_member( + &mut self, + struct_id: Word, + member_idx: Word, + binding_info: BindingDecorations, + ) { + match binding_info { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Location, + &[location], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + if let Some(blend_src) = blend_src { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Index, + &[blend_src], + )); + } + } + } + } + + pub fn map_binding( + &mut self, + ir_module: &crate::Module, + stage: crate::ShaderStage, + class: spirv::StorageClass, + ty: Handle, + binding: &crate::Binding, + ) -> Result { + use spirv::BuiltIn; + use spirv::Decoration; match *binding { crate::Binding::Location { location, interpolation, sampling, blend_src, - per_primitive: _, + per_primitive, } => { - self.decorate(id, Decoration::Location, &[location]); + let mut others = ArrayVec::new(); let no_decorations = // VUID-StandaloneSpirv-Flat-06202 @@ -2013,10 +2598,10 @@ impl Writer { // Perspective-correct interpolation is the default in SPIR-V. None | Some(crate::Interpolation::Perspective) => (), Some(crate::Interpolation::Flat) => { - self.decorate(id, Decoration::Flat, &[]); + others.push(Decoration::Flat); } Some(crate::Interpolation::Linear) => { - self.decorate(id, Decoration::NoPerspective, &[]); + others.push(Decoration::NoPerspective); } } match sampling { @@ -2028,27 +2613,34 @@ impl Writer { | crate::Sampling::Either, ) => (), Some(crate::Sampling::Centroid) => { - self.decorate(id, Decoration::Centroid, &[]); + others.push(Decoration::Centroid); } Some(crate::Sampling::Sample) => { self.require_any( "per-sample interpolation", &[spirv::Capability::SampleRateShading], )?; - self.decorate(id, Decoration::Sample, &[]); + others.push(Decoration::Sample); } } } - if let Some(blend_src) = blend_src { - self.decorate(id, Decoration::Index, &[blend_src]); + if per_primitive && stage == crate::ShaderStage::Fragment { + others.push(Decoration::PerPrimitiveEXT); + self.require_mesh_shaders()?; } + Ok(BindingDecorations::Location { + location, + others, + blend_src, + }) } crate::Binding::BuiltIn(built_in) => { use crate::BuiltIn as Bi; + let mut others = ArrayVec::new(); let built_in = match built_in { Bi::Position { invariant } => { if invariant { - self.decorate(id, Decoration::Invariant, &[]); + others.push(Decoration::Invariant); } if class == spirv::StorageClass::Output { @@ -2152,19 +2744,18 @@ impl Writer { )?; BuiltIn::SubgroupLocalInvocationId } - Bi::MeshTaskSize - | Bi::CullPrimitive - | Bi::PointIndex - | Bi::LineIndices - | Bi::TriangleIndices - | Bi::VertexCount - | Bi::PrimitiveCount - | Bi::Vertices - | Bi::Primitives => unreachable!(), + Bi::CullPrimitive => BuiltIn::CullPrimitiveEXT, + Bi::PointIndex => BuiltIn::PrimitivePointIndicesEXT, + Bi::LineIndices => BuiltIn::PrimitiveLineIndicesEXT, + Bi::TriangleIndices => BuiltIn::PrimitiveTriangleIndicesEXT, + // No decoration, this EmitMeshTasksEXT is called at function return + Bi::MeshTaskSize => return Ok(BindingDecorations::None), + // These aren't normal builtins and don't occur in function output + Bi::VertexCount | Bi::Vertices | Bi::PrimitiveCount | Bi::Primitives => { + unreachable!() + } }; - self.decorate(id, Decoration::BuiltIn, &[built_in as u32]); - use crate::ScalarKind as Sk; // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: @@ -2174,9 +2765,8 @@ impl Writer { // > shader, must be decorated Flat if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { let is_flat = match ir_module.types[ty].inner { - TypeInner::Scalar(scalar) | TypeInner::Vector { scalar, .. } => match scalar - .kind - { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { Sk::Uint | Sk::Sint | Sk::Bool => true, Sk::Float => false, Sk::AbstractInt | Sk::AbstractFloat => { @@ -2189,13 +2779,12 @@ impl Writer { }; if is_flat { - self.decorate(id, Decoration::Flat, &[]); + others.push(Decoration::Flat); } } + Ok(BindingDecorations::BuiltIn(built_in, others)) } } - - Ok(id) } /// Load an IO variable, converting from `f32` to `f16` if polyfill is active. @@ -2468,6 +3057,16 @@ impl Writer { self.physical_layout.bound = self.id_gen.0 + 1; } + pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { + self.use_extension("SPV_EXT_mesh_shader"); + self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; + let lang_version = self.lang_version(); + if lang_version.0 <= 1 && lang_version.1 < 4 { + return Err(Error::SpirvVersionTooLow(1, 4)); + } + Ok(()) + } + fn write_logical_layout( &mut self, ir_module: &crate::Module, @@ -2505,6 +3104,17 @@ impl Writer { | ir_module.special_types.ray_intersection.is_some(); let has_vertex_return = ir_module.special_types.ray_vertex_return.is_some(); + // Ways mesh shaders are required: + // * Mesh entry point used - checked for + // * Mesh function like setVertex used outside mesh entry point, this is handled when those are written + // * Fragment shader with per primitive data - handled in `map_binding` + let has_mesh_shaders = ir_module.entry_points.iter().any(|entry| { + entry.stage == crate::ShaderStage::Mesh || entry.stage == crate::ShaderStage::Task + }) || ir_module + .global_variables + .iter() + .any(|gvar| gvar.1.space == crate::AddressSpace::TaskPayload); + for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() { // spirv does not know whether these have vertex return - that is done by us if let &crate::TypeInner::AccelerationStructure { .. } @@ -2531,6 +3141,9 @@ impl Writer { Instruction::extension("SPV_KHR_ray_tracing_position_fetch") .to_words(&mut self.logical_layout.extensions); } + if has_mesh_shaders { + self.require_mesh_shaders()?; + } Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") .to_words(&mut self.logical_layout.ext_inst_imports); @@ -2546,11 +3159,11 @@ impl Writer { source_code: debug_info.source_code, source_file_id, }); - self.debugs.append(&mut Instruction::source_auto_continued( - debug_info.language, - 0, - &debug_info_inner, - )); + for ins in + Instruction::source_auto_continued(debug_info.language, 0, &debug_info_inner) + { + ins.to_words(&mut self.logical_layout.debugs); + } } } diff --git a/naga/tests/in/wgsl/mesh-shader.toml b/naga/tests/in/wgsl/mesh-shader.toml index 1f8b4e23baa..2dc97fa2c87 100644 --- a/naga/tests/in/wgsl/mesh-shader.toml +++ b/naga/tests/in/wgsl/mesh-shader.toml @@ -1,7 +1,7 @@ # Stolen from ray-query.toml god_mode = true -targets = "IR | ANALYSIS" +targets = "IR | ANALYSIS | SPIRV" [msl] fake_missing_bindings = true diff --git a/naga/tests/out/spv/wgsl-access.spvasm b/naga/tests/out/spv/wgsl-access.spvasm index 31e8e5d4c0b..8ae9d8e245a 100644 --- a/naga/tests/out/spv/wgsl-access.spvasm +++ b/naga/tests/out/spv/wgsl-access.spvasm @@ -11,7 +11,6 @@ OpEntryPoint Fragment %387 "foo_frag" %386 OpEntryPoint GLCompute %405 "foo_compute" OpExecutionMode %387 OriginUpperLeft OpExecutionMode %405 LocalSize 1 1 1 -%3 = OpString "access.wgsl" OpSource Unknown 0 %3 "// This snapshot tests accessing various containers, dereferencing pointers. struct GlobalConst { @@ -269,6 +268,7 @@ fn foo_compute() { var_members_of_members(); } " +%3 = OpString "access.wgsl" OpMemberName %7 0 "a" OpMemberName %7 1 "b" OpMemberName %7 2 "c" diff --git a/naga/tests/out/spv/wgsl-boids.spvasm b/naga/tests/out/spv/wgsl-boids.spvasm index 837d9326cd4..6df3e92f984 100644 --- a/naga/tests/out/spv/wgsl-boids.spvasm +++ b/naga/tests/out/spv/wgsl-boids.spvasm @@ -8,7 +8,6 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %24 "main" %21 OpExecutionMode %24 LocalSize 64 1 1 -%3 = OpString "boids.wgsl" OpSource Unknown 0 %3 "const NUM_PARTICLES: u32 = 1500u; struct Particle { @@ -117,6 +116,7 @@ fn main(@builtin(global_invocation_id) global_invocation_id : vec3) { particlesDst.particles[index].vel = vVel; } " +%3 = OpString "boids.wgsl" OpMemberName %7 0 "pos" OpMemberName %7 1 "vel" OpName %7 "Particle" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm index 2f7efbe55df..76c10d55e0b 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm @@ -8,7 +8,6 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %91 "fragment_shader" %89 OpExecutionMode %91 OriginUpperLeft -%3 = OpString "bounds-check-image-restrict-depth.wgsl" OpSource Unknown 0 %3 "// Cases from bounds-check-image-restrict that GLSL does not yet support. @group(0) @binding(0) @@ -46,6 +45,7 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " +%3 = OpString "bounds-check-image-restrict-depth.wgsl" OpName %12 "image_depth_2d" OpName %14 "image_depth_2d_array" OpName %16 "image_depth_multisampled_2d" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm index 4a7b1240e14..341c879b6b4 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm @@ -10,7 +10,6 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %174 "fragment_shader" %172 OpExecutionMode %174 OriginUpperLeft -%3 = OpString "bounds-check-image-restrict.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_1d: texture_1d; @@ -101,6 +100,7 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " +%3 = OpString "bounds-check-image-restrict.wgsl" OpName %19 "image_1d" OpName %21 "image_2d" OpName %23 "image_2d_array" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm index 4ecca6c2f38..d1458d9d27c 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm @@ -8,7 +8,6 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %102 "fragment_shader" %100 OpExecutionMode %102 OriginUpperLeft -%3 = OpString "bounds-check-image-rzsw-depth.wgsl" OpSource Unknown 0 %3 "// Cases from bounds-check-image-restrict that GLSL does not yet support. @group(0) @binding(0) @@ -46,6 +45,7 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " +%3 = OpString "bounds-check-image-rzsw-depth.wgsl" OpName %12 "image_depth_2d" OpName %14 "image_depth_2d_array" OpName %16 "image_depth_multisampled_2d" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm index 814d2aa24df..b5faac8d783 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm @@ -10,7 +10,6 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %189 "fragment_shader" %187 OpExecutionMode %189 OriginUpperLeft -%3 = OpString "bounds-check-image-rzsw.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_1d: texture_1d; @@ -101,6 +100,7 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " +%3 = OpString "bounds-check-image-rzsw.wgsl" OpName %19 "image_1d" OpName %21 "image_2d" OpName %23 "image_2d_array" diff --git a/naga/tests/out/spv/wgsl-collatz.spvasm b/naga/tests/out/spv/wgsl-collatz.spvasm index f40ee191dbc..09e0a381c62 100644 --- a/naga/tests/out/spv/wgsl-collatz.spvasm +++ b/naga/tests/out/spv/wgsl-collatz.spvasm @@ -8,7 +8,6 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %84 "main" %81 OpExecutionMode %84 LocalSize 1 1 1 -%3 = OpString "collatz.wgsl" OpSource Unknown 0 %3 "struct PrimeIndices { data: array } // this is used as both input and output for convenience @@ -42,6 +41,7 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { v_indices.data[global_id.x] = collatz_iterations(v_indices.data[global_id.x]); } " +%3 = OpString "collatz.wgsl" OpMemberName %6 0 "data" OpName %6 "PrimeIndices" OpName %8 "v_indices" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm index ef1d8bc534f..fea3721044e 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm @@ -14,7 +14,6 @@ OpEntryPoint Fragment %615 "fs_main" %608 %610 %612 %614 OpExecutionMode %367 LocalSize 64 1 1 OpExecutionMode %495 OriginUpperLeft OpExecutionMode %615 OriginUpperLeft -%3 = OpString "debug-symbol-large-source.wgsl" OpSource Unknown 0 %3 "//This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 //This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 //This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 @@ -7485,6 +7484,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(result, 1.0); } " +%3 = OpString "debug-symbol-large-source.wgsl" OpMemberName %13 0 "chunk_size" OpMemberName %13 1 "chunk_corner" OpMemberName %13 2 "min_max_height" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm index 4adb3411b35..95fa902f5e7 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm @@ -8,7 +8,6 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %21 "vs_main" %12 %15 %17 %19 OpEntryPoint Fragment %49 "fs_main" %43 %46 %48 OpExecutionMode %49 OriginUpperLeft -%3 = OpString "debug-symbol-simple.wgsl" OpSource Unknown 0 %3 "struct VertexInput { @location(0) position: vec3, @location(1) color: vec3, @@ -42,6 +41,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(color, 1.0); }" +%3 = OpString "debug-symbol-simple.wgsl" OpMemberName %6 0 "position" OpMemberName %6 1 "color" OpName %6 "VertexInput" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm index 47009186afb..f52dfb6becc 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm @@ -14,7 +14,6 @@ OpEntryPoint Fragment %615 "fs_main" %608 %610 %612 %614 OpExecutionMode %367 LocalSize 64 1 1 OpExecutionMode %495 OriginUpperLeft OpExecutionMode %615 OriginUpperLeft -%3 = OpString "debug-symbol-terrain.wgsl" OpSource Unknown 0 %3 "// Taken from https://github.com/sotrh/learn-wgpu/blob/11820796f5e1dbce42fb1119f04ddeb4b167d2a0/code/intermediate/tutorial13-terrain/src/terrain.wgsl // ============================ // Terrain Generation @@ -320,6 +319,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(result, 1.0); } " +%3 = OpString "debug-symbol-terrain.wgsl" OpMemberName %13 0 "chunk_size" OpMemberName %13 1 "chunk_corner" OpMemberName %13 2 "min_max_height" diff --git a/naga/tests/out/spv/wgsl-f16-native.spvasm b/naga/tests/out/spv/wgsl-f16-native.spvasm index 43210270933..1f48f22754c 100644 --- a/naga/tests/out/spv/wgsl-f16-native.spvasm +++ b/naga/tests/out/spv/wgsl-f16-native.spvasm @@ -20,7 +20,6 @@ OpExecutionMode %136 OriginUpperLeft OpExecutionMode %199 OriginUpperLeft OpExecutionMode %265 OriginUpperLeft OpExecutionMode %299 OriginUpperLeft -%3 = OpString "f16-native.wgsl" OpSource Unknown 0 %3 "enable f16; @fragment @@ -100,6 +99,7 @@ fn test_component_access(input: F16IO) -> F16IO { output.vec2_f16.y = input.vec2_f16.x; return output; }" +%3 = OpString "f16-native.wgsl" OpMemberName %12 0 "scalar_f16" OpMemberName %12 1 "scalar_f32" OpMemberName %12 2 "vec2_f16" diff --git a/naga/tests/out/spv/wgsl-f16-polyfill.spvasm b/naga/tests/out/spv/wgsl-f16-polyfill.spvasm index d673816a486..002e4fecde4 100644 --- a/naga/tests/out/spv/wgsl-f16-polyfill.spvasm +++ b/naga/tests/out/spv/wgsl-f16-polyfill.spvasm @@ -19,7 +19,6 @@ OpExecutionMode %140 OriginUpperLeft OpExecutionMode %211 OriginUpperLeft OpExecutionMode %285 OriginUpperLeft OpExecutionMode %324 OriginUpperLeft -%3 = OpString "f16-polyfill.wgsl" OpSource Unknown 0 %3 "enable f16; @fragment @@ -99,6 +98,7 @@ fn test_component_access(input: F16IO) -> F16IO { output.vec2_f16.y = input.vec2_f16.x; return output; }" +%3 = OpString "f16-polyfill.wgsl" OpMemberName %12 0 "scalar_f16" OpMemberName %12 1 "scalar_f32" OpMemberName %12 2 "vec2_f16" diff --git a/naga/tests/out/spv/wgsl-image.spvasm b/naga/tests/out/spv/wgsl-image.spvasm index 541fb1f5e64..a5c168e0e64 100644 --- a/naga/tests/out/spv/wgsl-image.spvasm +++ b/naga/tests/out/spv/wgsl-image.spvasm @@ -23,7 +23,6 @@ OpExecutionMode %303 OriginUpperLeft OpExecutionMode %461 OriginUpperLeft OpExecutionMode %516 OriginUpperLeft OpExecutionMode %550 OriginUpperLeft -%3 = OpString "image.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_mipmapped_src: texture_2d; @group(0) @binding(3) @@ -225,6 +224,7 @@ fn depth_no_comparison() -> @location(0) vec4 { return s2d + s2d_gather + s2d_level; } " +%3 = OpString "image.wgsl" OpName %29 "image_mipmapped_src" OpName %31 "image_multisampled_src" OpName %33 "image_depth_multisampled_src" diff --git a/naga/tests/out/spv/wgsl-interface.fragment.spvasm b/naga/tests/out/spv/wgsl-interface.fragment.spvasm index e78d69121f3..ab1dcbd0101 100644 --- a/naga/tests/out/spv/wgsl-interface.fragment.spvasm +++ b/naga/tests/out/spv/wgsl-interface.fragment.spvasm @@ -17,8 +17,8 @@ OpMemberDecorate %7 2 Offset 8 OpDecorate %9 ArrayStride 4 OpMemberDecorate %12 0 Offset 0 OpMemberDecorate %13 0 Offset 0 -OpDecorate %16 Invariant OpDecorate %16 BuiltIn FragCoord +OpDecorate %16 Invariant OpDecorate %19 Location 1 OpDecorate %22 BuiltIn FrontFacing OpDecorate %22 Flat diff --git a/naga/tests/out/spv/wgsl-interface.vertex.spvasm b/naga/tests/out/spv/wgsl-interface.vertex.spvasm index fa11c5b89f7..b72f38e7938 100644 --- a/naga/tests/out/spv/wgsl-interface.vertex.spvasm +++ b/naga/tests/out/spv/wgsl-interface.vertex.spvasm @@ -17,8 +17,8 @@ OpMemberDecorate %13 0 Offset 0 OpDecorate %15 BuiltIn VertexIndex OpDecorate %18 BuiltIn InstanceIndex OpDecorate %20 Location 10 -OpDecorate %22 Invariant OpDecorate %22 BuiltIn Position +OpDecorate %22 Invariant OpDecorate %24 Location 1 OpDecorate %26 BuiltIn PointSize %2 = OpTypeVoid diff --git a/naga/tests/out/spv/wgsl-interface.vertex_two_structs.spvasm b/naga/tests/out/spv/wgsl-interface.vertex_two_structs.spvasm index f83a5a624b3..9706a904dbe 100644 --- a/naga/tests/out/spv/wgsl-interface.vertex_two_structs.spvasm +++ b/naga/tests/out/spv/wgsl-interface.vertex_two_structs.spvasm @@ -16,8 +16,8 @@ OpMemberDecorate %12 0 Offset 0 OpMemberDecorate %13 0 Offset 0 OpDecorate %16 BuiltIn VertexIndex OpDecorate %20 BuiltIn InstanceIndex -OpDecorate %22 Invariant OpDecorate %22 BuiltIn Position +OpDecorate %22 Invariant OpDecorate %24 BuiltIn PointSize %2 = OpTypeVoid %3 = OpTypeFloat 32 diff --git a/naga/tests/out/spv/wgsl-interpolate.spvasm b/naga/tests/out/spv/wgsl-interpolate.spvasm index 91af2f3fe2c..f03f62fec74 100644 --- a/naga/tests/out/spv/wgsl-interpolate.spvasm +++ b/naga/tests/out/spv/wgsl-interpolate.spvasm @@ -9,7 +9,6 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %30 "vert_main" %11 %13 %15 %16 %17 %19 %21 %23 %24 %25 %26 %27 %28 OpEntryPoint Fragment %137 "frag_main" %108 %111 %114 %116 %118 %121 %124 %127 %129 %131 %133 %135 OpExecutionMode %137 OriginUpperLeft -%3 = OpString "interpolate.wgsl" OpSource Unknown 0 %3 "//TODO: merge with \"interface\"? // NOTE: invalid combinations are tested in the @@ -52,6 +51,7 @@ fn vert_main() -> FragmentInput { @fragment fn frag_main(val : FragmentInput) { } " +%3 = OpString "interpolate.wgsl" OpMemberName %9 0 "position" OpMemberName %9 1 "_flat" OpMemberName %9 2 "flat_first" diff --git a/naga/tests/out/spv/wgsl-interpolate_compat.spvasm b/naga/tests/out/spv/wgsl-interpolate_compat.spvasm index f09a39d5a52..d5ec9066e40 100644 --- a/naga/tests/out/spv/wgsl-interpolate_compat.spvasm +++ b/naga/tests/out/spv/wgsl-interpolate_compat.spvasm @@ -9,7 +9,6 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %29 "vert_main" %11 %13 %15 %16 %18 %20 %22 %23 %24 %25 %26 %27 OpEntryPoint Fragment %131 "frag_main" %104 %107 %110 %112 %115 %118 %121 %123 %125 %127 %129 OpExecutionMode %131 OriginUpperLeft -%3 = OpString "interpolate_compat.wgsl" OpSource Unknown 0 %3 "// NOTE: This is basically the same as `interpolate.wgsl`, except for the removal of // `@interpolate(flat, first)`, which is unsupported in GLSL and `compat`. @@ -54,6 +53,7 @@ fn vert_main() -> FragmentInput { @fragment fn frag_main(val : FragmentInput) { } " +%3 = OpString "interpolate_compat.wgsl" OpMemberName %9 0 "position" OpMemberName %9 1 "_flat" OpMemberName %9 2 "flat_either" diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm new file mode 100644 index 00000000000..d58789cef6e --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -0,0 +1,299 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 188 +OpCapability Shader +OpCapability MeshShadingEXT +OpExtension "SPV_EXT_mesh_shader" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TaskEXT %24 "ts_main" %17 %19 %33 +OpEntryPoint MeshEXT %80 "ms_main" %54 %57 %17 %64 %68 %72 %75 %79 %19 %21 %92 +OpEntryPoint Fragment %183 "fs_main" %174 %177 %180 %182 +OpExecutionMode %24 LocalSize 1 1 1 +OpExecutionMode %80 LocalSize 1 1 1 +OpExecutionMode %80 OutputTrianglesNV +OpExecutionMode %80 OutputVertices 3 +OpExecutionMode %80 OutputPrimitivesNV 1 +OpExecutionMode %183 OriginUpperLeft +OpMemberDecorate %61 0 BuiltIn Position +OpDecorate %61 Block +OpMemberDecorate %65 0 BuiltIn CullPrimitiveEXT +OpDecorate %65 Block +OpDecorate %68 PerPrimitiveNV +OpDecorate %69 Block +OpMemberDecorate %69 0 Location 0 +OpDecorate %75 PerPrimitiveNV +OpDecorate %75 BuiltIn PrimitiveTriangleIndicesEXT +OpDecorate %76 Block +OpMemberDecorate %76 0 Location 1 +OpDecorate %79 PerPrimitiveNV +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 16 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 12 +OpMemberDecorate %10 2 Offset 16 +OpMemberDecorate %11 0 Offset 0 +OpDecorate %12 ArrayStride 32 +OpDecorate %14 ArrayStride 32 +OpMemberDecorate %16 0 Offset 0 +OpMemberDecorate %16 1 Offset 96 +OpMemberDecorate %16 2 Offset 128 +OpMemberDecorate %16 3 Offset 132 +OpDecorate %33 BuiltIn LocalInvocationId +OpDecorate %54 BuiltIn LocalInvocationIndex +OpDecorate %57 BuiltIn GlobalInvocationId +OpDecorate %92 BuiltIn LocalInvocationId +OpDecorate %174 BuiltIn FragCoord +OpDecorate %177 Location 0 +OpDecorate %180 Location 1 +OpDecorate %180 PerPrimitiveNV +OpDecorate %182 Location 0 +%2 = OpTypeVoid +%3 = OpTypeFloat 32 +%4 = OpTypeVector %3 4 +%5 = OpTypeBool +%6 = OpTypeStruct %4 %5 +%7 = OpTypeStruct %4 %4 +%8 = OpTypeInt 32 0 +%9 = OpTypeVector %8 3 +%10 = OpTypeStruct %9 %5 %4 +%11 = OpTypeStruct %4 +%13 = OpConstant %8 3 +%12 = OpTypeArray %7 %13 +%15 = OpConstant %8 1 +%14 = OpTypeArray %10 %15 +%16 = OpTypeStruct %12 %14 %8 %8 +%18 = OpTypePointer TaskPayloadWorkgroupEXT %6 +%17 = OpVariable %18 TaskPayloadWorkgroupEXT +%20 = OpTypePointer Workgroup %3 +%19 = OpVariable %20 Workgroup +%22 = OpTypePointer Workgroup %16 +%21 = OpVariable %22 Workgroup +%25 = OpTypeFunction %2 +%26 = OpConstant %3 1 +%27 = OpConstant %3 0 +%28 = OpConstantComposite %4 %26 %26 %27 %26 +%29 = OpConstantTrue %5 +%30 = OpConstantComposite %9 %13 %15 %15 +%32 = OpConstantNull %3 +%34 = OpTypePointer Input %9 +%33 = OpVariable %34 Input +%36 = OpConstantNull %9 +%37 = OpTypeVector %5 3 +%42 = OpConstant %8 2 +%43 = OpConstant %8 264 +%45 = OpTypePointer TaskPayloadWorkgroupEXT %4 +%46 = OpConstant %8 0 +%48 = OpTypePointer TaskPayloadWorkgroupEXT %5 +%55 = OpTypePointer Input %8 +%54 = OpVariable %55 Input +%57 = OpVariable %34 Input +%60 = OpTypePointer Function %8 +%61 = OpTypeStruct %4 +%62 = OpTypeArray %61 %13 +%63 = OpTypePointer Output %62 +%64 = OpVariable %63 Output +%65 = OpTypeStruct %5 +%66 = OpTypeArray %65 %15 +%67 = OpTypePointer Output %66 +%68 = OpVariable %67 Output +%69 = OpTypeStruct %4 +%70 = OpTypeArray %69 %13 +%71 = OpTypePointer Output %70 +%72 = OpVariable %71 Output +%73 = OpTypeArray %9 %15 +%74 = OpTypePointer Output %73 +%75 = OpVariable %74 Output +%76 = OpTypeStruct %4 +%77 = OpTypeArray %76 %15 +%78 = OpTypePointer Output %77 +%79 = OpVariable %78 Output +%81 = OpConstant %3 2 +%82 = OpConstantComposite %4 %27 %26 %27 %26 +%83 = OpConstant %3 -1 +%84 = OpConstantComposite %4 %83 %83 %27 %26 +%85 = OpConstantComposite %4 %27 %27 %26 %26 +%86 = OpConstantComposite %4 %26 %83 %27 %26 +%87 = OpConstantComposite %4 %26 %27 %27 %26 +%88 = OpConstantComposite %9 %46 %15 %42 +%89 = OpConstantComposite %4 %26 %27 %26 %26 +%91 = OpConstantNull %16 +%92 = OpVariable %34 Input +%99 = OpTypePointer Workgroup %8 +%102 = OpTypePointer Workgroup %12 +%103 = OpTypePointer Workgroup %7 +%104 = OpTypePointer Workgroup %4 +%120 = OpTypePointer Workgroup %14 +%121 = OpTypePointer Workgroup %10 +%122 = OpTypePointer Workgroup %9 +%124 = OpTypePointer Workgroup %5 +%144 = OpTypePointer Output %4 +%152 = OpTypePointer Output %9 +%155 = OpTypePointer Output %5 +%175 = OpTypePointer Input %4 +%174 = OpVariable %175 Input +%177 = OpVariable %175 Input +%180 = OpVariable %175 Input +%182 = OpVariable %144 Output +%24 = OpFunction %2 None %25 +%23 = OpLabel +OpBranch %31 +%31 = OpLabel +%35 = OpLoad %9 %33 +%38 = OpIEqual %37 %35 %36 +%39 = OpAll %5 %38 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %40 +%41 = OpLabel +OpStore %19 %32 +OpBranch %40 +%40 = OpLabel +OpControlBarrier %42 %42 %43 +OpBranch %44 +%44 = OpLabel +OpStore %19 %26 +%47 = OpAccessChain %45 %17 %46 +OpStore %47 %28 +%49 = OpAccessChain %48 %17 %15 +OpStore %49 %29 +%50 = OpCompositeExtract %8 %30 0 +%51 = OpCompositeExtract %8 %30 1 +%52 = OpCompositeExtract %8 %30 2 +OpEmitMeshTasksEXT %50 %51 %52 %17 +OpFunctionEnd +%80 = OpFunction %2 None %25 +%53 = OpLabel +%59 = OpVariable %60 Function +%56 = OpLoad %8 %54 +%58 = OpLoad %9 %57 +OpBranch %90 +%90 = OpLabel +%93 = OpLoad %9 %92 +%94 = OpIEqual %37 %93 %36 +%95 = OpAll %5 %94 +OpSelectionMerge %96 None +OpBranchConditional %95 %97 %96 +%97 = OpLabel +OpStore %19 %32 +OpStore %21 %91 +OpBranch %96 +%96 = OpLabel +OpControlBarrier %42 %42 %43 +OpBranch %98 +%98 = OpLabel +%100 = OpAccessChain %99 %21 %42 +OpStore %100 %13 +%101 = OpAccessChain %99 %21 %13 +OpStore %101 %15 +OpStore %19 %81 +%105 = OpAccessChain %104 %21 %46 %46 %46 +OpStore %105 %82 +%106 = OpAccessChain %45 %17 %46 +%107 = OpLoad %4 %106 +%108 = OpFMul %4 %82 %107 +%109 = OpAccessChain %104 %21 %46 %46 %15 +OpStore %109 %108 +%110 = OpAccessChain %104 %21 %46 %15 %46 +OpStore %110 %84 +%111 = OpAccessChain %45 %17 %46 +%112 = OpLoad %4 %111 +%113 = OpFMul %4 %85 %112 +%114 = OpAccessChain %104 %21 %46 %15 %15 +OpStore %114 %113 +%115 = OpAccessChain %104 %21 %46 %42 %46 +OpStore %115 %86 +%116 = OpAccessChain %45 %17 %46 +%117 = OpLoad %4 %116 +%118 = OpFMul %4 %87 %117 +%119 = OpAccessChain %104 %21 %46 %42 %15 +OpStore %119 %118 +%123 = OpAccessChain %122 %21 %15 %46 %46 +OpStore %123 %88 +%125 = OpAccessChain %48 %17 %15 +%126 = OpLoad %5 %125 +%127 = OpLogicalNot %5 %126 +%128 = OpAccessChain %124 %21 %15 %46 %15 +OpStore %128 %127 +%129 = OpAccessChain %104 %21 %15 %46 %42 +OpStore %129 %89 +%130 = OpLoad %16 %21 +%131 = OpCompositeExtract %8 %130 2 +%132 = OpCompositeExtract %8 %130 3 +%133 = OpAccessChain %102 %21 %46 +%134 = OpAccessChain %120 %21 %15 +OpSetMeshOutputsEXT %131 %132 +OpStore %59 %56 +OpBranch %135 +%135 = OpLabel +OpLoopMerge %137 %159 None +OpBranch %158 +%158 = OpLabel +%161 = OpLoad %8 %59 +%162 = OpULessThan %5 %161 %131 +OpBranchConditional %162 %160 %137 +%160 = OpLabel +%139 = OpLoad %8 %59 +%140 = OpAccessChain %103 %133 %139 +%141 = OpLoad %7 %140 +%142 = OpCompositeExtract %4 %141 0 +%143 = OpAccessChain %144 %64 %139 %46 +OpStore %143 %142 +%145 = OpCompositeExtract %4 %141 1 +%146 = OpAccessChain %144 %72 %139 %46 +OpStore %146 %145 +OpBranch %159 +%159 = OpLabel +%163 = OpLoad %8 %59 +%164 = OpIAdd %8 %163 %15 +OpStore %59 %164 +OpBranch %135 +%137 = OpLabel +OpStore %59 %56 +OpBranch %136 +%136 = OpLabel +OpLoopMerge %138 %166 None +OpBranch %165 +%165 = OpLabel +%168 = OpLoad %8 %59 +%169 = OpULessThan %5 %168 %132 +OpBranchConditional %169 %167 %138 +%167 = OpLabel +%147 = OpLoad %8 %59 +%148 = OpAccessChain %121 %134 %147 +%149 = OpLoad %10 %148 +%150 = OpCompositeExtract %9 %149 0 +%151 = OpAccessChain %152 %75 %147 +OpStore %151 %150 +%153 = OpCompositeExtract %5 %149 1 +%154 = OpAccessChain %155 %68 %147 %46 +OpStore %154 %153 +%156 = OpCompositeExtract %4 %149 2 +%157 = OpAccessChain %144 %79 %147 %46 +OpStore %157 %156 +OpBranch %166 +%166 = OpLabel +%170 = OpLoad %8 %59 +%171 = OpIAdd %8 %170 %15 +OpStore %59 %171 +OpBranch %136 +%138 = OpLabel +OpReturn +OpFunctionEnd +%183 = OpFunction %2 None %25 +%172 = OpLabel +%176 = OpLoad %4 %174 +%178 = OpLoad %4 %177 +%173 = OpCompositeConstruct %7 %176 %178 +%181 = OpLoad %4 %180 +%179 = OpCompositeConstruct %11 %181 +OpBranch %184 +%184 = OpLabel +%185 = OpCompositeExtract %4 %173 1 +%186 = OpCompositeExtract %4 %179 0 +%187 = OpFMul %4 %185 %186 +OpStore %182 %187 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-padding.spvasm b/naga/tests/out/spv/wgsl-padding.spvasm index 615965cc3e4..2102f757626 100644 --- a/naga/tests/out/spv/wgsl-padding.spvasm +++ b/naga/tests/out/spv/wgsl-padding.spvasm @@ -6,7 +6,6 @@ OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %27 "vertex" %25 -%3 = OpString "padding.wgsl" OpSource Unknown 0 %3 "struct S { a: vec3, } @@ -41,6 +40,7 @@ fn vertex() -> @builtin(position) vec4 { return vec4(1.0) * input1.b * input2.b * input3.b; } " +%3 = OpString "padding.wgsl" OpMemberName %6 0 "a" OpName %6 "S" OpMemberName %7 0 "a" diff --git a/naga/tests/out/spv/wgsl-pointers.spvasm b/naga/tests/out/spv/wgsl-pointers.spvasm index 08867c95384..5b6a696c7c2 100644 --- a/naga/tests/out/spv/wgsl-pointers.spvasm +++ b/naga/tests/out/spv/wgsl-pointers.spvasm @@ -8,7 +8,6 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %47 "main" OpExecutionMode %47 LocalSize 1 1 1 -%3 = OpString "pointers.wgsl" OpSource Unknown 0 %3 "fn f() { var v: mat2x2; let px = &v[0]; @@ -43,6 +42,7 @@ fn main() { index_dynamic_array(1, 1); } " +%3 = OpString "pointers.wgsl" OpMemberName %9 0 "arr" OpName %9 "DynamicArray" OpName %11 "dynamic_array" diff --git a/naga/tests/out/spv/wgsl-policy-mix.spvasm b/naga/tests/out/spv/wgsl-policy-mix.spvasm index 8d9209d7b35..0bd5ff0d722 100644 --- a/naga/tests/out/spv/wgsl-policy-mix.spvasm +++ b/naga/tests/out/spv/wgsl-policy-mix.spvasm @@ -9,7 +9,6 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %102 "main" %115 OpExecutionMode %102 LocalSize 1 1 1 -%3 = OpString "policy-mix.wgsl" OpSource Unknown 0 %3 "// Tests that the index, buffer, and texture bounds checks policies are // implemented separately. @@ -49,6 +48,7 @@ fn main() { mock_function(vec2(1, 2), 3, 4); } " +%3 = OpString "policy-mix.wgsl" OpMemberName %9 0 "a" OpName %9 "InStorage" OpMemberName %12 0 "a" diff --git a/naga/tests/out/spv/wgsl-quad.spvasm b/naga/tests/out/spv/wgsl-quad.spvasm index 307fa304926..959da0946df 100644 --- a/naga/tests/out/spv/wgsl-quad.spvasm +++ b/naga/tests/out/spv/wgsl-quad.spvasm @@ -10,7 +10,6 @@ OpEntryPoint Fragment %45 "frag_main" %42 %44 OpEntryPoint Fragment %61 "fs_extra" %60 OpExecutionMode %45 OriginUpperLeft OpExecutionMode %61 OriginUpperLeft -%3 = OpString "quad.wgsl" OpSource Unknown 0 %3 "// vertex const c_scale: f32 = 1.2; @@ -50,6 +49,7 @@ fn fs_extra() -> @location(0) vec4 { return vec4(0.0, 0.5, 0.0, 0.5); } " +%3 = OpString "quad.wgsl" OpMemberName %7 0 "uv" OpMemberName %7 1 "position" OpName %7 "VertexOutput" diff --git a/naga/tests/out/spv/wgsl-shadow.spvasm b/naga/tests/out/spv/wgsl-shadow.spvasm index 1360528131a..916fe9807a4 100644 --- a/naga/tests/out/spv/wgsl-shadow.spvasm +++ b/naga/tests/out/spv/wgsl-shadow.spvasm @@ -11,7 +11,6 @@ OpEntryPoint Fragment %143 "fs_main" %134 %137 %140 %142 OpEntryPoint Fragment %228 "fs_main_without_storage" %221 %223 %225 %227 OpExecutionMode %143 OriginUpperLeft OpExecutionMode %228 OriginUpperLeft -%3 = OpString "shadow.wgsl" OpSource Unknown 0 %3 "struct Globals { view_proj: mat4x4, num_lights: vec4, @@ -130,6 +129,7 @@ fn fs_main_without_storage(in: VertexOutput) -> @location(0) vec4 { return vec4(color, 1.0) * u_entity.color; } " +%3 = OpString "shadow.wgsl" OpMemberName %9 0 "view_proj" OpMemberName %9 1 "num_lights" OpName %9 "Globals" diff --git a/naga/tests/out/spv/wgsl-texture-arg.spvasm b/naga/tests/out/spv/wgsl-texture-arg.spvasm index 23bd0ce2977..b808340b75f 100644 --- a/naga/tests/out/spv/wgsl-texture-arg.spvasm +++ b/naga/tests/out/spv/wgsl-texture-arg.spvasm @@ -7,7 +7,6 @@ OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %29 "main" %27 OpExecutionMode %29 OriginUpperLeft -%3 = OpString "texture-arg.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var Texture: texture_2d; @group(0) @binding(1) @@ -22,6 +21,7 @@ fn main() -> @location(0) vec4 { return test(Texture, Sampler); } " +%3 = OpString "texture-arg.wgsl" OpName %9 "Texture" OpName %11 "Sampler" OpName %14 "Passed_Texture" diff --git a/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm b/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm index cb214f3e357..35bcfb4a7d5 100644 --- a/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm +++ b/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm @@ -8,7 +8,6 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %18 "main" %26 OpExecutionMode %18 LocalSize 1 1 1 -%3 = OpString "workgroup-var-init.wgsl" OpSource Unknown 0 %3 "struct WStruct { arr: array, atom: atomic, @@ -24,6 +23,7 @@ var output: array; fn main() { output = w_mem.arr; }" +%3 = OpString "workgroup-var-init.wgsl" OpMemberName %11 0 "arr" OpMemberName %11 1 "atom" OpMemberName %11 2 "atom_arr" From f18a32a2e9efbc3ee23e0e8b368c278d2db5dcb4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 01:24:09 -0500 Subject: [PATCH 048/110] Undid stupid change to writer that I have no idea how it occured --- naga/src/back/spv/writer.rs | 11 +++++------ naga/tests/out/spv/wgsl-access.spvasm | 2 +- naga/tests/out/spv/wgsl-boids.spvasm | 2 +- .../spv/wgsl-bounds-check-image-restrict-depth.spvasm | 2 +- .../out/spv/wgsl-bounds-check-image-restrict.spvasm | 2 +- .../out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm | 2 +- .../tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm | 2 +- naga/tests/out/spv/wgsl-collatz.spvasm | 2 +- .../out/spv/wgsl-debug-symbol-large-source.spvasm | 2 +- naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm | 2 +- naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm | 2 +- naga/tests/out/spv/wgsl-f16-native.spvasm | 2 +- naga/tests/out/spv/wgsl-f16-polyfill.spvasm | 2 +- naga/tests/out/spv/wgsl-image.spvasm | 2 +- naga/tests/out/spv/wgsl-interpolate.spvasm | 2 +- naga/tests/out/spv/wgsl-interpolate_compat.spvasm | 2 +- naga/tests/out/spv/wgsl-padding.spvasm | 2 +- naga/tests/out/spv/wgsl-pointers.spvasm | 2 +- naga/tests/out/spv/wgsl-policy-mix.spvasm | 2 +- naga/tests/out/spv/wgsl-quad.spvasm | 2 +- naga/tests/out/spv/wgsl-shadow.spvasm | 2 +- naga/tests/out/spv/wgsl-texture-arg.spvasm | 2 +- naga/tests/out/spv/wgsl-workgroup-var-init.spvasm | 2 +- 23 files changed, 27 insertions(+), 28 deletions(-) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 4bfc7e48108..cf24b8440d8 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -854,7 +854,6 @@ impl Writer { ); // This is the information that is passed to the function writer // so that it can write the final return logic - let mut mesh_return_info = super::MeshReturnInfo { out_variable_id: self.global_variables[mesh_info.output_variable].var_id, out_type_id: self @@ -3159,11 +3158,11 @@ impl Writer { source_code: debug_info.source_code, source_file_id, }); - for ins in - Instruction::source_auto_continued(debug_info.language, 0, &debug_info_inner) - { - ins.to_words(&mut self.logical_layout.debugs); - } + self.debugs.append(&mut Instruction::source_auto_continued( + debug_info.language, + 0, + &debug_info_inner, + )); } } diff --git a/naga/tests/out/spv/wgsl-access.spvasm b/naga/tests/out/spv/wgsl-access.spvasm index 8ae9d8e245a..31e8e5d4c0b 100644 --- a/naga/tests/out/spv/wgsl-access.spvasm +++ b/naga/tests/out/spv/wgsl-access.spvasm @@ -11,6 +11,7 @@ OpEntryPoint Fragment %387 "foo_frag" %386 OpEntryPoint GLCompute %405 "foo_compute" OpExecutionMode %387 OriginUpperLeft OpExecutionMode %405 LocalSize 1 1 1 +%3 = OpString "access.wgsl" OpSource Unknown 0 %3 "// This snapshot tests accessing various containers, dereferencing pointers. struct GlobalConst { @@ -268,7 +269,6 @@ fn foo_compute() { var_members_of_members(); } " -%3 = OpString "access.wgsl" OpMemberName %7 0 "a" OpMemberName %7 1 "b" OpMemberName %7 2 "c" diff --git a/naga/tests/out/spv/wgsl-boids.spvasm b/naga/tests/out/spv/wgsl-boids.spvasm index 6df3e92f984..837d9326cd4 100644 --- a/naga/tests/out/spv/wgsl-boids.spvasm +++ b/naga/tests/out/spv/wgsl-boids.spvasm @@ -8,6 +8,7 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %24 "main" %21 OpExecutionMode %24 LocalSize 64 1 1 +%3 = OpString "boids.wgsl" OpSource Unknown 0 %3 "const NUM_PARTICLES: u32 = 1500u; struct Particle { @@ -116,7 +117,6 @@ fn main(@builtin(global_invocation_id) global_invocation_id : vec3) { particlesDst.particles[index].vel = vVel; } " -%3 = OpString "boids.wgsl" OpMemberName %7 0 "pos" OpMemberName %7 1 "vel" OpName %7 "Particle" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm index 76c10d55e0b..2f7efbe55df 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-restrict-depth.spvasm @@ -8,6 +8,7 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %91 "fragment_shader" %89 OpExecutionMode %91 OriginUpperLeft +%3 = OpString "bounds-check-image-restrict-depth.wgsl" OpSource Unknown 0 %3 "// Cases from bounds-check-image-restrict that GLSL does not yet support. @group(0) @binding(0) @@ -45,7 +46,6 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " -%3 = OpString "bounds-check-image-restrict-depth.wgsl" OpName %12 "image_depth_2d" OpName %14 "image_depth_2d_array" OpName %16 "image_depth_multisampled_2d" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm index 341c879b6b4..4a7b1240e14 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-restrict.spvasm @@ -10,6 +10,7 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %174 "fragment_shader" %172 OpExecutionMode %174 OriginUpperLeft +%3 = OpString "bounds-check-image-restrict.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_1d: texture_1d; @@ -100,7 +101,6 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " -%3 = OpString "bounds-check-image-restrict.wgsl" OpName %19 "image_1d" OpName %21 "image_2d" OpName %23 "image_2d_array" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm index d1458d9d27c..4ecca6c2f38 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw-depth.spvasm @@ -8,6 +8,7 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %102 "fragment_shader" %100 OpExecutionMode %102 OriginUpperLeft +%3 = OpString "bounds-check-image-rzsw-depth.wgsl" OpSource Unknown 0 %3 "// Cases from bounds-check-image-restrict that GLSL does not yet support. @group(0) @binding(0) @@ -45,7 +46,6 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " -%3 = OpString "bounds-check-image-rzsw-depth.wgsl" OpName %12 "image_depth_2d" OpName %14 "image_depth_2d_array" OpName %16 "image_depth_multisampled_2d" diff --git a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm index b5faac8d783..814d2aa24df 100644 --- a/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm +++ b/naga/tests/out/spv/wgsl-bounds-check-image-rzsw.spvasm @@ -10,6 +10,7 @@ OpCapability ImageQuery OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %189 "fragment_shader" %187 OpExecutionMode %189 OriginUpperLeft +%3 = OpString "bounds-check-image-rzsw.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_1d: texture_1d; @@ -100,7 +101,6 @@ fn fragment_shader() -> @location(0) vec4 { return vec4(0.,0.,0.,0.); } " -%3 = OpString "bounds-check-image-rzsw.wgsl" OpName %19 "image_1d" OpName %21 "image_2d" OpName %23 "image_2d_array" diff --git a/naga/tests/out/spv/wgsl-collatz.spvasm b/naga/tests/out/spv/wgsl-collatz.spvasm index 09e0a381c62..f40ee191dbc 100644 --- a/naga/tests/out/spv/wgsl-collatz.spvasm +++ b/naga/tests/out/spv/wgsl-collatz.spvasm @@ -8,6 +8,7 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %84 "main" %81 OpExecutionMode %84 LocalSize 1 1 1 +%3 = OpString "collatz.wgsl" OpSource Unknown 0 %3 "struct PrimeIndices { data: array } // this is used as both input and output for convenience @@ -41,7 +42,6 @@ fn main(@builtin(global_invocation_id) global_id: vec3) { v_indices.data[global_id.x] = collatz_iterations(v_indices.data[global_id.x]); } " -%3 = OpString "collatz.wgsl" OpMemberName %6 0 "data" OpName %6 "PrimeIndices" OpName %8 "v_indices" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm index fea3721044e..ef1d8bc534f 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-large-source.spvasm @@ -14,6 +14,7 @@ OpEntryPoint Fragment %615 "fs_main" %608 %610 %612 %614 OpExecutionMode %367 LocalSize 64 1 1 OpExecutionMode %495 OriginUpperLeft OpExecutionMode %615 OriginUpperLeft +%3 = OpString "debug-symbol-large-source.wgsl" OpSource Unknown 0 %3 "//This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 //This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 //This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 This is a test 这是测试 これはテストだ 테스트입니다 @@ -7484,7 +7485,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(result, 1.0); } " -%3 = OpString "debug-symbol-large-source.wgsl" OpMemberName %13 0 "chunk_size" OpMemberName %13 1 "chunk_corner" OpMemberName %13 2 "min_max_height" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm index 95fa902f5e7..4adb3411b35 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-simple.spvasm @@ -8,6 +8,7 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %21 "vs_main" %12 %15 %17 %19 OpEntryPoint Fragment %49 "fs_main" %43 %46 %48 OpExecutionMode %49 OriginUpperLeft +%3 = OpString "debug-symbol-simple.wgsl" OpSource Unknown 0 %3 "struct VertexInput { @location(0) position: vec3, @location(1) color: vec3, @@ -41,7 +42,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(color, 1.0); }" -%3 = OpString "debug-symbol-simple.wgsl" OpMemberName %6 0 "position" OpMemberName %6 1 "color" OpName %6 "VertexInput" diff --git a/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm b/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm index f52dfb6becc..47009186afb 100644 --- a/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm +++ b/naga/tests/out/spv/wgsl-debug-symbol-terrain.spvasm @@ -14,6 +14,7 @@ OpEntryPoint Fragment %615 "fs_main" %608 %610 %612 %614 OpExecutionMode %367 LocalSize 64 1 1 OpExecutionMode %495 OriginUpperLeft OpExecutionMode %615 OriginUpperLeft +%3 = OpString "debug-symbol-terrain.wgsl" OpSource Unknown 0 %3 "// Taken from https://github.com/sotrh/learn-wgpu/blob/11820796f5e1dbce42fb1119f04ddeb4b167d2a0/code/intermediate/tutorial13-terrain/src/terrain.wgsl // ============================ // Terrain Generation @@ -319,7 +320,6 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(result, 1.0); } " -%3 = OpString "debug-symbol-terrain.wgsl" OpMemberName %13 0 "chunk_size" OpMemberName %13 1 "chunk_corner" OpMemberName %13 2 "min_max_height" diff --git a/naga/tests/out/spv/wgsl-f16-native.spvasm b/naga/tests/out/spv/wgsl-f16-native.spvasm index 1f48f22754c..43210270933 100644 --- a/naga/tests/out/spv/wgsl-f16-native.spvasm +++ b/naga/tests/out/spv/wgsl-f16-native.spvasm @@ -20,6 +20,7 @@ OpExecutionMode %136 OriginUpperLeft OpExecutionMode %199 OriginUpperLeft OpExecutionMode %265 OriginUpperLeft OpExecutionMode %299 OriginUpperLeft +%3 = OpString "f16-native.wgsl" OpSource Unknown 0 %3 "enable f16; @fragment @@ -99,7 +100,6 @@ fn test_component_access(input: F16IO) -> F16IO { output.vec2_f16.y = input.vec2_f16.x; return output; }" -%3 = OpString "f16-native.wgsl" OpMemberName %12 0 "scalar_f16" OpMemberName %12 1 "scalar_f32" OpMemberName %12 2 "vec2_f16" diff --git a/naga/tests/out/spv/wgsl-f16-polyfill.spvasm b/naga/tests/out/spv/wgsl-f16-polyfill.spvasm index 002e4fecde4..d673816a486 100644 --- a/naga/tests/out/spv/wgsl-f16-polyfill.spvasm +++ b/naga/tests/out/spv/wgsl-f16-polyfill.spvasm @@ -19,6 +19,7 @@ OpExecutionMode %140 OriginUpperLeft OpExecutionMode %211 OriginUpperLeft OpExecutionMode %285 OriginUpperLeft OpExecutionMode %324 OriginUpperLeft +%3 = OpString "f16-polyfill.wgsl" OpSource Unknown 0 %3 "enable f16; @fragment @@ -98,7 +99,6 @@ fn test_component_access(input: F16IO) -> F16IO { output.vec2_f16.y = input.vec2_f16.x; return output; }" -%3 = OpString "f16-polyfill.wgsl" OpMemberName %12 0 "scalar_f16" OpMemberName %12 1 "scalar_f32" OpMemberName %12 2 "vec2_f16" diff --git a/naga/tests/out/spv/wgsl-image.spvasm b/naga/tests/out/spv/wgsl-image.spvasm index a5c168e0e64..541fb1f5e64 100644 --- a/naga/tests/out/spv/wgsl-image.spvasm +++ b/naga/tests/out/spv/wgsl-image.spvasm @@ -23,6 +23,7 @@ OpExecutionMode %303 OriginUpperLeft OpExecutionMode %461 OriginUpperLeft OpExecutionMode %516 OriginUpperLeft OpExecutionMode %550 OriginUpperLeft +%3 = OpString "image.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var image_mipmapped_src: texture_2d; @group(0) @binding(3) @@ -224,7 +225,6 @@ fn depth_no_comparison() -> @location(0) vec4 { return s2d + s2d_gather + s2d_level; } " -%3 = OpString "image.wgsl" OpName %29 "image_mipmapped_src" OpName %31 "image_multisampled_src" OpName %33 "image_depth_multisampled_src" diff --git a/naga/tests/out/spv/wgsl-interpolate.spvasm b/naga/tests/out/spv/wgsl-interpolate.spvasm index f03f62fec74..91af2f3fe2c 100644 --- a/naga/tests/out/spv/wgsl-interpolate.spvasm +++ b/naga/tests/out/spv/wgsl-interpolate.spvasm @@ -9,6 +9,7 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %30 "vert_main" %11 %13 %15 %16 %17 %19 %21 %23 %24 %25 %26 %27 %28 OpEntryPoint Fragment %137 "frag_main" %108 %111 %114 %116 %118 %121 %124 %127 %129 %131 %133 %135 OpExecutionMode %137 OriginUpperLeft +%3 = OpString "interpolate.wgsl" OpSource Unknown 0 %3 "//TODO: merge with \"interface\"? // NOTE: invalid combinations are tested in the @@ -51,7 +52,6 @@ fn vert_main() -> FragmentInput { @fragment fn frag_main(val : FragmentInput) { } " -%3 = OpString "interpolate.wgsl" OpMemberName %9 0 "position" OpMemberName %9 1 "_flat" OpMemberName %9 2 "flat_first" diff --git a/naga/tests/out/spv/wgsl-interpolate_compat.spvasm b/naga/tests/out/spv/wgsl-interpolate_compat.spvasm index d5ec9066e40..f09a39d5a52 100644 --- a/naga/tests/out/spv/wgsl-interpolate_compat.spvasm +++ b/naga/tests/out/spv/wgsl-interpolate_compat.spvasm @@ -9,6 +9,7 @@ OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %29 "vert_main" %11 %13 %15 %16 %18 %20 %22 %23 %24 %25 %26 %27 OpEntryPoint Fragment %131 "frag_main" %104 %107 %110 %112 %115 %118 %121 %123 %125 %127 %129 OpExecutionMode %131 OriginUpperLeft +%3 = OpString "interpolate_compat.wgsl" OpSource Unknown 0 %3 "// NOTE: This is basically the same as `interpolate.wgsl`, except for the removal of // `@interpolate(flat, first)`, which is unsupported in GLSL and `compat`. @@ -53,7 +54,6 @@ fn vert_main() -> FragmentInput { @fragment fn frag_main(val : FragmentInput) { } " -%3 = OpString "interpolate_compat.wgsl" OpMemberName %9 0 "position" OpMemberName %9 1 "_flat" OpMemberName %9 2 "flat_either" diff --git a/naga/tests/out/spv/wgsl-padding.spvasm b/naga/tests/out/spv/wgsl-padding.spvasm index 2102f757626..615965cc3e4 100644 --- a/naga/tests/out/spv/wgsl-padding.spvasm +++ b/naga/tests/out/spv/wgsl-padding.spvasm @@ -6,6 +6,7 @@ OpCapability Shader %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 OpEntryPoint Vertex %27 "vertex" %25 +%3 = OpString "padding.wgsl" OpSource Unknown 0 %3 "struct S { a: vec3, } @@ -40,7 +41,6 @@ fn vertex() -> @builtin(position) vec4 { return vec4(1.0) * input1.b * input2.b * input3.b; } " -%3 = OpString "padding.wgsl" OpMemberName %6 0 "a" OpName %6 "S" OpMemberName %7 0 "a" diff --git a/naga/tests/out/spv/wgsl-pointers.spvasm b/naga/tests/out/spv/wgsl-pointers.spvasm index 5b6a696c7c2..08867c95384 100644 --- a/naga/tests/out/spv/wgsl-pointers.spvasm +++ b/naga/tests/out/spv/wgsl-pointers.spvasm @@ -8,6 +8,7 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %47 "main" OpExecutionMode %47 LocalSize 1 1 1 +%3 = OpString "pointers.wgsl" OpSource Unknown 0 %3 "fn f() { var v: mat2x2; let px = &v[0]; @@ -42,7 +43,6 @@ fn main() { index_dynamic_array(1, 1); } " -%3 = OpString "pointers.wgsl" OpMemberName %9 0 "arr" OpName %9 "DynamicArray" OpName %11 "dynamic_array" diff --git a/naga/tests/out/spv/wgsl-policy-mix.spvasm b/naga/tests/out/spv/wgsl-policy-mix.spvasm index 0bd5ff0d722..8d9209d7b35 100644 --- a/naga/tests/out/spv/wgsl-policy-mix.spvasm +++ b/naga/tests/out/spv/wgsl-policy-mix.spvasm @@ -9,6 +9,7 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %102 "main" %115 OpExecutionMode %102 LocalSize 1 1 1 +%3 = OpString "policy-mix.wgsl" OpSource Unknown 0 %3 "// Tests that the index, buffer, and texture bounds checks policies are // implemented separately. @@ -48,7 +49,6 @@ fn main() { mock_function(vec2(1, 2), 3, 4); } " -%3 = OpString "policy-mix.wgsl" OpMemberName %9 0 "a" OpName %9 "InStorage" OpMemberName %12 0 "a" diff --git a/naga/tests/out/spv/wgsl-quad.spvasm b/naga/tests/out/spv/wgsl-quad.spvasm index 959da0946df..307fa304926 100644 --- a/naga/tests/out/spv/wgsl-quad.spvasm +++ b/naga/tests/out/spv/wgsl-quad.spvasm @@ -10,6 +10,7 @@ OpEntryPoint Fragment %45 "frag_main" %42 %44 OpEntryPoint Fragment %61 "fs_extra" %60 OpExecutionMode %45 OriginUpperLeft OpExecutionMode %61 OriginUpperLeft +%3 = OpString "quad.wgsl" OpSource Unknown 0 %3 "// vertex const c_scale: f32 = 1.2; @@ -49,7 +50,6 @@ fn fs_extra() -> @location(0) vec4 { return vec4(0.0, 0.5, 0.0, 0.5); } " -%3 = OpString "quad.wgsl" OpMemberName %7 0 "uv" OpMemberName %7 1 "position" OpName %7 "VertexOutput" diff --git a/naga/tests/out/spv/wgsl-shadow.spvasm b/naga/tests/out/spv/wgsl-shadow.spvasm index 916fe9807a4..1360528131a 100644 --- a/naga/tests/out/spv/wgsl-shadow.spvasm +++ b/naga/tests/out/spv/wgsl-shadow.spvasm @@ -11,6 +11,7 @@ OpEntryPoint Fragment %143 "fs_main" %134 %137 %140 %142 OpEntryPoint Fragment %228 "fs_main_without_storage" %221 %223 %225 %227 OpExecutionMode %143 OriginUpperLeft OpExecutionMode %228 OriginUpperLeft +%3 = OpString "shadow.wgsl" OpSource Unknown 0 %3 "struct Globals { view_proj: mat4x4, num_lights: vec4, @@ -129,7 +130,6 @@ fn fs_main_without_storage(in: VertexOutput) -> @location(0) vec4 { return vec4(color, 1.0) * u_entity.color; } " -%3 = OpString "shadow.wgsl" OpMemberName %9 0 "view_proj" OpMemberName %9 1 "num_lights" OpName %9 "Globals" diff --git a/naga/tests/out/spv/wgsl-texture-arg.spvasm b/naga/tests/out/spv/wgsl-texture-arg.spvasm index b808340b75f..23bd0ce2977 100644 --- a/naga/tests/out/spv/wgsl-texture-arg.spvasm +++ b/naga/tests/out/spv/wgsl-texture-arg.spvasm @@ -7,6 +7,7 @@ OpCapability Shader OpMemoryModel Logical GLSL450 OpEntryPoint Fragment %29 "main" %27 OpExecutionMode %29 OriginUpperLeft +%3 = OpString "texture-arg.wgsl" OpSource Unknown 0 %3 "@group(0) @binding(0) var Texture: texture_2d; @group(0) @binding(1) @@ -21,7 +22,6 @@ fn main() -> @location(0) vec4 { return test(Texture, Sampler); } " -%3 = OpString "texture-arg.wgsl" OpName %9 "Texture" OpName %11 "Sampler" OpName %14 "Passed_Texture" diff --git a/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm b/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm index 35bcfb4a7d5..cb214f3e357 100644 --- a/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm +++ b/naga/tests/out/spv/wgsl-workgroup-var-init.spvasm @@ -8,6 +8,7 @@ OpExtension "SPV_KHR_storage_buffer_storage_class" OpMemoryModel Logical GLSL450 OpEntryPoint GLCompute %18 "main" %26 OpExecutionMode %18 LocalSize 1 1 1 +%3 = OpString "workgroup-var-init.wgsl" OpSource Unknown 0 %3 "struct WStruct { arr: array, atom: atomic, @@ -23,7 +24,6 @@ var output: array; fn main() { output = w_mem.arr; }" -%3 = OpString "workgroup-var-init.wgsl" OpMemberName %11 0 "arr" OpMemberName %11 1 "atom" OpMemberName %11 2 "atom_arr" From d20ee08a7c690526e06f2422fec549ff6900965e Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 01:29:41 -0500 Subject: [PATCH 049/110] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b9d3c7128..c546982552d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,6 +113,7 @@ SamplerDescriptor { #### Vulkan - Fixed a validation error regarding atomic memory semantics. By @atlv24 in [#8391](https://github.com/gfx-rs/wgpu/pull/8391). +- Add mesh shader writer support, allowing WGSL shaders to be used on the vulkan backend. By @inner-daemons in [#8456](https://github.com/gfx-rs/wgpu/pull/8456f). #### hal From 64798dd1466c16db8a1291d35182fd469b0a3908 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 01:30:36 -0500 Subject: [PATCH 050/110] Added changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b9d3c7128..a8d0ce39a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,7 @@ SamplerDescriptor { - Removed three features from `wgpu-hal` which did nothing useful: `"cargo-clippy"`, `"gpu-allocator"`, and `"rustc-hash"`. By @kpreid in [#8357](https://github.com/gfx-rs/wgpu/pull/8357). - `wgpu_types::PollError` now always implements the `Error` trait. By @kpreid in [#8384](https://github.com/gfx-rs/wgpu/pull/8384). - The texture subresources used by the color attachments of a render pass are no longer allowed to overlap when accessed via different texture views. By @andyleiserson in [#8402](https://github.com/gfx-rs/wgpu/pull/8402). +- Add WGSL parsing for mesh shaders. By @inner-daemons in [#8370](https://github.com/gfx-rs/wgpu/pull/8370). #### DX12 From c85ad3f3cc9c72389eb8d6b6071ced5d401425b6 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 01:42:05 -0500 Subject: [PATCH 051/110] Made tests & example use WGSL input for vulkan --- examples/features/src/mesh_shader/mod.rs | 70 ++++++------------ examples/features/src/mesh_shader/shader.wgsl | 74 +++++++++++++++++++ tests/tests/wgpu-gpu/mesh_shader/mod.rs | 73 ++++++++---------- tests/tests/wgpu-gpu/mesh_shader/shader.wgsl | 74 +++++++++++++++++++ 4 files changed, 202 insertions(+), 89 deletions(-) create mode 100644 examples/features/src/mesh_shader/shader.wgsl create mode 100644 tests/tests/wgpu-gpu/mesh_shader/shader.wgsl diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index 50ddff39a07..33ac43421c6 100644 --- a/examples/features/src/mesh_shader/mod.rs +++ b/examples/features/src/mesh_shader/mod.rs @@ -1,32 +1,9 @@ -use std::process::Stdio; - // Same as in mesh shader tests -fn compile_glsl(device: &wgpu::Device, shader_stage: &'static str) -> wgpu::ShaderModule { - let cmd = std::process::Command::new("glslc") - .args([ - &format!( - "{}/src/mesh_shader/shader.{shader_stage}", - env!("CARGO_MANIFEST_DIR") - ), - "-o", - "-", - "--target-env=vulkan1.2", - "--target-spv=spv1.4", - ]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to call glslc"); - let output = cmd.wait_with_output().expect("Error waiting for glslc"); - assert!(output.status.success()); - unsafe { - device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { - entry_point: "main".into(), - label: None, - spirv: Some(wgpu::util::make_spirv_raw(&output.stdout)), - ..Default::default() - }) - } +fn compile_wgsl(device: &wgpu::Device) -> wgpu::ShaderModule { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }) } fn compile_hlsl(device: &wgpu::Device, entry: &str, stage_str: &str) -> wgpu::ShaderModule { let out_path = format!( @@ -71,21 +48,22 @@ impl crate::framework::Example for Example { device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { - let (ts, ms, fs) = if adapter.get_info().backend == wgpu::Backend::Vulkan { - ( - compile_glsl(device, "task"), - compile_glsl(device, "mesh"), - compile_glsl(device, "frag"), - ) - } else if adapter.get_info().backend == wgpu::Backend::Dx12 { - ( - compile_hlsl(device, "Task", "as"), - compile_hlsl(device, "Mesh", "ms"), - compile_hlsl(device, "Frag", "ps"), - ) - } else { - panic!("Example can only run on vulkan or dx12"); - }; + let (ts, ms, fs, ts_name, ms_name, fs_name) = + if adapter.get_info().backend == wgpu::Backend::Vulkan { + let s = compile_wgsl(device); + (s.clone(), s.clone(), s, "ts_main", "ms_main", "fs_main") + } else if adapter.get_info().backend == wgpu::Backend::Dx12 { + ( + compile_hlsl(device, "Task", "as"), + compile_hlsl(device, "Mesh", "ms"), + compile_hlsl(device, "Frag", "ps"), + "main", + "main", + "main", + ) + } else { + panic!("Example can only run on vulkan or dx12"); + }; let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], @@ -96,17 +74,17 @@ impl crate::framework::Example for Example { layout: Some(&pipeline_layout), task: Some(wgpu::TaskState { module: &ts, - entry_point: Some("main"), + entry_point: Some(ts_name), compilation_options: Default::default(), }), mesh: wgpu::MeshState { module: &ms, - entry_point: Some("main"), + entry_point: Some(ms_name), compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &fs, - entry_point: Some("main"), + entry_point: Some(fs_name), compilation_options: Default::default(), targets: &[Some(config.view_formats[0].into())], }), diff --git a/examples/features/src/mesh_shader/shader.wgsl b/examples/features/src/mesh_shader/shader.wgsl new file mode 100644 index 00000000000..cdc7366b415 --- /dev/null +++ b/examples/features/src/mesh_shader/shader.wgsl @@ -0,0 +1,74 @@ +enable mesh_shading; + +const positions = array( + vec4(0., 1., 0., 1.), + vec4(-1., -1., 0., 1.), + vec4(1., -1., 0., 1.) +); +const colors = array( + vec4(0., 1., 0., 1.), + vec4(0., 0., 1., 1.), + vec4(1., 0., 0., 1.) +); +struct TaskPayload { + colorMask: vec4, + visible: bool, +} +var taskPayload: TaskPayload; +var workgroupData: f32; +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} +struct PrimitiveOutput { + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, +} +struct PrimitiveInput { + @per_primitive @location(1) colorMask: vec4, +} + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + workgroupData = 1.0; + taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); + taskPayload.visible = true; + return vec3(3, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { + mesh_output.vertex_count = 3; + mesh_output.primitive_count = 1; + workgroupData = 2.0; + + mesh_output.vertices[0].position = positions[0]; + mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; + + mesh_output.vertices[1].position = positions[1]; + mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; + + mesh_output.vertices[2].position = positions[2]; + mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; + + mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); +} +@fragment +fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { + return vertex.color * primitive.colorMask; +} diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 8a3218970f0..3b3019818a2 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -1,7 +1,4 @@ -use std::{ - hash::{DefaultHasher, Hash, Hasher}, - process::Stdio, -}; +use std::hash::{DefaultHasher, Hash, Hasher}; use wgpu::{util::DeviceExt, Backends}; use wgpu_test::{ @@ -28,34 +25,12 @@ pub fn all_tests(tests: &mut Vec) { } // Same as in mesh shader example -fn compile_glsl(device: &wgpu::Device, shader_stage: &'static str) -> wgpu::ShaderModule { - let cmd = std::process::Command::new("glslc") - .args([ - &format!( - "{}/tests/wgpu-gpu/mesh_shader/basic.{shader_stage}", - env!("CARGO_MANIFEST_DIR") - ), - "-o", - "-", - "--target-env=vulkan1.2", - "--target-spv=spv1.4", - ]) - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Failed to call glslc"); - let output = cmd.wait_with_output().expect("Error waiting for glslc"); - assert!(output.status.success()); - unsafe { - device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough { - entry_point: "main".into(), - label: None, - spirv: Some(wgpu::util::make_spirv_raw(&output.stdout)), - ..Default::default() - }) - } +fn compile_wgsl(device: &wgpu::Device) -> wgpu::ShaderModule { + device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), + }) } - fn compile_hlsl( device: &wgpu::Device, entry: &str, @@ -107,6 +82,9 @@ fn get_shaders( Option, wgpu::ShaderModule, Option, + &'static str, + &'static str, + &'static str, ) { // On backends that don't support mesh shaders, or for the MESH_DISABLED // test, compile a dummy shader so we can construct a structurally valid @@ -115,14 +93,18 @@ fn get_shaders( // shader is used to avoid requiring EXPERIMENTAL_PASSTHROUGH_SHADERS.) let dummy_shader = device.create_shader_module(wgpu::include_wgsl!("non_mesh.wgsl")); if backend == wgpu::Backend::Vulkan { + let s = compile_wgsl(device); ( - info.use_task.then(|| compile_glsl(device, "task")), + info.use_task.then(|| s.clone()), if info.use_mesh { - compile_glsl(device, "mesh") + s.clone() } else { dummy_shader }, - info.use_frag.then(|| compile_glsl(device, "frag")), + info.use_frag.then_some(s), + "ts_main", + "ms_main", + "fs_main", ) } else if backend == wgpu::Backend::Dx12 { ( @@ -135,11 +117,14 @@ fn get_shaders( }, info.use_frag .then(|| compile_hlsl(device, "Frag", "ps", test_name)), + "main", + "main", + "main", ) } else { assert!(!MESH_SHADER_BACKENDS.contains(Backends::from(backend))); assert!(!info.use_task && !info.use_mesh && !info.use_frag); - (None, dummy_shader, None) + (None, dummy_shader, None, "main", "main", "main") } } @@ -191,7 +176,8 @@ fn mesh_pipeline_build(ctx: &TestingContext, info: MeshPipelineTestInfo) { let (_depth_image, depth_view, depth_state) = create_depth(device); let test_hash = hash_testing_context(ctx).to_string(); - let (task, mesh, frag) = get_shaders(device, backend, &test_hash, &info); + let (task, mesh, frag, ts_name, ms_name, fs_name) = + get_shaders(device, backend, &test_hash, &info); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: None, bind_group_layouts: &[], @@ -202,17 +188,17 @@ fn mesh_pipeline_build(ctx: &TestingContext, info: MeshPipelineTestInfo) { layout: Some(&layout), task: task.as_ref().map(|task| wgpu::TaskState { module: task, - entry_point: Some("main"), + entry_point: Some(ts_name), compilation_options: Default::default(), }), mesh: wgpu::MeshState { module: &mesh, - entry_point: Some("main"), + entry_point: Some(ms_name), compilation_options: Default::default(), }, fragment: frag.as_ref().map(|frag| wgpu::FragmentState { module: frag, - entry_point: Some("main"), + entry_point: Some(fs_name), targets: &[], compilation_options: Default::default(), }), @@ -276,7 +262,8 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) { use_frag: true, draw: true, }; - let (task, mesh, frag) = get_shaders(device, backend, &test_hash, &info); + let (task, mesh, frag, ts_name, ms_name, fs_name) = + get_shaders(device, backend, &test_hash, &info); let task = task.unwrap(); let frag = frag.unwrap(); let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { @@ -289,17 +276,17 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) { layout: Some(&layout), task: Some(wgpu::TaskState { module: &task, - entry_point: Some("main"), + entry_point: Some(ts_name), compilation_options: Default::default(), }), mesh: wgpu::MeshState { module: &mesh, - entry_point: Some("main"), + entry_point: Some(ms_name), compilation_options: Default::default(), }, fragment: Some(wgpu::FragmentState { module: &frag, - entry_point: Some("main"), + entry_point: Some(fs_name), targets: &[], compilation_options: Default::default(), }), diff --git a/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl new file mode 100644 index 00000000000..cdc7366b415 --- /dev/null +++ b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl @@ -0,0 +1,74 @@ +enable mesh_shading; + +const positions = array( + vec4(0., 1., 0., 1.), + vec4(-1., -1., 0., 1.), + vec4(1., -1., 0., 1.) +); +const colors = array( + vec4(0., 1., 0., 1.), + vec4(0., 0., 1., 1.), + vec4(1., 0., 0., 1.) +); +struct TaskPayload { + colorMask: vec4, + visible: bool, +} +var taskPayload: TaskPayload; +var workgroupData: f32; +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} +struct PrimitiveOutput { + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, +} +struct PrimitiveInput { + @per_primitive @location(1) colorMask: vec4, +} + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + workgroupData = 1.0; + taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); + taskPayload.visible = true; + return vec3(3, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { + mesh_output.vertex_count = 3; + mesh_output.primitive_count = 1; + workgroupData = 2.0; + + mesh_output.vertices[0].position = positions[0]; + mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; + + mesh_output.vertices[1].position = positions[1]; + mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; + + mesh_output.vertices[2].position = positions[2]; + mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; + + mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); +} +@fragment +fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { + return vertex.color * primitive.colorMask; +} From cdcd600a94ec4f1f12a60492228193f97a276889 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 01:57:16 -0500 Subject: [PATCH 052/110] Tried to fix capabiiities stuff --- wgpu-core/src/device/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 1ce57c2648e..5fcf2b60f6a 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -514,6 +514,10 @@ pub fn create_validator( Caps::SHADER_BARYCENTRICS, features.intersects(wgt::Features::SHADER_BARYCENTRICS), ); + caps.set( + Caps::MESH_SHADER, + features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER), + ); naga::valid::Validator::new(flags, caps) } From 6e85c6d44a14cdd05b33b649b62840443b966b43 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 1 Nov 2025 02:11:36 -0500 Subject: [PATCH 053/110] Cleaned up some edge cases on mesh shader capabilities --- naga/src/valid/interface.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 4d437477ca1..acc6f304a8c 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -98,8 +98,6 @@ pub enum VaryingError { InvalidPerPrimitive, #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] MissingPerPrimitive, - #[error("The `MESH_SHADER` capability must be enabled to use per-primitive fragment inputs.")] - PerPrimitiveNotAllowed, } #[derive(Clone, Debug, thiserror::Error)] @@ -402,6 +400,24 @@ impl VaryingContext<'_> { (true, true) } }; + match built_in { + Bi::CullPrimitive + | Bi::PointIndex + | Bi::LineIndices + | Bi::TriangleIndices + | Bi::MeshTaskSize + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => { + if !self.capabilities.contains(Capabilities::MESH_SHADER) { + return Err(VaryingError::UnsupportedCapability( + Capabilities::MESH_SHADER, + )); + } + } + _ => (), + } if !visible { return Err(VaryingError::InvalidBuiltInStage(built_in)); @@ -419,7 +435,9 @@ impl VaryingContext<'_> { per_primitive, } => { if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) { - return Err(VaryingError::PerPrimitiveNotAllowed); + return Err(VaryingError::UnsupportedCapability( + Capabilities::MESH_SHADER, + )); } // Only IO-shareable types may be stored in locations. if !self.type_info[ty.index()] From 9fc6dcb242821ed26887e7017586600bf89d3e08 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 09:49:04 -0600 Subject: [PATCH 054/110] Added mesh shader example test --- examples/features/src/lib.rs | 1 + examples/features/src/mesh_shader/mod.rs | 20 ++++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/examples/features/src/lib.rs b/examples/features/src/lib.rs index baacf6a6b39..05f2db5ef21 100644 --- a/examples/features/src/lib.rs +++ b/examples/features/src/lib.rs @@ -48,6 +48,7 @@ fn all_tests() -> Vec { cube::TEST, cube::TEST_LINES, hello_synchronization::tests::SYNC, + mesh_shader::TEST, mipmap::TEST, mipmap::TEST_QUERY, msaa_line::TEST, diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index 33ac43421c6..3ae9ea8fdbf 100644 --- a/examples/features/src/mesh_shader/mod.rs +++ b/examples/features/src/mesh_shader/mod.rs @@ -156,3 +156,23 @@ impl crate::framework::Example for Example { pub fn main() { crate::framework::run::("mesh_shader"); } + +#[cfg(test)] +#[wgpu_test::gpu_test] +pub static TEST: crate::framework::ExampleTestParams = + crate::framework::ExampleTestParams { + name: "mesh_shader", + // Generated on 1080ti on Vk/Windows + image_path: "/examples/features/src/mesh_shader/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default() + .features( + wgpu::Features::EXPERIMENTAL_MESH_SHADER + | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS, + ) + .limits(wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()), + comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], + _phantom: std::marker::PhantomData::, + }; From 18564160225722588976798846281c5fa85a4293 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 10:00:58 -0600 Subject: [PATCH 055/110] Corrected bug in spirv writer --- naga/src/back/spv/writer.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index cf24b8440d8..72f314b655b 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -943,8 +943,8 @@ impl Writer { .write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; iface.varying_ids.push(v.var_id); if self.flags.contains(WriterFlags::DEBUG) { - Instruction::name(v.var_id, "naga_vertex_builtin_outputs") - .to_words(&mut self.logical_layout.debugs); + self.debugs + .push(Instruction::name(v.var_id, "naga_vertex_builtin_outputs")); } mesh_return_info.vertex_builtin_block = Some(v); } @@ -1018,8 +1018,10 @@ impl Writer { .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v.var_id); if self.flags.contains(WriterFlags::DEBUG) { - Instruction::name(v.var_id, "naga_primitive_builtin_outputs") - .to_words(&mut self.logical_layout.debugs); + self.debugs.push(Instruction::name( + v.var_id, + "naga_primitive_builtin_outputs", + )); } mesh_return_info.primitive_builtin_block = Some(v); } @@ -1083,8 +1085,10 @@ impl Writer { .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v.var_id); if self.flags.contains(WriterFlags::DEBUG) { - Instruction::name(v.var_id, "naga_primitive_indices_outputs") - .to_words(&mut self.logical_layout.debugs); + self.debugs.push(Instruction::name( + v.var_id, + "naga_primitive_indices_outputs", + )); } mesh_return_info.primitive_indices = Some(v); } From d638d42cecd5a9df72c9aea4923e6608f2bb39ab Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 10:20:53 -0600 Subject: [PATCH 056/110] FIxed MESH_DISABLED test --- tests/tests/wgpu-gpu/mesh_shader/mod.rs | 46 +++++++++++++------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 40c9b9f1c3a..d05f0732d8b 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -480,25 +480,27 @@ pub static MESH_MULTI_DRAW_INDIRECT_COUNT: GpuTestConfiguration = /// should be rejected. This should be the case on all backends, not just the /// ones where the feature could be turned on. #[gpu_test] -pub static MESH_DISABLED: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| { - fail( - &ctx.device, - || { - mesh_pipeline_build( - &ctx, - MeshPipelineTestInfo { - use_task: false, - use_mesh: false, - use_frag: false, - draw: true, - }, - ); - }, - Some(concat![ - "Features Features { ", - "features_wgpu: FeaturesWGPU(EXPERIMENTAL_MESH_SHADER), ", - "features_webgpu: FeaturesWebGPU(0x0) ", - "} are required but not enabled on the device", - ]), - ) -}); +pub static MESH_DISABLED: GpuTestConfiguration = GpuTestConfiguration::new() + .parameters(TestParameters::default().features(wgpu::Features::EXPERIMENTAL_MESH_SHADER)) + .run_sync(|ctx| { + fail( + &ctx.device, + || { + mesh_pipeline_build( + &ctx, + MeshPipelineTestInfo { + use_task: false, + use_mesh: false, + use_frag: false, + draw: true, + }, + ); + }, + Some(concat![ + "Features Features { ", + "features_wgpu: FeaturesWGPU(EXPERIMENTAL_MESH_SHADER), ", + "features_webgpu: FeaturesWebGPU(0x0) ", + "} are required but not enabled on the device", + ]), + ) + }); From 62e456cbca9713142aca00a46c2d90a0f3570805 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 10:51:22 -0600 Subject: [PATCH 057/110] Removed mesh disabled test cus its stupid --- tests/tests/wgpu-gpu/mesh_shader/mod.rs | 62 +++---------------------- 1 file changed, 6 insertions(+), 56 deletions(-) diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index d05f0732d8b..94033850b3d 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -2,8 +2,7 @@ use std::hash::{DefaultHasher, Hash, Hasher}; use wgpu::{util::DeviceExt, Backends}; use wgpu_test::{ - fail, gpu_test, FailureCase, GpuTestConfiguration, GpuTestInitializer, TestParameters, - TestingContext, + gpu_test, FailureCase, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, }; /// Backends that support mesh shaders @@ -20,7 +19,6 @@ pub fn all_tests(tests: &mut Vec) { MESH_MULTI_DRAW_INDIRECT_COUNT, MESH_PIPELINE_BASIC_MESH_NO_DRAW, MESH_PIPELINE_BASIC_TASK_MESH_FRAG_NO_DRAW, - MESH_DISABLED, ]); } @@ -86,21 +84,14 @@ fn get_shaders( &'static str, &'static str, ) { - // On backends that don't support mesh shaders, or for the MESH_DISABLED - // test, compile a dummy shader so we can construct a structurally valid - // pipeline description and test that `create_mesh_pipeline` fails. - // (In the case that the platform does support mesh shaders, the dummy - // shader is used to avoid requiring EXPERIMENTAL_PASSTHROUGH_SHADERS.) + // In the case that the platform does support mesh shaders, the dummy + // shader is used to avoid requiring EXPERIMENTAL_PASSTHROUGH_SHADERS. let dummy_shader = device.create_shader_module(wgpu::include_wgsl!("non_mesh.wgsl")); if backend == wgpu::Backend::Vulkan { let s = compile_wgsl(device); ( info.use_task.then(|| s.clone()), - if info.use_mesh { - s.clone() - } else { - dummy_shader - }, + s.clone(), info.use_frag.then_some(s), "ts_main", "ms_main", @@ -110,11 +101,7 @@ fn get_shaders( ( info.use_task .then(|| compile_hlsl(device, "Task", "as", test_name)), - if info.use_mesh { - compile_hlsl(device, "Mesh", "ms", test_name) - } else { - dummy_shader - }, + compile_hlsl(device, "Mesh", "ms", test_name), info.use_frag .then(|| compile_hlsl(device, "Frag", "ps", test_name)), "main", @@ -123,7 +110,7 @@ fn get_shaders( ) } else { assert!(!MESH_SHADER_BACKENDS.contains(Backends::from(backend))); - assert!(!info.use_task && !info.use_mesh && !info.use_frag); + assert!(!info.use_task && !info.use_frag); (None, dummy_shader, None, "main", "main", "main") } } @@ -159,7 +146,6 @@ fn create_depth( struct MeshPipelineTestInfo { use_task: bool, - use_mesh: bool, use_frag: bool, draw: bool, } @@ -259,7 +245,6 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) { let test_hash = hash_testing_context(ctx).to_string(); let info = MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: true, draw: true, }; @@ -387,7 +372,6 @@ pub static MESH_PIPELINE_BASIC_MESH: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: false, draw: true, }, @@ -400,7 +384,6 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: false, draw: true, }, @@ -413,7 +396,6 @@ pub static MESH_PIPELINE_BASIC_MESH_FRAG: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: true, draw: true, }, @@ -426,7 +408,6 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH_FRAG: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: true, draw: true, }, @@ -439,7 +420,6 @@ pub static MESH_PIPELINE_BASIC_MESH_NO_DRAW: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: false, draw: false, }, @@ -452,7 +432,6 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH_FRAG_NO_DRAW: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: true, draw: false, }, @@ -475,32 +454,3 @@ pub static MESH_MULTI_DRAW_INDIRECT_COUNT: GpuTestConfiguration = default_gpu_test_config(DrawType::MultiIndirectCount).run_sync(|ctx| { mesh_draw(&ctx, DrawType::MultiIndirectCount); }); - -/// When the mesh shading feature is disabled, calls to `create_mesh_pipeline` -/// should be rejected. This should be the case on all backends, not just the -/// ones where the feature could be turned on. -#[gpu_test] -pub static MESH_DISABLED: GpuTestConfiguration = GpuTestConfiguration::new() - .parameters(TestParameters::default().features(wgpu::Features::EXPERIMENTAL_MESH_SHADER)) - .run_sync(|ctx| { - fail( - &ctx.device, - || { - mesh_pipeline_build( - &ctx, - MeshPipelineTestInfo { - use_task: false, - use_mesh: false, - use_frag: false, - draw: true, - }, - ); - }, - Some(concat![ - "Features Features { ", - "features_wgpu: FeaturesWGPU(EXPERIMENTAL_MESH_SHADER), ", - "features_webgpu: FeaturesWebGPU(0x0) ", - "} are required but not enabled on the device", - ]), - ) - }); From 50ca3826b123ff22446c87235bcdd7203357ae61 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 11:15:40 -0600 Subject: [PATCH 058/110] Updated shader to try to make it pass tests --- tests/tests/wgpu-gpu/mesh_shader/mod.rs | 2 +- tests/tests/wgpu-gpu/mesh_shader/shader.wgsl | 21 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 94033850b3d..a648b73834a 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -94,7 +94,7 @@ fn get_shaders( s.clone(), info.use_frag.then_some(s), "ts_main", - "ms_main", + if info.use_task { "ms_main" } else { "ms_no_ts" }, "fs_main", ) } else if backend == wgpu::Backend::Dx12 { diff --git a/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl index cdc7366b415..a2be409f5df 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl +++ b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl @@ -68,6 +68,27 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } +// Don't use task payload if no task shader is present +@mesh(mesh_output) +@workgroup_size(1) +fn ms_no_ts(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { + mesh_output.vertex_count = 3; + mesh_output.primitive_count = 1; + workgroupData = 2.0; + + mesh_output.vertices[0].position = positions[0]; + mesh_output.vertices[0].color = colors[0]; + + mesh_output.vertices[1].position = positions[1]; + mesh_output.vertices[1].color = colors[1]; + + mesh_output.vertices[2].position = positions[2]; + mesh_output.vertices[2].color = colors[2]; + + mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].cull = false; + mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); +} @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; From bd923cdc271aa862f4899d4199e8a407c2295c78 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 12:50:38 -0600 Subject: [PATCH 059/110] Made parser respect enable extension --- naga/src/front/wgsl/error.rs | 1 - naga/src/front/wgsl/parse/conv.rs | 34 +++++++++++++++++++++++--- naga/src/front/wgsl/parse/mod.rs | 40 +++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/naga/src/front/wgsl/error.rs b/naga/src/front/wgsl/error.rs index a8958525ad1..0cd7e11c737 100644 --- a/naga/src/front/wgsl/error.rs +++ b/naga/src/front/wgsl/error.rs @@ -1375,7 +1375,6 @@ impl<'a> Error<'a> { }, Error::ExpectedGlobalVariable { name_span } => ParseError { message: "expected global variable".to_string(), - // TODO: I would like to also include the global declaration span labels: vec![(name_span, "variable used here".into())], notes: vec![], }, diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 16e814f56f5..0303b7ed6bb 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -6,7 +6,11 @@ use crate::Span; use alloc::boxed::Box; -pub fn map_address_space(word: &str, span: Span) -> Result<'_, crate::AddressSpace> { +pub fn map_address_space<'a>( + word: &str, + span: Span, + enable_extensions: &EnableExtensions, +) -> Result<'a, crate::AddressSpace> { match word { "private" => Ok(crate::AddressSpace::Private), "workgroup" => Ok(crate::AddressSpace::WorkGroup), @@ -16,7 +20,16 @@ pub fn map_address_space(word: &str, span: Span) -> Result<'_, crate::AddressSpa }), "push_constant" => Ok(crate::AddressSpace::PushConstant), "function" => Ok(crate::AddressSpace::Function), - "task_payload" => Ok(crate::AddressSpace::TaskPayload), + "task_payload" => { + if enable_extensions.contains(ImplementedEnableExtension::MeshShader) { + Ok(crate::AddressSpace::TaskPayload) + } else { + Err(Box::new(Error::EnableExtensionNotEnabled { + span, + kind: ImplementedEnableExtension::MeshShader.into(), + })) + } + } _ => Err(Box::new(Error::UnknownAddressSpace(span))), } } @@ -53,7 +66,7 @@ pub fn map_built_in( "subgroup_invocation_id" => crate::BuiltIn::SubgroupInvocationId, // mesh "cull_primitive" => crate::BuiltIn::CullPrimitive, - "vertex_indices" => crate::BuiltIn::PointIndex, + "point_index" => crate::BuiltIn::PointIndex, "line_indices" => crate::BuiltIn::LineIndices, "triangle_indices" => crate::BuiltIn::TriangleIndices, "mesh_task_size" => crate::BuiltIn::MeshTaskSize, @@ -73,6 +86,21 @@ pub fn map_built_in( })); } } + crate::BuiltIn::CullPrimitive + | crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + | crate::BuiltIn::VertexCount + | crate::BuiltIn::Vertices + | crate::BuiltIn::PrimitiveCount + | crate::BuiltIn::Primitives => { + if !enable_extensions.contains(ImplementedEnableExtension::MeshShader) { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span, + kind: ImplementedEnableExtension::MeshShader.into(), + })); + } + } _ => {} } Ok(built_in) diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index 94df933a6a9..e4c04644347 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -240,6 +240,15 @@ impl<'a> BindingParser<'a> { lexer.expect(Token::Paren(')'))?; } "per_primitive" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::MeshShader) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span: name_span, + kind: ImplementedEnableExtension::MeshShader.into(), + })); + } self.per_primitive.set((), name_span)?; } _ => return Err(Box::new(Error::UnknownAttribute(name_span))), @@ -1324,7 +1333,7 @@ impl Parser { }; crate::AddressSpace::Storage { access } } - _ => conv::map_address_space(class_str, span)?, + _ => conv::map_address_space(class_str, span, &lexer.enable_extensions)?, }; lexer.expect(Token::Paren('>'))?; } @@ -1697,7 +1706,7 @@ impl Parser { "ptr" => { lexer.expect_generic_paren('<')?; let (ident, span) = lexer.next_ident_with_span()?; - let mut space = conv::map_address_space(ident, span)?; + let mut space = conv::map_address_space(ident, span, &lexer.enable_extensions)?; lexer.expect(Token::Separator(','))?; let base = self.type_decl(lexer, ctx)?; if let crate::AddressSpace::Storage { ref mut access } = space { @@ -2865,10 +2874,28 @@ impl Parser { compute_like_span = name_span; } "task" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::MeshShader) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span: name_span, + kind: ImplementedEnableExtension::MeshShader.into(), + })); + } stage.set(ShaderStage::Task, name_span)?; compute_like_span = name_span; } "mesh" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::MeshShader) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span: name_span, + kind: ImplementedEnableExtension::MeshShader.into(), + })); + } stage.set(ShaderStage::Mesh, name_span)?; compute_like_span = name_span; @@ -2877,6 +2904,15 @@ impl Parser { lexer.expect(Token::Paren(')'))?; } "payload" => { + if !lexer + .enable_extensions + .contains(ImplementedEnableExtension::MeshShader) + { + return Err(Box::new(Error::EnableExtensionNotEnabled { + span: name_span, + kind: ImplementedEnableExtension::MeshShader.into(), + })); + } lexer.expect(Token::Paren('('))?; payload.set(lexer.next_ident_with_span()?, name_span)?; lexer.expect(Token::Paren(')'))?; From d95070aeb7473cd90ba5c26f0ea7f3482dc87fd7 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 13:41:59 -0600 Subject: [PATCH 060/110] Updated mesh shader spec --- docs/api-specs/mesh_shading.md | 117 +++++++++++++++++---------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 4b28ec635e7..41720765a55 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -103,10 +103,8 @@ An example of using mesh shaders to render a single triangle can be seen [here]( * DirectX 12 support is planned. * Metal support is desired but not currently planned. - ## Naga implementation - ### Supported frontends * 🛠️ WGSL * ❌ SPIR-V @@ -114,7 +112,7 @@ An example of using mesh shaders to render a single triangle can be seen [here]( ### Supported backends * 🛠️ SPIR-V -* ❌ HLSL +* 🛠️ HLSL * ❌ MSL * 🚫 GLSL * 🚫 WGSL @@ -130,7 +128,7 @@ The majority of changes relating to mesh shaders will be in WGSL and `naga`. Using any of these features in a `wgsl` program will require adding the `enable mesh_shading` directive to the top of a program. -Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-specific functionality, such as subgroup operations. +Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-available functionality, including subgroup operations. ### Task shader @@ -145,6 +143,8 @@ A task shader entry point must return a `vec3` value. The return value of e Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid; and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload. +Task shaders can use compute and subgroup builtin inputs, in addition to `view_index` and `draw_id`. + ### Mesh shader A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh shaders must not return anything. @@ -159,17 +159,19 @@ A mesh shader entry point must have the following attributes: - `@workgroup_size`: this has the same meaning as when it appears on a compute shader entry point. -- `@vertex_output(V, NV)`: This indicates that the mesh shader workgroup will generate at most `NV` vertex values, each of type `V`. +- `@mesh(VAR)`: Here, `VAR` represents a workgroup variable storing the output information. -- `@primitive_output(P, NP)`: This indicates that the mesh shader workgroup will generate at most `NP` primitives, each of type `P`. +All mesh shader outputs are per-workgroup, and taken from the workgroup variable specified above. The type must have exactly 4 fields: +- A field decorated with `@builtin(vertex_count)`, with type `u32`: this field represents the number of vertices that will be drawn +- A field decorated with `@builtin(primitive_count)`, with type `u32`: this field represents the number of primitives that will be drawn +- A field decorated with `@builtin(vertices)`, typed as an array of `V`, where `V` is the vertex output type as specified below +- A field decorated with `@builtin(primitives)`, typed as an array of `P`, where `P` is the primitive output type as specified below -Each mesh shader entry point invocation must call the `setMeshOutputs(numVertices: u32, numPrimitives: u32)` builtin function at least once. The values passed by each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) determine how many vertices (values of type `V`) and primitives (values of type `P`) the workgroup must produce. The user can still write past these indices, but they won't be used in the output. +For a vertex count `NV`, the first `NV` elements of the vertex array above are outputted. Therefore, `NV` must be less than or equal to the size of the vertex array. The same is true for primitives with `NP`. -The `numVertices` and `numPrimitives` arguments must be no greater than `NV` and `NP` from the `@vertex_output` and `@primitive_output` attributes. +The vertex output type `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on. -To produce vertex data, the workgroup as a whole must make `numVertices` calls to the `setVertex(i: u32, vertex: V)` builtin function. This establishes `vertex` as the value of the `i`'th vertex, where `i` is less than the maximum number of output vertices in the `@vertex_output` attribute. `V` is the type given in the `@vertex_output` attribute. `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on. - -To produce primitives, the workgroup as a whole must make `numPrimitives` calls to the `setPrimitive(i: u32, primitive: P)` builtin function. This establishes `primitive` as the value of the `i`'th primitive, where `i` is less than the maximum number of output primitives in the `@primitive_output` attribute. `P` is the type given in the `@primitive_output` attribute. `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. The following `@builtin` attributes are allowed: +The primitive output type `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. All members decorated with `@location` must also be decorated with `@per_primitive`, as must the corresponding fragment input. The `@per_primitive` decoration may only be applied to members decorated with `@location`. The following `@builtin` attributes are allowed: - `triangle_indices`, `line_indices`, or `point_index`: The annotated member must be of type `vec3`, `vec2`, or `u32`. @@ -179,15 +181,13 @@ To produce primitives, the workgroup as a whole must make `numPrimitives` calls - `cull_primitive`: The annotated member must be of type `bool`. If it is true, then the primitive is skipped during rendering. -Every member of `P` with a `@location` attribute must either have a `@per_primitive` attribute, or be part of a struct type that appears in the primitive data as a struct member with the `@per_primitive` attribute. - The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader. -It is possible to write to the same vertex or primitive index repeatedly. Since the implicit arrays written by `setVertex` and `setPrimitive` are shared by the workgroup, data races on writes to the same index for a given type are undefined behavior. +Mesh shaders can use compute and mesh shader builtin inputs, in addition to `view_index`, and if no task shader is present, `draw_id`. ### Fragment shader -Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` attribute can be applied to a value directly, such as `@per_primitive @location(1) value: vec4`, to a struct such as `@per_primitive primitive_input: PrimitiveInput` where `PrimitiveInput` is a struct containing fields decorated with `@location` and `@builtin`, or to members of a struct that are themselves decorated with `@location` or `@builtin`. +Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` decoration may only be applied to inputs or struct members decorated with `@location`. The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. Using `@per_primitive` also requires enabling the mesh shader extension. Additionally, the locations of vertex and primitive input cannot overlap. @@ -199,72 +199,75 @@ The following is a full example of WGSL shaders that could be used to create a m enable mesh_shading; const positions = array( - vec4(0.,1.,0.,1.), - vec4(-1.,-1.,0.,1.), - vec4(1.,-1.,0.,1.) + vec4(0., 1., 0., 1.), + vec4(-1., -1., 0., 1.), + vec4(1., -1., 0., 1.) ); const colors = array( - vec4(0.,1.,0.,1.), - vec4(0.,0.,1.,1.), - vec4(1.,0.,0.,1.) + vec4(0., 1., 0., 1.), + vec4(0., 0., 1., 1.), + vec4(1., 0., 0., 1.) ); struct TaskPayload { - colorMask: vec4, - visible: bool, + colorMask: vec4, + visible: bool, } var taskPayload: TaskPayload; var workgroupData: f32; struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, + @builtin(position) position: vec4, + @location(0) color: vec4, } struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, - @builtin(cull_primitive) cull: bool, - @per_primitive @location(1) colorMask: vec4, + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { - @per_primitive @location(1) colorMask: vec4, + @per_primitive @location(1) colorMask: vec4, } @task @payload(taskPayload) @workgroup_size(1) fn ts_main() -> @builtin(mesh_task_size) vec3 { - workgroupData = 1.0; - taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); - taskPayload.visible = true; - return vec3(3, 1, 1); + workgroupData = 1.0; + taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); + taskPayload.visible = true; + return vec3(3, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, } -@mesh + +var mesh_output: MeshOutput; +@mesh(mesh_output) @payload(taskPayload) -@vertex_output(VertexOutput, 3) @primitive_output(PrimitiveOutput, 1) @workgroup_size(1) fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { - setMeshOutputs(3, 1); - workgroupData = 2.0; - var v: VertexOutput; - - v.position = positions[0]; - v.color = colors[0] * taskPayload.colorMask; - setVertex(0, v); - - v.position = positions[1]; - v.color = colors[1] * taskPayload.colorMask; - setVertex(1, v); - - v.position = positions[2]; - v.color = colors[2] * taskPayload.colorMask; - setVertex(2, v); - - var p: PrimitiveOutput; - p.index = vec3(0, 1, 2); - p.cull = !taskPayload.visible; - p.colorMask = vec4(1.0, 0.0, 1.0, 1.0); - setPrimitive(0, p); + mesh_output.vertex_count = 3; + mesh_output.primitive_count = 1; + workgroupData = 2.0; + + mesh_output.vertices[0].position = positions[0]; + mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask; + + mesh_output.vertices[1].position = positions[1]; + mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask; + + mesh_output.vertices[2].position = positions[2]; + mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; + + mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { - return vertex.color * primitive.colorMask; + return vertex.color * primitive.colorMask; } ``` From ace7e17f7f8b83d96e7b6f0cfc9012f1aa514a42 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 3 Nov 2025 14:50:00 -0600 Subject: [PATCH 061/110] Cleaned up the mesh shader analyzer function --- naga/src/proc/mod.rs | 55 ++++++++++++++++++++++++++++--------- naga/src/valid/interface.rs | 4 +-- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/naga/src/proc/mod.rs b/naga/src/proc/mod.rs index 4271db391c5..64da0a9661e 100644 --- a/naga/src/proc/mod.rs +++ b/naga/src/proc/mod.rs @@ -660,6 +660,12 @@ impl crate::Module { /// Extracts mesh shader info from a mesh output global variable. Used in frontends /// and by validators. This only validates the output variable itself, and not the /// vertex and primitive output types. + /// + /// The output contains the extracted mesh stage info, with overrides unset, + /// and then the overrides separately. This is because the overrides should be + /// treated as expressions elsewhere, but that requires mutably modifying the + /// module and the expressions should only be created at parse time, not validation + /// time. #[allow(clippy::type_complexity)] pub fn analyze_mesh_shader_info( &self, @@ -671,6 +677,19 @@ impl crate::Module { ) { use crate::span::AddSpan; use crate::valid::EntryPointError; + #[derive(Default)] + struct OutError { + pub inner: Option, + } + impl OutError { + pub fn set(&mut self, err: EntryPointError) { + if self.inner.is_none() { + self.inner = Some(err); + } + } + } + + // Used to temporarily initialize stuff let null_type = crate::Handle::new(NonMaxU32::new(0).unwrap()); let mut output = crate::MeshStageInfo { topology: crate::MeshOutputTopology::Triangles, @@ -682,7 +701,8 @@ impl crate::Module { primitive_output_type: null_type, output_variable: gv, }; - let mut error = None; + // Stores the error to output, if any. + let mut error = OutError::default(); let r#type = &self.types[self.global_variables[gv].ty].inner; let mut topology = output.topology; @@ -696,20 +716,24 @@ impl crate::Module { for member in members { match member.binding { Some(crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) => { + // Must have type u32 if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { - error = Some(EntryPointError::BadMeshOutputVariableField); + error.set(EntryPointError::BadMeshOutputVariableField); } + // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::VertexCount) { - error = Some(EntryPointError::BadMeshOutputVariableType); + error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::VertexCount); } Some(crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) => { + // Must have type u32 if self.types[member.ty].inner.scalar() != Some(crate::Scalar::U32) { - error = Some(EntryPointError::BadMeshOutputVariableField); + error.set(EntryPointError::BadMeshOutputVariableField); } + // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::PrimitiveCount) { - error = Some(EntryPointError::BadMeshOutputVariableType); + error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::PrimitiveCount); } @@ -717,6 +741,7 @@ impl crate::Module { crate::BuiltIn::Vertices | crate::BuiltIn::Primitives, )) => { let ty = &self.types[member.ty].inner; + // Analyze the array type to determine size and vertex/primitive type let (a, b, c) = match ty { &crate::TypeInner::Array { base, size, .. } => { let ty = base; @@ -724,15 +749,14 @@ impl crate::Module { crate::ArraySize::Constant(a) => (a.get(), None), crate::ArraySize::Pending(o) => (0, Some(o)), crate::ArraySize::Dynamic => { - error = - Some(EntryPointError::BadMeshOutputVariableField); + error.set(EntryPointError::BadMeshOutputVariableField); (0, None) } }; (max, max_override, ty) } _ => { - error = Some(EntryPointError::BadMeshOutputVariableField); + error.set(EntryPointError::BadMeshOutputVariableField); (0, None, null_type) } }; @@ -740,6 +764,7 @@ impl crate::Module { member.binding, Some(crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) ) { + // Primitives require special analysis to determine topology primitive_info = (a, b, c); match self.types[c].inner { crate::TypeInner::Struct { ref members, .. } => { @@ -766,19 +791,21 @@ impl crate::Module { } _ => (), } + // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::Primitives) { - error = Some(EntryPointError::BadMeshOutputVariableType); + error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Primitives); } else { vertex_info = (a, b, c); + // Each builtin should only occur once if builtins.contains(&crate::BuiltIn::Vertices) { - error = Some(EntryPointError::BadMeshOutputVariableType); + error.set(EntryPointError::BadMeshOutputVariableType); } builtins.insert(crate::BuiltIn::Vertices); } } - _ => error = Some(EntryPointError::BadMeshOutputVariableType), + _ => error.set(EntryPointError::BadMeshOutputVariableType), } } output = crate::MeshStageInfo { @@ -792,12 +819,14 @@ impl crate::Module { ..output } } - _ => error = Some(EntryPointError::BadMeshOutputVariableType), + _ => error.set(EntryPointError::BadMeshOutputVariableType), } ( output, [vertex_info.1, primitive_info.1], - error.map(|a| a.with_span_handle(self.global_variables[gv].ty, &self.types)), + error + .inner + .map(|a| a.with_span_handle(self.global_variables[gv].ty, &self.types)), ) } } diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 6c297112fc5..449ae5b163a 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -397,9 +397,9 @@ impl VaryingContext<'_> { scalar: crate::Scalar::U32, }, ), - // Validated elsewhere + // Validated elsewhere, shouldn't be here Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives => { - (true, true) + (false, true) } }; From 3b2bd30611d17c1c53a869ab0de4220bb8444817 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 5 Nov 2025 22:27:43 -0600 Subject: [PATCH 062/110] Slight refactor & changed stuff to use "Word" --- naga/src/back/spv/block.rs | 10 ++++---- naga/src/back/spv/mod.rs | 43 +++++++++++++++---------------- naga/src/back/spv/writer.rs | 51 +++++++++++++------------------------ 3 files changed, 44 insertions(+), 60 deletions(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 06a0b2dd7f8..a20a3b022f1 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -441,7 +441,7 @@ impl Writer { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, - return_info.vertex_builtin_block.as_ref().unwrap().var_id, + return_info.vertex_builtin_block.unwrap(), &[ val_i, self.get_constant_scalar(crate::Literal::U32(builtin_index)), @@ -455,7 +455,7 @@ impl Writer { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, - return_info.vertex_bindings[binding_index].var_id, + return_info.vertex_bindings[binding_index], &[val_i, zero_u32], )); binding_index += 1; @@ -536,7 +536,7 @@ impl Writer { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, - return_info.primitive_indices.as_ref().unwrap().var_id, + return_info.primitive_indices.unwrap(), &[val_i], )); } @@ -544,7 +544,7 @@ impl Writer { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, - return_info.primitive_builtin_block.as_ref().unwrap().var_id, + return_info.primitive_builtin_block.unwrap(), &[ val_i, self.get_constant_scalar(crate::Literal::U32(builtin_index)), @@ -556,7 +556,7 @@ impl Writer { body.push(Instruction::access_chain( self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), ptr_to_copy_to, - return_info.primitive_bindings[binding_index].var_id, + return_info.primitive_bindings[binding_index], &[val_i, zero_u32], )); binding_index += 1; diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index 4bd3150addd..e804e939a3a 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -144,40 +144,39 @@ struct ResultMember { built_in: Option, } -struct MeshReturnGlobalVariable { - _inner_ty: u32, - var_id: u32, -} - #[derive(Clone)] struct MeshReturnMember { pub ty_id: u32, pub binding: crate::Binding, } struct MeshReturnInfo { - out_variable_id: u32, - out_type_id: u32, + /// Id of the workgroup variable containing the data to be output + out_variable_id: Word, + /// ID of the type of the workgroup variable + out_type_id: Word, + /// All members of the output variable struct type out_members: Vec, - vertex_type_id: u32, - vertex_array_type_id: u32, + vertex_type_id: Word, + vertex_array_type_id: Word, vertex_members: Vec, - primitive_type_id: u32, - primitive_array_type_id: u32, + primitive_type_id: Word, + primitive_array_type_id: Word, primitive_members: Vec, - // In vulkan, all builtins must be in the same block. - // All bindings must be in their own unique block. - // Also, the primitive indices builtin family needs its own block. - // Also also, cull primitive doesn't care about having its own block. - vertex_builtin_block: Option, - vertex_bindings: Vec, - primitive_builtin_block: Option, - primitive_bindings: Vec, - primitive_indices: Option, - local_invocation_index_id: u32, + // * In vulkan, all builtins must be in the same block. + // * All bindings must be in their own unique block. + // * Also, the primitive indices builtin family needs its own block. + // * Also also, cull primitive doesn't care about having its own block, but + // some older validation layers didn't respect this. + vertex_builtin_block: Option, + vertex_bindings: Vec, + primitive_builtin_block: Option, + primitive_bindings: Vec, + primitive_indices: Option, + local_invocation_index_id: Word, workgroup_size: u32, /// The id of a function variable in the entry point for a u32 - function_variable: u32, + function_variable: Word, } struct EntryPointContext { diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 72f314b655b..6ecf0bf42a3 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -735,7 +735,7 @@ impl Writer { &mut self, ty: u32, array_size_id: u32, - ) -> Result { + ) -> Result { let array_ty = self.id_gen.next(); Instruction::type_array(array_ty, ty, array_size_id) .to_words(&mut self.logical_layout.declarations); @@ -743,10 +743,7 @@ impl Writer { let var_id = self.id_gen.next(); Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) .to_words(&mut self.logical_layout.declarations); - Ok(super::MeshReturnGlobalVariable { - _inner_ty: ty, - var_id, - }) + Ok(var_id) } /// This does various setup things to allow mesh shader entry points @@ -941,10 +938,10 @@ impl Writer { } let v = self .write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; - iface.varying_ids.push(v.var_id); + iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { self.debugs - .push(Instruction::name(v.var_id, "naga_vertex_builtin_outputs")); + .push(Instruction::name(v, "naga_vertex_builtin_outputs")); } mesh_return_info.vertex_builtin_block = Some(v); } @@ -1014,14 +1011,12 @@ impl Writer { } let v = self .write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; - Instruction::decorate(v.var_id, spirv::Decoration::PerPrimitiveEXT, &[]) + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v.var_id); + iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { - self.debugs.push(Instruction::name( - v.var_id, - "naga_primitive_builtin_outputs", - )); + self.debugs + .push(Instruction::name(v, "naga_primitive_builtin_outputs")); } mesh_return_info.primitive_builtin_block = Some(v); } @@ -1043,7 +1038,7 @@ impl Writer { .to_words(&mut self.logical_layout.annotations); let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; - iface.varying_ids.push(v.var_id); + iface.varying_ids.push(v); mesh_return_info.vertex_bindings.push(v); } crate::Binding::BuiltIn(_) => (), @@ -1060,14 +1055,10 @@ impl Writer { member.ty_id, prim_array_size_id, )?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); Instruction::decorate( - v.var_id, - spirv::Decoration::PerPrimitiveEXT, - &[], - ) - .to_words(&mut self.logical_layout.annotations); - Instruction::decorate( - v.var_id, + v, spirv::Decoration::BuiltIn, &[match member.binding.to_built_in().unwrap() { crate::BuiltIn::PointIndex => { @@ -1083,12 +1074,10 @@ impl Writer { } as Word], ) .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v.var_id); + iface.varying_ids.push(v); if self.flags.contains(WriterFlags::DEBUG) { - self.debugs.push(Instruction::name( - v.var_id, - "naga_primitive_indices_outputs", - )); + self.debugs + .push(Instruction::name(v, "naga_primitive_indices_outputs")); } mesh_return_info.primitive_indices = Some(v); } @@ -1107,13 +1096,9 @@ impl Writer { .to_words(&mut self.logical_layout.annotations); let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; - Instruction::decorate( - v.var_id, - spirv::Decoration::PerPrimitiveEXT, - &[], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v.var_id); + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); mesh_return_info.primitive_bindings.push(v); } crate::Binding::BuiltIn(_) => (), From a061ee7c586c3944e91d020857a462379aaba0ec Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 5 Nov 2025 22:50:02 -0600 Subject: [PATCH 063/110] Made mesh shader builtins require mesh shader capability --- naga/src/back/spv/writer.rs | 8 +++++ out.wgsl | 66 +++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 out.wgsl diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 6ecf0bf42a3..8f9c6922c64 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -2625,6 +2625,14 @@ impl Writer { crate::Binding::BuiltIn(built_in) => { use crate::BuiltIn as Bi; let mut others = ArrayVec::new(); + + if matches!( + built_in, + Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices + ) { + self.require_mesh_shaders()?; + } + let built_in = match built_in { Bi::Position { invariant } => { if invariant { diff --git a/out.wgsl b/out.wgsl new file mode 100644 index 00000000000..3736edb1b9f --- /dev/null +++ b/out.wgsl @@ -0,0 +1,66 @@ +enable mesh_shading; + +struct TaskPayload { + colorMask: vec4, + visible: bool, +} + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} + +struct PrimitiveOutput { + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, +} + +struct PrimitiveInput { + @per_primitive @location(1) colorMask: vec4, +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var taskPayload: TaskPayload; +var workgroupData: f32; +var mesh_output: MeshOutput; + +@task @workgroup_size(1, 1, 1) @payload(taskPayload) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + workgroupData = 1f; + taskPayload.colorMask = vec4(1f, 1f, 0f, 1f); + taskPayload.visible = true; + return vec3(3u, 1u, 1u); +} + +@mesh(mesh_output) @payload(taskPayload) @workgroup_size(1, 1, 1) +fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { + mesh_output.vertex_count = 3u; + mesh_output.primitive_count = 1u; + workgroupData = 2f; + mesh_output.vertices[0].position = vec4(0f, 1f, 0f, 1f); + let _e25 = taskPayload.colorMask; + mesh_output.vertices[0].color = (vec4(0f, 1f, 0f, 1f) * _e25); + mesh_output.vertices[1].position = vec4(-1f, -1f, 0f, 1f); + let _e47 = taskPayload.colorMask; + mesh_output.vertices[1].color = (vec4(0f, 0f, 1f, 1f) * _e47); + mesh_output.vertices[2].position = vec4(1f, -1f, 0f, 1f); + let _e69 = taskPayload.colorMask; + mesh_output.vertices[2].color = (vec4(1f, 0f, 0f, 1f) * _e69); + mesh_output.primitives[0].index = vec3(0u, 1u, 2u); + let _e90 = taskPayload.visible; + mesh_output.primitives[0].cull = !(_e90); + mesh_output.primitives[0].colorMask = vec4(1f, 0f, 1f, 1f); + return; +} + +@fragment +fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { + return (vertex.color * primitive.colorMask); +} From a5d28e6244de7017e16b24eb4c6c45f403b0e92a Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 17:39:50 -0600 Subject: [PATCH 064/110] Improved some stuff --- naga/src/back/spv/block.rs | 54 ++-- naga/src/back/spv/helpers.rs | 2 +- naga/src/back/spv/mod.rs | 4 +- naga/src/back/spv/writer.rs | 10 +- naga/src/valid/interface.rs | 1 + naga/tests/in/wgsl/mesh-shader.wgsl | 6 + .../out/analysis/wgsl-mesh-shader.info.ron | 21 +- .../tests/out/ir/wgsl-mesh-shader.compact.ron | 32 ++ naga/tests/out/ir/wgsl-mesh-shader.ron | 32 ++ naga/tests/out/spv/wgsl-mesh-shader.spvasm | 291 +++++++++++++----- out.wgsl | 66 ---- 11 files changed, 337 insertions(+), 182 deletions(-) delete mode 100644 out.wgsl diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index a20a3b022f1..0a82307960c 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -261,6 +261,9 @@ impl Writer { // output variables if there are any) for (index, res_member) in result_members.iter().enumerate() { if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + // If its a function like `fn a() -> @builtin(...) vec3 ...` + // then just use the output value. If it's a struct, extract the + // value from the struct. let member_value_id = match ir_result.binding { Some(_) => value_id, None => { @@ -275,6 +278,7 @@ impl Writer { } }; + // Extract the vec3 into 3 u32's let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; for (i, &value) in values.iter().enumerate() { let instruction = Instruction::composite_extract( @@ -289,6 +293,7 @@ impl Writer { for id in values { instruction.add_operand(id); } + // We have to include the task payload in our call if let Some(task_payload) = task_payload { instruction.add_operand(task_payload); } @@ -305,36 +310,37 @@ impl Writer { return_info: &super::MeshReturnInfo, body: &mut Vec, ) -> Result<(), Error> { - let output_value_id = self.id_gen.next(); - body.push(Instruction::load( - return_info.out_type_id, - output_value_id, - return_info.out_variable_id, - None, - )); + // This is the actual value (not pointer) + // of the data to be outputted + let out_var_id = return_info.out_variable_id; // Load the actual vertex and primitive counts - let vert_count_id = self.id_gen.next(); - body.push(Instruction::composite_extract( - self.get_u32_type_id(), - vert_count_id, - output_value_id, - &[return_info + let mut load_u32_by_member_index = |member_index: u32| { + let ptr_id = self.id_gen.next(); + let u32_id = self.get_u32_type_id(); + body.push(Instruction::access_chain( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), + ptr_id, + out_var_id, + &[self.get_constant_scalar(crate::Literal::U32(member_index))], + )); + let id = self.id_gen.next(); + body.push(Instruction::load(u32_id, id, ptr_id, None)); + id + }; + let vert_count_id = load_u32_by_member_index( + return_info .out_members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) - .unwrap() as u32], - )); - let prim_count_id = self.id_gen.next(); - body.push(Instruction::composite_extract( - self.get_u32_type_id(), - prim_count_id, - output_value_id, - &[return_info + .unwrap() as u32, + ); + let prim_count_id = load_u32_by_member_index( + return_info .out_members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) - .unwrap() as u32], - )); + .unwrap() as u32, + ); let vert_array_ptr = self.id_gen.next(); body.push(Instruction::access_chain( self.get_pointer_type_id( @@ -3641,7 +3647,7 @@ impl BlockContext<'_> { self.ir_function.result.as_ref().unwrap(), &context.results, &mut block.body, - context.task_payload, + context.task_payload_variable_id, )?, None => Instruction::return_value(value_id), }; diff --git a/naga/src/back/spv/helpers.rs b/naga/src/back/spv/helpers.rs index c0c8406b3ff..e31f9885325 100644 --- a/naga/src/back/spv/helpers.rs +++ b/naga/src/back/spv/helpers.rs @@ -158,7 +158,7 @@ impl StrUnstable for str { pub enum BindingDecorations { BuiltIn(spirv::BuiltIn, ArrayVec), Location { - location: Word, + location: u32, others: ArrayVec, /// If this is `Some`, use Decoration::Index with blend_src as an operand blend_src: Option, diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index e804e939a3a..b1654f63783 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -152,8 +152,6 @@ struct MeshReturnMember { struct MeshReturnInfo { /// Id of the workgroup variable containing the data to be output out_variable_id: Word, - /// ID of the type of the workgroup variable - out_type_id: Word, /// All members of the output variable struct type out_members: Vec, @@ -182,7 +180,7 @@ struct MeshReturnInfo { struct EntryPointContext { argument_ids: Vec, results: Vec, - task_payload: Option, + task_payload_variable_id: Option, mesh_state: Option, } diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 8f9c6922c64..2c70fd05ca7 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -757,6 +757,11 @@ impl Writer { ep_context: &mut EntryPointContext, ) -> Result<(), Error> { if let Some(ref mesh_info) = iface.mesh_info { + // In case for some reason the shader always writes out nothing, and doesn't use the global variable + /*iface + .varying_ids + .push(self.global_variables[mesh_info.output_variable].var_id);*/ + // Collect the members in the output structs let out_members: Vec = match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { @@ -827,6 +832,7 @@ impl Writer { &[spirv::BuiltIn::LocalInvocationIndex as u32], ) .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(var); let loaded_value = self.id_gen.next(); prelude @@ -853,8 +859,6 @@ impl Writer { // so that it can write the final return logic let mut mesh_return_info = super::MeshReturnInfo { out_variable_id: self.global_variables[mesh_info.output_variable].var_id, - out_type_id: self - .get_handle_type_id(ir_module.global_variables[mesh_info.output_variable].ty), out_members, vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), @@ -1128,7 +1132,7 @@ impl Writer { let mut ep_context = EntryPointContext { argument_ids: Vec::new(), results: Vec::new(), - task_payload: if let Some(ref i) = interface { + task_payload_variable_id: if let Some(ref i) = interface { i.task_payload.map(|a| self.global_variables[a].var_id) } else { None diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 0eccbd90e1e..a040fd1604d 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -1148,6 +1148,7 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; + info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); } Ok(info) diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index cdc7366b415..f5ba08f6cbb 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -68,6 +68,12 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } +// Ensures that even if the workgroup var isn't used through static analysis, +// it is still written just fine to be used by generated code. +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_no_write() {} @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 9422d07107d..757674635e3 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -233,7 +233,7 @@ global_uses: [ ("READ"), ("WRITE"), - ("WRITE"), + ("READ | WRITE"), ], expressions: [ ( @@ -1399,6 +1399,25 @@ dual_source_blending: false, diagnostic_filter_leaf: None, ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ"), + (""), + ("READ"), + ], + expressions: [], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron index 1147b017f5c..aefa331f48f 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -907,6 +907,38 @@ )), task_payload: Some(0), ), + ( + name: "ms_no_write", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_no_write"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 4, + primitive_output_type: 7, + output_variable: 2, + )), + task_payload: Some(0), + ), ( name: "fs_main", stage: Fragment, diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron index 1147b017f5c..aefa331f48f 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -907,6 +907,38 @@ )), task_payload: Some(0), ), + ( + name: "ms_no_write", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_no_write"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 4, + primitive_output_type: 7, + output_variable: 2, + )), + task_payload: Some(0), + ), ( name: "fs_main", stage: Fragment, diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index d58789cef6e..0ee4b56ec2c 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.4 ; Generator: rspirv -; Bound: 188 +; Bound: 261 OpCapability Shader OpCapability MeshShadingEXT OpExtension "SPV_EXT_mesh_shader" @@ -9,13 +9,18 @@ OpExtension "SPV_EXT_mesh_shader" OpMemoryModel Logical GLSL450 OpEntryPoint TaskEXT %24 "ts_main" %17 %19 %33 OpEntryPoint MeshEXT %80 "ms_main" %54 %57 %17 %64 %68 %72 %75 %79 %19 %21 %92 -OpEntryPoint Fragment %183 "fs_main" %174 %177 %180 %182 +OpEntryPoint MeshEXT %196 "ms_no_write" %17 %174 %180 %184 %188 %191 %195 %21 %198 +OpEntryPoint Fragment %256 "fs_main" %247 %250 %253 %255 OpExecutionMode %24 LocalSize 1 1 1 OpExecutionMode %80 LocalSize 1 1 1 OpExecutionMode %80 OutputTrianglesNV OpExecutionMode %80 OutputVertices 3 OpExecutionMode %80 OutputPrimitivesNV 1 -OpExecutionMode %183 OriginUpperLeft +OpExecutionMode %196 LocalSize 1 1 1 +OpExecutionMode %196 OutputTrianglesNV +OpExecutionMode %196 OutputVertices 3 +OpExecutionMode %196 OutputPrimitivesNV 1 +OpExecutionMode %256 OriginUpperLeft OpMemberDecorate %61 0 BuiltIn Position OpDecorate %61 Block OpMemberDecorate %65 0 BuiltIn CullPrimitiveEXT @@ -28,6 +33,19 @@ OpDecorate %75 BuiltIn PrimitiveTriangleIndicesEXT OpDecorate %76 Block OpMemberDecorate %76 0 Location 1 OpDecorate %79 PerPrimitiveNV +OpDecorate %174 BuiltIn LocalInvocationIndex +OpMemberDecorate %177 0 BuiltIn Position +OpDecorate %177 Block +OpMemberDecorate %181 0 BuiltIn CullPrimitiveEXT +OpDecorate %181 Block +OpDecorate %184 PerPrimitiveNV +OpDecorate %185 Block +OpMemberDecorate %185 0 Location 0 +OpDecorate %191 PerPrimitiveNV +OpDecorate %191 BuiltIn PrimitiveTriangleIndicesEXT +OpDecorate %192 Block +OpMemberDecorate %192 0 Location 1 +OpDecorate %195 PerPrimitiveNV OpMemberDecorate %6 0 Offset 0 OpMemberDecorate %6 1 Offset 16 OpMemberDecorate %7 0 Offset 0 @@ -46,11 +64,12 @@ OpDecorate %33 BuiltIn LocalInvocationId OpDecorate %54 BuiltIn LocalInvocationIndex OpDecorate %57 BuiltIn GlobalInvocationId OpDecorate %92 BuiltIn LocalInvocationId -OpDecorate %174 BuiltIn FragCoord -OpDecorate %177 Location 0 -OpDecorate %180 Location 1 -OpDecorate %180 PerPrimitiveNV -OpDecorate %182 Location 0 +OpDecorate %198 BuiltIn LocalInvocationId +OpDecorate %247 BuiltIn FragCoord +OpDecorate %250 Location 0 +OpDecorate %253 Location 1 +OpDecorate %253 PerPrimitiveNV +OpDecorate %255 Location 0 %2 = OpTypeVoid %3 = OpTypeFloat 32 %4 = OpTypeVector %3 4 @@ -130,14 +149,35 @@ OpDecorate %182 Location 0 %121 = OpTypePointer Workgroup %10 %122 = OpTypePointer Workgroup %9 %124 = OpTypePointer Workgroup %5 -%144 = OpTypePointer Output %4 -%152 = OpTypePointer Output %9 -%155 = OpTypePointer Output %5 -%175 = OpTypePointer Input %4 -%174 = OpVariable %175 Input -%177 = OpVariable %175 Input -%180 = OpVariable %175 Input -%182 = OpVariable %144 Output +%145 = OpTypePointer Output %4 +%153 = OpTypePointer Output %9 +%156 = OpTypePointer Output %5 +%174 = OpVariable %55 Input +%177 = OpTypeStruct %4 +%178 = OpTypeArray %177 %13 +%179 = OpTypePointer Output %178 +%180 = OpVariable %179 Output +%181 = OpTypeStruct %5 +%182 = OpTypeArray %181 %15 +%183 = OpTypePointer Output %182 +%184 = OpVariable %183 Output +%185 = OpTypeStruct %4 +%186 = OpTypeArray %185 %13 +%187 = OpTypePointer Output %186 +%188 = OpVariable %187 Output +%189 = OpTypeArray %9 %15 +%190 = OpTypePointer Output %189 +%191 = OpVariable %190 Output +%192 = OpTypeStruct %4 +%193 = OpTypeArray %192 %15 +%194 = OpTypePointer Output %193 +%195 = OpVariable %194 Output +%198 = OpVariable %34 Input +%248 = OpTypePointer Input %4 +%247 = OpVariable %248 Input +%250 = OpVariable %248 Input +%253 = OpVariable %248 Input +%255 = OpVariable %145 Output %24 = OpFunction %2 None %25 %23 = OpLabel OpBranch %31 @@ -219,81 +259,164 @@ OpStore %123 %88 OpStore %128 %127 %129 = OpAccessChain %104 %21 %15 %46 %42 OpStore %129 %89 -%130 = OpLoad %16 %21 -%131 = OpCompositeExtract %8 %130 2 -%132 = OpCompositeExtract %8 %130 3 -%133 = OpAccessChain %102 %21 %46 -%134 = OpAccessChain %120 %21 %15 -OpSetMeshOutputsEXT %131 %132 +%130 = OpAccessChain %99 %21 %42 +%131 = OpLoad %8 %130 +%132 = OpAccessChain %99 %21 %13 +%133 = OpLoad %8 %132 +%134 = OpAccessChain %102 %21 %46 +%135 = OpAccessChain %120 %21 %15 +OpSetMeshOutputsEXT %131 %133 OpStore %59 %56 -OpBranch %135 -%135 = OpLabel -OpLoopMerge %137 %159 None -OpBranch %158 -%158 = OpLabel -%161 = OpLoad %8 %59 -%162 = OpULessThan %5 %161 %131 -OpBranchConditional %162 %160 %137 -%160 = OpLabel -%139 = OpLoad %8 %59 -%140 = OpAccessChain %103 %133 %139 -%141 = OpLoad %7 %140 -%142 = OpCompositeExtract %4 %141 0 -%143 = OpAccessChain %144 %64 %139 %46 -OpStore %143 %142 -%145 = OpCompositeExtract %4 %141 1 -%146 = OpAccessChain %144 %72 %139 %46 -OpStore %146 %145 +OpBranch %136 +%136 = OpLabel +OpLoopMerge %138 %160 None OpBranch %159 %159 = OpLabel -%163 = OpLoad %8 %59 -%164 = OpIAdd %8 %163 %15 -OpStore %59 %164 -OpBranch %135 -%137 = OpLabel -OpStore %59 %56 +%162 = OpLoad %8 %59 +%163 = OpULessThan %5 %162 %131 +OpBranchConditional %163 %161 %138 +%161 = OpLabel +%140 = OpLoad %8 %59 +%141 = OpAccessChain %103 %134 %140 +%142 = OpLoad %7 %141 +%143 = OpCompositeExtract %4 %142 0 +%144 = OpAccessChain %145 %64 %140 %46 +OpStore %144 %143 +%146 = OpCompositeExtract %4 %142 1 +%147 = OpAccessChain %145 %72 %140 %46 +OpStore %147 %146 +OpBranch %160 +%160 = OpLabel +%164 = OpLoad %8 %59 +%165 = OpIAdd %8 %164 %15 +OpStore %59 %165 OpBranch %136 -%136 = OpLabel -OpLoopMerge %138 %166 None -OpBranch %165 -%165 = OpLabel -%168 = OpLoad %8 %59 -%169 = OpULessThan %5 %168 %132 -OpBranchConditional %169 %167 %138 -%167 = OpLabel -%147 = OpLoad %8 %59 -%148 = OpAccessChain %121 %134 %147 -%149 = OpLoad %10 %148 -%150 = OpCompositeExtract %9 %149 0 -%151 = OpAccessChain %152 %75 %147 -OpStore %151 %150 -%153 = OpCompositeExtract %5 %149 1 -%154 = OpAccessChain %155 %68 %147 %46 -OpStore %154 %153 -%156 = OpCompositeExtract %4 %149 2 -%157 = OpAccessChain %144 %79 %147 %46 -OpStore %157 %156 +%138 = OpLabel +OpStore %59 %56 +OpBranch %137 +%137 = OpLabel +OpLoopMerge %139 %167 None OpBranch %166 %166 = OpLabel -%170 = OpLoad %8 %59 -%171 = OpIAdd %8 %170 %15 -OpStore %59 %171 -OpBranch %136 -%138 = OpLabel +%169 = OpLoad %8 %59 +%170 = OpULessThan %5 %169 %133 +OpBranchConditional %170 %168 %139 +%168 = OpLabel +%148 = OpLoad %8 %59 +%149 = OpAccessChain %121 %135 %148 +%150 = OpLoad %10 %149 +%151 = OpCompositeExtract %9 %150 0 +%152 = OpAccessChain %153 %75 %148 +OpStore %152 %151 +%154 = OpCompositeExtract %5 %150 1 +%155 = OpAccessChain %156 %68 %148 %46 +OpStore %155 %154 +%157 = OpCompositeExtract %4 %150 2 +%158 = OpAccessChain %145 %79 %148 %46 +OpStore %158 %157 +OpBranch %167 +%167 = OpLabel +%171 = OpLoad %8 %59 +%172 = OpIAdd %8 %171 %15 +OpStore %59 %172 +OpBranch %137 +%139 = OpLabel +OpReturn +OpFunctionEnd +%196 = OpFunction %2 None %25 +%173 = OpLabel +%176 = OpVariable %60 Function +%175 = OpLoad %8 %174 +OpBranch %197 +%197 = OpLabel +%199 = OpLoad %9 %198 +%200 = OpIEqual %37 %199 %36 +%201 = OpAll %5 %200 +OpSelectionMerge %202 None +OpBranchConditional %201 %203 %202 +%203 = OpLabel +OpStore %21 %91 +OpBranch %202 +%202 = OpLabel +OpControlBarrier %42 %42 %43 +OpBranch %204 +%204 = OpLabel +%205 = OpAccessChain %99 %21 %42 +%206 = OpLoad %8 %205 +%207 = OpAccessChain %99 %21 %13 +%208 = OpLoad %8 %207 +%209 = OpAccessChain %102 %21 %46 +%210 = OpAccessChain %120 %21 %15 +OpSetMeshOutputsEXT %206 %208 +OpStore %176 %175 +OpBranch %211 +%211 = OpLabel +OpLoopMerge %213 %232 None +OpBranch %231 +%231 = OpLabel +%234 = OpLoad %8 %176 +%235 = OpULessThan %5 %234 %206 +OpBranchConditional %235 %233 %213 +%233 = OpLabel +%215 = OpLoad %8 %176 +%216 = OpAccessChain %103 %209 %215 +%217 = OpLoad %7 %216 +%218 = OpCompositeExtract %4 %217 0 +%219 = OpAccessChain %145 %180 %215 %46 +OpStore %219 %218 +%220 = OpCompositeExtract %4 %217 1 +%221 = OpAccessChain %145 %188 %215 %46 +OpStore %221 %220 +OpBranch %232 +%232 = OpLabel +%236 = OpLoad %8 %176 +%237 = OpIAdd %8 %236 %15 +OpStore %176 %237 +OpBranch %211 +%213 = OpLabel +OpStore %176 %175 +OpBranch %212 +%212 = OpLabel +OpLoopMerge %214 %239 None +OpBranch %238 +%238 = OpLabel +%241 = OpLoad %8 %176 +%242 = OpULessThan %5 %241 %208 +OpBranchConditional %242 %240 %214 +%240 = OpLabel +%222 = OpLoad %8 %176 +%223 = OpAccessChain %121 %210 %222 +%224 = OpLoad %10 %223 +%225 = OpCompositeExtract %9 %224 0 +%226 = OpAccessChain %153 %191 %222 +OpStore %226 %225 +%227 = OpCompositeExtract %5 %224 1 +%228 = OpAccessChain %156 %184 %222 %46 +OpStore %228 %227 +%229 = OpCompositeExtract %4 %224 2 +%230 = OpAccessChain %145 %195 %222 %46 +OpStore %230 %229 +OpBranch %239 +%239 = OpLabel +%243 = OpLoad %8 %176 +%244 = OpIAdd %8 %243 %15 +OpStore %176 %244 +OpBranch %212 +%214 = OpLabel OpReturn OpFunctionEnd -%183 = OpFunction %2 None %25 -%172 = OpLabel -%176 = OpLoad %4 %174 -%178 = OpLoad %4 %177 -%173 = OpCompositeConstruct %7 %176 %178 -%181 = OpLoad %4 %180 -%179 = OpCompositeConstruct %11 %181 -OpBranch %184 -%184 = OpLabel -%185 = OpCompositeExtract %4 %173 1 -%186 = OpCompositeExtract %4 %179 0 -%187 = OpFMul %4 %185 %186 -OpStore %182 %187 +%256 = OpFunction %2 None %25 +%245 = OpLabel +%249 = OpLoad %4 %247 +%251 = OpLoad %4 %250 +%246 = OpCompositeConstruct %7 %249 %251 +%254 = OpLoad %4 %253 +%252 = OpCompositeConstruct %11 %254 +OpBranch %257 +%257 = OpLabel +%258 = OpCompositeExtract %4 %246 1 +%259 = OpCompositeExtract %4 %252 0 +%260 = OpFMul %4 %258 %259 +OpStore %255 %260 OpReturn OpFunctionEnd \ No newline at end of file diff --git a/out.wgsl b/out.wgsl deleted file mode 100644 index 3736edb1b9f..00000000000 --- a/out.wgsl +++ /dev/null @@ -1,66 +0,0 @@ -enable mesh_shading; - -struct TaskPayload { - colorMask: vec4, - visible: bool, -} - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) color: vec4, -} - -struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, - @builtin(cull_primitive) cull: bool, - @per_primitive @location(1) colorMask: vec4, -} - -struct PrimitiveInput { - @per_primitive @location(1) colorMask: vec4, -} - -struct MeshOutput { - @builtin(vertices) vertices: array, - @builtin(primitives) primitives: array, - @builtin(vertex_count) vertex_count: u32, - @builtin(primitive_count) primitive_count: u32, -} - -var taskPayload: TaskPayload; -var workgroupData: f32; -var mesh_output: MeshOutput; - -@task @workgroup_size(1, 1, 1) @payload(taskPayload) -fn ts_main() -> @builtin(mesh_task_size) vec3 { - workgroupData = 1f; - taskPayload.colorMask = vec4(1f, 1f, 0f, 1f); - taskPayload.visible = true; - return vec3(3u, 1u, 1u); -} - -@mesh(mesh_output) @payload(taskPayload) @workgroup_size(1, 1, 1) -fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocation_id) id: vec3) { - mesh_output.vertex_count = 3u; - mesh_output.primitive_count = 1u; - workgroupData = 2f; - mesh_output.vertices[0].position = vec4(0f, 1f, 0f, 1f); - let _e25 = taskPayload.colorMask; - mesh_output.vertices[0].color = (vec4(0f, 1f, 0f, 1f) * _e25); - mesh_output.vertices[1].position = vec4(-1f, -1f, 0f, 1f); - let _e47 = taskPayload.colorMask; - mesh_output.vertices[1].color = (vec4(0f, 0f, 1f, 1f) * _e47); - mesh_output.vertices[2].position = vec4(1f, -1f, 0f, 1f); - let _e69 = taskPayload.colorMask; - mesh_output.vertices[2].color = (vec4(1f, 0f, 0f, 1f) * _e69); - mesh_output.primitives[0].index = vec3(0u, 1u, 2u); - let _e90 = taskPayload.visible; - mesh_output.primitives[0].cull = !(_e90); - mesh_output.primitives[0].colorMask = vec4(1f, 0f, 1f, 1f); - return; -} - -@fragment -fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { - return (vertex.color * primitive.colorMask); -} From d6b6ce2e6e3dd3ff65785e33096c9e57e97991ae Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 17:55:08 -0600 Subject: [PATCH 065/110] Added comments, cleaned stuff up --- naga/src/back/spv/block.rs | 33 +- naga/src/back/spv/writer.rs | 624 ++++++++++++++++++------------------ 2 files changed, 328 insertions(+), 329 deletions(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 0a82307960c..58434dc3843 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -224,6 +224,7 @@ impl Writer { task_payload: Option, ) -> Result { for (index, res_member) in result_members.iter().enumerate() { + // This isn't a real builtin, and is handled elsewhere if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { continue; } @@ -289,6 +290,7 @@ impl Writer { ); body.push(instruction); } + // TODO: make this guaranteed to be uniform let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); for id in values { instruction.add_operand(id); @@ -341,6 +343,7 @@ impl Writer { .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) .unwrap() as u32, ); + // Get pointers to the arrays of data to extract let vert_array_ptr = self.id_gen.next(); body.push(Instruction::access_chain( self.get_pointer_type_id( @@ -374,8 +377,7 @@ impl Writer { ))], )); - // Call this. It must be called exactly once, which the user shouldn't be assumed - // to have done correctly. + // This must be called exactly once before any other mesh outputs are written { let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); ins.add_operand(vert_count_id); @@ -384,7 +386,7 @@ impl Writer { } // All this for a `for i in 0..num_vertices` lol - // This is basically just a memcpy but the result is split up to multiple places + // This is basically just unzipping an array and copying to many arrays let u32_type_id = self.get_u32_type_id(); let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); let vertex_loop_header = self.id_gen.next(); @@ -400,7 +402,8 @@ impl Writer { )); body.push(Instruction::branch(vertex_loop_header)); - // Vertex copies + // This generates the instructions used to copy all parts of a single output vertex + // to their individual output locations let vertex_copy_body = { let mut body = Vec::new(); // Current index to copy @@ -440,8 +443,7 @@ impl Writer { &[member_id as u32], )); let ptr_to_copy_to = self.id_gen.next(); - // Get the variable that holds it and indexed pointer, which points to - // the value and not a wrapper struct + // Get a pointer to the struct member to copy match member.binding { crate::Binding::BuiltIn(bi) => { body.push(Instruction::access_chain( @@ -468,6 +470,7 @@ impl Writer { } } body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + // Flip the vertex position y coordinate in some cases // Can't use epilogue flip because can't read from this storage class I believe if needs_y_flip { let prev_y = self.id_gen.next(); @@ -498,8 +501,8 @@ impl Writer { }; // Primitive copies + // See comments in `vertex_copy_body` let primitive_copy_body = { - // See comments in `vertex_copy_body` let mut body = Vec::new(); let val_i = self.id_gen.next(); body.push(Instruction::load(u32_type_id, val_i, index_var, None)); @@ -574,12 +577,12 @@ impl Writer { }; // This writes the actual loop - let mut get_loop_continue_id = |body: &mut Vec, - mut loop_body_block, - loop_header, - loop_merge, - count_id, - index_var| { + let mut write_loop = |body: &mut Vec, + mut loop_body_block, + loop_header, + loop_merge, + count_id, + index_var| { let condition_check = self.id_gen.next(); let loop_continue = self.id_gen.next(); let loop_body = self.id_gen.next(); @@ -637,7 +640,7 @@ impl Writer { } }; // Write vertex copy loop - get_loop_continue_id( + write_loop( body, vertex_copy_body, vertex_loop_header, @@ -658,7 +661,7 @@ impl Writer { body.push(Instruction::branch(prim_loop_header)); } // Write primitive copy loop - get_loop_continue_id( + write_loop( body, primitive_copy_body, prim_loop_header, diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 2c70fd05ca7..2930d35b9bd 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -731,6 +731,7 @@ impl Writer { Ok(()) } + /// Sets up an output variable that will handle part of the mesh shader output fn write_mesh_return_global_variable( &mut self, ty: u32, @@ -756,51 +757,12 @@ impl Writer { prelude: &mut Block, ep_context: &mut EntryPointContext, ) -> Result<(), Error> { - if let Some(ref mesh_info) = iface.mesh_info { - // In case for some reason the shader always writes out nothing, and doesn't use the global variable - /*iface - .varying_ids - .push(self.global_variables[mesh_info.output_variable].var_id);*/ - - // Collect the members in the output structs - let out_members: Vec = - match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let vertex_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) - .unwrap() - .ty_id; - let primitive_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) - .unwrap() - .ty_id; - let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + let Some(ref mesh_info) = iface.mesh_info else { + return Ok(()); + }; + // Collect the members in the output structs + let out_members: Vec = + match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { &crate::Type { inner: crate::TypeInner::Struct { ref members, .. }, .. @@ -813,304 +775,338 @@ impl Writer { .collect(), _ => unreachable!(), }; - // In the final return, we do a giant memcpy, for which this is helpful - let local_invocation_index_id = match local_invocation_index_id { - Some(a) => a, - None => { - let u32_id = self.get_u32_type_id(); - let var = self.id_gen.next(); - Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), - var, - spirv::StorageClass::Input, - None, - ) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate( - var, - spirv::Decoration::BuiltIn, - &[spirv::BuiltIn::LocalInvocationIndex as u32], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(var); - - let loaded_value = self.id_gen.next(); - prelude - .body - .push(Instruction::load(u32_id, loaded_value, var, None)); - loaded_value - } - }; - let u32_id = self.get_u32_type_id(); - // A general function variable that we guarantee to allow in the final return. It must be - // declared at the top of the function. Currently it is used in the memcpy part to keep - // index to copy track of the current - let function_variable = self.id_gen.next(); - prelude.body.insert( - 0, + let vertex_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() + .ty_id; + let primitive_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() + .ty_id; + let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + // In the final return, we do a giant memcpy, for which this is helpful + let local_invocation_index_id = match local_invocation_index_id { + Some(a) => a, + None => { + let u32_id = self.get_u32_type_id(); + let var = self.id_gen.next(); Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), - function_variable, - spirv::StorageClass::Function, + self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), + var, + spirv::StorageClass::Input, None, - ), - ); - // This is the information that is passed to the function writer - // so that it can write the final return logic - let mut mesh_return_info = super::MeshReturnInfo { - out_variable_id: self.global_variables[mesh_info.output_variable].var_id, - out_members, - - vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), - vertex_array_type_id, - vertex_members, - primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), - primitive_array_type_id, - primitive_members, - vertex_bindings: Vec::new(), - vertex_builtin_block: None, - primitive_bindings: Vec::new(), - primitive_builtin_block: None, - primitive_indices: None, - local_invocation_index_id, - workgroup_size: self.get_constant_scalar(crate::Literal::U32( - iface.workgroup_size.iter().product(), - )), + ) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate( + var, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationIndex as u32], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(var); + + let loaded_value = self.id_gen.next(); + prelude + .body + .push(Instruction::load(u32_id, loaded_value, var, None)); + loaded_value + } + }; + let u32_id = self.get_u32_type_id(); + // A general function variable that we guarantee to allow in the final return. It must be + // declared at the top of the function. Currently it is used in the memcpy part to keep + // track of the current index to copy. + let function_variable = self.id_gen.next(); + prelude.body.insert( + 0, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), function_variable, - }; - let vert_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); - let prim_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); - // Create the actual output variables and types. - // According to SPIR-V, - // * All builtins must be in the same output `Block` - // * Each member with `location` must be in its own `Block`. - // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this!) - // * Some builtins like the indices ones need to be in their - // own output variable without a struct wrapper - if mesh_return_info - .vertex_members - .iter() - .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) - { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.vertex_members { - if let crate::Binding::BuiltIn(_) = member.binding { - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { + spirv::StorageClass::Function, + None, + ), + ); + // This is the information that is passed to the function writer + // so that it can write the final return logic + let mut mesh_return_info = super::MeshReturnInfo { + out_variable_id: self.global_variables[mesh_info.output_variable].var_id, + out_members, + + vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), + vertex_array_type_id, + vertex_members, + primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), + primitive_array_type_id, + primitive_members, + vertex_bindings: Vec::new(), + vertex_builtin_block: None, + primitive_bindings: Vec::new(), + primitive_builtin_block: None, + primitive_indices: None, + local_invocation_index_id, + workgroup_size: self + .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), + function_variable, + }; + let vert_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); + let prim_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); + + // Create the actual output variables and types. + // According to SPIR-V, + // * All builtins must be in the same output `Block` (except vertex/primitive builtins) + // * Each member with `location` must be in its own `Block` + // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) + // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper + + // Write vertex builtin block + if mesh_return_info + .vertex_members + .iter() + .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.vertex_members { + if let crate::Binding::BuiltIn(_) = member.binding { + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], + other, + &[], )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } } - _ => unreachable!(), } - bi_index += 1; + _ => unreachable!(), } + bi_index += 1; } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = self - .write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_vertex_builtin_outputs")); - } - mesh_return_info.vertex_builtin_block = Some(v); } - if mesh_return_info.primitive_members.iter().any(|a| { - !matches!( - a.binding, - crate::Binding::BuiltIn( + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_vertex_builtin_outputs")); + } + mesh_return_info.vertex_builtin_block = Some(v); + } + // Write primitive builtin block + if mesh_return_info.primitive_members.iter().any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.primitive_members { + if let crate::Binding::BuiltIn(bi) = member.binding { + // These need to be in their own block, unlike other builtins + if matches!( + bi, crate::BuiltIn::PointIndex | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices - ) | crate::Binding::Location { .. } - ) - }) { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.primitive_members { - if let crate::Binding::BuiltIn(bi) = member.binding { - if matches!( - bi, - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) { - continue; - } - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { + | crate::BuiltIn::TriangleIndices, + ) { + continue; + } + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { decorations.push(Instruction::member_decorate( builtin_block_ty_id, bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], + other, + &[], )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } } - _ => unreachable!(), } - bi_index += 1; + _ => unreachable!(), } + bi_index += 1; } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = self - .write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_builtin_outputs")); - } - mesh_return_info.primitive_builtin_block = Some(v); } - { - for member in &mesh_return_info.vertex_members { - match member.binding { - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = - self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; - iface.varying_ids.push(v); - mesh_return_info.vertex_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), - } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_builtin_outputs")); + } + mesh_return_info.primitive_builtin_block = Some(v); + } + + // Write vertex binding output blocks (1 array per output struct member) + for member in &mesh_return_info.vertex_members { + match member.binding { + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; + iface.varying_ids.push(v); + mesh_return_info.vertex_bindings.push(v); } - for member in &mesh_return_info.primitive_members { - match member.binding { - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) => { - let v = self.write_mesh_return_global_variable( - member.ty_id, - prim_array_size_id, - )?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::decorate( - v, - spirv::Decoration::BuiltIn, - &[match member.binding.to_built_in().unwrap() { - crate::BuiltIn::PointIndex => { - spirv::BuiltIn::PrimitivePointIndicesEXT - } - crate::BuiltIn::LineIndices => { - spirv::BuiltIn::PrimitiveLineIndicesEXT - } - crate::BuiltIn::TriangleIndices => { - spirv::BuiltIn::PrimitiveTriangleIndicesEXT - } - _ => unreachable!(), - } as Word], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_indices_outputs")); + crate::Binding::BuiltIn(_) => (), + } + } + // Write primitive binding output blocks (1 array per output struct member) + for member in &mesh_return_info.primitive_members { + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + let v = + self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::decorate( + v, + spirv::Decoration::BuiltIn, + &[match member.binding.to_built_in().unwrap() { + crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, + crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, + crate::BuiltIn::TriangleIndices => { + spirv::BuiltIn::PrimitiveTriangleIndicesEXT } - mesh_return_info.primitive_indices = Some(v); - } - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = - self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - mesh_return_info.primitive_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), + _ => unreachable!(), + } as Word], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_indices_outputs")); } + mesh_return_info.primitive_indices = Some(v); } + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + mesh_return_info.primitive_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), } - ep_context.mesh_state = Some(mesh_return_info); } + + // Store this where it can be read later during function write + ep_context.mesh_state = Some(mesh_return_info); + Ok(()) } From 19213fb0000e562b3bb1785ea18b044fb685bb1d Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 17:59:51 -0600 Subject: [PATCH 066/110] A few more comments --- naga/src/back/spv/writer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 2930d35b9bd..e00da21c54d 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1050,6 +1050,7 @@ impl Writer { } } // Write primitive binding output blocks (1 array per output struct member) + // Also write indices output block for member in &mesh_return_info.primitive_members { match member.binding { crate::Binding::BuiltIn( @@ -1057,6 +1058,7 @@ impl Writer { | crate::BuiltIn::LineIndices | crate::BuiltIn::TriangleIndices, ) => { + // This is written here instead of as part of the builtin block let v = self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) @@ -1098,6 +1100,7 @@ impl Writer { Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); + mesh_return_info.primitive_bindings.push(v); } crate::Binding::BuiltIn(_) => (), From c0278f34df186a080e7a92f7b48ecff5cc10542a Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 20:02:59 -0600 Subject: [PATCH 067/110] Updated changelog --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 528f30df279..a868658302b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,9 +139,8 @@ By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206 - Removed three features from `wgpu-hal` which did nothing useful: `"cargo-clippy"`, `"gpu-allocator"`, and `"rustc-hash"`. By @kpreid in [#8357](https://github.com/gfx-rs/wgpu/pull/8357). - `wgpu_types::PollError` now always implements the `Error` trait. By @kpreid in [#8384](https://github.com/gfx-rs/wgpu/pull/8384). - The texture subresources used by the color attachments of a render pass are no longer allowed to overlap when accessed via different texture views. By @andyleiserson in [#8402](https://github.com/gfx-rs/wgpu/pull/8402). -- Add WGSL parsing for mesh shaders. By @inner-daemons in [#8370](https://github.com/gfx-rs/wgpu/pull/8370). -- Fixed a bug where the texture aspect was not passed through when calling `copy_texture_to_buffer` in WebGPU, causing the copy to fail for depth/stencil textures. By @Tim-Evans-Seequent in [#8445](https://github.com/gfx-rs/wgpu/pull/8445). - Validate that buffers are unmapped in `write_buffer` calls. By @ErichDonGubler in [#8454](https://github.com/gfx-rs/wgpu/pull/8454). +- Add WGSL parsing for mesh shaders. By @inner-daemons in [#8370](https://github.com/gfx-rs/wgpu/pull/8370). #### DX12 From e7dc9e54f3b94058bb0be607f8ea1734869e2a24 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 20:09:40 -0600 Subject: [PATCH 068/110] Updated validation from spv-write --- naga/src/valid/interface.rs | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 6c30c554420..a040fd1604d 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -98,8 +98,6 @@ pub enum VaryingError { InvalidPerPrimitive, #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] MissingPerPrimitive, - #[error("The `MESH_SHADER` capability must be enabled to use per-primitive fragment inputs.")] - PerPrimitiveNotAllowed, } #[derive(Clone, Debug, thiserror::Error)] @@ -402,6 +400,24 @@ impl VaryingContext<'_> { (false, true) } }; + match built_in { + Bi::CullPrimitive + | Bi::PointIndex + | Bi::LineIndices + | Bi::TriangleIndices + | Bi::MeshTaskSize + | Bi::VertexCount + | Bi::PrimitiveCount + | Bi::Vertices + | Bi::Primitives => { + if !self.capabilities.contains(Capabilities::MESH_SHADER) { + return Err(VaryingError::UnsupportedCapability( + Capabilities::MESH_SHADER, + )); + } + } + _ => (), + } if !visible { return Err(VaryingError::InvalidBuiltInStage(built_in)); @@ -419,7 +435,9 @@ impl VaryingContext<'_> { per_primitive, } => { if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) { - return Err(VaryingError::PerPrimitiveNotAllowed); + return Err(VaryingError::UnsupportedCapability( + Capabilities::MESH_SHADER, + )); } // Only IO-shareable types may be stored in locations. if !self.type_info[ty.index()] @@ -1130,6 +1148,7 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; + info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); } Ok(info) From 592ac165a2a1d7763c0c59e48b444a6643a8e1a4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 20:24:25 -0600 Subject: [PATCH 069/110] Slight tweaks --- naga/src/ir/mod.rs | 2 +- naga/tests/out/analysis/wgsl-mesh-shader.info.ron | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 097220a46bb..c3deabe706d 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -2583,7 +2583,7 @@ pub struct DocComments { pub enum MeshOutputTopology { /// Outputs individual vertices to be rendered as points. Points, - /// Outputs groups of 2 vertices to be renderedas lines . + /// Outputs groups of 2 vertices to be rendered as lines. Lines, /// Outputs groups of 3 vertices to be rendered as triangles. Triangles, diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 9422d07107d..eacd33ad0f1 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -233,7 +233,7 @@ global_uses: [ ("READ"), ("WRITE"), - ("WRITE"), + ("READ | WRITE"), ], expressions: [ ( From f3daf23e1cbaf782392a02804f7c14e05dae6713 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Thu, 6 Nov 2025 20:24:57 -0600 Subject: [PATCH 070/110] Fix little typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c0a7d57821..224137417c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -149,7 +149,7 @@ By @SupaMaggie70Incorporated in [#8206](https://github.com/gfx-rs/wgpu/pull/8206 #### Vulkan - Fixed a validation error regarding atomic memory semantics. By @atlv24 in [#8391](https://github.com/gfx-rs/wgpu/pull/8391). -- Add mesh shader writer support, allowing WGSL shaders to be used on the vulkan backend. By @inner-daemons in [#8456](https://github.com/gfx-rs/wgpu/pull/8456f). +- Add mesh shader writer support, allowing WGSL shaders to be used on the vulkan backend. By @inner-daemons in [#8456](https://github.com/gfx-rs/wgpu/pull/8456). #### WebGPU From 439817127e5e3af94011a228bfc6e28a8bd7babe Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Fri, 7 Nov 2025 14:40:16 -0600 Subject: [PATCH 071/110] Added capabilitiy thing blah blah blah --- wgpu-core/src/device/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index ec5203f291b..c9e77e8ba45 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -514,6 +514,10 @@ pub fn create_validator( Caps::SHADER_BARYCENTRICS, features.intersects(wgt::Features::SHADER_BARYCENTRICS), ); + caps.set( + Caps::MESH_SHADER, + features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER), + ); naga::valid::Validator::new(flags, caps) } From b88fab56fc70e4ddd851780d74289bac506f16d0 Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Sat, 8 Nov 2025 03:15:35 -0500 Subject: [PATCH 072/110] Updated comment slightly to clarify something --- naga/src/back/spv/writer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index e00da21c54d..a6bc43c4c82 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -882,8 +882,8 @@ impl Writer { // Create the actual output variables and types. // According to SPIR-V, - // * All builtins must be in the same output `Block` (except vertex/primitive builtins) - // * Each member with `location` must be in its own `Block` + // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) + // * Each member with `location` must be in its own `Block` decorated `struct` // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper From ffa9219ef9ebb87debe2ec96c015993bacea1d01 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 8 Nov 2025 13:44:00 -0600 Subject: [PATCH 073/110] Removed thing from mesh shader test --- naga/tests/in/wgsl/mesh-shader.wgsl | 6 ------ 1 file changed, 6 deletions(-) diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index f5ba08f6cbb..cdc7366b415 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -68,12 +68,6 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } -// Ensures that even if the workgroup var isn't used through static analysis, -// it is still written just fine to be used by generated code. -@mesh(mesh_output) -@payload(taskPayload) -@workgroup_size(1) -fn ms_no_write() {} @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; From 1f071a662481688691fe9c9909b3bff5dc701bef Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 8 Nov 2025 14:14:47 -0600 Subject: [PATCH 074/110] Fixed compaction & clean stuff up --- naga/src/ir/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index c3deabe706d..097220a46bb 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -2583,7 +2583,7 @@ pub struct DocComments { pub enum MeshOutputTopology { /// Outputs individual vertices to be rendered as points. Points, - /// Outputs groups of 2 vertices to be rendered as lines. + /// Outputs groups of 2 vertices to be renderedas lines . Lines, /// Outputs groups of 3 vertices to be rendered as triangles. Triangles, From 7f744cf1335c96ebfb5173e9b1749c7989292345 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 8 Nov 2025 14:14:47 -0600 Subject: [PATCH 075/110] Fixed compaction and such pt 2 --- docs/api-specs/mesh_shading.md | 9 +- naga/src/compact/mod.rs | 82 +++-- naga/src/valid/interface.rs | 4 +- naga/tests/in/wgsl/mesh-shader-empty.toml | 19 ++ naga/tests/in/wgsl/mesh-shader-empty.wgsl | 42 +++ naga/tests/in/wgsl/mesh-shader.wgsl | 9 +- .../analysis/wgsl-mesh-shader-empty.info.ron | 100 ++++++ .../out/ir/wgsl-mesh-shader-empty.compact.ron | 277 +++++++++++++++++ naga/tests/out/ir/wgsl-mesh-shader-empty.ron | 291 ++++++++++++++++++ 9 files changed, 784 insertions(+), 49 deletions(-) create mode 100644 naga/tests/in/wgsl/mesh-shader-empty.toml create mode 100644 naga/tests/in/wgsl/mesh-shader-empty.wgsl create mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-empty.ron diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 41720765a55..9c98c684e76 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -208,12 +208,11 @@ const colors = array( vec4(0., 0., 1., 1.), vec4(1., 0., 0., 1.) ); + struct TaskPayload { colorMask: vec4, visible: bool, } -var taskPayload: TaskPayload; -var workgroupData: f32; struct VertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, @@ -227,6 +226,9 @@ struct PrimitiveInput { @per_primitive @location(1) colorMask: vec4, } +var taskPayload: TaskPayload; +var workgroupData: f32; + @task @payload(taskPayload) @workgroup_size(1) @@ -243,8 +245,8 @@ struct MeshOutput { @builtin(vertex_count) vertex_count: u32, @builtin(primitive_count) primitive_count: u32, } - var mesh_output: MeshOutput; + @mesh(mesh_output) @payload(taskPayload) @workgroup_size(1) @@ -266,6 +268,7 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } + @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; diff --git a/naga/src/compact/mod.rs b/naga/src/compact/mod.rs index 2761c7cfaf8..1c9d4b55053 100644 --- a/naga/src/compact/mod.rs +++ b/naga/src/compact/mod.rs @@ -132,6 +132,46 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { } } + if let Some(task_payload) = e.task_payload { + module_tracer.global_variables_used.insert(task_payload); + } + if let Some(ref mesh_info) = e.mesh_info { + module_tracer + .global_variables_used + .insert(mesh_info.output_variable); + module_tracer + .types_used + .insert(mesh_info.vertex_output_type); + module_tracer + .types_used + .insert(mesh_info.primitive_output_type); + if let Some(max_vertices_override) = mesh_info.max_vertices_override { + module_tracer + .global_expressions_used + .insert(max_vertices_override); + } + if let Some(max_primitives_override) = mesh_info.max_primitives_override { + module_tracer + .global_expressions_used + .insert(max_primitives_override); + } + } + if e.stage == crate::ShaderStage::Task || e.stage == crate::ShaderStage::Mesh { + // u32 should always be there if the module is valid, as it is e.g. the type of some expressions + let u32_type = module + .types + .iter() + .find_map(|tuple| { + if tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32) { + Some(tuple.0) + } else { + None + } + }) + .unwrap(); + module_tracer.types_used.insert(u32_type); + } + let mut used = module_tracer.as_function(&e.function); used.trace(); FunctionMap::from(used) @@ -221,48 +261,6 @@ pub fn compact(module: &mut crate::Module, keep_unused: KeepUnused) { } } - for entry in &module.entry_points { - if let Some(task_payload) = entry.task_payload { - module_tracer.global_variables_used.insert(task_payload); - } - if let Some(ref mesh_info) = entry.mesh_info { - module_tracer - .global_variables_used - .insert(mesh_info.output_variable); - module_tracer - .types_used - .insert(mesh_info.vertex_output_type); - module_tracer - .types_used - .insert(mesh_info.primitive_output_type); - if let Some(max_vertices_override) = mesh_info.max_vertices_override { - module_tracer - .global_expressions_used - .insert(max_vertices_override); - } - if let Some(max_primitives_override) = mesh_info.max_primitives_override { - module_tracer - .global_expressions_used - .insert(max_primitives_override); - } - } - if entry.stage == crate::ShaderStage::Task || entry.stage == crate::ShaderStage::Mesh { - // u32 should always be there if the module is valid, as it is e.g. the type of some expressions - let u32_type = module - .types - .iter() - .find_map(|tuple| { - if tuple.1.inner == crate::TypeInner::Scalar(crate::Scalar::U32) { - Some(tuple.0) - } else { - None - } - }) - .unwrap(); - module_tracer.types_used.insert(u32_type); - } - } - module_tracer.type_expression_tandem(); // Now that we know what is used and what is never touched, diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index a040fd1604d..fd33d3adebf 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -931,6 +931,9 @@ impl super::Validator { } info.insert_global_use(GlobalUse::READ, handle); } + if let Some(ref mesh_info) = ep.mesh_info { + info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); + } } // Other stages must not have a payload. @@ -1148,7 +1151,6 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; - info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); } Ok(info) diff --git a/naga/tests/in/wgsl/mesh-shader-empty.toml b/naga/tests/in/wgsl/mesh-shader-empty.toml new file mode 100644 index 00000000000..1f8b4e23baa --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-empty.toml @@ -0,0 +1,19 @@ +# Stolen from ray-query.toml + +god_mode = true +targets = "IR | ANALYSIS" + +[msl] +fake_missing_bindings = true +lang_version = [2, 4] +spirv_cross_compatibility = false +zero_initialize_workgroup_memory = false + +[hlsl] +shader_model = "V6_5" +fake_missing_bindings = true +zero_initialize_workgroup_memory = true + +[spv] +version = [1, 4] +capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader-empty.wgsl b/naga/tests/in/wgsl/mesh-shader-empty.wgsl new file mode 100644 index 00000000000..5e5169753e3 --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-empty.wgsl @@ -0,0 +1,42 @@ +// An empty WGSL shader to check that task payload/mesh output +// are still properly written without being used in the shader + +enable mesh_shading; + +struct TaskPayload { + colorMask: vec4, + visible: bool, +} +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) color: vec4, +} +struct PrimitiveOutput { + @builtin(triangle_indices) index: vec3, + @builtin(cull_primitive) cull: bool, + @per_primitive @location(1) colorMask: vec4, +} + +var taskPayload: TaskPayload; +var workgroupData: f32; + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + return vec3(3, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; + +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_main() {} diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index cdc7366b415..509cf3134f3 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -10,12 +10,11 @@ const colors = array( vec4(0., 0., 1., 1.), vec4(1., 0., 0., 1.) ); + struct TaskPayload { colorMask: vec4, visible: bool, } -var taskPayload: TaskPayload; -var workgroupData: f32; struct VertexOutput { @builtin(position) position: vec4, @location(0) color: vec4, @@ -29,6 +28,9 @@ struct PrimitiveInput { @per_primitive @location(1) colorMask: vec4, } +var taskPayload: TaskPayload; +var workgroupData: f32; + @task @payload(taskPayload) @workgroup_size(1) @@ -45,8 +47,8 @@ struct MeshOutput { @builtin(vertex_count) vertex_count: u32, @builtin(primitive_count) primitive_count: u32, } - var mesh_output: MeshOutput; + @mesh(mesh_output) @payload(taskPayload) @workgroup_size(1) @@ -68,6 +70,7 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } + @fragment fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4 { return vertex.color * primitive.colorMask; diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron new file mode 100644 index 00000000000..e1d6574109b --- /dev/null +++ b/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron @@ -0,0 +1,100 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ], + functions: [], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ | WRITE"), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ"), + ("READ"), + ], + expressions: [], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ], + const_expression_types: [], +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron new file mode 100644 index 00000000000..c48446dc8cb --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron @@ -0,0 +1,277 @@ +( + types: [ + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Bool, + width: 1, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 0, + binding: None, + offset: 0, + ), + ( + name: Some("visible"), + ty: 1, + binding: None, + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 0, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ( + name: Some("color"), + ty: 0, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("index"), + ty: 5, + binding: Some(BuiltIn(TriangleIndices)), + offset: 0, + ), + ( + name: Some("cull"), + ty: 1, + binding: Some(BuiltIn(CullPrimitive)), + offset: 12, + ), + ( + name: Some("colorMask"), + ty: 0, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(3), + stride: 32, + ), + ), + ( + name: None, + inner: Array( + base: 6, + size: Constant(1), + stride: 32, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 7, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 8, + binding: Some(BuiltIn(Primitives)), + offset: 96, + ), + ( + name: Some("vertex_count"), + ty: 4, + binding: Some(BuiltIn(VertexCount)), + offset: 128, + ), + ( + name: Some("primitive_count"), + ty: 4, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 132, + ), + ], + span: 144, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 2, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 9, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 5, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(3)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 5, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 3, + primitive_output_type: 6, + output_variable: 1, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron new file mode 100644 index 00000000000..d32fbceb184 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron @@ -0,0 +1,291 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Float, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: None, + inner: Scalar(( + kind: Bool, + width: 1, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("colorMask"), + ty: 1, + binding: None, + offset: 0, + ), + ( + name: Some("visible"), + ty: 2, + binding: None, + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 1, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ( + name: Some("color"), + ty: 1, + binding: Some(Location( + location: 0, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: false, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("index"), + ty: 6, + binding: Some(BuiltIn(TriangleIndices)), + offset: 0, + ), + ( + name: Some("cull"), + ty: 2, + binding: Some(BuiltIn(CullPrimitive)), + offset: 12, + ), + ( + name: Some("colorMask"), + ty: 1, + binding: Some(Location( + location: 1, + interpolation: Some(Perspective), + sampling: Some(Center), + blend_src: None, + per_primitive: true, + )), + offset: 16, + ), + ], + span: 32, + ), + ), + ( + name: None, + inner: Array( + base: 4, + size: Constant(3), + stride: 32, + ), + ), + ( + name: None, + inner: Array( + base: 7, + size: Constant(1), + stride: 32, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 8, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 9, + binding: Some(BuiltIn(Primitives)), + offset: 96, + ), + ( + name: Some("vertex_count"), + ty: 5, + binding: Some(BuiltIn(VertexCount)), + offset: 128, + ), + ( + name: Some("primitive_count"), + ty: 5, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 132, + ), + ], + span: 144, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 3, + init: None, + ), + ( + name: Some("workgroupData"), + space: WorkGroup, + binding: None, + ty: 0, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 10, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 6, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(3)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 6, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Triangles, + max_vertices: 3, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 4, + primitive_output_type: 7, + output_variable: 2, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file From afb16fa9bae2760d78be74cfc96a4fa107747c06 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 8 Nov 2025 14:19:50 -0600 Subject: [PATCH 076/110] Updated code for the newly merged stuff --- naga/src/valid/interface.rs | 1 - naga/tests/in/wgsl/mesh-shader-empty.toml | 2 +- .../out/analysis/wgsl-mesh-shader.info.ron | 19 -- .../tests/out/ir/wgsl-mesh-shader.compact.ron | 32 --- naga/tests/out/ir/wgsl-mesh-shader.ron | 32 --- .../out/spv/wgsl-mesh-shader-empty.spvasm | 192 ++++++++++++++++++ naga/tests/out/spv/wgsl-mesh-shader.spvasm | 172 +++------------- 7 files changed, 218 insertions(+), 232 deletions(-) create mode 100644 naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index c49f913e494..fd33d3adebf 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -1151,7 +1151,6 @@ impl super::Validator { mesh_info.primitive_output_type, MeshOutputType::PrimitiveOutput, )?; - info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); } Ok(info) diff --git a/naga/tests/in/wgsl/mesh-shader-empty.toml b/naga/tests/in/wgsl/mesh-shader-empty.toml index 1f8b4e23baa..2dc97fa2c87 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.toml +++ b/naga/tests/in/wgsl/mesh-shader-empty.toml @@ -1,7 +1,7 @@ # Stolen from ray-query.toml god_mode = true -targets = "IR | ANALYSIS" +targets = "IR | ANALYSIS | SPIRV" [msl] fake_missing_bindings = true diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 757674635e3..eacd33ad0f1 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -1399,25 +1399,6 @@ dual_source_blending: false, diagnostic_filter_leaf: None, ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ"), - (""), - ("READ"), - ], - expressions: [], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), ( flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron index aefa331f48f..1147b017f5c 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -907,38 +907,6 @@ )), task_payload: Some(0), ), - ( - name: "ms_no_write", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_no_write"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 4, - primitive_output_type: 7, - output_variable: 2, - )), - task_payload: Some(0), - ), ( name: "fs_main", stage: Fragment, diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron index aefa331f48f..1147b017f5c 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -907,38 +907,6 @@ )), task_payload: Some(0), ), - ( - name: "ms_no_write", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_no_write"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 4, - primitive_output_type: 7, - output_variable: 2, - )), - task_payload: Some(0), - ), ( name: "fs_main", stage: Fragment, diff --git a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm new file mode 100644 index 00000000000..20644fd38a6 --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm @@ -0,0 +1,192 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 117 +OpCapability Shader +OpCapability MeshShadingEXT +OpExtension "SPV_EXT_mesh_shader" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TaskEXT %21 "ts_main" %16 +OpEntryPoint MeshEXT %53 "ms_main" %16 %29 %37 %41 %45 %48 %52 %18 %56 +OpExecutionMode %21 LocalSize 1 1 1 +OpExecutionMode %53 LocalSize 1 1 1 +OpExecutionMode %53 OutputTrianglesNV +OpExecutionMode %53 OutputVertices 3 +OpExecutionMode %53 OutputPrimitivesNV 1 +OpDecorate %29 BuiltIn LocalInvocationIndex +OpMemberDecorate %34 0 BuiltIn Position +OpDecorate %34 Block +OpMemberDecorate %38 0 BuiltIn CullPrimitiveEXT +OpDecorate %38 Block +OpDecorate %41 PerPrimitiveNV +OpDecorate %42 Block +OpMemberDecorate %42 0 Location 0 +OpDecorate %48 PerPrimitiveNV +OpDecorate %48 BuiltIn PrimitiveTriangleIndicesEXT +OpDecorate %49 Block +OpMemberDecorate %49 0 Location 1 +OpDecorate %52 PerPrimitiveNV +OpMemberDecorate %6 0 Offset 0 +OpMemberDecorate %6 1 Offset 16 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %7 1 Offset 16 +OpMemberDecorate %10 0 Offset 0 +OpMemberDecorate %10 1 Offset 12 +OpMemberDecorate %10 2 Offset 16 +OpDecorate %11 ArrayStride 32 +OpDecorate %13 ArrayStride 32 +OpMemberDecorate %15 0 Offset 0 +OpMemberDecorate %15 1 Offset 96 +OpMemberDecorate %15 2 Offset 128 +OpMemberDecorate %15 3 Offset 132 +OpDecorate %56 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%4 = OpTypeFloat 32 +%3 = OpTypeVector %4 4 +%5 = OpTypeBool +%6 = OpTypeStruct %3 %5 +%7 = OpTypeStruct %3 %3 +%8 = OpTypeInt 32 0 +%9 = OpTypeVector %8 3 +%10 = OpTypeStruct %9 %5 %3 +%12 = OpConstant %8 3 +%11 = OpTypeArray %7 %12 +%14 = OpConstant %8 1 +%13 = OpTypeArray %10 %14 +%15 = OpTypeStruct %11 %13 %8 %8 +%17 = OpTypePointer TaskPayloadWorkgroupEXT %6 +%16 = OpVariable %17 TaskPayloadWorkgroupEXT +%19 = OpTypePointer Workgroup %15 +%18 = OpVariable %19 Workgroup +%22 = OpTypeFunction %2 +%23 = OpConstantComposite %9 %12 %14 %14 +%30 = OpTypePointer Input %8 +%29 = OpVariable %30 Input +%33 = OpTypePointer Function %8 +%34 = OpTypeStruct %3 +%35 = OpTypeArray %34 %12 +%36 = OpTypePointer Output %35 +%37 = OpVariable %36 Output +%38 = OpTypeStruct %5 +%39 = OpTypeArray %38 %14 +%40 = OpTypePointer Output %39 +%41 = OpVariable %40 Output +%42 = OpTypeStruct %3 +%43 = OpTypeArray %42 %12 +%44 = OpTypePointer Output %43 +%45 = OpVariable %44 Output +%46 = OpTypeArray %9 %14 +%47 = OpTypePointer Output %46 +%48 = OpVariable %47 Output +%49 = OpTypeStruct %3 +%50 = OpTypeArray %49 %14 +%51 = OpTypePointer Output %50 +%52 = OpVariable %51 Output +%55 = OpConstantNull %15 +%57 = OpTypePointer Input %9 +%56 = OpVariable %57 Input +%59 = OpConstantNull %9 +%60 = OpTypeVector %5 3 +%65 = OpConstant %8 2 +%66 = OpConstant %8 264 +%69 = OpTypePointer Workgroup %8 +%74 = OpTypePointer Workgroup %11 +%75 = OpConstant %8 0 +%77 = OpTypePointer Workgroup %13 +%84 = OpTypePointer Workgroup %7 +%88 = OpTypePointer Output %3 +%93 = OpTypePointer Workgroup %10 +%97 = OpTypePointer Output %9 +%100 = OpTypePointer Output %5 +%21 = OpFunction %2 None %22 +%20 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpCompositeExtract %8 %23 0 +%26 = OpCompositeExtract %8 %23 1 +%27 = OpCompositeExtract %8 %23 2 +OpEmitMeshTasksEXT %25 %26 %27 %16 +OpFunctionEnd +%53 = OpFunction %2 None %22 +%28 = OpLabel +%32 = OpVariable %33 Function +%31 = OpLoad %8 %29 +OpBranch %54 +%54 = OpLabel +%58 = OpLoad %9 %56 +%61 = OpIEqual %60 %58 %59 +%62 = OpAll %5 %61 +OpSelectionMerge %63 None +OpBranchConditional %62 %64 %63 +%64 = OpLabel +OpStore %18 %55 +OpBranch %63 +%63 = OpLabel +OpControlBarrier %65 %65 %66 +OpBranch %67 +%67 = OpLabel +%68 = OpAccessChain %69 %18 %65 +%70 = OpLoad %8 %68 +%71 = OpAccessChain %69 %18 %12 +%72 = OpLoad %8 %71 +%73 = OpAccessChain %74 %18 %75 +%76 = OpAccessChain %77 %18 %14 +OpSetMeshOutputsEXT %70 %72 +OpStore %32 %31 +OpBranch %78 +%78 = OpLabel +OpLoopMerge %80 %104 None +OpBranch %103 +%103 = OpLabel +%106 = OpLoad %8 %32 +%107 = OpULessThan %5 %106 %70 +OpBranchConditional %107 %105 %80 +%105 = OpLabel +%82 = OpLoad %8 %32 +%83 = OpAccessChain %84 %73 %82 +%85 = OpLoad %7 %83 +%86 = OpCompositeExtract %3 %85 0 +%87 = OpAccessChain %88 %37 %82 %75 +OpStore %87 %86 +%89 = OpCompositeExtract %3 %85 1 +%90 = OpAccessChain %88 %45 %82 %75 +OpStore %90 %89 +OpBranch %104 +%104 = OpLabel +%108 = OpLoad %8 %32 +%109 = OpIAdd %8 %108 %14 +OpStore %32 %109 +OpBranch %78 +%80 = OpLabel +OpStore %32 %31 +OpBranch %79 +%79 = OpLabel +OpLoopMerge %81 %111 None +OpBranch %110 +%110 = OpLabel +%113 = OpLoad %8 %32 +%114 = OpULessThan %5 %113 %72 +OpBranchConditional %114 %112 %81 +%112 = OpLabel +%91 = OpLoad %8 %32 +%92 = OpAccessChain %93 %76 %91 +%94 = OpLoad %10 %92 +%95 = OpCompositeExtract %9 %94 0 +%96 = OpAccessChain %97 %48 %91 +OpStore %96 %95 +%98 = OpCompositeExtract %5 %94 1 +%99 = OpAccessChain %100 %41 %91 %75 +OpStore %99 %98 +%101 = OpCompositeExtract %3 %94 2 +%102 = OpAccessChain %88 %52 %91 %75 +OpStore %102 %101 +OpBranch %111 +%111 = OpLabel +%115 = OpLoad %8 %32 +%116 = OpIAdd %8 %115 %14 +OpStore %32 %116 +OpBranch %79 +%81 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index 0ee4b56ec2c..f25b9ba2cdb 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.4 ; Generator: rspirv -; Bound: 261 +; Bound: 189 OpCapability Shader OpCapability MeshShadingEXT OpExtension "SPV_EXT_mesh_shader" @@ -9,18 +9,13 @@ OpExtension "SPV_EXT_mesh_shader" OpMemoryModel Logical GLSL450 OpEntryPoint TaskEXT %24 "ts_main" %17 %19 %33 OpEntryPoint MeshEXT %80 "ms_main" %54 %57 %17 %64 %68 %72 %75 %79 %19 %21 %92 -OpEntryPoint MeshEXT %196 "ms_no_write" %17 %174 %180 %184 %188 %191 %195 %21 %198 -OpEntryPoint Fragment %256 "fs_main" %247 %250 %253 %255 +OpEntryPoint Fragment %184 "fs_main" %175 %178 %181 %183 OpExecutionMode %24 LocalSize 1 1 1 OpExecutionMode %80 LocalSize 1 1 1 OpExecutionMode %80 OutputTrianglesNV OpExecutionMode %80 OutputVertices 3 OpExecutionMode %80 OutputPrimitivesNV 1 -OpExecutionMode %196 LocalSize 1 1 1 -OpExecutionMode %196 OutputTrianglesNV -OpExecutionMode %196 OutputVertices 3 -OpExecutionMode %196 OutputPrimitivesNV 1 -OpExecutionMode %256 OriginUpperLeft +OpExecutionMode %184 OriginUpperLeft OpMemberDecorate %61 0 BuiltIn Position OpDecorate %61 Block OpMemberDecorate %65 0 BuiltIn CullPrimitiveEXT @@ -33,19 +28,6 @@ OpDecorate %75 BuiltIn PrimitiveTriangleIndicesEXT OpDecorate %76 Block OpMemberDecorate %76 0 Location 1 OpDecorate %79 PerPrimitiveNV -OpDecorate %174 BuiltIn LocalInvocationIndex -OpMemberDecorate %177 0 BuiltIn Position -OpDecorate %177 Block -OpMemberDecorate %181 0 BuiltIn CullPrimitiveEXT -OpDecorate %181 Block -OpDecorate %184 PerPrimitiveNV -OpDecorate %185 Block -OpMemberDecorate %185 0 Location 0 -OpDecorate %191 PerPrimitiveNV -OpDecorate %191 BuiltIn PrimitiveTriangleIndicesEXT -OpDecorate %192 Block -OpMemberDecorate %192 0 Location 1 -OpDecorate %195 PerPrimitiveNV OpMemberDecorate %6 0 Offset 0 OpMemberDecorate %6 1 Offset 16 OpMemberDecorate %7 0 Offset 0 @@ -64,12 +46,11 @@ OpDecorate %33 BuiltIn LocalInvocationId OpDecorate %54 BuiltIn LocalInvocationIndex OpDecorate %57 BuiltIn GlobalInvocationId OpDecorate %92 BuiltIn LocalInvocationId -OpDecorate %198 BuiltIn LocalInvocationId -OpDecorate %247 BuiltIn FragCoord -OpDecorate %250 Location 0 -OpDecorate %253 Location 1 -OpDecorate %253 PerPrimitiveNV -OpDecorate %255 Location 0 +OpDecorate %175 BuiltIn FragCoord +OpDecorate %178 Location 0 +OpDecorate %181 Location 1 +OpDecorate %181 PerPrimitiveNV +OpDecorate %183 Location 0 %2 = OpTypeVoid %3 = OpTypeFloat 32 %4 = OpTypeVector %3 4 @@ -152,32 +133,11 @@ OpDecorate %255 Location 0 %145 = OpTypePointer Output %4 %153 = OpTypePointer Output %9 %156 = OpTypePointer Output %5 -%174 = OpVariable %55 Input -%177 = OpTypeStruct %4 -%178 = OpTypeArray %177 %13 -%179 = OpTypePointer Output %178 -%180 = OpVariable %179 Output -%181 = OpTypeStruct %5 -%182 = OpTypeArray %181 %15 -%183 = OpTypePointer Output %182 -%184 = OpVariable %183 Output -%185 = OpTypeStruct %4 -%186 = OpTypeArray %185 %13 -%187 = OpTypePointer Output %186 -%188 = OpVariable %187 Output -%189 = OpTypeArray %9 %15 -%190 = OpTypePointer Output %189 -%191 = OpVariable %190 Output -%192 = OpTypeStruct %4 -%193 = OpTypeArray %192 %15 -%194 = OpTypePointer Output %193 -%195 = OpVariable %194 Output -%198 = OpVariable %34 Input -%248 = OpTypePointer Input %4 -%247 = OpVariable %248 Input -%250 = OpVariable %248 Input -%253 = OpVariable %248 Input -%255 = OpVariable %145 Output +%176 = OpTypePointer Input %4 +%175 = OpVariable %176 Input +%178 = OpVariable %176 Input +%181 = OpVariable %176 Input +%183 = OpVariable %145 Output %24 = OpFunction %2 None %25 %23 = OpLabel OpBranch %31 @@ -323,100 +283,18 @@ OpBranch %137 %139 = OpLabel OpReturn OpFunctionEnd -%196 = OpFunction %2 None %25 +%184 = OpFunction %2 None %25 %173 = OpLabel -%176 = OpVariable %60 Function -%175 = OpLoad %8 %174 -OpBranch %197 -%197 = OpLabel -%199 = OpLoad %9 %198 -%200 = OpIEqual %37 %199 %36 -%201 = OpAll %5 %200 -OpSelectionMerge %202 None -OpBranchConditional %201 %203 %202 -%203 = OpLabel -OpStore %21 %91 -OpBranch %202 -%202 = OpLabel -OpControlBarrier %42 %42 %43 -OpBranch %204 -%204 = OpLabel -%205 = OpAccessChain %99 %21 %42 -%206 = OpLoad %8 %205 -%207 = OpAccessChain %99 %21 %13 -%208 = OpLoad %8 %207 -%209 = OpAccessChain %102 %21 %46 -%210 = OpAccessChain %120 %21 %15 -OpSetMeshOutputsEXT %206 %208 -OpStore %176 %175 -OpBranch %211 -%211 = OpLabel -OpLoopMerge %213 %232 None -OpBranch %231 -%231 = OpLabel -%234 = OpLoad %8 %176 -%235 = OpULessThan %5 %234 %206 -OpBranchConditional %235 %233 %213 -%233 = OpLabel -%215 = OpLoad %8 %176 -%216 = OpAccessChain %103 %209 %215 -%217 = OpLoad %7 %216 -%218 = OpCompositeExtract %4 %217 0 -%219 = OpAccessChain %145 %180 %215 %46 -OpStore %219 %218 -%220 = OpCompositeExtract %4 %217 1 -%221 = OpAccessChain %145 %188 %215 %46 -OpStore %221 %220 -OpBranch %232 -%232 = OpLabel -%236 = OpLoad %8 %176 -%237 = OpIAdd %8 %236 %15 -OpStore %176 %237 -OpBranch %211 -%213 = OpLabel -OpStore %176 %175 -OpBranch %212 -%212 = OpLabel -OpLoopMerge %214 %239 None -OpBranch %238 -%238 = OpLabel -%241 = OpLoad %8 %176 -%242 = OpULessThan %5 %241 %208 -OpBranchConditional %242 %240 %214 -%240 = OpLabel -%222 = OpLoad %8 %176 -%223 = OpAccessChain %121 %210 %222 -%224 = OpLoad %10 %223 -%225 = OpCompositeExtract %9 %224 0 -%226 = OpAccessChain %153 %191 %222 -OpStore %226 %225 -%227 = OpCompositeExtract %5 %224 1 -%228 = OpAccessChain %156 %184 %222 %46 -OpStore %228 %227 -%229 = OpCompositeExtract %4 %224 2 -%230 = OpAccessChain %145 %195 %222 %46 -OpStore %230 %229 -OpBranch %239 -%239 = OpLabel -%243 = OpLoad %8 %176 -%244 = OpIAdd %8 %243 %15 -OpStore %176 %244 -OpBranch %212 -%214 = OpLabel -OpReturn -OpFunctionEnd -%256 = OpFunction %2 None %25 -%245 = OpLabel -%249 = OpLoad %4 %247 -%251 = OpLoad %4 %250 -%246 = OpCompositeConstruct %7 %249 %251 -%254 = OpLoad %4 %253 -%252 = OpCompositeConstruct %11 %254 -OpBranch %257 -%257 = OpLabel -%258 = OpCompositeExtract %4 %246 1 -%259 = OpCompositeExtract %4 %252 0 -%260 = OpFMul %4 %258 %259 -OpStore %255 %260 +%177 = OpLoad %4 %175 +%179 = OpLoad %4 %178 +%174 = OpCompositeConstruct %7 %177 %179 +%182 = OpLoad %4 %181 +%180 = OpCompositeConstruct %11 %182 +OpBranch %185 +%185 = OpLabel +%186 = OpCompositeExtract %4 %174 1 +%187 = OpCompositeExtract %4 %180 0 +%188 = OpFMul %4 %186 %187 +OpStore %183 %188 OpReturn OpFunctionEnd \ No newline at end of file From 1d13726d07a5fab6df13f0a02f7e7d3fd768fe14 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 09:34:34 -0600 Subject: [PATCH 077/110] Paved the way for primitive-index stuff --- docs/api-specs/mesh_shading.md | 4 ++ naga/src/ir/mod.rs | 2 +- naga/src/valid/interface.rs | 5 +- naga/tests/in/wgsl/mesh-shader.wgsl | 6 +- .../out/analysis/wgsl-mesh-shader.info.ron | 60 +++++++++++++++++++ .../tests/out/ir/wgsl-mesh-shader.compact.ron | 60 ++++++++++++++----- naga/tests/out/ir/wgsl-mesh-shader.ron | 60 ++++++++++++++----- 7 files changed, 165 insertions(+), 32 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 9c98c684e76..c1958671737 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -183,6 +183,8 @@ The primitive output type `P` must be a struct type, every member of which eithe The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader. +Mesh shaders may write to the `primitive_index` builtin. This is treated just like a field decorated with `@location`, so if the mesh shader outputs `primitive_index` the fragment shader must input it and vice versa. + Mesh shaders can use compute and mesh shader builtin inputs, in addition to `view_index`, and if no task shader is present, `draw_id`. ### Fragment shader @@ -220,6 +222,7 @@ struct VertexOutput { struct PrimitiveOutput { @builtin(triangle_indices) index: vec3, @builtin(cull_primitive) cull: bool, + @builtin(primitive_index) primitive_index: u32, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @@ -266,6 +269,7 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].index = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].primitive_index = 1; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } diff --git a/naga/src/ir/mod.rs b/naga/src/ir/mod.rs index 097220a46bb..5aa411b72e5 100644 --- a/naga/src/ir/mod.rs +++ b/naga/src/ir/mod.rs @@ -409,7 +409,7 @@ pub enum BuiltIn { PointCoord, /// Read in fragment shaders FrontFacing, - /// Read in fragment shaders, in the future may written in mesh shaders + /// Read in fragment shaders, written in mesh shaders PrimitiveIndex, /// Read in fragment shaders Barycentric, diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index fd33d3adebf..00f39b010d0 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -317,7 +317,10 @@ impl VaryingContext<'_> { *ty_inner == Ti::Scalar(crate::Scalar::BOOL), ), Bi::PrimitiveIndex => ( - self.stage == St::Fragment && !self.output, + (self.stage == St::Fragment && !self.output) + || (self.stage == St::Mesh + && self.output + && self.mesh_output_type == MeshOutputType::PrimitiveOutput), *ty_inner == Ti::Scalar(crate::Scalar::U32), ), Bi::Barycentric => ( diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index 509cf3134f3..0689bd92f54 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -20,8 +20,9 @@ struct VertexOutput { @location(0) color: vec4, } struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, + @builtin(triangle_indices) indices: vec3, @builtin(cull_primitive) cull: bool, + @builtin(primitive_index) primitive_index: u32, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @@ -66,8 +67,9 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.vertices[2].position = positions[2]; mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; - mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].indices = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; + mesh_output.primitives[0].primitive_index = 1; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index eacd33ad0f1..49dbcc5fcc5 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -1325,6 +1325,66 @@ space: WorkGroup, )), ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 5, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 11, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 10, + space: WorkGroup, + )), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: Some(2), + ty: Value(Pointer( + base: 7, + space: WorkGroup, + )), + ), ( uniformity: ( non_uniform_result: None, diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron index 1147b017f5c..df54d8fdb78 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -94,7 +94,7 @@ inner: Struct( members: [ ( - name: Some("index"), + name: Some("indices"), ty: 6, binding: Some(BuiltIn(TriangleIndices)), offset: 0, @@ -105,6 +105,12 @@ binding: Some(BuiltIn(CullPrimitive)), offset: 12, ), + ( + name: Some("primitive_index"), + ty: 5, + binding: Some(BuiltIn(PrimitiveIndex)), + offset: 16, + ), ( name: Some("colorMask"), ty: 1, @@ -115,10 +121,10 @@ blend_src: None, per_primitive: true, )), - offset: 16, + offset: 32, ), ], - span: 32, + span: 48, ), ), ( @@ -154,7 +160,7 @@ inner: Array( base: 7, size: Constant(1), - stride: 32, + stride: 48, ), ), ( @@ -177,16 +183,16 @@ name: Some("vertex_count"), ty: 5, binding: Some(BuiltIn(VertexCount)), - offset: 128, + offset: 144, ), ( name: Some("primitive_count"), ty: 5, binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, + offset: 148, ), ], - span: 144, + span: 160, ), ), ], @@ -622,6 +628,20 @@ base: 94, index: 2, ), + Literal(U32(1)), + GlobalVariable(2), + AccessIndex( + base: 97, + index: 1, + ), + AccessIndex( + base: 98, + index: 0, + ), + AccessIndex( + base: 99, + index: 3, + ), Literal(F32(1.0)), Literal(F32(0.0)), Literal(F32(1.0)), @@ -629,10 +649,10 @@ Compose( ty: 1, components: [ - 96, - 97, - 98, - 99, + 101, + 102, + 103, + 104, ], ), ], @@ -881,13 +901,25 @@ start: 94, end: 96, )), + Store( + pointer: 95, + value: 96, + ), + Emit(( + start: 98, + end: 99, + )), Emit(( - start: 100, + start: 99, end: 101, )), + Emit(( + start: 105, + end: 106, + )), Store( - pointer: 95, - value: 100, + pointer: 100, + value: 105, ), Return( value: None, diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron index 1147b017f5c..df54d8fdb78 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -94,7 +94,7 @@ inner: Struct( members: [ ( - name: Some("index"), + name: Some("indices"), ty: 6, binding: Some(BuiltIn(TriangleIndices)), offset: 0, @@ -105,6 +105,12 @@ binding: Some(BuiltIn(CullPrimitive)), offset: 12, ), + ( + name: Some("primitive_index"), + ty: 5, + binding: Some(BuiltIn(PrimitiveIndex)), + offset: 16, + ), ( name: Some("colorMask"), ty: 1, @@ -115,10 +121,10 @@ blend_src: None, per_primitive: true, )), - offset: 16, + offset: 32, ), ], - span: 32, + span: 48, ), ), ( @@ -154,7 +160,7 @@ inner: Array( base: 7, size: Constant(1), - stride: 32, + stride: 48, ), ), ( @@ -177,16 +183,16 @@ name: Some("vertex_count"), ty: 5, binding: Some(BuiltIn(VertexCount)), - offset: 128, + offset: 144, ), ( name: Some("primitive_count"), ty: 5, binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, + offset: 148, ), ], - span: 144, + span: 160, ), ), ], @@ -622,6 +628,20 @@ base: 94, index: 2, ), + Literal(U32(1)), + GlobalVariable(2), + AccessIndex( + base: 97, + index: 1, + ), + AccessIndex( + base: 98, + index: 0, + ), + AccessIndex( + base: 99, + index: 3, + ), Literal(F32(1.0)), Literal(F32(0.0)), Literal(F32(1.0)), @@ -629,10 +649,10 @@ Compose( ty: 1, components: [ - 96, - 97, - 98, - 99, + 101, + 102, + 103, + 104, ], ), ], @@ -881,13 +901,25 @@ start: 94, end: 96, )), + Store( + pointer: 95, + value: 96, + ), + Emit(( + start: 98, + end: 99, + )), Emit(( - start: 100, + start: 99, end: 101, )), + Emit(( + start: 105, + end: 106, + )), Store( - pointer: 95, - value: 100, + pointer: 100, + value: 105, ), Return( value: None, From 942fe122820df12cd9779569955e4c7fe6eeeebb Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 09:41:05 -0600 Subject: [PATCH 078/110] Added a single comment --- naga/src/back/spv/block.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 58434dc3843..85d9313744d 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -312,6 +312,8 @@ impl Writer { return_info: &super::MeshReturnInfo, body: &mut Vec, ) -> Result<(), Error> { + // TODO: barrier here + // This is the actual value (not pointer) // of the data to be outputted let out_var_id = return_info.out_variable_id; From 25afe48f786fbf1dba3d4ff17f1e5eb64e550f4b Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 09:51:22 -0600 Subject: [PATCH 079/110] Little bit of cleanup --- docs/api-specs/mesh_shading.md | 4 +- naga/tests/in/wgsl/mesh-shader-empty.wgsl | 9 +- naga/tests/in/wgsl/mesh-shader.wgsl | 6 +- .../analysis/wgsl-mesh-shader-empty.info.ron | 11 +- .../out/analysis/wgsl-mesh-shader.info.ron | 60 -------- .../out/ir/wgsl-mesh-shader-empty.compact.ron | 113 +++++---------- naga/tests/out/ir/wgsl-mesh-shader-empty.ron | 133 +++++------------- .../tests/out/ir/wgsl-mesh-shader.compact.ron | 60 ++------ naga/tests/out/ir/wgsl-mesh-shader.ron | 60 ++------ 9 files changed, 112 insertions(+), 344 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index c1958671737..e136e14df80 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -222,7 +222,6 @@ struct VertexOutput { struct PrimitiveOutput { @builtin(triangle_indices) index: vec3, @builtin(cull_primitive) cull: bool, - @builtin(primitive_index) primitive_index: u32, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @@ -239,7 +238,7 @@ fn ts_main() -> @builtin(mesh_task_size) vec3 { workgroupData = 1.0; taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); taskPayload.visible = true; - return vec3(3, 1, 1); + return vec3(1, 1, 1); } struct MeshOutput { @@ -269,7 +268,6 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].index = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; - mesh_output.primitives[0].primitive_index = 1; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } diff --git a/naga/tests/in/wgsl/mesh-shader-empty.wgsl b/naga/tests/in/wgsl/mesh-shader-empty.wgsl index 5e5169753e3..776479a2e0e 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.wgsl +++ b/naga/tests/in/wgsl/mesh-shader-empty.wgsl @@ -4,27 +4,22 @@ enable mesh_shading; struct TaskPayload { - colorMask: vec4, - visible: bool, + dummy: u32, } struct VertexOutput { @builtin(position) position: vec4, - @location(0) color: vec4, } struct PrimitiveOutput { @builtin(triangle_indices) index: vec3, - @builtin(cull_primitive) cull: bool, - @per_primitive @location(1) colorMask: vec4, } var taskPayload: TaskPayload; -var workgroupData: f32; @task @payload(taskPayload) @workgroup_size(1) fn ts_main() -> @builtin(mesh_task_size) vec3 { - return vec3(3, 1, 1); + return vec3(1, 1, 1); } struct MeshOutput { diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index 0689bd92f54..382a95f62f6 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -1,3 +1,5 @@ +// Main mesh shader test file. Tests most features. + enable mesh_shading; const positions = array( @@ -22,7 +24,6 @@ struct VertexOutput { struct PrimitiveOutput { @builtin(triangle_indices) indices: vec3, @builtin(cull_primitive) cull: bool, - @builtin(primitive_index) primitive_index: u32, @per_primitive @location(1) colorMask: vec4, } struct PrimitiveInput { @@ -39,7 +40,7 @@ fn ts_main() -> @builtin(mesh_task_size) vec3 { workgroupData = 1.0; taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0); taskPayload.visible = true; - return vec3(3, 1, 1); + return vec3(1, 1, 1); } struct MeshOutput { @@ -69,7 +70,6 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.primitives[0].indices = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; - mesh_output.primitives[0].primitive_index = 1; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron index e1d6574109b..9f9feac9c09 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron @@ -1,15 +1,14 @@ ( type_flags: [ ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), ], functions: [], entry_points: [ @@ -70,7 +69,7 @@ ), ref_count: 1, assignable_global: None, - ty: Handle(5), + ty: Handle(4), ), ], sampling: [], diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron index 49dbcc5fcc5..eacd33ad0f1 100644 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron @@ -1325,66 +1325,6 @@ space: WorkGroup, )), ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 5, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 10, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 7, - space: WorkGroup, - )), - ), ( uniformity: ( non_uniform_result: None, diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron index c48446dc8cb..da59ffa13a4 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron @@ -1,20 +1,10 @@ ( types: [ - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), ( name: None, inner: Scalar(( - kind: Bool, - width: 1, + kind: Uint, + width: 4, )), ), ( @@ -22,19 +12,23 @@ inner: Struct( members: [ ( - name: Some("colorMask"), + name: Some("dummy"), ty: 0, binding: None, offset: 0, ), - ( - name: Some("visible"), - ty: 1, - binding: None, - offset: 16, - ), ], - span: 32, + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), ), ), ( @@ -43,35 +37,16 @@ members: [ ( name: Some("position"), - ty: 0, + ty: 2, binding: Some(BuiltIn(Position( invariant: false, ))), offset: 0, ), - ( - name: Some("color"), - ty: 0, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - offset: 16, - ), ], - span: 32, + span: 16, ), ), - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), ( name: None, inner: Vector( @@ -88,30 +63,12 @@ members: [ ( name: Some("index"), - ty: 5, + ty: 4, binding: Some(BuiltIn(TriangleIndices)), offset: 0, ), - ( - name: Some("cull"), - ty: 1, - binding: Some(BuiltIn(CullPrimitive)), - offset: 12, - ), - ( - name: Some("colorMask"), - ty: 0, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 16, - ), ], - span: 32, + span: 16, ), ), ( @@ -119,15 +76,15 @@ inner: Array( base: 3, size: Constant(3), - stride: 32, + stride: 16, ), ), ( name: None, inner: Array( - base: 6, + base: 5, size: Constant(1), - stride: 32, + stride: 16, ), ), ( @@ -136,30 +93,30 @@ members: [ ( name: Some("vertices"), - ty: 7, + ty: 6, binding: Some(BuiltIn(Vertices)), offset: 0, ), ( name: Some("primitives"), - ty: 8, + ty: 7, binding: Some(BuiltIn(Primitives)), - offset: 96, + offset: 48, ), ( name: Some("vertex_count"), - ty: 4, + ty: 0, binding: Some(BuiltIn(VertexCount)), - offset: 128, + offset: 64, ), ( name: Some("primitive_count"), - ty: 4, + ty: 0, binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, + offset: 68, ), ], - span: 144, + span: 80, ), ), ], @@ -178,14 +135,14 @@ name: Some("taskPayload"), space: TaskPayload, binding: None, - ty: 2, + ty: 1, init: None, ), ( name: Some("mesh_output"), space: WorkGroup, binding: None, - ty: 9, + ty: 8, init: None, ), ], @@ -202,16 +159,16 @@ name: Some("ts_main"), arguments: [], result: Some(( - ty: 5, + ty: 4, binding: Some(BuiltIn(MeshTaskSize)), )), local_variables: [], expressions: [ - Literal(U32(3)), + Literal(U32(1)), Literal(U32(1)), Literal(U32(1)), Compose( - ty: 5, + ty: 4, components: [ 0, 1, @@ -265,7 +222,7 @@ max_primitives: 1, max_primitives_override: None, vertex_output_type: 3, - primitive_output_type: 6, + primitive_output_type: 5, output_variable: 1, )), task_payload: Some(0), diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron index d32fbceb184..da59ffa13a4 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron @@ -3,45 +3,32 @@ ( name: None, inner: Scalar(( - kind: Float, + kind: Uint, width: 4, )), ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: None, - inner: Scalar(( - kind: Bool, - width: 1, - )), - ), ( name: Some("TaskPayload"), inner: Struct( members: [ ( - name: Some("colorMask"), - ty: 1, + name: Some("dummy"), + ty: 0, binding: None, offset: 0, ), - ( - name: Some("visible"), - ty: 2, - binding: None, - offset: 16, - ), ], - span: 32, + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), ), ), ( @@ -50,35 +37,16 @@ members: [ ( name: Some("position"), - ty: 1, + ty: 2, binding: Some(BuiltIn(Position( invariant: false, ))), offset: 0, ), - ( - name: Some("color"), - ty: 1, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - offset: 16, - ), ], - span: 32, + span: 16, ), ), - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), ( name: None, inner: Vector( @@ -95,46 +63,28 @@ members: [ ( name: Some("index"), - ty: 6, + ty: 4, binding: Some(BuiltIn(TriangleIndices)), offset: 0, ), - ( - name: Some("cull"), - ty: 2, - binding: Some(BuiltIn(CullPrimitive)), - offset: 12, - ), - ( - name: Some("colorMask"), - ty: 1, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 16, - ), ], - span: 32, + span: 16, ), ), ( name: None, inner: Array( - base: 4, + base: 3, size: Constant(3), - stride: 32, + stride: 16, ), ), ( name: None, inner: Array( - base: 7, + base: 5, size: Constant(1), - stride: 32, + stride: 16, ), ), ( @@ -143,30 +93,30 @@ members: [ ( name: Some("vertices"), - ty: 8, + ty: 6, binding: Some(BuiltIn(Vertices)), offset: 0, ), ( name: Some("primitives"), - ty: 9, + ty: 7, binding: Some(BuiltIn(Primitives)), - offset: 96, + offset: 48, ), ( name: Some("vertex_count"), - ty: 5, + ty: 0, binding: Some(BuiltIn(VertexCount)), - offset: 128, + offset: 64, ), ( name: Some("primitive_count"), - ty: 5, + ty: 0, binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, + offset: 68, ), ], - span: 144, + span: 80, ), ), ], @@ -185,21 +135,14 @@ name: Some("taskPayload"), space: TaskPayload, binding: None, - ty: 3, - init: None, - ), - ( - name: Some("workgroupData"), - space: WorkGroup, - binding: None, - ty: 0, + ty: 1, init: None, ), ( name: Some("mesh_output"), space: WorkGroup, binding: None, - ty: 10, + ty: 8, init: None, ), ], @@ -216,16 +159,16 @@ name: Some("ts_main"), arguments: [], result: Some(( - ty: 6, + ty: 4, binding: Some(BuiltIn(MeshTaskSize)), )), local_variables: [], expressions: [ - Literal(U32(3)), + Literal(U32(1)), Literal(U32(1)), Literal(U32(1)), Compose( - ty: 6, + ty: 4, components: [ 0, 1, @@ -278,9 +221,9 @@ max_vertices_override: None, max_primitives: 1, max_primitives_override: None, - vertex_output_type: 4, - primitive_output_type: 7, - output_variable: 2, + vertex_output_type: 3, + primitive_output_type: 5, + output_variable: 1, )), task_payload: Some(0), ), diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron index df54d8fdb78..9cb3c6479c3 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron @@ -105,12 +105,6 @@ binding: Some(BuiltIn(CullPrimitive)), offset: 12, ), - ( - name: Some("primitive_index"), - ty: 5, - binding: Some(BuiltIn(PrimitiveIndex)), - offset: 16, - ), ( name: Some("colorMask"), ty: 1, @@ -121,10 +115,10 @@ blend_src: None, per_primitive: true, )), - offset: 32, + offset: 16, ), ], - span: 48, + span: 32, ), ), ( @@ -160,7 +154,7 @@ inner: Array( base: 7, size: Constant(1), - stride: 48, + stride: 32, ), ), ( @@ -183,16 +177,16 @@ name: Some("vertex_count"), ty: 5, binding: Some(BuiltIn(VertexCount)), - offset: 144, + offset: 128, ), ( name: Some("primitive_count"), ty: 5, binding: Some(BuiltIn(PrimitiveCount)), - offset: 148, + offset: 132, ), ], - span: 160, + span: 144, ), ), ], @@ -273,7 +267,7 @@ index: 1, ), Literal(Bool(true)), - Literal(U32(3)), + Literal(U32(1)), Literal(U32(1)), Literal(U32(1)), Compose( @@ -628,20 +622,6 @@ base: 94, index: 2, ), - Literal(U32(1)), - GlobalVariable(2), - AccessIndex( - base: 97, - index: 1, - ), - AccessIndex( - base: 98, - index: 0, - ), - AccessIndex( - base: 99, - index: 3, - ), Literal(F32(1.0)), Literal(F32(0.0)), Literal(F32(1.0)), @@ -649,10 +629,10 @@ Compose( ty: 1, components: [ - 101, - 102, - 103, - 104, + 96, + 97, + 98, + 99, ], ), ], @@ -901,25 +881,13 @@ start: 94, end: 96, )), - Store( - pointer: 95, - value: 96, - ), - Emit(( - start: 98, - end: 99, - )), Emit(( - start: 99, + start: 100, end: 101, )), - Emit(( - start: 105, - end: 106, - )), Store( - pointer: 100, - value: 105, + pointer: 95, + value: 100, ), Return( value: None, diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron index df54d8fdb78..9cb3c6479c3 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader.ron @@ -105,12 +105,6 @@ binding: Some(BuiltIn(CullPrimitive)), offset: 12, ), - ( - name: Some("primitive_index"), - ty: 5, - binding: Some(BuiltIn(PrimitiveIndex)), - offset: 16, - ), ( name: Some("colorMask"), ty: 1, @@ -121,10 +115,10 @@ blend_src: None, per_primitive: true, )), - offset: 32, + offset: 16, ), ], - span: 48, + span: 32, ), ), ( @@ -160,7 +154,7 @@ inner: Array( base: 7, size: Constant(1), - stride: 48, + stride: 32, ), ), ( @@ -183,16 +177,16 @@ name: Some("vertex_count"), ty: 5, binding: Some(BuiltIn(VertexCount)), - offset: 144, + offset: 128, ), ( name: Some("primitive_count"), ty: 5, binding: Some(BuiltIn(PrimitiveCount)), - offset: 148, + offset: 132, ), ], - span: 160, + span: 144, ), ), ], @@ -273,7 +267,7 @@ index: 1, ), Literal(Bool(true)), - Literal(U32(3)), + Literal(U32(1)), Literal(U32(1)), Literal(U32(1)), Compose( @@ -628,20 +622,6 @@ base: 94, index: 2, ), - Literal(U32(1)), - GlobalVariable(2), - AccessIndex( - base: 97, - index: 1, - ), - AccessIndex( - base: 98, - index: 0, - ), - AccessIndex( - base: 99, - index: 3, - ), Literal(F32(1.0)), Literal(F32(0.0)), Literal(F32(1.0)), @@ -649,10 +629,10 @@ Compose( ty: 1, components: [ - 101, - 102, - 103, - 104, + 96, + 97, + 98, + 99, ], ), ], @@ -901,25 +881,13 @@ start: 94, end: 96, )), - Store( - pointer: 95, - value: 96, - ), - Emit(( - start: 98, - end: 99, - )), Emit(( - start: 99, + start: 100, end: 101, )), - Emit(( - start: 105, - end: 106, - )), Store( - pointer: 100, - value: 105, + pointer: 95, + value: 100, ), Return( value: None, From d1f2e9231c177788bb511678ac748fe83fac58de Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 09:53:09 -0600 Subject: [PATCH 080/110] Updated snapshots --- .../out/spv/wgsl-mesh-shader-empty.spvasm | 320 ++++++++---------- naga/tests/out/spv/wgsl-mesh-shader.spvasm | 2 +- 2 files changed, 144 insertions(+), 178 deletions(-) diff --git a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm index 20644fd38a6..186f90ba133 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm @@ -1,192 +1,158 @@ ; SPIR-V ; Version: 1.4 ; Generator: rspirv -; Bound: 117 +; Bound: 98 OpCapability Shader OpCapability MeshShadingEXT OpExtension "SPV_EXT_mesh_shader" %1 = OpExtInstImport "GLSL.std.450" OpMemoryModel Logical GLSL450 -OpEntryPoint TaskEXT %21 "ts_main" %16 -OpEntryPoint MeshEXT %53 "ms_main" %16 %29 %37 %41 %45 %48 %52 %18 %56 -OpExecutionMode %21 LocalSize 1 1 1 -OpExecutionMode %53 LocalSize 1 1 1 -OpExecutionMode %53 OutputTrianglesNV -OpExecutionMode %53 OutputVertices 3 -OpExecutionMode %53 OutputPrimitivesNV 1 -OpDecorate %29 BuiltIn LocalInvocationIndex -OpMemberDecorate %34 0 BuiltIn Position -OpDecorate %34 Block -OpMemberDecorate %38 0 BuiltIn CullPrimitiveEXT -OpDecorate %38 Block -OpDecorate %41 PerPrimitiveNV -OpDecorate %42 Block -OpMemberDecorate %42 0 Location 0 -OpDecorate %48 PerPrimitiveNV -OpDecorate %48 BuiltIn PrimitiveTriangleIndicesEXT -OpDecorate %49 Block -OpMemberDecorate %49 0 Location 1 -OpDecorate %52 PerPrimitiveNV -OpMemberDecorate %6 0 Offset 0 -OpMemberDecorate %6 1 Offset 16 +OpEntryPoint TaskEXT %20 "ts_main" %15 +OpEntryPoint MeshEXT %40 "ms_main" %15 %28 %36 %39 %17 %43 +OpExecutionMode %20 LocalSize 1 1 1 +OpExecutionMode %40 LocalSize 1 1 1 +OpExecutionMode %40 OutputTrianglesNV +OpExecutionMode %40 OutputVertices 3 +OpExecutionMode %40 OutputPrimitivesNV 1 +OpDecorate %28 BuiltIn LocalInvocationIndex +OpMemberDecorate %33 0 BuiltIn Position +OpDecorate %33 Block +OpDecorate %39 PerPrimitiveNV +OpDecorate %39 BuiltIn PrimitiveTriangleIndicesEXT +OpMemberDecorate %4 0 Offset 0 OpMemberDecorate %7 0 Offset 0 -OpMemberDecorate %7 1 Offset 16 -OpMemberDecorate %10 0 Offset 0 -OpMemberDecorate %10 1 Offset 12 -OpMemberDecorate %10 2 Offset 16 -OpDecorate %11 ArrayStride 32 -OpDecorate %13 ArrayStride 32 -OpMemberDecorate %15 0 Offset 0 -OpMemberDecorate %15 1 Offset 96 -OpMemberDecorate %15 2 Offset 128 -OpMemberDecorate %15 3 Offset 132 -OpDecorate %56 BuiltIn LocalInvocationId +OpMemberDecorate %9 0 Offset 0 +OpDecorate %10 ArrayStride 16 +OpDecorate %12 ArrayStride 16 +OpMemberDecorate %14 0 Offset 0 +OpMemberDecorate %14 1 Offset 48 +OpMemberDecorate %14 2 Offset 64 +OpMemberDecorate %14 3 Offset 68 +OpDecorate %43 BuiltIn LocalInvocationId %2 = OpTypeVoid -%4 = OpTypeFloat 32 -%3 = OpTypeVector %4 4 -%5 = OpTypeBool -%6 = OpTypeStruct %3 %5 -%7 = OpTypeStruct %3 %3 -%8 = OpTypeInt 32 0 -%9 = OpTypeVector %8 3 -%10 = OpTypeStruct %9 %5 %3 -%12 = OpConstant %8 3 -%11 = OpTypeArray %7 %12 -%14 = OpConstant %8 1 -%13 = OpTypeArray %10 %14 -%15 = OpTypeStruct %11 %13 %8 %8 -%17 = OpTypePointer TaskPayloadWorkgroupEXT %6 -%16 = OpVariable %17 TaskPayloadWorkgroupEXT -%19 = OpTypePointer Workgroup %15 -%18 = OpVariable %19 Workgroup -%22 = OpTypeFunction %2 -%23 = OpConstantComposite %9 %12 %14 %14 -%30 = OpTypePointer Input %8 -%29 = OpVariable %30 Input -%33 = OpTypePointer Function %8 -%34 = OpTypeStruct %3 -%35 = OpTypeArray %34 %12 -%36 = OpTypePointer Output %35 -%37 = OpVariable %36 Output -%38 = OpTypeStruct %5 -%39 = OpTypeArray %38 %14 -%40 = OpTypePointer Output %39 -%41 = OpVariable %40 Output -%42 = OpTypeStruct %3 -%43 = OpTypeArray %42 %12 -%44 = OpTypePointer Output %43 -%45 = OpVariable %44 Output -%46 = OpTypeArray %9 %14 -%47 = OpTypePointer Output %46 -%48 = OpVariable %47 Output -%49 = OpTypeStruct %3 -%50 = OpTypeArray %49 %14 -%51 = OpTypePointer Output %50 -%52 = OpVariable %51 Output -%55 = OpConstantNull %15 -%57 = OpTypePointer Input %9 -%56 = OpVariable %57 Input -%59 = OpConstantNull %9 -%60 = OpTypeVector %5 3 -%65 = OpConstant %8 2 -%66 = OpConstant %8 264 -%69 = OpTypePointer Workgroup %8 -%74 = OpTypePointer Workgroup %11 -%75 = OpConstant %8 0 -%77 = OpTypePointer Workgroup %13 -%84 = OpTypePointer Workgroup %7 -%88 = OpTypePointer Output %3 -%93 = OpTypePointer Workgroup %10 -%97 = OpTypePointer Output %9 -%100 = OpTypePointer Output %5 -%21 = OpFunction %2 None %22 -%20 = OpLabel -OpBranch %24 -%24 = OpLabel -%25 = OpCompositeExtract %8 %23 0 -%26 = OpCompositeExtract %8 %23 1 -%27 = OpCompositeExtract %8 %23 2 -OpEmitMeshTasksEXT %25 %26 %27 %16 +%3 = OpTypeInt 32 0 +%4 = OpTypeStruct %3 +%6 = OpTypeFloat 32 +%5 = OpTypeVector %6 4 +%7 = OpTypeStruct %5 +%8 = OpTypeVector %3 3 +%9 = OpTypeStruct %8 +%11 = OpConstant %3 3 +%10 = OpTypeArray %7 %11 +%13 = OpConstant %3 1 +%12 = OpTypeArray %9 %13 +%14 = OpTypeStruct %10 %12 %3 %3 +%16 = OpTypePointer TaskPayloadWorkgroupEXT %4 +%15 = OpVariable %16 TaskPayloadWorkgroupEXT +%18 = OpTypePointer Workgroup %14 +%17 = OpVariable %18 Workgroup +%21 = OpTypeFunction %2 +%22 = OpConstantComposite %8 %13 %13 %13 +%29 = OpTypePointer Input %3 +%28 = OpVariable %29 Input +%32 = OpTypePointer Function %3 +%33 = OpTypeStruct %5 +%34 = OpTypeArray %33 %11 +%35 = OpTypePointer Output %34 +%36 = OpVariable %35 Output +%37 = OpTypeArray %8 %13 +%38 = OpTypePointer Output %37 +%39 = OpVariable %38 Output +%42 = OpConstantNull %14 +%44 = OpTypePointer Input %8 +%43 = OpVariable %44 Input +%46 = OpConstantNull %8 +%48 = OpTypeBool +%47 = OpTypeVector %48 3 +%53 = OpConstant %3 2 +%54 = OpConstant %3 264 +%57 = OpTypePointer Workgroup %3 +%62 = OpTypePointer Workgroup %10 +%63 = OpConstant %3 0 +%65 = OpTypePointer Workgroup %12 +%72 = OpTypePointer Workgroup %7 +%76 = OpTypePointer Output %5 +%79 = OpTypePointer Workgroup %9 +%83 = OpTypePointer Output %8 +%20 = OpFunction %2 None %21 +%19 = OpLabel +OpBranch %23 +%23 = OpLabel +%24 = OpCompositeExtract %3 %22 0 +%25 = OpCompositeExtract %3 %22 1 +%26 = OpCompositeExtract %3 %22 2 +OpEmitMeshTasksEXT %24 %25 %26 %15 OpFunctionEnd -%53 = OpFunction %2 None %22 -%28 = OpLabel -%32 = OpVariable %33 Function -%31 = OpLoad %8 %29 -OpBranch %54 -%54 = OpLabel -%58 = OpLoad %9 %56 -%61 = OpIEqual %60 %58 %59 -%62 = OpAll %5 %61 -OpSelectionMerge %63 None -OpBranchConditional %62 %64 %63 -%64 = OpLabel -OpStore %18 %55 -OpBranch %63 -%63 = OpLabel -OpControlBarrier %65 %65 %66 +%40 = OpFunction %2 None %21 +%27 = OpLabel +%31 = OpVariable %32 Function +%30 = OpLoad %3 %28 +OpBranch %41 +%41 = OpLabel +%45 = OpLoad %8 %43 +%49 = OpIEqual %47 %45 %46 +%50 = OpAll %48 %49 +OpSelectionMerge %51 None +OpBranchConditional %50 %52 %51 +%52 = OpLabel +OpStore %17 %42 +OpBranch %51 +%51 = OpLabel +OpControlBarrier %53 %53 %54 +OpBranch %55 +%55 = OpLabel +%56 = OpAccessChain %57 %17 %53 +%58 = OpLoad %3 %56 +%59 = OpAccessChain %57 %17 %11 +%60 = OpLoad %3 %59 +%61 = OpAccessChain %62 %17 %63 +%64 = OpAccessChain %65 %17 %13 +OpSetMeshOutputsEXT %58 %60 +OpStore %31 %30 +OpBranch %66 +%66 = OpLabel +OpLoopMerge %68 %85 None +OpBranch %84 +%84 = OpLabel +%87 = OpLoad %3 %31 +%88 = OpULessThan %48 %87 %58 +OpBranchConditional %88 %86 %68 +%86 = OpLabel +%70 = OpLoad %3 %31 +%71 = OpAccessChain %72 %61 %70 +%73 = OpLoad %7 %71 +%74 = OpCompositeExtract %5 %73 0 +%75 = OpAccessChain %76 %36 %70 %63 +OpStore %75 %74 +OpBranch %85 +%85 = OpLabel +%89 = OpLoad %3 %31 +%90 = OpIAdd %3 %89 %13 +OpStore %31 %90 +OpBranch %66 +%68 = OpLabel +OpStore %31 %30 OpBranch %67 %67 = OpLabel -%68 = OpAccessChain %69 %18 %65 -%70 = OpLoad %8 %68 -%71 = OpAccessChain %69 %18 %12 -%72 = OpLoad %8 %71 -%73 = OpAccessChain %74 %18 %75 -%76 = OpAccessChain %77 %18 %14 -OpSetMeshOutputsEXT %70 %72 -OpStore %32 %31 -OpBranch %78 -%78 = OpLabel -OpLoopMerge %80 %104 None -OpBranch %103 -%103 = OpLabel -%106 = OpLoad %8 %32 -%107 = OpULessThan %5 %106 %70 -OpBranchConditional %107 %105 %80 -%105 = OpLabel -%82 = OpLoad %8 %32 -%83 = OpAccessChain %84 %73 %82 -%85 = OpLoad %7 %83 -%86 = OpCompositeExtract %3 %85 0 -%87 = OpAccessChain %88 %37 %82 %75 -OpStore %87 %86 -%89 = OpCompositeExtract %3 %85 1 -%90 = OpAccessChain %88 %45 %82 %75 -OpStore %90 %89 -OpBranch %104 -%104 = OpLabel -%108 = OpLoad %8 %32 -%109 = OpIAdd %8 %108 %14 -OpStore %32 %109 -OpBranch %78 -%80 = OpLabel -OpStore %32 %31 -OpBranch %79 -%79 = OpLabel -OpLoopMerge %81 %111 None -OpBranch %110 -%110 = OpLabel -%113 = OpLoad %8 %32 -%114 = OpULessThan %5 %113 %72 -OpBranchConditional %114 %112 %81 -%112 = OpLabel -%91 = OpLoad %8 %32 -%92 = OpAccessChain %93 %76 %91 -%94 = OpLoad %10 %92 -%95 = OpCompositeExtract %9 %94 0 -%96 = OpAccessChain %97 %48 %91 -OpStore %96 %95 -%98 = OpCompositeExtract %5 %94 1 -%99 = OpAccessChain %100 %41 %91 %75 -OpStore %99 %98 -%101 = OpCompositeExtract %3 %94 2 -%102 = OpAccessChain %88 %52 %91 %75 -OpStore %102 %101 -OpBranch %111 -%111 = OpLabel -%115 = OpLoad %8 %32 -%116 = OpIAdd %8 %115 %14 -OpStore %32 %116 -OpBranch %79 -%81 = OpLabel +OpLoopMerge %69 %92 None +OpBranch %91 +%91 = OpLabel +%94 = OpLoad %3 %31 +%95 = OpULessThan %48 %94 %60 +OpBranchConditional %95 %93 %69 +%93 = OpLabel +%77 = OpLoad %3 %31 +%78 = OpAccessChain %79 %64 %77 +%80 = OpLoad %9 %78 +%81 = OpCompositeExtract %8 %80 0 +%82 = OpAccessChain %83 %39 %77 +OpStore %82 %81 +OpBranch %92 +%92 = OpLabel +%96 = OpLoad %3 %31 +%97 = OpIAdd %3 %96 %13 +OpStore %31 %97 +OpBranch %67 +%69 = OpLabel OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index f25b9ba2cdb..fe61986571f 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -77,7 +77,7 @@ OpDecorate %183 Location 0 %27 = OpConstant %3 0 %28 = OpConstantComposite %4 %26 %26 %27 %26 %29 = OpConstantTrue %5 -%30 = OpConstantComposite %9 %13 %15 %15 +%30 = OpConstantComposite %9 %15 %15 %15 %32 = OpConstantNull %3 %34 = OpTypePointer Input %9 %33 = OpVariable %34 Input From a15190b99da59340a64e1c15e364587689c57f0f Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 09:58:58 -0600 Subject: [PATCH 081/110] Added a barrier thing --- naga/src/back/spv/block.rs | 33 ++++++++++--------- .../out/spv/wgsl-mesh-shader-empty.spvasm | 1 + naga/tests/out/spv/wgsl-mesh-shader.spvasm | 1 + 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 85d9313744d..147847d451a 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -310,25 +310,26 @@ impl Writer { fn write_mesh_shader_return( &mut self, return_info: &super::MeshReturnInfo, - body: &mut Vec, + block: &mut Block, ) -> Result<(), Error> { - // TODO: barrier here + self.write_control_barrier(crate::Barrier::WORK_GROUP, block); // This is the actual value (not pointer) // of the data to be outputted let out_var_id = return_info.out_variable_id; // Load the actual vertex and primitive counts + // TODO: take the min of this and the maximum output count let mut load_u32_by_member_index = |member_index: u32| { let ptr_id = self.id_gen.next(); let u32_id = self.get_u32_type_id(); - body.push(Instruction::access_chain( + block.body.push(Instruction::access_chain( self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), ptr_id, out_var_id, &[self.get_constant_scalar(crate::Literal::U32(member_index))], )); let id = self.id_gen.next(); - body.push(Instruction::load(u32_id, id, ptr_id, None)); + block.body.push(Instruction::load(u32_id, id, ptr_id, None)); id }; let vert_count_id = load_u32_by_member_index( @@ -347,7 +348,7 @@ impl Writer { ); // Get pointers to the arrays of data to extract let vert_array_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( + block.body.push(Instruction::access_chain( self.get_pointer_type_id( return_info.vertex_array_type_id, spirv::StorageClass::Workgroup, @@ -363,7 +364,7 @@ impl Writer { ))], )); let prim_array_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( + block.body.push(Instruction::access_chain( self.get_pointer_type_id( return_info.primitive_array_type_id, spirv::StorageClass::Workgroup, @@ -384,7 +385,7 @@ impl Writer { let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); ins.add_operand(vert_count_id); ins.add_operand(prim_count_id); - body.push(ins); + block.body.push(ins); } // All this for a `for i in 0..num_vertices` lol @@ -397,12 +398,12 @@ impl Writer { let func_end = self.id_gen.next(); let index_var = return_info.function_variable; - body.push(Instruction::store( + block.body.push(Instruction::store( index_var, return_info.local_invocation_index_id, None, )); - body.push(Instruction::branch(vertex_loop_header)); + block.body.push(Instruction::branch(vertex_loop_header)); // This generates the instructions used to copy all parts of a single output vertex // to their individual output locations @@ -643,7 +644,7 @@ impl Writer { }; // Write vertex copy loop write_loop( - body, + &mut block.body, vertex_copy_body, vertex_loop_header, in_between_loops, @@ -652,19 +653,19 @@ impl Writer { ); // In between loops, reset the initial index { - body.push(Instruction::label(in_between_loops)); + block.body.push(Instruction::label(in_between_loops)); - body.push(Instruction::store( + block.body.push(Instruction::store( index_var, return_info.local_invocation_index_id, None, )); - body.push(Instruction::branch(prim_loop_header)); + block.body.push(Instruction::branch(prim_loop_header)); } // Write primitive copy loop write_loop( - body, + &mut block.body, primitive_copy_body, prim_loop_header, func_end, @@ -672,7 +673,7 @@ impl Writer { index_var, ); - body.push(Instruction::label(func_end)); + block.body.push(Instruction::label(func_end)); Ok(()) } } @@ -3666,7 +3667,7 @@ impl BlockContext<'_> { }) = self.function.entry_point_context { self.writer - .write_mesh_shader_return(mesh_state, &mut block.body)?; + .write_mesh_shader_return(mesh_state, &mut block)?; }; self.function.consume(block, Instruction::return_void()); return Ok(BlockExitDisposition::Discarded); diff --git a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm index 186f90ba133..f35414887cb 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm @@ -101,6 +101,7 @@ OpBranch %51 OpControlBarrier %53 %53 %54 OpBranch %55 %55 = OpLabel +OpControlBarrier %53 %53 %54 %56 = OpAccessChain %57 %17 %53 %58 = OpLoad %3 %56 %59 = OpAccessChain %57 %17 %11 diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index fe61986571f..44d2861c3e6 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -219,6 +219,7 @@ OpStore %123 %88 OpStore %128 %127 %129 = OpAccessChain %104 %21 %15 %46 %42 OpStore %129 %89 +OpControlBarrier %42 %42 %43 %130 = OpAccessChain %99 %21 %42 %131 = OpLoad %8 %130 %132 = OpAccessChain %99 %21 %13 From 4f811ee97066e1b50e42d896f74b10c2cd4c8361 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 12:42:03 -0600 Subject: [PATCH 082/110] Made it clamp the number of vertices/primitives writte --- naga/src/back/spv/block.rs | 29 +++- naga/src/back/spv/mod.rs | 2 + naga/src/back/spv/writer.rs | 4 + .../out/spv/wgsl-mesh-shader-empty.spvasm | 104 +++++------ naga/tests/out/spv/wgsl-mesh-shader.spvasm | 164 +++++++++--------- 5 files changed, 168 insertions(+), 135 deletions(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 147847d451a..a7f03826f86 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -314,14 +314,16 @@ impl Writer { ) -> Result<(), Error> { self.write_control_barrier(crate::Barrier::WORK_GROUP, block); + let u32_id = self.get_u32_type_id(); + // This is the actual value (not pointer) // of the data to be outputted let out_var_id = return_info.out_variable_id; + // Load the actual vertex and primitive counts // TODO: take the min of this and the maximum output count let mut load_u32_by_member_index = |member_index: u32| { let ptr_id = self.id_gen.next(); - let u32_id = self.get_u32_type_id(); block.body.push(Instruction::access_chain( self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), ptr_id, @@ -332,20 +334,41 @@ impl Writer { block.body.push(Instruction::load(u32_id, id, ptr_id, None)); id }; - let vert_count_id = load_u32_by_member_index( + let vert_count_id_before_max = load_u32_by_member_index( return_info .out_members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) .unwrap() as u32, ); - let prim_count_id = load_u32_by_member_index( + let prim_count_id_before_max = load_u32_by_member_index( return_info .out_members .iter() .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) .unwrap() as u32, ); + + // Clamp them to the allowed range + let vert_count_id = self.id_gen.next(); + block.body.push(Instruction::ext_inst( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + vert_count_id, + &[vert_count_id_before_max, return_info.max_vertices_constant], + )); + let prim_count_id = self.id_gen.next(); + block.body.push(Instruction::ext_inst( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + prim_count_id, + &[ + prim_count_id_before_max, + return_info.max_primitives_constant, + ], + )); // Get pointers to the arrays of data to extract let vert_array_ptr = self.id_gen.next(); block.body.push(Instruction::access_chain( diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index b1654f63783..0a71f4801ce 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -155,9 +155,11 @@ struct MeshReturnInfo { /// All members of the output variable struct type out_members: Vec, + max_vertices_constant: Word, vertex_type_id: Word, vertex_array_type_id: Word, vertex_members: Vec, + max_primitives_constant: Word, primitive_type_id: Word, primitive_array_type_id: Word, primitive_members: Vec, diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index a6bc43c4c82..dac477ba6dd 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -862,9 +862,13 @@ impl Writer { vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), vertex_array_type_id, vertex_members, + max_vertices_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), primitive_array_type_id, primitive_members, + max_primitives_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), vertex_bindings: Vec::new(), vertex_builtin_block: None, primitive_bindings: Vec::new(), diff --git a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm index f35414887cb..28fd0ff5b67 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.4 ; Generator: rspirv -; Bound: 98 +; Bound: 100 OpCapability Shader OpCapability MeshShadingEXT OpExtension "SPV_EXT_mesh_shader" @@ -67,13 +67,13 @@ OpDecorate %43 BuiltIn LocalInvocationId %53 = OpConstant %3 2 %54 = OpConstant %3 264 %57 = OpTypePointer Workgroup %3 -%62 = OpTypePointer Workgroup %10 -%63 = OpConstant %3 0 -%65 = OpTypePointer Workgroup %12 -%72 = OpTypePointer Workgroup %7 -%76 = OpTypePointer Output %5 -%79 = OpTypePointer Workgroup %9 -%83 = OpTypePointer Output %8 +%64 = OpTypePointer Workgroup %10 +%65 = OpConstant %3 0 +%67 = OpTypePointer Workgroup %12 +%74 = OpTypePointer Workgroup %7 +%78 = OpTypePointer Output %5 +%81 = OpTypePointer Workgroup %9 +%85 = OpTypePointer Output %8 %20 = OpFunction %2 None %21 %19 = OpLabel OpBranch %23 @@ -106,54 +106,56 @@ OpControlBarrier %53 %53 %54 %58 = OpLoad %3 %56 %59 = OpAccessChain %57 %17 %11 %60 = OpLoad %3 %59 -%61 = OpAccessChain %62 %17 %63 -%64 = OpAccessChain %65 %17 %13 -OpSetMeshOutputsEXT %58 %60 +%61 = OpExtInst %3 %1 UMin %58 %11 +%62 = OpExtInst %3 %1 UMin %60 %13 +%63 = OpAccessChain %64 %17 %65 +%66 = OpAccessChain %67 %17 %13 +OpSetMeshOutputsEXT %61 %62 OpStore %31 %30 -OpBranch %66 -%66 = OpLabel -OpLoopMerge %68 %85 None -OpBranch %84 -%84 = OpLabel -%87 = OpLoad %3 %31 -%88 = OpULessThan %48 %87 %58 -OpBranchConditional %88 %86 %68 +OpBranch %68 +%68 = OpLabel +OpLoopMerge %70 %87 None +OpBranch %86 %86 = OpLabel -%70 = OpLoad %3 %31 -%71 = OpAccessChain %72 %61 %70 -%73 = OpLoad %7 %71 -%74 = OpCompositeExtract %5 %73 0 -%75 = OpAccessChain %76 %36 %70 %63 -OpStore %75 %74 -OpBranch %85 -%85 = OpLabel %89 = OpLoad %3 %31 -%90 = OpIAdd %3 %89 %13 -OpStore %31 %90 -OpBranch %66 -%68 = OpLabel +%90 = OpULessThan %48 %89 %61 +OpBranchConditional %90 %88 %70 +%88 = OpLabel +%72 = OpLoad %3 %31 +%73 = OpAccessChain %74 %63 %72 +%75 = OpLoad %7 %73 +%76 = OpCompositeExtract %5 %75 0 +%77 = OpAccessChain %78 %36 %72 %65 +OpStore %77 %76 +OpBranch %87 +%87 = OpLabel +%91 = OpLoad %3 %31 +%92 = OpIAdd %3 %91 %13 +OpStore %31 %92 +OpBranch %68 +%70 = OpLabel OpStore %31 %30 -OpBranch %67 -%67 = OpLabel -OpLoopMerge %69 %92 None -OpBranch %91 -%91 = OpLabel -%94 = OpLoad %3 %31 -%95 = OpULessThan %48 %94 %60 -OpBranchConditional %95 %93 %69 +OpBranch %69 +%69 = OpLabel +OpLoopMerge %71 %94 None +OpBranch %93 %93 = OpLabel -%77 = OpLoad %3 %31 -%78 = OpAccessChain %79 %64 %77 -%80 = OpLoad %9 %78 -%81 = OpCompositeExtract %8 %80 0 -%82 = OpAccessChain %83 %39 %77 -OpStore %82 %81 -OpBranch %92 -%92 = OpLabel %96 = OpLoad %3 %31 -%97 = OpIAdd %3 %96 %13 -OpStore %31 %97 -OpBranch %67 -%69 = OpLabel +%97 = OpULessThan %48 %96 %62 +OpBranchConditional %97 %95 %71 +%95 = OpLabel +%79 = OpLoad %3 %31 +%80 = OpAccessChain %81 %66 %79 +%82 = OpLoad %9 %80 +%83 = OpCompositeExtract %8 %82 0 +%84 = OpAccessChain %85 %39 %79 +OpStore %84 %83 +OpBranch %94 +%94 = OpLabel +%98 = OpLoad %3 %31 +%99 = OpIAdd %3 %98 %13 +OpStore %31 %99 +OpBranch %69 +%71 = OpLabel OpReturn OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index 44d2861c3e6..85c63fd091e 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -1,7 +1,7 @@ ; SPIR-V ; Version: 1.4 ; Generator: rspirv -; Bound: 189 +; Bound: 191 OpCapability Shader OpCapability MeshShadingEXT OpExtension "SPV_EXT_mesh_shader" @@ -9,13 +9,13 @@ OpExtension "SPV_EXT_mesh_shader" OpMemoryModel Logical GLSL450 OpEntryPoint TaskEXT %24 "ts_main" %17 %19 %33 OpEntryPoint MeshEXT %80 "ms_main" %54 %57 %17 %64 %68 %72 %75 %79 %19 %21 %92 -OpEntryPoint Fragment %184 "fs_main" %175 %178 %181 %183 +OpEntryPoint Fragment %186 "fs_main" %177 %180 %183 %185 OpExecutionMode %24 LocalSize 1 1 1 OpExecutionMode %80 LocalSize 1 1 1 OpExecutionMode %80 OutputTrianglesNV OpExecutionMode %80 OutputVertices 3 OpExecutionMode %80 OutputPrimitivesNV 1 -OpExecutionMode %184 OriginUpperLeft +OpExecutionMode %186 OriginUpperLeft OpMemberDecorate %61 0 BuiltIn Position OpDecorate %61 Block OpMemberDecorate %65 0 BuiltIn CullPrimitiveEXT @@ -46,11 +46,11 @@ OpDecorate %33 BuiltIn LocalInvocationId OpDecorate %54 BuiltIn LocalInvocationIndex OpDecorate %57 BuiltIn GlobalInvocationId OpDecorate %92 BuiltIn LocalInvocationId -OpDecorate %175 BuiltIn FragCoord -OpDecorate %178 Location 0 -OpDecorate %181 Location 1 -OpDecorate %181 PerPrimitiveNV -OpDecorate %183 Location 0 +OpDecorate %177 BuiltIn FragCoord +OpDecorate %180 Location 0 +OpDecorate %183 Location 1 +OpDecorate %183 PerPrimitiveNV +OpDecorate %185 Location 0 %2 = OpTypeVoid %3 = OpTypeFloat 32 %4 = OpTypeVector %3 4 @@ -130,14 +130,14 @@ OpDecorate %183 Location 0 %121 = OpTypePointer Workgroup %10 %122 = OpTypePointer Workgroup %9 %124 = OpTypePointer Workgroup %5 -%145 = OpTypePointer Output %4 -%153 = OpTypePointer Output %9 -%156 = OpTypePointer Output %5 -%176 = OpTypePointer Input %4 -%175 = OpVariable %176 Input -%178 = OpVariable %176 Input -%181 = OpVariable %176 Input -%183 = OpVariable %145 Output +%147 = OpTypePointer Output %4 +%155 = OpTypePointer Output %9 +%158 = OpTypePointer Output %5 +%178 = OpTypePointer Input %4 +%177 = OpVariable %178 Input +%180 = OpVariable %178 Input +%183 = OpVariable %178 Input +%185 = OpVariable %147 Output %24 = OpFunction %2 None %25 %23 = OpLabel OpBranch %31 @@ -224,78 +224,80 @@ OpControlBarrier %42 %42 %43 %131 = OpLoad %8 %130 %132 = OpAccessChain %99 %21 %13 %133 = OpLoad %8 %132 -%134 = OpAccessChain %102 %21 %46 -%135 = OpAccessChain %120 %21 %15 -OpSetMeshOutputsEXT %131 %133 +%134 = OpExtInst %8 %1 UMin %131 %13 +%135 = OpExtInst %8 %1 UMin %133 %15 +%136 = OpAccessChain %102 %21 %46 +%137 = OpAccessChain %120 %21 %15 +OpSetMeshOutputsEXT %134 %135 OpStore %59 %56 -OpBranch %136 -%136 = OpLabel -OpLoopMerge %138 %160 None -OpBranch %159 -%159 = OpLabel -%162 = OpLoad %8 %59 -%163 = OpULessThan %5 %162 %131 -OpBranchConditional %163 %161 %138 +OpBranch %138 +%138 = OpLabel +OpLoopMerge %140 %162 None +OpBranch %161 %161 = OpLabel -%140 = OpLoad %8 %59 -%141 = OpAccessChain %103 %134 %140 -%142 = OpLoad %7 %141 -%143 = OpCompositeExtract %4 %142 0 -%144 = OpAccessChain %145 %64 %140 %46 -OpStore %144 %143 -%146 = OpCompositeExtract %4 %142 1 -%147 = OpAccessChain %145 %72 %140 %46 -OpStore %147 %146 -OpBranch %160 -%160 = OpLabel %164 = OpLoad %8 %59 -%165 = OpIAdd %8 %164 %15 -OpStore %59 %165 -OpBranch %136 -%138 = OpLabel +%165 = OpULessThan %5 %164 %134 +OpBranchConditional %165 %163 %140 +%163 = OpLabel +%142 = OpLoad %8 %59 +%143 = OpAccessChain %103 %136 %142 +%144 = OpLoad %7 %143 +%145 = OpCompositeExtract %4 %144 0 +%146 = OpAccessChain %147 %64 %142 %46 +OpStore %146 %145 +%148 = OpCompositeExtract %4 %144 1 +%149 = OpAccessChain %147 %72 %142 %46 +OpStore %149 %148 +OpBranch %162 +%162 = OpLabel +%166 = OpLoad %8 %59 +%167 = OpIAdd %8 %166 %15 +OpStore %59 %167 +OpBranch %138 +%140 = OpLabel OpStore %59 %56 -OpBranch %137 -%137 = OpLabel -OpLoopMerge %139 %167 None -OpBranch %166 -%166 = OpLabel -%169 = OpLoad %8 %59 -%170 = OpULessThan %5 %169 %133 -OpBranchConditional %170 %168 %139 +OpBranch %139 +%139 = OpLabel +OpLoopMerge %141 %169 None +OpBranch %168 %168 = OpLabel -%148 = OpLoad %8 %59 -%149 = OpAccessChain %121 %135 %148 -%150 = OpLoad %10 %149 -%151 = OpCompositeExtract %9 %150 0 -%152 = OpAccessChain %153 %75 %148 -OpStore %152 %151 -%154 = OpCompositeExtract %5 %150 1 -%155 = OpAccessChain %156 %68 %148 %46 -OpStore %155 %154 -%157 = OpCompositeExtract %4 %150 2 -%158 = OpAccessChain %145 %79 %148 %46 -OpStore %158 %157 -OpBranch %167 -%167 = OpLabel %171 = OpLoad %8 %59 -%172 = OpIAdd %8 %171 %15 -OpStore %59 %172 -OpBranch %137 -%139 = OpLabel +%172 = OpULessThan %5 %171 %135 +OpBranchConditional %172 %170 %141 +%170 = OpLabel +%150 = OpLoad %8 %59 +%151 = OpAccessChain %121 %137 %150 +%152 = OpLoad %10 %151 +%153 = OpCompositeExtract %9 %152 0 +%154 = OpAccessChain %155 %75 %150 +OpStore %154 %153 +%156 = OpCompositeExtract %5 %152 1 +%157 = OpAccessChain %158 %68 %150 %46 +OpStore %157 %156 +%159 = OpCompositeExtract %4 %152 2 +%160 = OpAccessChain %147 %79 %150 %46 +OpStore %160 %159 +OpBranch %169 +%169 = OpLabel +%173 = OpLoad %8 %59 +%174 = OpIAdd %8 %173 %15 +OpStore %59 %174 +OpBranch %139 +%141 = OpLabel OpReturn OpFunctionEnd -%184 = OpFunction %2 None %25 -%173 = OpLabel -%177 = OpLoad %4 %175 -%179 = OpLoad %4 %178 -%174 = OpCompositeConstruct %7 %177 %179 -%182 = OpLoad %4 %181 -%180 = OpCompositeConstruct %11 %182 -OpBranch %185 -%185 = OpLabel -%186 = OpCompositeExtract %4 %174 1 -%187 = OpCompositeExtract %4 %180 0 -%188 = OpFMul %4 %186 %187 -OpStore %183 %188 +%186 = OpFunction %2 None %25 +%175 = OpLabel +%179 = OpLoad %4 %177 +%181 = OpLoad %4 %180 +%176 = OpCompositeConstruct %7 %179 %181 +%184 = OpLoad %4 %183 +%182 = OpCompositeConstruct %11 %184 +OpBranch %187 +%187 = OpLabel +%188 = OpCompositeExtract %4 %176 1 +%189 = OpCompositeExtract %4 %182 0 +%190 = OpFMul %4 %188 %189 +OpStore %185 %190 OpReturn OpFunctionEnd \ No newline at end of file From 453a1501c8cc77bc0254a121a8909f7c2c866dc0 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 13:02:02 -0600 Subject: [PATCH 083/110] Added new features/capabilities for point primitives and added snapshots for stuff --- naga/src/valid/interface.rs | 20 +- naga/src/valid/mod.rs | 2 + naga/tests/in/wgsl/mesh-shader-empty.wgsl | 2 +- naga/tests/in/wgsl/mesh-shader-lines.toml | 19 ++ naga/tests/in/wgsl/mesh-shader-lines.wgsl | 37 +++ naga/tests/in/wgsl/mesh-shader-points.toml | 19 ++ naga/tests/in/wgsl/mesh-shader-points.wgsl | 37 +++ .../analysis/wgsl-mesh-shader-lines.info.ron | 100 +++++++ .../analysis/wgsl-mesh-shader-points.info.ron | 99 +++++++ .../out/ir/wgsl-mesh-shader-empty.compact.ron | 2 +- naga/tests/out/ir/wgsl-mesh-shader-empty.ron | 2 +- .../out/ir/wgsl-mesh-shader-lines.compact.ron | 244 ++++++++++++++++++ naga/tests/out/ir/wgsl-mesh-shader-lines.ron | 244 ++++++++++++++++++ .../ir/wgsl-mesh-shader-points.compact.ron | 234 +++++++++++++++++ naga/tests/out/ir/wgsl-mesh-shader-points.ron | 234 +++++++++++++++++ wgpu-core/src/device/mod.rs | 4 + wgpu-hal/src/vulkan/adapter.rs | 4 + wgpu-types/src/features.rs | 10 + 18 files changed, 1305 insertions(+), 8 deletions(-) create mode 100644 naga/tests/in/wgsl/mesh-shader-lines.toml create mode 100644 naga/tests/in/wgsl/mesh-shader-lines.wgsl create mode 100644 naga/tests/in/wgsl/mesh-shader-points.toml create mode 100644 naga/tests/in/wgsl/mesh-shader-points.wgsl create mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron create mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-lines.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron create mode 100644 naga/tests/out/ir/wgsl-mesh-shader-points.ron diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 00f39b010d0..9cb1beefbb3 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -143,10 +143,8 @@ pub enum EntryPointError { WrongTaskShaderEntryResult, #[error("Task shaders must declare a task payload output")] ExpectedTaskPayload, - #[error( - "The `MESH_SHADER` capability must be enabled to compile mesh shaders and task shaders" - )] - MeshShaderCapabilityDisabled, + #[error("Capability {0:?} is not supported")] + UnsupportedCapability(Capabilities), #[error( "Mesh shader output variable must be a struct with fields that are all allowed builtins" @@ -868,7 +866,9 @@ impl super::Validator { crate::ShaderStage::Task | crate::ShaderStage::Mesh ) && !self.capabilities.contains(Capabilities::MESH_SHADER) { - return Err(EntryPointError::MeshShaderCapabilityDisabled.with_span()); + return Err( + EntryPointError::UnsupportedCapability(Capabilities::MESH_SHADER).with_span(), + ); } if ep.early_depth_test.is_some() { let required = Capabilities::EARLY_DEPTH_TEST; @@ -1141,6 +1141,16 @@ impl super::Validator { if implied.0 != *mesh_info { return Err(EntryPointError::BadMeshOutputVariableType.with_span()); } + if mesh_info.topology == crate::MeshOutputTopology::Points + && !self + .capabilities + .contains(Capabilities::MESH_SHADER_POINT_TOPOLOGY) + { + return Err(EntryPointError::UnsupportedCapability( + Capabilities::MESH_SHADER_POINT_TOPOLOGY, + ) + .with_span()); + } self.validate_mesh_output_type( ep, diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index a5ec5affceb..0ec5c5184f2 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -190,6 +190,8 @@ bitflags::bitflags! { const SHADER_BARYCENTRICS = 1 << 29; /// Support for task shaders, mesh shaders, and per-primitive fragment inputs const MESH_SHADER = 1 << 30; + /// Support for mesh shaders which output points. + const MESH_SHADER_POINT_TOPOLOGY = 1 << 30; } } diff --git a/naga/tests/in/wgsl/mesh-shader-empty.wgsl b/naga/tests/in/wgsl/mesh-shader-empty.wgsl index 776479a2e0e..1e8c4faed3b 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.wgsl +++ b/naga/tests/in/wgsl/mesh-shader-empty.wgsl @@ -10,7 +10,7 @@ struct VertexOutput { @builtin(position) position: vec4, } struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, + @builtin(triangle_indices) indices: vec3, } var taskPayload: TaskPayload; diff --git a/naga/tests/in/wgsl/mesh-shader-lines.toml b/naga/tests/in/wgsl/mesh-shader-lines.toml new file mode 100644 index 00000000000..1f8b4e23baa --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-lines.toml @@ -0,0 +1,19 @@ +# Stolen from ray-query.toml + +god_mode = true +targets = "IR | ANALYSIS" + +[msl] +fake_missing_bindings = true +lang_version = [2, 4] +spirv_cross_compatibility = false +zero_initialize_workgroup_memory = false + +[hlsl] +shader_model = "V6_5" +fake_missing_bindings = true +zero_initialize_workgroup_memory = true + +[spv] +version = [1, 4] +capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader-lines.wgsl b/naga/tests/in/wgsl/mesh-shader-lines.wgsl new file mode 100644 index 00000000000..e4caa9e32d7 --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-lines.wgsl @@ -0,0 +1,37 @@ +// An empty WGSL shader to check that task payload/mesh output +// are still properly written without being used in the shader + +enable mesh_shading; + +struct TaskPayload { + dummy: u32, +} +struct VertexOutput { + @builtin(position) position: vec4, +} +struct PrimitiveOutput { + @builtin(line_indices) indices: vec2, +} + +var taskPayload: TaskPayload; + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + return vec3(1, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; + +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_main() {} diff --git a/naga/tests/in/wgsl/mesh-shader-points.toml b/naga/tests/in/wgsl/mesh-shader-points.toml new file mode 100644 index 00000000000..1f8b4e23baa --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-points.toml @@ -0,0 +1,19 @@ +# Stolen from ray-query.toml + +god_mode = true +targets = "IR | ANALYSIS" + +[msl] +fake_missing_bindings = true +lang_version = [2, 4] +spirv_cross_compatibility = false +zero_initialize_workgroup_memory = false + +[hlsl] +shader_model = "V6_5" +fake_missing_bindings = true +zero_initialize_workgroup_memory = true + +[spv] +version = [1, 4] +capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader-points.wgsl b/naga/tests/in/wgsl/mesh-shader-points.wgsl new file mode 100644 index 00000000000..652a83d6a6a --- /dev/null +++ b/naga/tests/in/wgsl/mesh-shader-points.wgsl @@ -0,0 +1,37 @@ +// An empty WGSL shader to check that task payload/mesh output +// are still properly written without being used in the shader + +enable mesh_shading; + +struct TaskPayload { + dummy: u32, +} +struct VertexOutput { + @builtin(position) position: vec4, +} +struct PrimitiveOutput { + @builtin(point_index) indices: u32, +} + +var taskPayload: TaskPayload; + +@task +@payload(taskPayload) +@workgroup_size(1) +fn ts_main() -> @builtin(mesh_task_size) vec3 { + return vec3(1, 1, 1); +} + +struct MeshOutput { + @builtin(vertices) vertices: array, + @builtin(primitives) primitives: array, + @builtin(vertex_count) vertex_count: u32, + @builtin(primitive_count) primitive_count: u32, +} + +var mesh_output: MeshOutput; + +@mesh(mesh_output) +@payload(taskPayload) +@workgroup_size(1) +fn ms_main() {} diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron new file mode 100644 index 00000000000..b4c8790508d --- /dev/null +++ b/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron @@ -0,0 +1,100 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ], + functions: [], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ | WRITE"), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(6), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ"), + ("READ"), + ], + expressions: [], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ], + const_expression_types: [], +) \ No newline at end of file diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron new file mode 100644 index 00000000000..fa62867576b --- /dev/null +++ b/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron @@ -0,0 +1,99 @@ +( + type_flags: [ + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), + ], + functions: [], + entry_points: [ + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ | WRITE"), + (""), + ], + expressions: [ + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Value(Scalar(( + kind: Uint, + width: 4, + ))), + ), + ( + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + ref_count: 1, + assignable_global: None, + ty: Handle(5), + ), + ], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ( + flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), + available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), + uniformity: ( + non_uniform_result: None, + requirements: (""), + ), + may_kill: false, + sampling_set: [], + global_uses: [ + ("READ"), + ("READ"), + ], + expressions: [], + sampling: [], + dual_source_blending: false, + diagnostic_filter_leaf: None, + ), + ], + const_expression_types: [], +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron index da59ffa13a4..8bdaa72da69 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron @@ -62,7 +62,7 @@ inner: Struct( members: [ ( - name: Some("index"), + name: Some("indices"), ty: 4, binding: Some(BuiltIn(TriangleIndices)), offset: 0, diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron index da59ffa13a4..8bdaa72da69 100644 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron +++ b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron @@ -62,7 +62,7 @@ inner: Struct( members: [ ( - name: Some("index"), + name: Some("indices"), ty: 4, binding: Some(BuiltIn(TriangleIndices)), offset: 0, diff --git a/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron new file mode 100644 index 00000000000..b298f296985 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron @@ -0,0 +1,244 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("dummy"), + ty: 0, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 2, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("indices"), + ty: 4, + binding: Some(BuiltIn(LineIndices)), + offset: 0, + ), + ], + span: 8, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Array( + base: 5, + size: Constant(1), + stride: 8, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 7, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 8, + binding: Some(BuiltIn(Primitives)), + offset: 32, + ), + ( + name: Some("vertex_count"), + ty: 0, + binding: Some(BuiltIn(VertexCount)), + offset: 40, + ), + ( + name: Some("primitive_count"), + ty: 0, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 44, + ), + ], + span: 48, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 1, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 9, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 6, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(1)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 6, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Lines, + max_vertices: 2, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 3, + primitive_output_type: 5, + output_variable: 1, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-lines.ron b/naga/tests/out/ir/wgsl-mesh-shader-lines.ron new file mode 100644 index 00000000000..b298f296985 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-lines.ron @@ -0,0 +1,244 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("dummy"), + ty: 0, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 2, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: None, + inner: Vector( + size: Bi, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("indices"), + ty: 4, + binding: Some(BuiltIn(LineIndices)), + offset: 0, + ), + ], + span: 8, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(2), + stride: 16, + ), + ), + ( + name: None, + inner: Array( + base: 5, + size: Constant(1), + stride: 8, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 7, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 8, + binding: Some(BuiltIn(Primitives)), + offset: 32, + ), + ( + name: Some("vertex_count"), + ty: 0, + binding: Some(BuiltIn(VertexCount)), + offset: 40, + ), + ( + name: Some("primitive_count"), + ty: 0, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 44, + ), + ], + span: 48, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 1, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 9, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 6, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(1)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 6, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Lines, + max_vertices: 2, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 3, + primitive_output_type: 5, + output_variable: 1, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron new file mode 100644 index 00000000000..558a88e28d8 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron @@ -0,0 +1,234 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("dummy"), + ty: 0, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 2, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("indices"), + ty: 0, + binding: Some(BuiltIn(PointIndex)), + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(1), + stride: 16, + ), + ), + ( + name: None, + inner: Array( + base: 4, + size: Constant(1), + stride: 4, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 6, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 7, + binding: Some(BuiltIn(Primitives)), + offset: 16, + ), + ( + name: Some("vertex_count"), + ty: 0, + binding: Some(BuiltIn(VertexCount)), + offset: 20, + ), + ( + name: Some("primitive_count"), + ty: 0, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 24, + ), + ], + span: 32, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 1, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 8, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 5, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(1)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 5, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Points, + max_vertices: 1, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 3, + primitive_output_type: 4, + output_variable: 1, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-points.ron b/naga/tests/out/ir/wgsl-mesh-shader-points.ron new file mode 100644 index 00000000000..558a88e28d8 --- /dev/null +++ b/naga/tests/out/ir/wgsl-mesh-shader-points.ron @@ -0,0 +1,234 @@ +( + types: [ + ( + name: None, + inner: Scalar(( + kind: Uint, + width: 4, + )), + ), + ( + name: Some("TaskPayload"), + inner: Struct( + members: [ + ( + name: Some("dummy"), + ty: 0, + binding: None, + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Quad, + scalar: ( + kind: Float, + width: 4, + ), + ), + ), + ( + name: Some("VertexOutput"), + inner: Struct( + members: [ + ( + name: Some("position"), + ty: 2, + binding: Some(BuiltIn(Position( + invariant: false, + ))), + offset: 0, + ), + ], + span: 16, + ), + ), + ( + name: Some("PrimitiveOutput"), + inner: Struct( + members: [ + ( + name: Some("indices"), + ty: 0, + binding: Some(BuiltIn(PointIndex)), + offset: 0, + ), + ], + span: 4, + ), + ), + ( + name: None, + inner: Vector( + size: Tri, + scalar: ( + kind: Uint, + width: 4, + ), + ), + ), + ( + name: None, + inner: Array( + base: 3, + size: Constant(1), + stride: 16, + ), + ), + ( + name: None, + inner: Array( + base: 4, + size: Constant(1), + stride: 4, + ), + ), + ( + name: Some("MeshOutput"), + inner: Struct( + members: [ + ( + name: Some("vertices"), + ty: 6, + binding: Some(BuiltIn(Vertices)), + offset: 0, + ), + ( + name: Some("primitives"), + ty: 7, + binding: Some(BuiltIn(Primitives)), + offset: 16, + ), + ( + name: Some("vertex_count"), + ty: 0, + binding: Some(BuiltIn(VertexCount)), + offset: 20, + ), + ( + name: Some("primitive_count"), + ty: 0, + binding: Some(BuiltIn(PrimitiveCount)), + offset: 24, + ), + ], + span: 32, + ), + ), + ], + special_types: ( + ray_desc: None, + ray_intersection: None, + ray_vertex_return: None, + external_texture_params: None, + external_texture_transfer_function: None, + predeclared_types: {}, + ), + constants: [], + overrides: [], + global_variables: [ + ( + name: Some("taskPayload"), + space: TaskPayload, + binding: None, + ty: 1, + init: None, + ), + ( + name: Some("mesh_output"), + space: WorkGroup, + binding: None, + ty: 8, + init: None, + ), + ], + global_expressions: [], + functions: [], + entry_points: [ + ( + name: "ts_main", + stage: Task, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ts_main"), + arguments: [], + result: Some(( + ty: 5, + binding: Some(BuiltIn(MeshTaskSize)), + )), + local_variables: [], + expressions: [ + Literal(U32(1)), + Literal(U32(1)), + Literal(U32(1)), + Compose( + ty: 5, + components: [ + 0, + 1, + 2, + ], + ), + ], + named_expressions: {}, + body: [ + Emit(( + start: 0, + end: 0, + )), + Emit(( + start: 3, + end: 4, + )), + Return( + value: Some(3), + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: None, + task_payload: Some(0), + ), + ( + name: "ms_main", + stage: Mesh, + early_depth_test: None, + workgroup_size: (1, 1, 1), + workgroup_size_overrides: None, + function: ( + name: Some("ms_main"), + arguments: [], + result: None, + local_variables: [], + expressions: [], + named_expressions: {}, + body: [ + Return( + value: None, + ), + ], + diagnostic_filter_leaf: None, + ), + mesh_info: Some(( + topology: Points, + max_vertices: 1, + max_vertices_override: None, + max_primitives: 1, + max_primitives_override: None, + vertex_output_type: 3, + primitive_output_type: 4, + output_variable: 1, + )), + task_payload: Some(0), + ), + ], + diagnostic_filters: [], + diagnostic_filter_leaf: None, + doc_comments: None, +) \ No newline at end of file diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index c9e77e8ba45..8683b37dd0e 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -518,6 +518,10 @@ pub fn create_validator( Caps::MESH_SHADER, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER), ); + caps.set( + Caps::MESH_SHADER_POINT_TOPOLOGY, + features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER_POINTS), + ); naga::valid::Validator::new(flags, caps) } diff --git a/wgpu-hal/src/vulkan/adapter.rs b/wgpu-hal/src/vulkan/adapter.rs index dbd61694fde..f890336cff4 100644 --- a/wgpu-hal/src/vulkan/adapter.rs +++ b/wgpu-hal/src/vulkan/adapter.rs @@ -917,6 +917,10 @@ impl PhysicalDeviceFeatures { F::EXPERIMENTAL_MESH_SHADER, caps.supports_extension(ext::mesh_shader::NAME), ); + features.set( + F::EXPERIMENTAL_MESH_SHADER_POINTS, + caps.supports_extension(ext::mesh_shader::NAME), + ); if let Some(ref mesh_shader) = self.mesh_shader { features.set( F::EXPERIMENTAL_MESH_SHADER_MULTIVIEW, diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index 234d01d8544..7c9816ffdba 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -1255,6 +1255,16 @@ bitflags_array! { /// /// While metal supports this in theory, the behavior of `view_index` differs from vulkan and dx12 so the feature isn't exposed. const SELECTIVE_MULTIVIEW = 1 << 54; + + /// Enables the use of point-primitive outputs from mesh shaders. Making use of this feature also requires enabling + /// `Features::EXPERIMENTAL_MESH_SHADER`. + /// + /// Supported platforms + /// - Vulkan + /// - Metal + /// + /// This is a native only feature. + const EXPERIMENTAL_MESH_SHADER_POINTS = 1 << 55; } /// Features that are not guaranteed to be supported. From 2b96eb228eaa857853de8f788a4dede0e9d6dbaa Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 13:02:52 -0600 Subject: [PATCH 084/110] Updated spec to rename something --- docs/api-specs/mesh_shading.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index e136e14df80..80a5e001046 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -220,7 +220,7 @@ struct VertexOutput { @location(0) color: vec4, } struct PrimitiveOutput { - @builtin(triangle_indices) index: vec3, + @builtin(triangle_indices) indices: vec3, @builtin(cull_primitive) cull: bool, @per_primitive @location(1) colorMask: vec4, } @@ -266,7 +266,7 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati mesh_output.vertices[2].position = positions[2]; mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask; - mesh_output.primitives[0].index = vec3(0, 1, 2); + mesh_output.primitives[0].indices = vec3(0, 1, 2); mesh_output.primitives[0].cull = !taskPayload.visible; mesh_output.primitives[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0); } From 4b7ba3adcdd4794d95401c40523a6e93917adfbc Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 13:05:55 -0600 Subject: [PATCH 085/110] Updated new snapshots with SPIR-V --- naga/tests/in/wgsl/mesh-shader-lines.toml | 2 +- naga/tests/in/wgsl/mesh-shader-points.toml | 2 +- .../out/spv/wgsl-mesh-shader-lines.spvasm | 162 ++++++++++++++++++ .../out/spv/wgsl-mesh-shader-points.spvasm | 161 +++++++++++++++++ 4 files changed, 325 insertions(+), 2 deletions(-) create mode 100644 naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm create mode 100644 naga/tests/out/spv/wgsl-mesh-shader-points.spvasm diff --git a/naga/tests/in/wgsl/mesh-shader-lines.toml b/naga/tests/in/wgsl/mesh-shader-lines.toml index 1f8b4e23baa..2dc97fa2c87 100644 --- a/naga/tests/in/wgsl/mesh-shader-lines.toml +++ b/naga/tests/in/wgsl/mesh-shader-lines.toml @@ -1,7 +1,7 @@ # Stolen from ray-query.toml god_mode = true -targets = "IR | ANALYSIS" +targets = "IR | ANALYSIS | SPIRV" [msl] fake_missing_bindings = true diff --git a/naga/tests/in/wgsl/mesh-shader-points.toml b/naga/tests/in/wgsl/mesh-shader-points.toml index 1f8b4e23baa..2dc97fa2c87 100644 --- a/naga/tests/in/wgsl/mesh-shader-points.toml +++ b/naga/tests/in/wgsl/mesh-shader-points.toml @@ -1,7 +1,7 @@ # Stolen from ray-query.toml god_mode = true -targets = "IR | ANALYSIS" +targets = "IR | ANALYSIS | SPIRV" [msl] fake_missing_bindings = true diff --git a/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm new file mode 100644 index 00000000000..d6edbf70d7b --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm @@ -0,0 +1,162 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 101 +OpCapability Shader +OpCapability MeshShadingEXT +OpExtension "SPV_EXT_mesh_shader" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TaskEXT %21 "ts_main" %16 +OpEntryPoint MeshEXT %41 "ms_main" %16 %29 %37 %40 %18 %44 +OpExecutionMode %21 LocalSize 1 1 1 +OpExecutionMode %41 LocalSize 1 1 1 +OpExecutionMode %41 OutputLinesNV +OpExecutionMode %41 OutputVertices 2 +OpExecutionMode %41 OutputPrimitivesNV 1 +OpDecorate %29 BuiltIn LocalInvocationIndex +OpMemberDecorate %34 0 BuiltIn Position +OpDecorate %34 Block +OpDecorate %40 PerPrimitiveNV +OpDecorate %40 BuiltIn PrimitiveLineIndicesEXT +OpMemberDecorate %4 0 Offset 0 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %9 0 Offset 0 +OpDecorate %11 ArrayStride 16 +OpDecorate %13 ArrayStride 8 +OpMemberDecorate %15 0 Offset 0 +OpMemberDecorate %15 1 Offset 32 +OpMemberDecorate %15 2 Offset 40 +OpMemberDecorate %15 3 Offset 44 +OpDecorate %44 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeStruct %3 +%6 = OpTypeFloat 32 +%5 = OpTypeVector %6 4 +%7 = OpTypeStruct %5 +%8 = OpTypeVector %3 2 +%9 = OpTypeStruct %8 +%10 = OpTypeVector %3 3 +%12 = OpConstant %3 2 +%11 = OpTypeArray %7 %12 +%14 = OpConstant %3 1 +%13 = OpTypeArray %9 %14 +%15 = OpTypeStruct %11 %13 %3 %3 +%17 = OpTypePointer TaskPayloadWorkgroupEXT %4 +%16 = OpVariable %17 TaskPayloadWorkgroupEXT +%19 = OpTypePointer Workgroup %15 +%18 = OpVariable %19 Workgroup +%22 = OpTypeFunction %2 +%23 = OpConstantComposite %10 %14 %14 %14 +%30 = OpTypePointer Input %3 +%29 = OpVariable %30 Input +%33 = OpTypePointer Function %3 +%34 = OpTypeStruct %5 +%35 = OpTypeArray %34 %12 +%36 = OpTypePointer Output %35 +%37 = OpVariable %36 Output +%38 = OpTypeArray %8 %14 +%39 = OpTypePointer Output %38 +%40 = OpVariable %39 Output +%43 = OpConstantNull %15 +%45 = OpTypePointer Input %10 +%44 = OpVariable %45 Input +%47 = OpConstantNull %10 +%49 = OpTypeBool +%48 = OpTypeVector %49 3 +%54 = OpConstant %3 264 +%57 = OpTypePointer Workgroup %3 +%60 = OpConstant %3 3 +%65 = OpTypePointer Workgroup %11 +%66 = OpConstant %3 0 +%68 = OpTypePointer Workgroup %13 +%75 = OpTypePointer Workgroup %7 +%79 = OpTypePointer Output %5 +%82 = OpTypePointer Workgroup %9 +%86 = OpTypePointer Output %8 +%21 = OpFunction %2 None %22 +%20 = OpLabel +OpBranch %24 +%24 = OpLabel +%25 = OpCompositeExtract %3 %23 0 +%26 = OpCompositeExtract %3 %23 1 +%27 = OpCompositeExtract %3 %23 2 +OpEmitMeshTasksEXT %25 %26 %27 %16 +OpFunctionEnd +%41 = OpFunction %2 None %22 +%28 = OpLabel +%32 = OpVariable %33 Function +%31 = OpLoad %3 %29 +OpBranch %42 +%42 = OpLabel +%46 = OpLoad %10 %44 +%50 = OpIEqual %48 %46 %47 +%51 = OpAll %49 %50 +OpSelectionMerge %52 None +OpBranchConditional %51 %53 %52 +%53 = OpLabel +OpStore %18 %43 +OpBranch %52 +%52 = OpLabel +OpControlBarrier %12 %12 %54 +OpBranch %55 +%55 = OpLabel +OpControlBarrier %12 %12 %54 +%56 = OpAccessChain %57 %18 %12 +%58 = OpLoad %3 %56 +%59 = OpAccessChain %57 %18 %60 +%61 = OpLoad %3 %59 +%62 = OpExtInst %3 %1 UMin %58 %12 +%63 = OpExtInst %3 %1 UMin %61 %14 +%64 = OpAccessChain %65 %18 %66 +%67 = OpAccessChain %68 %18 %14 +OpSetMeshOutputsEXT %62 %63 +OpStore %32 %31 +OpBranch %69 +%69 = OpLabel +OpLoopMerge %71 %88 None +OpBranch %87 +%87 = OpLabel +%90 = OpLoad %3 %32 +%91 = OpULessThan %49 %90 %62 +OpBranchConditional %91 %89 %71 +%89 = OpLabel +%73 = OpLoad %3 %32 +%74 = OpAccessChain %75 %64 %73 +%76 = OpLoad %7 %74 +%77 = OpCompositeExtract %5 %76 0 +%78 = OpAccessChain %79 %37 %73 %66 +OpStore %78 %77 +OpBranch %88 +%88 = OpLabel +%92 = OpLoad %3 %32 +%93 = OpIAdd %3 %92 %14 +OpStore %32 %93 +OpBranch %69 +%71 = OpLabel +OpStore %32 %31 +OpBranch %70 +%70 = OpLabel +OpLoopMerge %72 %95 None +OpBranch %94 +%94 = OpLabel +%97 = OpLoad %3 %32 +%98 = OpULessThan %49 %97 %63 +OpBranchConditional %98 %96 %72 +%96 = OpLabel +%80 = OpLoad %3 %32 +%81 = OpAccessChain %82 %67 %80 +%83 = OpLoad %9 %81 +%84 = OpCompositeExtract %8 %83 0 +%85 = OpAccessChain %86 %40 %80 +OpStore %85 %84 +OpBranch %95 +%95 = OpLabel +%99 = OpLoad %3 %32 +%100 = OpIAdd %3 %99 %14 +OpStore %32 %100 +OpBranch %70 +%72 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm new file mode 100644 index 00000000000..109d572df66 --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm @@ -0,0 +1,161 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 100 +OpCapability Shader +OpCapability MeshShadingEXT +OpExtension "SPV_EXT_mesh_shader" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint TaskEXT %19 "ts_main" %14 +OpEntryPoint MeshEXT %39 "ms_main" %14 %27 %35 %38 %16 %42 +OpExecutionMode %19 LocalSize 1 1 1 +OpExecutionMode %39 LocalSize 1 1 1 +OpExecutionMode %39 OutputPoints +OpExecutionMode %39 OutputVertices 1 +OpExecutionMode %39 OutputPrimitivesNV 1 +OpDecorate %27 BuiltIn LocalInvocationIndex +OpMemberDecorate %32 0 BuiltIn Position +OpDecorate %32 Block +OpDecorate %38 PerPrimitiveNV +OpDecorate %38 BuiltIn PrimitivePointIndicesEXT +OpMemberDecorate %4 0 Offset 0 +OpMemberDecorate %7 0 Offset 0 +OpMemberDecorate %8 0 Offset 0 +OpDecorate %10 ArrayStride 16 +OpDecorate %12 ArrayStride 4 +OpMemberDecorate %13 0 Offset 0 +OpMemberDecorate %13 1 Offset 16 +OpMemberDecorate %13 2 Offset 20 +OpMemberDecorate %13 3 Offset 24 +OpDecorate %42 BuiltIn LocalInvocationId +%2 = OpTypeVoid +%3 = OpTypeInt 32 0 +%4 = OpTypeStruct %3 +%6 = OpTypeFloat 32 +%5 = OpTypeVector %6 4 +%7 = OpTypeStruct %5 +%8 = OpTypeStruct %3 +%9 = OpTypeVector %3 3 +%11 = OpConstant %3 1 +%10 = OpTypeArray %7 %11 +%12 = OpTypeArray %8 %11 +%13 = OpTypeStruct %10 %12 %3 %3 +%15 = OpTypePointer TaskPayloadWorkgroupEXT %4 +%14 = OpVariable %15 TaskPayloadWorkgroupEXT +%17 = OpTypePointer Workgroup %13 +%16 = OpVariable %17 Workgroup +%20 = OpTypeFunction %2 +%21 = OpConstantComposite %9 %11 %11 %11 +%28 = OpTypePointer Input %3 +%27 = OpVariable %28 Input +%31 = OpTypePointer Function %3 +%32 = OpTypeStruct %5 +%33 = OpTypeArray %32 %11 +%34 = OpTypePointer Output %33 +%35 = OpVariable %34 Output +%36 = OpTypeArray %3 %11 +%37 = OpTypePointer Output %36 +%38 = OpVariable %37 Output +%41 = OpConstantNull %13 +%43 = OpTypePointer Input %9 +%42 = OpVariable %43 Input +%45 = OpConstantNull %9 +%47 = OpTypeBool +%46 = OpTypeVector %47 3 +%52 = OpConstant %3 2 +%53 = OpConstant %3 264 +%56 = OpTypePointer Workgroup %3 +%59 = OpConstant %3 3 +%64 = OpTypePointer Workgroup %10 +%65 = OpConstant %3 0 +%67 = OpTypePointer Workgroup %12 +%74 = OpTypePointer Workgroup %7 +%78 = OpTypePointer Output %5 +%81 = OpTypePointer Workgroup %8 +%85 = OpTypePointer Output %3 +%19 = OpFunction %2 None %20 +%18 = OpLabel +OpBranch %22 +%22 = OpLabel +%23 = OpCompositeExtract %3 %21 0 +%24 = OpCompositeExtract %3 %21 1 +%25 = OpCompositeExtract %3 %21 2 +OpEmitMeshTasksEXT %23 %24 %25 %14 +OpFunctionEnd +%39 = OpFunction %2 None %20 +%26 = OpLabel +%30 = OpVariable %31 Function +%29 = OpLoad %3 %27 +OpBranch %40 +%40 = OpLabel +%44 = OpLoad %9 %42 +%48 = OpIEqual %46 %44 %45 +%49 = OpAll %47 %48 +OpSelectionMerge %50 None +OpBranchConditional %49 %51 %50 +%51 = OpLabel +OpStore %16 %41 +OpBranch %50 +%50 = OpLabel +OpControlBarrier %52 %52 %53 +OpBranch %54 +%54 = OpLabel +OpControlBarrier %52 %52 %53 +%55 = OpAccessChain %56 %16 %52 +%57 = OpLoad %3 %55 +%58 = OpAccessChain %56 %16 %59 +%60 = OpLoad %3 %58 +%61 = OpExtInst %3 %1 UMin %57 %11 +%62 = OpExtInst %3 %1 UMin %60 %11 +%63 = OpAccessChain %64 %16 %65 +%66 = OpAccessChain %67 %16 %11 +OpSetMeshOutputsEXT %61 %62 +OpStore %30 %29 +OpBranch %68 +%68 = OpLabel +OpLoopMerge %70 %87 None +OpBranch %86 +%86 = OpLabel +%89 = OpLoad %3 %30 +%90 = OpULessThan %47 %89 %61 +OpBranchConditional %90 %88 %70 +%88 = OpLabel +%72 = OpLoad %3 %30 +%73 = OpAccessChain %74 %63 %72 +%75 = OpLoad %7 %73 +%76 = OpCompositeExtract %5 %75 0 +%77 = OpAccessChain %78 %35 %72 %65 +OpStore %77 %76 +OpBranch %87 +%87 = OpLabel +%91 = OpLoad %3 %30 +%92 = OpIAdd %3 %91 %11 +OpStore %30 %92 +OpBranch %68 +%70 = OpLabel +OpStore %30 %29 +OpBranch %69 +%69 = OpLabel +OpLoopMerge %71 %94 None +OpBranch %93 +%93 = OpLabel +%96 = OpLoad %3 %30 +%97 = OpULessThan %47 %96 %62 +OpBranchConditional %97 %95 %71 +%95 = OpLabel +%79 = OpLoad %3 %30 +%80 = OpAccessChain %81 %66 %79 +%82 = OpLoad %8 %80 +%83 = OpCompositeExtract %3 %82 0 +%84 = OpAccessChain %85 %38 %79 +OpStore %84 %83 +OpBranch %94 +%94 = OpLabel +%98 = OpLoad %3 %30 +%99 = OpIAdd %3 %98 %11 +OpStore %30 %99 +OpBranch %69 +%71 = OpLabel +OpReturn +OpFunctionEnd \ No newline at end of file From f888b740374b86b1bcc0f61fe3b8de62d55f7e81 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 14:29:17 -0600 Subject: [PATCH 086/110] Fixed experimental features stuff --- wgpu-types/src/features.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu-types/src/features.rs b/wgpu-types/src/features.rs index 7c9816ffdba..f3a0a69e8a2 100644 --- a/wgpu-types/src/features.rs +++ b/wgpu-types/src/features.rs @@ -1542,6 +1542,7 @@ impl Features { Self::from_bits_truncate(FeatureBits([ FeaturesWGPU::EXPERIMENTAL_MESH_SHADER.bits() | FeaturesWGPU::EXPERIMENTAL_MESH_SHADER_MULTIVIEW.bits() + | FeaturesWGPU::EXPERIMENTAL_MESH_SHADER_POINTS.bits() | FeaturesWGPU::EXPERIMENTAL_RAY_QUERY.bits() | FeaturesWGPU::EXPERIMENTAL_RAY_HIT_VERTEX_RETURN.bits() | FeaturesWGPU::EXPERIMENTAL_PASSTHROUGH_SHADERS.bits(), From 703bac7de157ce65f727c98197ba2fede620211e Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 16:39:41 -0600 Subject: [PATCH 087/110] Updated the spec --- docs/api-specs/mesh_shading.md | 24 +++++++++--------------- naga/src/valid/interface.rs | 13 ++++++++++--- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 80a5e001046..25bf5795c2e 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -3,11 +3,10 @@ 🧪Experimental🧪 `wgpu` supports an experimental version of mesh shading when `Features::EXPERIMENTAL_MESH_SHADER` is enabled. -Currently `naga` has no support for parsing or writing mesh shaders. -For this reason, all shaders must be created with `Device::create_shader_module_passthrough`. +The status of the implementation is documented in [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). This document will **NOT** be updated regularly to communicate implementation progress. **Note**: The features documented here may have major bugs in them and are expected to be subject -to breaking changes, suggestions for the API exposed by this should be posted on [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). +to breaking changes. Suggestions for the API exposed by this should be posted on the issue above. ## Mesh shaders overview @@ -83,10 +82,9 @@ An example of using mesh shaders to render a single triangle can be seen [here]( ### Features * Using mesh shaders requires enabling `Features::EXPERIMENTAL_MESH_SHADER`. * Using mesh shaders with multiview requires enabling `Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW`. -* Currently, only triangle rendering is tested -* Line rendering is supported but untested -* Point rendering is supported on vulkan. It is impossible on DirectX. Metal support hasn't been checked. +* Using mesh shaders with point primitives requires enabling `Features::EXPERIMENTAL_MESH_SHADER_POINTS`. * Queries are unsupported +* Primitive index support will be added once support lands in for them in general. ### Limits @@ -97,12 +95,6 @@ An example of using mesh shaders to render a single triangle can be seen [here]( * `max_mesh_multiview_count` - The maximum number of views used when multiview rendering with a mesh shader pipeline. * `max_mesh_output_layers` - the maximum number of output layers for a mesh shader pipeline. -### Backend specific information -* Only Vulkan is currently supported. -* DirectX 12 doesn't support point rendering. -* DirectX 12 support is planned. -* Metal support is desired but not currently planned. - ## Naga implementation ### Supported frontends @@ -136,13 +128,15 @@ A function with the `@task` attribute is a **task shader entry point**. A mesh s A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point. -A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. +A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. A task payload variable must be at least 4 bytes in size. A task shader entry point must return a `vec3` value. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section. Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid; and `@builtin(num_workgroups)` matches the task shader workgroup's return value. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload. +Task shaders must return a value of type `vec3` decorated with `@builtin(mesh_task_size)`. + Task shaders can use compute and subgroup builtin inputs, in addition to `view_index` and `draw_id`. ### Mesh shader @@ -151,7 +145,7 @@ A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh sha Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes. -If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, naming the same variable, and the sizes must match. Mesh shader invocations can read, but not write, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. +If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, and the sizes of the variables must match. Mesh shader invocations can read from, but not write to, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid. If the mesh shader pipeline does not have a task shader entry point, then the mesh shader entry point must not have any `@payload` attribute. @@ -183,7 +177,7 @@ The primitive output type `P` must be a struct type, every member of which eithe The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader. -Mesh shaders may write to the `primitive_index` builtin. This is treated just like a field decorated with `@location`, so if the mesh shader outputs `primitive_index` the fragment shader must input it and vice versa. +Mesh shaders may write to the `primitive_index` builtin. This is treated just like a field decorated with `@location`, so if the mesh shader outputs `primitive_index` the fragment shader must input it, and if the fragment shader inputs it, the mesh shader must write it (unlike vertex shader pipelines). Mesh shaders can use compute and mesh shader builtin inputs, in addition to `view_index`, and if no task shader is present, `draw_id`. diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index 9cb1beefbb3..fb43de76d17 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -129,6 +129,9 @@ pub enum EntryPointError { InvalidIntegerInterpolation { location: u32 }, #[error(transparent)] Function(#[from] FunctionError), + #[error("Capability {0:?} is not supported")] + UnsupportedCapability(Capabilities), + #[error("mesh shader entry point missing mesh shader attributes")] ExpectedMeshShaderAttributes, #[error("Non mesh shader entry point cannot have mesh shader attributes")] @@ -143,9 +146,6 @@ pub enum EntryPointError { WrongTaskShaderEntryResult, #[error("Task shaders must declare a task payload output")] ExpectedTaskPayload, - #[error("Capability {0:?} is not supported")] - UnsupportedCapability(Capabilities), - #[error( "Mesh shader output variable must be a struct with fields that are all allowed builtins" )] @@ -162,6 +162,8 @@ pub enum EntryPointError { InvalidMeshPrimitiveOutputType, #[error("Mesh output global variable must live in the workgroup address space")] WrongMeshOutputAddressSpace, + #[error("Task payload must be at least 4 bytes, but is {0} bytes")] + TaskPayloadTooSmall(u32), } fn storage_usage(access: crate::StorageAccess) -> GlobalUse { @@ -1105,6 +1107,11 @@ impl super::Validator { } } } + let size = module.types[var.ty].inner.size(module.to_ctx()); + if size < 4 { + return Err(EntryPointError::TaskPayloadTooSmall(size) + .with_span_handle(var_handle, &module.global_variables)); + } } // If this is a `Mesh` entry point, check its vertex and primitive output types. From 6df4efad0f84630ccd1160b8270cd2c060e71053 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Mon, 10 Nov 2025 17:35:42 -0600 Subject: [PATCH 088/110] Fixed new validation rule for task payload size --- naga/src/valid/interface.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index fb43de76d17..a18b3738e55 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -1058,6 +1058,11 @@ impl super::Validator { return Err(EntryPointError::WrongTaskPayloadUsed .with_span_handle(var_handle, &module.global_variables)); } + let size = module.types[var.ty].inner.size(module.to_ctx()); + if size < 4 { + return Err(EntryPointError::TaskPayloadTooSmall(size) + .with_span_handle(var_handle, &module.global_variables)); + } } let allowed_usage = match var.space { @@ -1107,11 +1112,6 @@ impl super::Validator { } } } - let size = module.types[var.ty].inner.size(module.to_ctx()); - if size < 4 { - return Err(EntryPointError::TaskPayloadTooSmall(size) - .with_span_handle(var_handle, &module.global_variables)); - } } // If this is a `Mesh` entry point, check its vertex and primitive output types. From 5f8662372655eec72e7a1a0fe94ad2630c9ab60b Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Wed, 12 Nov 2025 19:46:10 -0500 Subject: [PATCH 089/110] Update mesh_shading.md Co-authored-by: Connor Fitzgerald --- docs/api-specs/mesh_shading.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index 25bf5795c2e..f636f18ead4 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -3,7 +3,7 @@ 🧪Experimental🧪 `wgpu` supports an experimental version of mesh shading when `Features::EXPERIMENTAL_MESH_SHADER` is enabled. -The status of the implementation is documented in [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). This document will **NOT** be updated regularly to communicate implementation progress. +The status of the implementation is documented in [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197). **Note**: The features documented here may have major bugs in them and are expected to be subject to breaking changes. Suggestions for the API exposed by this should be posted on the issue above. From c55d5be3cf387a076f350a7550cba51d18c851fd Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 12 Nov 2025 19:15:41 -0600 Subject: [PATCH 090/110] Updated toml files --- naga/tests/in/wgsl/mesh-shader-empty.toml | 17 ----------------- naga/tests/in/wgsl/mesh-shader-lines.toml | 17 ----------------- naga/tests/in/wgsl/mesh-shader-points.toml | 17 ----------------- naga/tests/in/wgsl/mesh-shader.toml | 17 ----------------- 4 files changed, 68 deletions(-) diff --git a/naga/tests/in/wgsl/mesh-shader-empty.toml b/naga/tests/in/wgsl/mesh-shader-empty.toml index 1f8b4e23baa..8500399f936 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.toml +++ b/naga/tests/in/wgsl/mesh-shader-empty.toml @@ -1,19 +1,2 @@ -# Stolen from ray-query.toml - god_mode = true targets = "IR | ANALYSIS" - -[msl] -fake_missing_bindings = true -lang_version = [2, 4] -spirv_cross_compatibility = false -zero_initialize_workgroup_memory = false - -[hlsl] -shader_model = "V6_5" -fake_missing_bindings = true -zero_initialize_workgroup_memory = true - -[spv] -version = [1, 4] -capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader-lines.toml b/naga/tests/in/wgsl/mesh-shader-lines.toml index 1f8b4e23baa..8500399f936 100644 --- a/naga/tests/in/wgsl/mesh-shader-lines.toml +++ b/naga/tests/in/wgsl/mesh-shader-lines.toml @@ -1,19 +1,2 @@ -# Stolen from ray-query.toml - god_mode = true targets = "IR | ANALYSIS" - -[msl] -fake_missing_bindings = true -lang_version = [2, 4] -spirv_cross_compatibility = false -zero_initialize_workgroup_memory = false - -[hlsl] -shader_model = "V6_5" -fake_missing_bindings = true -zero_initialize_workgroup_memory = true - -[spv] -version = [1, 4] -capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader-points.toml b/naga/tests/in/wgsl/mesh-shader-points.toml index 1f8b4e23baa..8500399f936 100644 --- a/naga/tests/in/wgsl/mesh-shader-points.toml +++ b/naga/tests/in/wgsl/mesh-shader-points.toml @@ -1,19 +1,2 @@ -# Stolen from ray-query.toml - god_mode = true targets = "IR | ANALYSIS" - -[msl] -fake_missing_bindings = true -lang_version = [2, 4] -spirv_cross_compatibility = false -zero_initialize_workgroup_memory = false - -[hlsl] -shader_model = "V6_5" -fake_missing_bindings = true -zero_initialize_workgroup_memory = true - -[spv] -version = [1, 4] -capabilities = ["MeshShadingEXT"] diff --git a/naga/tests/in/wgsl/mesh-shader.toml b/naga/tests/in/wgsl/mesh-shader.toml index 1f8b4e23baa..8500399f936 100644 --- a/naga/tests/in/wgsl/mesh-shader.toml +++ b/naga/tests/in/wgsl/mesh-shader.toml @@ -1,19 +1,2 @@ -# Stolen from ray-query.toml - god_mode = true targets = "IR | ANALYSIS" - -[msl] -fake_missing_bindings = true -lang_version = [2, 4] -spirv_cross_compatibility = false -zero_initialize_workgroup_memory = false - -[hlsl] -shader_model = "V6_5" -fake_missing_bindings = true -zero_initialize_workgroup_memory = true - -[spv] -version = [1, 4] -capabilities = ["MeshShadingEXT"] From 5a2bb01920e862ad0ad65203479e3bcbe6a102e8 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 12 Nov 2025 19:20:38 -0600 Subject: [PATCH 091/110] Updated stuff to use wgpu_mesh_shader name --- docs/api-specs/mesh_shading.md | 4 ++-- naga/src/front/wgsl/parse/conv.rs | 8 ++++---- .../wgsl/parse/directive/enable_extension.rs | 18 +++++++++--------- naga/src/front/wgsl/parse/mod.rs | 16 ++++++++-------- naga/tests/in/wgsl/mesh-shader-empty.wgsl | 2 +- naga/tests/in/wgsl/mesh-shader-lines.wgsl | 2 +- naga/tests/in/wgsl/mesh-shader-points.wgsl | 2 +- naga/tests/in/wgsl/mesh-shader.wgsl | 2 +- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/api-specs/mesh_shading.md b/docs/api-specs/mesh_shading.md index f636f18ead4..abb4fa49c1b 100644 --- a/docs/api-specs/mesh_shading.md +++ b/docs/api-specs/mesh_shading.md @@ -118,7 +118,7 @@ An example of using mesh shaders to render a single triangle can be seen [here]( The majority of changes relating to mesh shaders will be in WGSL and `naga`. -Using any of these features in a `wgsl` program will require adding the `enable mesh_shading` directive to the top of a program. +Using any of these features in a `wgsl` program will require adding the `enable wgpu_mesh_shader;` directive to the top of a program. Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-available functionality, including subgroup operations. @@ -192,7 +192,7 @@ The primitive state is part of the fragment input and must match the output of t The following is a full example of WGSL shaders that could be used to create a mesh shader pipeline, showing off many of the features. ```wgsl -enable mesh_shading; +enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), diff --git a/naga/src/front/wgsl/parse/conv.rs b/naga/src/front/wgsl/parse/conv.rs index 0303b7ed6bb..5431f0d2525 100644 --- a/naga/src/front/wgsl/parse/conv.rs +++ b/naga/src/front/wgsl/parse/conv.rs @@ -21,12 +21,12 @@ pub fn map_address_space<'a>( "push_constant" => Ok(crate::AddressSpace::PushConstant), "function" => Ok(crate::AddressSpace::Function), "task_payload" => { - if enable_extensions.contains(ImplementedEnableExtension::MeshShader) { + if enable_extensions.contains(ImplementedEnableExtension::WgpuMeshShader) { Ok(crate::AddressSpace::TaskPayload) } else { Err(Box::new(Error::EnableExtensionNotEnabled { span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })) } } @@ -94,10 +94,10 @@ pub fn map_built_in( | crate::BuiltIn::Vertices | crate::BuiltIn::PrimitiveCount | crate::BuiltIn::Primitives => { - if !enable_extensions.contains(ImplementedEnableExtension::MeshShader) { + if !enable_extensions.contains(ImplementedEnableExtension::WgpuMeshShader) { return Err(Box::new(Error::EnableExtensionNotEnabled { span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })); } } diff --git a/naga/src/front/wgsl/parse/directive/enable_extension.rs b/naga/src/front/wgsl/parse/directive/enable_extension.rs index d376c114ff0..7d1e1b2df6e 100644 --- a/naga/src/front/wgsl/parse/directive/enable_extension.rs +++ b/naga/src/front/wgsl/parse/directive/enable_extension.rs @@ -10,7 +10,7 @@ use alloc::boxed::Box; /// Tracks the status of every enable-extension known to Naga. #[derive(Clone, Debug, Eq, PartialEq)] pub struct EnableExtensions { - mesh_shader: bool, + wgpu_mesh_shader: bool, dual_source_blending: bool, /// Whether `enable f16;` was written earlier in the shader module. f16: bool, @@ -20,7 +20,7 @@ pub struct EnableExtensions { impl EnableExtensions { pub(crate) const fn empty() -> Self { Self { - mesh_shader: false, + wgpu_mesh_shader: false, f16: false, dual_source_blending: false, clip_distances: false, @@ -30,7 +30,7 @@ impl EnableExtensions { /// Add an enable-extension to the set requested by a module. pub(crate) fn add(&mut self, ext: ImplementedEnableExtension) { let field = match ext { - ImplementedEnableExtension::MeshShader => &mut self.mesh_shader, + ImplementedEnableExtension::WgpuMeshShader => &mut self.wgpu_mesh_shader, ImplementedEnableExtension::DualSourceBlending => &mut self.dual_source_blending, ImplementedEnableExtension::F16 => &mut self.f16, ImplementedEnableExtension::ClipDistances => &mut self.clip_distances, @@ -41,7 +41,7 @@ impl EnableExtensions { /// Query whether an enable-extension tracked here has been requested. pub(crate) const fn contains(&self, ext: ImplementedEnableExtension) -> bool { match ext { - ImplementedEnableExtension::MeshShader => self.mesh_shader, + ImplementedEnableExtension::WgpuMeshShader => self.wgpu_mesh_shader, ImplementedEnableExtension::DualSourceBlending => self.dual_source_blending, ImplementedEnableExtension::F16 => self.f16, ImplementedEnableExtension::ClipDistances => self.clip_distances, @@ -74,7 +74,7 @@ impl EnableExtension { const F16: &'static str = "f16"; const CLIP_DISTANCES: &'static str = "clip_distances"; const DUAL_SOURCE_BLENDING: &'static str = "dual_source_blending"; - const MESH_SHADER: &'static str = "mesh_shading"; + const MESH_SHADER: &'static str = "wgpu_mesh_shader"; const SUBGROUPS: &'static str = "subgroups"; const PRIMITIVE_INDEX: &'static str = "primitive_index"; @@ -86,7 +86,7 @@ impl EnableExtension { Self::DUAL_SOURCE_BLENDING => { Self::Implemented(ImplementedEnableExtension::DualSourceBlending) } - Self::MESH_SHADER => Self::Implemented(ImplementedEnableExtension::MeshShader), + Self::MESH_SHADER => Self::Implemented(ImplementedEnableExtension::WgpuMeshShader), Self::SUBGROUPS => Self::Unimplemented(UnimplementedEnableExtension::Subgroups), Self::PRIMITIVE_INDEX => { Self::Unimplemented(UnimplementedEnableExtension::PrimitiveIndex) @@ -99,7 +99,7 @@ impl EnableExtension { pub const fn to_ident(self) -> &'static str { match self { Self::Implemented(kind) => match kind { - ImplementedEnableExtension::MeshShader => Self::MESH_SHADER, + ImplementedEnableExtension::WgpuMeshShader => Self::MESH_SHADER, ImplementedEnableExtension::DualSourceBlending => Self::DUAL_SOURCE_BLENDING, ImplementedEnableExtension::F16 => Self::F16, ImplementedEnableExtension::ClipDistances => Self::CLIP_DISTANCES, @@ -133,8 +133,8 @@ pub enum ImplementedEnableExtension { /// /// [`enable clip_distances;`]: https://www.w3.org/TR/WGSL/#extension-clip_distances ClipDistances, - /// Enables the `mesh_shader` extension, native only - MeshShader, + /// Enables the `wgpu_mesh_shader` extension, native only + WgpuMeshShader, } /// A variant of [`EnableExtension::Unimplemented`]. diff --git a/naga/src/front/wgsl/parse/mod.rs b/naga/src/front/wgsl/parse/mod.rs index e4c04644347..838a4dd1a88 100644 --- a/naga/src/front/wgsl/parse/mod.rs +++ b/naga/src/front/wgsl/parse/mod.rs @@ -242,11 +242,11 @@ impl<'a> BindingParser<'a> { "per_primitive" => { if !lexer .enable_extensions - .contains(ImplementedEnableExtension::MeshShader) + .contains(ImplementedEnableExtension::WgpuMeshShader) { return Err(Box::new(Error::EnableExtensionNotEnabled { span: name_span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })); } self.per_primitive.set((), name_span)?; @@ -2876,11 +2876,11 @@ impl Parser { "task" => { if !lexer .enable_extensions - .contains(ImplementedEnableExtension::MeshShader) + .contains(ImplementedEnableExtension::WgpuMeshShader) { return Err(Box::new(Error::EnableExtensionNotEnabled { span: name_span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })); } stage.set(ShaderStage::Task, name_span)?; @@ -2889,11 +2889,11 @@ impl Parser { "mesh" => { if !lexer .enable_extensions - .contains(ImplementedEnableExtension::MeshShader) + .contains(ImplementedEnableExtension::WgpuMeshShader) { return Err(Box::new(Error::EnableExtensionNotEnabled { span: name_span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })); } stage.set(ShaderStage::Mesh, name_span)?; @@ -2906,11 +2906,11 @@ impl Parser { "payload" => { if !lexer .enable_extensions - .contains(ImplementedEnableExtension::MeshShader) + .contains(ImplementedEnableExtension::WgpuMeshShader) { return Err(Box::new(Error::EnableExtensionNotEnabled { span: name_span, - kind: ImplementedEnableExtension::MeshShader.into(), + kind: ImplementedEnableExtension::WgpuMeshShader.into(), })); } lexer.expect(Token::Paren('('))?; diff --git a/naga/tests/in/wgsl/mesh-shader-empty.wgsl b/naga/tests/in/wgsl/mesh-shader-empty.wgsl index 1e8c4faed3b..98a6bf8448b 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.wgsl +++ b/naga/tests/in/wgsl/mesh-shader-empty.wgsl @@ -1,7 +1,7 @@ // An empty WGSL shader to check that task payload/mesh output // are still properly written without being used in the shader -enable mesh_shading; +enable wgpu_mesh_shader; struct TaskPayload { dummy: u32, diff --git a/naga/tests/in/wgsl/mesh-shader-lines.wgsl b/naga/tests/in/wgsl/mesh-shader-lines.wgsl index e4caa9e32d7..c475ff10619 100644 --- a/naga/tests/in/wgsl/mesh-shader-lines.wgsl +++ b/naga/tests/in/wgsl/mesh-shader-lines.wgsl @@ -1,7 +1,7 @@ // An empty WGSL shader to check that task payload/mesh output // are still properly written without being used in the shader -enable mesh_shading; +enable wgpu_mesh_shader; struct TaskPayload { dummy: u32, diff --git a/naga/tests/in/wgsl/mesh-shader-points.wgsl b/naga/tests/in/wgsl/mesh-shader-points.wgsl index 652a83d6a6a..84516ee8f2a 100644 --- a/naga/tests/in/wgsl/mesh-shader-points.wgsl +++ b/naga/tests/in/wgsl/mesh-shader-points.wgsl @@ -1,7 +1,7 @@ // An empty WGSL shader to check that task payload/mesh output // are still properly written without being used in the shader -enable mesh_shading; +enable wgpu_mesh_shader; struct TaskPayload { dummy: u32, diff --git a/naga/tests/in/wgsl/mesh-shader.wgsl b/naga/tests/in/wgsl/mesh-shader.wgsl index 382a95f62f6..4ed3a18cfdb 100644 --- a/naga/tests/in/wgsl/mesh-shader.wgsl +++ b/naga/tests/in/wgsl/mesh-shader.wgsl @@ -1,6 +1,6 @@ // Main mesh shader test file. Tests most features. -enable mesh_shading; +enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), From 18fa9a8d158afc3a51bc8b4fbc4ddafffe76d3a6 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 12 Nov 2025 19:37:18 -0600 Subject: [PATCH 092/110] Updated the example & tests --- examples/features/src/mesh_shader/shader.wgsl | 2 +- tests/tests/wgpu-gpu/mesh_shader/shader.wgsl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/features/src/mesh_shader/shader.wgsl b/examples/features/src/mesh_shader/shader.wgsl index cdc7366b415..fb65d51a86e 100644 --- a/examples/features/src/mesh_shader/shader.wgsl +++ b/examples/features/src/mesh_shader/shader.wgsl @@ -1,4 +1,4 @@ -enable mesh_shading; +enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), diff --git a/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl index a2be409f5df..48a8bfe6e3f 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl +++ b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl @@ -1,4 +1,4 @@ -enable mesh_shading; +enable wgpu_mesh_shader; const positions = array( vec4(0., 1., 0., 1.), From 65be73b982dd591201dddf8225767b9d2faff7bd Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 00:39:43 -0600 Subject: [PATCH 093/110] I am so fucking confused --- tests/tests/wgpu-gpu/mesh_shader/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 4d57a9a693b..f291beb05ff 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -360,7 +360,6 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) { fn default_gpu_test_config(draw_type: DrawType) -> GpuTestConfiguration { GpuTestConfiguration::new().parameters( TestParameters::default() - .test_features_limits() .features( wgpu::Features::EXPERIMENTAL_MESH_SHADER | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS From b569fd1ea66faf26356a88879baff1572e0ac5d4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 01:28:17 -0600 Subject: [PATCH 094/110] Seeing what happens --- wgpu-core/src/device/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index 8683b37dd0e..e38d9e7aec9 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -518,6 +518,7 @@ pub fn create_validator( Caps::MESH_SHADER, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER), ); + caps.set(Caps::MESH_SHADER, true); caps.set( Caps::MESH_SHADER_POINT_TOPOLOGY, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER_POINTS), From 8df685ab16c2c57657dc84d4b986bed059798658 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 01:33:51 -0600 Subject: [PATCH 095/110] WHAT THE FUCK IS GOING ON --- wgpu-core/src/device/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/wgpu-core/src/device/mod.rs b/wgpu-core/src/device/mod.rs index e38d9e7aec9..8683b37dd0e 100644 --- a/wgpu-core/src/device/mod.rs +++ b/wgpu-core/src/device/mod.rs @@ -518,7 +518,6 @@ pub fn create_validator( Caps::MESH_SHADER, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER), ); - caps.set(Caps::MESH_SHADER, true); caps.set( Caps::MESH_SHADER_POINT_TOPOLOGY, features.intersects(wgt::Features::EXPERIMENTAL_MESH_SHADER_POINTS), From aa06d41de087390c3c69137054dd3bef5d9b40f5 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 01:41:46 -0600 Subject: [PATCH 096/110] Didn't notice this thing --- naga/src/valid/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index 0ec5c5184f2..060b9fe28af 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -208,6 +208,7 @@ impl Capabilities { // NOTE: `SHADER_FLOAT16_IN_FLOAT32` _does not_ require the `f16` extension Self::SHADER_FLOAT16 => Some(Ext::F16), Self::CLIP_DISTANCE => Some(Ext::ClipDistances), + Self::MESH_SHADER => Some(Ext::WgpuMeshShader), _ => None, } } From a9e112e92a96ded21894bb87c1e23c260e315617 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 01:51:03 -0600 Subject: [PATCH 097/110] Does this fix it? --- naga/src/valid/interface.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index a18b3738e55..cc0fe466be9 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -734,11 +734,11 @@ impl super::Validator { ), crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false), crate::AddressSpace::TaskPayload => { - if !self.capabilities.contains(Capabilities::MESH_SHADER) { + /*if !self.capabilities.contains(Capabilities::MESH_SHADER) { return Err(GlobalVariableError::UnsupportedCapability( Capabilities::MESH_SHADER, )); - } + }*/ (TypeFlags::DATA | TypeFlags::SIZED, false) } crate::AddressSpace::PushConstant => { From 7d43d25c0b32616b601e5e3f0dab51bbc6659b8c Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 01:58:59 -0600 Subject: [PATCH 098/110] That didn't help I guess, whatever man, fuck --- naga/src/valid/interface.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/valid/interface.rs b/naga/src/valid/interface.rs index cc0fe466be9..a18b3738e55 100644 --- a/naga/src/valid/interface.rs +++ b/naga/src/valid/interface.rs @@ -734,11 +734,11 @@ impl super::Validator { ), crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false), crate::AddressSpace::TaskPayload => { - /*if !self.capabilities.contains(Capabilities::MESH_SHADER) { + if !self.capabilities.contains(Capabilities::MESH_SHADER) { return Err(GlobalVariableError::UnsupportedCapability( Capabilities::MESH_SHADER, )); - }*/ + } (TypeFlags::DATA | TypeFlags::SIZED, false) } crate::AddressSpace::PushConstant => { From 62d0ed5b5dac6c886a8cd9321dd671dd37b34260 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 13:30:02 -0600 Subject: [PATCH 099/110] This should fix some of it --- naga/src/valid/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index 060b9fe28af..3fc79c37052 100644 --- a/naga/src/valid/mod.rs +++ b/naga/src/valid/mod.rs @@ -191,7 +191,7 @@ bitflags::bitflags! { /// Support for task shaders, mesh shaders, and per-primitive fragment inputs const MESH_SHADER = 1 << 30; /// Support for mesh shaders which output points. - const MESH_SHADER_POINT_TOPOLOGY = 1 << 30; + const MESH_SHADER_POINT_TOPOLOGY = 1 << 31; } } From 14cb5debdf4a15474d490b41841d7e4d94d8175d Mon Sep 17 00:00:00 2001 From: SupaMaggie70Incorporated Date: Sat, 15 Nov 2025 20:03:29 -0500 Subject: [PATCH 100/110] Updated writer, will regenerate snapshots elsewhere cus fuck this --- naga/src/back/spv/writer.rs | 6900 ++++++++++++++++++----------------- 1 file changed, 3458 insertions(+), 3442 deletions(-) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index a43d463eba0..a835f08550c 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1,3442 +1,3458 @@ -use alloc::{string::String, vec, vec::Vec}; - -use arrayvec::ArrayVec; -use hashbrown::hash_map::Entry; -use spirv::Word; - -use super::{ - block::DebugInfoInner, - helpers::{contains_builtin, global_needs_wrapper, map_storage_class}, - Block, BlockContext, CachedConstant, CachedExpressions, DebugInfo, EntryPointContext, Error, - Function, FunctionArgument, GlobalVariable, IdGenerator, Instruction, LocalImageType, - LocalType, LocalVariable, LogicalLayout, LookupFunctionType, LookupType, NumericType, Options, - PhysicalLayout, PipelineOptions, ResultMember, Writer, WriterFlags, BITS_PER_BYTE, -}; -use crate::{ - arena::{Handle, HandleVec, UniqueArena}, - back::spv::{helpers::BindingDecorations, BindingInfo, WrappedFunction}, - non_max_u32::NonMaxU32, - proc::{Alignment, TypeResolution}, - valid::{FunctionInfo, ModuleInfo}, -}; - -struct FunctionInterface<'a> { - varying_ids: &'a mut Vec, - stage: crate::ShaderStage, - task_payload: Option>, - mesh_info: Option, - workgroup_size: [u32; 3], -} - -impl Function { - pub(super) fn to_words(&self, sink: &mut impl Extend) { - self.signature.as_ref().unwrap().to_words(sink); - for argument in self.parameters.iter() { - argument.instruction.to_words(sink); - } - for (index, block) in self.blocks.iter().enumerate() { - Instruction::label(block.label_id).to_words(sink); - if index == 0 { - for local_var in self.variables.values() { - local_var.instruction.to_words(sink); - } - for local_var in self.ray_query_initialization_tracker_variables.values() { - local_var.instruction.to_words(sink); - } - for local_var in self.ray_query_t_max_tracker_variables.values() { - local_var.instruction.to_words(sink); - } - for local_var in self.force_loop_bounding_vars.iter() { - local_var.instruction.to_words(sink); - } - for internal_var in self.spilled_composites.values() { - internal_var.instruction.to_words(sink); - } - } - for instruction in block.body.iter() { - instruction.to_words(sink); - } - } - Instruction::function_end().to_words(sink); - } -} - -impl Writer { - pub fn new(options: &Options) -> Result { - let (major, minor) = options.lang_version; - if major != 1 { - return Err(Error::UnsupportedVersion(major, minor)); - } - - let mut capabilities_used = crate::FastIndexSet::default(); - capabilities_used.insert(spirv::Capability::Shader); - - let mut id_gen = IdGenerator::default(); - let gl450_ext_inst_id = id_gen.next(); - let void_type = id_gen.next(); - - Ok(Writer { - physical_layout: PhysicalLayout::new(major, minor), - logical_layout: LogicalLayout::default(), - id_gen, - capabilities_available: options.capabilities.clone(), - capabilities_used, - extensions_used: crate::FastIndexSet::default(), - debug_strings: vec![], - debugs: vec![], - annotations: vec![], - flags: options.flags, - bounds_check_policies: options.bounds_check_policies, - zero_initialize_workgroup_memory: options.zero_initialize_workgroup_memory, - force_loop_bounding: options.force_loop_bounding, - ray_query_initialization_tracking: options.ray_query_initialization_tracking, - use_storage_input_output_16: options.use_storage_input_output_16, - void_type, - lookup_type: crate::FastHashMap::default(), - lookup_function: crate::FastHashMap::default(), - lookup_function_type: crate::FastHashMap::default(), - wrapped_functions: crate::FastHashMap::default(), - constant_ids: HandleVec::new(), - cached_constants: crate::FastHashMap::default(), - global_variables: HandleVec::new(), - fake_missing_bindings: options.fake_missing_bindings, - binding_map: options.binding_map.clone(), - saved_cached: CachedExpressions::default(), - gl450_ext_inst_id, - temp_list: Vec::new(), - ray_query_functions: crate::FastHashMap::default(), - io_f16_polyfills: super::f16_polyfill::F16IoPolyfill::new( - options.use_storage_input_output_16, - ), - debug_printf: None, - }) - } - - pub fn set_options(&mut self, options: &Options) -> Result<(), Error> { - let (major, minor) = options.lang_version; - if major != 1 { - return Err(Error::UnsupportedVersion(major, minor)); - } - self.physical_layout = PhysicalLayout::new(major, minor); - self.capabilities_available = options.capabilities.clone(); - self.flags = options.flags; - self.bounds_check_policies = options.bounds_check_policies; - self.zero_initialize_workgroup_memory = options.zero_initialize_workgroup_memory; - self.force_loop_bounding = options.force_loop_bounding; - self.use_storage_input_output_16 = options.use_storage_input_output_16; - self.binding_map = options.binding_map.clone(); - self.io_f16_polyfills = - super::f16_polyfill::F16IoPolyfill::new(options.use_storage_input_output_16); - Ok(()) - } - - /// Returns `(major, minor)` of the SPIR-V language version. - pub const fn lang_version(&self) -> (u8, u8) { - self.physical_layout.lang_version() - } - - /// Reset `Writer` to its initial state, retaining any allocations. - /// - /// Why not just implement `Recyclable` for `Writer`? By design, - /// `Recyclable::recycle` requires ownership of the value, not just - /// `&mut`; see the trait documentation. But we need to use this method - /// from functions like `Writer::write`, which only have `&mut Writer`. - /// Workarounds include unsafe code (`core::ptr::read`, then `write`, ugh) - /// or something like a `Default` impl that returns an oddly-initialized - /// `Writer`, which is worse. - fn reset(&mut self) { - use super::recyclable::Recyclable; - use core::mem::take; - - let mut id_gen = IdGenerator::default(); - let gl450_ext_inst_id = id_gen.next(); - let void_type = id_gen.next(); - - // Every field of the old writer that is not determined by the `Options` - // passed to `Writer::new` should be reset somehow. - let fresh = Writer { - // Copied from the old Writer: - flags: self.flags, - bounds_check_policies: self.bounds_check_policies, - zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory, - force_loop_bounding: self.force_loop_bounding, - ray_query_initialization_tracking: self.ray_query_initialization_tracking, - use_storage_input_output_16: self.use_storage_input_output_16, - capabilities_available: take(&mut self.capabilities_available), - fake_missing_bindings: self.fake_missing_bindings, - binding_map: take(&mut self.binding_map), - - // Initialized afresh: - id_gen, - void_type, - gl450_ext_inst_id, - - // Recycled: - capabilities_used: take(&mut self.capabilities_used).recycle(), - extensions_used: take(&mut self.extensions_used).recycle(), - physical_layout: self.physical_layout.clone().recycle(), - logical_layout: take(&mut self.logical_layout).recycle(), - debug_strings: take(&mut self.debug_strings).recycle(), - debugs: take(&mut self.debugs).recycle(), - annotations: take(&mut self.annotations).recycle(), - lookup_type: take(&mut self.lookup_type).recycle(), - lookup_function: take(&mut self.lookup_function).recycle(), - lookup_function_type: take(&mut self.lookup_function_type).recycle(), - wrapped_functions: take(&mut self.wrapped_functions).recycle(), - constant_ids: take(&mut self.constant_ids).recycle(), - cached_constants: take(&mut self.cached_constants).recycle(), - global_variables: take(&mut self.global_variables).recycle(), - saved_cached: take(&mut self.saved_cached).recycle(), - temp_list: take(&mut self.temp_list).recycle(), - ray_query_functions: take(&mut self.ray_query_functions).recycle(), - io_f16_polyfills: take(&mut self.io_f16_polyfills).recycle(), - debug_printf: None, - }; - - *self = fresh; - - self.capabilities_used.insert(spirv::Capability::Shader); - } - - /// Indicate that the code requires any one of the listed capabilities. - /// - /// If nothing in `capabilities` appears in the available capabilities - /// specified in the [`Options`] from which this `Writer` was created, - /// return an error. The `what` string is used in the error message to - /// explain what provoked the requirement. (If no available capabilities were - /// given, assume everything is available.) - /// - /// The first acceptable capability will be added to this `Writer`'s - /// [`capabilities_used`] table, and an `OpCapability` emitted for it in the - /// result. For this reason, more specific capabilities should be listed - /// before more general. - /// - /// [`capabilities_used`]: Writer::capabilities_used - pub(super) fn require_any( - &mut self, - what: &'static str, - capabilities: &[spirv::Capability], - ) -> Result<(), Error> { - match *capabilities { - [] => Ok(()), - [first, ..] => { - // Find the first acceptable capability, or return an error if - // there is none. - let selected = match self.capabilities_available { - None => first, - Some(ref available) => { - match capabilities - .iter() - // need explicit type for hashbrown::HashSet::contains fn call to keep rustc happy - .find(|cap| available.contains::(cap)) - { - Some(&cap) => cap, - None => { - return Err(Error::MissingCapabilities(what, capabilities.to_vec())) - } - } - } - }; - self.capabilities_used.insert(selected); - Ok(()) - } - } - } - - /// Indicate that the code requires all of the listed capabilities. - /// - /// If all entries of `capabilities` appear in the available capabilities - /// specified in the [`Options`] from which this `Writer` was created - /// (including the case where [`Options::capabilities`] is `None`), add - /// them all to this `Writer`'s [`capabilities_used`] table, and return - /// `Ok(())`. If at least one of the listed capabilities is not available, - /// do not add anything to the `capabilities_used` table, and return the - /// first unavailable requested capability, wrapped in `Err()`. - /// - /// This method is does not return an [`enum@Error`] in case of failure - /// because it may be used in cases where the caller can recover (e.g., - /// with a polyfill) if the requested capabilities are not available. In - /// this case, it would be unnecessary work to find *all* the unavailable - /// requested capabilities, and to allocate a `Vec` for them, just so we - /// could return an [`Error::MissingCapabilities`]). - /// - /// [`capabilities_used`]: Writer::capabilities_used - pub(super) fn require_all( - &mut self, - capabilities: &[spirv::Capability], - ) -> Result<(), spirv::Capability> { - if let Some(ref available) = self.capabilities_available { - for requested in capabilities { - if !available.contains(requested) { - return Err(*requested); - } - } - } - - for requested in capabilities { - self.capabilities_used.insert(*requested); - } - - Ok(()) - } - - /// Indicate that the code uses the given extension. - pub(super) fn use_extension(&mut self, extension: &'static str) { - self.extensions_used.insert(extension); - } - - pub(super) fn get_type_id(&mut self, lookup_ty: LookupType) -> Word { - match self.lookup_type.entry(lookup_ty) { - Entry::Occupied(e) => *e.get(), - Entry::Vacant(e) => { - let local = match lookup_ty { - LookupType::Handle(_handle) => unreachable!("Handles are populated at start"), - LookupType::Local(local) => local, - }; - - let id = self.id_gen.next(); - e.insert(id); - self.write_type_declaration_local(id, local); - id - } - } - } - - pub(super) fn get_handle_type_id(&mut self, handle: Handle) -> Word { - self.get_type_id(LookupType::Handle(handle)) - } - - pub(super) fn get_expression_lookup_type(&mut self, tr: &TypeResolution) -> LookupType { - match *tr { - TypeResolution::Handle(ty_handle) => LookupType::Handle(ty_handle), - TypeResolution::Value(ref inner) => { - let inner_local_type = self.localtype_from_inner(inner).unwrap(); - LookupType::Local(inner_local_type) - } - } - } - - pub(super) fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { - let lookup_ty = self.get_expression_lookup_type(tr); - self.get_type_id(lookup_ty) - } - - pub(super) fn get_localtype_id(&mut self, local: LocalType) -> Word { - self.get_type_id(LookupType::Local(local)) - } - - pub(super) fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word { - self.get_type_id(LookupType::Local(LocalType::Pointer { base, class })) - } - - pub(super) fn get_handle_pointer_type_id( - &mut self, - base: Handle, - class: spirv::StorageClass, - ) -> Word { - let base_id = self.get_handle_type_id(base); - self.get_pointer_type_id(base_id, class) - } - - pub(super) fn get_ray_query_pointer_id(&mut self) -> Word { - let rq_id = self.get_type_id(LookupType::Local(LocalType::RayQuery)); - self.get_pointer_type_id(rq_id, spirv::StorageClass::Function) - } - - /// Return a SPIR-V type for a pointer to `resolution`. - /// - /// The given `resolution` must be one that we can represent - /// either as a `LocalType::Pointer` or `LocalType::LocalPointer`. - pub(super) fn get_resolution_pointer_id( - &mut self, - resolution: &TypeResolution, - class: spirv::StorageClass, - ) -> Word { - let resolution_type_id = self.get_expression_type_id(resolution); - self.get_pointer_type_id(resolution_type_id, class) - } - - pub(super) fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word { - self.get_type_id(LocalType::Numeric(numeric).into()) - } - - pub(super) fn get_u32_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)) - } - - pub(super) fn get_f32_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32)) - } - - pub(super) fn get_vec2u_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Bi, - scalar: crate::Scalar::U32, - }) - } - - pub(super) fn get_vec2f_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Bi, - scalar: crate::Scalar::F32, - }) - } - - pub(super) fn get_vec3u_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Tri, - scalar: crate::Scalar::U32, - }) - } - - pub(super) fn get_f32_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { - let f32_id = self.get_f32_type_id(); - self.get_pointer_type_id(f32_id, class) - } - - pub(super) fn get_vec2u_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { - let vec2u_id = self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Bi, - scalar: crate::Scalar::U32, - }); - self.get_pointer_type_id(vec2u_id, class) - } - - pub(super) fn get_vec3u_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { - let vec3u_id = self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Tri, - scalar: crate::Scalar::U32, - }); - self.get_pointer_type_id(vec3u_id, class) - } - - pub(super) fn get_bool_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::BOOL)) - } - - pub(super) fn get_vec2_bool_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Bi, - scalar: crate::Scalar::BOOL, - }) - } - - pub(super) fn get_vec3_bool_type_id(&mut self) -> Word { - self.get_numeric_type_id(NumericType::Vector { - size: crate::VectorSize::Tri, - scalar: crate::Scalar::BOOL, - }) - } - - pub(super) fn decorate(&mut self, id: Word, decoration: spirv::Decoration, operands: &[Word]) { - self.annotations - .push(Instruction::decorate(id, decoration, operands)); - } - - /// Return `inner` as a `LocalType`, if that's possible. - /// - /// If `inner` can be represented as a `LocalType`, return - /// `Some(local_type)`. - /// - /// Otherwise, return `None`. In this case, the type must always be looked - /// up using a `LookupType::Handle`. - fn localtype_from_inner(&mut self, inner: &crate::TypeInner) -> Option { - Some(match *inner { - crate::TypeInner::Scalar(_) - | crate::TypeInner::Atomic(_) - | crate::TypeInner::Vector { .. } - | crate::TypeInner::Matrix { .. } => { - // We expect `NumericType::from_inner` to handle all - // these cases, so unwrap. - LocalType::Numeric(NumericType::from_inner(inner).unwrap()) - } - crate::TypeInner::Pointer { base, space } => { - let base_type_id = self.get_handle_type_id(base); - LocalType::Pointer { - base: base_type_id, - class: map_storage_class(space), - } - } - crate::TypeInner::ValuePointer { - size, - scalar, - space, - } => { - let base_numeric_type = match size { - Some(size) => NumericType::Vector { size, scalar }, - None => NumericType::Scalar(scalar), - }; - LocalType::Pointer { - base: self.get_numeric_type_id(base_numeric_type), - class: map_storage_class(space), - } - } - crate::TypeInner::Image { - dim, - arrayed, - class, - } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)), - crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler, - crate::TypeInner::AccelerationStructure { .. } => LocalType::AccelerationStructure, - crate::TypeInner::RayQuery { .. } => LocalType::RayQuery, - crate::TypeInner::Array { .. } - | crate::TypeInner::Struct { .. } - | crate::TypeInner::BindingArray { .. } => return None, - }) - } - - /// Resolve the [`BindingInfo`] for a [`crate::ResourceBinding`] from the - /// provided [`Writer::binding_map`]. - /// - /// If the specified resource is not present in the binding map this will - /// return an error, unless [`Writer::fake_missing_bindings`] is set. - fn resolve_resource_binding( - &self, - res_binding: &crate::ResourceBinding, - ) -> Result { - match self.binding_map.get(res_binding) { - Some(target) => Ok(*target), - None if self.fake_missing_bindings => Ok(BindingInfo { - descriptor_set: res_binding.group, - binding: res_binding.binding, - binding_array_size: None, - }), - None => Err(Error::MissingBinding(*res_binding)), - } - } - - /// Emits code for any wrapper functions required by the expressions in ir_function. - /// The IDs of any emitted functions will be stored in [`Self::wrapped_functions`]. - fn write_wrapped_functions( - &mut self, - ir_function: &crate::Function, - info: &FunctionInfo, - ir_module: &crate::Module, - ) -> Result<(), Error> { - log::trace!("Generating wrapped functions for {:?}", ir_function.name); - - for (expr_handle, expr) in ir_function.expressions.iter() { - match *expr { - crate::Expression::Binary { op, left, right } => { - let expr_ty_inner = info[expr_handle].ty.inner_with(&ir_module.types); - if let Some(expr_ty) = NumericType::from_inner(expr_ty_inner) { - match (op, expr_ty.scalar().kind) { - // Division and modulo are undefined behaviour when the - // dividend is the minimum representable value and the divisor - // is negative one, or when the divisor is zero. These wrapped - // functions override the divisor to one in these cases, - // matching the WGSL spec. - ( - crate::BinaryOperator::Divide | crate::BinaryOperator::Modulo, - crate::ScalarKind::Sint | crate::ScalarKind::Uint, - ) => { - self.write_wrapped_binary_op( - op, - expr_ty, - &info[left].ty, - &info[right].ty, - )?; - } - _ => {} - } - } - } - _ => {} - } - } - - Ok(()) - } - - /// Write a SPIR-V function that performs the operator `op` with Naga IR semantics. - /// - /// Define a function that performs an integer division or modulo operation, - /// except that using a divisor of zero or causing signed overflow with a - /// divisor of -1 returns the numerator unchanged, rather than exhibiting - /// undefined behavior. - /// - /// Store the generated function's id in the [`wrapped_functions`] table. - /// - /// The operator `op` must be either [`Divide`] or [`Modulo`]. - /// - /// # Panics - /// - /// The `return_type`, `left_type` or `right_type` arguments must all be - /// integer scalars or vectors. If not, this function panics. - /// - /// [`wrapped_functions`]: Writer::wrapped_functions - /// [`Divide`]: crate::BinaryOperator::Divide - /// [`Modulo`]: crate::BinaryOperator::Modulo - fn write_wrapped_binary_op( - &mut self, - op: crate::BinaryOperator, - return_type: NumericType, - left_type: &TypeResolution, - right_type: &TypeResolution, - ) -> Result<(), Error> { - let return_type_id = self.get_localtype_id(LocalType::Numeric(return_type)); - let left_type_id = self.get_expression_type_id(left_type); - let right_type_id = self.get_expression_type_id(right_type); - - // Check if we've already emitted this function. - let wrapped = WrappedFunction::BinaryOp { - op, - left_type_id, - right_type_id, - }; - let function_id = match self.wrapped_functions.entry(wrapped) { - Entry::Occupied(_) => return Ok(()), - Entry::Vacant(e) => *e.insert(self.id_gen.next()), - }; - - let scalar = return_type.scalar(); - - if self.flags.contains(WriterFlags::DEBUG) { - let function_name = match op { - crate::BinaryOperator::Divide => "naga_div", - crate::BinaryOperator::Modulo => "naga_mod", - _ => unreachable!(), - }; - self.debugs - .push(Instruction::name(function_id, function_name)); - } - let mut function = Function::default(); - - let function_type_id = self.get_function_type(LookupFunctionType { - parameter_type_ids: vec![left_type_id, right_type_id], - return_type_id, - }); - function.signature = Some(Instruction::function( - return_type_id, - function_id, - spirv::FunctionControl::empty(), - function_type_id, - )); - - let lhs_id = self.id_gen.next(); - let rhs_id = self.id_gen.next(); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs.push(Instruction::name(lhs_id, "lhs")); - self.debugs.push(Instruction::name(rhs_id, "rhs")); - } - let left_par = Instruction::function_parameter(left_type_id, lhs_id); - let right_par = Instruction::function_parameter(right_type_id, rhs_id); - for instruction in [left_par, right_par] { - function.parameters.push(FunctionArgument { - instruction, - handle_id: 0, - }); - } - - let label_id = self.id_gen.next(); - let mut block = Block::new(label_id); - - let bool_type = return_type.with_scalar(crate::Scalar::BOOL); - let bool_type_id = self.get_numeric_type_id(bool_type); - - let maybe_splat_const = |writer: &mut Self, const_id| match return_type { - NumericType::Scalar(_) => const_id, - NumericType::Vector { size, .. } => { - let constituent_ids = [const_id; crate::VectorSize::MAX]; - writer.get_constant_composite( - LookupType::Local(LocalType::Numeric(return_type)), - &constituent_ids[..size as usize], - ) - } - NumericType::Matrix { .. } => unreachable!(), - }; - - let const_zero_id = self.get_constant_scalar_with(0, scalar)?; - let composite_zero_id = maybe_splat_const(self, const_zero_id); - let rhs_eq_zero_id = self.id_gen.next(); - block.body.push(Instruction::binary( - spirv::Op::IEqual, - bool_type_id, - rhs_eq_zero_id, - rhs_id, - composite_zero_id, - )); - let divisor_selector_id = match scalar.kind { - crate::ScalarKind::Sint => { - let (const_min_id, const_neg_one_id) = match scalar.width { - 4 => Ok(( - self.get_constant_scalar(crate::Literal::I32(i32::MIN)), - self.get_constant_scalar(crate::Literal::I32(-1i32)), - )), - 8 => Ok(( - self.get_constant_scalar(crate::Literal::I64(i64::MIN)), - self.get_constant_scalar(crate::Literal::I64(-1i64)), - )), - _ => Err(Error::Validation("Unexpected scalar width")), - }?; - let composite_min_id = maybe_splat_const(self, const_min_id); - let composite_neg_one_id = maybe_splat_const(self, const_neg_one_id); - - let lhs_eq_int_min_id = self.id_gen.next(); - block.body.push(Instruction::binary( - spirv::Op::IEqual, - bool_type_id, - lhs_eq_int_min_id, - lhs_id, - composite_min_id, - )); - let rhs_eq_neg_one_id = self.id_gen.next(); - block.body.push(Instruction::binary( - spirv::Op::IEqual, - bool_type_id, - rhs_eq_neg_one_id, - rhs_id, - composite_neg_one_id, - )); - let lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); - block.body.push(Instruction::binary( - spirv::Op::LogicalAnd, - bool_type_id, - lhs_eq_int_min_and_rhs_eq_neg_one_id, - lhs_eq_int_min_id, - rhs_eq_neg_one_id, - )); - let rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); - block.body.push(Instruction::binary( - spirv::Op::LogicalOr, - bool_type_id, - rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id, - rhs_eq_zero_id, - lhs_eq_int_min_and_rhs_eq_neg_one_id, - )); - rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id - } - crate::ScalarKind::Uint => rhs_eq_zero_id, - _ => unreachable!(), - }; - - let const_one_id = self.get_constant_scalar_with(1, scalar)?; - let composite_one_id = maybe_splat_const(self, const_one_id); - let divisor_id = self.id_gen.next(); - block.body.push(Instruction::select( - right_type_id, - divisor_id, - divisor_selector_id, - composite_one_id, - rhs_id, - )); - let op = match (op, scalar.kind) { - (crate::BinaryOperator::Divide, crate::ScalarKind::Sint) => spirv::Op::SDiv, - (crate::BinaryOperator::Divide, crate::ScalarKind::Uint) => spirv::Op::UDiv, - (crate::BinaryOperator::Modulo, crate::ScalarKind::Sint) => spirv::Op::SRem, - (crate::BinaryOperator::Modulo, crate::ScalarKind::Uint) => spirv::Op::UMod, - _ => unreachable!(), - }; - let return_id = self.id_gen.next(); - block.body.push(Instruction::binary( - op, - return_type_id, - return_id, - lhs_id, - divisor_id, - )); - - function.consume(block, Instruction::return_value(return_id)); - function.to_words(&mut self.logical_layout.function_definitions); - Ok(()) - } - - /// Sets up an output variable that will handle part of the mesh shader output - fn write_mesh_return_global_variable( - &mut self, - ty: u32, - array_size_id: u32, - ) -> Result { - let array_ty = self.id_gen.next(); - Instruction::type_array(array_ty, ty, array_size_id) - .to_words(&mut self.logical_layout.declarations); - let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); - let var_id = self.id_gen.next(); - Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) - .to_words(&mut self.logical_layout.declarations); - Ok(var_id) - } - - /// This does various setup things to allow mesh shader entry points - /// to be properly written, such as creating the output variables - fn write_entry_point_mesh_shader_info( - &mut self, - iface: &mut FunctionInterface, - local_invocation_index_id: Option, - ir_module: &crate::Module, - prelude: &mut Block, - ep_context: &mut EntryPointContext, - ) -> Result<(), Error> { - let Some(ref mesh_info) = iface.mesh_info else { - return Ok(()); - }; - // Collect the members in the output structs - let out_members: Vec = - match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let vertex_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) - .unwrap() - .ty_id; - let primitive_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) - .unwrap() - .ty_id; - let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - // In the final return, we do a giant memcpy, for which this is helpful - let local_invocation_index_id = match local_invocation_index_id { - Some(a) => a, - None => { - let u32_id = self.get_u32_type_id(); - let var = self.id_gen.next(); - Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), - var, - spirv::StorageClass::Input, - None, - ) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate( - var, - spirv::Decoration::BuiltIn, - &[spirv::BuiltIn::LocalInvocationIndex as u32], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(var); - - let loaded_value = self.id_gen.next(); - prelude - .body - .push(Instruction::load(u32_id, loaded_value, var, None)); - loaded_value - } - }; - let u32_id = self.get_u32_type_id(); - // A general function variable that we guarantee to allow in the final return. It must be - // declared at the top of the function. Currently it is used in the memcpy part to keep - // track of the current index to copy. - let function_variable = self.id_gen.next(); - prelude.body.insert( - 0, - Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), - function_variable, - spirv::StorageClass::Function, - None, - ), - ); - // This is the information that is passed to the function writer - // so that it can write the final return logic - let mut mesh_return_info = super::MeshReturnInfo { - out_variable_id: self.global_variables[mesh_info.output_variable].var_id, - out_members, - - vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), - vertex_array_type_id, - vertex_members, - max_vertices_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), - primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), - primitive_array_type_id, - primitive_members, - max_primitives_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), - vertex_bindings: Vec::new(), - vertex_builtin_block: None, - primitive_bindings: Vec::new(), - primitive_builtin_block: None, - primitive_indices: None, - local_invocation_index_id, - workgroup_size: self - .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), - function_variable, - }; - let vert_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); - let prim_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); - - // Create the actual output variables and types. - // According to SPIR-V, - // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) - // * Each member with `location` must be in its own `Block` decorated `struct` - // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) - // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper - - // Write vertex builtin block - if mesh_return_info - .vertex_members - .iter() - .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) - { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.vertex_members { - if let crate::Binding::BuiltIn(_) = member.binding { - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], - )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } - } - _ => unreachable!(), - } - bi_index += 1; - } - } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = - self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_vertex_builtin_outputs")); - } - mesh_return_info.vertex_builtin_block = Some(v); - } - // Write primitive builtin block - if mesh_return_info.primitive_members.iter().any(|a| { - !matches!( - a.binding, - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices - ) | crate::Binding::Location { .. } - ) - }) { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.primitive_members { - if let crate::Binding::BuiltIn(bi) = member.binding { - // These need to be in their own block, unlike other builtins - if matches!( - bi, - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) { - continue; - } - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], - )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } - } - _ => unreachable!(), - } - bi_index += 1; - } - } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = - self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_builtin_outputs")); - } - mesh_return_info.primitive_builtin_block = Some(v); - } - - // Write vertex binding output blocks (1 array per output struct member) - for member in &mesh_return_info.vertex_members { - match member.binding { - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; - iface.varying_ids.push(v); - mesh_return_info.vertex_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), - } - } - // Write primitive binding output blocks (1 array per output struct member) - // Also write indices output block - for member in &mesh_return_info.primitive_members { - match member.binding { - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) => { - // This is written here instead of as part of the builtin block - let v = - self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::decorate( - v, - spirv::Decoration::BuiltIn, - &[match member.binding.to_built_in().unwrap() { - crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, - crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, - crate::BuiltIn::TriangleIndices => { - spirv::BuiltIn::PrimitiveTriangleIndicesEXT - } - _ => unreachable!(), - } as Word], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_indices_outputs")); - } - mesh_return_info.primitive_indices = Some(v); - } - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - - mesh_return_info.primitive_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), - } - } - - // Store this where it can be read later during function write - ep_context.mesh_state = Some(mesh_return_info); - - Ok(()) - } - - fn write_function( - &mut self, - ir_function: &crate::Function, - info: &FunctionInfo, - ir_module: &crate::Module, - mut interface: Option, - debug_info: &Option, - ) -> Result { - self.write_wrapped_functions(ir_function, info, ir_module)?; - - log::trace!("Generating code for {:?}", ir_function.name); - let mut function = Function::default(); - - let prelude_id = self.id_gen.next(); - let mut prelude = Block::new(prelude_id); - let mut ep_context = EntryPointContext { - argument_ids: Vec::new(), - results: Vec::new(), - task_payload_variable_id: if let Some(ref i) = interface { - i.task_payload.map(|a| self.global_variables[a].var_id) - } else { - None - }, - mesh_state: None, - }; - - let mut local_invocation_id = None; - - let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); - - let mut local_invocation_index_id = None; - - for argument in ir_function.arguments.iter() { - let class = spirv::StorageClass::Input; - let handle_ty = ir_module.types[argument.ty].inner.is_handle(); - let argument_type_id = if handle_ty { - self.get_handle_pointer_type_id(argument.ty, spirv::StorageClass::UniformConstant) - } else { - self.get_handle_type_id(argument.ty) - }; - - if let Some(ref mut iface) = interface { - let id = if let Some(ref binding) = argument.binding { - let name = argument.name.as_deref(); - - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - argument.ty, - binding, - )?; - iface.varying_ids.push(varying_id); - let id = self.load_io_with_f16_polyfill( - &mut prelude.body, - varying_id, - argument_type_id, - ); - - if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { - local_invocation_id = Some(id); - } else if binding - == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) - { - local_invocation_index_id = Some(id); - } - - id - } else if let crate::TypeInner::Struct { ref members, .. } = - ir_module.types[argument.ty].inner - { - let struct_id = self.id_gen.next(); - let mut constituent_ids = Vec::with_capacity(members.len()); - for member in members { - let type_id = self.get_handle_type_id(member.ty); - let name = member.name.as_deref(); - let binding = member.binding.as_ref().unwrap(); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - member.ty, - binding, - )?; - iface.varying_ids.push(varying_id); - let id = - self.load_io_with_f16_polyfill(&mut prelude.body, varying_id, type_id); - constituent_ids.push(id); - - if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { - local_invocation_id = Some(id); - } else if binding - == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) - { - local_invocation_index_id = Some(id); - } - } - prelude.body.push(Instruction::composite_construct( - argument_type_id, - struct_id, - &constituent_ids, - )); - struct_id - } else { - unreachable!("Missing argument binding on an entry point"); - }; - ep_context.argument_ids.push(id); - } else { - let argument_id = self.id_gen.next(); - let instruction = Instruction::function_parameter(argument_type_id, argument_id); - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = argument.name { - self.debugs.push(Instruction::name(argument_id, name)); - } - } - function.parameters.push(FunctionArgument { - instruction, - handle_id: if handle_ty { - let id = self.id_gen.next(); - prelude.body.push(Instruction::load( - self.get_handle_type_id(argument.ty), - id, - argument_id, - None, - )); - id - } else { - 0 - }, - }); - parameter_type_ids.push(argument_type_id); - }; - } - - let return_type_id = match ir_function.result { - Some(ref result) => { - if let Some(ref mut iface) = interface { - let mut has_point_size = false; - let class = spirv::StorageClass::Output; - if let Some(ref binding) = result.binding { - has_point_size |= - *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); - let type_id = self.get_handle_type_id(result.ty); - let varying_id = - if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { - 0 - } else { - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - None, - result.ty, - binding, - )?; - iface.varying_ids.push(varying_id); - varying_id - }; - ep_context.results.push(ResultMember { - id: varying_id, - type_id, - built_in: binding.to_built_in(), - }); - } else if let crate::TypeInner::Struct { ref members, .. } = - ir_module.types[result.ty].inner - { - for member in members { - let type_id = self.get_handle_type_id(member.ty); - let name = member.name.as_deref(); - let binding = member.binding.as_ref().unwrap(); - has_point_size |= - *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); - // This isn't an actual builtin in SPIR-V. It can only appear as the - // output of a task shader and the output is used when writing the - // entry point return, in which case the id is ignored anyway. - let varying_id = if *binding - == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) - { - 0 - } else { - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - member.ty, - binding, - )?; - iface.varying_ids.push(varying_id); - varying_id - }; - ep_context.results.push(ResultMember { - id: varying_id, - type_id, - built_in: binding.to_built_in(), - }); - } - } else { - unreachable!("Missing result binding on an entry point"); - } - - if self.flags.contains(WriterFlags::FORCE_POINT_SIZE) - && iface.stage == crate::ShaderStage::Vertex - && !has_point_size - { - // add point size artificially - let varying_id = self.id_gen.next(); - let pointer_type_id = self.get_f32_pointer_type_id(class); - Instruction::variable(pointer_type_id, varying_id, class, None) - .to_words(&mut self.logical_layout.declarations); - self.decorate( - varying_id, - spirv::Decoration::BuiltIn, - &[spirv::BuiltIn::PointSize as u32], - ); - iface.varying_ids.push(varying_id); - - let default_value_id = self.get_constant_scalar(crate::Literal::F32(1.0)); - prelude - .body - .push(Instruction::store(varying_id, default_value_id, None)); - } - self.void_type - } else { - self.get_handle_type_id(result.ty) - } - } - None => self.void_type, - }; - - if let Some(ref mut iface) = interface { - if let Some(task_payload) = iface.task_payload { - iface - .varying_ids - .push(self.global_variables[task_payload].var_id); - } - self.write_entry_point_mesh_shader_info( - iface, - local_invocation_index_id, - ir_module, - &mut prelude, - &mut ep_context, - )?; - } - - let lookup_function_type = LookupFunctionType { - parameter_type_ids, - return_type_id, - }; - - let function_id = self.id_gen.next(); - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = ir_function.name { - self.debugs.push(Instruction::name(function_id, name)); - } - } - - let function_type = self.get_function_type(lookup_function_type); - function.signature = Some(Instruction::function( - return_type_id, - function_id, - spirv::FunctionControl::empty(), - function_type, - )); - - if interface.is_some() { - function.entry_point_context = Some(ep_context); - } - - // fill up the `GlobalVariable::access_id` - for gv in self.global_variables.iter_mut() { - gv.reset_for_function(); - } - for (handle, var) in ir_module.global_variables.iter() { - if info[handle].is_empty() { - continue; - } - - let mut gv = self.global_variables[handle].clone(); - if let Some(ref mut iface) = interface { - // Have to include global variables in the interface - if self.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) { - iface.varying_ids.push(gv.var_id); - } - } - - // Handle globals are pre-emitted and should be loaded automatically. - // - // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing. - match ir_module.types[var.ty].inner { - crate::TypeInner::BindingArray { .. } => { - gv.access_id = gv.var_id; - } - _ => { - if var.space == crate::AddressSpace::Handle { - let var_type_id = self.get_handle_type_id(var.ty); - let id = self.id_gen.next(); - prelude - .body - .push(Instruction::load(var_type_id, id, gv.var_id, None)); - gv.access_id = gv.var_id; - gv.handle_id = id; - } else if global_needs_wrapper(ir_module, var) { - let class = map_storage_class(var.space); - let pointer_type_id = self.get_handle_pointer_type_id(var.ty, class); - let index_id = self.get_index_constant(0); - let id = self.id_gen.next(); - prelude.body.push(Instruction::access_chain( - pointer_type_id, - id, - gv.var_id, - &[index_id], - )); - gv.access_id = id; - } else { - // by default, the variable ID is accessed as is - gv.access_id = gv.var_id; - }; - } - } - - // work around borrow checking in the presence of `self.xxx()` calls - self.global_variables[handle] = gv; - } - - // Create a `BlockContext` for generating SPIR-V for the function's - // body. - let mut context = BlockContext { - ir_module, - ir_function, - fun_info: info, - function: &mut function, - // Re-use the cached expression table from prior functions. - cached: core::mem::take(&mut self.saved_cached), - - // Steal the Writer's temp list for a bit. - temp_list: core::mem::take(&mut self.temp_list), - force_loop_bounding: self.force_loop_bounding, - writer: self, - expression_constness: super::ExpressionConstnessTracker::from_arena( - &ir_function.expressions, - ), - ray_query_tracker_expr: crate::FastHashMap::default(), - }; - - // fill up the pre-emitted and const expressions - context.cached.reset(ir_function.expressions.len()); - for (handle, expr) in ir_function.expressions.iter() { - if (expr.needs_pre_emit() && !matches!(*expr, crate::Expression::LocalVariable(_))) - || context.expression_constness.is_const(handle) - { - context.cache_expression_value(handle, &mut prelude)?; - } - } - - for (handle, variable) in ir_function.local_variables.iter() { - let id = context.gen_id(); - - if context.writer.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = variable.name { - context.writer.debugs.push(Instruction::name(id, name)); - } - } - - let init_word = variable.init.map(|constant| context.cached[constant]); - let pointer_type_id = context - .writer - .get_handle_pointer_type_id(variable.ty, spirv::StorageClass::Function); - let instruction = Instruction::variable( - pointer_type_id, - id, - spirv::StorageClass::Function, - init_word.or_else(|| match ir_module.types[variable.ty].inner { - crate::TypeInner::RayQuery { .. } => None, - _ => { - let type_id = context.get_handle_type_id(variable.ty); - Some(context.writer.write_constant_null(type_id)) - } - }), - ); - context - .function - .variables - .insert(handle, LocalVariable { id, instruction }); - - if let crate::TypeInner::RayQuery { .. } = ir_module.types[variable.ty].inner { - // Don't refactor this into a struct: Although spirv itself allows opaque types in structs, - // the vulkan environment for spirv does not. Putting ray queries into structs can cause - // confusing bugs. - let u32_type_id = context.writer.get_u32_type_id(); - let ptr_u32_type_id = context - .writer - .get_pointer_type_id(u32_type_id, spirv::StorageClass::Function); - let tracker_id = context.gen_id(); - let tracker_init_id = context - .writer - .get_constant_scalar(crate::Literal::U32(super::RayQueryPoint::empty().bits())); - let tracker_instruction = Instruction::variable( - ptr_u32_type_id, - tracker_id, - spirv::StorageClass::Function, - Some(tracker_init_id), - ); - - context - .function - .ray_query_initialization_tracker_variables - .insert( - handle, - LocalVariable { - id: tracker_id, - instruction: tracker_instruction, - }, - ); - let f32_type_id = context.writer.get_f32_type_id(); - let ptr_f32_type_id = context - .writer - .get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); - let t_max_tracker_id = context.gen_id(); - let t_max_tracker_init_id = - context.writer.get_constant_scalar(crate::Literal::F32(0.0)); - let t_max_tracker_instruction = Instruction::variable( - ptr_f32_type_id, - t_max_tracker_id, - spirv::StorageClass::Function, - Some(t_max_tracker_init_id), - ); - - context.function.ray_query_t_max_tracker_variables.insert( - handle, - LocalVariable { - id: t_max_tracker_id, - instruction: t_max_tracker_instruction, - }, - ); - } - } - - for (handle, expr) in ir_function.expressions.iter() { - match *expr { - crate::Expression::LocalVariable(_) => { - // Cache the `OpVariable` instruction we generated above as - // the value of this expression. - context.cache_expression_value(handle, &mut prelude)?; - } - crate::Expression::Access { base, .. } - | crate::Expression::AccessIndex { base, .. } => { - // Count references to `base` by `Access` and `AccessIndex` - // instructions. See `access_uses` for details. - *context.function.access_uses.entry(base).or_insert(0) += 1; - } - _ => {} - } - } - - let next_id = context.gen_id(); - - context - .function - .consume(prelude, Instruction::branch(next_id)); - - let workgroup_vars_init_exit_block_id = - match (context.writer.zero_initialize_workgroup_memory, interface) { - ( - super::ZeroInitializeWorkgroupMemoryMode::Polyfill, - Some( - ref mut interface @ FunctionInterface { - stage: - crate::ShaderStage::Compute - | crate::ShaderStage::Mesh - | crate::ShaderStage::Task, - .. - }, - ), - ) => context.writer.generate_workgroup_vars_init_block( - next_id, - ir_module, - info, - local_invocation_id, - interface, - context.function, - ), - _ => None, - }; - - let main_id = if let Some(exit_id) = workgroup_vars_init_exit_block_id { - exit_id - } else { - next_id - }; - - context.write_function_body(main_id, debug_info.as_ref())?; - - // Consume the `BlockContext`, ending its borrows and letting the - // `Writer` steal back its cached expression table and temp_list. - let BlockContext { - cached, temp_list, .. - } = context; - self.saved_cached = cached; - self.temp_list = temp_list; - - function.to_words(&mut self.logical_layout.function_definitions); - - Ok(function_id) - } - - fn write_execution_mode( - &mut self, - function_id: Word, - mode: spirv::ExecutionMode, - ) -> Result<(), Error> { - //self.check(mode.required_capabilities())?; - Instruction::execution_mode(function_id, mode, &[]) - .to_words(&mut self.logical_layout.execution_modes); - Ok(()) - } - - // TODO Move to instructions module - fn write_entry_point( - &mut self, - entry_point: &crate::EntryPoint, - info: &FunctionInfo, - ir_module: &crate::Module, - debug_info: &Option, - ) -> Result { - let mut interface_ids = Vec::new(); - let function_id = self.write_function( - &entry_point.function, - info, - ir_module, - Some(FunctionInterface { - varying_ids: &mut interface_ids, - stage: entry_point.stage, - task_payload: entry_point.task_payload, - mesh_info: entry_point.mesh_info.clone(), - workgroup_size: entry_point.workgroup_size, - }), - debug_info, - )?; - - let exec_model = match entry_point.stage { - crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex, - crate::ShaderStage::Fragment => { - self.write_execution_mode(function_id, spirv::ExecutionMode::OriginUpperLeft)?; - match entry_point.early_depth_test { - Some(crate::EarlyDepthTest::Force) => { - self.write_execution_mode( - function_id, - spirv::ExecutionMode::EarlyFragmentTests, - )?; - } - Some(crate::EarlyDepthTest::Allow { conservative }) => { - // TODO: Consider emitting EarlyAndLateFragmentTestsAMD here, if available. - // https://github.khronos.org/SPIRV-Registry/extensions/AMD/SPV_AMD_shader_early_and_late_fragment_tests.html - // This permits early depth tests even if the shader writes to a storage - // binding - match conservative { - crate::ConservativeDepth::GreaterEqual => self.write_execution_mode( - function_id, - spirv::ExecutionMode::DepthGreater, - )?, - crate::ConservativeDepth::LessEqual => self.write_execution_mode( - function_id, - spirv::ExecutionMode::DepthLess, - )?, - crate::ConservativeDepth::Unchanged => self.write_execution_mode( - function_id, - spirv::ExecutionMode::DepthUnchanged, - )?, - } - } - None => {} - } - if let Some(ref result) = entry_point.function.result { - if contains_builtin( - result.binding.as_ref(), - result.ty, - &ir_module.types, - crate::BuiltIn::FragDepth, - ) { - self.write_execution_mode( - function_id, - spirv::ExecutionMode::DepthReplacing, - )?; - } - } - spirv::ExecutionModel::Fragment - } - crate::ShaderStage::Compute => { - let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; - Instruction::execution_mode( - function_id, - execution_mode, - &entry_point.workgroup_size, - ) - .to_words(&mut self.logical_layout.execution_modes); - spirv::ExecutionModel::GLCompute - } - crate::ShaderStage::Task => { - let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; - Instruction::execution_mode( - function_id, - execution_mode, - &entry_point.workgroup_size, - ) - .to_words(&mut self.logical_layout.execution_modes); - spirv::ExecutionModel::TaskEXT - } - crate::ShaderStage::Mesh => { - let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; - Instruction::execution_mode( - function_id, - execution_mode, - &entry_point.workgroup_size, - ) - .to_words(&mut self.logical_layout.execution_modes); - let mesh_info = entry_point.mesh_info.as_ref().unwrap(); - Instruction::execution_mode( - function_id, - match mesh_info.topology { - crate::MeshOutputTopology::Points => spirv::ExecutionMode::OutputPoints, - crate::MeshOutputTopology::Lines => spirv::ExecutionMode::OutputLinesEXT, - crate::MeshOutputTopology::Triangles => { - spirv::ExecutionMode::OutputTrianglesEXT - } - }, - &[], - ) - .to_words(&mut self.logical_layout.execution_modes); - Instruction::execution_mode( - function_id, - spirv::ExecutionMode::OutputVertices, - core::slice::from_ref(&mesh_info.max_vertices), - ) - .to_words(&mut self.logical_layout.execution_modes); - Instruction::execution_mode( - function_id, - spirv::ExecutionMode::OutputPrimitivesEXT, - core::slice::from_ref(&mesh_info.max_primitives), - ) - .to_words(&mut self.logical_layout.execution_modes); - spirv::ExecutionModel::MeshEXT - } - }; - //self.check(exec_model.required_capabilities())?; - - Ok(Instruction::entry_point( - exec_model, - function_id, - &entry_point.name, - interface_ids.as_slice(), - )) - } - - fn make_scalar(&mut self, id: Word, scalar: crate::Scalar) -> Instruction { - use crate::ScalarKind as Sk; - - let bits = (scalar.width * BITS_PER_BYTE) as u32; - match scalar.kind { - Sk::Sint | Sk::Uint => { - let signedness = if scalar.kind == Sk::Sint { - super::instructions::Signedness::Signed - } else { - super::instructions::Signedness::Unsigned - }; - let cap = match bits { - 8 => Some(spirv::Capability::Int8), - 16 => Some(spirv::Capability::Int16), - 64 => Some(spirv::Capability::Int64), - _ => None, - }; - if let Some(cap) = cap { - self.capabilities_used.insert(cap); - } - Instruction::type_int(id, bits, signedness) - } - Sk::Float => { - if bits == 64 { - self.capabilities_used.insert(spirv::Capability::Float64); - } - if bits == 16 { - self.capabilities_used.insert(spirv::Capability::Float16); - self.capabilities_used - .insert(spirv::Capability::StorageBuffer16BitAccess); - self.capabilities_used - .insert(spirv::Capability::UniformAndStorageBuffer16BitAccess); - if self.use_storage_input_output_16 { - self.capabilities_used - .insert(spirv::Capability::StorageInputOutput16); - } - } - Instruction::type_float(id, bits) - } - Sk::Bool => Instruction::type_bool(id), - Sk::AbstractInt | Sk::AbstractFloat => { - unreachable!("abstract types should never reach the backend"); - } - } - } - - fn request_type_capabilities(&mut self, inner: &crate::TypeInner) -> Result<(), Error> { - match *inner { - crate::TypeInner::Image { - dim, - arrayed, - class, - } => { - let sampled = match class { - crate::ImageClass::Sampled { .. } => true, - crate::ImageClass::Depth { .. } => true, - crate::ImageClass::Storage { format, .. } => { - self.request_image_format_capabilities(format.into())?; - false - } - crate::ImageClass::External => unimplemented!(), - }; - - match dim { - crate::ImageDimension::D1 => { - if sampled { - self.require_any("sampled 1D images", &[spirv::Capability::Sampled1D])?; - } else { - self.require_any("1D storage images", &[spirv::Capability::Image1D])?; - } - } - crate::ImageDimension::Cube if arrayed => { - if sampled { - self.require_any( - "sampled cube array images", - &[spirv::Capability::SampledCubeArray], - )?; - } else { - self.require_any( - "cube array storage images", - &[spirv::Capability::ImageCubeArray], - )?; - } - } - _ => {} - } - } - crate::TypeInner::AccelerationStructure { .. } => { - self.require_any("Acceleration Structure", &[spirv::Capability::RayQueryKHR])?; - } - crate::TypeInner::RayQuery { .. } => { - self.require_any("Ray Query", &[spirv::Capability::RayQueryKHR])?; - } - crate::TypeInner::Atomic(crate::Scalar { width: 8, kind: _ }) => { - self.require_any("64 bit integer atomics", &[spirv::Capability::Int64Atomics])?; - } - crate::TypeInner::Atomic(crate::Scalar { - width: 4, - kind: crate::ScalarKind::Float, - }) => { - self.require_any( - "32 bit floating-point atomics", - &[spirv::Capability::AtomicFloat32AddEXT], - )?; - self.use_extension("SPV_EXT_shader_atomic_float_add"); - } - // 16 bit floating-point support requires Float16 capability - crate::TypeInner::Matrix { - scalar: crate::Scalar::F16, - .. - } - | crate::TypeInner::Vector { - scalar: crate::Scalar::F16, - .. - } - | crate::TypeInner::Scalar(crate::Scalar::F16) => { - self.require_any("16 bit floating-point", &[spirv::Capability::Float16])?; - self.use_extension("SPV_KHR_16bit_storage"); - } - _ => {} - } - Ok(()) - } - - fn write_numeric_type_declaration_local(&mut self, id: Word, numeric: NumericType) { - let instruction = match numeric { - NumericType::Scalar(scalar) => self.make_scalar(id, scalar), - NumericType::Vector { size, scalar } => { - let scalar_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); - Instruction::type_vector(id, scalar_id, size) - } - NumericType::Matrix { - columns, - rows, - scalar, - } => { - let column_id = - self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); - Instruction::type_matrix(id, column_id, columns) - } - }; - - instruction.to_words(&mut self.logical_layout.declarations); - } - - fn write_type_declaration_local(&mut self, id: Word, local_ty: LocalType) { - let instruction = match local_ty { - LocalType::Numeric(numeric) => { - self.write_numeric_type_declaration_local(id, numeric); - return; - } - LocalType::Pointer { base, class } => Instruction::type_pointer(id, class, base), - LocalType::Image(image) => { - let local_type = LocalType::Numeric(NumericType::Scalar(image.sampled_type)); - let type_id = self.get_localtype_id(local_type); - Instruction::type_image(id, type_id, image.dim, image.flags, image.image_format) - } - LocalType::Sampler => Instruction::type_sampler(id), - LocalType::SampledImage { image_type_id } => { - Instruction::type_sampled_image(id, image_type_id) - } - LocalType::BindingArray { base, size } => { - let inner_ty = self.get_handle_type_id(base); - let scalar_id = self.get_constant_scalar(crate::Literal::U32(size)); - Instruction::type_array(id, inner_ty, scalar_id) - } - LocalType::AccelerationStructure => Instruction::type_acceleration_structure(id), - LocalType::RayQuery => Instruction::type_ray_query(id), - }; - - instruction.to_words(&mut self.logical_layout.declarations); - } - - fn write_type_declaration_arena( - &mut self, - module: &crate::Module, - handle: Handle, - ) -> Result { - let ty = &module.types[handle]; - // If it's a type that needs SPIR-V capabilities, request them now. - // This needs to happen regardless of the LocalType lookup succeeding, - // because some types which map to the same LocalType have different - // capability requirements. See https://github.com/gfx-rs/wgpu/issues/5569 - self.request_type_capabilities(&ty.inner)?; - let id = if let Some(local) = self.localtype_from_inner(&ty.inner) { - // This type can be represented as a `LocalType`, so check if we've - // already written an instruction for it. If not, do so now, with - // `write_type_declaration_local`. - match self.lookup_type.entry(LookupType::Local(local)) { - // We already have an id for this `LocalType`. - Entry::Occupied(e) => *e.get(), - - // It's a type we haven't seen before. - Entry::Vacant(e) => { - let id = self.id_gen.next(); - e.insert(id); - - self.write_type_declaration_local(id, local); - - id - } - } - } else { - use spirv::Decoration; - - let id = self.id_gen.next(); - let instruction = match ty.inner { - crate::TypeInner::Array { base, size, stride } => { - self.decorate(id, Decoration::ArrayStride, &[stride]); - - let type_id = self.get_handle_type_id(base); - match size.resolve(module.to_ctx())? { - crate::proc::IndexableLength::Known(length) => { - let length_id = self.get_index_constant(length); - Instruction::type_array(id, type_id, length_id) - } - crate::proc::IndexableLength::Dynamic => { - Instruction::type_runtime_array(id, type_id) - } - } - } - crate::TypeInner::BindingArray { base, size } => { - let type_id = self.get_handle_type_id(base); - match size.resolve(module.to_ctx())? { - crate::proc::IndexableLength::Known(length) => { - let length_id = self.get_index_constant(length); - Instruction::type_array(id, type_id, length_id) - } - crate::proc::IndexableLength::Dynamic => { - Instruction::type_runtime_array(id, type_id) - } - } - } - crate::TypeInner::Struct { - ref members, - span: _, - } => { - let mut has_runtime_array = false; - let mut member_ids = Vec::with_capacity(members.len()); - for (index, member) in members.iter().enumerate() { - let member_ty = &module.types[member.ty]; - match member_ty.inner { - crate::TypeInner::Array { - base: _, - size: crate::ArraySize::Dynamic, - stride: _, - } => { - has_runtime_array = true; - } - _ => (), - } - self.decorate_struct_member(id, index, member, &module.types)?; - let member_id = self.get_handle_type_id(member.ty); - member_ids.push(member_id); - } - if has_runtime_array { - self.decorate(id, Decoration::Block, &[]); - } - Instruction::type_struct(id, member_ids.as_slice()) - } - - // These all have TypeLocal representations, so they should have been - // handled by `write_type_declaration_local` above. - crate::TypeInner::Scalar(_) - | crate::TypeInner::Atomic(_) - | crate::TypeInner::Vector { .. } - | crate::TypeInner::Matrix { .. } - | crate::TypeInner::Pointer { .. } - | crate::TypeInner::ValuePointer { .. } - | crate::TypeInner::Image { .. } - | crate::TypeInner::Sampler { .. } - | crate::TypeInner::AccelerationStructure { .. } - | crate::TypeInner::RayQuery { .. } => unreachable!(), - }; - - instruction.to_words(&mut self.logical_layout.declarations); - id - }; - - // Add this handle as a new alias for that type. - self.lookup_type.insert(LookupType::Handle(handle), id); - - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = ty.name { - self.debugs.push(Instruction::name(id, name)); - } - } - - Ok(id) - } - - fn request_image_format_capabilities( - &mut self, - format: spirv::ImageFormat, - ) -> Result<(), Error> { - use spirv::ImageFormat as If; - match format { - If::Rg32f - | If::Rg16f - | If::R11fG11fB10f - | If::R16f - | If::Rgba16 - | If::Rgb10A2 - | If::Rg16 - | If::Rg8 - | If::R16 - | If::R8 - | If::Rgba16Snorm - | If::Rg16Snorm - | If::Rg8Snorm - | If::R16Snorm - | If::R8Snorm - | If::Rg32i - | If::Rg16i - | If::Rg8i - | If::R16i - | If::R8i - | If::Rgb10a2ui - | If::Rg32ui - | If::Rg16ui - | If::Rg8ui - | If::R16ui - | If::R8ui => self.require_any( - "storage image format", - &[spirv::Capability::StorageImageExtendedFormats], - ), - If::R64ui | If::R64i => { - self.use_extension("SPV_EXT_shader_image_int64"); - self.require_any( - "64-bit integer storage image format", - &[spirv::Capability::Int64ImageEXT], - ) - } - If::Unknown - | If::Rgba32f - | If::Rgba16f - | If::R32f - | If::Rgba8 - | If::Rgba8Snorm - | If::Rgba32i - | If::Rgba16i - | If::Rgba8i - | If::R32i - | If::Rgba32ui - | If::Rgba16ui - | If::Rgba8ui - | If::R32ui => Ok(()), - } - } - - pub(super) fn get_index_constant(&mut self, index: Word) -> Word { - self.get_constant_scalar(crate::Literal::U32(index)) - } - - pub(super) fn get_constant_scalar_with( - &mut self, - value: u8, - scalar: crate::Scalar, - ) -> Result { - Ok( - self.get_constant_scalar(crate::Literal::new(value, scalar).ok_or( - Error::Validation("Unexpected kind and/or width for Literal"), - )?), - ) - } - - pub(super) fn get_constant_scalar(&mut self, value: crate::Literal) -> Word { - let scalar = CachedConstant::Literal(value.into()); - if let Some(&id) = self.cached_constants.get(&scalar) { - return id; - } - let id = self.id_gen.next(); - self.write_constant_scalar(id, &value, None); - self.cached_constants.insert(scalar, id); - id - } - - fn write_constant_scalar( - &mut self, - id: Word, - value: &crate::Literal, - debug_name: Option<&String>, - ) { - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(name) = debug_name { - self.debugs.push(Instruction::name(id, name)); - } - } - let type_id = self.get_numeric_type_id(NumericType::Scalar(value.scalar())); - let instruction = match *value { - crate::Literal::F64(value) => { - let bits = value.to_bits(); - Instruction::constant_64bit(type_id, id, bits as u32, (bits >> 32) as u32) - } - crate::Literal::F32(value) => Instruction::constant_32bit(type_id, id, value.to_bits()), - crate::Literal::F16(value) => { - let low = value.to_bits(); - Instruction::constant_16bit(type_id, id, low as u32) - } - crate::Literal::U32(value) => Instruction::constant_32bit(type_id, id, value), - crate::Literal::I32(value) => Instruction::constant_32bit(type_id, id, value as u32), - crate::Literal::U64(value) => { - Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) - } - crate::Literal::I64(value) => { - Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) - } - crate::Literal::Bool(true) => Instruction::constant_true(type_id, id), - crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), - crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { - unreachable!("Abstract types should not appear in IR presented to backends"); - } - }; - - instruction.to_words(&mut self.logical_layout.declarations); - } - - pub(super) fn get_constant_composite( - &mut self, - ty: LookupType, - constituent_ids: &[Word], - ) -> Word { - let composite = CachedConstant::Composite { - ty, - constituent_ids: constituent_ids.to_vec(), - }; - if let Some(&id) = self.cached_constants.get(&composite) { - return id; - } - let id = self.id_gen.next(); - self.write_constant_composite(id, ty, constituent_ids, None); - self.cached_constants.insert(composite, id); - id - } - - fn write_constant_composite( - &mut self, - id: Word, - ty: LookupType, - constituent_ids: &[Word], - debug_name: Option<&String>, - ) { - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(name) = debug_name { - self.debugs.push(Instruction::name(id, name)); - } - } - let type_id = self.get_type_id(ty); - Instruction::constant_composite(type_id, id, constituent_ids) - .to_words(&mut self.logical_layout.declarations); - } - - pub(super) fn get_constant_null(&mut self, type_id: Word) -> Word { - let null = CachedConstant::ZeroValue(type_id); - if let Some(&id) = self.cached_constants.get(&null) { - return id; - } - let id = self.write_constant_null(type_id); - self.cached_constants.insert(null, id); - id - } - - pub(super) fn write_constant_null(&mut self, type_id: Word) -> Word { - let null_id = self.id_gen.next(); - Instruction::constant_null(type_id, null_id) - .to_words(&mut self.logical_layout.declarations); - null_id - } - - fn write_constant_expr( - &mut self, - handle: Handle, - ir_module: &crate::Module, - mod_info: &ModuleInfo, - ) -> Result { - let id = match ir_module.global_expressions[handle] { - crate::Expression::Literal(literal) => self.get_constant_scalar(literal), - crate::Expression::Constant(constant) => { - let constant = &ir_module.constants[constant]; - self.constant_ids[constant.init] - } - crate::Expression::ZeroValue(ty) => { - let type_id = self.get_handle_type_id(ty); - self.get_constant_null(type_id) - } - crate::Expression::Compose { ty, ref components } => { - let component_ids: Vec<_> = crate::proc::flatten_compose( - ty, - components, - &ir_module.global_expressions, - &ir_module.types, - ) - .map(|component| self.constant_ids[component]) - .collect(); - self.get_constant_composite(LookupType::Handle(ty), component_ids.as_slice()) - } - crate::Expression::Splat { size, value } => { - let value_id = self.constant_ids[value]; - let component_ids = &[value_id; 4][..size as usize]; - - let ty = self.get_expression_lookup_type(&mod_info[handle]); - - self.get_constant_composite(ty, component_ids) - } - _ => { - return Err(Error::Override); - } - }; - - self.constant_ids[handle] = id; - - Ok(id) - } - - pub(super) fn write_control_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { - let memory_scope = if flags.contains(crate::Barrier::STORAGE) { - spirv::Scope::Device - } else if flags.contains(crate::Barrier::SUB_GROUP) { - spirv::Scope::Subgroup - } else { - spirv::Scope::Workgroup - }; - let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; - semantics.set( - spirv::MemorySemantics::UNIFORM_MEMORY, - flags.contains(crate::Barrier::STORAGE), - ); - semantics.set( - spirv::MemorySemantics::WORKGROUP_MEMORY, - flags.contains(crate::Barrier::WORK_GROUP), - ); - semantics.set( - spirv::MemorySemantics::SUBGROUP_MEMORY, - flags.contains(crate::Barrier::SUB_GROUP), - ); - semantics.set( - spirv::MemorySemantics::IMAGE_MEMORY, - flags.contains(crate::Barrier::TEXTURE), - ); - let exec_scope_id = if flags.contains(crate::Barrier::SUB_GROUP) { - self.get_index_constant(spirv::Scope::Subgroup as u32) - } else { - self.get_index_constant(spirv::Scope::Workgroup as u32) - }; - let mem_scope_id = self.get_index_constant(memory_scope as u32); - let semantics_id = self.get_index_constant(semantics.bits()); - block.body.push(Instruction::control_barrier( - exec_scope_id, - mem_scope_id, - semantics_id, - )); - } - - pub(super) fn write_memory_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { - let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; - semantics.set( - spirv::MemorySemantics::UNIFORM_MEMORY, - flags.contains(crate::Barrier::STORAGE), - ); - semantics.set( - spirv::MemorySemantics::WORKGROUP_MEMORY, - flags.contains(crate::Barrier::WORK_GROUP), - ); - semantics.set( - spirv::MemorySemantics::SUBGROUP_MEMORY, - flags.contains(crate::Barrier::SUB_GROUP), - ); - semantics.set( - spirv::MemorySemantics::IMAGE_MEMORY, - flags.contains(crate::Barrier::TEXTURE), - ); - let mem_scope_id = if flags.contains(crate::Barrier::STORAGE) { - self.get_index_constant(spirv::Scope::Device as u32) - } else if flags.contains(crate::Barrier::SUB_GROUP) { - self.get_index_constant(spirv::Scope::Subgroup as u32) - } else { - self.get_index_constant(spirv::Scope::Workgroup as u32) - }; - let semantics_id = self.get_index_constant(semantics.bits()); - block - .body - .push(Instruction::memory_barrier(mem_scope_id, semantics_id)); - } - - fn generate_workgroup_vars_init_block( - &mut self, - entry_id: Word, - ir_module: &crate::Module, - info: &FunctionInfo, - local_invocation_id: Option, - interface: &mut FunctionInterface, - function: &mut Function, - ) -> Option { - let body = ir_module - .global_variables - .iter() - .filter(|&(handle, var)| { - !info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup - }) - .map(|(handle, var)| { - // It's safe to use `var_id` here, not `access_id`, because only - // variables in the `Uniform` and `StorageBuffer` address spaces - // get wrapped, and we're initializing `WorkGroup` variables. - let var_id = self.global_variables[handle].var_id; - let var_type_id = self.get_handle_type_id(var.ty); - let init_word = self.get_constant_null(var_type_id); - Instruction::store(var_id, init_word, None) - }) - .collect::>(); - - if body.is_empty() { - return None; - } - - let uint3_type_id = self.get_vec3u_type_id(); - - let mut pre_if_block = Block::new(entry_id); - - let local_invocation_id = if let Some(local_invocation_id) = local_invocation_id { - local_invocation_id - } else { - let varying_id = self.id_gen.next(); - let class = spirv::StorageClass::Input; - let pointer_type_id = self.get_vec3u_pointer_type_id(class); - - Instruction::variable(pointer_type_id, varying_id, class, None) - .to_words(&mut self.logical_layout.declarations); - - self.decorate( - varying_id, - spirv::Decoration::BuiltIn, - &[spirv::BuiltIn::LocalInvocationId as u32], - ); - - interface.varying_ids.push(varying_id); - let id = self.id_gen.next(); - pre_if_block - .body - .push(Instruction::load(uint3_type_id, id, varying_id, None)); - - id - }; - - let zero_id = self.get_constant_null(uint3_type_id); - let bool3_type_id = self.get_vec3_bool_type_id(); - - let eq_id = self.id_gen.next(); - pre_if_block.body.push(Instruction::binary( - spirv::Op::IEqual, - bool3_type_id, - eq_id, - local_invocation_id, - zero_id, - )); - - let condition_id = self.id_gen.next(); - let bool_type_id = self.get_bool_type_id(); - pre_if_block.body.push(Instruction::relational( - spirv::Op::All, - bool_type_id, - condition_id, - eq_id, - )); - - let merge_id = self.id_gen.next(); - pre_if_block.body.push(Instruction::selection_merge( - merge_id, - spirv::SelectionControl::NONE, - )); - - let accept_id = self.id_gen.next(); - function.consume( - pre_if_block, - Instruction::branch_conditional(condition_id, accept_id, merge_id), - ); - - let accept_block = Block { - label_id: accept_id, - body, - }; - function.consume(accept_block, Instruction::branch(merge_id)); - - let mut post_if_block = Block::new(merge_id); - - self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block); - - let next_id = self.id_gen.next(); - function.consume(post_if_block, Instruction::branch(next_id)); - Some(next_id) - } - - /// Generate an `OpVariable` for one value in an [`EntryPoint`]'s IO interface. - /// - /// The [`Binding`]s of the arguments and result of an [`EntryPoint`]'s - /// [`Function`] describe a SPIR-V shader interface. In SPIR-V, the - /// interface is represented by global variables in the `Input` and `Output` - /// storage classes, with decorations indicating which builtin or location - /// each variable corresponds to. - /// - /// This function emits a single global `OpVariable` for a single value from - /// the interface, and adds appropriate decorations to indicate which - /// builtin or location it represents, how it should be interpolated, and so - /// on. The `class` argument gives the variable's SPIR-V storage class, - /// which should be either [`Input`] or [`Output`]. - /// - /// [`Binding`]: crate::Binding - /// [`Function`]: crate::Function - /// [`EntryPoint`]: crate::EntryPoint - /// [`Input`]: spirv::StorageClass::Input - /// [`Output`]: spirv::StorageClass::Output - fn write_varying( - &mut self, - ir_module: &crate::Module, - stage: crate::ShaderStage, - class: spirv::StorageClass, - debug_name: Option<&str>, - ty: Handle, - binding: &crate::Binding, - ) -> Result { - let id = self.id_gen.next(); - let ty_inner = &ir_module.types[ty].inner; - let needs_polyfill = self.needs_f16_polyfill(ty_inner); - - let pointer_type_id = if needs_polyfill { - let f32_value_local = - super::f16_polyfill::F16IoPolyfill::create_polyfill_type(ty_inner) - .expect("needs_polyfill returned true but create_polyfill_type returned None"); - - let f32_type_id = self.get_localtype_id(f32_value_local); - let ptr_id = self.get_pointer_type_id(f32_type_id, class); - self.io_f16_polyfills.register_io_var(id, f32_type_id); - - ptr_id - } else { - self.get_handle_pointer_type_id(ty, class) - }; - - Instruction::variable(pointer_type_id, id, class, None) - .to_words(&mut self.logical_layout.declarations); - - if self - .flags - .contains(WriterFlags::DEBUG | WriterFlags::LABEL_VARYINGS) - { - if let Some(name) = debug_name { - self.debugs.push(Instruction::name(id, name)); - } - } - - let binding = self.map_binding(ir_module, stage, class, ty, binding)?; - self.write_binding(id, binding); - - Ok(id) - } - - pub fn write_binding(&mut self, id: Word, binding: BindingDecorations) { - match binding { - BindingDecorations::None => (), - BindingDecorations::BuiltIn(bi, others) => { - self.decorate(id, spirv::Decoration::BuiltIn, &[bi as u32]); - for other in others { - self.decorate(id, other, &[]); - } - } - BindingDecorations::Location { - location, - others, - blend_src, - } => { - self.decorate(id, spirv::Decoration::Location, &[location]); - for other in others { - self.decorate(id, other, &[]); - } - if let Some(blend_src) = blend_src { - self.decorate(id, spirv::Decoration::Index, &[blend_src]); - } - } - } - } - - pub fn write_binding_struct_member( - &mut self, - struct_id: Word, - member_idx: Word, - binding_info: BindingDecorations, - ) { - match binding_info { - BindingDecorations::None => (), - BindingDecorations::BuiltIn(bi, others) => { - self.annotations.push(Instruction::member_decorate( - struct_id, - member_idx, - spirv::Decoration::BuiltIn, - &[bi as Word], - )); - for other in others { - self.annotations.push(Instruction::member_decorate( - struct_id, - member_idx, - other, - &[], - )); - } - } - BindingDecorations::Location { - location, - others, - blend_src, - } => { - self.annotations.push(Instruction::member_decorate( - struct_id, - member_idx, - spirv::Decoration::Location, - &[location], - )); - for other in others { - self.annotations.push(Instruction::member_decorate( - struct_id, - member_idx, - other, - &[], - )); - } - if let Some(blend_src) = blend_src { - self.annotations.push(Instruction::member_decorate( - struct_id, - member_idx, - spirv::Decoration::Index, - &[blend_src], - )); - } - } - } - } - - pub fn map_binding( - &mut self, - ir_module: &crate::Module, - stage: crate::ShaderStage, - class: spirv::StorageClass, - ty: Handle, - binding: &crate::Binding, - ) -> Result { - use spirv::BuiltIn; - use spirv::Decoration; - match *binding { - crate::Binding::Location { - location, - interpolation, - sampling, - blend_src, - per_primitive, - } => { - let mut others = ArrayVec::new(); - - let no_decorations = - // VUID-StandaloneSpirv-Flat-06202 - // > The Flat, NoPerspective, Sample, and Centroid decorations - // > must not be used on variables with the Input storage class in a vertex shader - (class == spirv::StorageClass::Input && stage == crate::ShaderStage::Vertex) || - // VUID-StandaloneSpirv-Flat-06201 - // > The Flat, NoPerspective, Sample, and Centroid decorations - // > must not be used on variables with the Output storage class in a fragment shader - (class == spirv::StorageClass::Output && stage == crate::ShaderStage::Fragment); - - if !no_decorations { - match interpolation { - // Perspective-correct interpolation is the default in SPIR-V. - None | Some(crate::Interpolation::Perspective) => (), - Some(crate::Interpolation::Flat) => { - others.push(Decoration::Flat); - } - Some(crate::Interpolation::Linear) => { - others.push(Decoration::NoPerspective); - } - } - match sampling { - // Center sampling is the default in SPIR-V. - None - | Some( - crate::Sampling::Center - | crate::Sampling::First - | crate::Sampling::Either, - ) => (), - Some(crate::Sampling::Centroid) => { - others.push(Decoration::Centroid); - } - Some(crate::Sampling::Sample) => { - self.require_any( - "per-sample interpolation", - &[spirv::Capability::SampleRateShading], - )?; - others.push(Decoration::Sample); - } - } - } - if per_primitive && stage == crate::ShaderStage::Fragment { - others.push(Decoration::PerPrimitiveEXT); - self.require_mesh_shaders()?; - } - Ok(BindingDecorations::Location { - location, - others, - blend_src, - }) - } - crate::Binding::BuiltIn(built_in) => { - use crate::BuiltIn as Bi; - let mut others = ArrayVec::new(); - - if matches!( - built_in, - Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices - ) { - self.require_mesh_shaders()?; - } - - let built_in = match built_in { - Bi::Position { invariant } => { - if invariant { - others.push(Decoration::Invariant); - } - - if class == spirv::StorageClass::Output { - BuiltIn::Position - } else { - BuiltIn::FragCoord - } - } - Bi::ViewIndex => { - self.require_any("`view_index` built-in", &[spirv::Capability::MultiView])?; - BuiltIn::ViewIndex - } - // vertex - Bi::BaseInstance => BuiltIn::BaseInstance, - Bi::BaseVertex => BuiltIn::BaseVertex, - Bi::ClipDistance => { - self.require_any( - "`clip_distance` built-in", - &[spirv::Capability::ClipDistance], - )?; - BuiltIn::ClipDistance - } - Bi::CullDistance => { - self.require_any( - "`cull_distance` built-in", - &[spirv::Capability::CullDistance], - )?; - BuiltIn::CullDistance - } - Bi::InstanceIndex => BuiltIn::InstanceIndex, - Bi::PointSize => BuiltIn::PointSize, - Bi::VertexIndex => BuiltIn::VertexIndex, - Bi::DrawID => BuiltIn::DrawIndex, - // fragment - Bi::FragDepth => BuiltIn::FragDepth, - Bi::PointCoord => BuiltIn::PointCoord, - Bi::FrontFacing => BuiltIn::FrontFacing, - Bi::PrimitiveIndex => { - self.require_any( - "`primitive_index` built-in", - &[spirv::Capability::Geometry], - )?; - BuiltIn::PrimitiveId - } - Bi::Barycentric => { - self.require_any( - "`barycentric` built-in", - &[spirv::Capability::FragmentBarycentricKHR], - )?; - self.use_extension("SPV_KHR_fragment_shader_barycentric"); - BuiltIn::BaryCoordKHR - } - Bi::SampleIndex => { - self.require_any( - "`sample_index` built-in", - &[spirv::Capability::SampleRateShading], - )?; - - BuiltIn::SampleId - } - Bi::SampleMask => BuiltIn::SampleMask, - // compute - Bi::GlobalInvocationId => BuiltIn::GlobalInvocationId, - Bi::LocalInvocationId => BuiltIn::LocalInvocationId, - Bi::LocalInvocationIndex => BuiltIn::LocalInvocationIndex, - Bi::WorkGroupId => BuiltIn::WorkgroupId, - Bi::WorkGroupSize => BuiltIn::WorkgroupSize, - Bi::NumWorkGroups => BuiltIn::NumWorkgroups, - // Subgroup - Bi::NumSubgroups => { - self.require_any( - "`num_subgroups` built-in", - &[spirv::Capability::GroupNonUniform], - )?; - BuiltIn::NumSubgroups - } - Bi::SubgroupId => { - self.require_any( - "`subgroup_id` built-in", - &[spirv::Capability::GroupNonUniform], - )?; - BuiltIn::SubgroupId - } - Bi::SubgroupSize => { - self.require_any( - "`subgroup_size` built-in", - &[ - spirv::Capability::GroupNonUniform, - spirv::Capability::SubgroupBallotKHR, - ], - )?; - BuiltIn::SubgroupSize - } - Bi::SubgroupInvocationId => { - self.require_any( - "`subgroup_invocation_id` built-in", - &[ - spirv::Capability::GroupNonUniform, - spirv::Capability::SubgroupBallotKHR, - ], - )?; - BuiltIn::SubgroupLocalInvocationId - } - Bi::CullPrimitive => BuiltIn::CullPrimitiveEXT, - Bi::PointIndex => BuiltIn::PrimitivePointIndicesEXT, - Bi::LineIndices => BuiltIn::PrimitiveLineIndicesEXT, - Bi::TriangleIndices => BuiltIn::PrimitiveTriangleIndicesEXT, - // No decoration, this EmitMeshTasksEXT is called at function return - Bi::MeshTaskSize => return Ok(BindingDecorations::None), - // These aren't normal builtins and don't occur in function output - Bi::VertexCount | Bi::Vertices | Bi::PrimitiveCount | Bi::Primitives => { - unreachable!() - } - }; - - use crate::ScalarKind as Sk; - - // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: - // - // > Any variable with integer or double-precision floating- - // > point type and with Input storage class in a fragment - // > shader, must be decorated Flat - if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { - let is_flat = match ir_module.types[ty].inner { - crate::TypeInner::Scalar(scalar) - | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { - Sk::Uint | Sk::Sint | Sk::Bool => true, - Sk::Float => false, - Sk::AbstractInt | Sk::AbstractFloat => { - return Err(Error::Validation( - "Abstract types should not appear in IR presented to backends", - )) - } - }, - _ => false, - }; - - if is_flat { - others.push(Decoration::Flat); - } - } - Ok(BindingDecorations::BuiltIn(built_in, others)) - } - } - } - - /// Load an IO variable, converting from `f32` to `f16` if polyfill is active. - /// Returns the id of the loaded value matching `target_type_id`. - pub(super) fn load_io_with_f16_polyfill( - &mut self, - body: &mut Vec, - varying_id: Word, - target_type_id: Word, - ) -> Word { - let tmp = self.id_gen.next(); - if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { - body.push(Instruction::load(f32_ty, tmp, varying_id, None)); - let converted = self.id_gen.next(); - super::f16_polyfill::F16IoPolyfill::emit_f32_to_f16_conversion( - tmp, - target_type_id, - converted, - body, - ); - converted - } else { - body.push(Instruction::load(target_type_id, tmp, varying_id, None)); - tmp - } - } - - /// Store an IO variable, converting from `f16` to `f32` if polyfill is active. - pub(super) fn store_io_with_f16_polyfill( - &mut self, - body: &mut Vec, - varying_id: Word, - value_id: Word, - ) { - if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { - let converted = self.id_gen.next(); - super::f16_polyfill::F16IoPolyfill::emit_f16_to_f32_conversion( - value_id, f32_ty, converted, body, - ); - body.push(Instruction::store(varying_id, converted, None)); - } else { - body.push(Instruction::store(varying_id, value_id, None)); - } - } - - fn write_global_variable( - &mut self, - ir_module: &crate::Module, - global_variable: &crate::GlobalVariable, - ) -> Result { - use spirv::Decoration; - - let id = self.id_gen.next(); - let class = map_storage_class(global_variable.space); - - //self.check(class.required_capabilities())?; - - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = global_variable.name { - self.debugs.push(Instruction::name(id, name)); - } - } - - let storage_access = match global_variable.space { - crate::AddressSpace::Storage { access } => Some(access), - _ => match ir_module.types[global_variable.ty].inner { - crate::TypeInner::Image { - class: crate::ImageClass::Storage { access, .. }, - .. - } => Some(access), - _ => None, - }, - }; - if let Some(storage_access) = storage_access { - if !storage_access.contains(crate::StorageAccess::LOAD) { - self.decorate(id, Decoration::NonReadable, &[]); - } - if !storage_access.contains(crate::StorageAccess::STORE) { - self.decorate(id, Decoration::NonWritable, &[]); - } - } - - // Note: we should be able to substitute `binding_array`, - // but there is still code that tries to register the pre-substituted type, - // and it is failing on 0. - let mut substitute_inner_type_lookup = None; - if let Some(ref res_binding) = global_variable.binding { - let bind_target = self.resolve_resource_binding(res_binding)?; - self.decorate(id, Decoration::DescriptorSet, &[bind_target.descriptor_set]); - self.decorate(id, Decoration::Binding, &[bind_target.binding]); - - if let Some(remapped_binding_array_size) = bind_target.binding_array_size { - if let crate::TypeInner::BindingArray { base, .. } = - ir_module.types[global_variable.ty].inner - { - let binding_array_type_id = - self.get_type_id(LookupType::Local(LocalType::BindingArray { - base, - size: remapped_binding_array_size, - })); - substitute_inner_type_lookup = Some(LookupType::Local(LocalType::Pointer { - base: binding_array_type_id, - class, - })); - } - } - }; - - let init_word = global_variable - .init - .map(|constant| self.constant_ids[constant]); - let inner_type_id = self.get_type_id( - substitute_inner_type_lookup.unwrap_or(LookupType::Handle(global_variable.ty)), - ); - - // generate the wrapping structure if needed - let pointer_type_id = if global_needs_wrapper(ir_module, global_variable) { - let wrapper_type_id = self.id_gen.next(); - - self.decorate(wrapper_type_id, Decoration::Block, &[]); - let member = crate::StructMember { - name: None, - ty: global_variable.ty, - binding: None, - offset: 0, - }; - self.decorate_struct_member(wrapper_type_id, 0, &member, &ir_module.types)?; - - Instruction::type_struct(wrapper_type_id, &[inner_type_id]) - .to_words(&mut self.logical_layout.declarations); - - let pointer_type_id = self.id_gen.next(); - Instruction::type_pointer(pointer_type_id, class, wrapper_type_id) - .to_words(&mut self.logical_layout.declarations); - - pointer_type_id - } else { - // This is a global variable in the Storage address space. The only - // way it could have `global_needs_wrapper() == false` is if it has - // a runtime-sized or binding array. - // Runtime-sized arrays were decorated when iterating through struct content. - // Now binding arrays require Block decorating. - if let crate::AddressSpace::Storage { .. } = global_variable.space { - match ir_module.types[global_variable.ty].inner { - crate::TypeInner::BindingArray { base, .. } => { - let ty = &ir_module.types[base]; - let mut should_decorate = true; - // Check if the type has a runtime array. - // A normal runtime array gets validated out, - // so only structs can be with runtime arrays - if let crate::TypeInner::Struct { ref members, .. } = ty.inner { - // only the last member in a struct can be dynamically sized - if let Some(last_member) = members.last() { - if let &crate::TypeInner::Array { - size: crate::ArraySize::Dynamic, - .. - } = &ir_module.types[last_member.ty].inner - { - should_decorate = false; - } - } - } - if should_decorate { - let decorated_id = self.get_handle_type_id(base); - self.decorate(decorated_id, Decoration::Block, &[]); - } - } - _ => (), - }; - } - if substitute_inner_type_lookup.is_some() { - inner_type_id - } else { - self.get_handle_pointer_type_id(global_variable.ty, class) - } - }; - - let init_word = match (global_variable.space, self.zero_initialize_workgroup_memory) { - (crate::AddressSpace::Private, _) - | (crate::AddressSpace::WorkGroup, super::ZeroInitializeWorkgroupMemoryMode::Native) => { - init_word.or_else(|| Some(self.get_constant_null(inner_type_id))) - } - _ => init_word, - }; - - Instruction::variable(pointer_type_id, id, class, init_word) - .to_words(&mut self.logical_layout.declarations); - Ok(id) - } - - /// Write the necessary decorations for a struct member. - /// - /// Emit decorations for the `index`'th member of the struct type - /// designated by `struct_id`, described by `member`. - fn decorate_struct_member( - &mut self, - struct_id: Word, - index: usize, - member: &crate::StructMember, - arena: &UniqueArena, - ) -> Result<(), Error> { - use spirv::Decoration; - - self.annotations.push(Instruction::member_decorate( - struct_id, - index as u32, - Decoration::Offset, - &[member.offset], - )); - - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(ref name) = member.name { - self.debugs - .push(Instruction::member_name(struct_id, index as u32, name)); - } - } - - // Matrices and (potentially nested) arrays of matrices both require decorations, - // so "see through" any arrays to determine if they're needed. - let mut member_array_subty_inner = &arena[member.ty].inner; - while let crate::TypeInner::Array { base, .. } = *member_array_subty_inner { - member_array_subty_inner = &arena[base].inner; - } - - if let crate::TypeInner::Matrix { - columns: _, - rows, - scalar, - } = *member_array_subty_inner - { - let byte_stride = Alignment::from(rows) * scalar.width as u32; - self.annotations.push(Instruction::member_decorate( - struct_id, - index as u32, - Decoration::ColMajor, - &[], - )); - self.annotations.push(Instruction::member_decorate( - struct_id, - index as u32, - Decoration::MatrixStride, - &[byte_stride], - )); - } - - Ok(()) - } - - pub(super) fn get_function_type(&mut self, lookup_function_type: LookupFunctionType) -> Word { - match self - .lookup_function_type - .entry(lookup_function_type.clone()) - { - Entry::Occupied(e) => *e.get(), - Entry::Vacant(_) => { - let id = self.id_gen.next(); - let instruction = Instruction::type_function( - id, - lookup_function_type.return_type_id, - &lookup_function_type.parameter_type_ids, - ); - instruction.to_words(&mut self.logical_layout.declarations); - self.lookup_function_type.insert(lookup_function_type, id); - id - } - } - } - - fn write_physical_layout(&mut self) { - self.physical_layout.bound = self.id_gen.0 + 1; - } - - pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { - self.use_extension("SPV_EXT_mesh_shader"); - self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; - let lang_version = self.lang_version(); - if lang_version.0 <= 1 && lang_version.1 < 4 { - return Err(Error::SpirvVersionTooLow(1, 4)); - } - Ok(()) - } - - fn write_logical_layout( - &mut self, - ir_module: &crate::Module, - mod_info: &ModuleInfo, - ep_index: Option, - debug_info: &Option, - ) -> Result<(), Error> { - fn has_view_index_check( - ir_module: &crate::Module, - binding: Option<&crate::Binding>, - ty: Handle, - ) -> bool { - match ir_module.types[ty].inner { - crate::TypeInner::Struct { ref members, .. } => members.iter().any(|member| { - has_view_index_check(ir_module, member.binding.as_ref(), member.ty) - }), - _ => binding == Some(&crate::Binding::BuiltIn(crate::BuiltIn::ViewIndex)), - } - } - - let has_storage_buffers = - ir_module - .global_variables - .iter() - .any(|(_, var)| match var.space { - crate::AddressSpace::Storage { .. } => true, - _ => false, - }); - let has_view_index = ir_module - .entry_points - .iter() - .flat_map(|entry| entry.function.arguments.iter()) - .any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty)); - let mut has_ray_query = ir_module.special_types.ray_desc.is_some() - | ir_module.special_types.ray_intersection.is_some(); - let has_vertex_return = ir_module.special_types.ray_vertex_return.is_some(); - - // Ways mesh shaders are required: - // * Mesh entry point used - checked for - // * Mesh function like setVertex used outside mesh entry point, this is handled when those are written - // * Fragment shader with per primitive data - handled in `map_binding` - let has_mesh_shaders = ir_module.entry_points.iter().any(|entry| { - entry.stage == crate::ShaderStage::Mesh || entry.stage == crate::ShaderStage::Task - }) || ir_module - .global_variables - .iter() - .any(|gvar| gvar.1.space == crate::AddressSpace::TaskPayload); - - for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() { - // spirv does not know whether these have vertex return - that is done by us - if let &crate::TypeInner::AccelerationStructure { .. } - | &crate::TypeInner::RayQuery { .. } = inner - { - has_ray_query = true - } - } - - if self.physical_layout.version < 0x10300 && has_storage_buffers { - // enable the storage buffer class on < SPV-1.3 - Instruction::extension("SPV_KHR_storage_buffer_storage_class") - .to_words(&mut self.logical_layout.extensions); - } - if has_view_index { - Instruction::extension("SPV_KHR_multiview") - .to_words(&mut self.logical_layout.extensions) - } - if has_ray_query { - Instruction::extension("SPV_KHR_ray_query") - .to_words(&mut self.logical_layout.extensions) - } - if has_vertex_return { - Instruction::extension("SPV_KHR_ray_tracing_position_fetch") - .to_words(&mut self.logical_layout.extensions); - } - if has_mesh_shaders { - self.require_mesh_shaders()?; - } - Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); - Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") - .to_words(&mut self.logical_layout.ext_inst_imports); - - let mut debug_info_inner = None; - if self.flags.contains(WriterFlags::DEBUG) { - if let Some(debug_info) = debug_info.as_ref() { - let source_file_id = self.id_gen.next(); - self.debugs - .push(Instruction::string(debug_info.file_name, source_file_id)); - - debug_info_inner = Some(DebugInfoInner { - source_code: debug_info.source_code, - source_file_id, - }); - self.debugs.append(&mut Instruction::source_auto_continued( - debug_info.language, - 0, - &debug_info_inner, - )); - } - } - - // write all types - for (handle, _) in ir_module.types.iter() { - self.write_type_declaration_arena(ir_module, handle)?; - } - - // write all const-expressions as constants - self.constant_ids - .resize(ir_module.global_expressions.len(), 0); - for (handle, _) in ir_module.global_expressions.iter() { - self.write_constant_expr(handle, ir_module, mod_info)?; - } - debug_assert!(self.constant_ids.iter().all(|&id| id != 0)); - - // write the name of constants on their respective const-expression initializer - if self.flags.contains(WriterFlags::DEBUG) { - for (_, constant) in ir_module.constants.iter() { - if let Some(ref name) = constant.name { - let id = self.constant_ids[constant.init]; - self.debugs.push(Instruction::name(id, name)); - } - } - } - - // write all global variables - for (handle, var) in ir_module.global_variables.iter() { - // If a single entry point was specified, only write `OpVariable` instructions - // for the globals it actually uses. Emit dummies for the others, - // to preserve the indices in `global_variables`. - let gvar = match ep_index { - Some(index) if mod_info.get_entry_point(index)[handle].is_empty() => { - GlobalVariable::dummy() - } - _ => { - let id = self.write_global_variable(ir_module, var)?; - GlobalVariable::new(id) - } - }; - self.global_variables.insert(handle, gvar); - } - - // write all functions - for (handle, ir_function) in ir_module.functions.iter() { - let info = &mod_info[handle]; - if let Some(index) = ep_index { - let ep_info = mod_info.get_entry_point(index); - // If this function uses globals that we omitted from the SPIR-V - // because the entry point and its callees didn't use them, - // then we must skip it. - if !ep_info.dominates_global_use(info) { - log::info!("Skip function {:?}", ir_function.name); - continue; - } - - // Skip functions that that are not compatible with this entry point's stage. - // - // When validation is enabled, it rejects modules whose entry points try to call - // incompatible functions, so if we got this far, then any functions incompatible - // with our selected entry point must not be used. - // - // When validation is disabled, `fun_info.available_stages` is always just - // `ShaderStages::all()`, so this will write all functions in the module, and - // the downstream GLSL compiler will catch any problems. - if !info.available_stages.contains(ep_info.available_stages) { - continue; - } - } - let id = self.write_function(ir_function, info, ir_module, None, &debug_info_inner)?; - self.lookup_function.insert(handle, id); - } - - // write all or one entry points - for (index, ir_ep) in ir_module.entry_points.iter().enumerate() { - if ep_index.is_some() && ep_index != Some(index) { - continue; - } - let info = mod_info.get_entry_point(index); - let ep_instruction = - self.write_entry_point(ir_ep, info, ir_module, &debug_info_inner)?; - ep_instruction.to_words(&mut self.logical_layout.entry_points); - } - - for capability in self.capabilities_used.iter() { - Instruction::capability(*capability).to_words(&mut self.logical_layout.capabilities); - } - for extension in self.extensions_used.iter() { - Instruction::extension(extension).to_words(&mut self.logical_layout.extensions); - } - if ir_module.entry_points.is_empty() { - // SPIR-V doesn't like modules without entry points - Instruction::capability(spirv::Capability::Linkage) - .to_words(&mut self.logical_layout.capabilities); - } - - let addressing_model = spirv::AddressingModel::Logical; - let memory_model = spirv::MemoryModel::GLSL450; - //self.check(addressing_model.required_capabilities())?; - //self.check(memory_model.required_capabilities())?; - - Instruction::memory_model(addressing_model, memory_model) - .to_words(&mut self.logical_layout.memory_model); - - for debug_string in self.debug_strings.iter() { - debug_string.to_words(&mut self.logical_layout.debugs); - } - - if self.flags.contains(WriterFlags::DEBUG) { - for debug in self.debugs.iter() { - debug.to_words(&mut self.logical_layout.debugs); - } - } - - for annotation in self.annotations.iter() { - annotation.to_words(&mut self.logical_layout.annotations); - } - - Ok(()) - } - - pub fn write( - &mut self, - ir_module: &crate::Module, - info: &ModuleInfo, - pipeline_options: Option<&PipelineOptions>, - debug_info: &Option, - words: &mut Vec, - ) -> Result<(), Error> { - self.reset(); - - // Try to find the entry point and corresponding index - let ep_index = match pipeline_options { - Some(po) => { - let index = ir_module - .entry_points - .iter() - .position(|ep| po.shader_stage == ep.stage && po.entry_point == ep.name) - .ok_or(Error::EntryPointNotFound)?; - Some(index) - } - None => None, - }; - - self.write_logical_layout(ir_module, info, ep_index, debug_info)?; - self.write_physical_layout(); - - self.physical_layout.in_words(words); - self.logical_layout.in_words(words); - Ok(()) - } - - /// Return the set of capabilities the last module written used. - pub const fn get_capabilities_used(&self) -> &crate::FastIndexSet { - &self.capabilities_used - } - - pub fn decorate_non_uniform_binding_array_access(&mut self, id: Word) -> Result<(), Error> { - self.require_any("NonUniformEXT", &[spirv::Capability::ShaderNonUniform])?; - self.use_extension("SPV_EXT_descriptor_indexing"); - self.decorate(id, spirv::Decoration::NonUniform, &[]); - Ok(()) - } - - pub(super) fn needs_f16_polyfill(&self, ty_inner: &crate::TypeInner) -> bool { - self.io_f16_polyfills.needs_polyfill(ty_inner) - } - - pub(super) fn write_debug_printf( - &mut self, - block: &mut Block, - string: &str, - format_params: &[Word], - ) { - if self.debug_printf.is_none() { - self.use_extension("SPV_KHR_non_semantic_info"); - let import_id = self.id_gen.next(); - Instruction::ext_inst_import(import_id, "NonSemantic.DebugPrintf") - .to_words(&mut self.logical_layout.ext_inst_imports); - self.debug_printf = Some(import_id) - } - - let import_id = self.debug_printf.unwrap(); - - let string_id = self.id_gen.next(); - self.debug_strings - .push(Instruction::string(string, string_id)); - - let mut operands = Vec::with_capacity(1 + format_params.len()); - operands.push(string_id); - operands.extend(format_params.iter()); - - let print_id = self.id_gen.next(); - block.body.push(Instruction::ext_inst( - import_id, - 1, - self.void_type, - print_id, - &operands, - )); - } -} - -#[test] -fn test_write_physical_layout() { - let mut writer = Writer::new(&Options::default()).unwrap(); - assert_eq!(writer.physical_layout.bound, 0); - writer.write_physical_layout(); - assert_eq!(writer.physical_layout.bound, 3); -} +use alloc::{string::String, vec, vec::Vec}; + +use arrayvec::ArrayVec; +use hashbrown::hash_map::Entry; +use spirv::Word; + +use super::{ + block::DebugInfoInner, + helpers::{contains_builtin, global_needs_wrapper, map_storage_class}, + Block, BlockContext, CachedConstant, CachedExpressions, DebugInfo, EntryPointContext, Error, + Function, FunctionArgument, GlobalVariable, IdGenerator, Instruction, LocalImageType, + LocalType, LocalVariable, LogicalLayout, LookupFunctionType, LookupType, NumericType, Options, + PhysicalLayout, PipelineOptions, ResultMember, Writer, WriterFlags, BITS_PER_BYTE, +}; +use crate::{ + arena::{Handle, HandleVec, UniqueArena}, + back::spv::{helpers::BindingDecorations, BindingInfo, WrappedFunction}, + non_max_u32::NonMaxU32, + proc::{Alignment, TypeResolution}, + valid::{FunctionInfo, ModuleInfo}, +}; + +struct FunctionInterface<'a> { + varying_ids: &'a mut Vec, + stage: crate::ShaderStage, + task_payload: Option>, + mesh_info: Option, + workgroup_size: [u32; 3], +} + +impl Function { + pub(super) fn to_words(&self, sink: &mut impl Extend) { + self.signature.as_ref().unwrap().to_words(sink); + for argument in self.parameters.iter() { + argument.instruction.to_words(sink); + } + for (index, block) in self.blocks.iter().enumerate() { + Instruction::label(block.label_id).to_words(sink); + if index == 0 { + for local_var in self.variables.values() { + local_var.instruction.to_words(sink); + } + for local_var in self.ray_query_initialization_tracker_variables.values() { + local_var.instruction.to_words(sink); + } + for local_var in self.ray_query_t_max_tracker_variables.values() { + local_var.instruction.to_words(sink); + } + for local_var in self.force_loop_bounding_vars.iter() { + local_var.instruction.to_words(sink); + } + for internal_var in self.spilled_composites.values() { + internal_var.instruction.to_words(sink); + } + } + for instruction in block.body.iter() { + instruction.to_words(sink); + } + } + Instruction::function_end().to_words(sink); + } +} + +impl Writer { + pub fn new(options: &Options) -> Result { + let (major, minor) = options.lang_version; + if major != 1 { + return Err(Error::UnsupportedVersion(major, minor)); + } + + let mut capabilities_used = crate::FastIndexSet::default(); + capabilities_used.insert(spirv::Capability::Shader); + + let mut id_gen = IdGenerator::default(); + let gl450_ext_inst_id = id_gen.next(); + let void_type = id_gen.next(); + + Ok(Writer { + physical_layout: PhysicalLayout::new(major, minor), + logical_layout: LogicalLayout::default(), + id_gen, + capabilities_available: options.capabilities.clone(), + capabilities_used, + extensions_used: crate::FastIndexSet::default(), + debug_strings: vec![], + debugs: vec![], + annotations: vec![], + flags: options.flags, + bounds_check_policies: options.bounds_check_policies, + zero_initialize_workgroup_memory: options.zero_initialize_workgroup_memory, + force_loop_bounding: options.force_loop_bounding, + ray_query_initialization_tracking: options.ray_query_initialization_tracking, + use_storage_input_output_16: options.use_storage_input_output_16, + void_type, + lookup_type: crate::FastHashMap::default(), + lookup_function: crate::FastHashMap::default(), + lookup_function_type: crate::FastHashMap::default(), + wrapped_functions: crate::FastHashMap::default(), + constant_ids: HandleVec::new(), + cached_constants: crate::FastHashMap::default(), + global_variables: HandleVec::new(), + fake_missing_bindings: options.fake_missing_bindings, + binding_map: options.binding_map.clone(), + saved_cached: CachedExpressions::default(), + gl450_ext_inst_id, + temp_list: Vec::new(), + ray_query_functions: crate::FastHashMap::default(), + io_f16_polyfills: super::f16_polyfill::F16IoPolyfill::new( + options.use_storage_input_output_16, + ), + debug_printf: None, + }) + } + + pub fn set_options(&mut self, options: &Options) -> Result<(), Error> { + let (major, minor) = options.lang_version; + if major != 1 { + return Err(Error::UnsupportedVersion(major, minor)); + } + self.physical_layout = PhysicalLayout::new(major, minor); + self.capabilities_available = options.capabilities.clone(); + self.flags = options.flags; + self.bounds_check_policies = options.bounds_check_policies; + self.zero_initialize_workgroup_memory = options.zero_initialize_workgroup_memory; + self.force_loop_bounding = options.force_loop_bounding; + self.use_storage_input_output_16 = options.use_storage_input_output_16; + self.binding_map = options.binding_map.clone(); + self.io_f16_polyfills = + super::f16_polyfill::F16IoPolyfill::new(options.use_storage_input_output_16); + Ok(()) + } + + /// Returns `(major, minor)` of the SPIR-V language version. + pub const fn lang_version(&self) -> (u8, u8) { + self.physical_layout.lang_version() + } + + /// Reset `Writer` to its initial state, retaining any allocations. + /// + /// Why not just implement `Recyclable` for `Writer`? By design, + /// `Recyclable::recycle` requires ownership of the value, not just + /// `&mut`; see the trait documentation. But we need to use this method + /// from functions like `Writer::write`, which only have `&mut Writer`. + /// Workarounds include unsafe code (`core::ptr::read`, then `write`, ugh) + /// or something like a `Default` impl that returns an oddly-initialized + /// `Writer`, which is worse. + fn reset(&mut self) { + use super::recyclable::Recyclable; + use core::mem::take; + + let mut id_gen = IdGenerator::default(); + let gl450_ext_inst_id = id_gen.next(); + let void_type = id_gen.next(); + + // Every field of the old writer that is not determined by the `Options` + // passed to `Writer::new` should be reset somehow. + let fresh = Writer { + // Copied from the old Writer: + flags: self.flags, + bounds_check_policies: self.bounds_check_policies, + zero_initialize_workgroup_memory: self.zero_initialize_workgroup_memory, + force_loop_bounding: self.force_loop_bounding, + ray_query_initialization_tracking: self.ray_query_initialization_tracking, + use_storage_input_output_16: self.use_storage_input_output_16, + capabilities_available: take(&mut self.capabilities_available), + fake_missing_bindings: self.fake_missing_bindings, + binding_map: take(&mut self.binding_map), + + // Initialized afresh: + id_gen, + void_type, + gl450_ext_inst_id, + + // Recycled: + capabilities_used: take(&mut self.capabilities_used).recycle(), + extensions_used: take(&mut self.extensions_used).recycle(), + physical_layout: self.physical_layout.clone().recycle(), + logical_layout: take(&mut self.logical_layout).recycle(), + debug_strings: take(&mut self.debug_strings).recycle(), + debugs: take(&mut self.debugs).recycle(), + annotations: take(&mut self.annotations).recycle(), + lookup_type: take(&mut self.lookup_type).recycle(), + lookup_function: take(&mut self.lookup_function).recycle(), + lookup_function_type: take(&mut self.lookup_function_type).recycle(), + wrapped_functions: take(&mut self.wrapped_functions).recycle(), + constant_ids: take(&mut self.constant_ids).recycle(), + cached_constants: take(&mut self.cached_constants).recycle(), + global_variables: take(&mut self.global_variables).recycle(), + saved_cached: take(&mut self.saved_cached).recycle(), + temp_list: take(&mut self.temp_list).recycle(), + ray_query_functions: take(&mut self.ray_query_functions).recycle(), + io_f16_polyfills: take(&mut self.io_f16_polyfills).recycle(), + debug_printf: None, + }; + + *self = fresh; + + self.capabilities_used.insert(spirv::Capability::Shader); + } + + /// Indicate that the code requires any one of the listed capabilities. + /// + /// If nothing in `capabilities` appears in the available capabilities + /// specified in the [`Options`] from which this `Writer` was created, + /// return an error. The `what` string is used in the error message to + /// explain what provoked the requirement. (If no available capabilities were + /// given, assume everything is available.) + /// + /// The first acceptable capability will be added to this `Writer`'s + /// [`capabilities_used`] table, and an `OpCapability` emitted for it in the + /// result. For this reason, more specific capabilities should be listed + /// before more general. + /// + /// [`capabilities_used`]: Writer::capabilities_used + pub(super) fn require_any( + &mut self, + what: &'static str, + capabilities: &[spirv::Capability], + ) -> Result<(), Error> { + match *capabilities { + [] => Ok(()), + [first, ..] => { + // Find the first acceptable capability, or return an error if + // there is none. + let selected = match self.capabilities_available { + None => first, + Some(ref available) => { + match capabilities + .iter() + // need explicit type for hashbrown::HashSet::contains fn call to keep rustc happy + .find(|cap| available.contains::(cap)) + { + Some(&cap) => cap, + None => { + return Err(Error::MissingCapabilities(what, capabilities.to_vec())) + } + } + } + }; + self.capabilities_used.insert(selected); + Ok(()) + } + } + } + + /// Indicate that the code requires all of the listed capabilities. + /// + /// If all entries of `capabilities` appear in the available capabilities + /// specified in the [`Options`] from which this `Writer` was created + /// (including the case where [`Options::capabilities`] is `None`), add + /// them all to this `Writer`'s [`capabilities_used`] table, and return + /// `Ok(())`. If at least one of the listed capabilities is not available, + /// do not add anything to the `capabilities_used` table, and return the + /// first unavailable requested capability, wrapped in `Err()`. + /// + /// This method is does not return an [`enum@Error`] in case of failure + /// because it may be used in cases where the caller can recover (e.g., + /// with a polyfill) if the requested capabilities are not available. In + /// this case, it would be unnecessary work to find *all* the unavailable + /// requested capabilities, and to allocate a `Vec` for them, just so we + /// could return an [`Error::MissingCapabilities`]). + /// + /// [`capabilities_used`]: Writer::capabilities_used + pub(super) fn require_all( + &mut self, + capabilities: &[spirv::Capability], + ) -> Result<(), spirv::Capability> { + if let Some(ref available) = self.capabilities_available { + for requested in capabilities { + if !available.contains(requested) { + return Err(*requested); + } + } + } + + for requested in capabilities { + self.capabilities_used.insert(*requested); + } + + Ok(()) + } + + /// Indicate that the code uses the given extension. + pub(super) fn use_extension(&mut self, extension: &'static str) { + self.extensions_used.insert(extension); + } + + pub(super) fn get_type_id(&mut self, lookup_ty: LookupType) -> Word { + match self.lookup_type.entry(lookup_ty) { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(e) => { + let local = match lookup_ty { + LookupType::Handle(_handle) => unreachable!("Handles are populated at start"), + LookupType::Local(local) => local, + }; + + let id = self.id_gen.next(); + e.insert(id); + self.write_type_declaration_local(id, local); + id + } + } + } + + pub(super) fn get_handle_type_id(&mut self, handle: Handle) -> Word { + self.get_type_id(LookupType::Handle(handle)) + } + + pub(super) fn get_expression_lookup_type(&mut self, tr: &TypeResolution) -> LookupType { + match *tr { + TypeResolution::Handle(ty_handle) => LookupType::Handle(ty_handle), + TypeResolution::Value(ref inner) => { + let inner_local_type = self.localtype_from_inner(inner).unwrap(); + LookupType::Local(inner_local_type) + } + } + } + + pub(super) fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word { + let lookup_ty = self.get_expression_lookup_type(tr); + self.get_type_id(lookup_ty) + } + + pub(super) fn get_localtype_id(&mut self, local: LocalType) -> Word { + self.get_type_id(LookupType::Local(local)) + } + + pub(super) fn get_pointer_type_id(&mut self, base: Word, class: spirv::StorageClass) -> Word { + self.get_type_id(LookupType::Local(LocalType::Pointer { base, class })) + } + + pub(super) fn get_handle_pointer_type_id( + &mut self, + base: Handle, + class: spirv::StorageClass, + ) -> Word { + let base_id = self.get_handle_type_id(base); + self.get_pointer_type_id(base_id, class) + } + + pub(super) fn get_ray_query_pointer_id(&mut self) -> Word { + let rq_id = self.get_type_id(LookupType::Local(LocalType::RayQuery)); + self.get_pointer_type_id(rq_id, spirv::StorageClass::Function) + } + + /// Return a SPIR-V type for a pointer to `resolution`. + /// + /// The given `resolution` must be one that we can represent + /// either as a `LocalType::Pointer` or `LocalType::LocalPointer`. + pub(super) fn get_resolution_pointer_id( + &mut self, + resolution: &TypeResolution, + class: spirv::StorageClass, + ) -> Word { + let resolution_type_id = self.get_expression_type_id(resolution); + self.get_pointer_type_id(resolution_type_id, class) + } + + pub(super) fn get_numeric_type_id(&mut self, numeric: NumericType) -> Word { + self.get_type_id(LocalType::Numeric(numeric).into()) + } + + pub(super) fn get_u32_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::U32)) + } + + pub(super) fn get_f32_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32)) + } + + pub(super) fn get_vec2u_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::U32, + }) + } + + pub(super) fn get_vec2f_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::F32, + }) + } + + pub(super) fn get_vec3u_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::U32, + }) + } + + pub(super) fn get_f32_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { + let f32_id = self.get_f32_type_id(); + self.get_pointer_type_id(f32_id, class) + } + + pub(super) fn get_vec2u_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { + let vec2u_id = self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::U32, + }); + self.get_pointer_type_id(vec2u_id, class) + } + + pub(super) fn get_vec3u_pointer_type_id(&mut self, class: spirv::StorageClass) -> Word { + let vec3u_id = self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::U32, + }); + self.get_pointer_type_id(vec3u_id, class) + } + + pub(super) fn get_bool_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::BOOL)) + } + + pub(super) fn get_vec2_bool_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Bi, + scalar: crate::Scalar::BOOL, + }) + } + + pub(super) fn get_vec3_bool_type_id(&mut self) -> Word { + self.get_numeric_type_id(NumericType::Vector { + size: crate::VectorSize::Tri, + scalar: crate::Scalar::BOOL, + }) + } + + pub(super) fn decorate(&mut self, id: Word, decoration: spirv::Decoration, operands: &[Word]) { + self.annotations + .push(Instruction::decorate(id, decoration, operands)); + } + + /// Return `inner` as a `LocalType`, if that's possible. + /// + /// If `inner` can be represented as a `LocalType`, return + /// `Some(local_type)`. + /// + /// Otherwise, return `None`. In this case, the type must always be looked + /// up using a `LookupType::Handle`. + fn localtype_from_inner(&mut self, inner: &crate::TypeInner) -> Option { + Some(match *inner { + crate::TypeInner::Scalar(_) + | crate::TypeInner::Atomic(_) + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } => { + // We expect `NumericType::from_inner` to handle all + // these cases, so unwrap. + LocalType::Numeric(NumericType::from_inner(inner).unwrap()) + } + crate::TypeInner::Pointer { base, space } => { + let base_type_id = self.get_handle_type_id(base); + LocalType::Pointer { + base: base_type_id, + class: map_storage_class(space), + } + } + crate::TypeInner::ValuePointer { + size, + scalar, + space, + } => { + let base_numeric_type = match size { + Some(size) => NumericType::Vector { size, scalar }, + None => NumericType::Scalar(scalar), + }; + LocalType::Pointer { + base: self.get_numeric_type_id(base_numeric_type), + class: map_storage_class(space), + } + } + crate::TypeInner::Image { + dim, + arrayed, + class, + } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)), + crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler, + crate::TypeInner::AccelerationStructure { .. } => LocalType::AccelerationStructure, + crate::TypeInner::RayQuery { .. } => LocalType::RayQuery, + crate::TypeInner::Array { .. } + | crate::TypeInner::Struct { .. } + | crate::TypeInner::BindingArray { .. } => return None, + }) + } + + /// Resolve the [`BindingInfo`] for a [`crate::ResourceBinding`] from the + /// provided [`Writer::binding_map`]. + /// + /// If the specified resource is not present in the binding map this will + /// return an error, unless [`Writer::fake_missing_bindings`] is set. + fn resolve_resource_binding( + &self, + res_binding: &crate::ResourceBinding, + ) -> Result { + match self.binding_map.get(res_binding) { + Some(target) => Ok(*target), + None if self.fake_missing_bindings => Ok(BindingInfo { + descriptor_set: res_binding.group, + binding: res_binding.binding, + binding_array_size: None, + }), + None => Err(Error::MissingBinding(*res_binding)), + } + } + + /// Emits code for any wrapper functions required by the expressions in ir_function. + /// The IDs of any emitted functions will be stored in [`Self::wrapped_functions`]. + fn write_wrapped_functions( + &mut self, + ir_function: &crate::Function, + info: &FunctionInfo, + ir_module: &crate::Module, + ) -> Result<(), Error> { + log::trace!("Generating wrapped functions for {:?}", ir_function.name); + + for (expr_handle, expr) in ir_function.expressions.iter() { + match *expr { + crate::Expression::Binary { op, left, right } => { + let expr_ty_inner = info[expr_handle].ty.inner_with(&ir_module.types); + if let Some(expr_ty) = NumericType::from_inner(expr_ty_inner) { + match (op, expr_ty.scalar().kind) { + // Division and modulo are undefined behaviour when the + // dividend is the minimum representable value and the divisor + // is negative one, or when the divisor is zero. These wrapped + // functions override the divisor to one in these cases, + // matching the WGSL spec. + ( + crate::BinaryOperator::Divide | crate::BinaryOperator::Modulo, + crate::ScalarKind::Sint | crate::ScalarKind::Uint, + ) => { + self.write_wrapped_binary_op( + op, + expr_ty, + &info[left].ty, + &info[right].ty, + )?; + } + _ => {} + } + } + } + _ => {} + } + } + + Ok(()) + } + + /// Write a SPIR-V function that performs the operator `op` with Naga IR semantics. + /// + /// Define a function that performs an integer division or modulo operation, + /// except that using a divisor of zero or causing signed overflow with a + /// divisor of -1 returns the numerator unchanged, rather than exhibiting + /// undefined behavior. + /// + /// Store the generated function's id in the [`wrapped_functions`] table. + /// + /// The operator `op` must be either [`Divide`] or [`Modulo`]. + /// + /// # Panics + /// + /// The `return_type`, `left_type` or `right_type` arguments must all be + /// integer scalars or vectors. If not, this function panics. + /// + /// [`wrapped_functions`]: Writer::wrapped_functions + /// [`Divide`]: crate::BinaryOperator::Divide + /// [`Modulo`]: crate::BinaryOperator::Modulo + fn write_wrapped_binary_op( + &mut self, + op: crate::BinaryOperator, + return_type: NumericType, + left_type: &TypeResolution, + right_type: &TypeResolution, + ) -> Result<(), Error> { + let return_type_id = self.get_localtype_id(LocalType::Numeric(return_type)); + let left_type_id = self.get_expression_type_id(left_type); + let right_type_id = self.get_expression_type_id(right_type); + + // Check if we've already emitted this function. + let wrapped = WrappedFunction::BinaryOp { + op, + left_type_id, + right_type_id, + }; + let function_id = match self.wrapped_functions.entry(wrapped) { + Entry::Occupied(_) => return Ok(()), + Entry::Vacant(e) => *e.insert(self.id_gen.next()), + }; + + let scalar = return_type.scalar(); + + if self.flags.contains(WriterFlags::DEBUG) { + let function_name = match op { + crate::BinaryOperator::Divide => "naga_div", + crate::BinaryOperator::Modulo => "naga_mod", + _ => unreachable!(), + }; + self.debugs + .push(Instruction::name(function_id, function_name)); + } + let mut function = Function::default(); + + let function_type_id = self.get_function_type(LookupFunctionType { + parameter_type_ids: vec![left_type_id, right_type_id], + return_type_id, + }); + function.signature = Some(Instruction::function( + return_type_id, + function_id, + spirv::FunctionControl::empty(), + function_type_id, + )); + + let lhs_id = self.id_gen.next(); + let rhs_id = self.id_gen.next(); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs.push(Instruction::name(lhs_id, "lhs")); + self.debugs.push(Instruction::name(rhs_id, "rhs")); + } + let left_par = Instruction::function_parameter(left_type_id, lhs_id); + let right_par = Instruction::function_parameter(right_type_id, rhs_id); + for instruction in [left_par, right_par] { + function.parameters.push(FunctionArgument { + instruction, + handle_id: 0, + }); + } + + let label_id = self.id_gen.next(); + let mut block = Block::new(label_id); + + let bool_type = return_type.with_scalar(crate::Scalar::BOOL); + let bool_type_id = self.get_numeric_type_id(bool_type); + + let maybe_splat_const = |writer: &mut Self, const_id| match return_type { + NumericType::Scalar(_) => const_id, + NumericType::Vector { size, .. } => { + let constituent_ids = [const_id; crate::VectorSize::MAX]; + writer.get_constant_composite( + LookupType::Local(LocalType::Numeric(return_type)), + &constituent_ids[..size as usize], + ) + } + NumericType::Matrix { .. } => unreachable!(), + }; + + let const_zero_id = self.get_constant_scalar_with(0, scalar)?; + let composite_zero_id = maybe_splat_const(self, const_zero_id); + let rhs_eq_zero_id = self.id_gen.next(); + block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool_type_id, + rhs_eq_zero_id, + rhs_id, + composite_zero_id, + )); + let divisor_selector_id = match scalar.kind { + crate::ScalarKind::Sint => { + let (const_min_id, const_neg_one_id) = match scalar.width { + 4 => Ok(( + self.get_constant_scalar(crate::Literal::I32(i32::MIN)), + self.get_constant_scalar(crate::Literal::I32(-1i32)), + )), + 8 => Ok(( + self.get_constant_scalar(crate::Literal::I64(i64::MIN)), + self.get_constant_scalar(crate::Literal::I64(-1i64)), + )), + _ => Err(Error::Validation("Unexpected scalar width")), + }?; + let composite_min_id = maybe_splat_const(self, const_min_id); + let composite_neg_one_id = maybe_splat_const(self, const_neg_one_id); + + let lhs_eq_int_min_id = self.id_gen.next(); + block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool_type_id, + lhs_eq_int_min_id, + lhs_id, + composite_min_id, + )); + let rhs_eq_neg_one_id = self.id_gen.next(); + block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool_type_id, + rhs_eq_neg_one_id, + rhs_id, + composite_neg_one_id, + )); + let lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); + block.body.push(Instruction::binary( + spirv::Op::LogicalAnd, + bool_type_id, + lhs_eq_int_min_and_rhs_eq_neg_one_id, + lhs_eq_int_min_id, + rhs_eq_neg_one_id, + )); + let rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id = self.id_gen.next(); + block.body.push(Instruction::binary( + spirv::Op::LogicalOr, + bool_type_id, + rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id, + rhs_eq_zero_id, + lhs_eq_int_min_and_rhs_eq_neg_one_id, + )); + rhs_eq_zero_or_lhs_eq_int_min_and_rhs_eq_neg_one_id + } + crate::ScalarKind::Uint => rhs_eq_zero_id, + _ => unreachable!(), + }; + + let const_one_id = self.get_constant_scalar_with(1, scalar)?; + let composite_one_id = maybe_splat_const(self, const_one_id); + let divisor_id = self.id_gen.next(); + block.body.push(Instruction::select( + right_type_id, + divisor_id, + divisor_selector_id, + composite_one_id, + rhs_id, + )); + let op = match (op, scalar.kind) { + (crate::BinaryOperator::Divide, crate::ScalarKind::Sint) => spirv::Op::SDiv, + (crate::BinaryOperator::Divide, crate::ScalarKind::Uint) => spirv::Op::UDiv, + (crate::BinaryOperator::Modulo, crate::ScalarKind::Sint) => spirv::Op::SRem, + (crate::BinaryOperator::Modulo, crate::ScalarKind::Uint) => spirv::Op::UMod, + _ => unreachable!(), + }; + let return_id = self.id_gen.next(); + block.body.push(Instruction::binary( + op, + return_type_id, + return_id, + lhs_id, + divisor_id, + )); + + function.consume(block, Instruction::return_value(return_id)); + function.to_words(&mut self.logical_layout.function_definitions); + Ok(()) + } + + /// Sets up an output variable that will handle part of the mesh shader output + fn write_mesh_return_global_variable( + &mut self, + ty: u32, + array_size_id: u32, + ) -> Result { + let array_ty = self.id_gen.next(); + Instruction::type_array(array_ty, ty, array_size_id) + .to_words(&mut self.logical_layout.declarations); + let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); + let var_id = self.id_gen.next(); + Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) + .to_words(&mut self.logical_layout.declarations); + Ok(var_id) + } + + /// This does various setup things to allow mesh shader entry points + /// to be properly written, such as creating the output variables + fn write_entry_point_mesh_shader_info( + &mut self, + iface: &mut FunctionInterface, + local_invocation_index_id: Option, + ir_module: &crate::Module, + prelude: &mut Block, + ep_context: &mut EntryPointContext, + ) -> Result<(), Error> { + let Some(ref mesh_info) = iface.mesh_info else { + return Ok(()); + }; + // Collect the members in the output structs + let out_members: Vec = + match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let vertex_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() + .ty_id; + let primitive_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() + .ty_id; + let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| super::MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + // In the final return, we do a giant memcpy, for which this is helpful + let local_invocation_index_id = match local_invocation_index_id { + Some(a) => a, + None => { + let u32_id = self.get_u32_type_id(); + let var = self.id_gen.next(); + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), + var, + spirv::StorageClass::Input, + None, + ) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate( + var, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationIndex as u32], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(var); + + let loaded_value = self.id_gen.next(); + prelude + .body + .push(Instruction::load(u32_id, loaded_value, var, None)); + loaded_value + } + }; + let u32_id = self.get_u32_type_id(); + // A general function variable that we guarantee to allow in the final return. It must be + // declared at the top of the function. Currently it is used in the memcpy part to keep + // track of the current index to copy. + let function_variable = self.id_gen.next(); + prelude.body.insert( + 0, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), + function_variable, + spirv::StorageClass::Function, + None, + ), + ); + // This is the information that is passed to the function writer + // so that it can write the final return logic + let mut mesh_return_info = super::MeshReturnInfo { + out_variable_id: self.global_variables[mesh_info.output_variable].var_id, + out_members, + + vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), + vertex_array_type_id, + vertex_members, + max_vertices_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), + primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), + primitive_array_type_id, + primitive_members, + max_primitives_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), + vertex_bindings: Vec::new(), + vertex_builtin_block: None, + primitive_bindings: Vec::new(), + primitive_builtin_block: None, + primitive_indices: None, + local_invocation_index_id, + workgroup_size: self + .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), + function_variable, + }; + let vert_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); + let prim_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); + + // Create the actual output variables and types. + // According to SPIR-V, + // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) + // * Each member with `location` must be in its own `Block` decorated `struct` + // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) + // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper + + // Write vertex builtin block + if mesh_return_info + .vertex_members + .iter() + .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.vertex_members { + if let crate::Binding::BuiltIn(_) = member.binding { + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_vertex_builtin_outputs")); + } + mesh_return_info.vertex_builtin_block = Some(v); + } + // Write primitive builtin block + if mesh_return_info.primitive_members.iter().any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.primitive_members { + if let crate::Binding::BuiltIn(bi) = member.binding { + // These need to be in their own block, unlike other builtins + if matches!( + bi, + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) { + continue; + } + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_builtin_outputs")); + } + mesh_return_info.primitive_builtin_block = Some(v); + } + + // Write vertex binding output blocks (1 array per output struct member) + for member in &mesh_return_info.vertex_members { + match member.binding { + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; + iface.varying_ids.push(v); + mesh_return_info.vertex_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + // Write primitive binding output blocks (1 array per output struct member) + // Also write indices output block + for member in &mesh_return_info.primitive_members { + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + // This is written here instead of as part of the builtin block + let v = + self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::decorate( + v, + spirv::Decoration::BuiltIn, + &[match member.binding.to_built_in().unwrap() { + crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, + crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, + crate::BuiltIn::TriangleIndices => { + spirv::BuiltIn::PrimitiveTriangleIndicesEXT + } + _ => unreachable!(), + } as Word], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_indices_outputs")); + } + mesh_return_info.primitive_indices = Some(v); + } + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + + mesh_return_info.primitive_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + + // Store this where it can be read later during function write + ep_context.mesh_state = Some(mesh_return_info); + + Ok(()) + } + + fn write_function( + &mut self, + ir_function: &crate::Function, + info: &FunctionInfo, + ir_module: &crate::Module, + mut interface: Option, + debug_info: &Option, + ) -> Result { + self.write_wrapped_functions(ir_function, info, ir_module)?; + + log::trace!("Generating code for {:?}", ir_function.name); + let mut function = Function::default(); + + let prelude_id = self.id_gen.next(); + let mut prelude = Block::new(prelude_id); + let mut ep_context = EntryPointContext { + argument_ids: Vec::new(), + results: Vec::new(), + task_payload_variable_id: if let Some(ref i) = interface { + i.task_payload.map(|a| self.global_variables[a].var_id) + } else { + None + }, + mesh_state: None, + }; + + let mut local_invocation_id = None; + + let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); + + let mut local_invocation_index_id = None; + + for argument in ir_function.arguments.iter() { + let class = spirv::StorageClass::Input; + let handle_ty = ir_module.types[argument.ty].inner.is_handle(); + let argument_type_id = if handle_ty { + self.get_handle_pointer_type_id(argument.ty, spirv::StorageClass::UniformConstant) + } else { + self.get_handle_type_id(argument.ty) + }; + + if let Some(ref mut iface) = interface { + let id = if let Some(ref binding) = argument.binding { + let name = argument.name.as_deref(); + + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + argument.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + let id = self.load_io_with_f16_polyfill( + &mut prelude.body, + varying_id, + argument_type_id, + ); + + if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { + local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); + } + + id + } else if let crate::TypeInner::Struct { ref members, .. } = + ir_module.types[argument.ty].inner + { + let struct_id = self.id_gen.next(); + let mut constituent_ids = Vec::with_capacity(members.len()); + for member in members { + let type_id = self.get_handle_type_id(member.ty); + let name = member.name.as_deref(); + let binding = member.binding.as_ref().unwrap(); + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + let id = + self.load_io_with_f16_polyfill(&mut prelude.body, varying_id, type_id); + constituent_ids.push(id); + + if binding == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationId) { + local_invocation_id = Some(id); + } else if binding + == &crate::Binding::BuiltIn(crate::BuiltIn::LocalInvocationIndex) + { + local_invocation_index_id = Some(id); + } + } + prelude.body.push(Instruction::composite_construct( + argument_type_id, + struct_id, + &constituent_ids, + )); + struct_id + } else { + unreachable!("Missing argument binding on an entry point"); + }; + ep_context.argument_ids.push(id); + } else { + let argument_id = self.id_gen.next(); + let instruction = Instruction::function_parameter(argument_type_id, argument_id); + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = argument.name { + self.debugs.push(Instruction::name(argument_id, name)); + } + } + function.parameters.push(FunctionArgument { + instruction, + handle_id: if handle_ty { + let id = self.id_gen.next(); + prelude.body.push(Instruction::load( + self.get_handle_type_id(argument.ty), + id, + argument_id, + None, + )); + id + } else { + 0 + }, + }); + parameter_type_ids.push(argument_type_id); + }; + } + + let return_type_id = match ir_function.result { + Some(ref result) => { + if let Some(ref mut iface) = interface { + let mut has_point_size = false; + let class = spirv::StorageClass::Output; + if let Some(ref binding) = result.binding { + has_point_size |= + *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); + let type_id = self.get_handle_type_id(result.ty); + let varying_id = + if *binding == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + None, + result.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; + ep_context.results.push(ResultMember { + id: varying_id, + type_id, + built_in: binding.to_built_in(), + }); + } else if let crate::TypeInner::Struct { ref members, .. } = + ir_module.types[result.ty].inner + { + for member in members { + let type_id = self.get_handle_type_id(member.ty); + let name = member.name.as_deref(); + let binding = member.binding.as_ref().unwrap(); + has_point_size |= + *binding == crate::Binding::BuiltIn(crate::BuiltIn::PointSize); + // This isn't an actual builtin in SPIR-V. It can only appear as the + // output of a task shader and the output is used when writing the + // entry point return, in which case the id is ignored anyway. + let varying_id = if *binding + == crate::Binding::BuiltIn(crate::BuiltIn::MeshTaskSize) + { + 0 + } else { + let varying_id = self.write_varying( + ir_module, + iface.stage, + class, + name, + member.ty, + binding, + )?; + iface.varying_ids.push(varying_id); + varying_id + }; + ep_context.results.push(ResultMember { + id: varying_id, + type_id, + built_in: binding.to_built_in(), + }); + } + } else { + unreachable!("Missing result binding on an entry point"); + } + + if self.flags.contains(WriterFlags::FORCE_POINT_SIZE) + && iface.stage == crate::ShaderStage::Vertex + && !has_point_size + { + // add point size artificially + let varying_id = self.id_gen.next(); + let pointer_type_id = self.get_f32_pointer_type_id(class); + Instruction::variable(pointer_type_id, varying_id, class, None) + .to_words(&mut self.logical_layout.declarations); + self.decorate( + varying_id, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::PointSize as u32], + ); + iface.varying_ids.push(varying_id); + + let default_value_id = self.get_constant_scalar(crate::Literal::F32(1.0)); + prelude + .body + .push(Instruction::store(varying_id, default_value_id, None)); + } + self.void_type + } else { + self.get_handle_type_id(result.ty) + } + } + None => self.void_type, + }; + + if let Some(ref mut iface) = interface { + if let Some(task_payload) = iface.task_payload { + iface + .varying_ids + .push(self.global_variables[task_payload].var_id); + } + self.write_entry_point_mesh_shader_info( + iface, + local_invocation_index_id, + ir_module, + &mut prelude, + &mut ep_context, + )?; + } + + let lookup_function_type = LookupFunctionType { + parameter_type_ids, + return_type_id, + }; + + let function_id = self.id_gen.next(); + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = ir_function.name { + self.debugs.push(Instruction::name(function_id, name)); + } + } + + let function_type = self.get_function_type(lookup_function_type); + function.signature = Some(Instruction::function( + return_type_id, + function_id, + spirv::FunctionControl::empty(), + function_type, + )); + + if interface.is_some() { + function.entry_point_context = Some(ep_context); + } + + // fill up the `GlobalVariable::access_id` + for gv in self.global_variables.iter_mut() { + gv.reset_for_function(); + } + for (handle, var) in ir_module.global_variables.iter() { + if info[handle].is_empty() { + continue; + } + + let mut gv = self.global_variables[handle].clone(); + if let Some(ref mut iface) = interface { + // Have to include global variables in the interface + if self.physical_layout.version >= 0x10400 && iface.task_payload != Some(handle) { + iface.varying_ids.push(gv.var_id); + } + } + + // Handle globals are pre-emitted and should be loaded automatically. + // + // Any that are binding arrays we skip as we cannot load the array, we must load the result after indexing. + match ir_module.types[var.ty].inner { + crate::TypeInner::BindingArray { .. } => { + gv.access_id = gv.var_id; + } + _ => { + if var.space == crate::AddressSpace::Handle { + let var_type_id = self.get_handle_type_id(var.ty); + let id = self.id_gen.next(); + prelude + .body + .push(Instruction::load(var_type_id, id, gv.var_id, None)); + gv.access_id = gv.var_id; + gv.handle_id = id; + } else if global_needs_wrapper(ir_module, var) { + let class = map_storage_class(var.space); + let pointer_type_id = self.get_handle_pointer_type_id(var.ty, class); + let index_id = self.get_index_constant(0); + let id = self.id_gen.next(); + prelude.body.push(Instruction::access_chain( + pointer_type_id, + id, + gv.var_id, + &[index_id], + )); + gv.access_id = id; + } else { + // by default, the variable ID is accessed as is + gv.access_id = gv.var_id; + }; + } + } + + // work around borrow checking in the presence of `self.xxx()` calls + self.global_variables[handle] = gv; + } + + // Create a `BlockContext` for generating SPIR-V for the function's + // body. + let mut context = BlockContext { + ir_module, + ir_function, + fun_info: info, + function: &mut function, + // Re-use the cached expression table from prior functions. + cached: core::mem::take(&mut self.saved_cached), + + // Steal the Writer's temp list for a bit. + temp_list: core::mem::take(&mut self.temp_list), + force_loop_bounding: self.force_loop_bounding, + writer: self, + expression_constness: super::ExpressionConstnessTracker::from_arena( + &ir_function.expressions, + ), + ray_query_tracker_expr: crate::FastHashMap::default(), + }; + + // fill up the pre-emitted and const expressions + context.cached.reset(ir_function.expressions.len()); + for (handle, expr) in ir_function.expressions.iter() { + if (expr.needs_pre_emit() && !matches!(*expr, crate::Expression::LocalVariable(_))) + || context.expression_constness.is_const(handle) + { + context.cache_expression_value(handle, &mut prelude)?; + } + } + + for (handle, variable) in ir_function.local_variables.iter() { + let id = context.gen_id(); + + if context.writer.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = variable.name { + context.writer.debugs.push(Instruction::name(id, name)); + } + } + + let init_word = variable.init.map(|constant| context.cached[constant]); + let pointer_type_id = context + .writer + .get_handle_pointer_type_id(variable.ty, spirv::StorageClass::Function); + let instruction = Instruction::variable( + pointer_type_id, + id, + spirv::StorageClass::Function, + init_word.or_else(|| match ir_module.types[variable.ty].inner { + crate::TypeInner::RayQuery { .. } => None, + _ => { + let type_id = context.get_handle_type_id(variable.ty); + Some(context.writer.write_constant_null(type_id)) + } + }), + ); + context + .function + .variables + .insert(handle, LocalVariable { id, instruction }); + + if let crate::TypeInner::RayQuery { .. } = ir_module.types[variable.ty].inner { + // Don't refactor this into a struct: Although spirv itself allows opaque types in structs, + // the vulkan environment for spirv does not. Putting ray queries into structs can cause + // confusing bugs. + let u32_type_id = context.writer.get_u32_type_id(); + let ptr_u32_type_id = context + .writer + .get_pointer_type_id(u32_type_id, spirv::StorageClass::Function); + let tracker_id = context.gen_id(); + let tracker_init_id = context + .writer + .get_constant_scalar(crate::Literal::U32(super::RayQueryPoint::empty().bits())); + let tracker_instruction = Instruction::variable( + ptr_u32_type_id, + tracker_id, + spirv::StorageClass::Function, + Some(tracker_init_id), + ); + + context + .function + .ray_query_initialization_tracker_variables + .insert( + handle, + LocalVariable { + id: tracker_id, + instruction: tracker_instruction, + }, + ); + let f32_type_id = context.writer.get_f32_type_id(); + let ptr_f32_type_id = context + .writer + .get_pointer_type_id(f32_type_id, spirv::StorageClass::Function); + let t_max_tracker_id = context.gen_id(); + let t_max_tracker_init_id = + context.writer.get_constant_scalar(crate::Literal::F32(0.0)); + let t_max_tracker_instruction = Instruction::variable( + ptr_f32_type_id, + t_max_tracker_id, + spirv::StorageClass::Function, + Some(t_max_tracker_init_id), + ); + + context.function.ray_query_t_max_tracker_variables.insert( + handle, + LocalVariable { + id: t_max_tracker_id, + instruction: t_max_tracker_instruction, + }, + ); + } + } + + for (handle, expr) in ir_function.expressions.iter() { + match *expr { + crate::Expression::LocalVariable(_) => { + // Cache the `OpVariable` instruction we generated above as + // the value of this expression. + context.cache_expression_value(handle, &mut prelude)?; + } + crate::Expression::Access { base, .. } + | crate::Expression::AccessIndex { base, .. } => { + // Count references to `base` by `Access` and `AccessIndex` + // instructions. See `access_uses` for details. + *context.function.access_uses.entry(base).or_insert(0) += 1; + } + _ => {} + } + } + + let next_id = context.gen_id(); + + context + .function + .consume(prelude, Instruction::branch(next_id)); + + let workgroup_vars_init_exit_block_id = + match (context.writer.zero_initialize_workgroup_memory, interface) { + ( + super::ZeroInitializeWorkgroupMemoryMode::Polyfill, + Some( + ref mut interface @ FunctionInterface { + stage: + crate::ShaderStage::Compute + | crate::ShaderStage::Mesh + | crate::ShaderStage::Task, + .. + }, + ), + ) => context.writer.generate_workgroup_vars_init_block( + next_id, + ir_module, + info, + local_invocation_id, + interface, + context.function, + ), + _ => None, + }; + + let main_id = if let Some(exit_id) = workgroup_vars_init_exit_block_id { + exit_id + } else { + next_id + }; + + context.write_function_body(main_id, debug_info.as_ref())?; + + // Consume the `BlockContext`, ending its borrows and letting the + // `Writer` steal back its cached expression table and temp_list. + let BlockContext { + cached, temp_list, .. + } = context; + self.saved_cached = cached; + self.temp_list = temp_list; + + function.to_words(&mut self.logical_layout.function_definitions); + + Ok(function_id) + } + + fn write_execution_mode( + &mut self, + function_id: Word, + mode: spirv::ExecutionMode, + ) -> Result<(), Error> { + //self.check(mode.required_capabilities())?; + Instruction::execution_mode(function_id, mode, &[]) + .to_words(&mut self.logical_layout.execution_modes); + Ok(()) + } + + // TODO Move to instructions module + fn write_entry_point( + &mut self, + entry_point: &crate::EntryPoint, + info: &FunctionInfo, + ir_module: &crate::Module, + debug_info: &Option, + ) -> Result { + let mut interface_ids = Vec::new(); + let function_id = self.write_function( + &entry_point.function, + info, + ir_module, + Some(FunctionInterface { + varying_ids: &mut interface_ids, + stage: entry_point.stage, + task_payload: entry_point.task_payload, + mesh_info: entry_point.mesh_info.clone(), + workgroup_size: entry_point.workgroup_size, + }), + debug_info, + )?; + + let exec_model = match entry_point.stage { + crate::ShaderStage::Vertex => spirv::ExecutionModel::Vertex, + crate::ShaderStage::Fragment => { + self.write_execution_mode(function_id, spirv::ExecutionMode::OriginUpperLeft)?; + match entry_point.early_depth_test { + Some(crate::EarlyDepthTest::Force) => { + self.write_execution_mode( + function_id, + spirv::ExecutionMode::EarlyFragmentTests, + )?; + } + Some(crate::EarlyDepthTest::Allow { conservative }) => { + // TODO: Consider emitting EarlyAndLateFragmentTestsAMD here, if available. + // https://github.khronos.org/SPIRV-Registry/extensions/AMD/SPV_AMD_shader_early_and_late_fragment_tests.html + // This permits early depth tests even if the shader writes to a storage + // binding + match conservative { + crate::ConservativeDepth::GreaterEqual => self.write_execution_mode( + function_id, + spirv::ExecutionMode::DepthGreater, + )?, + crate::ConservativeDepth::LessEqual => self.write_execution_mode( + function_id, + spirv::ExecutionMode::DepthLess, + )?, + crate::ConservativeDepth::Unchanged => self.write_execution_mode( + function_id, + spirv::ExecutionMode::DepthUnchanged, + )?, + } + } + None => {} + } + if let Some(ref result) = entry_point.function.result { + if contains_builtin( + result.binding.as_ref(), + result.ty, + &ir_module.types, + crate::BuiltIn::FragDepth, + ) { + self.write_execution_mode( + function_id, + spirv::ExecutionMode::DepthReplacing, + )?; + } + } + spirv::ExecutionModel::Fragment + } + crate::ShaderStage::Compute => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::GLCompute + } + crate::ShaderStage::Task => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::TaskEXT + } + crate::ShaderStage::Mesh => { + let execution_mode = spirv::ExecutionMode::LocalSize; + //self.check(execution_mode.required_capabilities())?; + Instruction::execution_mode( + function_id, + execution_mode, + &entry_point.workgroup_size, + ) + .to_words(&mut self.logical_layout.execution_modes); + let mesh_info = entry_point.mesh_info.as_ref().unwrap(); + Instruction::execution_mode( + function_id, + match mesh_info.topology { + crate::MeshOutputTopology::Points => spirv::ExecutionMode::OutputPoints, + crate::MeshOutputTopology::Lines => spirv::ExecutionMode::OutputLinesEXT, + crate::MeshOutputTopology::Triangles => { + spirv::ExecutionMode::OutputTrianglesEXT + } + }, + &[], + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputVertices, + core::slice::from_ref(&mesh_info.max_vertices), + ) + .to_words(&mut self.logical_layout.execution_modes); + Instruction::execution_mode( + function_id, + spirv::ExecutionMode::OutputPrimitivesEXT, + core::slice::from_ref(&mesh_info.max_primitives), + ) + .to_words(&mut self.logical_layout.execution_modes); + spirv::ExecutionModel::MeshEXT + } + }; + //self.check(exec_model.required_capabilities())?; + + Ok(Instruction::entry_point( + exec_model, + function_id, + &entry_point.name, + interface_ids.as_slice(), + )) + } + + fn make_scalar(&mut self, id: Word, scalar: crate::Scalar) -> Instruction { + use crate::ScalarKind as Sk; + + let bits = (scalar.width * BITS_PER_BYTE) as u32; + match scalar.kind { + Sk::Sint | Sk::Uint => { + let signedness = if scalar.kind == Sk::Sint { + super::instructions::Signedness::Signed + } else { + super::instructions::Signedness::Unsigned + }; + let cap = match bits { + 8 => Some(spirv::Capability::Int8), + 16 => Some(spirv::Capability::Int16), + 64 => Some(spirv::Capability::Int64), + _ => None, + }; + if let Some(cap) = cap { + self.capabilities_used.insert(cap); + } + Instruction::type_int(id, bits, signedness) + } + Sk::Float => { + if bits == 64 { + self.capabilities_used.insert(spirv::Capability::Float64); + } + if bits == 16 { + self.capabilities_used.insert(spirv::Capability::Float16); + self.capabilities_used + .insert(spirv::Capability::StorageBuffer16BitAccess); + self.capabilities_used + .insert(spirv::Capability::UniformAndStorageBuffer16BitAccess); + if self.use_storage_input_output_16 { + self.capabilities_used + .insert(spirv::Capability::StorageInputOutput16); + } + } + Instruction::type_float(id, bits) + } + Sk::Bool => Instruction::type_bool(id), + Sk::AbstractInt | Sk::AbstractFloat => { + unreachable!("abstract types should never reach the backend"); + } + } + } + + fn request_type_capabilities(&mut self, inner: &crate::TypeInner) -> Result<(), Error> { + match *inner { + crate::TypeInner::Image { + dim, + arrayed, + class, + } => { + let sampled = match class { + crate::ImageClass::Sampled { .. } => true, + crate::ImageClass::Depth { .. } => true, + crate::ImageClass::Storage { format, .. } => { + self.request_image_format_capabilities(format.into())?; + false + } + crate::ImageClass::External => unimplemented!(), + }; + + match dim { + crate::ImageDimension::D1 => { + if sampled { + self.require_any("sampled 1D images", &[spirv::Capability::Sampled1D])?; + } else { + self.require_any("1D storage images", &[spirv::Capability::Image1D])?; + } + } + crate::ImageDimension::Cube if arrayed => { + if sampled { + self.require_any( + "sampled cube array images", + &[spirv::Capability::SampledCubeArray], + )?; + } else { + self.require_any( + "cube array storage images", + &[spirv::Capability::ImageCubeArray], + )?; + } + } + _ => {} + } + } + crate::TypeInner::AccelerationStructure { .. } => { + self.require_any("Acceleration Structure", &[spirv::Capability::RayQueryKHR])?; + } + crate::TypeInner::RayQuery { .. } => { + self.require_any("Ray Query", &[spirv::Capability::RayQueryKHR])?; + } + crate::TypeInner::Atomic(crate::Scalar { width: 8, kind: _ }) => { + self.require_any("64 bit integer atomics", &[spirv::Capability::Int64Atomics])?; + } + crate::TypeInner::Atomic(crate::Scalar { + width: 4, + kind: crate::ScalarKind::Float, + }) => { + self.require_any( + "32 bit floating-point atomics", + &[spirv::Capability::AtomicFloat32AddEXT], + )?; + self.use_extension("SPV_EXT_shader_atomic_float_add"); + } + // 16 bit floating-point support requires Float16 capability + crate::TypeInner::Matrix { + scalar: crate::Scalar::F16, + .. + } + | crate::TypeInner::Vector { + scalar: crate::Scalar::F16, + .. + } + | crate::TypeInner::Scalar(crate::Scalar::F16) => { + self.require_any("16 bit floating-point", &[spirv::Capability::Float16])?; + self.use_extension("SPV_KHR_16bit_storage"); + } + _ => {} + } + Ok(()) + } + + fn write_numeric_type_declaration_local(&mut self, id: Word, numeric: NumericType) { + let instruction = match numeric { + NumericType::Scalar(scalar) => self.make_scalar(id, scalar), + NumericType::Vector { size, scalar } => { + let scalar_id = self.get_numeric_type_id(NumericType::Scalar(scalar)); + Instruction::type_vector(id, scalar_id, size) + } + NumericType::Matrix { + columns, + rows, + scalar, + } => { + let column_id = + self.get_numeric_type_id(NumericType::Vector { size: rows, scalar }); + Instruction::type_matrix(id, column_id, columns) + } + }; + + instruction.to_words(&mut self.logical_layout.declarations); + } + + fn write_type_declaration_local(&mut self, id: Word, local_ty: LocalType) { + let instruction = match local_ty { + LocalType::Numeric(numeric) => { + self.write_numeric_type_declaration_local(id, numeric); + return; + } + LocalType::Pointer { base, class } => Instruction::type_pointer(id, class, base), + LocalType::Image(image) => { + let local_type = LocalType::Numeric(NumericType::Scalar(image.sampled_type)); + let type_id = self.get_localtype_id(local_type); + Instruction::type_image(id, type_id, image.dim, image.flags, image.image_format) + } + LocalType::Sampler => Instruction::type_sampler(id), + LocalType::SampledImage { image_type_id } => { + Instruction::type_sampled_image(id, image_type_id) + } + LocalType::BindingArray { base, size } => { + let inner_ty = self.get_handle_type_id(base); + let scalar_id = self.get_constant_scalar(crate::Literal::U32(size)); + Instruction::type_array(id, inner_ty, scalar_id) + } + LocalType::AccelerationStructure => Instruction::type_acceleration_structure(id), + LocalType::RayQuery => Instruction::type_ray_query(id), + }; + + instruction.to_words(&mut self.logical_layout.declarations); + } + + fn write_type_declaration_arena( + &mut self, + module: &crate::Module, + handle: Handle, + ) -> Result { + let ty = &module.types[handle]; + // If it's a type that needs SPIR-V capabilities, request them now. + // This needs to happen regardless of the LocalType lookup succeeding, + // because some types which map to the same LocalType have different + // capability requirements. See https://github.com/gfx-rs/wgpu/issues/5569 + self.request_type_capabilities(&ty.inner)?; + let id = if let Some(local) = self.localtype_from_inner(&ty.inner) { + // This type can be represented as a `LocalType`, so check if we've + // already written an instruction for it. If not, do so now, with + // `write_type_declaration_local`. + match self.lookup_type.entry(LookupType::Local(local)) { + // We already have an id for this `LocalType`. + Entry::Occupied(e) => *e.get(), + + // It's a type we haven't seen before. + Entry::Vacant(e) => { + let id = self.id_gen.next(); + e.insert(id); + + self.write_type_declaration_local(id, local); + + id + } + } + } else { + use spirv::Decoration; + + let id = self.id_gen.next(); + let instruction = match ty.inner { + crate::TypeInner::Array { base, size, stride } => { + self.decorate(id, Decoration::ArrayStride, &[stride]); + + let type_id = self.get_handle_type_id(base); + match size.resolve(module.to_ctx())? { + crate::proc::IndexableLength::Known(length) => { + let length_id = self.get_index_constant(length); + Instruction::type_array(id, type_id, length_id) + } + crate::proc::IndexableLength::Dynamic => { + Instruction::type_runtime_array(id, type_id) + } + } + } + crate::TypeInner::BindingArray { base, size } => { + let type_id = self.get_handle_type_id(base); + match size.resolve(module.to_ctx())? { + crate::proc::IndexableLength::Known(length) => { + let length_id = self.get_index_constant(length); + Instruction::type_array(id, type_id, length_id) + } + crate::proc::IndexableLength::Dynamic => { + Instruction::type_runtime_array(id, type_id) + } + } + } + crate::TypeInner::Struct { + ref members, + span: _, + } => { + let mut has_runtime_array = false; + let mut member_ids = Vec::with_capacity(members.len()); + for (index, member) in members.iter().enumerate() { + let member_ty = &module.types[member.ty]; + match member_ty.inner { + crate::TypeInner::Array { + base: _, + size: crate::ArraySize::Dynamic, + stride: _, + } => { + has_runtime_array = true; + } + _ => (), + } + self.decorate_struct_member(id, index, member, &module.types)?; + let member_id = self.get_handle_type_id(member.ty); + member_ids.push(member_id); + } + if has_runtime_array { + self.decorate(id, Decoration::Block, &[]); + } + Instruction::type_struct(id, member_ids.as_slice()) + } + + // These all have TypeLocal representations, so they should have been + // handled by `write_type_declaration_local` above. + crate::TypeInner::Scalar(_) + | crate::TypeInner::Atomic(_) + | crate::TypeInner::Vector { .. } + | crate::TypeInner::Matrix { .. } + | crate::TypeInner::Pointer { .. } + | crate::TypeInner::ValuePointer { .. } + | crate::TypeInner::Image { .. } + | crate::TypeInner::Sampler { .. } + | crate::TypeInner::AccelerationStructure { .. } + | crate::TypeInner::RayQuery { .. } => unreachable!(), + }; + + instruction.to_words(&mut self.logical_layout.declarations); + id + }; + + // Add this handle as a new alias for that type. + self.lookup_type.insert(LookupType::Handle(handle), id); + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = ty.name { + self.debugs.push(Instruction::name(id, name)); + } + } + + Ok(id) + } + + fn request_image_format_capabilities( + &mut self, + format: spirv::ImageFormat, + ) -> Result<(), Error> { + use spirv::ImageFormat as If; + match format { + If::Rg32f + | If::Rg16f + | If::R11fG11fB10f + | If::R16f + | If::Rgba16 + | If::Rgb10A2 + | If::Rg16 + | If::Rg8 + | If::R16 + | If::R8 + | If::Rgba16Snorm + | If::Rg16Snorm + | If::Rg8Snorm + | If::R16Snorm + | If::R8Snorm + | If::Rg32i + | If::Rg16i + | If::Rg8i + | If::R16i + | If::R8i + | If::Rgb10a2ui + | If::Rg32ui + | If::Rg16ui + | If::Rg8ui + | If::R16ui + | If::R8ui => self.require_any( + "storage image format", + &[spirv::Capability::StorageImageExtendedFormats], + ), + If::R64ui | If::R64i => { + self.use_extension("SPV_EXT_shader_image_int64"); + self.require_any( + "64-bit integer storage image format", + &[spirv::Capability::Int64ImageEXT], + ) + } + If::Unknown + | If::Rgba32f + | If::Rgba16f + | If::R32f + | If::Rgba8 + | If::Rgba8Snorm + | If::Rgba32i + | If::Rgba16i + | If::Rgba8i + | If::R32i + | If::Rgba32ui + | If::Rgba16ui + | If::Rgba8ui + | If::R32ui => Ok(()), + } + } + + pub(super) fn get_index_constant(&mut self, index: Word) -> Word { + self.get_constant_scalar(crate::Literal::U32(index)) + } + + pub(super) fn get_constant_scalar_with( + &mut self, + value: u8, + scalar: crate::Scalar, + ) -> Result { + Ok( + self.get_constant_scalar(crate::Literal::new(value, scalar).ok_or( + Error::Validation("Unexpected kind and/or width for Literal"), + )?), + ) + } + + pub(super) fn get_constant_scalar(&mut self, value: crate::Literal) -> Word { + let scalar = CachedConstant::Literal(value.into()); + if let Some(&id) = self.cached_constants.get(&scalar) { + return id; + } + let id = self.id_gen.next(); + self.write_constant_scalar(id, &value, None); + self.cached_constants.insert(scalar, id); + id + } + + fn write_constant_scalar( + &mut self, + id: Word, + value: &crate::Literal, + debug_name: Option<&String>, + ) { + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + let type_id = self.get_numeric_type_id(NumericType::Scalar(value.scalar())); + let instruction = match *value { + crate::Literal::F64(value) => { + let bits = value.to_bits(); + Instruction::constant_64bit(type_id, id, bits as u32, (bits >> 32) as u32) + } + crate::Literal::F32(value) => Instruction::constant_32bit(type_id, id, value.to_bits()), + crate::Literal::F16(value) => { + let low = value.to_bits(); + Instruction::constant_16bit(type_id, id, low as u32) + } + crate::Literal::U32(value) => Instruction::constant_32bit(type_id, id, value), + crate::Literal::I32(value) => Instruction::constant_32bit(type_id, id, value as u32), + crate::Literal::U64(value) => { + Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) + } + crate::Literal::I64(value) => { + Instruction::constant_64bit(type_id, id, value as u32, (value >> 32) as u32) + } + crate::Literal::Bool(true) => Instruction::constant_true(type_id, id), + crate::Literal::Bool(false) => Instruction::constant_false(type_id, id), + crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => { + unreachable!("Abstract types should not appear in IR presented to backends"); + } + }; + + instruction.to_words(&mut self.logical_layout.declarations); + } + + pub(super) fn get_constant_composite( + &mut self, + ty: LookupType, + constituent_ids: &[Word], + ) -> Word { + let composite = CachedConstant::Composite { + ty, + constituent_ids: constituent_ids.to_vec(), + }; + if let Some(&id) = self.cached_constants.get(&composite) { + return id; + } + let id = self.id_gen.next(); + self.write_constant_composite(id, ty, constituent_ids, None); + self.cached_constants.insert(composite, id); + id + } + + fn write_constant_composite( + &mut self, + id: Word, + ty: LookupType, + constituent_ids: &[Word], + debug_name: Option<&String>, + ) { + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + let type_id = self.get_type_id(ty); + Instruction::constant_composite(type_id, id, constituent_ids) + .to_words(&mut self.logical_layout.declarations); + } + + pub(super) fn get_constant_null(&mut self, type_id: Word) -> Word { + let null = CachedConstant::ZeroValue(type_id); + if let Some(&id) = self.cached_constants.get(&null) { + return id; + } + let id = self.write_constant_null(type_id); + self.cached_constants.insert(null, id); + id + } + + pub(super) fn write_constant_null(&mut self, type_id: Word) -> Word { + let null_id = self.id_gen.next(); + Instruction::constant_null(type_id, null_id) + .to_words(&mut self.logical_layout.declarations); + null_id + } + + fn write_constant_expr( + &mut self, + handle: Handle, + ir_module: &crate::Module, + mod_info: &ModuleInfo, + ) -> Result { + let id = match ir_module.global_expressions[handle] { + crate::Expression::Literal(literal) => self.get_constant_scalar(literal), + crate::Expression::Constant(constant) => { + let constant = &ir_module.constants[constant]; + self.constant_ids[constant.init] + } + crate::Expression::ZeroValue(ty) => { + let type_id = self.get_handle_type_id(ty); + self.get_constant_null(type_id) + } + crate::Expression::Compose { ty, ref components } => { + let component_ids: Vec<_> = crate::proc::flatten_compose( + ty, + components, + &ir_module.global_expressions, + &ir_module.types, + ) + .map(|component| self.constant_ids[component]) + .collect(); + self.get_constant_composite(LookupType::Handle(ty), component_ids.as_slice()) + } + crate::Expression::Splat { size, value } => { + let value_id = self.constant_ids[value]; + let component_ids = &[value_id; 4][..size as usize]; + + let ty = self.get_expression_lookup_type(&mod_info[handle]); + + self.get_constant_composite(ty, component_ids) + } + _ => { + return Err(Error::Override); + } + }; + + self.constant_ids[handle] = id; + + Ok(id) + } + + pub(super) fn write_control_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { + let memory_scope = if flags.contains(crate::Barrier::STORAGE) { + spirv::Scope::Device + } else if flags.contains(crate::Barrier::SUB_GROUP) { + spirv::Scope::Subgroup + } else { + spirv::Scope::Workgroup + }; + let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; + semantics.set( + spirv::MemorySemantics::UNIFORM_MEMORY, + flags.contains(crate::Barrier::STORAGE), + ); + semantics.set( + spirv::MemorySemantics::WORKGROUP_MEMORY, + flags.contains(crate::Barrier::WORK_GROUP), + ); + semantics.set( + spirv::MemorySemantics::SUBGROUP_MEMORY, + flags.contains(crate::Barrier::SUB_GROUP), + ); + semantics.set( + spirv::MemorySemantics::IMAGE_MEMORY, + flags.contains(crate::Barrier::TEXTURE), + ); + let exec_scope_id = if flags.contains(crate::Barrier::SUB_GROUP) { + self.get_index_constant(spirv::Scope::Subgroup as u32) + } else { + self.get_index_constant(spirv::Scope::Workgroup as u32) + }; + let mem_scope_id = self.get_index_constant(memory_scope as u32); + let semantics_id = self.get_index_constant(semantics.bits()); + block.body.push(Instruction::control_barrier( + exec_scope_id, + mem_scope_id, + semantics_id, + )); + } + + pub(super) fn write_memory_barrier(&mut self, flags: crate::Barrier, block: &mut Block) { + let mut semantics = spirv::MemorySemantics::ACQUIRE_RELEASE; + semantics.set( + spirv::MemorySemantics::UNIFORM_MEMORY, + flags.contains(crate::Barrier::STORAGE), + ); + semantics.set( + spirv::MemorySemantics::WORKGROUP_MEMORY, + flags.contains(crate::Barrier::WORK_GROUP), + ); + semantics.set( + spirv::MemorySemantics::SUBGROUP_MEMORY, + flags.contains(crate::Barrier::SUB_GROUP), + ); + semantics.set( + spirv::MemorySemantics::IMAGE_MEMORY, + flags.contains(crate::Barrier::TEXTURE), + ); + let mem_scope_id = if flags.contains(crate::Barrier::STORAGE) { + self.get_index_constant(spirv::Scope::Device as u32) + } else if flags.contains(crate::Barrier::SUB_GROUP) { + self.get_index_constant(spirv::Scope::Subgroup as u32) + } else { + self.get_index_constant(spirv::Scope::Workgroup as u32) + }; + let semantics_id = self.get_index_constant(semantics.bits()); + block + .body + .push(Instruction::memory_barrier(mem_scope_id, semantics_id)); + } + + fn generate_workgroup_vars_init_block( + &mut self, + entry_id: Word, + ir_module: &crate::Module, + info: &FunctionInfo, + local_invocation_id: Option, + interface: &mut FunctionInterface, + function: &mut Function, + ) -> Option { + let body = ir_module + .global_variables + .iter() + .filter(|&(handle, var)| { + !info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup + }) + .map(|(handle, var)| { + // It's safe to use `var_id` here, not `access_id`, because only + // variables in the `Uniform` and `StorageBuffer` address spaces + // get wrapped, and we're initializing `WorkGroup` variables. + let var_id = self.global_variables[handle].var_id; + let var_type_id = self.get_handle_type_id(var.ty); + let init_word = self.get_constant_null(var_type_id); + Instruction::store(var_id, init_word, None) + }) + .collect::>(); + + if body.is_empty() { + return None; + } + + let uint3_type_id = self.get_vec3u_type_id(); + + let mut pre_if_block = Block::new(entry_id); + + let local_invocation_id = if let Some(local_invocation_id) = local_invocation_id { + local_invocation_id + } else { + let varying_id = self.id_gen.next(); + let class = spirv::StorageClass::Input; + let pointer_type_id = self.get_vec3u_pointer_type_id(class); + + Instruction::variable(pointer_type_id, varying_id, class, None) + .to_words(&mut self.logical_layout.declarations); + + self.decorate( + varying_id, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationId as u32], + ); + + interface.varying_ids.push(varying_id); + let id = self.id_gen.next(); + pre_if_block + .body + .push(Instruction::load(uint3_type_id, id, varying_id, None)); + + id + }; + + let zero_id = self.get_constant_null(uint3_type_id); + let bool3_type_id = self.get_vec3_bool_type_id(); + + let eq_id = self.id_gen.next(); + pre_if_block.body.push(Instruction::binary( + spirv::Op::IEqual, + bool3_type_id, + eq_id, + local_invocation_id, + zero_id, + )); + + let condition_id = self.id_gen.next(); + let bool_type_id = self.get_bool_type_id(); + pre_if_block.body.push(Instruction::relational( + spirv::Op::All, + bool_type_id, + condition_id, + eq_id, + )); + + let merge_id = self.id_gen.next(); + pre_if_block.body.push(Instruction::selection_merge( + merge_id, + spirv::SelectionControl::NONE, + )); + + let accept_id = self.id_gen.next(); + function.consume( + pre_if_block, + Instruction::branch_conditional(condition_id, accept_id, merge_id), + ); + + let accept_block = Block { + label_id: accept_id, + body, + }; + function.consume(accept_block, Instruction::branch(merge_id)); + + let mut post_if_block = Block::new(merge_id); + + self.write_control_barrier(crate::Barrier::WORK_GROUP, &mut post_if_block); + + let next_id = self.id_gen.next(); + function.consume(post_if_block, Instruction::branch(next_id)); + Some(next_id) + } + + /// Generate an `OpVariable` for one value in an [`EntryPoint`]'s IO interface. + /// + /// The [`Binding`]s of the arguments and result of an [`EntryPoint`]'s + /// [`Function`] describe a SPIR-V shader interface. In SPIR-V, the + /// interface is represented by global variables in the `Input` and `Output` + /// storage classes, with decorations indicating which builtin or location + /// each variable corresponds to. + /// + /// This function emits a single global `OpVariable` for a single value from + /// the interface, and adds appropriate decorations to indicate which + /// builtin or location it represents, how it should be interpolated, and so + /// on. The `class` argument gives the variable's SPIR-V storage class, + /// which should be either [`Input`] or [`Output`]. + /// + /// [`Binding`]: crate::Binding + /// [`Function`]: crate::Function + /// [`EntryPoint`]: crate::EntryPoint + /// [`Input`]: spirv::StorageClass::Input + /// [`Output`]: spirv::StorageClass::Output + fn write_varying( + &mut self, + ir_module: &crate::Module, + stage: crate::ShaderStage, + class: spirv::StorageClass, + debug_name: Option<&str>, + ty: Handle, + binding: &crate::Binding, + ) -> Result { + let id = self.id_gen.next(); + let ty_inner = &ir_module.types[ty].inner; + let needs_polyfill = self.needs_f16_polyfill(ty_inner); + + let pointer_type_id = if needs_polyfill { + let f32_value_local = + super::f16_polyfill::F16IoPolyfill::create_polyfill_type(ty_inner) + .expect("needs_polyfill returned true but create_polyfill_type returned None"); + + let f32_type_id = self.get_localtype_id(f32_value_local); + let ptr_id = self.get_pointer_type_id(f32_type_id, class); + self.io_f16_polyfills.register_io_var(id, f32_type_id); + + ptr_id + } else { + self.get_handle_pointer_type_id(ty, class) + }; + + Instruction::variable(pointer_type_id, id, class, None) + .to_words(&mut self.logical_layout.declarations); + + if self + .flags + .contains(WriterFlags::DEBUG | WriterFlags::LABEL_VARYINGS) + { + if let Some(name) = debug_name { + self.debugs.push(Instruction::name(id, name)); + } + } + + let binding = self.map_binding(ir_module, stage, class, ty, binding)?; + self.write_binding(id, binding); + + Ok(id) + } + + pub fn write_binding(&mut self, id: Word, binding: BindingDecorations) { + match binding { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.decorate(id, spirv::Decoration::BuiltIn, &[bi as u32]); + for other in others { + self.decorate(id, other, &[]); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.decorate(id, spirv::Decoration::Location, &[location]); + for other in others { + self.decorate(id, other, &[]); + } + if let Some(blend_src) = blend_src { + self.decorate(id, spirv::Decoration::Index, &[blend_src]); + } + } + } + } + + pub fn write_binding_struct_member( + &mut self, + struct_id: Word, + member_idx: Word, + binding_info: BindingDecorations, + ) { + match binding_info { + BindingDecorations::None => (), + BindingDecorations::BuiltIn(bi, others) => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + } + BindingDecorations::Location { + location, + others, + blend_src, + } => { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Location, + &[location], + )); + for other in others { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + other, + &[], + )); + } + if let Some(blend_src) = blend_src { + self.annotations.push(Instruction::member_decorate( + struct_id, + member_idx, + spirv::Decoration::Index, + &[blend_src], + )); + } + } + } + } + + pub fn map_binding( + &mut self, + ir_module: &crate::Module, + stage: crate::ShaderStage, + class: spirv::StorageClass, + ty: Handle, + binding: &crate::Binding, + ) -> Result { + use spirv::BuiltIn; + use spirv::Decoration; + match *binding { + crate::Binding::Location { + location, + interpolation, + sampling, + blend_src, + per_primitive, + } => { + let mut others = ArrayVec::new(); + + let no_decorations = + // VUID-StandaloneSpirv-Flat-06202 + // > The Flat, NoPerspective, Sample, and Centroid decorations + // > must not be used on variables with the Input storage class in a vertex shader + (class == spirv::StorageClass::Input && stage == crate::ShaderStage::Vertex) || + // VUID-StandaloneSpirv-Flat-06201 + // > The Flat, NoPerspective, Sample, and Centroid decorations + // > must not be used on variables with the Output storage class in a fragment shader + (class == spirv::StorageClass::Output && stage == crate::ShaderStage::Fragment); + + if !no_decorations { + match interpolation { + // Perspective-correct interpolation is the default in SPIR-V. + None | Some(crate::Interpolation::Perspective) => (), + Some(crate::Interpolation::Flat) => { + others.push(Decoration::Flat); + } + Some(crate::Interpolation::Linear) => { + others.push(Decoration::NoPerspective); + } + } + match sampling { + // Center sampling is the default in SPIR-V. + None + | Some( + crate::Sampling::Center + | crate::Sampling::First + | crate::Sampling::Either, + ) => (), + Some(crate::Sampling::Centroid) => { + others.push(Decoration::Centroid); + } + Some(crate::Sampling::Sample) => { + self.require_any( + "per-sample interpolation", + &[spirv::Capability::SampleRateShading], + )?; + others.push(Decoration::Sample); + } + } + } + if per_primitive && stage == crate::ShaderStage::Fragment { + others.push(Decoration::PerPrimitiveEXT); + self.require_mesh_shaders()?; + } + Ok(BindingDecorations::Location { + location, + others, + blend_src, + }) + } + crate::Binding::BuiltIn(built_in) => { + use crate::BuiltIn as Bi; + let mut others = ArrayVec::new(); + + if matches!( + built_in, + Bi::CullPrimitive | Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices + ) { + self.require_mesh_shaders()?; + } + + let built_in = match built_in { + Bi::Position { invariant } => { + if invariant { + others.push(Decoration::Invariant); + } + + if class == spirv::StorageClass::Output { + BuiltIn::Position + } else { + BuiltIn::FragCoord + } + } + Bi::ViewIndex => { + self.require_any("`view_index` built-in", &[spirv::Capability::MultiView])?; + BuiltIn::ViewIndex + } + // vertex + Bi::BaseInstance => BuiltIn::BaseInstance, + Bi::BaseVertex => BuiltIn::BaseVertex, + Bi::ClipDistance => { + self.require_any( + "`clip_distance` built-in", + &[spirv::Capability::ClipDistance], + )?; + BuiltIn::ClipDistance + } + Bi::CullDistance => { + self.require_any( + "`cull_distance` built-in", + &[spirv::Capability::CullDistance], + )?; + BuiltIn::CullDistance + } + Bi::InstanceIndex => BuiltIn::InstanceIndex, + Bi::PointSize => BuiltIn::PointSize, + Bi::VertexIndex => BuiltIn::VertexIndex, + Bi::DrawID => BuiltIn::DrawIndex, + // fragment + Bi::FragDepth => BuiltIn::FragDepth, + Bi::PointCoord => BuiltIn::PointCoord, + Bi::FrontFacing => BuiltIn::FrontFacing, + Bi::PrimitiveIndex => { + self.require_any( + "`primitive_index` built-in", + &[spirv::Capability::Geometry], + )?; + if stage == crate::ShaderStage::Mesh { + others.push(Decoration::PerPrimitiveEXT); + } + BuiltIn::PrimitiveId + } + Bi::Barycentric => { + self.require_any( + "`barycentric` built-in", + &[spirv::Capability::FragmentBarycentricKHR], + )?; + self.use_extension("SPV_KHR_fragment_shader_barycentric"); + BuiltIn::BaryCoordKHR + } + Bi::SampleIndex => { + self.require_any( + "`sample_index` built-in", + &[spirv::Capability::SampleRateShading], + )?; + + BuiltIn::SampleId + } + Bi::SampleMask => BuiltIn::SampleMask, + // compute + Bi::GlobalInvocationId => BuiltIn::GlobalInvocationId, + Bi::LocalInvocationId => BuiltIn::LocalInvocationId, + Bi::LocalInvocationIndex => BuiltIn::LocalInvocationIndex, + Bi::WorkGroupId => BuiltIn::WorkgroupId, + Bi::WorkGroupSize => BuiltIn::WorkgroupSize, + Bi::NumWorkGroups => BuiltIn::NumWorkgroups, + // Subgroup + Bi::NumSubgroups => { + self.require_any( + "`num_subgroups` built-in", + &[spirv::Capability::GroupNonUniform], + )?; + BuiltIn::NumSubgroups + } + Bi::SubgroupId => { + self.require_any( + "`subgroup_id` built-in", + &[spirv::Capability::GroupNonUniform], + )?; + BuiltIn::SubgroupId + } + Bi::SubgroupSize => { + self.require_any( + "`subgroup_size` built-in", + &[ + spirv::Capability::GroupNonUniform, + spirv::Capability::SubgroupBallotKHR, + ], + )?; + BuiltIn::SubgroupSize + } + Bi::SubgroupInvocationId => { + self.require_any( + "`subgroup_invocation_id` built-in", + &[ + spirv::Capability::GroupNonUniform, + spirv::Capability::SubgroupBallotKHR, + ], + )?; + BuiltIn::SubgroupLocalInvocationId + } + Bi::CullPrimitive => { + self.require_mesh_shaders()?; + others.push(Decoration::PerPrimitiveEXT); + BuiltIn::CullPrimitiveEXT + } + Bi::PointIndex => { + self.require_mesh_shaders()?; + BuiltIn::PrimitivePointIndicesEXT + } + Bi::LineIndices => { + self.require_mesh_shaders()?; + BuiltIn::PrimitiveLineIndicesEXT + } + Bi::TriangleIndices => { + self.require_mesh_shaders()?; + BuiltIn::PrimitiveTriangleIndicesEXT + } + // No decoration, this EmitMeshTasksEXT is called at function return + Bi::MeshTaskSize => return Ok(BindingDecorations::None), + // These aren't normal builtins and don't occur in function output + Bi::VertexCount | Bi::Vertices | Bi::PrimitiveCount | Bi::Primitives => { + unreachable!() + } + }; + + use crate::ScalarKind as Sk; + + // Per the Vulkan spec, `VUID-StandaloneSpirv-Flat-04744`: + // + // > Any variable with integer or double-precision floating- + // > point type and with Input storage class in a fragment + // > shader, must be decorated Flat + if class == spirv::StorageClass::Input && stage == crate::ShaderStage::Fragment { + let is_flat = match ir_module.types[ty].inner { + crate::TypeInner::Scalar(scalar) + | crate::TypeInner::Vector { scalar, .. } => match scalar.kind { + Sk::Uint | Sk::Sint | Sk::Bool => true, + Sk::Float => false, + Sk::AbstractInt | Sk::AbstractFloat => { + return Err(Error::Validation( + "Abstract types should not appear in IR presented to backends", + )) + } + }, + _ => false, + }; + + if is_flat { + others.push(Decoration::Flat); + } + } + Ok(BindingDecorations::BuiltIn(built_in, others)) + } + } + } + + /// Load an IO variable, converting from `f32` to `f16` if polyfill is active. + /// Returns the id of the loaded value matching `target_type_id`. + pub(super) fn load_io_with_f16_polyfill( + &mut self, + body: &mut Vec, + varying_id: Word, + target_type_id: Word, + ) -> Word { + let tmp = self.id_gen.next(); + if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { + body.push(Instruction::load(f32_ty, tmp, varying_id, None)); + let converted = self.id_gen.next(); + super::f16_polyfill::F16IoPolyfill::emit_f32_to_f16_conversion( + tmp, + target_type_id, + converted, + body, + ); + converted + } else { + body.push(Instruction::load(target_type_id, tmp, varying_id, None)); + tmp + } + } + + /// Store an IO variable, converting from `f16` to `f32` if polyfill is active. + pub(super) fn store_io_with_f16_polyfill( + &mut self, + body: &mut Vec, + varying_id: Word, + value_id: Word, + ) { + if let Some(f32_ty) = self.io_f16_polyfills.get_f32_io_type(varying_id) { + let converted = self.id_gen.next(); + super::f16_polyfill::F16IoPolyfill::emit_f16_to_f32_conversion( + value_id, f32_ty, converted, body, + ); + body.push(Instruction::store(varying_id, converted, None)); + } else { + body.push(Instruction::store(varying_id, value_id, None)); + } + } + + fn write_global_variable( + &mut self, + ir_module: &crate::Module, + global_variable: &crate::GlobalVariable, + ) -> Result { + use spirv::Decoration; + + let id = self.id_gen.next(); + let class = map_storage_class(global_variable.space); + + //self.check(class.required_capabilities())?; + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = global_variable.name { + self.debugs.push(Instruction::name(id, name)); + } + } + + let storage_access = match global_variable.space { + crate::AddressSpace::Storage { access } => Some(access), + _ => match ir_module.types[global_variable.ty].inner { + crate::TypeInner::Image { + class: crate::ImageClass::Storage { access, .. }, + .. + } => Some(access), + _ => None, + }, + }; + if let Some(storage_access) = storage_access { + if !storage_access.contains(crate::StorageAccess::LOAD) { + self.decorate(id, Decoration::NonReadable, &[]); + } + if !storage_access.contains(crate::StorageAccess::STORE) { + self.decorate(id, Decoration::NonWritable, &[]); + } + } + + // Note: we should be able to substitute `binding_array`, + // but there is still code that tries to register the pre-substituted type, + // and it is failing on 0. + let mut substitute_inner_type_lookup = None; + if let Some(ref res_binding) = global_variable.binding { + let bind_target = self.resolve_resource_binding(res_binding)?; + self.decorate(id, Decoration::DescriptorSet, &[bind_target.descriptor_set]); + self.decorate(id, Decoration::Binding, &[bind_target.binding]); + + if let Some(remapped_binding_array_size) = bind_target.binding_array_size { + if let crate::TypeInner::BindingArray { base, .. } = + ir_module.types[global_variable.ty].inner + { + let binding_array_type_id = + self.get_type_id(LookupType::Local(LocalType::BindingArray { + base, + size: remapped_binding_array_size, + })); + substitute_inner_type_lookup = Some(LookupType::Local(LocalType::Pointer { + base: binding_array_type_id, + class, + })); + } + } + }; + + let init_word = global_variable + .init + .map(|constant| self.constant_ids[constant]); + let inner_type_id = self.get_type_id( + substitute_inner_type_lookup.unwrap_or(LookupType::Handle(global_variable.ty)), + ); + + // generate the wrapping structure if needed + let pointer_type_id = if global_needs_wrapper(ir_module, global_variable) { + let wrapper_type_id = self.id_gen.next(); + + self.decorate(wrapper_type_id, Decoration::Block, &[]); + let member = crate::StructMember { + name: None, + ty: global_variable.ty, + binding: None, + offset: 0, + }; + self.decorate_struct_member(wrapper_type_id, 0, &member, &ir_module.types)?; + + Instruction::type_struct(wrapper_type_id, &[inner_type_id]) + .to_words(&mut self.logical_layout.declarations); + + let pointer_type_id = self.id_gen.next(); + Instruction::type_pointer(pointer_type_id, class, wrapper_type_id) + .to_words(&mut self.logical_layout.declarations); + + pointer_type_id + } else { + // This is a global variable in the Storage address space. The only + // way it could have `global_needs_wrapper() == false` is if it has + // a runtime-sized or binding array. + // Runtime-sized arrays were decorated when iterating through struct content. + // Now binding arrays require Block decorating. + if let crate::AddressSpace::Storage { .. } = global_variable.space { + match ir_module.types[global_variable.ty].inner { + crate::TypeInner::BindingArray { base, .. } => { + let ty = &ir_module.types[base]; + let mut should_decorate = true; + // Check if the type has a runtime array. + // A normal runtime array gets validated out, + // so only structs can be with runtime arrays + if let crate::TypeInner::Struct { ref members, .. } = ty.inner { + // only the last member in a struct can be dynamically sized + if let Some(last_member) = members.last() { + if let &crate::TypeInner::Array { + size: crate::ArraySize::Dynamic, + .. + } = &ir_module.types[last_member.ty].inner + { + should_decorate = false; + } + } + } + if should_decorate { + let decorated_id = self.get_handle_type_id(base); + self.decorate(decorated_id, Decoration::Block, &[]); + } + } + _ => (), + }; + } + if substitute_inner_type_lookup.is_some() { + inner_type_id + } else { + self.get_handle_pointer_type_id(global_variable.ty, class) + } + }; + + let init_word = match (global_variable.space, self.zero_initialize_workgroup_memory) { + (crate::AddressSpace::Private, _) + | (crate::AddressSpace::WorkGroup, super::ZeroInitializeWorkgroupMemoryMode::Native) => { + init_word.or_else(|| Some(self.get_constant_null(inner_type_id))) + } + _ => init_word, + }; + + Instruction::variable(pointer_type_id, id, class, init_word) + .to_words(&mut self.logical_layout.declarations); + Ok(id) + } + + /// Write the necessary decorations for a struct member. + /// + /// Emit decorations for the `index`'th member of the struct type + /// designated by `struct_id`, described by `member`. + fn decorate_struct_member( + &mut self, + struct_id: Word, + index: usize, + member: &crate::StructMember, + arena: &UniqueArena, + ) -> Result<(), Error> { + use spirv::Decoration; + + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::Offset, + &[member.offset], + )); + + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(ref name) = member.name { + self.debugs + .push(Instruction::member_name(struct_id, index as u32, name)); + } + } + + // Matrices and (potentially nested) arrays of matrices both require decorations, + // so "see through" any arrays to determine if they're needed. + let mut member_array_subty_inner = &arena[member.ty].inner; + while let crate::TypeInner::Array { base, .. } = *member_array_subty_inner { + member_array_subty_inner = &arena[base].inner; + } + + if let crate::TypeInner::Matrix { + columns: _, + rows, + scalar, + } = *member_array_subty_inner + { + let byte_stride = Alignment::from(rows) * scalar.width as u32; + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::ColMajor, + &[], + )); + self.annotations.push(Instruction::member_decorate( + struct_id, + index as u32, + Decoration::MatrixStride, + &[byte_stride], + )); + } + + Ok(()) + } + + pub(super) fn get_function_type(&mut self, lookup_function_type: LookupFunctionType) -> Word { + match self + .lookup_function_type + .entry(lookup_function_type.clone()) + { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(_) => { + let id = self.id_gen.next(); + let instruction = Instruction::type_function( + id, + lookup_function_type.return_type_id, + &lookup_function_type.parameter_type_ids, + ); + instruction.to_words(&mut self.logical_layout.declarations); + self.lookup_function_type.insert(lookup_function_type, id); + id + } + } + } + + fn write_physical_layout(&mut self) { + self.physical_layout.bound = self.id_gen.0 + 1; + } + + pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { + self.use_extension("SPV_EXT_mesh_shader"); + self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; + let lang_version = self.lang_version(); + if lang_version.0 <= 1 && lang_version.1 < 4 { + return Err(Error::SpirvVersionTooLow(1, 4)); + } + Ok(()) + } + + fn write_logical_layout( + &mut self, + ir_module: &crate::Module, + mod_info: &ModuleInfo, + ep_index: Option, + debug_info: &Option, + ) -> Result<(), Error> { + fn has_view_index_check( + ir_module: &crate::Module, + binding: Option<&crate::Binding>, + ty: Handle, + ) -> bool { + match ir_module.types[ty].inner { + crate::TypeInner::Struct { ref members, .. } => members.iter().any(|member| { + has_view_index_check(ir_module, member.binding.as_ref(), member.ty) + }), + _ => binding == Some(&crate::Binding::BuiltIn(crate::BuiltIn::ViewIndex)), + } + } + + let has_storage_buffers = + ir_module + .global_variables + .iter() + .any(|(_, var)| match var.space { + crate::AddressSpace::Storage { .. } => true, + _ => false, + }); + let has_view_index = ir_module + .entry_points + .iter() + .flat_map(|entry| entry.function.arguments.iter()) + .any(|arg| has_view_index_check(ir_module, arg.binding.as_ref(), arg.ty)); + let mut has_ray_query = ir_module.special_types.ray_desc.is_some() + | ir_module.special_types.ray_intersection.is_some(); + let has_vertex_return = ir_module.special_types.ray_vertex_return.is_some(); + + // Ways mesh shaders are required: + // * Mesh entry point used - checked for + // * Mesh function like setVertex used outside mesh entry point, this is handled when those are written + // * Fragment shader with per primitive data - handled in `map_binding` + let has_mesh_shaders = ir_module.entry_points.iter().any(|entry| { + entry.stage == crate::ShaderStage::Mesh || entry.stage == crate::ShaderStage::Task + }) || ir_module + .global_variables + .iter() + .any(|gvar| gvar.1.space == crate::AddressSpace::TaskPayload); + + for (_, &crate::Type { ref inner, .. }) in ir_module.types.iter() { + // spirv does not know whether these have vertex return - that is done by us + if let &crate::TypeInner::AccelerationStructure { .. } + | &crate::TypeInner::RayQuery { .. } = inner + { + has_ray_query = true + } + } + + if self.physical_layout.version < 0x10300 && has_storage_buffers { + // enable the storage buffer class on < SPV-1.3 + Instruction::extension("SPV_KHR_storage_buffer_storage_class") + .to_words(&mut self.logical_layout.extensions); + } + if has_view_index { + Instruction::extension("SPV_KHR_multiview") + .to_words(&mut self.logical_layout.extensions) + } + if has_ray_query { + Instruction::extension("SPV_KHR_ray_query") + .to_words(&mut self.logical_layout.extensions) + } + if has_vertex_return { + Instruction::extension("SPV_KHR_ray_tracing_position_fetch") + .to_words(&mut self.logical_layout.extensions); + } + if has_mesh_shaders { + self.require_mesh_shaders()?; + } + Instruction::type_void(self.void_type).to_words(&mut self.logical_layout.declarations); + Instruction::ext_inst_import(self.gl450_ext_inst_id, "GLSL.std.450") + .to_words(&mut self.logical_layout.ext_inst_imports); + + let mut debug_info_inner = None; + if self.flags.contains(WriterFlags::DEBUG) { + if let Some(debug_info) = debug_info.as_ref() { + let source_file_id = self.id_gen.next(); + self.debugs + .push(Instruction::string(debug_info.file_name, source_file_id)); + + debug_info_inner = Some(DebugInfoInner { + source_code: debug_info.source_code, + source_file_id, + }); + self.debugs.append(&mut Instruction::source_auto_continued( + debug_info.language, + 0, + &debug_info_inner, + )); + } + } + + // write all types + for (handle, _) in ir_module.types.iter() { + self.write_type_declaration_arena(ir_module, handle)?; + } + + // write all const-expressions as constants + self.constant_ids + .resize(ir_module.global_expressions.len(), 0); + for (handle, _) in ir_module.global_expressions.iter() { + self.write_constant_expr(handle, ir_module, mod_info)?; + } + debug_assert!(self.constant_ids.iter().all(|&id| id != 0)); + + // write the name of constants on their respective const-expression initializer + if self.flags.contains(WriterFlags::DEBUG) { + for (_, constant) in ir_module.constants.iter() { + if let Some(ref name) = constant.name { + let id = self.constant_ids[constant.init]; + self.debugs.push(Instruction::name(id, name)); + } + } + } + + // write all global variables + for (handle, var) in ir_module.global_variables.iter() { + // If a single entry point was specified, only write `OpVariable` instructions + // for the globals it actually uses. Emit dummies for the others, + // to preserve the indices in `global_variables`. + let gvar = match ep_index { + Some(index) if mod_info.get_entry_point(index)[handle].is_empty() => { + GlobalVariable::dummy() + } + _ => { + let id = self.write_global_variable(ir_module, var)?; + GlobalVariable::new(id) + } + }; + self.global_variables.insert(handle, gvar); + } + + // write all functions + for (handle, ir_function) in ir_module.functions.iter() { + let info = &mod_info[handle]; + if let Some(index) = ep_index { + let ep_info = mod_info.get_entry_point(index); + // If this function uses globals that we omitted from the SPIR-V + // because the entry point and its callees didn't use them, + // then we must skip it. + if !ep_info.dominates_global_use(info) { + log::info!("Skip function {:?}", ir_function.name); + continue; + } + + // Skip functions that that are not compatible with this entry point's stage. + // + // When validation is enabled, it rejects modules whose entry points try to call + // incompatible functions, so if we got this far, then any functions incompatible + // with our selected entry point must not be used. + // + // When validation is disabled, `fun_info.available_stages` is always just + // `ShaderStages::all()`, so this will write all functions in the module, and + // the downstream GLSL compiler will catch any problems. + if !info.available_stages.contains(ep_info.available_stages) { + continue; + } + } + let id = self.write_function(ir_function, info, ir_module, None, &debug_info_inner)?; + self.lookup_function.insert(handle, id); + } + + // write all or one entry points + for (index, ir_ep) in ir_module.entry_points.iter().enumerate() { + if ep_index.is_some() && ep_index != Some(index) { + continue; + } + let info = mod_info.get_entry_point(index); + let ep_instruction = + self.write_entry_point(ir_ep, info, ir_module, &debug_info_inner)?; + ep_instruction.to_words(&mut self.logical_layout.entry_points); + } + + for capability in self.capabilities_used.iter() { + Instruction::capability(*capability).to_words(&mut self.logical_layout.capabilities); + } + for extension in self.extensions_used.iter() { + Instruction::extension(extension).to_words(&mut self.logical_layout.extensions); + } + if ir_module.entry_points.is_empty() { + // SPIR-V doesn't like modules without entry points + Instruction::capability(spirv::Capability::Linkage) + .to_words(&mut self.logical_layout.capabilities); + } + + let addressing_model = spirv::AddressingModel::Logical; + let memory_model = spirv::MemoryModel::GLSL450; + //self.check(addressing_model.required_capabilities())?; + //self.check(memory_model.required_capabilities())?; + + Instruction::memory_model(addressing_model, memory_model) + .to_words(&mut self.logical_layout.memory_model); + + for debug_string in self.debug_strings.iter() { + debug_string.to_words(&mut self.logical_layout.debugs); + } + + if self.flags.contains(WriterFlags::DEBUG) { + for debug in self.debugs.iter() { + debug.to_words(&mut self.logical_layout.debugs); + } + } + + for annotation in self.annotations.iter() { + annotation.to_words(&mut self.logical_layout.annotations); + } + + Ok(()) + } + + pub fn write( + &mut self, + ir_module: &crate::Module, + info: &ModuleInfo, + pipeline_options: Option<&PipelineOptions>, + debug_info: &Option, + words: &mut Vec, + ) -> Result<(), Error> { + self.reset(); + + // Try to find the entry point and corresponding index + let ep_index = match pipeline_options { + Some(po) => { + let index = ir_module + .entry_points + .iter() + .position(|ep| po.shader_stage == ep.stage && po.entry_point == ep.name) + .ok_or(Error::EntryPointNotFound)?; + Some(index) + } + None => None, + }; + + self.write_logical_layout(ir_module, info, ep_index, debug_info)?; + self.write_physical_layout(); + + self.physical_layout.in_words(words); + self.logical_layout.in_words(words); + Ok(()) + } + + /// Return the set of capabilities the last module written used. + pub const fn get_capabilities_used(&self) -> &crate::FastIndexSet { + &self.capabilities_used + } + + pub fn decorate_non_uniform_binding_array_access(&mut self, id: Word) -> Result<(), Error> { + self.require_any("NonUniformEXT", &[spirv::Capability::ShaderNonUniform])?; + self.use_extension("SPV_EXT_descriptor_indexing"); + self.decorate(id, spirv::Decoration::NonUniform, &[]); + Ok(()) + } + + pub(super) fn needs_f16_polyfill(&self, ty_inner: &crate::TypeInner) -> bool { + self.io_f16_polyfills.needs_polyfill(ty_inner) + } + + pub(super) fn write_debug_printf( + &mut self, + block: &mut Block, + string: &str, + format_params: &[Word], + ) { + if self.debug_printf.is_none() { + self.use_extension("SPV_KHR_non_semantic_info"); + let import_id = self.id_gen.next(); + Instruction::ext_inst_import(import_id, "NonSemantic.DebugPrintf") + .to_words(&mut self.logical_layout.ext_inst_imports); + self.debug_printf = Some(import_id) + } + + let import_id = self.debug_printf.unwrap(); + + let string_id = self.id_gen.next(); + self.debug_strings + .push(Instruction::string(string, string_id)); + + let mut operands = Vec::with_capacity(1 + format_params.len()); + operands.push(string_id); + operands.extend(format_params.iter()); + + let print_id = self.id_gen.next(); + block.body.push(Instruction::ext_inst( + import_id, + 1, + self.void_type, + print_id, + &operands, + )); + } +} + +#[test] +fn test_write_physical_layout() { + let mut writer = Writer::new(&Options::default()).unwrap(); + assert_eq!(writer.physical_layout.bound, 0); + writer.write_physical_layout(); + assert_eq!(writer.physical_layout.bound, 3); +} From 0a3c94d2a8df7bf263a8022c993f4d3d26885f88 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sat, 15 Nov 2025 14:06:19 -0600 Subject: [PATCH 101/110] UPdated snapshots, removed IR and analysis targets --- naga/tests/in/wgsl/mesh-shader-empty.toml | 2 +- naga/tests/in/wgsl/mesh-shader-lines.toml | 2 +- naga/tests/in/wgsl/mesh-shader-points.toml | 2 +- naga/tests/in/wgsl/mesh-shader.toml | 2 +- .../analysis/wgsl-mesh-shader-empty.info.ron | 99 -- .../analysis/wgsl-mesh-shader-lines.info.ron | 100 -- .../analysis/wgsl-mesh-shader-points.info.ron | 99 -- .../out/analysis/wgsl-mesh-shader.info.ron | 1469 ----------------- .../out/ir/wgsl-mesh-shader-empty.compact.ron | 234 --- naga/tests/out/ir/wgsl-mesh-shader-empty.ron | 234 --- .../out/ir/wgsl-mesh-shader-lines.compact.ron | 244 --- naga/tests/out/ir/wgsl-mesh-shader-lines.ron | 244 --- .../ir/wgsl-mesh-shader-points.compact.ron | 234 --- naga/tests/out/ir/wgsl-mesh-shader-points.ron | 234 --- .../tests/out/ir/wgsl-mesh-shader.compact.ron | 980 ----------- naga/tests/out/ir/wgsl-mesh-shader.ron | 980 ----------- naga/tests/out/spv/wgsl-mesh-shader.spvasm | 1 + 17 files changed, 5 insertions(+), 5155 deletions(-) delete mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron delete mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron delete mode 100644 naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron delete mode 100644 naga/tests/out/analysis/wgsl-mesh-shader.info.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-empty.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-lines.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader-points.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader.compact.ron delete mode 100644 naga/tests/out/ir/wgsl-mesh-shader.ron diff --git a/naga/tests/in/wgsl/mesh-shader-empty.toml b/naga/tests/in/wgsl/mesh-shader-empty.toml index 45deb0ef9ce..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.toml +++ b/naga/tests/in/wgsl/mesh-shader-empty.toml @@ -1,5 +1,5 @@ god_mode = true -targets = "IR | ANALYSIS | SPIRV" +targets = "SPIRV" [spv] version = [1, 4] diff --git a/naga/tests/in/wgsl/mesh-shader-lines.toml b/naga/tests/in/wgsl/mesh-shader-lines.toml index 45deb0ef9ce..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-lines.toml +++ b/naga/tests/in/wgsl/mesh-shader-lines.toml @@ -1,5 +1,5 @@ god_mode = true -targets = "IR | ANALYSIS | SPIRV" +targets = "SPIRV" [spv] version = [1, 4] diff --git a/naga/tests/in/wgsl/mesh-shader-points.toml b/naga/tests/in/wgsl/mesh-shader-points.toml index 45deb0ef9ce..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-points.toml +++ b/naga/tests/in/wgsl/mesh-shader-points.toml @@ -1,5 +1,5 @@ god_mode = true -targets = "IR | ANALYSIS | SPIRV" +targets = "SPIRV" [spv] version = [1, 4] diff --git a/naga/tests/in/wgsl/mesh-shader.toml b/naga/tests/in/wgsl/mesh-shader.toml index 45deb0ef9ce..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader.toml +++ b/naga/tests/in/wgsl/mesh-shader.toml @@ -1,5 +1,5 @@ god_mode = true -targets = "IR | ANALYSIS | SPIRV" +targets = "SPIRV" [spv] version = [1, 4] diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron deleted file mode 100644 index 9f9feac9c09..00000000000 --- a/naga/tests/out/analysis/wgsl-mesh-shader-empty.info.ron +++ /dev/null @@ -1,99 +0,0 @@ -( - type_flags: [ - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ], - functions: [], - entry_points: [ - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ | WRITE"), - (""), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(4), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ"), - ("READ"), - ], - expressions: [], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ], - const_expression_types: [], -) \ No newline at end of file diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron deleted file mode 100644 index b4c8790508d..00000000000 --- a/naga/tests/out/analysis/wgsl-mesh-shader-lines.info.ron +++ /dev/null @@ -1,100 +0,0 @@ -( - type_flags: [ - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ], - functions: [], - entry_points: [ - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ | WRITE"), - (""), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(6), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ"), - ("READ"), - ], - expressions: [], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ], - const_expression_types: [], -) \ No newline at end of file diff --git a/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron deleted file mode 100644 index fa62867576b..00000000000 --- a/naga/tests/out/analysis/wgsl-mesh-shader-points.info.ron +++ /dev/null @@ -1,99 +0,0 @@ -( - type_flags: [ - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ], - functions: [], - entry_points: [ - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ | WRITE"), - (""), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(5), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ"), - ("READ"), - ], - expressions: [], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ], - const_expression_types: [], -) \ No newline at end of file diff --git a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron b/naga/tests/out/analysis/wgsl-mesh-shader.info.ron deleted file mode 100644 index eacd33ad0f1..00000000000 --- a/naga/tests/out/analysis/wgsl-mesh-shader.info.ron +++ /dev/null @@ -1,1469 +0,0 @@ -( - type_flags: [ - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | IO_SHAREABLE | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | HOST_SHAREABLE | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ("DATA | SIZED | COPY | CREATION_RESOLVED | ARGUMENT | CONSTRUCTIBLE"), - ], - functions: [], - entry_points: [ - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ | WRITE"), - ("WRITE"), - (""), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(1), - ty: Value(Pointer( - base: 0, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 1, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 2, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Bool, - width: 1, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(6), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - ("READ"), - ("WRITE"), - ("READ | WRITE"), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: Some(0), - requirements: (""), - ), - ref_count: 0, - assignable_global: None, - ty: Handle(5), - ), - ( - uniformity: ( - non_uniform_result: Some(1), - requirements: (""), - ), - ref_count: 0, - assignable_global: None, - ty: Handle(6), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 5, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 5, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(1), - ty: Value(Pointer( - base: 0, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 1, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 1, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 9, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 4, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 1, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 10, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 7, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 6, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Uint, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(6), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 10, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 7, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 2, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 3, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(0), - ty: Value(Pointer( - base: 2, - space: TaskPayload, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(2), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(2), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 11, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 10, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 7, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: Some(2), - ty: Value(Pointer( - base: 1, - space: WorkGroup, - )), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Value(Scalar(( - kind: Float, - width: 4, - ))), - ), - ( - uniformity: ( - non_uniform_result: None, - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ( - flags: ("EXPRESSIONS | BLOCKS | CONTROL_FLOW_UNIFORMITY | STRUCT_LAYOUTS | CONSTANTS | BINDINGS"), - available_stages: ("VERTEX | FRAGMENT | COMPUTE | MESH | TASK"), - uniformity: ( - non_uniform_result: Some(0), - requirements: (""), - ), - may_kill: false, - sampling_set: [], - global_uses: [ - (""), - (""), - (""), - ], - expressions: [ - ( - uniformity: ( - non_uniform_result: Some(0), - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(4), - ), - ( - uniformity: ( - non_uniform_result: Some(1), - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(8), - ), - ( - uniformity: ( - non_uniform_result: Some(0), - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: Some(1), - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ( - uniformity: ( - non_uniform_result: Some(0), - requirements: (""), - ), - ref_count: 1, - assignable_global: None, - ty: Handle(1), - ), - ], - sampling: [], - dual_source_blending: false, - diagnostic_filter_leaf: None, - ), - ], - const_expression_types: [], -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron deleted file mode 100644 index 8bdaa72da69..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.compact.ron +++ /dev/null @@ -1,234 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 4, - binding: Some(BuiltIn(TriangleIndices)), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(3), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 5, - size: Constant(1), - stride: 16, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 6, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 7, - binding: Some(BuiltIn(Primitives)), - offset: 48, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 64, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 68, - ), - ], - span: 80, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 8, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 4, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 4, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 5, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron b/naga/tests/out/ir/wgsl-mesh-shader-empty.ron deleted file mode 100644 index 8bdaa72da69..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-empty.ron +++ /dev/null @@ -1,234 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 4, - binding: Some(BuiltIn(TriangleIndices)), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(3), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 5, - size: Constant(1), - stride: 16, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 6, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 7, - binding: Some(BuiltIn(Primitives)), - offset: 48, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 64, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 68, - ), - ], - span: 80, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 8, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 4, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 4, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 5, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron deleted file mode 100644 index b298f296985..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-lines.compact.ron +++ /dev/null @@ -1,244 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Vector( - size: Bi, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 4, - binding: Some(BuiltIn(LineIndices)), - offset: 0, - ), - ], - span: 8, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(2), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 5, - size: Constant(1), - stride: 8, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 7, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 8, - binding: Some(BuiltIn(Primitives)), - offset: 32, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 40, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 44, - ), - ], - span: 48, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 9, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 6, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 6, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Lines, - max_vertices: 2, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 5, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-lines.ron b/naga/tests/out/ir/wgsl-mesh-shader-lines.ron deleted file mode 100644 index b298f296985..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-lines.ron +++ /dev/null @@ -1,244 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Vector( - size: Bi, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 4, - binding: Some(BuiltIn(LineIndices)), - offset: 0, - ), - ], - span: 8, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(2), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 5, - size: Constant(1), - stride: 8, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 7, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 8, - binding: Some(BuiltIn(Primitives)), - offset: 32, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 40, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 44, - ), - ], - span: 48, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 9, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 6, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 6, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Lines, - max_vertices: 2, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 5, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron deleted file mode 100644 index 558a88e28d8..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-points.compact.ron +++ /dev/null @@ -1,234 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 0, - binding: Some(BuiltIn(PointIndex)), - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(1), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 4, - size: Constant(1), - stride: 4, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 6, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 7, - binding: Some(BuiltIn(Primitives)), - offset: 16, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 20, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 24, - ), - ], - span: 32, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 8, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 5, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 5, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Points, - max_vertices: 1, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 4, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader-points.ron b/naga/tests/out/ir/wgsl-mesh-shader-points.ron deleted file mode 100644 index 558a88e28d8..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader-points.ron +++ /dev/null @@ -1,234 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("dummy"), - ty: 0, - binding: None, - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 2, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 0, - binding: Some(BuiltIn(PointIndex)), - offset: 0, - ), - ], - span: 4, - ), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: None, - inner: Array( - base: 3, - size: Constant(1), - stride: 16, - ), - ), - ( - name: None, - inner: Array( - base: 4, - size: Constant(1), - stride: 4, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 6, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 7, - binding: Some(BuiltIn(Primitives)), - offset: 16, - ), - ( - name: Some("vertex_count"), - ty: 0, - binding: Some(BuiltIn(VertexCount)), - offset: 20, - ), - ( - name: Some("primitive_count"), - ty: 0, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 24, - ), - ], - span: 32, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 1, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 8, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 5, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 5, - components: [ - 0, - 1, - 2, - ], - ), - ], - named_expressions: {}, - body: [ - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 3, - end: 4, - )), - Return( - value: Some(3), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [], - result: None, - local_variables: [], - expressions: [], - named_expressions: {}, - body: [ - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Points, - max_vertices: 1, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 3, - primitive_output_type: 4, - output_variable: 1, - )), - task_payload: Some(0), - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron b/naga/tests/out/ir/wgsl-mesh-shader.compact.ron deleted file mode 100644 index 9cb3c6479c3..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader.compact.ron +++ /dev/null @@ -1,980 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Float, - width: 4, - )), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: None, - inner: Scalar(( - kind: Bool, - width: 1, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("colorMask"), - ty: 1, - binding: None, - offset: 0, - ), - ( - name: Some("visible"), - ty: 2, - binding: None, - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 1, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ( - name: Some("color"), - ty: 1, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 6, - binding: Some(BuiltIn(TriangleIndices)), - offset: 0, - ), - ( - name: Some("cull"), - ty: 2, - binding: Some(BuiltIn(CullPrimitive)), - offset: 12, - ), - ( - name: Some("colorMask"), - ty: 1, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: Some("PrimitiveInput"), - inner: Struct( - members: [ - ( - name: Some("colorMask"), - ty: 1, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Array( - base: 4, - size: Constant(3), - stride: 32, - ), - ), - ( - name: None, - inner: Array( - base: 7, - size: Constant(1), - stride: 32, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 9, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 10, - binding: Some(BuiltIn(Primitives)), - offset: 96, - ), - ( - name: Some("vertex_count"), - ty: 5, - binding: Some(BuiltIn(VertexCount)), - offset: 128, - ), - ( - name: Some("primitive_count"), - ty: 5, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, - ), - ], - span: 144, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 3, - init: None, - ), - ( - name: Some("workgroupData"), - space: WorkGroup, - binding: None, - ty: 0, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 11, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 6, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - GlobalVariable(1), - Literal(F32(1.0)), - GlobalVariable(0), - AccessIndex( - base: 2, - index: 0, - ), - Literal(F32(1.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 4, - 5, - 6, - 7, - ], - ), - GlobalVariable(0), - AccessIndex( - base: 9, - index: 1, - ), - Literal(Bool(true)), - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 6, - components: [ - 12, - 13, - 14, - ], - ), - ], - named_expressions: {}, - body: [ - Store( - pointer: 0, - value: 1, - ), - Emit(( - start: 3, - end: 4, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 8, - end: 9, - )), - Store( - pointer: 3, - value: 8, - ), - Emit(( - start: 10, - end: 11, - )), - Store( - pointer: 10, - value: 11, - ), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 15, - end: 16, - )), - Return( - value: Some(15), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [ - ( - name: Some("index"), - ty: 5, - binding: Some(BuiltIn(LocalInvocationIndex)), - ), - ( - name: Some("id"), - ty: 6, - binding: Some(BuiltIn(GlobalInvocationId)), - ), - ], - result: None, - local_variables: [], - expressions: [ - FunctionArgument(0), - FunctionArgument(1), - GlobalVariable(2), - AccessIndex( - base: 2, - index: 2, - ), - Literal(U32(3)), - GlobalVariable(2), - AccessIndex( - base: 5, - index: 3, - ), - Literal(U32(1)), - GlobalVariable(1), - Literal(F32(2.0)), - GlobalVariable(2), - AccessIndex( - base: 10, - index: 0, - ), - AccessIndex( - base: 11, - index: 0, - ), - AccessIndex( - base: 12, - index: 0, - ), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 14, - 15, - 16, - 17, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 19, - index: 0, - ), - AccessIndex( - base: 20, - index: 0, - ), - AccessIndex( - base: 21, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 23, - index: 0, - ), - Load( - pointer: 24, - ), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 26, - 27, - 28, - 29, - ], - ), - Binary( - op: Multiply, - left: 30, - right: 25, - ), - GlobalVariable(2), - AccessIndex( - base: 32, - index: 0, - ), - AccessIndex( - base: 33, - index: 1, - ), - AccessIndex( - base: 34, - index: 0, - ), - Literal(F32(-1.0)), - Literal(F32(-1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 36, - 37, - 38, - 39, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 41, - index: 0, - ), - AccessIndex( - base: 42, - index: 1, - ), - AccessIndex( - base: 43, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 45, - index: 0, - ), - Load( - pointer: 46, - ), - Literal(F32(0.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 48, - 49, - 50, - 51, - ], - ), - Binary( - op: Multiply, - left: 52, - right: 47, - ), - GlobalVariable(2), - AccessIndex( - base: 54, - index: 0, - ), - AccessIndex( - base: 55, - index: 2, - ), - AccessIndex( - base: 56, - index: 0, - ), - Literal(F32(1.0)), - Literal(F32(-1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 58, - 59, - 60, - 61, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 63, - index: 0, - ), - AccessIndex( - base: 64, - index: 2, - ), - AccessIndex( - base: 65, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 67, - index: 0, - ), - Load( - pointer: 68, - ), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 70, - 71, - 72, - 73, - ], - ), - Binary( - op: Multiply, - left: 74, - right: 69, - ), - GlobalVariable(2), - AccessIndex( - base: 76, - index: 1, - ), - AccessIndex( - base: 77, - index: 0, - ), - AccessIndex( - base: 78, - index: 0, - ), - Literal(U32(0)), - Literal(U32(1)), - Literal(U32(2)), - Compose( - ty: 6, - components: [ - 80, - 81, - 82, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 84, - index: 1, - ), - AccessIndex( - base: 85, - index: 0, - ), - AccessIndex( - base: 86, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 88, - index: 1, - ), - Load( - pointer: 89, - ), - Unary( - op: LogicalNot, - expr: 90, - ), - GlobalVariable(2), - AccessIndex( - base: 92, - index: 1, - ), - AccessIndex( - base: 93, - index: 0, - ), - AccessIndex( - base: 94, - index: 2, - ), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 96, - 97, - 98, - 99, - ], - ), - ], - named_expressions: { - 0: "index", - 1: "id", - }, - body: [ - Emit(( - start: 3, - end: 4, - )), - Store( - pointer: 3, - value: 4, - ), - Emit(( - start: 6, - end: 7, - )), - Store( - pointer: 6, - value: 7, - ), - Store( - pointer: 8, - value: 9, - ), - Emit(( - start: 11, - end: 12, - )), - Emit(( - start: 12, - end: 14, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 18, - end: 19, - )), - Store( - pointer: 13, - value: 18, - ), - Emit(( - start: 20, - end: 21, - )), - Emit(( - start: 21, - end: 23, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 24, - end: 26, - )), - Emit(( - start: 30, - end: 32, - )), - Store( - pointer: 22, - value: 31, - ), - Emit(( - start: 33, - end: 34, - )), - Emit(( - start: 34, - end: 36, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 40, - end: 41, - )), - Store( - pointer: 35, - value: 40, - ), - Emit(( - start: 42, - end: 43, - )), - Emit(( - start: 43, - end: 45, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 46, - end: 48, - )), - Emit(( - start: 52, - end: 54, - )), - Store( - pointer: 44, - value: 53, - ), - Emit(( - start: 55, - end: 56, - )), - Emit(( - start: 56, - end: 58, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 62, - end: 63, - )), - Store( - pointer: 57, - value: 62, - ), - Emit(( - start: 64, - end: 65, - )), - Emit(( - start: 65, - end: 67, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 68, - end: 70, - )), - Emit(( - start: 74, - end: 76, - )), - Store( - pointer: 66, - value: 75, - ), - Emit(( - start: 77, - end: 78, - )), - Emit(( - start: 78, - end: 80, - )), - Emit(( - start: 83, - end: 84, - )), - Store( - pointer: 79, - value: 83, - ), - Emit(( - start: 85, - end: 86, - )), - Emit(( - start: 86, - end: 88, - )), - Emit(( - start: 89, - end: 92, - )), - Store( - pointer: 87, - value: 91, - ), - Emit(( - start: 93, - end: 94, - )), - Emit(( - start: 94, - end: 96, - )), - Emit(( - start: 100, - end: 101, - )), - Store( - pointer: 95, - value: 100, - ), - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 4, - primitive_output_type: 7, - output_variable: 2, - )), - task_payload: Some(0), - ), - ( - name: "fs_main", - stage: Fragment, - early_depth_test: None, - workgroup_size: (0, 0, 0), - workgroup_size_overrides: None, - function: ( - name: Some("fs_main"), - arguments: [ - ( - name: Some("vertex"), - ty: 4, - binding: None, - ), - ( - name: Some("primitive"), - ty: 8, - binding: None, - ), - ], - result: Some(( - ty: 1, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - )), - local_variables: [], - expressions: [ - FunctionArgument(0), - FunctionArgument(1), - AccessIndex( - base: 0, - index: 1, - ), - AccessIndex( - base: 1, - index: 0, - ), - Binary( - op: Multiply, - left: 2, - right: 3, - ), - ], - named_expressions: { - 0: "vertex", - 1: "primitive", - }, - body: [ - Emit(( - start: 2, - end: 5, - )), - Return( - value: Some(4), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: None, - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/ir/wgsl-mesh-shader.ron b/naga/tests/out/ir/wgsl-mesh-shader.ron deleted file mode 100644 index 9cb3c6479c3..00000000000 --- a/naga/tests/out/ir/wgsl-mesh-shader.ron +++ /dev/null @@ -1,980 +0,0 @@ -( - types: [ - ( - name: None, - inner: Scalar(( - kind: Float, - width: 4, - )), - ), - ( - name: None, - inner: Vector( - size: Quad, - scalar: ( - kind: Float, - width: 4, - ), - ), - ), - ( - name: None, - inner: Scalar(( - kind: Bool, - width: 1, - )), - ), - ( - name: Some("TaskPayload"), - inner: Struct( - members: [ - ( - name: Some("colorMask"), - ty: 1, - binding: None, - offset: 0, - ), - ( - name: Some("visible"), - ty: 2, - binding: None, - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: Some("VertexOutput"), - inner: Struct( - members: [ - ( - name: Some("position"), - ty: 1, - binding: Some(BuiltIn(Position( - invariant: false, - ))), - offset: 0, - ), - ( - name: Some("color"), - ty: 1, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: None, - inner: Scalar(( - kind: Uint, - width: 4, - )), - ), - ( - name: None, - inner: Vector( - size: Tri, - scalar: ( - kind: Uint, - width: 4, - ), - ), - ), - ( - name: Some("PrimitiveOutput"), - inner: Struct( - members: [ - ( - name: Some("indices"), - ty: 6, - binding: Some(BuiltIn(TriangleIndices)), - offset: 0, - ), - ( - name: Some("cull"), - ty: 2, - binding: Some(BuiltIn(CullPrimitive)), - offset: 12, - ), - ( - name: Some("colorMask"), - ty: 1, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 16, - ), - ], - span: 32, - ), - ), - ( - name: Some("PrimitiveInput"), - inner: Struct( - members: [ - ( - name: Some("colorMask"), - ty: 1, - binding: Some(Location( - location: 1, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: true, - )), - offset: 0, - ), - ], - span: 16, - ), - ), - ( - name: None, - inner: Array( - base: 4, - size: Constant(3), - stride: 32, - ), - ), - ( - name: None, - inner: Array( - base: 7, - size: Constant(1), - stride: 32, - ), - ), - ( - name: Some("MeshOutput"), - inner: Struct( - members: [ - ( - name: Some("vertices"), - ty: 9, - binding: Some(BuiltIn(Vertices)), - offset: 0, - ), - ( - name: Some("primitives"), - ty: 10, - binding: Some(BuiltIn(Primitives)), - offset: 96, - ), - ( - name: Some("vertex_count"), - ty: 5, - binding: Some(BuiltIn(VertexCount)), - offset: 128, - ), - ( - name: Some("primitive_count"), - ty: 5, - binding: Some(BuiltIn(PrimitiveCount)), - offset: 132, - ), - ], - span: 144, - ), - ), - ], - special_types: ( - ray_desc: None, - ray_intersection: None, - ray_vertex_return: None, - external_texture_params: None, - external_texture_transfer_function: None, - predeclared_types: {}, - ), - constants: [], - overrides: [], - global_variables: [ - ( - name: Some("taskPayload"), - space: TaskPayload, - binding: None, - ty: 3, - init: None, - ), - ( - name: Some("workgroupData"), - space: WorkGroup, - binding: None, - ty: 0, - init: None, - ), - ( - name: Some("mesh_output"), - space: WorkGroup, - binding: None, - ty: 11, - init: None, - ), - ], - global_expressions: [], - functions: [], - entry_points: [ - ( - name: "ts_main", - stage: Task, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ts_main"), - arguments: [], - result: Some(( - ty: 6, - binding: Some(BuiltIn(MeshTaskSize)), - )), - local_variables: [], - expressions: [ - GlobalVariable(1), - Literal(F32(1.0)), - GlobalVariable(0), - AccessIndex( - base: 2, - index: 0, - ), - Literal(F32(1.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 4, - 5, - 6, - 7, - ], - ), - GlobalVariable(0), - AccessIndex( - base: 9, - index: 1, - ), - Literal(Bool(true)), - Literal(U32(1)), - Literal(U32(1)), - Literal(U32(1)), - Compose( - ty: 6, - components: [ - 12, - 13, - 14, - ], - ), - ], - named_expressions: {}, - body: [ - Store( - pointer: 0, - value: 1, - ), - Emit(( - start: 3, - end: 4, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 8, - end: 9, - )), - Store( - pointer: 3, - value: 8, - ), - Emit(( - start: 10, - end: 11, - )), - Store( - pointer: 10, - value: 11, - ), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 15, - end: 16, - )), - Return( - value: Some(15), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: Some(0), - ), - ( - name: "ms_main", - stage: Mesh, - early_depth_test: None, - workgroup_size: (1, 1, 1), - workgroup_size_overrides: None, - function: ( - name: Some("ms_main"), - arguments: [ - ( - name: Some("index"), - ty: 5, - binding: Some(BuiltIn(LocalInvocationIndex)), - ), - ( - name: Some("id"), - ty: 6, - binding: Some(BuiltIn(GlobalInvocationId)), - ), - ], - result: None, - local_variables: [], - expressions: [ - FunctionArgument(0), - FunctionArgument(1), - GlobalVariable(2), - AccessIndex( - base: 2, - index: 2, - ), - Literal(U32(3)), - GlobalVariable(2), - AccessIndex( - base: 5, - index: 3, - ), - Literal(U32(1)), - GlobalVariable(1), - Literal(F32(2.0)), - GlobalVariable(2), - AccessIndex( - base: 10, - index: 0, - ), - AccessIndex( - base: 11, - index: 0, - ), - AccessIndex( - base: 12, - index: 0, - ), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 14, - 15, - 16, - 17, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 19, - index: 0, - ), - AccessIndex( - base: 20, - index: 0, - ), - AccessIndex( - base: 21, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 23, - index: 0, - ), - Load( - pointer: 24, - ), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 26, - 27, - 28, - 29, - ], - ), - Binary( - op: Multiply, - left: 30, - right: 25, - ), - GlobalVariable(2), - AccessIndex( - base: 32, - index: 0, - ), - AccessIndex( - base: 33, - index: 1, - ), - AccessIndex( - base: 34, - index: 0, - ), - Literal(F32(-1.0)), - Literal(F32(-1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 36, - 37, - 38, - 39, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 41, - index: 0, - ), - AccessIndex( - base: 42, - index: 1, - ), - AccessIndex( - base: 43, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 45, - index: 0, - ), - Load( - pointer: 46, - ), - Literal(F32(0.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 48, - 49, - 50, - 51, - ], - ), - Binary( - op: Multiply, - left: 52, - right: 47, - ), - GlobalVariable(2), - AccessIndex( - base: 54, - index: 0, - ), - AccessIndex( - base: 55, - index: 2, - ), - AccessIndex( - base: 56, - index: 0, - ), - Literal(F32(1.0)), - Literal(F32(-1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 58, - 59, - 60, - 61, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 63, - index: 0, - ), - AccessIndex( - base: 64, - index: 2, - ), - AccessIndex( - base: 65, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 67, - index: 0, - ), - Load( - pointer: 68, - ), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 70, - 71, - 72, - 73, - ], - ), - Binary( - op: Multiply, - left: 74, - right: 69, - ), - GlobalVariable(2), - AccessIndex( - base: 76, - index: 1, - ), - AccessIndex( - base: 77, - index: 0, - ), - AccessIndex( - base: 78, - index: 0, - ), - Literal(U32(0)), - Literal(U32(1)), - Literal(U32(2)), - Compose( - ty: 6, - components: [ - 80, - 81, - 82, - ], - ), - GlobalVariable(2), - AccessIndex( - base: 84, - index: 1, - ), - AccessIndex( - base: 85, - index: 0, - ), - AccessIndex( - base: 86, - index: 1, - ), - GlobalVariable(0), - AccessIndex( - base: 88, - index: 1, - ), - Load( - pointer: 89, - ), - Unary( - op: LogicalNot, - expr: 90, - ), - GlobalVariable(2), - AccessIndex( - base: 92, - index: 1, - ), - AccessIndex( - base: 93, - index: 0, - ), - AccessIndex( - base: 94, - index: 2, - ), - Literal(F32(1.0)), - Literal(F32(0.0)), - Literal(F32(1.0)), - Literal(F32(1.0)), - Compose( - ty: 1, - components: [ - 96, - 97, - 98, - 99, - ], - ), - ], - named_expressions: { - 0: "index", - 1: "id", - }, - body: [ - Emit(( - start: 3, - end: 4, - )), - Store( - pointer: 3, - value: 4, - ), - Emit(( - start: 6, - end: 7, - )), - Store( - pointer: 6, - value: 7, - ), - Store( - pointer: 8, - value: 9, - ), - Emit(( - start: 11, - end: 12, - )), - Emit(( - start: 12, - end: 14, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 18, - end: 19, - )), - Store( - pointer: 13, - value: 18, - ), - Emit(( - start: 20, - end: 21, - )), - Emit(( - start: 21, - end: 23, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 24, - end: 26, - )), - Emit(( - start: 30, - end: 32, - )), - Store( - pointer: 22, - value: 31, - ), - Emit(( - start: 33, - end: 34, - )), - Emit(( - start: 34, - end: 36, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 40, - end: 41, - )), - Store( - pointer: 35, - value: 40, - ), - Emit(( - start: 42, - end: 43, - )), - Emit(( - start: 43, - end: 45, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 46, - end: 48, - )), - Emit(( - start: 52, - end: 54, - )), - Store( - pointer: 44, - value: 53, - ), - Emit(( - start: 55, - end: 56, - )), - Emit(( - start: 56, - end: 58, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 62, - end: 63, - )), - Store( - pointer: 57, - value: 62, - ), - Emit(( - start: 64, - end: 65, - )), - Emit(( - start: 65, - end: 67, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 0, - end: 0, - )), - Emit(( - start: 68, - end: 70, - )), - Emit(( - start: 74, - end: 76, - )), - Store( - pointer: 66, - value: 75, - ), - Emit(( - start: 77, - end: 78, - )), - Emit(( - start: 78, - end: 80, - )), - Emit(( - start: 83, - end: 84, - )), - Store( - pointer: 79, - value: 83, - ), - Emit(( - start: 85, - end: 86, - )), - Emit(( - start: 86, - end: 88, - )), - Emit(( - start: 89, - end: 92, - )), - Store( - pointer: 87, - value: 91, - ), - Emit(( - start: 93, - end: 94, - )), - Emit(( - start: 94, - end: 96, - )), - Emit(( - start: 100, - end: 101, - )), - Store( - pointer: 95, - value: 100, - ), - Return( - value: None, - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: Some(( - topology: Triangles, - max_vertices: 3, - max_vertices_override: None, - max_primitives: 1, - max_primitives_override: None, - vertex_output_type: 4, - primitive_output_type: 7, - output_variable: 2, - )), - task_payload: Some(0), - ), - ( - name: "fs_main", - stage: Fragment, - early_depth_test: None, - workgroup_size: (0, 0, 0), - workgroup_size_overrides: None, - function: ( - name: Some("fs_main"), - arguments: [ - ( - name: Some("vertex"), - ty: 4, - binding: None, - ), - ( - name: Some("primitive"), - ty: 8, - binding: None, - ), - ], - result: Some(( - ty: 1, - binding: Some(Location( - location: 0, - interpolation: Some(Perspective), - sampling: Some(Center), - blend_src: None, - per_primitive: false, - )), - )), - local_variables: [], - expressions: [ - FunctionArgument(0), - FunctionArgument(1), - AccessIndex( - base: 0, - index: 1, - ), - AccessIndex( - base: 1, - index: 0, - ), - Binary( - op: Multiply, - left: 2, - right: 3, - ), - ], - named_expressions: { - 0: "vertex", - 1: "primitive", - }, - body: [ - Emit(( - start: 2, - end: 5, - )), - Return( - value: Some(4), - ), - ], - diagnostic_filter_leaf: None, - ), - mesh_info: None, - task_payload: None, - ), - ], - diagnostic_filters: [], - diagnostic_filter_leaf: None, - doc_comments: None, -) \ No newline at end of file diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index 85c63fd091e..ef5a79e131e 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -19,6 +19,7 @@ OpExecutionMode %186 OriginUpperLeft OpMemberDecorate %61 0 BuiltIn Position OpDecorate %61 Block OpMemberDecorate %65 0 BuiltIn CullPrimitiveEXT +OpMemberDecorate %65 0 PerPrimitiveNV OpDecorate %65 Block OpDecorate %68 PerPrimitiveNV OpDecorate %69 Block From 9c381bb1966b74e2061d247b68bcaf5ade57a0b1 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Sun, 16 Nov 2025 17:47:37 -0600 Subject: [PATCH 102/110] Updated thing --- naga/src/back/spv/block.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 6bdf97a9eb6..4d10f859f13 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -321,7 +321,6 @@ impl Writer { let out_var_id = return_info.out_variable_id; // Load the actual vertex and primitive counts - // TODO: take the min of this and the maximum output count let mut load_u32_by_member_index = |member_index: u32| { let ptr_id = self.id_gen.next(); block.body.push(Instruction::access_chain( From 1a3319bb7facb47c3cc8ebc2d9e8eeef9cfc8dac Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:19:05 -0600 Subject: [PATCH 103/110] Update naga/src/back/spv/block.rs Co-authored-by: Connor Fitzgerald --- naga/src/back/spv/block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 4d10f859f13..dac40a55526 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -496,7 +496,7 @@ impl Writer { } body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); // Flip the vertex position y coordinate in some cases - // Can't use epilogue flip because can't read from this storage class I believe + // Can't use epilogue flip because can't read from this storage class if needs_y_flip { let prev_y = self.id_gen.next(); body.push(Instruction::composite_extract( From 8bd35d3bce2a6513fdc518fc1af3b2900d7fa3ab Mon Sep 17 00:00:00 2001 From: Inner Daemons <85136135+inner-daemons@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:19:29 -0600 Subject: [PATCH 104/110] Update naga/src/back/spv/block.rs Co-authored-by: Connor Fitzgerald --- naga/src/back/spv/block.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index dac40a55526..d48a7b5a9fe 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -410,8 +410,8 @@ impl Writer { block.body.push(ins); } - // All this for a `for i in 0..num_vertices` lol - // This is basically just unzipping an array and copying to many arrays + // This is iterating over every returned vertex and splitting + // it out into the multiple per-output arrays. let u32_type_id = self.get_u32_type_id(); let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); let vertex_loop_header = self.id_gen.next(); From 186c916b50586953149d3b3731b82d2d90b77cb7 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 13:34:34 -0600 Subject: [PATCH 105/110] Addressed some stuff --- examples/features/src/mesh_shader/mod.rs | 33 ++++++++++++------------ naga/src/back/spv/block.rs | 9 ++++--- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index f4e733d0456..e598953f890 100644 --- a/examples/features/src/mesh_shader/mod.rs +++ b/examples/features/src/mesh_shader/mod.rs @@ -183,20 +183,19 @@ pub fn main() { #[cfg(test)] #[wgpu_test::gpu_test] -pub static TEST: crate::framework::ExampleTestParams = - crate::framework::ExampleTestParams { - name: "mesh_shader", - // Generated on 1080ti on Vk/Windows - image_path: "/examples/features/src/mesh_shader/screenshot.png", - width: 1024, - height: 768, - optional_features: wgpu::Features::default(), - base_test_parameters: wgpu_test::TestParameters::default() - .features( - wgpu::Features::EXPERIMENTAL_MESH_SHADER - | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS, - ) - .limits(wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()), - comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], - _phantom: std::marker::PhantomData::, - }; +pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams { + name: "mesh_shader", + // Generated on 1080ti on Vk/Windows + image_path: "/examples/features/src/mesh_shader/screenshot.png", + width: 1024, + height: 768, + optional_features: wgpu::Features::default(), + base_test_parameters: wgpu_test::TestParameters::default() + .features( + wgpu::Features::EXPERIMENTAL_MESH_SHADER + | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS, + ) + .limits(wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()), + comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], + _phantom: std::marker::PhantomData::, +}; diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index d48a7b5a9fe..83050e74bb5 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -350,17 +350,18 @@ impl Writer { // Clamp them to the allowed range let vert_count_id = self.id_gen.next(); - block.body.push(Instruction::ext_inst( + block.body.push(Instruction::ext_inst_gl_op( self.gl450_ext_inst_id, - spirv::GLOp::UMin as Word, + spirv::GLOp::UMin, u32_id, vert_count_id, &[vert_count_id_before_max, return_info.max_vertices_constant], )); let prim_count_id = self.id_gen.next(); - block.body.push(Instruction::ext_inst( + + block.body.push(Instruction::ext_inst_gl_op( self.gl450_ext_inst_id, - spirv::GLOp::UMin as Word, + spirv::GLOp::UMin, u32_id, prim_count_id, &[ From cefee1b1e15df7395563ed20459ac222500e04e2 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 13:40:40 -0600 Subject: [PATCH 106/110] Another fix --- naga/src/back/spv/writer.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index a835f08550c..d64a04f2642 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1719,7 +1719,6 @@ impl Writer { } crate::ShaderStage::Compute => { let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; Instruction::execution_mode( function_id, execution_mode, @@ -1730,7 +1729,6 @@ impl Writer { } crate::ShaderStage::Task => { let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; Instruction::execution_mode( function_id, execution_mode, @@ -1741,7 +1739,6 @@ impl Writer { } crate::ShaderStage::Mesh => { let execution_mode = spirv::ExecutionMode::LocalSize; - //self.check(execution_mode.required_capabilities())?; Instruction::execution_mode( function_id, execution_mode, From 019e8ed74daadf421f0fa7efd8cf3fb70622a9e4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 14:13:39 -0600 Subject: [PATCH 107/110] Cleaned some stuff up --- naga/src/back/spv/block.rs | 441 +-------------- naga/src/back/spv/mesh_shader.rs | 894 +++++++++++++++++++++++++++++++ naga/src/back/spv/mod.rs | 37 +- naga/src/back/spv/writer.rs | 409 +------------- 4 files changed, 903 insertions(+), 878 deletions(-) create mode 100644 naga/src/back/spv/mesh_shader.rs diff --git a/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index 83050e74bb5..e94d5197acf 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -258,446 +258,7 @@ impl Writer { _ => {} } } - // OpEmitMeshTasksEXT must be called right before exiting (after setting other - // output variables if there are any) - for (index, res_member) in result_members.iter().enumerate() { - if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { - // If its a function like `fn a() -> @builtin(...) vec3 ...` - // then just use the output value. If it's a struct, extract the - // value from the struct. - let member_value_id = match ir_result.binding { - Some(_) => value_id, - None => { - let member_value_id = self.id_gen.next(); - body.push(Instruction::composite_extract( - res_member.type_id, - member_value_id, - value_id, - &[index as Word], - )); - member_value_id - } - }; - - // Extract the vec3 into 3 u32's - let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; - for (i, &value) in values.iter().enumerate() { - let instruction = Instruction::composite_extract( - self.get_u32_type_id(), - value, - member_value_id, - &[i as Word], - ); - body.push(instruction); - } - // TODO: make this guaranteed to be uniform - let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); - for id in values { - instruction.add_operand(id); - } - // We have to include the task payload in our call - if let Some(task_payload) = task_payload { - instruction.add_operand(task_payload); - } - return Ok(instruction); - } - } - Ok(Instruction::return_void()) - } - - /// Writes the return call for a mesh shader, which involves copying previously - /// written vertices/primitives into the actual output location. - fn write_mesh_shader_return( - &mut self, - return_info: &super::MeshReturnInfo, - block: &mut Block, - ) -> Result<(), Error> { - self.write_control_barrier(crate::Barrier::WORK_GROUP, block); - - let u32_id = self.get_u32_type_id(); - - // This is the actual value (not pointer) - // of the data to be outputted - let out_var_id = return_info.out_variable_id; - - // Load the actual vertex and primitive counts - let mut load_u32_by_member_index = |member_index: u32| { - let ptr_id = self.id_gen.next(); - block.body.push(Instruction::access_chain( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), - ptr_id, - out_var_id, - &[self.get_constant_scalar(crate::Literal::U32(member_index))], - )); - let id = self.id_gen.next(); - block.body.push(Instruction::load(u32_id, id, ptr_id, None)); - id - }; - let vert_count_id_before_max = load_u32_by_member_index( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) - .unwrap() as u32, - ); - let prim_count_id_before_max = load_u32_by_member_index( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) - .unwrap() as u32, - ); - - // Clamp them to the allowed range - let vert_count_id = self.id_gen.next(); - block.body.push(Instruction::ext_inst_gl_op( - self.gl450_ext_inst_id, - spirv::GLOp::UMin, - u32_id, - vert_count_id, - &[vert_count_id_before_max, return_info.max_vertices_constant], - )); - let prim_count_id = self.id_gen.next(); - - block.body.push(Instruction::ext_inst_gl_op( - self.gl450_ext_inst_id, - spirv::GLOp::UMin, - u32_id, - prim_count_id, - &[ - prim_count_id_before_max, - return_info.max_primitives_constant, - ], - )); - // Get pointers to the arrays of data to extract - let vert_array_ptr = self.id_gen.next(); - block.body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.vertex_array_type_id, - spirv::StorageClass::Workgroup, - ), - vert_array_ptr, - return_info.out_variable_id, - &[self.get_constant_scalar(crate::Literal::U32( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) - .unwrap() as u32, - ))], - )); - let prim_array_ptr = self.id_gen.next(); - block.body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.primitive_array_type_id, - spirv::StorageClass::Workgroup, - ), - prim_array_ptr, - return_info.out_variable_id, - &[self.get_constant_scalar(crate::Literal::U32( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) - .unwrap() as u32, - ))], - )); - - // This must be called exactly once before any other mesh outputs are written - { - let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); - ins.add_operand(vert_count_id); - ins.add_operand(prim_count_id); - block.body.push(ins); - } - - // This is iterating over every returned vertex and splitting - // it out into the multiple per-output arrays. - let u32_type_id = self.get_u32_type_id(); - let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); - let vertex_loop_header = self.id_gen.next(); - let prim_loop_header = self.id_gen.next(); - let in_between_loops = self.id_gen.next(); - let func_end = self.id_gen.next(); - let index_var = return_info.function_variable; - - block.body.push(Instruction::store( - index_var, - return_info.local_invocation_index_id, - None, - )); - block.body.push(Instruction::branch(vertex_loop_header)); - - // This generates the instructions used to copy all parts of a single output vertex - // to their individual output locations - let vertex_copy_body = { - let mut body = Vec::new(); - // Current index to copy - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let vert_to_copy_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.vertex_type_id, - spirv::StorageClass::Workgroup, - ), - vert_to_copy_ptr, - vert_array_ptr, - &[val_i], - )); - - // Load the entire vertex value - let vert_to_copy = self.id_gen.next(); - body.push(Instruction::load( - return_info.vertex_type_id, - vert_to_copy, - vert_to_copy_ptr, - None, - )); - - let mut builtin_index = 0; - let mut binding_index = 0; - // Write individual members of the vertex - for (member_id, member) in return_info.vertex_members.iter().enumerate() { - let val_to_copy = self.id_gen.next(); - let mut needs_y_flip = false; - body.push(Instruction::composite_extract( - member.ty_id, - val_to_copy, - vert_to_copy, - &[member_id as u32], - )); - let ptr_to_copy_to = self.id_gen.next(); - // Get a pointer to the struct member to copy - match member.binding { - crate::Binding::BuiltIn(bi) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.vertex_builtin_block.unwrap(), - &[ - val_i, - self.get_constant_scalar(crate::Literal::U32(builtin_index)), - ], - )); - needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) - && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); - builtin_index += 1; - } - crate::Binding::Location { .. } => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.vertex_bindings[binding_index], - &[val_i, zero_u32], - )); - binding_index += 1; - } - } - body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); - // Flip the vertex position y coordinate in some cases - // Can't use epilogue flip because can't read from this storage class - if needs_y_flip { - let prev_y = self.id_gen.next(); - body.push(Instruction::composite_extract( - self.get_f32_type_id(), - prev_y, - val_to_copy, - &[1], - )); - let new_y = self.id_gen.next(); - body.push(Instruction::unary( - spirv::Op::FNegate, - self.get_f32_type_id(), - new_y, - prev_y, - )); - let new_ptr_to_copy_to = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_f32_pointer_type_id(spirv::StorageClass::Output), - new_ptr_to_copy_to, - ptr_to_copy_to, - &[self.get_constant_scalar(crate::Literal::U32(1))], - )); - body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); - } - } - body - }; - - // Primitive copies - // See comments in `vertex_copy_body` - let primitive_copy_body = { - let mut body = Vec::new(); - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let prim_to_copy_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.primitive_type_id, - spirv::StorageClass::Workgroup, - ), - prim_to_copy_ptr, - prim_array_ptr, - &[val_i], - )); - let prim_to_copy = self.id_gen.next(); - body.push(Instruction::load( - return_info.primitive_type_id, - prim_to_copy, - prim_to_copy_ptr, - None, - )); - - let mut builtin_index = 0; - let mut binding_index = 0; - for (member_id, member) in return_info.primitive_members.iter().enumerate() { - let val_to_copy = self.id_gen.next(); - body.push(Instruction::composite_extract( - member.ty_id, - val_to_copy, - prim_to_copy, - &[member_id as u32], - )); - let ptr_to_copy_to = self.id_gen.next(); - match member.binding { - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_indices.unwrap(), - &[val_i], - )); - } - crate::Binding::BuiltIn(_) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_builtin_block.unwrap(), - &[ - val_i, - self.get_constant_scalar(crate::Literal::U32(builtin_index)), - ], - )); - builtin_index += 1; - } - crate::Binding::Location { .. } => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_bindings[binding_index], - &[val_i, zero_u32], - )); - binding_index += 1; - } - } - body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); - } - body - }; - - // This writes the actual loop - let mut write_loop = |body: &mut Vec, - mut loop_body_block, - loop_header, - loop_merge, - count_id, - index_var| { - let condition_check = self.id_gen.next(); - let loop_continue = self.id_gen.next(); - let loop_body = self.id_gen.next(); - - // Loop header - { - body.push(Instruction::label(loop_header)); - body.push(Instruction::loop_merge( - loop_merge, - loop_continue, - spirv::SelectionControl::empty(), - )); - body.push(Instruction::branch(condition_check)); - } - // Condition check - check if i is less than num vertices to copy - { - body.push(Instruction::label(condition_check)); - - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let cond = self.id_gen.next(); - body.push(Instruction::binary( - spirv::Op::ULessThan, - self.get_bool_type_id(), - cond, - val_i, - count_id, - )); - body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); - } - // Loop body - { - body.push(Instruction::label(loop_body)); - body.append(&mut loop_body_block); - body.push(Instruction::branch(loop_continue)); - } - // Loop continue - increment i - { - body.push(Instruction::label(loop_continue)); - - let prev_val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, prev_val_i, index_var, None)); - let new_val_i = self.id_gen.next(); - body.push(Instruction::binary( - spirv::Op::IAdd, - u32_type_id, - new_val_i, - prev_val_i, - return_info.workgroup_size, - )); - body.push(Instruction::store(index_var, new_val_i, None)); - - body.push(Instruction::branch(loop_header)); - } - }; - // Write vertex copy loop - write_loop( - &mut block.body, - vertex_copy_body, - vertex_loop_header, - in_between_loops, - vert_count_id, - index_var, - ); - // In between loops, reset the initial index - { - block.body.push(Instruction::label(in_between_loops)); - - block.body.push(Instruction::store( - index_var, - return_info.local_invocation_index_id, - None, - )); - - block.body.push(Instruction::branch(prim_loop_header)); - } - // Write primitive copy loop - write_loop( - &mut block.body, - primitive_copy_body, - prim_loop_header, - func_end, - prim_count_id, - index_var, - ); - - block.body.push(Instruction::label(func_end)); - Ok(()) + self.write_entry_point_task_return(value_id, ir_result, result_members, body, task_payload) } } diff --git a/naga/src/back/spv/mesh_shader.rs b/naga/src/back/spv/mesh_shader.rs new file mode 100644 index 00000000000..0d41c3c2882 --- /dev/null +++ b/naga/src/back/spv/mesh_shader.rs @@ -0,0 +1,894 @@ +use alloc::vec::Vec; +use spirv::Word; + +use crate::{ + back::spv::{ + helpers::BindingDecorations, writer::FunctionInterface, Block, EntryPointContext, Error, + Instruction, ResultMember, WriterFlags, + }, + non_max_u32::NonMaxU32, + Handle, +}; + +#[derive(Clone)] +pub struct MeshReturnMember { + pub ty_id: u32, + pub binding: crate::Binding, +} +pub struct MeshReturnInfo { + /// Id of the workgroup variable containing the data to be output + pub out_variable_id: Word, + /// All members of the output variable struct type + pub out_members: Vec, + + pub max_vertices_constant: Word, + pub vertex_type_id: Word, + pub vertex_array_type_id: Word, + pub vertex_members: Vec, + pub max_primitives_constant: Word, + pub primitive_type_id: Word, + pub primitive_array_type_id: Word, + pub primitive_members: Vec, + // * In vulkan, all builtins must be in the same block. + // * All bindings must be in their own unique block. + // * Also, the primitive indices builtin family needs its own block. + // * Also also, cull primitive doesn't care about having its own block, but + // some older validation layers didn't respect this. + pub vertex_builtin_block: Option, + pub vertex_bindings: Vec, + pub primitive_builtin_block: Option, + pub primitive_bindings: Vec, + pub primitive_indices: Option, + pub local_invocation_index_id: Word, + pub workgroup_size: u32, + /// The id of a function variable in the entry point for a u32 + pub function_variable: Word, +} + +impl super::Writer { + pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { + self.use_extension("SPV_EXT_mesh_shader"); + self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; + let lang_version = self.lang_version(); + if lang_version.0 <= 1 && lang_version.1 < 4 { + return Err(Error::SpirvVersionTooLow(1, 4)); + } + Ok(()) + } + + /// Sets up an output variable that will handle part of the mesh shader output + pub(super) fn write_mesh_return_global_variable( + &mut self, + ty: u32, + array_size_id: u32, + ) -> Result { + let array_ty = self.id_gen.next(); + Instruction::type_array(array_ty, ty, array_size_id) + .to_words(&mut self.logical_layout.declarations); + let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); + let var_id = self.id_gen.next(); + Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) + .to_words(&mut self.logical_layout.declarations); + Ok(var_id) + } + + /// This does various setup things to allow mesh shader entry points + /// to be properly written, such as creating the output variables + pub(super) fn write_entry_point_mesh_shader_info( + &mut self, + iface: &mut FunctionInterface, + local_invocation_index_id: Option, + ir_module: &crate::Module, + prelude: &mut Block, + ep_context: &mut EntryPointContext, + ) -> Result<(), Error> { + let Some(ref mesh_info) = iface.mesh_info else { + return Ok(()); + }; + // Collect the members in the output structs + let out_members: Vec = + match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let vertex_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() + .ty_id; + let primitive_array_type_id = out_members + .iter() + .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() + .ty_id; + let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { + &crate::Type { + inner: crate::TypeInner::Struct { ref members, .. }, + .. + } => members + .iter() + .map(|a| MeshReturnMember { + ty_id: self.get_handle_type_id(a.ty), + binding: a.binding.clone().unwrap(), + }) + .collect(), + _ => unreachable!(), + }; + // In the final return, we do a giant memcpy, for which this is helpful + let local_invocation_index_id = match local_invocation_index_id { + Some(a) => a, + None => { + let u32_id = self.get_u32_type_id(); + let var = self.id_gen.next(); + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), + var, + spirv::StorageClass::Input, + None, + ) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate( + var, + spirv::Decoration::BuiltIn, + &[spirv::BuiltIn::LocalInvocationIndex as u32], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(var); + + let loaded_value = self.id_gen.next(); + prelude + .body + .push(Instruction::load(u32_id, loaded_value, var, None)); + loaded_value + } + }; + let u32_id = self.get_u32_type_id(); + // A general function variable that we guarantee to allow in the final return. It must be + // declared at the top of the function. Currently it is used in the memcpy part to keep + // track of the current index to copy. + let function_variable = self.id_gen.next(); + prelude.body.insert( + 0, + Instruction::variable( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), + function_variable, + spirv::StorageClass::Function, + None, + ), + ); + // This is the information that is passed to the function writer + // so that it can write the final return logic + let mut mesh_return_info = MeshReturnInfo { + out_variable_id: self.global_variables[mesh_info.output_variable].var_id, + out_members, + + vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), + vertex_array_type_id, + vertex_members, + max_vertices_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), + primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), + primitive_array_type_id, + primitive_members, + max_primitives_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), + vertex_bindings: Vec::new(), + vertex_builtin_block: None, + primitive_bindings: Vec::new(), + primitive_builtin_block: None, + primitive_indices: None, + local_invocation_index_id, + workgroup_size: self + .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), + function_variable, + }; + let vert_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); + let prim_array_size_id = + self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); + + // Create the actual output variables and types. + // According to SPIR-V, + // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) + // * Each member with `location` must be in its own `Block` decorated `struct` + // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) + // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper + + // Write vertex builtin block + if mesh_return_info + .vertex_members + .iter() + .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) + { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.vertex_members { + if let crate::Binding::BuiltIn(_) = member.binding { + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_vertex_builtin_outputs")); + } + mesh_return_info.vertex_builtin_block = Some(v); + } + // Write primitive builtin block + if mesh_return_info.primitive_members.iter().any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) { + let builtin_block_ty_id = self.id_gen.next(); + let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); + let mut bi_index = 0; + let mut decorations = Vec::new(); + for member in &mesh_return_info.primitive_members { + if let crate::Binding::BuiltIn(bi) = member.binding { + // These need to be in their own block, unlike other builtins + if matches!( + bi, + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) { + continue; + } + ins.add_operand(member.ty_id); + let binding = self.map_binding( + ir_module, + iface.stage, + spirv::StorageClass::Output, + // Unused except in fragment shaders with other conditions, so we can pass null + Handle::new(NonMaxU32::new(0).unwrap()), + &member.binding, + )?; + match binding { + BindingDecorations::BuiltIn(bi, others) => { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + spirv::Decoration::BuiltIn, + &[bi as Word], + )); + for other in others { + decorations.push(Instruction::member_decorate( + builtin_block_ty_id, + bi_index, + other, + &[], + )); + } + } + _ => unreachable!(), + } + bi_index += 1; + } + } + ins.to_words(&mut self.logical_layout.declarations); + decorations.push(Instruction::decorate( + builtin_block_ty_id, + spirv::Decoration::Block, + &[], + )); + for dec in decorations { + dec.to_words(&mut self.logical_layout.annotations); + } + let v = + self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_builtin_outputs")); + } + mesh_return_info.primitive_builtin_block = Some(v); + } + + // Write vertex binding output blocks (1 array per output struct member) + for member in &mesh_return_info.vertex_members { + match member.binding { + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; + iface.varying_ids.push(v); + mesh_return_info.vertex_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + // Write primitive binding output blocks (1 array per output struct member) + // Also write indices output block + for member in &mesh_return_info.primitive_members { + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + // This is written here instead of as part of the builtin block + let v = + self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::decorate( + v, + spirv::Decoration::BuiltIn, + &[match member.binding.to_built_in().unwrap() { + crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, + crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, + crate::BuiltIn::TriangleIndices => { + spirv::BuiltIn::PrimitiveTriangleIndicesEXT + } + _ => unreachable!(), + } as Word], + ) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + if self.flags.contains(WriterFlags::DEBUG) { + self.debugs + .push(Instruction::name(v, "naga_primitive_indices_outputs")); + } + mesh_return_info.primitive_indices = Some(v); + } + crate::Binding::Location { location, .. } => { + let s_type = self.id_gen.next(); + Instruction::type_struct(s_type, &[member.ty_id]) + .to_words(&mut self.logical_layout.declarations); + Instruction::decorate(s_type, spirv::Decoration::Block, &[]) + .to_words(&mut self.logical_layout.annotations); + Instruction::member_decorate( + s_type, + 0, + spirv::Decoration::Location, + &[location], + ) + .to_words(&mut self.logical_layout.annotations); + let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; + Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) + .to_words(&mut self.logical_layout.annotations); + iface.varying_ids.push(v); + + mesh_return_info.primitive_bindings.push(v); + } + crate::Binding::BuiltIn(_) => (), + } + } + + // Store this where it can be read later during function write + ep_context.mesh_state = Some(mesh_return_info); + + Ok(()) + } + + pub(super) fn write_entry_point_task_return( + &mut self, + value_id: Word, + ir_result: &crate::FunctionResult, + result_members: &[ResultMember], + body: &mut Vec, + task_payload: Option, + ) -> Result { + // OpEmitMeshTasksEXT must be called right before exiting (after setting other + // output variables if there are any) + for (index, res_member) in result_members.iter().enumerate() { + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + // If its a function like `fn a() -> @builtin(...) vec3 ...` + // then just use the output value. If it's a struct, extract the + // value from the struct. + let member_value_id = match ir_result.binding { + Some(_) => value_id, + None => { + let member_value_id = self.id_gen.next(); + body.push(Instruction::composite_extract( + res_member.type_id, + member_value_id, + value_id, + &[index as Word], + )); + member_value_id + } + }; + + // Extract the vec3 into 3 u32's + let values = [self.id_gen.next(), self.id_gen.next(), self.id_gen.next()]; + for (i, &value) in values.iter().enumerate() { + let instruction = Instruction::composite_extract( + self.get_u32_type_id(), + value, + member_value_id, + &[i as Word], + ); + body.push(instruction); + } + // TODO: make this guaranteed to be uniform + let mut instruction = Instruction::new(spirv::Op::EmitMeshTasksEXT); + for id in values { + instruction.add_operand(id); + } + // We have to include the task payload in our call + if let Some(task_payload) = task_payload { + instruction.add_operand(task_payload); + } + return Ok(instruction); + } + } + Ok(Instruction::return_void()) + } + + /// Writes the return call for a mesh shader, which involves copying previously + /// written vertices/primitives into the actual output location. + pub(super) fn write_mesh_shader_return( + &mut self, + return_info: &MeshReturnInfo, + block: &mut Block, + ) -> Result<(), Error> { + self.write_control_barrier(crate::Barrier::WORK_GROUP, block); + + let u32_id = self.get_u32_type_id(); + + // This is the actual value (not pointer) + // of the data to be outputted + let out_var_id = return_info.out_variable_id; + + // Load the actual vertex and primitive counts + let mut load_u32_by_member_index = |member_index: u32| { + let ptr_id = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), + ptr_id, + out_var_id, + &[self.get_constant_scalar(crate::Literal::U32(member_index))], + )); + let id = self.id_gen.next(); + block.body.push(Instruction::load(u32_id, id, ptr_id, None)); + id + }; + let vert_count_id_before_max = load_u32_by_member_index( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) + .unwrap() as u32, + ); + let prim_count_id_before_max = load_u32_by_member_index( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) + .unwrap() as u32, + ); + + // Clamp them to the allowed range + let vert_count_id = self.id_gen.next(); + block.body.push(Instruction::ext_inst_gl_op( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + vert_count_id, + &[vert_count_id_before_max, return_info.max_vertices_constant], + )); + let prim_count_id = self.id_gen.next(); + + block.body.push(Instruction::ext_inst_gl_op( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + prim_count_id, + &[ + prim_count_id_before_max, + return_info.max_primitives_constant, + ], + )); + // Get pointers to the arrays of data to extract + let vert_array_ptr = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.vertex_array_type_id, + spirv::StorageClass::Workgroup, + ), + vert_array_ptr, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) + .unwrap() as u32, + ))], + )); + let prim_array_ptr = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.primitive_array_type_id, + spirv::StorageClass::Workgroup, + ), + prim_array_ptr, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) + .unwrap() as u32, + ))], + )); + + // This must be called exactly once before any other mesh outputs are written + { + let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); + ins.add_operand(vert_count_id); + ins.add_operand(prim_count_id); + block.body.push(ins); + } + + // This is iterating over every returned vertex and splitting + // it out into the multiple per-output arrays. + let u32_type_id = self.get_u32_type_id(); + let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); + let vertex_loop_header = self.id_gen.next(); + let prim_loop_header = self.id_gen.next(); + let in_between_loops = self.id_gen.next(); + let func_end = self.id_gen.next(); + let index_var = return_info.function_variable; + + block.body.push(Instruction::store( + index_var, + return_info.local_invocation_index_id, + None, + )); + block.body.push(Instruction::branch(vertex_loop_header)); + + // This generates the instructions used to copy all parts of a single output vertex + // to their individual output locations + let vertex_copy_body = { + let mut body = Vec::new(); + // Current index to copy + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let vert_to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.vertex_type_id, + spirv::StorageClass::Workgroup, + ), + vert_to_copy_ptr, + vert_array_ptr, + &[val_i], + )); + + // Load the entire vertex value + let vert_to_copy = self.id_gen.next(); + body.push(Instruction::load( + return_info.vertex_type_id, + vert_to_copy, + vert_to_copy_ptr, + None, + )); + + let mut builtin_index = 0; + let mut binding_index = 0; + // Write individual members of the vertex + for (member_id, member) in return_info.vertex_members.iter().enumerate() { + let val_to_copy = self.id_gen.next(); + let mut needs_y_flip = false; + body.push(Instruction::composite_extract( + member.ty_id, + val_to_copy, + vert_to_copy, + &[member_id as u32], + )); + let ptr_to_copy_to = self.id_gen.next(); + // Get a pointer to the struct member to copy + match member.binding { + crate::Binding::BuiltIn(bi) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.vertex_builtin_block.unwrap(), + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) + && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.vertex_bindings[binding_index], + &[val_i, zero_u32], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + // Flip the vertex position y coordinate in some cases + // Can't use epilogue flip because can't read from this storage class + if needs_y_flip { + let prev_y = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_f32_type_id(), + prev_y, + val_to_copy, + &[1], + )); + let new_y = self.id_gen.next(); + body.push(Instruction::unary( + spirv::Op::FNegate, + self.get_f32_type_id(), + new_y, + prev_y, + )); + let new_ptr_to_copy_to = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_f32_pointer_type_id(spirv::StorageClass::Output), + new_ptr_to_copy_to, + ptr_to_copy_to, + &[self.get_constant_scalar(crate::Literal::U32(1))], + )); + body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); + } + } + body + }; + + // Primitive copies + // See comments in `vertex_copy_body` + let primitive_copy_body = { + let mut body = Vec::new(); + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let prim_to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id( + return_info.primitive_type_id, + spirv::StorageClass::Workgroup, + ), + prim_to_copy_ptr, + prim_array_ptr, + &[val_i], + )); + let prim_to_copy = self.id_gen.next(); + body.push(Instruction::load( + return_info.primitive_type_id, + prim_to_copy, + prim_to_copy_ptr, + None, + )); + + let mut builtin_index = 0; + let mut binding_index = 0; + for (member_id, member) in return_info.primitive_members.iter().enumerate() { + let val_to_copy = self.id_gen.next(); + body.push(Instruction::composite_extract( + member.ty_id, + val_to_copy, + prim_to_copy, + &[member_id as u32], + )); + let ptr_to_copy_to = self.id_gen.next(); + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_indices.unwrap(), + &[val_i], + )); + } + crate::Binding::BuiltIn(_) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_builtin_block.unwrap(), + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_bindings[binding_index], + &[val_i, zero_u32], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + } + body + }; + + // This writes the actual loop + let mut write_loop = |body: &mut Vec, + mut loop_body_block, + loop_header, + loop_merge, + count_id, + index_var| { + let condition_check = self.id_gen.next(); + let loop_continue = self.id_gen.next(); + let loop_body = self.id_gen.next(); + + // Loop header + { + body.push(Instruction::label(loop_header)); + body.push(Instruction::loop_merge( + loop_merge, + loop_continue, + spirv::SelectionControl::empty(), + )); + body.push(Instruction::branch(condition_check)); + } + // Condition check - check if i is less than num vertices to copy + { + body.push(Instruction::label(condition_check)); + + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let cond = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::ULessThan, + self.get_bool_type_id(), + cond, + val_i, + count_id, + )); + body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); + } + // Loop body + { + body.push(Instruction::label(loop_body)); + body.append(&mut loop_body_block); + body.push(Instruction::branch(loop_continue)); + } + // Loop continue - increment i + { + body.push(Instruction::label(loop_continue)); + + let prev_val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, prev_val_i, index_var, None)); + let new_val_i = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::IAdd, + u32_type_id, + new_val_i, + prev_val_i, + return_info.workgroup_size, + )); + body.push(Instruction::store(index_var, new_val_i, None)); + + body.push(Instruction::branch(loop_header)); + } + }; + // Write vertex copy loop + write_loop( + &mut block.body, + vertex_copy_body, + vertex_loop_header, + in_between_loops, + vert_count_id, + index_var, + ); + // In between loops, reset the initial index + { + block.body.push(Instruction::label(in_between_loops)); + + block.body.push(Instruction::store( + index_var, + return_info.local_invocation_index_id, + None, + )); + + block.body.push(Instruction::branch(prim_loop_header)); + } + // Write primitive copy loop + write_loop( + &mut block.body, + primitive_copy_body, + prim_loop_header, + func_end, + prim_count_id, + index_var, + ); + + block.body.push(Instruction::label(func_end)); + Ok(()) + } +} diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index 2d88c0501f5..921f68eb5c0 100644 --- a/naga/src/back/spv/mod.rs +++ b/naga/src/back/spv/mod.rs @@ -11,12 +11,14 @@ mod image; mod index; mod instructions; mod layout; +mod mesh_shader; mod ray; mod recyclable; mod selection; mod subgroup; mod writer; +pub use mesh_shader::{MeshReturnInfo, MeshReturnMember}; pub use spirv::{Capability, SourceLanguage}; use alloc::{string::String, vec::Vec}; @@ -144,41 +146,6 @@ struct ResultMember { built_in: Option, } -#[derive(Clone)] -struct MeshReturnMember { - pub ty_id: u32, - pub binding: crate::Binding, -} -struct MeshReturnInfo { - /// Id of the workgroup variable containing the data to be output - out_variable_id: Word, - /// All members of the output variable struct type - out_members: Vec, - - max_vertices_constant: Word, - vertex_type_id: Word, - vertex_array_type_id: Word, - vertex_members: Vec, - max_primitives_constant: Word, - primitive_type_id: Word, - primitive_array_type_id: Word, - primitive_members: Vec, - // * In vulkan, all builtins must be in the same block. - // * All bindings must be in their own unique block. - // * Also, the primitive indices builtin family needs its own block. - // * Also also, cull primitive doesn't care about having its own block, but - // some older validation layers didn't respect this. - vertex_builtin_block: Option, - vertex_bindings: Vec, - primitive_builtin_block: Option, - primitive_bindings: Vec, - primitive_indices: Option, - local_invocation_index_id: Word, - workgroup_size: u32, - /// The id of a function variable in the entry point for a u32 - function_variable: Word, -} - struct EntryPointContext { argument_ids: Vec, results: Vec, diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index d64a04f2642..1174391e8eb 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -15,17 +15,16 @@ use super::{ use crate::{ arena::{Handle, HandleVec, UniqueArena}, back::spv::{helpers::BindingDecorations, BindingInfo, WrappedFunction}, - non_max_u32::NonMaxU32, proc::{Alignment, TypeResolution}, valid::{FunctionInfo, ModuleInfo}, }; -struct FunctionInterface<'a> { - varying_ids: &'a mut Vec, - stage: crate::ShaderStage, - task_payload: Option>, - mesh_info: Option, - workgroup_size: [u32; 3], +pub struct FunctionInterface<'a> { + pub varying_ids: &'a mut Vec, + pub stage: crate::ShaderStage, + pub task_payload: Option>, + pub mesh_info: Option, + pub workgroup_size: [u32; 3], } impl Function { @@ -741,392 +740,6 @@ impl Writer { Ok(()) } - /// Sets up an output variable that will handle part of the mesh shader output - fn write_mesh_return_global_variable( - &mut self, - ty: u32, - array_size_id: u32, - ) -> Result { - let array_ty = self.id_gen.next(); - Instruction::type_array(array_ty, ty, array_size_id) - .to_words(&mut self.logical_layout.declarations); - let ptr_ty = self.get_pointer_type_id(array_ty, spirv::StorageClass::Output); - let var_id = self.id_gen.next(); - Instruction::variable(ptr_ty, var_id, spirv::StorageClass::Output, None) - .to_words(&mut self.logical_layout.declarations); - Ok(var_id) - } - - /// This does various setup things to allow mesh shader entry points - /// to be properly written, such as creating the output variables - fn write_entry_point_mesh_shader_info( - &mut self, - iface: &mut FunctionInterface, - local_invocation_index_id: Option, - ir_module: &crate::Module, - prelude: &mut Block, - ep_context: &mut EntryPointContext, - ) -> Result<(), Error> { - let Some(ref mesh_info) = iface.mesh_info else { - return Ok(()); - }; - // Collect the members in the output structs - let out_members: Vec = - match &ir_module.types[ir_module.global_variables[mesh_info.output_variable].ty] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let vertex_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) - .unwrap() - .ty_id; - let primitive_array_type_id = out_members - .iter() - .find(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) - .unwrap() - .ty_id; - let vertex_members = match &ir_module.types[mesh_info.vertex_output_type] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - let primitive_members = match &ir_module.types[mesh_info.primitive_output_type] { - &crate::Type { - inner: crate::TypeInner::Struct { ref members, .. }, - .. - } => members - .iter() - .map(|a| super::MeshReturnMember { - ty_id: self.get_handle_type_id(a.ty), - binding: a.binding.clone().unwrap(), - }) - .collect(), - _ => unreachable!(), - }; - // In the final return, we do a giant memcpy, for which this is helpful - let local_invocation_index_id = match local_invocation_index_id { - Some(a) => a, - None => { - let u32_id = self.get_u32_type_id(); - let var = self.id_gen.next(); - Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Input), - var, - spirv::StorageClass::Input, - None, - ) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate( - var, - spirv::Decoration::BuiltIn, - &[spirv::BuiltIn::LocalInvocationIndex as u32], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(var); - - let loaded_value = self.id_gen.next(); - prelude - .body - .push(Instruction::load(u32_id, loaded_value, var, None)); - loaded_value - } - }; - let u32_id = self.get_u32_type_id(); - // A general function variable that we guarantee to allow in the final return. It must be - // declared at the top of the function. Currently it is used in the memcpy part to keep - // track of the current index to copy. - let function_variable = self.id_gen.next(); - prelude.body.insert( - 0, - Instruction::variable( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Function), - function_variable, - spirv::StorageClass::Function, - None, - ), - ); - // This is the information that is passed to the function writer - // so that it can write the final return logic - let mut mesh_return_info = super::MeshReturnInfo { - out_variable_id: self.global_variables[mesh_info.output_variable].var_id, - out_members, - - vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), - vertex_array_type_id, - vertex_members, - max_vertices_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), - primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), - primitive_array_type_id, - primitive_members, - max_primitives_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), - vertex_bindings: Vec::new(), - vertex_builtin_block: None, - primitive_bindings: Vec::new(), - primitive_builtin_block: None, - primitive_indices: None, - local_invocation_index_id, - workgroup_size: self - .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), - function_variable, - }; - let vert_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); - let prim_array_size_id = - self.get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)); - - // Create the actual output variables and types. - // According to SPIR-V, - // * All builtins must be in the same output `Block` (except builtins for different output types like vertex/primitive) - // * Each member with `location` must be in its own `Block` decorated `struct` - // * Some builtins like CullPrimitiveEXT don't care as much (older validation layers don't know this! Wonderful!) - // * Some builtins like the indices ones need to be in their own output variable without a struct wrapper - - // Write vertex builtin block - if mesh_return_info - .vertex_members - .iter() - .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) - { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.vertex_members { - if let crate::Binding::BuiltIn(_) = member.binding { - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], - )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } - } - _ => unreachable!(), - } - bi_index += 1; - } - } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = - self.write_mesh_return_global_variable(builtin_block_ty_id, vert_array_size_id)?; - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_vertex_builtin_outputs")); - } - mesh_return_info.vertex_builtin_block = Some(v); - } - // Write primitive builtin block - if mesh_return_info.primitive_members.iter().any(|a| { - !matches!( - a.binding, - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices - ) | crate::Binding::Location { .. } - ) - }) { - let builtin_block_ty_id = self.id_gen.next(); - let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); - let mut bi_index = 0; - let mut decorations = Vec::new(); - for member in &mesh_return_info.primitive_members { - if let crate::Binding::BuiltIn(bi) = member.binding { - // These need to be in their own block, unlike other builtins - if matches!( - bi, - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) { - continue; - } - ins.add_operand(member.ty_id); - let binding = self.map_binding( - ir_module, - iface.stage, - spirv::StorageClass::Output, - // Unused except in fragment shaders with other conditions, so we can pass null - Handle::new(NonMaxU32::new(0).unwrap()), - &member.binding, - )?; - match binding { - BindingDecorations::BuiltIn(bi, others) => { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - spirv::Decoration::BuiltIn, - &[bi as Word], - )); - for other in others { - decorations.push(Instruction::member_decorate( - builtin_block_ty_id, - bi_index, - other, - &[], - )); - } - } - _ => unreachable!(), - } - bi_index += 1; - } - } - ins.to_words(&mut self.logical_layout.declarations); - decorations.push(Instruction::decorate( - builtin_block_ty_id, - spirv::Decoration::Block, - &[], - )); - for dec in decorations { - dec.to_words(&mut self.logical_layout.annotations); - } - let v = - self.write_mesh_return_global_variable(builtin_block_ty_id, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_builtin_outputs")); - } - mesh_return_info.primitive_builtin_block = Some(v); - } - - // Write vertex binding output blocks (1 array per output struct member) - for member in &mesh_return_info.vertex_members { - match member.binding { - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; - iface.varying_ids.push(v); - mesh_return_info.vertex_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), - } - } - // Write primitive binding output blocks (1 array per output struct member) - // Also write indices output block - for member in &mesh_return_info.primitive_members { - match member.binding { - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) => { - // This is written here instead of as part of the builtin block - let v = - self.write_mesh_return_global_variable(member.ty_id, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::decorate( - v, - spirv::Decoration::BuiltIn, - &[match member.binding.to_built_in().unwrap() { - crate::BuiltIn::PointIndex => spirv::BuiltIn::PrimitivePointIndicesEXT, - crate::BuiltIn::LineIndices => spirv::BuiltIn::PrimitiveLineIndicesEXT, - crate::BuiltIn::TriangleIndices => { - spirv::BuiltIn::PrimitiveTriangleIndicesEXT - } - _ => unreachable!(), - } as Word], - ) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - if self.flags.contains(WriterFlags::DEBUG) { - self.debugs - .push(Instruction::name(v, "naga_primitive_indices_outputs")); - } - mesh_return_info.primitive_indices = Some(v); - } - crate::Binding::Location { location, .. } => { - let s_type = self.id_gen.next(); - Instruction::type_struct(s_type, &[member.ty_id]) - .to_words(&mut self.logical_layout.declarations); - Instruction::decorate(s_type, spirv::Decoration::Block, &[]) - .to_words(&mut self.logical_layout.annotations); - Instruction::member_decorate( - s_type, - 0, - spirv::Decoration::Location, - &[location], - ) - .to_words(&mut self.logical_layout.annotations); - let v = self.write_mesh_return_global_variable(s_type, prim_array_size_id)?; - Instruction::decorate(v, spirv::Decoration::PerPrimitiveEXT, &[]) - .to_words(&mut self.logical_layout.annotations); - iface.varying_ids.push(v); - - mesh_return_info.primitive_bindings.push(v); - } - crate::Binding::BuiltIn(_) => (), - } - } - - // Store this where it can be read later during function write - ep_context.mesh_state = Some(mesh_return_info); - - Ok(()) - } - fn write_function( &mut self, ir_function: &crate::Function, @@ -3136,16 +2749,6 @@ impl Writer { self.physical_layout.bound = self.id_gen.0 + 1; } - pub(super) fn require_mesh_shaders(&mut self) -> Result<(), Error> { - self.use_extension("SPV_EXT_mesh_shader"); - self.require_any("Mesh Shaders", &[spirv::Capability::MeshShadingEXT])?; - let lang_version = self.lang_version(); - if lang_version.0 <= 1 && lang_version.1 < 4 { - return Err(Error::SpirvVersionTooLow(1, 4)); - } - Ok(()) - } - fn write_logical_layout( &mut self, ir_module: &crate::Module, From 3c7a258143e1f341a7dc54e4cb4183cf37c3d7d4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 19:12:39 -0600 Subject: [PATCH 108/110] Refactored slightly --- naga/src/back/spv/mesh_shader.rs | 585 +++++++++++++++---------------- 1 file changed, 285 insertions(+), 300 deletions(-) diff --git a/naga/src/back/spv/mesh_shader.rs b/naga/src/back/spv/mesh_shader.rs index 0d41c3c2882..fcb1d9b5bb8 100644 --- a/naga/src/back/spv/mesh_shader.rs +++ b/naga/src/back/spv/mesh_shader.rs @@ -15,34 +15,40 @@ pub struct MeshReturnMember { pub ty_id: u32, pub binding: crate::Binding, } -pub struct MeshReturnInfo { - /// Id of the workgroup variable containing the data to be output - pub out_variable_id: Word, - /// All members of the output variable struct type - pub out_members: Vec, - - pub max_vertices_constant: Word, - pub vertex_type_id: Word, - pub vertex_array_type_id: Word, - pub vertex_members: Vec, - pub max_primitives_constant: Word, - pub primitive_type_id: Word, - pub primitive_array_type_id: Word, - pub primitive_members: Vec, + +struct PerOutputTypeMeshReturnInfo { + max_length_constant: Word, + type_id: Word, + array_type_id: Word, + struct_members: Vec, + // * In vulkan, all builtins must be in the same block. // * All bindings must be in their own unique block. // * Also, the primitive indices builtin family needs its own block. // * Also also, cull primitive doesn't care about having its own block, but // some older validation layers didn't respect this. - pub vertex_builtin_block: Option, - pub vertex_bindings: Vec, - pub primitive_builtin_block: Option, - pub primitive_bindings: Vec, - pub primitive_indices: Option, - pub local_invocation_index_id: Word, - pub workgroup_size: u32, + builtin_block: Option, + bindings: Vec, +} + +pub struct MeshReturnInfo { + /// Id of the workgroup variable containing the data to be output + out_variable_id: Word, + /// All members of the output variable struct type + out_members: Vec, + /// Id of the input variable for local invocation id + local_invocation_index_id: Word, + /// Total workgroup size (product) + workgroup_size: u32, /// The id of a function variable in the entry point for a u32 - pub function_variable: Word, + function_variable: Word, + + /// Vertex-specific info + vertex_info: PerOutputTypeMeshReturnInfo, + /// Primitive-specific info + primitive_info: PerOutputTypeMeshReturnInfo, + /// Array variable for the primitive indices builtin + primitive_indices: Option, } impl super::Writer { @@ -183,26 +189,30 @@ impl super::Writer { let mut mesh_return_info = MeshReturnInfo { out_variable_id: self.global_variables[mesh_info.output_variable].var_id, out_members, - - vertex_type_id: self.get_handle_type_id(mesh_info.vertex_output_type), - vertex_array_type_id, - vertex_members, - max_vertices_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), - primitive_type_id: self.get_handle_type_id(mesh_info.primitive_output_type), - primitive_array_type_id, - primitive_members, - max_primitives_constant: self - .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), - vertex_bindings: Vec::new(), - vertex_builtin_block: None, - primitive_bindings: Vec::new(), - primitive_builtin_block: None, - primitive_indices: None, local_invocation_index_id, workgroup_size: self .get_constant_scalar(crate::Literal::U32(iface.workgroup_size.iter().product())), function_variable, + + vertex_info: PerOutputTypeMeshReturnInfo { + type_id: self.get_handle_type_id(mesh_info.vertex_output_type), + array_type_id: vertex_array_type_id, + struct_members: vertex_members, + max_length_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)), + bindings: Vec::new(), + builtin_block: None, + }, + primitive_info: PerOutputTypeMeshReturnInfo { + type_id: self.get_handle_type_id(mesh_info.primitive_output_type), + array_type_id: primitive_array_type_id, + struct_members: primitive_members, + max_length_constant: self + .get_constant_scalar(crate::Literal::U32(mesh_info.max_primitives)), + bindings: Vec::new(), + builtin_block: None, + }, + primitive_indices: None, }; let vert_array_size_id = self.get_constant_scalar(crate::Literal::U32(mesh_info.max_vertices)); @@ -218,7 +228,8 @@ impl super::Writer { // Write vertex builtin block if mesh_return_info - .vertex_members + .vertex_info + .struct_members .iter() .any(|a| matches!(a.binding, crate::Binding::BuiltIn(..))) { @@ -226,7 +237,7 @@ impl super::Writer { let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); let mut bi_index = 0; let mut decorations = Vec::new(); - for member in &mesh_return_info.vertex_members { + for member in &mesh_return_info.vertex_info.struct_members { if let crate::Binding::BuiltIn(_) = member.binding { ins.add_operand(member.ty_id); let binding = self.map_binding( @@ -275,24 +286,29 @@ impl super::Writer { self.debugs .push(Instruction::name(v, "naga_vertex_builtin_outputs")); } - mesh_return_info.vertex_builtin_block = Some(v); + mesh_return_info.vertex_info.builtin_block = Some(v); } // Write primitive builtin block - if mesh_return_info.primitive_members.iter().any(|a| { - !matches!( - a.binding, - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices - ) | crate::Binding::Location { .. } - ) - }) { + if mesh_return_info + .primitive_info + .struct_members + .iter() + .any(|a| { + !matches!( + a.binding, + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices + ) | crate::Binding::Location { .. } + ) + }) + { let builtin_block_ty_id = self.id_gen.next(); let mut ins = Instruction::type_struct(builtin_block_ty_id, &[]); let mut bi_index = 0; let mut decorations = Vec::new(); - for member in &mesh_return_info.primitive_members { + for member in &mesh_return_info.primitive_info.struct_members { if let crate::Binding::BuiltIn(bi) = member.binding { // These need to be in their own block, unlike other builtins if matches!( @@ -352,11 +368,11 @@ impl super::Writer { self.debugs .push(Instruction::name(v, "naga_primitive_builtin_outputs")); } - mesh_return_info.primitive_builtin_block = Some(v); + mesh_return_info.primitive_info.builtin_block = Some(v); } // Write vertex binding output blocks (1 array per output struct member) - for member in &mesh_return_info.vertex_members { + for member in &mesh_return_info.vertex_info.struct_members { match member.binding { crate::Binding::Location { location, .. } => { let s_type = self.id_gen.next(); @@ -373,14 +389,14 @@ impl super::Writer { .to_words(&mut self.logical_layout.annotations); let v = self.write_mesh_return_global_variable(s_type, vert_array_size_id)?; iface.varying_ids.push(v); - mesh_return_info.vertex_bindings.push(v); + mesh_return_info.vertex_info.bindings.push(v); } crate::Binding::BuiltIn(_) => (), } } // Write primitive binding output blocks (1 array per output struct member) // Also write indices output block - for member in &mesh_return_info.primitive_members { + for member in &mesh_return_info.primitive_info.struct_members { match member.binding { crate::Binding::BuiltIn( crate::BuiltIn::PointIndex @@ -430,7 +446,7 @@ impl super::Writer { .to_words(&mut self.logical_layout.annotations); iface.varying_ids.push(v); - mesh_return_info.primitive_bindings.push(v); + mesh_return_info.primitive_info.bindings.push(v); } crate::Binding::BuiltIn(_) => (), } @@ -497,6 +513,198 @@ impl super::Writer { Ok(Instruction::return_void()) } + // This writes the actual loop + #[allow(clippy::too_many_arguments)] + fn write_mesh_copy_loop( + &mut self, + body: &mut Vec, + mut loop_body_block: Vec, + loop_header: u32, + loop_merge: u32, + count_id: u32, + index_var: u32, + return_info: &MeshReturnInfo, + ) { + let u32_id = self.get_u32_type_id(); + let condition_check = self.id_gen.next(); + let loop_continue = self.id_gen.next(); + let loop_body = self.id_gen.next(); + + // Loop header + { + body.push(Instruction::label(loop_header)); + body.push(Instruction::loop_merge( + loop_merge, + loop_continue, + spirv::SelectionControl::empty(), + )); + body.push(Instruction::branch(condition_check)); + } + // Condition check - check if i is less than num vertices to copy + { + body.push(Instruction::label(condition_check)); + + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_id, val_i, index_var, None)); + + let cond = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::ULessThan, + self.get_bool_type_id(), + cond, + val_i, + count_id, + )); + body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); + } + // Loop body + { + body.push(Instruction::label(loop_body)); + body.append(&mut loop_body_block); + body.push(Instruction::branch(loop_continue)); + } + // Loop continue - increment i + { + body.push(Instruction::label(loop_continue)); + + let prev_val_i = self.id_gen.next(); + body.push(Instruction::load(u32_id, prev_val_i, index_var, None)); + let new_val_i = self.id_gen.next(); + body.push(Instruction::binary( + spirv::Op::IAdd, + u32_id, + new_val_i, + prev_val_i, + return_info.workgroup_size, + )); + body.push(Instruction::store(index_var, new_val_i, None)); + + body.push(Instruction::branch(loop_header)); + } + } + + /// This generates the instructions used to copy all parts of a single output vertex/primitive + /// to their individual output locations + fn write_mesh_copy_body( + &mut self, + is_primitive: bool, + return_info: &MeshReturnInfo, + index_var: u32, + vert_array_ptr: u32, + prim_array_ptr: u32, + ) -> Vec { + let u32_type_id = self.get_u32_type_id(); + let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); + let mut body = Vec::new(); + // Current index to copy + let val_i = self.id_gen.next(); + body.push(Instruction::load(u32_type_id, val_i, index_var, None)); + + let info = if is_primitive { + &return_info.primitive_info + } else { + &return_info.vertex_info + }; + let array_ptr = if is_primitive { + prim_array_ptr + } else { + vert_array_ptr + }; + + let to_copy_ptr = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_pointer_type_id(info.type_id, spirv::StorageClass::Workgroup), + to_copy_ptr, + array_ptr, + &[val_i], + )); + + // Load the entire vertex value + let to_copy = self.id_gen.next(); + body.push(Instruction::load(info.type_id, to_copy, to_copy_ptr, None)); + + let mut builtin_index = 0; + let mut binding_index = 0; + // Write individual members of the vertex + for (member_id, member) in info.struct_members.iter().enumerate() { + let val_to_copy = self.id_gen.next(); + let mut needs_y_flip = false; + body.push(Instruction::composite_extract( + member.ty_id, + val_to_copy, + to_copy, + &[member_id as u32], + )); + let ptr_to_copy_to = self.id_gen.next(); + // Get a pointer to the struct member to copy + match member.binding { + crate::Binding::BuiltIn( + crate::BuiltIn::PointIndex + | crate::BuiltIn::LineIndices + | crate::BuiltIn::TriangleIndices, + ) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + return_info.primitive_indices.unwrap(), + &[val_i], + )); + } + crate::Binding::BuiltIn(bi) => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + info.builtin_block.unwrap(), + &[ + val_i, + self.get_constant_scalar(crate::Literal::U32(builtin_index)), + ], + )); + needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) + && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); + builtin_index += 1; + } + crate::Binding::Location { .. } => { + body.push(Instruction::access_chain( + self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), + ptr_to_copy_to, + info.bindings[binding_index], + &[val_i, zero_u32], + )); + binding_index += 1; + } + } + body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); + // Flip the vertex position y coordinate in some cases + // Can't use epilogue flip because can't read from this storage class + if needs_y_flip { + let prev_y = self.id_gen.next(); + body.push(Instruction::composite_extract( + self.get_f32_type_id(), + prev_y, + val_to_copy, + &[1], + )); + let new_y = self.id_gen.next(); + body.push(Instruction::unary( + spirv::Op::FNegate, + self.get_f32_type_id(), + new_y, + prev_y, + )); + let new_ptr_to_copy_to = self.id_gen.next(); + body.push(Instruction::access_chain( + self.get_f32_pointer_type_id(spirv::StorageClass::Output), + new_ptr_to_copy_to, + ptr_to_copy_to, + &[self.get_constant_scalar(crate::Literal::U32(1))], + )); + body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); + } + } + body + } + /// Writes the return call for a mesh shader, which involves copying previously /// written vertices/primitives into the actual output location. pub(super) fn write_mesh_shader_return( @@ -547,7 +755,10 @@ impl super::Writer { spirv::GLOp::UMin, u32_id, vert_count_id, - &[vert_count_id_before_max, return_info.max_vertices_constant], + &[ + vert_count_id_before_max, + return_info.vertex_info.max_length_constant, + ], )); let prim_count_id = self.id_gen.next(); @@ -558,14 +769,14 @@ impl super::Writer { prim_count_id, &[ prim_count_id_before_max, - return_info.max_primitives_constant, + return_info.primitive_info.max_length_constant, ], )); // Get pointers to the arrays of data to extract let vert_array_ptr = self.id_gen.next(); block.body.push(Instruction::access_chain( self.get_pointer_type_id( - return_info.vertex_array_type_id, + return_info.vertex_info.array_type_id, spirv::StorageClass::Workgroup, ), vert_array_ptr, @@ -581,7 +792,7 @@ impl super::Writer { let prim_array_ptr = self.id_gen.next(); block.body.push(Instruction::access_chain( self.get_pointer_type_id( - return_info.primitive_array_type_id, + return_info.primitive_info.array_type_id, spirv::StorageClass::Workgroup, ), prim_array_ptr, @@ -605,8 +816,6 @@ impl super::Writer { // This is iterating over every returned vertex and splitting // it out into the multiple per-output arrays. - let u32_type_id = self.get_u32_type_id(); - let zero_u32 = self.get_constant_scalar(crate::Literal::U32(0)); let vertex_loop_header = self.id_gen.next(); let prim_loop_header = self.id_gen.next(); let in_between_loops = self.id_gen.next(); @@ -620,251 +829,26 @@ impl super::Writer { )); block.body.push(Instruction::branch(vertex_loop_header)); - // This generates the instructions used to copy all parts of a single output vertex - // to their individual output locations - let vertex_copy_body = { - let mut body = Vec::new(); - // Current index to copy - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let vert_to_copy_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.vertex_type_id, - spirv::StorageClass::Workgroup, - ), - vert_to_copy_ptr, - vert_array_ptr, - &[val_i], - )); - - // Load the entire vertex value - let vert_to_copy = self.id_gen.next(); - body.push(Instruction::load( - return_info.vertex_type_id, - vert_to_copy, - vert_to_copy_ptr, - None, - )); - - let mut builtin_index = 0; - let mut binding_index = 0; - // Write individual members of the vertex - for (member_id, member) in return_info.vertex_members.iter().enumerate() { - let val_to_copy = self.id_gen.next(); - let mut needs_y_flip = false; - body.push(Instruction::composite_extract( - member.ty_id, - val_to_copy, - vert_to_copy, - &[member_id as u32], - )); - let ptr_to_copy_to = self.id_gen.next(); - // Get a pointer to the struct member to copy - match member.binding { - crate::Binding::BuiltIn(bi) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.vertex_builtin_block.unwrap(), - &[ - val_i, - self.get_constant_scalar(crate::Literal::U32(builtin_index)), - ], - )); - needs_y_flip = matches!(bi, crate::BuiltIn::Position { .. }) - && self.flags.contains(WriterFlags::ADJUST_COORDINATE_SPACE); - builtin_index += 1; - } - crate::Binding::Location { .. } => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.vertex_bindings[binding_index], - &[val_i, zero_u32], - )); - binding_index += 1; - } - } - body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); - // Flip the vertex position y coordinate in some cases - // Can't use epilogue flip because can't read from this storage class - if needs_y_flip { - let prev_y = self.id_gen.next(); - body.push(Instruction::composite_extract( - self.get_f32_type_id(), - prev_y, - val_to_copy, - &[1], - )); - let new_y = self.id_gen.next(); - body.push(Instruction::unary( - spirv::Op::FNegate, - self.get_f32_type_id(), - new_y, - prev_y, - )); - let new_ptr_to_copy_to = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_f32_pointer_type_id(spirv::StorageClass::Output), - new_ptr_to_copy_to, - ptr_to_copy_to, - &[self.get_constant_scalar(crate::Literal::U32(1))], - )); - body.push(Instruction::store(new_ptr_to_copy_to, new_y, None)); - } - } - body - }; - - // Primitive copies - // See comments in `vertex_copy_body` - let primitive_copy_body = { - let mut body = Vec::new(); - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let prim_to_copy_ptr = self.id_gen.next(); - body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.primitive_type_id, - spirv::StorageClass::Workgroup, - ), - prim_to_copy_ptr, - prim_array_ptr, - &[val_i], - )); - let prim_to_copy = self.id_gen.next(); - body.push(Instruction::load( - return_info.primitive_type_id, - prim_to_copy, - prim_to_copy_ptr, - None, - )); - - let mut builtin_index = 0; - let mut binding_index = 0; - for (member_id, member) in return_info.primitive_members.iter().enumerate() { - let val_to_copy = self.id_gen.next(); - body.push(Instruction::composite_extract( - member.ty_id, - val_to_copy, - prim_to_copy, - &[member_id as u32], - )); - let ptr_to_copy_to = self.id_gen.next(); - match member.binding { - crate::Binding::BuiltIn( - crate::BuiltIn::PointIndex - | crate::BuiltIn::LineIndices - | crate::BuiltIn::TriangleIndices, - ) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_indices.unwrap(), - &[val_i], - )); - } - crate::Binding::BuiltIn(_) => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_builtin_block.unwrap(), - &[ - val_i, - self.get_constant_scalar(crate::Literal::U32(builtin_index)), - ], - )); - builtin_index += 1; - } - crate::Binding::Location { .. } => { - body.push(Instruction::access_chain( - self.get_pointer_type_id(member.ty_id, spirv::StorageClass::Output), - ptr_to_copy_to, - return_info.primitive_bindings[binding_index], - &[val_i, zero_u32], - )); - binding_index += 1; - } - } - body.push(Instruction::store(ptr_to_copy_to, val_to_copy, None)); - } - body - }; + let vertex_copy_body = self.write_mesh_copy_body( + false, + return_info, + index_var, + vert_array_ptr, + prim_array_ptr, + ); - // This writes the actual loop - let mut write_loop = |body: &mut Vec, - mut loop_body_block, - loop_header, - loop_merge, - count_id, - index_var| { - let condition_check = self.id_gen.next(); - let loop_continue = self.id_gen.next(); - let loop_body = self.id_gen.next(); - - // Loop header - { - body.push(Instruction::label(loop_header)); - body.push(Instruction::loop_merge( - loop_merge, - loop_continue, - spirv::SelectionControl::empty(), - )); - body.push(Instruction::branch(condition_check)); - } - // Condition check - check if i is less than num vertices to copy - { - body.push(Instruction::label(condition_check)); - - let val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, val_i, index_var, None)); - - let cond = self.id_gen.next(); - body.push(Instruction::binary( - spirv::Op::ULessThan, - self.get_bool_type_id(), - cond, - val_i, - count_id, - )); - body.push(Instruction::branch_conditional(cond, loop_body, loop_merge)); - } - // Loop body - { - body.push(Instruction::label(loop_body)); - body.append(&mut loop_body_block); - body.push(Instruction::branch(loop_continue)); - } - // Loop continue - increment i - { - body.push(Instruction::label(loop_continue)); - - let prev_val_i = self.id_gen.next(); - body.push(Instruction::load(u32_type_id, prev_val_i, index_var, None)); - let new_val_i = self.id_gen.next(); - body.push(Instruction::binary( - spirv::Op::IAdd, - u32_type_id, - new_val_i, - prev_val_i, - return_info.workgroup_size, - )); - body.push(Instruction::store(index_var, new_val_i, None)); + let primitive_copy_body = + self.write_mesh_copy_body(true, return_info, index_var, vert_array_ptr, prim_array_ptr); - body.push(Instruction::branch(loop_header)); - } - }; // Write vertex copy loop - write_loop( + self.write_mesh_copy_loop( &mut block.body, vertex_copy_body, vertex_loop_header, in_between_loops, vert_count_id, index_var, + return_info, ); // In between loops, reset the initial index { @@ -879,13 +863,14 @@ impl super::Writer { block.body.push(Instruction::branch(prim_loop_header)); } // Write primitive copy loop - write_loop( + self.write_mesh_copy_loop( &mut block.body, primitive_copy_body, prim_loop_header, func_end, prim_count_id, index_var, + return_info, ); block.body.push(Instruction::label(func_end)); From 9840c36ca32450a3e6488ca65e8fd35ea0ad0f61 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 19:28:33 -0600 Subject: [PATCH 109/110] Another slight improvement --- naga/src/back/spv/mesh_shader.rs | 141 +++++++++++++------------------ 1 file changed, 61 insertions(+), 80 deletions(-) diff --git a/naga/src/back/spv/mesh_shader.rs b/naga/src/back/spv/mesh_shader.rs index fcb1d9b5bb8..d2911037b36 100644 --- a/naga/src/back/spv/mesh_shader.rs +++ b/naga/src/back/spv/mesh_shader.rs @@ -513,7 +513,7 @@ impl super::Writer { Ok(Instruction::return_void()) } - // This writes the actual loop + /// This writes the actual loop #[allow(clippy::too_many_arguments)] fn write_mesh_copy_loop( &mut self, @@ -712,8 +712,8 @@ impl super::Writer { return_info: &MeshReturnInfo, block: &mut Block, ) -> Result<(), Error> { + // Start with a control barrier so that everything that follows is guaranteed to see the same variables self.write_control_barrier(crate::Barrier::WORK_GROUP, block); - let u32_id = self.get_u32_type_id(); // This is the actual value (not pointer) @@ -721,91 +721,72 @@ impl super::Writer { let out_var_id = return_info.out_variable_id; // Load the actual vertex and primitive counts - let mut load_u32_by_member_index = |member_index: u32| { - let ptr_id = self.id_gen.next(); + let mut load_u32_by_member_index = + |members: &[MeshReturnMember], bi: crate::BuiltIn, max: u32| { + let member_index = members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(bi)) + .unwrap() as u32; + let ptr_id = self.id_gen.next(); + block.body.push(Instruction::access_chain( + self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), + ptr_id, + out_var_id, + &[self.get_constant_scalar(crate::Literal::U32(member_index))], + )); + let before_min_id = self.id_gen.next(); + block + .body + .push(Instruction::load(u32_id, before_min_id, ptr_id, None)); + + // Clamp the values + let id = self.id_gen.next(); + block.body.push(Instruction::ext_inst_gl_op( + self.gl450_ext_inst_id, + spirv::GLOp::UMin, + u32_id, + id, + &[before_min_id, max], + )); + id + }; + let vert_count_id = load_u32_by_member_index( + &return_info.out_members, + crate::BuiltIn::VertexCount, + return_info.vertex_info.max_length_constant, + ); + let prim_count_id = load_u32_by_member_index( + &return_info.out_members, + crate::BuiltIn::PrimitiveCount, + return_info.primitive_info.max_length_constant, + ); + + // Get pointers to the arrays of data to extract + let mut get_array_ptr = |bi: crate::BuiltIn, array_type_id: u32| { + let id = self.id_gen.next(); block.body.push(Instruction::access_chain( - self.get_pointer_type_id(u32_id, spirv::StorageClass::Workgroup), - ptr_id, - out_var_id, - &[self.get_constant_scalar(crate::Literal::U32(member_index))], + self.get_pointer_type_id(array_type_id, spirv::StorageClass::Workgroup), + id, + return_info.out_variable_id, + &[self.get_constant_scalar(crate::Literal::U32( + return_info + .out_members + .iter() + .position(|a| a.binding == crate::Binding::BuiltIn(bi)) + .unwrap() as u32, + ))], )); - let id = self.id_gen.next(); - block.body.push(Instruction::load(u32_id, id, ptr_id, None)); id }; - let vert_count_id_before_max = load_u32_by_member_index( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::VertexCount)) - .unwrap() as u32, + let vert_array_ptr = get_array_ptr( + crate::BuiltIn::Vertices, + return_info.vertex_info.array_type_id, ); - let prim_count_id_before_max = load_u32_by_member_index( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::PrimitiveCount)) - .unwrap() as u32, + let prim_array_ptr = get_array_ptr( + crate::BuiltIn::Primitives, + return_info.primitive_info.array_type_id, ); - // Clamp them to the allowed range - let vert_count_id = self.id_gen.next(); - block.body.push(Instruction::ext_inst_gl_op( - self.gl450_ext_inst_id, - spirv::GLOp::UMin, - u32_id, - vert_count_id, - &[ - vert_count_id_before_max, - return_info.vertex_info.max_length_constant, - ], - )); - let prim_count_id = self.id_gen.next(); - - block.body.push(Instruction::ext_inst_gl_op( - self.gl450_ext_inst_id, - spirv::GLOp::UMin, - u32_id, - prim_count_id, - &[ - prim_count_id_before_max, - return_info.primitive_info.max_length_constant, - ], - )); - // Get pointers to the arrays of data to extract - let vert_array_ptr = self.id_gen.next(); - block.body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.vertex_info.array_type_id, - spirv::StorageClass::Workgroup, - ), - vert_array_ptr, - return_info.out_variable_id, - &[self.get_constant_scalar(crate::Literal::U32( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Vertices)) - .unwrap() as u32, - ))], - )); - let prim_array_ptr = self.id_gen.next(); - block.body.push(Instruction::access_chain( - self.get_pointer_type_id( - return_info.primitive_info.array_type_id, - spirv::StorageClass::Workgroup, - ), - prim_array_ptr, - return_info.out_variable_id, - &[self.get_constant_scalar(crate::Literal::U32( - return_info - .out_members - .iter() - .position(|a| a.binding == crate::Binding::BuiltIn(crate::BuiltIn::Primitives)) - .unwrap() as u32, - ))], - )); - // This must be called exactly once before any other mesh outputs are written { let mut ins = Instruction::new(spirv::Op::SetMeshOutputsEXT); From b9cd262ccaa1682ca2237fcdb52c68d0c8dd5ff4 Mon Sep 17 00:00:00 2001 From: SupaMaggie70 Date: Wed, 26 Nov 2025 23:38:29 -0600 Subject: [PATCH 110/110] Updated snapshots --- naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm | 12 ++++++------ naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm | 14 +++++++------- naga/tests/out/spv/wgsl-mesh-shader-points.spvasm | 14 +++++++------- naga/tests/out/spv/wgsl-mesh-shader.spvasm | 12 ++++++------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm index 28fd0ff5b67..fd1a66129f9 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm @@ -104,13 +104,13 @@ OpBranch %55 OpControlBarrier %53 %53 %54 %56 = OpAccessChain %57 %17 %53 %58 = OpLoad %3 %56 -%59 = OpAccessChain %57 %17 %11 -%60 = OpLoad %3 %59 -%61 = OpExtInst %3 %1 UMin %58 %11 -%62 = OpExtInst %3 %1 UMin %60 %13 +%59 = OpExtInst %3 %1 UMin %58 %11 +%60 = OpAccessChain %57 %17 %11 +%61 = OpLoad %3 %60 +%62 = OpExtInst %3 %1 UMin %61 %13 %63 = OpAccessChain %64 %17 %65 %66 = OpAccessChain %67 %17 %13 -OpSetMeshOutputsEXT %61 %62 +OpSetMeshOutputsEXT %59 %62 OpStore %31 %30 OpBranch %68 %68 = OpLabel @@ -118,7 +118,7 @@ OpLoopMerge %70 %87 None OpBranch %86 %86 = OpLabel %89 = OpLoad %3 %31 -%90 = OpULessThan %48 %89 %61 +%90 = OpULessThan %48 %89 %59 OpBranchConditional %90 %88 %70 %88 = OpLabel %72 = OpLoad %3 %31 diff --git a/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm index d6edbf70d7b..7b3e3cb7324 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm @@ -67,7 +67,7 @@ OpDecorate %44 BuiltIn LocalInvocationId %48 = OpTypeVector %49 3 %54 = OpConstant %3 264 %57 = OpTypePointer Workgroup %3 -%60 = OpConstant %3 3 +%61 = OpConstant %3 3 %65 = OpTypePointer Workgroup %11 %66 = OpConstant %3 0 %68 = OpTypePointer Workgroup %13 @@ -105,13 +105,13 @@ OpBranch %55 OpControlBarrier %12 %12 %54 %56 = OpAccessChain %57 %18 %12 %58 = OpLoad %3 %56 -%59 = OpAccessChain %57 %18 %60 -%61 = OpLoad %3 %59 -%62 = OpExtInst %3 %1 UMin %58 %12 -%63 = OpExtInst %3 %1 UMin %61 %14 +%59 = OpExtInst %3 %1 UMin %58 %12 +%60 = OpAccessChain %57 %18 %61 +%62 = OpLoad %3 %60 +%63 = OpExtInst %3 %1 UMin %62 %14 %64 = OpAccessChain %65 %18 %66 %67 = OpAccessChain %68 %18 %14 -OpSetMeshOutputsEXT %62 %63 +OpSetMeshOutputsEXT %59 %63 OpStore %32 %31 OpBranch %69 %69 = OpLabel @@ -119,7 +119,7 @@ OpLoopMerge %71 %88 None OpBranch %87 %87 = OpLabel %90 = OpLoad %3 %32 -%91 = OpULessThan %49 %90 %62 +%91 = OpULessThan %49 %90 %59 OpBranchConditional %91 %89 %71 %89 = OpLabel %73 = OpLoad %3 %32 diff --git a/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm index 109d572df66..55a9bbe0b47 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader-points.spvasm @@ -66,7 +66,7 @@ OpDecorate %42 BuiltIn LocalInvocationId %52 = OpConstant %3 2 %53 = OpConstant %3 264 %56 = OpTypePointer Workgroup %3 -%59 = OpConstant %3 3 +%60 = OpConstant %3 3 %64 = OpTypePointer Workgroup %10 %65 = OpConstant %3 0 %67 = OpTypePointer Workgroup %12 @@ -104,13 +104,13 @@ OpBranch %54 OpControlBarrier %52 %52 %53 %55 = OpAccessChain %56 %16 %52 %57 = OpLoad %3 %55 -%58 = OpAccessChain %56 %16 %59 -%60 = OpLoad %3 %58 -%61 = OpExtInst %3 %1 UMin %57 %11 -%62 = OpExtInst %3 %1 UMin %60 %11 +%58 = OpExtInst %3 %1 UMin %57 %11 +%59 = OpAccessChain %56 %16 %60 +%61 = OpLoad %3 %59 +%62 = OpExtInst %3 %1 UMin %61 %11 %63 = OpAccessChain %64 %16 %65 %66 = OpAccessChain %67 %16 %11 -OpSetMeshOutputsEXT %61 %62 +OpSetMeshOutputsEXT %58 %62 OpStore %30 %29 OpBranch %68 %68 = OpLabel @@ -118,7 +118,7 @@ OpLoopMerge %70 %87 None OpBranch %86 %86 = OpLabel %89 = OpLoad %3 %30 -%90 = OpULessThan %47 %89 %61 +%90 = OpULessThan %47 %89 %58 OpBranchConditional %90 %88 %70 %88 = OpLabel %72 = OpLoad %3 %30 diff --git a/naga/tests/out/spv/wgsl-mesh-shader.spvasm b/naga/tests/out/spv/wgsl-mesh-shader.spvasm index ef5a79e131e..0408de0db7c 100644 --- a/naga/tests/out/spv/wgsl-mesh-shader.spvasm +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -223,13 +223,13 @@ OpStore %129 %89 OpControlBarrier %42 %42 %43 %130 = OpAccessChain %99 %21 %42 %131 = OpLoad %8 %130 -%132 = OpAccessChain %99 %21 %13 -%133 = OpLoad %8 %132 -%134 = OpExtInst %8 %1 UMin %131 %13 -%135 = OpExtInst %8 %1 UMin %133 %15 +%132 = OpExtInst %8 %1 UMin %131 %13 +%133 = OpAccessChain %99 %21 %13 +%134 = OpLoad %8 %133 +%135 = OpExtInst %8 %1 UMin %134 %15 %136 = OpAccessChain %102 %21 %46 %137 = OpAccessChain %120 %21 %15 -OpSetMeshOutputsEXT %134 %135 +OpSetMeshOutputsEXT %132 %135 OpStore %59 %56 OpBranch %138 %138 = OpLabel @@ -237,7 +237,7 @@ OpLoopMerge %140 %162 None OpBranch %161 %161 = OpLabel %164 = OpLoad %8 %59 -%165 = OpULessThan %5 %164 %134 +%165 = OpULessThan %5 %164 %132 OpBranchConditional %165 %163 %140 %163 = OpLabel %142 = OpLoad %8 %59