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::{
|
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
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>,
|
||||||
|
@ -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]
|
||||||
|
17
src/lib.rs
17
src/lib.rs
@ -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 },
|
||||||
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 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
|
||||||
|
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",
|
"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),
|
||||||
|
@ -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