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::{ use super::{
index::BoundsCheckResult, make_local, selection::Selection, Block, BlockContext, Dimension, helpers, index::BoundsCheckResult, make_local, selection::Selection, Block, BlockContext,
Error, Instruction, LocalType, LookupType, LoopContext, ResultMember, Writer, WriterFlags, Dimension, Error, Instruction, LocalType, LookupType, LoopContext, ResultMember, Writer,
WriterFlags,
}; };
use crate::{arena::Handle, proc::TypeResolution}; use crate::{arena::Handle, proc::TypeResolution};
use spirv::Word; use spirv::Word;
@ -196,9 +197,8 @@ impl<'w> BlockContext<'w> {
fn is_intermediate(&self, expr_handle: Handle<crate::Expression>) -> bool { fn is_intermediate(&self, expr_handle: Handle<crate::Expression>) -> bool {
match self.ir_function.expressions[expr_handle] { match self.ir_function.expressions[expr_handle] {
crate::Expression::GlobalVariable(handle) => { crate::Expression::GlobalVariable(handle) => {
let ty = self.ir_module.global_variables[handle].ty; match self.ir_module.global_variables[handle].space {
match self.ir_module.types[ty].inner { crate::AddressSpace::Handle => false,
crate::TypeInner::BindingArray { .. } => false,
_ => true, _ => true,
} }
} }
@ -221,7 +221,6 @@ impl<'w> BlockContext<'w> {
block: &mut Block, block: &mut Block,
) -> Result<(), Error> { ) -> Result<(), Error> {
let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty); let result_type_id = self.get_expression_type_id(&self.fun_info[expr_handle].ty);
let id = match self.ir_function.expressions[expr_handle] { let id = match self.ir_function.expressions[expr_handle] {
crate::Expression::Access { base, index: _ } if self.is_intermediate(base) => { crate::Expression::Access { base, index: _ } if self.is_intermediate(base) => {
// See `is_intermediate`; we'll handle this later in // See `is_intermediate`; we'll handle this later in
@ -237,9 +236,15 @@ impl<'w> BlockContext<'w> {
crate::TypeInner::BindingArray { crate::TypeInner::BindingArray {
base: binding_type, .. 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 { let binding_array_false_pointer = LookupType::Local(LocalType::Pointer {
base: binding_type, base: binding_type,
class: spirv::StorageClass::UniformConstant, class: helpers::map_storage_class(space),
}); });
let result_id = match self.write_expression_pointer( let result_id = match self.write_expression_pointer(
@ -265,15 +270,6 @@ impl<'w> BlockContext<'w> {
None, 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 load_id
} }
ref other => { ref other => {
@ -316,9 +312,15 @@ impl<'w> BlockContext<'w> {
crate::TypeInner::BindingArray { crate::TypeInner::BindingArray {
base: binding_type, .. 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 { let binding_array_false_pointer = LookupType::Local(LocalType::Pointer {
base: binding_type, base: binding_type,
class: spirv::StorageClass::UniformConstant, class: helpers::map_storage_class(space),
}); });
let result_id = match self.write_expression_pointer( let result_id = match self.write_expression_pointer(
@ -1403,8 +1405,8 @@ impl<'w> BlockContext<'w> {
/// Emit any needed bounds-checking expressions to `block`. /// Emit any needed bounds-checking expressions to `block`.
/// ///
/// Some cases we need to generate a different return type than what the IR gives us. /// 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 /// This is because pointers to binding arrays of handles (such as images or samplers)
/// create them to create an access chain in SPIRV. /// 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 /// On success, the return value is an [`ExpressionPointer`] value; see the
/// documentation for that type. /// 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 // but we expect these checks to almost always succeed, and keeping branches to a
// minimum is essential. // minimum is essential.
let mut accumulated_checks = None; 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(); self.temp_list.clear();
let root_id = loop { let root_id = loop {
expr_handle = match self.ir_function.expressions[expr_handle] { expr_handle = match self.ir_function.expressions[expr_handle] {
crate::Expression::Access { base, index } => { 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)? { let index_id = match self.write_bounds_check(base, index, block)? {
BoundsCheckResult::KnownInBounds(known_index) => { BoundsCheckResult::KnownInBounds(known_index) => {
// Even if the index is known, `OpAccessIndex` // Even if the index is known, `OpAccessIndex`
@ -1471,7 +1487,6 @@ impl<'w> BlockContext<'w> {
} }
}; };
self.temp_list.push(index_id); self.temp_list.push(index_id);
base base
} }
crate::Expression::AccessIndex { base, index } => { crate::Expression::AccessIndex { base, index } => {
@ -1494,10 +1509,13 @@ impl<'w> BlockContext<'w> {
} }
}; };
let pointer = if self.temp_list.is_empty() { let (pointer_id, expr_pointer) = if self.temp_list.is_empty() {
ExpressionPointer::Ready { (
pointer_id: root_id, root_id,
} ExpressionPointer::Ready {
pointer_id: root_id,
},
)
} else { } else {
self.temp_list.reverse(); self.temp_list.reverse();
let pointer_id = self.gen_id(); 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 // caller to generate the branch, the access, the load or store, and
// the zero value (for loads). Otherwise, we can emit the access // the zero value (for loads). Otherwise, we can emit the access
// ourselves, and just hand them the id of the pointer. // 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 }, Some(condition) => ExpressionPointer::Conditional { condition, access },
None => { None => {
block.body.push(access); block.body.push(access);
ExpressionPointer::Ready { pointer_id } 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 /// 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, 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, _ => true,
} }
} }

View File

@ -288,9 +288,15 @@ enum LocalType {
image_type_id: Word, image_type_id: Word,
}, },
Sampler, 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 { PointerToBindingArray {
base: Handle<crate::Type>, base: Handle<crate::Type>,
size: u64, size: u64,
space: crate::AddressSpace,
}, },
BindingArray { BindingArray {
base: Handle<crate::Type>, base: Handle<crate::Type>,

View File

@ -940,10 +940,11 @@ impl Writer {
let scalar_id = self.get_constant_scalar(crate::ScalarValue::Uint(size), 4); let scalar_id = self.get_constant_scalar(crate::ScalarValue::Uint(size), 4);
Instruction::type_array(id, inner_ty, scalar_id) Instruction::type_array(id, inner_ty, scalar_id)
} }
LocalType::PointerToBindingArray { base, size } => { LocalType::PointerToBindingArray { base, size, space } => {
let inner_ty = let inner_ty =
self.get_type_id(LookupType::Local(LocalType::BindingArray { base, size })); 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::AccelerationStructure => Instruction::type_acceleration_structure(id),
LocalType::RayQuery => Instruction::type_ray_query(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; let mut substitute_inner_type_lookup = None;
if let Some(ref res_binding) = global_variable.binding { if let Some(ref res_binding) = global_variable.binding {
self.decorate(id, Decoration::DescriptorSet, &[res_binding.group]); self.decorate(id, Decoration::DescriptorSet, &[res_binding.group]);
@ -1595,6 +1599,7 @@ impl Writer {
Some(LookupType::Local(LocalType::PointerToBindingArray { Some(LookupType::Local(LocalType::PointerToBindingArray {
base, base,
size: remapped_binding_array_size as u64, size: remapped_binding_array_size as u64,
space: global_variable.space,
})) }))
} }
} else { } else {
@ -1635,7 +1640,13 @@ impl Writer {
// a runtime-sized array. In this case, we need to decorate it with // a runtime-sized array. In this case, we need to decorate it with
// Block. // Block.
if let crate::AddressSpace::Storage { .. } = global_variable.space { 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() { if substitute_inner_type_lookup.is_some() {
inner_type_id inner_type_id
@ -1955,6 +1966,13 @@ impl Writer {
pub const fn get_capabilities_used(&self) -> &crate::FastHashSet<spirv::Capability> { pub const fn get_capabilities_used(&self) -> &crate::FastHashSet<spirv::Capability> {
&self.capabilities_used &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] #[test]

View File

@ -200,7 +200,8 @@ tree.
clippy::match_like_matches_macro, clippy::match_like_matches_macro,
clippy::collapsible_if, clippy::collapsible_if,
clippy::derive_partial_eq_without_eq, clippy::derive_partial_eq_without_eq,
clippy::needless_borrowed_reference clippy::needless_borrowed_reference,
clippy::single_match
)] )]
#![warn( #![warn(
trivial_casts, trivial_casts,
@ -752,13 +753,12 @@ pub enum TypeInner {
/// buffers could have elements that are dynamically sized arrays, each with /// buffers could have elements that are dynamically sized arrays, each with
/// a different length. /// a different length.
/// ///
/// Binding arrays are not [`DATA`]. This means that all binding array /// Binding arrays are in the same address spaces as their underlying type.
/// globals must be placed in the [`Handle`] address space. Referring to /// As such, referring to an array of images produces an [`Image`] value
/// such a global produces a `BindingArray` value directly; there are never /// directly (as opposed to a pointer). The only operation permitted on
/// pointers to binding arrays. The only operation permitted on /// `BindingArray` values is indexing, which works transparently: indexing
/// `BindingArray` values is indexing, which yields the element by value, /// a binding array of samplers yields a [`Sampler`], indexing a pointer to the
/// not a pointer to the element. (This means that buffer array contents /// binding array of storage buffers produces a pointer to the storage struct.
/// cannot be stored to; [naga#1864] covers lifting this restriction.)
/// ///
/// Unlike textures and samplers, binding arrays are not [`ARGUMENT`], so /// Unlike textures and samplers, binding arrays are not [`ARGUMENT`], so
/// they cannot be passed as arguments to functions. /// 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 /// [`SamplerArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.SamplerArray
/// [`BufferArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.BufferArray /// [`BufferArray`]: https://docs.rs/wgpu/latest/wgpu/enum.BindingResource.html#variant.BufferArray
/// [`DATA`]: crate::valid::TypeFlags::DATA /// [`DATA`]: crate::valid::TypeFlags::DATA
/// [`Handle`]: AddressSpace::Handle
/// [`ARGUMENT`]: crate::valid::TypeFlags::ARGUMENT /// [`ARGUMENT`]: crate::valid::TypeFlags::ARGUMENT
/// [naga#1864]: https://github.com/gfx-rs/naga/issues/1864 /// [naga#1864]: https://github.com/gfx-rs/naga/issues/1864
BindingArray { base: Handle<Type>, size: ArraySize }, BindingArray { base: Handle<Type>, size: ArraySize },

View File

@ -390,7 +390,9 @@ impl crate::TypeInner {
match *base_inner { match *base_inner {
Ti::Vector { size, .. } => size as _, Ti::Vector { size, .. } => size as _,
Ti::Matrix { columns, .. } => columns 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), _ => return Err(IndexableLengthError::TypeNotIndexable),
} }
} }

View File

@ -298,6 +298,7 @@ impl<'a> ResolveContext<'a> {
width, width,
space, space,
}, },
Ti::BindingArray { base, .. } => Ti::Pointer { base, space },
ref other => { ref other => {
log::error!("Access sub-type {:?}", other); log::error!("Access sub-type {:?}", other);
return Err(ResolveError::InvalidSubAccess { return Err(ResolveError::InvalidSubAccess {
@ -401,6 +402,7 @@ impl<'a> ResolveContext<'a> {
space, space,
} }
} }
Ti::BindingArray { base, .. } => Ti::Pointer { base, space },
ref other => { ref other => {
log::error!("Access index sub-type {:?}", other); log::error!("Access index sub-type {:?}", other);
return Err(ResolveError::InvalidSubAccess { return Err(ResolveError::InvalidSubAccess {

View File

@ -400,7 +400,14 @@ impl super::Validator {
use super::TypeFlags; use super::TypeFlags;
log::debug!("var {:?}", var); 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 { let (required_type_flags, is_resource) = match var.space {
crate::AddressSpace::Function => { crate::AddressSpace::Function => {
@ -437,22 +444,8 @@ impl super::Validator {
) )
} }
crate::AddressSpace::Handle => { crate::AddressSpace::Handle => {
match types[var.ty].inner { match types[inner_ty].inner {
crate::TypeInner::Image { .. } crate::TypeInner::Image { class, .. } => match class {
| 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:
crate::ImageClass::Storage { crate::ImageClass::Storage {
format: format:
crate::StorageFormat::R16Unorm crate::StorageFormat::R16Unorm
@ -462,17 +455,23 @@ impl super::Validator {
| crate::StorageFormat::Rgba16Unorm | crate::StorageFormat::Rgba16Unorm
| crate::StorageFormat::Rgba16Snorm, | crate::StorageFormat::Rgba16Snorm,
.. ..
}, } => {
.. if !self
} = *inner_ty .capabilities
{ .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS)
if !self {
.capabilities return Err(GlobalVariableError::UnsupportedCapability(
.contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) 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 }, InvalidArrayStride { stride: u32, expected: u32 },
#[error("Field '{0}' can't be dynamically-sized, has type {1:?}")] #[error("Field '{0}' can't be dynamically-sized, has type {1:?}")]
InvalidDynamicArray(String, Handle<crate::Type>), 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")] #[error("Structure member[{index}] at {offset} overlaps the previous member")]
MemberOverlap { index: u32, offset: u32 }, MemberOverlap { index: u32, offset: u32 },
#[error( #[error(
@ -612,13 +614,35 @@ impl super::Validator {
} }
Ti::AccelerationStructure => { Ti::AccelerationStructure => {
self.require_type_capability(Capabilities::RAY_QUERY)?; self.require_type_capability(Capabilities::RAY_QUERY)?;
TypeInfo::new(TypeFlags::empty(), Alignment::ONE) TypeInfo::new(TypeFlags::ARGUMENT, Alignment::ONE)
} }
Ti::RayQuery => { Ti::RayQuery => {
self.require_type_capability(Capabilities::RAY_QUERY)?; self.require_type_capability(Capabilities::RAY_QUERY)?;
TypeInfo::new(TypeFlags::DATA | TypeFlags::SIZED, Alignment::ONE) 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 Location 0
OpDecorate %65 Flat OpDecorate %65 Flat
OpDecorate %68 Location 0 OpDecorate %68 Location 0
OpDecorate %97 NonUniform OpDecorate %96 NonUniform
OpDecorate %121 NonUniform OpDecorate %120 NonUniform
OpDecorate %123 NonUniform OpDecorate %122 NonUniform
OpDecorate %148 NonUniform OpDecorate %147 NonUniform
OpDecorate %150 NonUniform OpDecorate %149 NonUniform
OpDecorate %188 NonUniform OpDecorate %187 NonUniform
OpDecorate %219 NonUniform OpDecorate %218 NonUniform
OpDecorate %238 NonUniform OpDecorate %237 NonUniform
OpDecorate %257 NonUniform OpDecorate %256 NonUniform
OpDecorate %279 NonUniform OpDecorate %278 NonUniform
OpDecorate %281 NonUniform OpDecorate %280 NonUniform
OpDecorate %303 NonUniform OpDecorate %302 NonUniform
OpDecorate %305 NonUniform OpDecorate %304 NonUniform
OpDecorate %327 NonUniform OpDecorate %326 NonUniform
OpDecorate %329 NonUniform OpDecorate %328 NonUniform
OpDecorate %351 NonUniform OpDecorate %350 NonUniform
OpDecorate %353 NonUniform OpDecorate %352 NonUniform
OpDecorate %375 NonUniform OpDecorate %374 NonUniform
OpDecorate %377 NonUniform OpDecorate %376 NonUniform
OpDecorate %399 NonUniform OpDecorate %398 NonUniform
OpDecorate %401 NonUniform OpDecorate %400 NonUniform
OpDecorate %424 NonUniform OpDecorate %423 NonUniform
%2 = OpTypeVoid %2 = OpTypeVoid
%4 = OpTypeInt 32 1 %4 = OpTypeInt 32 1
%3 = OpConstant %4 5 %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", "binding-arrays",
Targets::WGSL | Targets::HLSL | Targets::METAL | Targets::SPIRV, 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), ("resource-binding-map", Targets::METAL),
("multiview", Targets::SPIRV | Targets::GLSL | Targets::WGSL), ("multiview", Targets::SPIRV | Targets::GLSL | Targets::WGSL),
("multiview_webgl", Targets::GLSL), ("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(_),
..
})
}
}