diff --git a/src/back/spv/block.rs b/src/back/spv/block.rs index 8366df415a..37c78bdf9b 100644 --- a/src/back/spv/block.rs +++ b/src/back/spv/block.rs @@ -3,8 +3,9 @@ Implementations for `BlockContext` methods. */ use super::{ - index::BoundsCheckResult, make_local, selection::Selection, Block, BlockContext, Dimension, - Error, Instruction, LocalType, LookupType, LoopContext, ResultMember, Writer, WriterFlags, + helpers, index::BoundsCheckResult, make_local, selection::Selection, Block, BlockContext, + Dimension, Error, Instruction, LocalType, LookupType, LoopContext, ResultMember, Writer, + WriterFlags, }; use crate::{arena::Handle, proc::TypeResolution}; use spirv::Word; @@ -196,9 +197,8 @@ impl<'w> BlockContext<'w> { fn is_intermediate(&self, expr_handle: Handle) -> bool { match self.ir_function.expressions[expr_handle] { crate::Expression::GlobalVariable(handle) => { - let ty = self.ir_module.global_variables[handle].ty; - match self.ir_module.types[ty].inner { - crate::TypeInner::BindingArray { .. } => false, + match self.ir_module.global_variables[handle].space { + crate::AddressSpace::Handle => false, _ => true, } } @@ -221,7 +221,6 @@ impl<'w> BlockContext<'w> { block: &mut Block, ) -> Result<(), Error> { let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty); - let id = match self.ir_function.expressions[expr_handle] { crate::Expression::Access { base, index: _ } if self.is_intermediate(base) => { // See `is_intermediate`; we'll handle this later in @@ -237,9 +236,15 @@ impl<'w> BlockContext<'w> { crate::TypeInner::BindingArray { base: binding_type, .. } => { + let space = match self.ir_function.expressions[base] { + crate::Expression::GlobalVariable(gvar) => { + self.ir_module.global_variables[gvar].space + } + _ => unreachable!(), + }; let binding_array_false_pointer = LookupType::Local(LocalType::Pointer { base: binding_type, - class: spirv::StorageClass::UniformConstant, + class: helpers::map_storage_class(space), }); let result_id = match self.write_expression_pointer( @@ -265,15 +270,6 @@ impl<'w> BlockContext<'w> { None, )); - if self.fun_info[index].uniformity.non_uniform_result.is_some() { - self.writer.require_any( - "NonUniformEXT", - &[spirv::Capability::ShaderNonUniform], - )?; - self.writer.use_extension("SPV_EXT_descriptor_indexing"); - self.writer - .decorate(load_id, spirv::Decoration::NonUniform, &[]); - } load_id } ref other => { @@ -316,9 +312,15 @@ impl<'w> BlockContext<'w> { crate::TypeInner::BindingArray { base: binding_type, .. } => { + let space = match self.ir_function.expressions[base] { + crate::Expression::GlobalVariable(gvar) => { + self.ir_module.global_variables[gvar].space + } + _ => unreachable!(), + }; let binding_array_false_pointer = LookupType::Local(LocalType::Pointer { base: binding_type, - class: spirv::StorageClass::UniformConstant, + class: helpers::map_storage_class(space), }); let result_id = match self.write_expression_pointer( @@ -1403,8 +1405,8 @@ impl<'w> BlockContext<'w> { /// Emit any needed bounds-checking expressions to `block`. /// /// Some cases we need to generate a different return type than what the IR gives us. - /// This is because pointers to binding arrays don't exist in the IR, but we need to - /// create them to create an access chain in SPIRV. + /// This is because pointers to binding arrays of handles (such as images or samplers) + /// don't exist in the IR, but we need to create them to create an access chain in SPIRV. /// /// On success, the return value is an [`ExpressionPointer`] value; see the /// documentation for that type. @@ -1434,11 +1436,25 @@ impl<'w> BlockContext<'w> { // but we expect these checks to almost always succeed, and keeping branches to a // minimum is essential. let mut accumulated_checks = None; + // Is true if we are accessing into a binding array of buffers with a non-uniform index. + let mut is_non_uniform_binding_array = false; self.temp_list.clear(); let root_id = loop { expr_handle = match self.ir_function.expressions[expr_handle] { crate::Expression::Access { base, index } => { + if let crate::Expression::GlobalVariable(var_handle) = + self.ir_function.expressions[base] + { + let gvar = &self.ir_module.global_variables[var_handle]; + if let crate::TypeInner::BindingArray { .. } = + self.ir_module.types[gvar.ty].inner + { + is_non_uniform_binding_array |= + self.fun_info[index].uniformity.non_uniform_result.is_some(); + } + } + let index_id = match self.write_bounds_check(base, index, block)? { BoundsCheckResult::KnownInBounds(known_index) => { // Even if the index is known, `OpAccessIndex` @@ -1471,7 +1487,6 @@ impl<'w> BlockContext<'w> { } }; self.temp_list.push(index_id); - base } crate::Expression::AccessIndex { base, index } => { @@ -1494,10 +1509,13 @@ impl<'w> BlockContext<'w> { } }; - let pointer = if self.temp_list.is_empty() { - ExpressionPointer::Ready { - pointer_id: root_id, - } + let (pointer_id, expr_pointer) = if self.temp_list.is_empty() { + ( + root_id, + ExpressionPointer::Ready { + pointer_id: root_id, + }, + ) } else { self.temp_list.reverse(); let pointer_id = self.gen_id(); @@ -1508,16 +1526,21 @@ impl<'w> BlockContext<'w> { // caller to generate the branch, the access, the load or store, and // the zero value (for loads). Otherwise, we can emit the access // ourselves, and just hand them the id of the pointer. - match accumulated_checks { + let expr_pointer = match accumulated_checks { Some(condition) => ExpressionPointer::Conditional { condition, access }, None => { block.body.push(access); ExpressionPointer::Ready { pointer_id } } - } + }; + (pointer_id, expr_pointer) }; + if is_non_uniform_binding_array { + self.writer + .decorate_non_uniform_binding_array_access(pointer_id)?; + } - Ok(pointer) + Ok(expr_pointer) } /// Build the instructions for matrix - matrix column operations diff --git a/src/back/spv/helpers.rs b/src/back/spv/helpers.rs index 1ef0db1912..5b6226db85 100644 --- a/src/back/spv/helpers.rs +++ b/src/back/spv/helpers.rs @@ -102,7 +102,8 @@ pub fn global_needs_wrapper(ir_module: &crate::Module, var: &crate::GlobalVariab }, None => false, }, - // if it's not a structure, let's wrap it to be able to put "Block" + crate::TypeInner::BindingArray { .. } => false, + // if it's not a structure or a binding array, let's wrap it to be able to put "Block" _ => true, } } diff --git a/src/back/spv/mod.rs b/src/back/spv/mod.rs index 9b084911b1..adc3dd7a7a 100644 --- a/src/back/spv/mod.rs +++ b/src/back/spv/mod.rs @@ -288,9 +288,15 @@ enum LocalType { image_type_id: Word, }, Sampler, + /// Equivalent to a [`LocalType::Pointer`] whose `base` is a Naga IR [`BindingArray`]. SPIR-V + /// permits duplicated `OpTypePointer` ids, so it's fine to have two different [`LocalType`] + /// representations for pointer types. + /// + /// [`BindingArray`]: crate::TypeInner::BindingArray PointerToBindingArray { base: Handle, size: u64, + space: crate::AddressSpace, }, BindingArray { base: Handle, diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs index ba235e6d03..90c6b5089d 100644 --- a/src/back/spv/writer.rs +++ b/src/back/spv/writer.rs @@ -940,10 +940,11 @@ impl Writer { let scalar_id = self.get_constant_scalar(crate::ScalarValue::Uint(size), 4); Instruction::type_array(id, inner_ty, scalar_id) } - LocalType::PointerToBindingArray { base, size } => { + LocalType::PointerToBindingArray { base, size, space } => { let inner_ty = self.get_type_id(LookupType::Local(LocalType::BindingArray { base, size })); - Instruction::type_pointer(id, spirv::StorageClass::UniformConstant, inner_ty) + let class = map_storage_class(space); + Instruction::type_pointer(id, class, inner_ty) } LocalType::AccelerationStructure => Instruction::type_acceleration_structure(id), LocalType::RayQuery => Instruction::type_ray_query(id), @@ -1579,6 +1580,9 @@ impl Writer { } } + // 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 { self.decorate(id, Decoration::DescriptorSet, &[res_binding.group]); @@ -1595,6 +1599,7 @@ impl Writer { Some(LookupType::Local(LocalType::PointerToBindingArray { base, size: remapped_binding_array_size as u64, + space: global_variable.space, })) } } else { @@ -1635,7 +1640,13 @@ impl Writer { // a runtime-sized array. In this case, we need to decorate it with // Block. if let crate::AddressSpace::Storage { .. } = global_variable.space { - self.decorate(inner_type_id, Decoration::Block, &[]); + let decorated_id = match ir_module.types[global_variable.ty].inner { + crate::TypeInner::BindingArray { base, .. } => { + self.get_type_id(LookupType::Handle(base)) + } + _ => inner_type_id, + }; + self.decorate(decorated_id, Decoration::Block, &[]); } if substitute_inner_type_lookup.is_some() { inner_type_id @@ -1955,6 +1966,13 @@ impl Writer { pub const fn get_capabilities_used(&self) -> &crate::FastHashSet { &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(()) + } } #[test] diff --git a/src/lib.rs b/src/lib.rs index a70015d16d..e744b7d40d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -200,7 +200,8 @@ tree. clippy::match_like_matches_macro, clippy::collapsible_if, clippy::derive_partial_eq_without_eq, - clippy::needless_borrowed_reference + clippy::needless_borrowed_reference, + clippy::single_match )] #![warn( trivial_casts, @@ -752,13 +753,12 @@ pub enum TypeInner { /// buffers could have elements that are dynamically sized arrays, each with /// a different length. /// - /// Binding arrays are not [`DATA`]. This means that all binding array - /// globals must be placed in the [`Handle`] address space. Referring to - /// such a global produces a `BindingArray` value directly; there are never - /// pointers to binding arrays. The only operation permitted on - /// `BindingArray` values is indexing, which yields the element by value, - /// not a pointer to the element. (This means that buffer array contents - /// cannot be stored to; [naga#1864] covers lifting this restriction.) + /// Binding arrays are in the same address spaces as their underlying type. + /// As such, referring to an array of images produces an [`Image`] value + /// directly (as opposed to a pointer). The only operation permitted on + /// `BindingArray` values is indexing, which works transparently: indexing + /// a binding array of samplers yields a [`Sampler`], indexing a pointer to the + /// binding array of storage buffers produces a pointer to the storage struct. /// /// Unlike textures and samplers, binding arrays are not [`ARGUMENT`], so /// they cannot be passed as arguments to functions. @@ -774,7 +774,6 @@ pub enum TypeInner { /// [`SamplerArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.SamplerArray /// [`BufferArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.BufferArray /// [`DATA`]: crate::valid::TypeFlags::DATA - /// [`Handle`]: AddressSpace::Handle /// [`ARGUMENT`]: crate::valid::TypeFlags::ARGUMENT /// [naga#1864]: https://github.com/gfx-rs/naga/issues/1864 BindingArray { base: Handle, size: ArraySize }, diff --git a/src/proc/index.rs b/src/proc/index.rs index 3fea79ec01..aaf8ee11b7 100644 --- a/src/proc/index.rs +++ b/src/proc/index.rs @@ -390,7 +390,9 @@ impl crate::TypeInner { match *base_inner { Ti::Vector { size, .. } => size as _, Ti::Matrix { columns, .. } => columns as _, - Ti::Array { size, .. } => return size.to_indexable_length(module), + Ti::Array { size, .. } | Ti::BindingArray { size, .. } => { + return size.to_indexable_length(module) + } _ => return Err(IndexableLengthError::TypeNotIndexable), } } diff --git a/src/proc/typifier.rs b/src/proc/typifier.rs index 0bb9019a29..ad77b0ed6c 100644 --- a/src/proc/typifier.rs +++ b/src/proc/typifier.rs @@ -298,6 +298,7 @@ impl<'a> ResolveContext<'a> { width, space, }, + Ti::BindingArray { base, .. } => Ti::Pointer { base, space }, ref other => { log::error!("Access sub-type {:?}", other); return Err(ResolveError::InvalidSubAccess { @@ -401,6 +402,7 @@ impl<'a> ResolveContext<'a> { space, } } + Ti::BindingArray { base, .. } => Ti::Pointer { base, space }, ref other => { log::error!("Access index sub-type {:?}", other); return Err(ResolveError::InvalidSubAccess { diff --git a/src/valid/interface.rs b/src/valid/interface.rs index 268490c52e..1fafef2f66 100644 --- a/src/valid/interface.rs +++ b/src/valid/interface.rs @@ -400,7 +400,14 @@ impl super::Validator { use super::TypeFlags; log::debug!("var {:?}", var); - let type_info = &self.types[var.ty.index()]; + let inner_ty = match types[var.ty].inner { + // A binding array is (mostly) supposed to behave the same as a + // series of individually bound resources, so we can (mostly) + // validate a `binding_array` as if it were just a plain `T`. + crate::TypeInner::BindingArray { base, .. } => base, + _ => var.ty, + }; + let type_info = &self.types[inner_ty.index()]; let (required_type_flags, is_resource) = match var.space { crate::AddressSpace::Function => { @@ -437,22 +444,8 @@ impl super::Validator { ) } crate::AddressSpace::Handle => { - match types[var.ty].inner { - crate::TypeInner::Image { .. } - | crate::TypeInner::Sampler { .. } - | crate::TypeInner::BindingArray { .. } - | crate::TypeInner::AccelerationStructure - | crate::TypeInner::RayQuery => {} - _ => { - return Err(GlobalVariableError::InvalidType(var.space)); - } - }; - let inner_ty = match &types[var.ty].inner { - &crate::TypeInner::BindingArray { base, .. } => &types[base].inner, - ty => ty, - }; - if let crate::TypeInner::Image { - class: + match types[inner_ty].inner { + crate::TypeInner::Image { class, .. } => match class { crate::ImageClass::Storage { format: crate::StorageFormat::R16Unorm @@ -462,17 +455,23 @@ impl super::Validator { | crate::StorageFormat::Rgba16Unorm | crate::StorageFormat::Rgba16Snorm, .. - }, - .. - } = *inner_ty - { - if !self - .capabilities - .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) - { - return Err(GlobalVariableError::UnsupportedCapability( - Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS, - )); + } => { + if !self + .capabilities + .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) + { + return Err(GlobalVariableError::UnsupportedCapability( + Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS, + )); + } + } + _ => {} + }, + crate::TypeInner::Sampler { .. } + | crate::TypeInner::AccelerationStructure + | crate::TypeInner::RayQuery => {} + _ => { + return Err(GlobalVariableError::InvalidType(var.space)); } } diff --git a/src/valid/type.rs b/src/valid/type.rs index d8dd37d09b..33940f22e7 100644 --- a/src/valid/type.rs +++ b/src/valid/type.rs @@ -119,6 +119,8 @@ pub enum TypeError { InvalidArrayStride { stride: u32, expected: u32 }, #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")] InvalidDynamicArray(String, Handle), + #[error("The base handle {0:?} has to be a struct")] + BindingArrayBaseTypeNotStruct(Handle), #[error("Structure member[{index}] at {offset} overlaps the previous member")] MemberOverlap { index: u32, offset: u32 }, #[error( @@ -624,13 +626,35 @@ impl super::Validator { } Ti::AccelerationStructure => { self.require_type_capability(Capabilities::RAY_QUERY)?; - TypeInfo::new(TypeFlags::empty(), Alignment::ONE) + TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE) } Ti::RayQuery => { self.require_type_capability(Capabilities::RAY_QUERY)?; TypeInfo::new(TypeFlags::DATA | TypeFlags::SIZED, Alignment::ONE) } - Ti::BindingArray { .. } => TypeInfo::new(TypeFlags::empty(), Alignment::ONE), + Ti::BindingArray { base, size } => { + if base >= handle { + return Err(TypeError::InvalidArrayBaseType(base)); + } + let type_info_mask = match size { + crate::ArraySize::Constant(_) => TypeFlags::SIZED | TypeFlags::HOST_SHAREABLE, + crate::ArraySize::Dynamic => { + // Final type is non-sized + TypeFlags::HOST_SHAREABLE + } + }; + let base_info = &self.types[base.index()]; + + if base_info.flags.contains(TypeFlags::DATA) { + // Currently Naga only supports binding arrays of structs for non-handle types. + match types[base].inner { + crate::TypeInner::Struct { .. } => {} + _ => return Err(TypeError::BindingArrayBaseTypeNotStruct(base)), + }; + } + + TypeInfo::new(base_info.flags & type_info_mask, Alignment::ONE) + } }) } } diff --git a/tests/in/binding-buffer-arrays.param.ron b/tests/in/binding-buffer-arrays.param.ron new file mode 100644 index 0000000000..4f653bb21b --- /dev/null +++ b/tests/in/binding-buffer-arrays.param.ron @@ -0,0 +1,14 @@ +( + god_mode: false, + spv: ( + version: (1, 1), + binding_map: { + (group: 0, binding: 0): (binding_array_size: Some(10)), + }, + ), + bounds_check_policies: ( + index: ReadZeroSkipWrite, + buffer: ReadZeroSkipWrite, + image: ReadZeroSkipWrite, + ) +) diff --git a/tests/in/binding-buffer-arrays.wgsl b/tests/in/binding-buffer-arrays.wgsl new file mode 100644 index 0000000000..a76d52c200 --- /dev/null +++ b/tests/in/binding-buffer-arrays.wgsl @@ -0,0 +1,27 @@ +struct UniformIndex { + index: u32 +} + +struct Foo { x: u32 } +@group(0) @binding(0) +var storage_array: binding_array; +@group(0) @binding(10) +var uni: UniformIndex; + +struct FragmentIn { + @location(0) index: u32, +} + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) u32 { + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + + var u1 = 0u; + + u1 += storage_array[0].x; + u1 += storage_array[uniform_index].x; + u1 += storage_array[non_uniform_index].x; + + return u1; +} diff --git a/tests/out/spv/binding-arrays.spvasm b/tests/out/spv/binding-arrays.spvasm index 30cf4847ce..31929d155b 100644 --- a/tests/out/spv/binding-arrays.spvasm +++ b/tests/out/spv/binding-arrays.spvasm @@ -35,28 +35,28 @@ OpMemberDecorate %49 0 Offset 0 OpDecorate %65 Location 0 OpDecorate %65 Flat OpDecorate %68 Location 0 -OpDecorate %97 NonUniform -OpDecorate %121 NonUniform -OpDecorate %123 NonUniform -OpDecorate %148 NonUniform -OpDecorate %150 NonUniform -OpDecorate %188 NonUniform -OpDecorate %219 NonUniform -OpDecorate %238 NonUniform -OpDecorate %257 NonUniform -OpDecorate %279 NonUniform -OpDecorate %281 NonUniform -OpDecorate %303 NonUniform -OpDecorate %305 NonUniform -OpDecorate %327 NonUniform -OpDecorate %329 NonUniform -OpDecorate %351 NonUniform -OpDecorate %353 NonUniform -OpDecorate %375 NonUniform -OpDecorate %377 NonUniform -OpDecorate %399 NonUniform -OpDecorate %401 NonUniform -OpDecorate %424 NonUniform +OpDecorate %96 NonUniform +OpDecorate %120 NonUniform +OpDecorate %122 NonUniform +OpDecorate %147 NonUniform +OpDecorate %149 NonUniform +OpDecorate %187 NonUniform +OpDecorate %218 NonUniform +OpDecorate %237 NonUniform +OpDecorate %256 NonUniform +OpDecorate %278 NonUniform +OpDecorate %280 NonUniform +OpDecorate %302 NonUniform +OpDecorate %304 NonUniform +OpDecorate %326 NonUniform +OpDecorate %328 NonUniform +OpDecorate %350 NonUniform +OpDecorate %352 NonUniform +OpDecorate %374 NonUniform +OpDecorate %376 NonUniform +OpDecorate %398 NonUniform +OpDecorate %400 NonUniform +OpDecorate %423 NonUniform %2 = OpTypeVoid %4 = OpTypeInt 32 1 %3 = OpConstant %4 5 diff --git a/tests/out/spv/binding-buffer-arrays.spvasm b/tests/out/spv/binding-buffer-arrays.spvasm new file mode 100644 index 0000000000..cd73bd756c --- /dev/null +++ b/tests/out/spv/binding-buffer-arrays.spvasm @@ -0,0 +1,105 @@ +; SPIR-V +; Version: 1.1 +; Generator: rspirv +; Bound: 66 +OpCapability Shader +OpCapability ShaderNonUniform +OpExtension "SPV_KHR_storage_buffer_storage_class" +OpExtension "SPV_EXT_descriptor_indexing" +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %29 "main" %24 %27 +OpExecutionMode %29 OriginUpperLeft +OpMemberDecorate %8 0 Offset 0 +OpMemberDecorate %9 0 Offset 0 +OpMemberDecorate %11 0 Offset 0 +OpDecorate %12 NonWritable +OpDecorate %12 DescriptorSet 0 +OpDecorate %12 Binding 0 +OpDecorate %9 Block +OpDecorate %16 DescriptorSet 0 +OpDecorate %16 Binding 10 +OpDecorate %17 Block +OpMemberDecorate %17 0 Offset 0 +OpDecorate %24 Location 0 +OpDecorate %24 Flat +OpDecorate %27 Location 0 +OpDecorate %57 NonUniform +%2 = OpTypeVoid +%4 = OpTypeInt 32 1 +%3 = OpConstant %4 1 +%6 = OpTypeInt 32 0 +%5 = OpConstant %6 0 +%7 = OpConstant %4 0 +%8 = OpTypeStruct %6 +%9 = OpTypeStruct %6 +%10 = OpTypeArray %9 %3 +%11 = OpTypeStruct %6 +%15 = OpConstant %6 10 +%14 = OpTypeArray %9 %15 +%13 = OpTypePointer StorageBuffer %14 +%12 = OpVariable %13 StorageBuffer +%17 = OpTypeStruct %8 +%18 = OpTypePointer Uniform %17 +%16 = OpVariable %18 Uniform +%20 = OpTypePointer Function %6 +%21 = OpConstantNull %6 +%25 = OpTypePointer Input %6 +%24 = OpVariable %25 Input +%28 = OpTypePointer Output %6 +%27 = OpVariable %28 Output +%30 = OpTypeFunction %2 +%31 = OpTypePointer Uniform %8 +%33 = OpTypePointer StorageBuffer %10 +%35 = OpTypePointer Uniform %6 +%39 = OpTypePointer StorageBuffer %9 +%40 = OpTypePointer StorageBuffer %6 +%45 = OpConstant %6 1 +%47 = OpTypeBool +%49 = OpConstantNull %6 +%58 = OpConstantNull %6 +%29 = OpFunction %2 None %30 +%22 = OpLabel +%19 = OpVariable %20 Function %21 +%26 = OpLoad %6 %24 +%23 = OpCompositeConstruct %11 %26 +%32 = OpAccessChain %31 %16 %5 +OpBranch %34 +%34 = OpLabel +%36 = OpAccessChain %35 %32 %5 +%37 = OpLoad %6 %36 +%38 = OpCompositeExtract %6 %23 0 +OpStore %19 %5 +%41 = OpAccessChain %40 %12 %5 %5 +%42 = OpLoad %6 %41 +%43 = OpLoad %6 %19 +%44 = OpIAdd %6 %43 %42 +OpStore %19 %44 +%46 = OpULessThan %47 %37 %45 +OpSelectionMerge %50 None +OpBranchConditional %46 %51 %50 +%51 = OpLabel +%48 = OpAccessChain %40 %12 %37 %5 +%52 = OpLoad %6 %48 +OpBranch %50 +%50 = OpLabel +%53 = OpPhi %6 %49 %34 %52 %51 +%54 = OpLoad %6 %19 +%55 = OpIAdd %6 %54 %53 +OpStore %19 %55 +%56 = OpULessThan %47 %38 %45 +OpSelectionMerge %59 None +OpBranchConditional %56 %60 %59 +%60 = OpLabel +%57 = OpAccessChain %40 %12 %38 %5 +%61 = OpLoad %6 %57 +OpBranch %59 +%59 = OpLabel +%62 = OpPhi %6 %58 %50 %61 %60 +%63 = OpLoad %6 %19 +%64 = OpIAdd %6 %63 %62 +OpStore %19 %64 +%65 = OpLoad %6 %19 +OpStore %27 %65 +OpReturn +OpFunctionEnd \ No newline at end of file diff --git a/tests/out/wgsl/binding-buffer-arrays.wgsl b/tests/out/wgsl/binding-buffer-arrays.wgsl new file mode 100644 index 0000000000..0c9cd50035 --- /dev/null +++ b/tests/out/wgsl/binding-buffer-arrays.wgsl @@ -0,0 +1,36 @@ +struct UniformIndex { + index: u32, +} + +struct Foo { + x: u32, +} + +struct FragmentIn { + @location(0) index: u32, +} + +@group(0) @binding(0) +var storage_array: binding_array; +@group(0) @binding(10) +var uni: UniformIndex; + +@fragment +fn main(fragment_in: FragmentIn) -> @location(0) u32 { + var u1_: u32; + + let uniform_index = uni.index; + let non_uniform_index = fragment_in.index; + u1_ = 0u; + let _e11 = storage_array[0].x; + let _e12 = u1_; + u1_ = (_e12 + _e11); + let _e17 = storage_array[uniform_index].x; + let _e18 = u1_; + u1_ = (_e18 + _e17); + let _e23 = storage_array[non_uniform_index].x; + let _e24 = u1_; + u1_ = (_e24 + _e23); + let _e26 = u1_; + return _e26; +} diff --git a/tests/snapshots.rs b/tests/snapshots.rs index eea2ccd5b9..09e98607be 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -558,6 +558,10 @@ fn convert_wgsl() { "binding-arrays", Targets::WGSL | Targets::HLSL | Targets::METAL | Targets::SPIRV, ), + ( + "binding-buffer-arrays", + Targets::WGSL | Targets::SPIRV, //TODO: more backends, eventually merge into "binding-arrays" + ), ("resource-binding-map", Targets::METAL), ("multiview", Targets::SPIRV | Targets::GLSL | Targets::WGSL), ("multiview_webgl", Targets::GLSL), diff --git a/tests/wgsl-errors.rs b/tests/wgsl-errors.rs index a981d92dd5..8bd13dacfe 100644 --- a/tests/wgsl-errors.rs +++ b/tests/wgsl-errors.rs @@ -1856,3 +1856,30 @@ fn function_returns_void() { "###, ) } + +#[test] +fn binding_array_local() { + check_validation! { + "fn f() { var x: binding_array; }": + Err(_) + } +} + +#[test] +fn binding_array_private() { + check_validation! { + "var x: binding_array;": + Err(_) + } +} + +#[test] +fn binding_array_non_struct() { + check_validation! { + "var x: binding_array;": + Err(naga::valid::ValidationError::Type { + source: naga::valid::TypeError::BindingArrayBaseTypeNotStruct(_), + .. + }) + } +}