Support array bindings of buffers (#2282)

* Support buffer resource arrays in IR, wgsl-in, and spv-out

* spv-out: refactor non-uniform indexing semantics to support buffers

* Update the doc comment on BindingArray type

* Improve TypeInfo restrictions on binding arrays

* Strip DATA out of binding arrays

* Include suggested documentation, more binding array tests, enforce structs
This commit is contained in:
Dzmitry Malyshau 2023-04-24 21:30:49 -07:00 committed by GitHub
parent 1421a5e1ab
commit 37b3c36a8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 380 additions and 93 deletions

View File

@ -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<crate::Expression>) -> 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

View File

@ -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,
}
}

View File

@ -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<crate::Type>,
size: u64,
space: crate::AddressSpace,
},
BindingArray {
base: Handle<crate::Type>,

View File

@ -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<Foo, 0>`,
// 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<spirv::Capability> {
&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]

View File

@ -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<Type>, size: ArraySize },

View File

@ -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),
}
}

View File

@ -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 {

View File

@ -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<T>` 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));
}
}

View File

@ -117,6 +117,8 @@ pub enum TypeError {
InvalidArrayStride { stride: u32, expected: u32 },
#[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
InvalidDynamicArray(String, Handle<crate::Type>),
#[error("The base handle {0:?} has to be a struct")]
BindingArrayBaseTypeNotStruct(Handle<crate::Type>),
#[error("Structure member[{index}] at {offset} overlaps the previous member")]
MemberOverlap { index: u32, offset: u32 },
#[error(
@ -612,13 +614,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)
}
})
}
}

View File

@ -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,
)
)

View File

@ -0,0 +1,27 @@
struct UniformIndex {
index: u32
}
struct Foo { x: u32 }
@group(0) @binding(0)
var<storage, read> storage_array: binding_array<Foo, 1>;
@group(0) @binding(10)
var<uniform> 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;
}

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,36 @@
struct UniformIndex {
index: u32,
}
struct Foo {
x: u32,
}
struct FragmentIn {
@location(0) index: u32,
}
@group(0) @binding(0)
var<storage> storage_array: binding_array<Foo,1>;
@group(0) @binding(10)
var<uniform> 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;
}

View File

@ -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),

View File

@ -1856,3 +1856,30 @@ fn function_returns_void() {
"###,
)
}
#[test]
fn binding_array_local() {
check_validation! {
"fn f() { var x: binding_array<sampler, 4>; }":
Err(_)
}
}
#[test]
fn binding_array_private() {
check_validation! {
"var<private> x: binding_array<sampler, 4>;":
Err(_)
}
}
#[test]
fn binding_array_non_struct() {
check_validation! {
"var<storage> x: binding_array<i32, 4>;":
Err(naga::valid::ValidationError::Type {
source: naga::valid::TypeError::BindingArrayBaseTypeNotStruct(_),
..
})
}
}