mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-22 23:04:07 +00:00
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:
parent
1421a5e1ab
commit
37b3c36a8f
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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]
|
||||
|
17
src/lib.rs
17
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<Type>, size: ArraySize },
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
14
tests/in/binding-buffer-arrays.param.ron
Normal file
14
tests/in/binding-buffer-arrays.param.ron
Normal 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,
|
||||
)
|
||||
)
|
27
tests/in/binding-buffer-arrays.wgsl
Normal file
27
tests/in/binding-buffer-arrays.wgsl
Normal 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;
|
||||
}
|
@ -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
|
||||
|
105
tests/out/spv/binding-buffer-arrays.spvasm
Normal file
105
tests/out/spv/binding-buffer-arrays.spvasm
Normal 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
|
36
tests/out/wgsl/binding-buffer-arrays.wgsl
Normal file
36
tests/out/wgsl/binding-buffer-arrays.wgsl
Normal 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;
|
||||
}
|
@ -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),
|
||||
|
@ -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(_),
|
||||
..
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user