diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e5b9dc30c7..d4768b207fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -148,7 +148,6 @@ 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). -- The `STORAGE_READ_ONLY` texture usage is now permitted to coexist with other read-only usages. By @andyleiserson in [#8490](https://github.com/gfx-rs/wgpu/pull/8490). - 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). @@ -163,6 +162,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/8456). #### WebGPU diff --git a/examples/features/src/mesh_shader/mod.rs b/examples/features/src/mesh_shader/mod.rs index a0d2272363d..e598953f890 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!( @@ -83,21 +60,30 @@ impl crate::framework::Example for Example { device: &wgpu::Device, _queue: &wgpu::Queue, ) -> Self { - let (ts, ms, fs) = match adapter.get_info().backend { + let (ts, ms, fs, ts_name, ms_name, fs_name) = match adapter.get_info().backend { wgpu::Backend::Vulkan => ( - compile_glsl(device, "task"), - compile_glsl(device, "mesh"), - compile_glsl(device, "frag"), + compile_wgsl(device), + compile_wgsl(device), + compile_wgsl(device), + "ts_main", + "ms_main", + "fs_main", ), wgpu::Backend::Dx12 => ( compile_hlsl(device, "Task", "as"), compile_hlsl(device, "Mesh", "ms"), compile_hlsl(device, "Frag", "ps"), + "main", + "main", + "main", ), wgpu::Backend::Metal => ( compile_msl(device, "taskShader"), compile_msl(device, "meshShader"), compile_msl(device, "fragShader"), + "main", + "main", + "main", ), _ => panic!("Example can currently only run on vulkan, dx12 or metal"), }; @@ -111,17 +97,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())], }), @@ -199,6 +185,7 @@ pub fn main() { #[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, @@ -209,6 +196,6 @@ pub static TEST: crate::framework::ExampleTestParams = crate::framework::Example | wgpu::Features::EXPERIMENTAL_PASSTHROUGH_SHADERS, ) .limits(wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()), - comparisons: &[wgpu_test::ComparisonType::Mean(0.01)], + comparisons: &[wgpu_test::ComparisonType::Mean(0.005)], _phantom: std::marker::PhantomData::, }; diff --git a/examples/features/src/mesh_shader/shader.wgsl b/examples/features/src/mesh_shader/shader.wgsl new file mode 100644 index 00000000000..fb65d51a86e --- /dev/null +++ b/examples/features/src/mesh_shader/shader.wgsl @@ -0,0 +1,74 @@ +enable wgpu_mesh_shader; + +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/naga/src/back/spv/block.rs b/naga/src/back/spv/block.rs index ace9565a1cd..e94d5197acf 100644 --- a/naga/src/back/spv/block.rs +++ b/naga/src/back/spv/block.rs @@ -221,8 +221,13 @@ 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() { + // This isn't a real builtin, and is handled elsewhere + if res_member.built_in == Some(crate::BuiltIn::MeshTaskSize) { + continue; + } let member_value_id = match ir_result.binding { Some(_) => value_id, None => { @@ -253,7 +258,7 @@ impl Writer { _ => {} } } - Ok(()) + self.write_entry_point_task_return(value_id, ir_result, result_members, body, task_payload) } } @@ -3251,21 +3256,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_variable_id, + )?, 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)?; + }; 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..e31f9885325 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: u32, + 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/mesh_shader.rs b/naga/src/back/spv/mesh_shader.rs new file mode 100644 index 00000000000..d2911037b36 --- /dev/null +++ b/naga/src/back/spv/mesh_shader.rs @@ -0,0 +1,860 @@ +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, +} + +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. + 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 + 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 { + 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, + 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)); + 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_info + .struct_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_info.struct_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_info.builtin_block = Some(v); + } + // Write primitive builtin block + 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_info.struct_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_info.builtin_block = Some(v); + } + + // Write vertex binding output blocks (1 array per output struct member) + for member in &mesh_return_info.vertex_info.struct_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_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_info.struct_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_info.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()) + } + + /// 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( + &mut self, + 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) + // 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 = + |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(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, + ))], + )); + id + }; + let vert_array_ptr = get_array_ptr( + crate::BuiltIn::Vertices, + return_info.vertex_info.array_type_id, + ); + let prim_array_ptr = get_array_ptr( + crate::BuiltIn::Primitives, + return_info.primitive_info.array_type_id, + ); + + // 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 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)); + + let vertex_copy_body = self.write_mesh_copy_body( + false, + return_info, + index_var, + vert_array_ptr, + prim_array_ptr, + ); + + let primitive_copy_body = + self.write_mesh_copy_body(true, return_info, index_var, vert_array_ptr, prim_array_ptr); + + // Write vertex copy 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 + { + 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 + 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)); + Ok(()) + } +} diff --git a/naga/src/back/spv/mod.rs b/naga/src/back/spv/mod.rs index 3d356112b2d..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}; @@ -52,6 +54,7 @@ struct LogicalLayout { function_definitions: Vec, } +#[derive(Clone)] struct Instruction { op: spirv::Op, wc: u32, @@ -78,6 +81,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), } @@ -144,6 +149,8 @@ struct ResultMember { struct EntryPointContext { argument_ids: Vec, results: Vec, + task_payload_variable_id: Option, + mesh_state: Option, } #[derive(Default)] diff --git a/naga/src/back/spv/writer.rs b/naga/src/back/spv/writer.rs index 6180f8a599f..1174391e8eb 100644 --- a/naga/src/back/spv/writer.rs +++ b/naga/src/back/spv/writer.rs @@ -1,2826 +1,3058 @@ -use alloc::{string::String, vec, vec::Vec}; - -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::{BindingInfo, WrappedFunction}, - proc::{Alignment, TypeResolution}, - valid::{FunctionInfo, ModuleInfo}, -}; - -struct FunctionInterface<'a> { - varying_ids: &'a mut Vec, - stage: crate::ShaderStage, -} - -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(()) - } - - 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(), - }; - - let mut local_invocation_id = None; - - let mut parameter_type_ids = Vec::with_capacity(ir_function.arguments.len()); - 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); - } - - 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); - } - } - 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 = self.write_varying( - ir_module, - iface.stage, - class, - None, - result.ty, - binding, - )?; - iface.varying_ids.push(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); - let varying_id = self.write_varying( - ir_module, - iface.stage, - class, - name, - member.ty, - binding, - )?; - iface.varying_ids.push(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, - }; - - 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.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, - }), - 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 | crate::ShaderStage::Mesh => unreachable!(), - }; - //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 { - 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); - - 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)); - } - } - - use spirv::{BuiltIn, Decoration}; - - match *binding { - crate::Binding::Location { - location, - interpolation, - sampling, - blend_src, - per_primitive: _, - } => { - self.decorate(id, Decoration::Location, &[location]); - - 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) => { - self.decorate(id, Decoration::Flat, &[]); - } - Some(crate::Interpolation::Linear) => { - self.decorate(id, 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) => { - self.decorate(id, Decoration::Centroid, &[]); - } - Some(crate::Sampling::Sample) => { - self.require_any( - "per-sample interpolation", - &[spirv::Capability::SampleRateShading], - )?; - self.decorate(id, Decoration::Sample, &[]); - } - } - } - if let Some(blend_src) = blend_src { - self.decorate(id, Decoration::Index, &[blend_src]); - } - } - crate::Binding::BuiltIn(built_in) => { - use crate::BuiltIn as Bi; - let built_in = match built_in { - Bi::Position { invariant } => { - if invariant { - self.decorate(id, 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::MeshTaskSize - | Bi::CullPrimitive - | Bi::PointIndex - | Bi::LineIndices - | Bi::TriangleIndices - | Bi::VertexCount - | Bi::PrimitiveCount - | Bi::Vertices - | 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`: - // - // > 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 { - TypeInner::Scalar(scalar) | 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 { - self.decorate(id, Decoration::Flat, &[]); - } - } - } - } - - Ok(id) - } - - /// 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; - } - - 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(); - - 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); - } - 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}, + proc::{Alignment, TypeResolution}, + valid::{FunctionInfo, ModuleInfo}, +}; + +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 { + 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(()) + } + + 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; + 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; + 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; + 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; + } + + 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); +} diff --git a/naga/src/valid/mod.rs b/naga/src/valid/mod.rs index 8dcea0acffc..36d1e445641 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; } } @@ -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), Self::RAY_QUERY => Some(Ext::WgpuRayQuery), Self::RAY_HIT_VERTEX_POSITION => Some(Ext::WgpuRayQueryVertexReturn), _ => None, diff --git a/naga/tests/in/wgsl/mesh-shader-empty.toml b/naga/tests/in/wgsl/mesh-shader-empty.toml index 8500399f936..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-empty.toml +++ b/naga/tests/in/wgsl/mesh-shader-empty.toml @@ -1,2 +1,6 @@ god_mode = true -targets = "IR | ANALYSIS" +targets = "SPIRV" + +[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 8500399f936..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-lines.toml +++ b/naga/tests/in/wgsl/mesh-shader-lines.toml @@ -1,2 +1,6 @@ god_mode = true -targets = "IR | ANALYSIS" +targets = "SPIRV" + +[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 8500399f936..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader-points.toml +++ b/naga/tests/in/wgsl/mesh-shader-points.toml @@ -1,2 +1,6 @@ god_mode = true -targets = "IR | ANALYSIS" +targets = "SPIRV" + +[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 8500399f936..78c53a2415e 100644 --- a/naga/tests/in/wgsl/mesh-shader.toml +++ b/naga/tests/in/wgsl/mesh-shader.toml @@ -1,2 +1,6 @@ god_mode = true -targets = "IR | ANALYSIS" +targets = "SPIRV" + +[spv] +version = [1, 4] +capabilities = ["MeshShadingEXT"] 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-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-mesh-shader-empty.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-empty.spvasm new file mode 100644 index 00000000000..fd1a66129f9 --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader-empty.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 %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 %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 +%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 +%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 +%23 = OpLabel +%24 = OpCompositeExtract %3 %22 0 +%25 = OpCompositeExtract %3 %22 1 +%26 = OpCompositeExtract %3 %22 2 +OpEmitMeshTasksEXT %24 %25 %26 %15 +OpFunctionEnd +%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 +OpControlBarrier %53 %53 %54 +%56 = OpAccessChain %57 %17 %53 +%58 = OpLoad %3 %56 +%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 %59 %62 +OpStore %31 %30 +OpBranch %68 +%68 = OpLabel +OpLoopMerge %70 %87 None +OpBranch %86 +%86 = OpLabel +%89 = OpLoad %3 %31 +%90 = OpULessThan %48 %89 %59 +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 %69 +%69 = OpLabel +OpLoopMerge %71 %94 None +OpBranch %93 +%93 = OpLabel +%96 = OpLoad %3 %31 +%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-lines.spvasm b/naga/tests/out/spv/wgsl-mesh-shader-lines.spvasm new file mode 100644 index 00000000000..7b3e3cb7324 --- /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 +%61 = 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 = 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 %59 %63 +OpStore %32 %31 +OpBranch %69 +%69 = OpLabel +OpLoopMerge %71 %88 None +OpBranch %87 +%87 = OpLabel +%90 = OpLoad %3 %32 +%91 = OpULessThan %49 %90 %59 +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..55a9bbe0b47 --- /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 +%60 = 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 = 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 %58 %62 +OpStore %30 %29 +OpBranch %68 +%68 = OpLabel +OpLoopMerge %70 %87 None +OpBranch %86 +%86 = OpLabel +%89 = OpLoad %3 %30 +%90 = OpULessThan %47 %89 %58 +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 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..0408de0db7c --- /dev/null +++ b/naga/tests/out/spv/wgsl-mesh-shader.spvasm @@ -0,0 +1,304 @@ +; SPIR-V +; Version: 1.4 +; Generator: rspirv +; Bound: 191 +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 %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 %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 +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 %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 +%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 %15 %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 +%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 +%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 +OpControlBarrier %42 %42 %43 +%130 = OpAccessChain %99 %21 %42 +%131 = OpLoad %8 %130 +%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 %132 %135 +OpStore %59 %56 +OpBranch %138 +%138 = OpLabel +OpLoopMerge %140 %162 None +OpBranch %161 +%161 = OpLabel +%164 = OpLoad %8 %59 +%165 = OpULessThan %5 %164 %132 +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 %139 +%139 = OpLabel +OpLoopMerge %141 %169 None +OpBranch %168 +%168 = OpLabel +%171 = OpLoad %8 %59 +%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 +%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 diff --git a/tests/tests/wgpu-gpu/mesh_shader/mod.rs b/tests/tests/wgpu-gpu/mesh_shader/mod.rs index 161c49de569..f291beb05ff 100644 --- a/tests/tests/wgpu-gpu/mesh_shader/mod.rs +++ b/tests/tests/wgpu-gpu/mesh_shader/mod.rs @@ -1,11 +1,8 @@ -use std::{ - hash::{DefaultHasher, Hash, Hasher}, - process::Stdio, -}; +use std::hash::{DefaultHasher, Hash, Hasher}; use wgpu::util::DeviceExt; use wgpu_test::{ - fail, gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, + gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, }; pub fn all_tests(tests: &mut Vec) { @@ -19,39 +16,16 @@ 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, ]); } // 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, @@ -115,47 +89,40 @@ 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 - // 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.) - let dummy_shader = device.create_shader_module(wgpu::include_wgsl!("non_mesh.wgsl")); + // In the case that the platform does support mesh shaders, the dummy + // shader is used to avoid requiring EXPERIMENTAL_PASSTHROUGH_SHADERS. match backend { wgpu::Backend::Vulkan => ( - info.use_task.then(|| compile_glsl(device, "task")), - if info.use_mesh { - compile_glsl(device, "mesh") - } else { - dummy_shader - }, - info.use_frag.then(|| compile_glsl(device, "frag")), + info.use_task.then(|| compile_wgsl(device)), + compile_wgsl(device), + info.use_frag.then(|| compile_wgsl(device)), + "ts_main", + "ms_main", + "fs_main", ), wgpu::Backend::Dx12 => ( 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", + "main", + "main", ), wgpu::Backend::Metal => ( info.use_task.then(|| compile_msl(device, "taskShader")), - if info.use_mesh { - compile_msl(device, "meshShader") - } else { - dummy_shader - }, + compile_msl(device, "meshShader"), info.use_frag.then(|| compile_msl(device, "fragShader")), + "main", + "main", + "main", ), - _ => { - assert!(!info.use_task && !info.use_mesh && !info.use_frag); - (None, dummy_shader, None) - } + _ => unreachable!(), } } @@ -190,7 +157,6 @@ fn create_depth( struct MeshPipelineTestInfo { use_task: bool, - use_mesh: bool, use_frag: bool, draw: bool, } @@ -207,7 +173,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: &[], @@ -218,17 +185,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(), }), @@ -289,11 +256,11 @@ 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, }; - 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 { @@ -306,17 +273,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(), }), @@ -393,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 @@ -415,7 +381,6 @@ pub static MESH_PIPELINE_BASIC_MESH: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: false, draw: true, }, @@ -428,7 +393,6 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: false, draw: true, }, @@ -441,7 +405,6 @@ pub static MESH_PIPELINE_BASIC_MESH_FRAG: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: true, draw: true, }, @@ -454,7 +417,6 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH_FRAG: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: true, - use_mesh: true, use_frag: true, draw: true, }, @@ -467,7 +429,6 @@ pub static MESH_PIPELINE_BASIC_MESH_NO_DRAW: GpuTestConfiguration = &ctx, MeshPipelineTestInfo { use_task: false, - use_mesh: true, use_frag: false, draw: false, }, @@ -480,7 +441,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, }, @@ -503,30 +463,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().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", - ]), - ) -}); diff --git a/tests/tests/wgpu-gpu/mesh_shader/non_mesh.wgsl b/tests/tests/wgpu-gpu/mesh_shader/non_mesh.wgsl deleted file mode 100644 index f84ccfe94da..00000000000 --- a/tests/tests/wgpu-gpu/mesh_shader/non_mesh.wgsl +++ /dev/null @@ -1,11 +0,0 @@ -@vertex -fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4 { - let x = f32(i32(in_vertex_index) - 1); - let y = f32(i32(in_vertex_index & 1u) * 2 - 1); - return vec4(x, y, 0.0, 1.0); -} - -@fragment -fn fs_main() -> @location(0) vec4 { - return vec4(1.0, 0.0, 0.0, 1.0); -} 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..48a8bfe6e3f --- /dev/null +++ b/tests/tests/wgpu-gpu/mesh_shader/shader.wgsl @@ -0,0 +1,95 @@ +enable wgpu_mesh_shader; + +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); +} +// 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; +}