mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[spv-out] Implement BoundsCheckPolicy for image access
This commit is contained in:
committed by
Dzmitry Malyshau
parent
c6ecd973e7
commit
901e2c0694
@@ -10,12 +10,19 @@ struct Args {
|
||||
#[argh(switch)]
|
||||
validate: bool,
|
||||
|
||||
/// what policy to use for index bounds checking.
|
||||
/// what policy to use for index bounds checking for arrays, vectors, and
|
||||
/// matrices.
|
||||
///
|
||||
/// May be `Restrict`, `ReadZeroSkipWrite`, or `UndefinedBehavior`
|
||||
#[argh(option)]
|
||||
index_bounds_check_policy: Option<BoundsCheckPolicyArg>,
|
||||
|
||||
/// what policy to use for texture bounds checking.
|
||||
///
|
||||
/// May be `Restrict`, `ReadZeroSkipWrite`, or `UndefinedBehavior`
|
||||
#[argh(option)]
|
||||
image_bounds_check_policy: Option<BoundsCheckPolicyArg>,
|
||||
|
||||
/// directory to dump the SPIR-V flow dump to
|
||||
#[argh(option)]
|
||||
flow_dir: Option<String>,
|
||||
@@ -107,6 +114,7 @@ impl FromStr for GlslProfileArg {
|
||||
struct Parameters {
|
||||
validation_flags: naga::valid::ValidationFlags,
|
||||
index_bounds_check_policy: naga::back::BoundsCheckPolicy,
|
||||
image_bounds_check_policy: naga::back::BoundsCheckPolicy,
|
||||
entry_point: Option<String>,
|
||||
spv_adjust_coordinate_space: bool,
|
||||
spv_flow_dump_prefix: Option<String>,
|
||||
@@ -186,6 +194,9 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if let Some(policy) = args.index_bounds_check_policy {
|
||||
params.index_bounds_check_policy = policy.0;
|
||||
}
|
||||
if let Some(policy) = args.image_bounds_check_policy {
|
||||
params.image_bounds_check_policy = policy.0;
|
||||
}
|
||||
params.spv_flow_dump_prefix = args.flow_dir;
|
||||
params.entry_point = args.entry_point;
|
||||
if let Some(version) = args.profile {
|
||||
@@ -307,6 +318,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
use naga::back::spv;
|
||||
|
||||
params.spv.index_bounds_check_policy = params.index_bounds_check_policy;
|
||||
params.spv.image_bounds_check_policy = params.image_bounds_check_policy;
|
||||
|
||||
let spv = spv::write_vec(
|
||||
&module,
|
||||
|
||||
@@ -1,9 +1,231 @@
|
||||
//! Generating SPIR-V for image operations.
|
||||
|
||||
use super::{Block, BlockContext, Error, Instruction, LocalType, LookupType};
|
||||
use super::{
|
||||
selection::{MergeTuple, Selection},
|
||||
Block, BlockContext, Error, IdGenerator, Instruction, LocalType, LookupType,
|
||||
};
|
||||
use crate::arena::Handle;
|
||||
use spirv::Word;
|
||||
|
||||
/// Information about a vector of coordinates.
|
||||
///
|
||||
/// The coordinate vectors expected by SPIR-V `OpImageRead` and `OpImageFetch`
|
||||
/// supply the array index for arrayed images as an additional component at
|
||||
/// the end, whereas Naga's `ImageLoad`, `ImageStore`, and `ImageSample` carry
|
||||
/// the array index as a separate field.
|
||||
///
|
||||
/// In the process of generating code to compute the combined vector, we also
|
||||
/// produce SPIR-V types and vector lengths that are useful elsewhere. This
|
||||
/// struct gathers that information into one place, with standard names.
|
||||
struct ImageCoordinates {
|
||||
/// The SPIR-V id of the combined coordinate/index vector value.
|
||||
///
|
||||
/// Note: when indexing a non-arrayed 1D image, this will be a scalar.
|
||||
value_id: Word,
|
||||
|
||||
/// The SPIR-V id of the type of `value`.
|
||||
type_id: Word,
|
||||
|
||||
/// The number of components in `value`, if it is a vector, or `None` if it
|
||||
/// is a scalar.
|
||||
size: Option<crate::VectorSize>,
|
||||
}
|
||||
|
||||
/// A trait for image access (load or store) code generators.
|
||||
///
|
||||
/// When generating code for `ImageLoad` and `ImageStore` expressions, the image
|
||||
/// bounds checks policy can affect some operands of the image access
|
||||
/// instruction (the coordinates, level of detail, and sample index), but other
|
||||
/// aspects are unaffected: the image id, result type (if any), and the specific
|
||||
/// SPIR-V instruction used.
|
||||
///
|
||||
/// This struct holds the latter category of information, saving us from passing
|
||||
/// a half-dozen parameters along the various code paths. The parts that are
|
||||
/// affected by bounds checks, are passed as parameters to the `generate`
|
||||
/// method.
|
||||
trait Access {
|
||||
/// The Rust type that represents SPIR-V values and types for this access.
|
||||
///
|
||||
/// For operations like loads, this is `Word`. For operations like stores,
|
||||
/// this is `()`.
|
||||
///
|
||||
/// For `ReadZeroSkipWrite`, this will be the type of the selection
|
||||
/// construct that performs the bounds checks, so it must implement
|
||||
/// `MergeTuple`.
|
||||
type Output: MergeTuple + Copy + Clone;
|
||||
|
||||
/// Write an image access to `block`.
|
||||
///
|
||||
/// Access the texel at `coordinates_id`. The optional `level_id` indicates
|
||||
/// the level of detail, and `sample_id` is the index of the sample to
|
||||
/// access in a multisampled texel.
|
||||
///
|
||||
/// Ths method assumes that `coordinates_id` has already had the image array
|
||||
/// index, if any, folded in, as done by `write_image_coordinates`.
|
||||
///
|
||||
/// Return the value id produced by the instruction, if any.
|
||||
///
|
||||
/// Use `id_gen` to generate SPIR-V ids as necessary.
|
||||
fn generate(
|
||||
&self,
|
||||
id_gen: &mut IdGenerator,
|
||||
coordinates_id: Word,
|
||||
level_id: Option<Word>,
|
||||
sample_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
) -> Self::Output;
|
||||
|
||||
/// Return the SPIR-V type of the value produced by the code written by
|
||||
/// `generate`. If the access does not produce a value, `Self::Output`
|
||||
/// should be `()`.
|
||||
fn result_type(&self) -> Self::Output;
|
||||
|
||||
/// Construct the SPIR-V 'zero' value to be returned for an out-of-bounds
|
||||
/// access under the `ReadZeroSkipWrite` policy. If the access does not
|
||||
/// produce a value, `Self::Output` should be `()`.
|
||||
fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Self::Output;
|
||||
}
|
||||
|
||||
/// Texel access information for an [`ImageLoad`] expression.
|
||||
///
|
||||
/// [`ImageLoad`]: crate::Expression::ImageLoad
|
||||
struct Load {
|
||||
/// The specific opcode we'll use to perform the fetch. Storage images
|
||||
/// require `OpImageRead`, while sampled images require `OpImageFetch`.
|
||||
opcode: spirv::Op,
|
||||
|
||||
/// The type id produced by the actual image access instruction.
|
||||
type_id: Word,
|
||||
|
||||
/// The id of the image being accessed.
|
||||
image_id: Word,
|
||||
}
|
||||
|
||||
impl Load {
|
||||
fn from_image_expr(
|
||||
ctx: &mut BlockContext<'_>,
|
||||
image_id: Word,
|
||||
image_class: crate::ImageClass,
|
||||
result_type_id: Word,
|
||||
) -> Result<Load, Error> {
|
||||
let opcode = match image_class {
|
||||
crate::ImageClass::Storage { .. } => spirv::Op::ImageRead,
|
||||
crate::ImageClass::Depth { .. } | crate::ImageClass::Sampled { .. } => {
|
||||
spirv::Op::ImageFetch
|
||||
}
|
||||
};
|
||||
|
||||
// `OpImageRead` and `OpImageFetch` instructions produce vec4<f32>
|
||||
// values. Most of the time, we can just use `result_type_id` for
|
||||
// this. The exception is that `Expression::ImageLoad` from a depth
|
||||
// image produces a scalar `f32`, so in that case we need to find
|
||||
// the right SPIR-V type for the access instruction here.
|
||||
let type_id = match image_class {
|
||||
crate::ImageClass::Depth { .. } => {
|
||||
ctx.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: Some(crate::VectorSize::Quad),
|
||||
kind: crate::ScalarKind::Float,
|
||||
width: 4,
|
||||
pointer_class: None,
|
||||
}))
|
||||
}
|
||||
_ => result_type_id,
|
||||
};
|
||||
|
||||
Ok(Load {
|
||||
opcode,
|
||||
type_id,
|
||||
image_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Access for Load {
|
||||
type Output = Word;
|
||||
|
||||
/// Write an instruction to access a given texel of this image.
|
||||
fn generate(
|
||||
&self,
|
||||
id_gen: &mut IdGenerator,
|
||||
coordinates_id: Word,
|
||||
level_id: Option<Word>,
|
||||
sample_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
) -> Word {
|
||||
let texel_id = id_gen.next();
|
||||
let mut instruction = Instruction::image_fetch_or_read(
|
||||
self.opcode,
|
||||
self.type_id,
|
||||
texel_id,
|
||||
self.image_id,
|
||||
coordinates_id,
|
||||
);
|
||||
|
||||
match (level_id, sample_id) {
|
||||
(None, None) => {}
|
||||
(Some(level_id), None) => {
|
||||
instruction.add_operand(spirv::ImageOperands::LOD.bits());
|
||||
instruction.add_operand(level_id);
|
||||
}
|
||||
(None, Some(sample_id)) => {
|
||||
instruction.add_operand(spirv::ImageOperands::SAMPLE.bits());
|
||||
instruction.add_operand(sample_id);
|
||||
}
|
||||
// There's no such thing as a multi-sampled mipmap.
|
||||
(Some(_), Some(_)) => unreachable!(),
|
||||
}
|
||||
|
||||
block.body.push(instruction);
|
||||
|
||||
texel_id
|
||||
}
|
||||
|
||||
fn result_type(&self) -> Word {
|
||||
self.type_id
|
||||
}
|
||||
|
||||
fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Word {
|
||||
ctx.writer.write_constant_null(self.type_id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Texel access information for a [`Store`] statement.
|
||||
///
|
||||
/// [`Store`]: crate::Statement::Store
|
||||
struct Store {
|
||||
/// The id of the image being written to.
|
||||
image_id: Word,
|
||||
|
||||
/// The value we're going to write to the texel.
|
||||
value_id: Word,
|
||||
}
|
||||
|
||||
impl Access for Store {
|
||||
/// Stores don't generate any value.
|
||||
type Output = ();
|
||||
|
||||
fn generate(
|
||||
&self,
|
||||
_id_gen: &mut IdGenerator,
|
||||
coordinates_id: Word,
|
||||
_level_id: Option<Word>,
|
||||
_sample_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
) {
|
||||
block.body.push(Instruction::image_write(
|
||||
self.image_id,
|
||||
coordinates_id,
|
||||
self.value_id,
|
||||
));
|
||||
}
|
||||
|
||||
/// Stores don't generate any value, so this just returns `()`.
|
||||
fn result_type(&self) {}
|
||||
|
||||
/// Stores don't generate any value, so this just returns `()`.
|
||||
fn out_of_bounds_value(&self, _ctx: &mut BlockContext<'_>) {}
|
||||
}
|
||||
|
||||
impl<'w> BlockContext<'w> {
|
||||
/// Extend image coordinates with an array index, if necessary.
|
||||
///
|
||||
@@ -11,20 +233,23 @@ impl<'w> BlockContext<'w> {
|
||||
/// index as a separate operand from the coordinates, SPIR-V image access
|
||||
/// instructions include the array index in the `coordinates` operand. This
|
||||
/// function builds a SPIR-V coordinate vector from a Naga coordinate vector
|
||||
/// and array index.
|
||||
/// and array index, if one is supplied, and returns a `ImageCoordinates`
|
||||
/// struct describing what it built.
|
||||
///
|
||||
/// If `array_index` is `Some(expr)`, then this function constructs a new
|
||||
/// vector that is `coordinates` with `array_index` concatenated onto the
|
||||
/// end: a `vec2` becomes a `vec3`, a scalar becomes a `vec2`, and so on.
|
||||
///
|
||||
/// If `array_index` is `None`, then the return value uses `coordinates`
|
||||
/// unchanged. Note that, when indexing a non-arrayed 1D image, this will be
|
||||
/// a scalar value.
|
||||
///
|
||||
/// If needed, this function generates code to convert the array index,
|
||||
/// always an integer scalar, to match the component type of `coordinates`.
|
||||
/// Naga's `ImageLoad` and SPIR-V's `OpImageRead`, `OpImageFetch`, and
|
||||
/// `OpImageWrite` all use integer coordinates, while Naga's `ImageSample`
|
||||
/// and SPIR-V's `OpImageSample...` instructions all take floating-point
|
||||
/// coordinate vectors. The array index, always an integer scalar, may need
|
||||
/// to be converted to match the component type of `coordinates`.
|
||||
///
|
||||
/// If `array_index` is `None`, this function simply returns the id for
|
||||
/// `coordinates`.
|
||||
/// coordinate vectors.
|
||||
///
|
||||
/// [`Expression::ImageLoad`]: crate::Expression::ImageLoad
|
||||
/// [`ImageSample`]: crate::Expression::ImageSample
|
||||
@@ -33,36 +258,48 @@ impl<'w> BlockContext<'w> {
|
||||
coordinates: Handle<crate::Expression>,
|
||||
array_index: Option<Handle<crate::Expression>>,
|
||||
block: &mut Block,
|
||||
) -> Result<Word, Error> {
|
||||
) -> Result<ImageCoordinates, Error> {
|
||||
use crate::TypeInner as Ti;
|
||||
use crate::VectorSize as Vs;
|
||||
|
||||
let coordinate_id = self.cached[coordinates];
|
||||
let coordinates_id = self.cached[coordinates];
|
||||
let ty = &self.fun_info[coordinates].ty;
|
||||
let inner_ty = ty.inner_with(&self.ir_module.types);
|
||||
|
||||
// If there's no array index, the image coordinates are exactly the
|
||||
// `coordinate` field of the `Expression::ImageLoad`. No work is needed.
|
||||
let array_index = match array_index {
|
||||
None => return Ok(coordinate_id),
|
||||
None => {
|
||||
let value_id = coordinates_id;
|
||||
let type_id = self.get_expression_type_id(ty);
|
||||
let size = match *inner_ty {
|
||||
Ti::Scalar { .. } => None,
|
||||
Ti::Vector { size, .. } => Some(size),
|
||||
_ => return Err(Error::Validation("coordinate type")),
|
||||
};
|
||||
return Ok(ImageCoordinates {
|
||||
value_id,
|
||||
type_id,
|
||||
size,
|
||||
});
|
||||
}
|
||||
Some(ix) => ix,
|
||||
};
|
||||
|
||||
// Find the component type of `coordinates`, and figure out the size the
|
||||
// combined coordinate vector will have.
|
||||
let (component_kind, result_size) = match *self.fun_info[coordinates]
|
||||
.ty
|
||||
.inner_with(&self.ir_module.types)
|
||||
{
|
||||
Ti::Scalar { kind, width: 4 } => (kind, Vs::Bi),
|
||||
let (component_kind, size) = match *inner_ty {
|
||||
Ti::Scalar { kind, width: 4 } => (kind, Some(Vs::Bi)),
|
||||
Ti::Vector {
|
||||
kind,
|
||||
width: 4,
|
||||
size: Vs::Bi,
|
||||
} => (kind, Vs::Tri),
|
||||
} => (kind, Some(Vs::Tri)),
|
||||
Ti::Vector {
|
||||
kind,
|
||||
width: 4,
|
||||
size: Vs::Tri,
|
||||
} => (kind, Vs::Quad),
|
||||
} => (kind, Some(Vs::Quad)),
|
||||
Ti::Vector { size: Vs::Quad, .. } => {
|
||||
return Err(Error::Validation("extending vec4 coordinate"));
|
||||
}
|
||||
@@ -95,21 +332,25 @@ impl<'w> BlockContext<'w> {
|
||||
};
|
||||
|
||||
// Find the SPIR-V type for the combined coordinates/index vector.
|
||||
let combined_coordinate_type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: Some(result_size),
|
||||
let type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: size,
|
||||
kind: component_kind,
|
||||
width: 4,
|
||||
pointer_class: None,
|
||||
}));
|
||||
|
||||
// Schmear the coordinates and index together.
|
||||
let id = self.gen_id();
|
||||
let value_id = self.gen_id();
|
||||
block.body.push(Instruction::composite_construct(
|
||||
combined_coordinate_type_id,
|
||||
id,
|
||||
&[coordinate_id, reconciled_array_index_id],
|
||||
type_id,
|
||||
value_id,
|
||||
&[coordinates_id, reconciled_array_index_id],
|
||||
));
|
||||
Ok(id)
|
||||
Ok(ImageCoordinates {
|
||||
value_id,
|
||||
type_id,
|
||||
size,
|
||||
})
|
||||
}
|
||||
|
||||
fn get_image_id(&mut self, expr_handle: Handle<crate::Expression>) -> Word {
|
||||
@@ -133,6 +374,334 @@ impl<'w> BlockContext<'w> {
|
||||
id
|
||||
}
|
||||
|
||||
/// Generate a vector or scalar 'one' for arithmetic on `coordinates`.
|
||||
///
|
||||
/// If `coordinates` is a scalar, return a scalar one. Otherwise, return
|
||||
/// a vector of ones.
|
||||
fn write_coordinate_one(&mut self, coordinates: &ImageCoordinates) -> Result<Word, Error> {
|
||||
let one = self.get_scope_constant(1);
|
||||
match coordinates.size {
|
||||
None => Ok(one),
|
||||
Some(vector_size) => {
|
||||
let ones = [one; 4];
|
||||
let id = self.gen_id();
|
||||
Instruction::constant_composite(
|
||||
coordinates.type_id,
|
||||
id,
|
||||
&ones[..vector_size as usize],
|
||||
)
|
||||
.to_words(&mut self.writer.logical_layout.declarations);
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate code to restrict `input` to fall between zero and one less than
|
||||
/// `size_id`.
|
||||
///
|
||||
/// Both must be 32-bit scalar integer values, whose type is given by
|
||||
/// `type_id`. The computed value is also of type `type_id`.
|
||||
fn restrict_scalar(
|
||||
&mut self,
|
||||
type_id: Word,
|
||||
input_id: Word,
|
||||
size_id: Word,
|
||||
block: &mut Block,
|
||||
) -> Result<Word, Error> {
|
||||
let i32_one_id = self.get_scope_constant(1);
|
||||
|
||||
// Subtract one from `size` to get the largest valid value.
|
||||
let limit_id = self.gen_id();
|
||||
block.body.push(Instruction::binary(
|
||||
spirv::Op::ISub,
|
||||
type_id,
|
||||
limit_id,
|
||||
size_id,
|
||||
i32_one_id,
|
||||
));
|
||||
|
||||
// Use an unsigned minimum, to handle both positive out-of-range values
|
||||
// and negative values in a single instruction: negative values of
|
||||
// `input_id` get treated as very large positive values.
|
||||
let restricted_id = self.gen_id();
|
||||
block.body.push(Instruction::ext_inst(
|
||||
self.writer.gl450_ext_inst_id,
|
||||
spirv::GLOp::UMin,
|
||||
type_id,
|
||||
restricted_id,
|
||||
&[input_id, limit_id],
|
||||
));
|
||||
|
||||
Ok(restricted_id)
|
||||
}
|
||||
|
||||
/// Write instructions to query the size of an image.
|
||||
///
|
||||
/// This takes care of selecting the right instruction depending on whether
|
||||
/// a level of detail parameter is present.
|
||||
fn write_coordinate_bounds(
|
||||
&mut self,
|
||||
type_id: Word,
|
||||
image_id: Word,
|
||||
level_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
) -> Word {
|
||||
let coordinate_bounds_id = self.gen_id();
|
||||
match level_id {
|
||||
Some(level_id) => {
|
||||
// A level of detail was provided, so fetch the image size for
|
||||
// that level.
|
||||
let mut inst = Instruction::image_query(
|
||||
spirv::Op::ImageQuerySizeLod,
|
||||
type_id,
|
||||
coordinate_bounds_id,
|
||||
image_id,
|
||||
);
|
||||
inst.add_operand(level_id);
|
||||
block.body.push(inst);
|
||||
}
|
||||
_ => {
|
||||
// No level of detail was given.
|
||||
block.body.push(Instruction::image_query(
|
||||
spirv::Op::ImageQuerySize,
|
||||
type_id,
|
||||
coordinate_bounds_id,
|
||||
image_id,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
coordinate_bounds_id
|
||||
}
|
||||
|
||||
/// Write code to restrict coordinates for an image reference.
|
||||
///
|
||||
/// First, clamp the level of detail or sample index to fall within bounds.
|
||||
/// Then, obtain the image size, possibly using the clamped level of detail.
|
||||
/// Finally, use an unsigned minimum instruction to force all coordinates
|
||||
/// into range.
|
||||
///
|
||||
/// Return a triple `(COORDS, LEVEL, SAMPLE)`, where `COORDS` is a coordinate
|
||||
/// vector (including the array index, if any), `LEVEL` is an optional level
|
||||
/// of detail, and `SAMPLE` is an optional sample index, all guaranteed to
|
||||
/// be in-bounds for `image_id`.
|
||||
///
|
||||
/// The result is usually a vector, but it is a scalar when indexing
|
||||
/// non-arrayed 1D images.
|
||||
fn write_restricted_coordinates(
|
||||
&mut self,
|
||||
image_id: Word,
|
||||
coordinates: ImageCoordinates,
|
||||
level_id: Option<Word>,
|
||||
sample_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
) -> Result<(Word, Option<Word>, Option<Word>), Error> {
|
||||
self.writer.require_any(
|
||||
"the `Restrict` image bounds check policy",
|
||||
&[spirv::Capability::ImageQuery],
|
||||
)?;
|
||||
|
||||
let i32_type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: None,
|
||||
kind: crate::ScalarKind::Sint,
|
||||
width: 4,
|
||||
pointer_class: None,
|
||||
}));
|
||||
|
||||
// If `level` is `Some`, clamp it to fall within bounds. This must
|
||||
// happen first, because we'll use it to query the image size for
|
||||
// clamping the actual coordinates.
|
||||
let level_id = level_id
|
||||
.map(|level_id| {
|
||||
// Find the number of mipmap levels in this image.
|
||||
let num_levels_id = self.gen_id();
|
||||
block.body.push(Instruction::image_query(
|
||||
spirv::Op::ImageQueryLevels,
|
||||
i32_type_id,
|
||||
num_levels_id,
|
||||
image_id,
|
||||
));
|
||||
|
||||
self.restrict_scalar(i32_type_id, level_id, num_levels_id, block)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// If `sample_id` is `Some`, clamp it to fall within bounds.
|
||||
let sample_id = sample_id
|
||||
.map(|sample_id| {
|
||||
// Find the number of samples per texel.
|
||||
let num_samples_id = self.gen_id();
|
||||
block.body.push(Instruction::image_query(
|
||||
spirv::Op::ImageQuerySamples,
|
||||
i32_type_id,
|
||||
num_samples_id,
|
||||
image_id,
|
||||
));
|
||||
|
||||
self.restrict_scalar(i32_type_id, sample_id, num_samples_id, block)
|
||||
})
|
||||
.transpose()?;
|
||||
|
||||
// Obtain the image bounds, including the array element count.
|
||||
let coordinate_bounds_id =
|
||||
self.write_coordinate_bounds(coordinates.type_id, image_id, level_id, block);
|
||||
|
||||
// Compute maximum valid values from the bounds.
|
||||
let ones = self.write_coordinate_one(&coordinates)?;
|
||||
let coordinate_limit_id = self.gen_id();
|
||||
block.body.push(Instruction::binary(
|
||||
spirv::Op::ISub,
|
||||
coordinates.type_id,
|
||||
coordinate_limit_id,
|
||||
coordinate_bounds_id,
|
||||
ones,
|
||||
));
|
||||
|
||||
// Restrict the coordinates to fall within those bounds.
|
||||
//
|
||||
// Use an unsigned minimum, to handle both positive out-of-range values
|
||||
// and negative values in a single instruction: negative values of
|
||||
// `coordinates` get treated as very large positive values.
|
||||
let restricted_coordinates_id = self.gen_id();
|
||||
block.body.push(Instruction::ext_inst(
|
||||
self.writer.gl450_ext_inst_id,
|
||||
spirv::GLOp::UMin,
|
||||
coordinates.type_id,
|
||||
restricted_coordinates_id,
|
||||
&[coordinates.value_id, coordinate_limit_id],
|
||||
));
|
||||
|
||||
Ok((restricted_coordinates_id, level_id, sample_id))
|
||||
}
|
||||
|
||||
fn write_conditional_image_access<A: Access>(
|
||||
&mut self,
|
||||
image_id: Word,
|
||||
coordinates: ImageCoordinates,
|
||||
level_id: Option<Word>,
|
||||
sample_id: Option<Word>,
|
||||
block: &mut Block,
|
||||
access: &A,
|
||||
) -> Result<A::Output, Error> {
|
||||
self.writer.require_any(
|
||||
"the `ReadZeroSkipWrite` image bounds check policy",
|
||||
&[spirv::Capability::ImageQuery],
|
||||
)?;
|
||||
|
||||
let bool_type_id = self.writer.get_bool_type_id();
|
||||
let i32_type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: None,
|
||||
kind: crate::ScalarKind::Sint,
|
||||
width: 4,
|
||||
pointer_class: None,
|
||||
}));
|
||||
|
||||
let null_id = access.out_of_bounds_value(self);
|
||||
|
||||
let mut selection = Selection::start(block, access.result_type());
|
||||
|
||||
// If `level_id` is `Some`, check whether it is within bounds. This must
|
||||
// happen first, because we'll be supplying this as an argument when we
|
||||
// query the image size.
|
||||
if let Some(level_id) = level_id {
|
||||
// Find the number of mipmap levels in this image.
|
||||
let num_levels_id = self.gen_id();
|
||||
selection.block().body.push(Instruction::image_query(
|
||||
spirv::Op::ImageQueryLevels,
|
||||
i32_type_id,
|
||||
num_levels_id,
|
||||
image_id,
|
||||
));
|
||||
|
||||
let lod_cond_id = self.gen_id();
|
||||
selection.block().body.push(Instruction::binary(
|
||||
spirv::Op::ULessThan,
|
||||
bool_type_id,
|
||||
lod_cond_id,
|
||||
level_id,
|
||||
num_levels_id,
|
||||
));
|
||||
|
||||
selection.if_true(self, lod_cond_id, null_id);
|
||||
}
|
||||
|
||||
// If `sample_id` is `Some`, check whether it is in bounds.
|
||||
if let Some(sample_id) = sample_id {
|
||||
// Find the number of samples per texel.
|
||||
let num_samples_id = self.gen_id();
|
||||
selection.block().body.push(Instruction::image_query(
|
||||
spirv::Op::ImageQuerySamples,
|
||||
i32_type_id,
|
||||
num_samples_id,
|
||||
image_id,
|
||||
));
|
||||
|
||||
let samples_cond_id = self.gen_id();
|
||||
selection.block().body.push(Instruction::binary(
|
||||
spirv::Op::ULessThan,
|
||||
bool_type_id,
|
||||
samples_cond_id,
|
||||
sample_id,
|
||||
num_samples_id,
|
||||
));
|
||||
|
||||
selection.if_true(self, samples_cond_id, null_id);
|
||||
}
|
||||
|
||||
// Obtain the image bounds, including any array element count.
|
||||
let coordinate_bounds_id = self.write_coordinate_bounds(
|
||||
coordinates.type_id,
|
||||
image_id,
|
||||
level_id,
|
||||
selection.block(),
|
||||
);
|
||||
|
||||
// Compare the coordinates against the bounds.
|
||||
let coords_bool_type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: coordinates.size,
|
||||
kind: crate::ScalarKind::Bool,
|
||||
width: 1,
|
||||
pointer_class: None,
|
||||
}));
|
||||
let coords_conds_id = self.gen_id();
|
||||
selection.block().body.push(Instruction::binary(
|
||||
spirv::Op::ULessThan,
|
||||
coords_bool_type_id,
|
||||
coords_conds_id,
|
||||
coordinates.value_id,
|
||||
coordinate_bounds_id,
|
||||
));
|
||||
|
||||
// If the comparison above was a vector comparison, then we need to
|
||||
// check that all components of the comparison are true.
|
||||
let coords_cond_id = if coords_bool_type_id != bool_type_id {
|
||||
let id = self.gen_id();
|
||||
selection.block().body.push(Instruction::relational(
|
||||
spirv::Op::All,
|
||||
bool_type_id,
|
||||
id,
|
||||
coords_conds_id,
|
||||
));
|
||||
id
|
||||
} else {
|
||||
coords_conds_id
|
||||
};
|
||||
|
||||
selection.if_true(self, coords_cond_id, null_id);
|
||||
|
||||
// All conditions are met. We can carry out the access.
|
||||
let texel_id = access.generate(
|
||||
&mut self.writer.id_gen,
|
||||
coordinates.value_id,
|
||||
level_id,
|
||||
sample_id,
|
||||
selection.block(),
|
||||
);
|
||||
|
||||
// This, then, is the value of the 'true' branch.
|
||||
Ok(selection.finish(self, texel_id))
|
||||
}
|
||||
|
||||
/// Generate code for an `ImageLoad` expression.
|
||||
///
|
||||
/// The arguments are the components of an `Expression::ImageLoad` variant.
|
||||
@@ -142,70 +711,83 @@ impl<'w> BlockContext<'w> {
|
||||
image: Handle<crate::Expression>,
|
||||
coordinate: Handle<crate::Expression>,
|
||||
array_index: Option<Handle<crate::Expression>>,
|
||||
index: Option<Handle<crate::Expression>>,
|
||||
level_or_sample: Option<Handle<crate::Expression>>,
|
||||
block: &mut Block,
|
||||
) -> Result<Word, Error> {
|
||||
let image_id = self.get_image_id(image);
|
||||
let coordinate_id = self.write_image_coordinates(coordinate, array_index, block)?;
|
||||
|
||||
let id = self.gen_id();
|
||||
|
||||
let image_ty = self.fun_info[image].ty.inner_with(&self.ir_module.types);
|
||||
let mut instruction = match *image_ty {
|
||||
crate::TypeInner::Image {
|
||||
class: crate::ImageClass::Storage { .. },
|
||||
..
|
||||
} => Instruction::image_read(result_type_id, id, image_id, coordinate_id),
|
||||
crate::TypeInner::Image {
|
||||
class: crate::ImageClass::Depth { multi: _ },
|
||||
..
|
||||
} => {
|
||||
// Vulkan doesn't know about our `Depth` class, and it returns `vec4<f32>`,
|
||||
// so we need to grab the first component out of it.
|
||||
let load_result_type_id = self.get_type_id(LookupType::Local(LocalType::Value {
|
||||
vector_size: Some(crate::VectorSize::Quad),
|
||||
kind: crate::ScalarKind::Float,
|
||||
width: 4,
|
||||
pointer_class: None,
|
||||
}));
|
||||
Instruction::image_fetch(load_result_type_id, id, image_id, coordinate_id)
|
||||
}
|
||||
_ => Instruction::image_fetch(result_type_id, id, image_id, coordinate_id),
|
||||
let image_type = self.fun_info[image].ty.inner_with(&self.ir_module.types);
|
||||
let image_class = match *image_type {
|
||||
crate::TypeInner::Image { class, .. } => class,
|
||||
_ => return Err(Error::Validation("image type")),
|
||||
};
|
||||
|
||||
if let Some(index) = index {
|
||||
let index_id = self.cached[index];
|
||||
let image_ops = match *self.fun_info[image].ty.inner_with(&self.ir_module.types) {
|
||||
crate::TypeInner::Image {
|
||||
class: crate::ImageClass::Sampled { multi: true, .. },
|
||||
..
|
||||
}
|
||||
| crate::TypeInner::Image {
|
||||
class: crate::ImageClass::Depth { multi: true },
|
||||
..
|
||||
} => spirv::ImageOperands::SAMPLE,
|
||||
_ => spirv::ImageOperands::LOD,
|
||||
};
|
||||
instruction.add_operand(image_ops.bits());
|
||||
instruction.add_operand(index_id);
|
||||
}
|
||||
let access = Load::from_image_expr(self, image_id, image_class, result_type_id)?;
|
||||
let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
|
||||
|
||||
let inst_type_id = instruction.type_id;
|
||||
block.body.push(instruction);
|
||||
let id = if inst_type_id != Some(result_type_id) {
|
||||
let sub_id = self.gen_id();
|
||||
// Figure out what the `level_or_sample` operand really means.
|
||||
let level_or_sample_id = level_or_sample.map(|i| self.cached[i]);
|
||||
let (level_id, sample_id) = match image_class {
|
||||
crate::ImageClass::Sampled { multi, .. } | crate::ImageClass::Depth { multi } => {
|
||||
if multi {
|
||||
(None, level_or_sample_id)
|
||||
} else {
|
||||
(level_or_sample_id, None)
|
||||
}
|
||||
}
|
||||
crate::ImageClass::Storage { .. } => (None, None),
|
||||
};
|
||||
|
||||
// Perform the access, according to the bounds check policy.
|
||||
let access_id = match self.writer.image_bounds_check_policy {
|
||||
crate::back::BoundsCheckPolicy::Restrict => {
|
||||
let (coords, level_id, sample_id) = self.write_restricted_coordinates(
|
||||
image_id,
|
||||
coordinates,
|
||||
level_id,
|
||||
sample_id,
|
||||
block,
|
||||
)?;
|
||||
access.generate(&mut self.writer.id_gen, coords, level_id, sample_id, block)
|
||||
}
|
||||
crate::back::BoundsCheckPolicy::ReadZeroSkipWrite => self
|
||||
.write_conditional_image_access(
|
||||
image_id,
|
||||
coordinates,
|
||||
level_id,
|
||||
sample_id,
|
||||
block,
|
||||
&access,
|
||||
)?,
|
||||
crate::back::BoundsCheckPolicy::UndefinedBehavior => access.generate(
|
||||
&mut self.writer.id_gen,
|
||||
coordinates.value_id,
|
||||
level_id,
|
||||
sample_id,
|
||||
block,
|
||||
),
|
||||
};
|
||||
|
||||
// For depth images, `ImageLoad` expressions produce a single f32,
|
||||
// whereas the SPIR-V instructions always produce a vec4. So we may have
|
||||
// to pull out the component we need.
|
||||
let result_id = if result_type_id == access.result_type() {
|
||||
// The instruction produced the type we expected. We can use
|
||||
// its result as-is.
|
||||
access_id
|
||||
} else {
|
||||
// For `ImageClass::Depth` images, SPIR-V gave us four components,
|
||||
// but we only want the first one.
|
||||
let component_id = self.gen_id();
|
||||
block.body.push(Instruction::composite_extract(
|
||||
result_type_id,
|
||||
sub_id,
|
||||
id,
|
||||
component_id,
|
||||
access_id,
|
||||
&[0],
|
||||
));
|
||||
sub_id
|
||||
} else {
|
||||
id
|
||||
component_id
|
||||
};
|
||||
|
||||
Ok(id)
|
||||
Ok(result_id)
|
||||
}
|
||||
|
||||
/// Generate code for an `ImageSample` expression.
|
||||
@@ -228,8 +810,8 @@ impl<'w> BlockContext<'w> {
|
||||
// image
|
||||
let image_id = self.get_image_id(image);
|
||||
let image_type = self.fun_info[image].ty.handle().unwrap();
|
||||
// Vulkan doesn't know about our `Depth` class, and it returns `vec4<f32>`,
|
||||
// so we need to grab the first component out of it.
|
||||
// SPIR-V doesn't know about our `Depth` class, and it returns
|
||||
// `vec4<f32>`, so we need to grab the first component out of it.
|
||||
let needs_sub_access = match self.ir_module.types[image_type].inner {
|
||||
crate::TypeInner::Image {
|
||||
class: crate::ImageClass::Depth { .. },
|
||||
@@ -254,7 +836,9 @@ impl<'w> BlockContext<'w> {
|
||||
self.get_type_id(LookupType::Local(LocalType::SampledImage { image_type_id }));
|
||||
|
||||
let sampler_id = self.get_image_id(sampler);
|
||||
let coordinate_id = self.write_image_coordinates(coordinate, array_index, block)?;
|
||||
let coordinates_id = self
|
||||
.write_image_coordinates(coordinate, array_index, block)?
|
||||
.value_id;
|
||||
|
||||
let sampled_image_id = self.gen_id();
|
||||
block.body.push(Instruction::sampled_image(
|
||||
@@ -276,7 +860,7 @@ impl<'w> BlockContext<'w> {
|
||||
id,
|
||||
SampleLod::Explicit,
|
||||
sampled_image_id,
|
||||
coordinate_id,
|
||||
coordinates_id,
|
||||
depth_id,
|
||||
);
|
||||
|
||||
@@ -296,7 +880,7 @@ impl<'w> BlockContext<'w> {
|
||||
id,
|
||||
SampleLod::Implicit,
|
||||
sampled_image_id,
|
||||
coordinate_id,
|
||||
coordinates_id,
|
||||
depth_id,
|
||||
);
|
||||
if !mask.is_empty() {
|
||||
@@ -310,7 +894,7 @@ impl<'w> BlockContext<'w> {
|
||||
id,
|
||||
SampleLod::Explicit,
|
||||
sampled_image_id,
|
||||
coordinate_id,
|
||||
coordinates_id,
|
||||
depth_id,
|
||||
);
|
||||
|
||||
@@ -327,7 +911,7 @@ impl<'w> BlockContext<'w> {
|
||||
id,
|
||||
SampleLod::Implicit,
|
||||
sampled_image_id,
|
||||
coordinate_id,
|
||||
coordinates_id,
|
||||
depth_id,
|
||||
);
|
||||
|
||||
@@ -344,7 +928,7 @@ impl<'w> BlockContext<'w> {
|
||||
id,
|
||||
SampleLod::Explicit,
|
||||
sampled_image_id,
|
||||
coordinate_id,
|
||||
coordinates_id,
|
||||
depth_id,
|
||||
);
|
||||
|
||||
@@ -541,12 +1125,37 @@ impl<'w> BlockContext<'w> {
|
||||
block: &mut Block,
|
||||
) -> Result<(), Error> {
|
||||
let image_id = self.get_image_id(image);
|
||||
let coordinate_id = self.write_image_coordinates(coordinate, array_index, block)?;
|
||||
let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
|
||||
let value_id = self.cached[value];
|
||||
|
||||
block
|
||||
.body
|
||||
.push(Instruction::image_write(image_id, coordinate_id, value_id));
|
||||
let write = Store { image_id, value_id };
|
||||
|
||||
match self.writer.image_bounds_check_policy {
|
||||
crate::back::BoundsCheckPolicy::Restrict => {
|
||||
let (coords, _, _) =
|
||||
self.write_restricted_coordinates(image_id, coordinates, None, None, block)?;
|
||||
write.generate(&mut self.writer.id_gen, coords, None, None, block);
|
||||
}
|
||||
crate::back::BoundsCheckPolicy::ReadZeroSkipWrite => {
|
||||
self.write_conditional_image_access(
|
||||
image_id,
|
||||
coordinates,
|
||||
None,
|
||||
None,
|
||||
block,
|
||||
&write,
|
||||
)?;
|
||||
}
|
||||
crate::back::BoundsCheckPolicy::UndefinedBehavior => {
|
||||
write.generate(
|
||||
&mut self.writer.id_gen,
|
||||
coordinates.value_id,
|
||||
None,
|
||||
None,
|
||||
block,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub(super) enum ExpressionPointer {
|
||||
/// The results of performing a bounds check.
|
||||
///
|
||||
/// On success, `write_bounds_check` returns a value of this type.
|
||||
pub enum BoundsCheckResult {
|
||||
pub(super) enum BoundsCheckResult {
|
||||
/// The index is statically known and in bounds, with the given value.
|
||||
KnownInBounds(u32),
|
||||
|
||||
@@ -38,7 +38,7 @@ pub enum BoundsCheckResult {
|
||||
}
|
||||
|
||||
/// A value that we either know at translation time, or need to compute at runtime.
|
||||
pub enum MaybeKnown<T> {
|
||||
pub(super) enum MaybeKnown<T> {
|
||||
/// The value is known at shader translation time.
|
||||
Known(T),
|
||||
|
||||
|
||||
@@ -609,27 +609,14 @@ impl super::Instruction {
|
||||
instruction
|
||||
}
|
||||
|
||||
pub(super) fn image_fetch(
|
||||
pub(super) fn image_fetch_or_read(
|
||||
op: Op,
|
||||
result_type_id: Word,
|
||||
id: Word,
|
||||
image: Word,
|
||||
coordinates: Word,
|
||||
) -> Self {
|
||||
let mut instruction = Self::new(Op::ImageFetch);
|
||||
instruction.set_type(result_type_id);
|
||||
instruction.set_result(id);
|
||||
instruction.add_operand(image);
|
||||
instruction.add_operand(coordinates);
|
||||
instruction
|
||||
}
|
||||
|
||||
pub(super) fn image_read(
|
||||
result_type_id: Word,
|
||||
id: Word,
|
||||
image: Word,
|
||||
coordinates: Word,
|
||||
) -> Self {
|
||||
let mut instruction = Self::new(Op::ImageRead);
|
||||
let mut instruction = Self::new(op);
|
||||
instruction.set_type(result_type_id);
|
||||
instruction.set_result(id);
|
||||
instruction.add_operand(image);
|
||||
|
||||
@@ -418,6 +418,7 @@ pub struct Writer {
|
||||
annotations: Vec<Instruction>,
|
||||
flags: WriterFlags,
|
||||
index_bounds_check_policy: BoundsCheckPolicy,
|
||||
image_bounds_check_policy: BoundsCheckPolicy,
|
||||
void_type: Word,
|
||||
//TODO: convert most of these into vectors, addressable by handle indices
|
||||
lookup_type: crate::FastHashMap<LookupType, Word>,
|
||||
@@ -462,6 +463,9 @@ pub struct Options {
|
||||
/// How should the generated code handle array, vector, or matrix indices
|
||||
/// that are out of range?
|
||||
pub index_bounds_check_policy: BoundsCheckPolicy,
|
||||
/// How should the generated code handle image references that are out of
|
||||
/// range?
|
||||
pub image_bounds_check_policy: BoundsCheckPolicy,
|
||||
}
|
||||
|
||||
impl Default for Options {
|
||||
@@ -475,6 +479,7 @@ impl Default for Options {
|
||||
flags,
|
||||
capabilities: None,
|
||||
index_bounds_check_policy: super::BoundsCheckPolicy::default(),
|
||||
image_bounds_check_policy: super::BoundsCheckPolicy::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,8 +55,6 @@
|
||||
//! pointer for the duration of its lifetime. To obtain the block for generating
|
||||
//! code in the selection's body, call the `Selection::block` method.
|
||||
|
||||
#![allow(dead_code)] // until texture bounds checks
|
||||
|
||||
use super::{Block, BlockContext, Instruction};
|
||||
use spirv::Word;
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ impl Writer {
|
||||
annotations: vec![],
|
||||
flags: options.flags,
|
||||
index_bounds_check_policy: options.index_bounds_check_policy,
|
||||
image_bounds_check_policy: options.image_bounds_check_policy,
|
||||
void_type,
|
||||
lookup_type: crate::FastHashMap::default(),
|
||||
lookup_function: crate::FastHashMap::default(),
|
||||
@@ -103,6 +104,7 @@ impl Writer {
|
||||
// Copied from the old Writer:
|
||||
flags: self.flags,
|
||||
index_bounds_check_policy: self.index_bounds_check_policy,
|
||||
image_bounds_check_policy: self.image_bounds_check_policy,
|
||||
capabilities_available: take(&mut self.capabilities_available),
|
||||
|
||||
// Initialized afresh:
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -1044,7 +1044,22 @@ pub enum Expression {
|
||||
level: SampleLevel,
|
||||
depth_ref: Option<Handle<Expression>>,
|
||||
},
|
||||
|
||||
/// Load a texel from an image.
|
||||
///
|
||||
/// For most images, this returns a four-element vector of the same
|
||||
/// [`ScalarKind`] as the image. If the format of the image does not have
|
||||
/// four components, default values are provided: the first three components
|
||||
/// (typically R, G, and B) default to zero, and the final component
|
||||
/// (typically alpha) defaults to one.
|
||||
///
|
||||
/// However, if the image's [`class`] is [`Depth`], then this returns a
|
||||
/// [`Float`] scalar value.
|
||||
///
|
||||
/// [`ScalarKind`]: ScalarKind
|
||||
/// [`class`]: TypeInner::Image::class
|
||||
/// [`Depth`]: ImageClass::Depth
|
||||
/// [`Float`]: ScalarKind::Float
|
||||
ImageLoad {
|
||||
/// The image to load a texel from. This must have type [`Image`]. (This
|
||||
/// will necessarily be a [`GlobalVariable`] or [`FunctionArgument`]
|
||||
@@ -1110,6 +1125,7 @@ pub enum Expression {
|
||||
/// [`multi`]: ImageClass::Sampled::multi
|
||||
index: Option<Handle<Expression>>,
|
||||
},
|
||||
|
||||
/// Query information from an image.
|
||||
ImageQuery {
|
||||
image: Handle<Expression>,
|
||||
|
||||
5
tests/in/bounds-check-image-restrict.param.ron
Normal file
5
tests/in/bounds-check-image-restrict.param.ron
Normal file
@@ -0,0 +1,5 @@
|
||||
(
|
||||
image_bounds_check_restrict: true,
|
||||
spv_version: (1, 1),
|
||||
spv_debug: true,
|
||||
)
|
||||
83
tests/in/bounds-check-image-restrict.wgsl
Normal file
83
tests/in/bounds-check-image-restrict.wgsl
Normal file
@@ -0,0 +1,83 @@
|
||||
[[group(0), binding(0)]]
|
||||
var image_1d: texture_1d<f32>;
|
||||
|
||||
fn test_textureLoad_1d(coords: i32, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_1d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_2d: texture_2d<f32>;
|
||||
|
||||
fn test_textureLoad_2d(coords: vec2<i32>, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_2d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_2d_array: texture_2d_array<f32>;
|
||||
|
||||
fn test_textureLoad_2d_array(coords: vec2<i32>, index: i32, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_2d_array, coords, index, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_3d: texture_3d<f32>;
|
||||
|
||||
fn test_textureLoad_3d(coords: vec3<i32>, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_3d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_multisampled_2d: texture_multisampled_2d<f32>;
|
||||
|
||||
fn test_textureLoad_multisampled_2d(coords: vec2<i32>, sample: i32) -> vec4<f32> {
|
||||
return textureLoad(image_multisampled_2d, coords, sample);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_2d: texture_depth_2d;
|
||||
|
||||
fn test_textureLoad_depth_2d(coords: vec2<i32>, level: i32) -> f32 {
|
||||
return textureLoad(image_depth_2d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_2d_array: texture_depth_2d_array;
|
||||
|
||||
fn test_textureLoad_depth_2d_array(coords: vec2<i32>, index: i32, level: i32) -> f32 {
|
||||
return textureLoad(image_depth_2d_array, coords, index, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_multisampled_2d: texture_depth_multisampled_2d;
|
||||
|
||||
fn test_textureLoad_depth_multisampled_2d(coords: vec2<i32>, sample: i32) -> f32 {
|
||||
return textureLoad(image_depth_multisampled_2d, coords, sample);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_1d: texture_storage_1d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_1d(coords: i32, value: vec4<f32>) {
|
||||
textureStore(image_storage_1d, coords, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_2d: texture_storage_2d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_2d(coords: vec2<i32>, value: vec4<f32>) {
|
||||
textureStore(image_storage_2d, coords, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_2d_array: texture_storage_2d_array<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_2d_array(coords: vec2<i32>, array_index: i32, value: vec4<f32>) {
|
||||
textureStore(image_storage_2d_array, coords, array_index, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_3d: texture_storage_3d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_3d(coords: vec3<i32>, value: vec4<f32>) {
|
||||
textureStore(image_storage_3d, coords, value);
|
||||
}
|
||||
5
tests/in/bounds-check-image-rzsw.param.ron
Normal file
5
tests/in/bounds-check-image-rzsw.param.ron
Normal file
@@ -0,0 +1,5 @@
|
||||
(
|
||||
image_bounds_check_read_zero_skip_write: true,
|
||||
spv_version: (1, 1),
|
||||
spv_debug: true,
|
||||
)
|
||||
83
tests/in/bounds-check-image-rzsw.wgsl
Normal file
83
tests/in/bounds-check-image-rzsw.wgsl
Normal file
@@ -0,0 +1,83 @@
|
||||
[[group(0), binding(0)]]
|
||||
var image_1d: texture_1d<f32>;
|
||||
|
||||
fn test_textureLoad_1d(coords: i32, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_1d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_2d: texture_2d<f32>;
|
||||
|
||||
fn test_textureLoad_2d(coords: vec2<i32>, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_2d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_2d_array: texture_2d_array<f32>;
|
||||
|
||||
fn test_textureLoad_2d_array(coords: vec2<i32>, index: i32, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_2d_array, coords, index, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_3d: texture_3d<f32>;
|
||||
|
||||
fn test_textureLoad_3d(coords: vec3<i32>, level: i32) -> vec4<f32> {
|
||||
return textureLoad(image_3d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_multisampled_2d: texture_multisampled_2d<f32>;
|
||||
|
||||
fn test_textureLoad_multisampled_2d(coords: vec2<i32>, sample: i32) -> vec4<f32> {
|
||||
return textureLoad(image_multisampled_2d, coords, sample);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_2d: texture_depth_2d;
|
||||
|
||||
fn test_textureLoad_depth_2d(coords: vec2<i32>, level: i32) -> f32 {
|
||||
return textureLoad(image_depth_2d, coords, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_2d_array: texture_depth_2d_array;
|
||||
|
||||
fn test_textureLoad_depth_2d_array(coords: vec2<i32>, index: i32, level: i32) -> f32 {
|
||||
return textureLoad(image_depth_2d_array, coords, index, level);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_depth_multisampled_2d: texture_depth_multisampled_2d;
|
||||
|
||||
fn test_textureLoad_depth_multisampled_2d(coords: vec2<i32>, sample: i32) -> f32 {
|
||||
return textureLoad(image_depth_multisampled_2d, coords, sample);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_1d: texture_storage_1d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_1d(coords: i32, value: vec4<f32>) {
|
||||
textureStore(image_storage_1d, coords, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_2d: texture_storage_2d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_2d(coords: vec2<i32>, value: vec4<f32>) {
|
||||
textureStore(image_storage_2d, coords, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_2d_array: texture_storage_2d_array<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_2d_array(coords: vec2<i32>, array_index: i32, value: vec4<f32>) {
|
||||
textureStore(image_storage_2d_array, coords, array_index, value);
|
||||
}
|
||||
|
||||
[[group(0), binding(0)]]
|
||||
var image_storage_3d: texture_storage_3d<rgba8unorm, write>;
|
||||
|
||||
fn test_textureStore_3d(coords: vec3<i32>, value: vec4<f32>) {
|
||||
textureStore(image_storage_3d, coords, value);
|
||||
}
|
||||
316
tests/out/spv/bounds-check-image-restrict.spvasm
Normal file
316
tests/out/spv/bounds-check-image-restrict.spvasm
Normal file
@@ -0,0 +1,316 @@
|
||||
; SPIR-V
|
||||
; Version: 1.1
|
||||
; Generator: rspirv
|
||||
; Bound: 214
|
||||
OpCapability ImageQuery
|
||||
OpCapability Image1D
|
||||
OpCapability Shader
|
||||
OpCapability Sampled1D
|
||||
OpCapability Linkage
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpSource GLSL 450
|
||||
OpName %20 "image_1d"
|
||||
OpName %22 "image_2d"
|
||||
OpName %24 "image_2d_array"
|
||||
OpName %26 "image_3d"
|
||||
OpName %28 "image_multisampled_2d"
|
||||
OpName %30 "image_depth_2d"
|
||||
OpName %32 "image_depth_2d_array"
|
||||
OpName %34 "image_depth_multisampled_2d"
|
||||
OpName %36 "image_storage_1d"
|
||||
OpName %38 "image_storage_2d"
|
||||
OpName %40 "image_storage_2d_array"
|
||||
OpName %42 "image_storage_3d"
|
||||
OpName %47 "test_textureLoad_1d"
|
||||
OpName %62 "test_textureLoad_2d"
|
||||
OpName %78 "test_textureLoad_2d_array"
|
||||
OpName %94 "test_textureLoad_3d"
|
||||
OpName %109 "test_textureLoad_multisampled_2d"
|
||||
OpName %123 "test_textureLoad_depth_2d"
|
||||
OpName %140 "test_textureLoad_depth_2d_array"
|
||||
OpName %157 "test_textureLoad_depth_multisampled_2d"
|
||||
OpName %172 "test_textureStore_1d"
|
||||
OpName %182 "test_textureStore_2d"
|
||||
OpName %194 "test_textureStore_2d_array"
|
||||
OpName %206 "test_textureStore_3d"
|
||||
OpDecorate %20 DescriptorSet 0
|
||||
OpDecorate %20 Binding 0
|
||||
OpDecorate %22 DescriptorSet 0
|
||||
OpDecorate %22 Binding 0
|
||||
OpDecorate %24 DescriptorSet 0
|
||||
OpDecorate %24 Binding 0
|
||||
OpDecorate %26 DescriptorSet 0
|
||||
OpDecorate %26 Binding 0
|
||||
OpDecorate %28 DescriptorSet 0
|
||||
OpDecorate %28 Binding 0
|
||||
OpDecorate %30 DescriptorSet 0
|
||||
OpDecorate %30 Binding 0
|
||||
OpDecorate %32 DescriptorSet 0
|
||||
OpDecorate %32 Binding 0
|
||||
OpDecorate %34 DescriptorSet 0
|
||||
OpDecorate %34 Binding 0
|
||||
OpDecorate %36 NonReadable
|
||||
OpDecorate %36 DescriptorSet 0
|
||||
OpDecorate %36 Binding 0
|
||||
OpDecorate %38 NonReadable
|
||||
OpDecorate %38 DescriptorSet 0
|
||||
OpDecorate %38 Binding 0
|
||||
OpDecorate %40 NonReadable
|
||||
OpDecorate %40 DescriptorSet 0
|
||||
OpDecorate %40 Binding 0
|
||||
OpDecorate %42 NonReadable
|
||||
OpDecorate %42 DescriptorSet 0
|
||||
OpDecorate %42 Binding 0
|
||||
%2 = OpTypeVoid
|
||||
%4 = OpTypeFloat 32
|
||||
%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
|
||||
%5 = OpTypeInt 32 1
|
||||
%6 = OpTypeVector %4 4
|
||||
%7 = OpTypeImage %4 2D 0 0 0 1 Unknown
|
||||
%8 = OpTypeVector %5 2
|
||||
%9 = OpTypeImage %4 2D 0 1 0 1 Unknown
|
||||
%10 = OpTypeImage %4 3D 0 0 0 1 Unknown
|
||||
%11 = OpTypeVector %5 3
|
||||
%12 = OpTypeImage %4 2D 0 0 1 1 Unknown
|
||||
%13 = OpTypeImage %4 2D 1 0 0 1 Unknown
|
||||
%14 = OpTypeImage %4 2D 1 1 0 1 Unknown
|
||||
%15 = OpTypeImage %4 2D 1 0 1 1 Unknown
|
||||
%16 = OpTypeImage %4 1D 0 0 0 2 Rgba8
|
||||
%17 = OpTypeImage %4 2D 0 0 0 2 Rgba8
|
||||
%18 = OpTypeImage %4 2D 0 1 0 2 Rgba8
|
||||
%19 = OpTypeImage %4 3D 0 0 0 2 Rgba8
|
||||
%21 = OpTypePointer UniformConstant %3
|
||||
%20 = OpVariable %21 UniformConstant
|
||||
%23 = OpTypePointer UniformConstant %7
|
||||
%22 = OpVariable %23 UniformConstant
|
||||
%25 = OpTypePointer UniformConstant %9
|
||||
%24 = OpVariable %25 UniformConstant
|
||||
%27 = OpTypePointer UniformConstant %10
|
||||
%26 = OpVariable %27 UniformConstant
|
||||
%29 = OpTypePointer UniformConstant %12
|
||||
%28 = OpVariable %29 UniformConstant
|
||||
%31 = OpTypePointer UniformConstant %13
|
||||
%30 = OpVariable %31 UniformConstant
|
||||
%33 = OpTypePointer UniformConstant %14
|
||||
%32 = OpVariable %33 UniformConstant
|
||||
%35 = OpTypePointer UniformConstant %15
|
||||
%34 = OpVariable %35 UniformConstant
|
||||
%37 = OpTypePointer UniformConstant %16
|
||||
%36 = OpVariable %37 UniformConstant
|
||||
%39 = OpTypePointer UniformConstant %17
|
||||
%38 = OpVariable %39 UniformConstant
|
||||
%41 = OpTypePointer UniformConstant %18
|
||||
%40 = OpVariable %41 UniformConstant
|
||||
%43 = OpTypePointer UniformConstant %19
|
||||
%42 = OpVariable %43 UniformConstant
|
||||
%48 = OpTypeFunction %6 %5 %5
|
||||
%52 = OpConstant %5 1
|
||||
%63 = OpTypeFunction %6 %8 %5
|
||||
%70 = OpConstantComposite %8 %52 %52
|
||||
%79 = OpTypeFunction %6 %8 %5 %5
|
||||
%87 = OpConstantComposite %11 %52 %52 %52
|
||||
%95 = OpTypeFunction %6 %11 %5
|
||||
%102 = OpConstantComposite %11 %52 %52 %52
|
||||
%116 = OpConstantComposite %8 %52 %52
|
||||
%124 = OpTypeFunction %4 %8 %5
|
||||
%131 = OpConstantComposite %8 %52 %52
|
||||
%141 = OpTypeFunction %4 %8 %5 %5
|
||||
%149 = OpConstantComposite %11 %52 %52 %52
|
||||
%164 = OpConstantComposite %8 %52 %52
|
||||
%173 = OpTypeFunction %2 %5 %6
|
||||
%183 = OpTypeFunction %2 %8 %6
|
||||
%187 = OpConstantComposite %8 %52 %52
|
||||
%195 = OpTypeFunction %2 %8 %5 %6
|
||||
%200 = OpConstantComposite %11 %52 %52 %52
|
||||
%207 = OpTypeFunction %2 %11 %6
|
||||
%211 = OpConstantComposite %11 %52 %52 %52
|
||||
%47 = OpFunction %6 None %48
|
||||
%45 = OpFunctionParameter %5
|
||||
%46 = OpFunctionParameter %5
|
||||
%44 = OpLabel
|
||||
%49 = OpLoad %3 %20
|
||||
OpBranch %50
|
||||
%50 = OpLabel
|
||||
%51 = OpImageQueryLevels %5 %49
|
||||
%53 = OpISub %5 %51 %52
|
||||
%54 = OpExtInst %5 %1 UMin %46 %53
|
||||
%55 = OpImageQuerySizeLod %5 %49 %54
|
||||
%56 = OpISub %5 %55 %52
|
||||
%57 = OpExtInst %5 %1 UMin %45 %56
|
||||
%58 = OpImageFetch %6 %49 %57 Lod %54
|
||||
OpReturnValue %58
|
||||
OpFunctionEnd
|
||||
%62 = OpFunction %6 None %63
|
||||
%60 = OpFunctionParameter %8
|
||||
%61 = OpFunctionParameter %5
|
||||
%59 = OpLabel
|
||||
%64 = OpLoad %7 %22
|
||||
OpBranch %65
|
||||
%65 = OpLabel
|
||||
%66 = OpImageQueryLevels %5 %64
|
||||
%67 = OpISub %5 %66 %52
|
||||
%68 = OpExtInst %5 %1 UMin %61 %67
|
||||
%69 = OpImageQuerySizeLod %8 %64 %68
|
||||
%71 = OpISub %8 %69 %70
|
||||
%72 = OpExtInst %8 %1 UMin %60 %71
|
||||
%73 = OpImageFetch %6 %64 %72 Lod %68
|
||||
OpReturnValue %73
|
||||
OpFunctionEnd
|
||||
%78 = OpFunction %6 None %79
|
||||
%75 = OpFunctionParameter %8
|
||||
%76 = OpFunctionParameter %5
|
||||
%77 = OpFunctionParameter %5
|
||||
%74 = OpLabel
|
||||
%80 = OpLoad %9 %24
|
||||
OpBranch %81
|
||||
%81 = OpLabel
|
||||
%82 = OpCompositeConstruct %11 %75 %76
|
||||
%83 = OpImageQueryLevels %5 %80
|
||||
%84 = OpISub %5 %83 %52
|
||||
%85 = OpExtInst %5 %1 UMin %77 %84
|
||||
%86 = OpImageQuerySizeLod %11 %80 %85
|
||||
%88 = OpISub %11 %86 %87
|
||||
%89 = OpExtInst %11 %1 UMin %82 %88
|
||||
%90 = OpImageFetch %6 %80 %89 Lod %85
|
||||
OpReturnValue %90
|
||||
OpFunctionEnd
|
||||
%94 = OpFunction %6 None %95
|
||||
%92 = OpFunctionParameter %11
|
||||
%93 = OpFunctionParameter %5
|
||||
%91 = OpLabel
|
||||
%96 = OpLoad %10 %26
|
||||
OpBranch %97
|
||||
%97 = OpLabel
|
||||
%98 = OpImageQueryLevels %5 %96
|
||||
%99 = OpISub %5 %98 %52
|
||||
%100 = OpExtInst %5 %1 UMin %93 %99
|
||||
%101 = OpImageQuerySizeLod %11 %96 %100
|
||||
%103 = OpISub %11 %101 %102
|
||||
%104 = OpExtInst %11 %1 UMin %92 %103
|
||||
%105 = OpImageFetch %6 %96 %104 Lod %100
|
||||
OpReturnValue %105
|
||||
OpFunctionEnd
|
||||
%109 = OpFunction %6 None %63
|
||||
%107 = OpFunctionParameter %8
|
||||
%108 = OpFunctionParameter %5
|
||||
%106 = OpLabel
|
||||
%110 = OpLoad %12 %28
|
||||
OpBranch %111
|
||||
%111 = OpLabel
|
||||
%112 = OpImageQuerySamples %5 %110
|
||||
%113 = OpISub %5 %112 %52
|
||||
%114 = OpExtInst %5 %1 UMin %108 %113
|
||||
%115 = OpImageQuerySize %8 %110
|
||||
%117 = OpISub %8 %115 %116
|
||||
%118 = OpExtInst %8 %1 UMin %107 %117
|
||||
%119 = OpImageFetch %6 %110 %118 Sample %114
|
||||
OpReturnValue %119
|
||||
OpFunctionEnd
|
||||
%123 = OpFunction %4 None %124
|
||||
%121 = OpFunctionParameter %8
|
||||
%122 = OpFunctionParameter %5
|
||||
%120 = OpLabel
|
||||
%125 = OpLoad %13 %30
|
||||
OpBranch %126
|
||||
%126 = OpLabel
|
||||
%127 = OpImageQueryLevels %5 %125
|
||||
%128 = OpISub %5 %127 %52
|
||||
%129 = OpExtInst %5 %1 UMin %122 %128
|
||||
%130 = OpImageQuerySizeLod %8 %125 %129
|
||||
%132 = OpISub %8 %130 %131
|
||||
%133 = OpExtInst %8 %1 UMin %121 %132
|
||||
%134 = OpImageFetch %6 %125 %133 Lod %129
|
||||
%135 = OpCompositeExtract %4 %134 0
|
||||
OpReturnValue %135
|
||||
OpFunctionEnd
|
||||
%140 = OpFunction %4 None %141
|
||||
%137 = OpFunctionParameter %8
|
||||
%138 = OpFunctionParameter %5
|
||||
%139 = OpFunctionParameter %5
|
||||
%136 = OpLabel
|
||||
%142 = OpLoad %14 %32
|
||||
OpBranch %143
|
||||
%143 = OpLabel
|
||||
%144 = OpCompositeConstruct %11 %137 %138
|
||||
%145 = OpImageQueryLevels %5 %142
|
||||
%146 = OpISub %5 %145 %52
|
||||
%147 = OpExtInst %5 %1 UMin %139 %146
|
||||
%148 = OpImageQuerySizeLod %11 %142 %147
|
||||
%150 = OpISub %11 %148 %149
|
||||
%151 = OpExtInst %11 %1 UMin %144 %150
|
||||
%152 = OpImageFetch %6 %142 %151 Lod %147
|
||||
%153 = OpCompositeExtract %4 %152 0
|
||||
OpReturnValue %153
|
||||
OpFunctionEnd
|
||||
%157 = OpFunction %4 None %124
|
||||
%155 = OpFunctionParameter %8
|
||||
%156 = OpFunctionParameter %5
|
||||
%154 = OpLabel
|
||||
%158 = OpLoad %15 %34
|
||||
OpBranch %159
|
||||
%159 = OpLabel
|
||||
%160 = OpImageQuerySamples %5 %158
|
||||
%161 = OpISub %5 %160 %52
|
||||
%162 = OpExtInst %5 %1 UMin %156 %161
|
||||
%163 = OpImageQuerySize %8 %158
|
||||
%165 = OpISub %8 %163 %164
|
||||
%166 = OpExtInst %8 %1 UMin %155 %165
|
||||
%167 = OpImageFetch %6 %158 %166 Sample %162
|
||||
%168 = OpCompositeExtract %4 %167 0
|
||||
OpReturnValue %168
|
||||
OpFunctionEnd
|
||||
%172 = OpFunction %2 None %173
|
||||
%170 = OpFunctionParameter %5
|
||||
%171 = OpFunctionParameter %6
|
||||
%169 = OpLabel
|
||||
%174 = OpLoad %16 %36
|
||||
OpBranch %175
|
||||
%175 = OpLabel
|
||||
%176 = OpImageQuerySize %5 %174
|
||||
%177 = OpISub %5 %176 %52
|
||||
%178 = OpExtInst %5 %1 UMin %170 %177
|
||||
OpImageWrite %174 %178 %171
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%182 = OpFunction %2 None %183
|
||||
%180 = OpFunctionParameter %8
|
||||
%181 = OpFunctionParameter %6
|
||||
%179 = OpLabel
|
||||
%184 = OpLoad %17 %38
|
||||
OpBranch %185
|
||||
%185 = OpLabel
|
||||
%186 = OpImageQuerySize %8 %184
|
||||
%188 = OpISub %8 %186 %187
|
||||
%189 = OpExtInst %8 %1 UMin %180 %188
|
||||
OpImageWrite %184 %189 %181
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%194 = OpFunction %2 None %195
|
||||
%191 = OpFunctionParameter %8
|
||||
%192 = OpFunctionParameter %5
|
||||
%193 = OpFunctionParameter %6
|
||||
%190 = OpLabel
|
||||
%196 = OpLoad %18 %40
|
||||
OpBranch %197
|
||||
%197 = OpLabel
|
||||
%198 = OpCompositeConstruct %11 %191 %192
|
||||
%199 = OpImageQuerySize %11 %196
|
||||
%201 = OpISub %11 %199 %200
|
||||
%202 = OpExtInst %11 %1 UMin %198 %201
|
||||
OpImageWrite %196 %202 %193
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%206 = OpFunction %2 None %207
|
||||
%204 = OpFunctionParameter %11
|
||||
%205 = OpFunctionParameter %6
|
||||
%203 = OpLabel
|
||||
%208 = OpLoad %19 %42
|
||||
OpBranch %209
|
||||
%209 = OpLabel
|
||||
%210 = OpImageQuerySize %11 %208
|
||||
%212 = OpISub %11 %210 %211
|
||||
%213 = OpExtInst %11 %1 UMin %204 %212
|
||||
OpImageWrite %208 %213 %205
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
390
tests/out/spv/bounds-check-image-rzsw.spvasm
Normal file
390
tests/out/spv/bounds-check-image-rzsw.spvasm
Normal file
@@ -0,0 +1,390 @@
|
||||
; SPIR-V
|
||||
; Version: 1.1
|
||||
; Generator: rspirv
|
||||
; Bound: 244
|
||||
OpCapability ImageQuery
|
||||
OpCapability Image1D
|
||||
OpCapability Shader
|
||||
OpCapability Sampled1D
|
||||
OpCapability Linkage
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpSource GLSL 450
|
||||
OpName %20 "image_1d"
|
||||
OpName %22 "image_2d"
|
||||
OpName %24 "image_2d_array"
|
||||
OpName %26 "image_3d"
|
||||
OpName %28 "image_multisampled_2d"
|
||||
OpName %30 "image_depth_2d"
|
||||
OpName %32 "image_depth_2d_array"
|
||||
OpName %34 "image_depth_multisampled_2d"
|
||||
OpName %36 "image_storage_1d"
|
||||
OpName %38 "image_storage_2d"
|
||||
OpName %40 "image_storage_2d_array"
|
||||
OpName %42 "image_storage_3d"
|
||||
OpName %47 "test_textureLoad_1d"
|
||||
OpName %65 "test_textureLoad_2d"
|
||||
OpName %85 "test_textureLoad_2d_array"
|
||||
OpName %105 "test_textureLoad_3d"
|
||||
OpName %123 "test_textureLoad_multisampled_2d"
|
||||
OpName %140 "test_textureLoad_depth_2d"
|
||||
OpName %160 "test_textureLoad_depth_2d_array"
|
||||
OpName %180 "test_textureLoad_depth_multisampled_2d"
|
||||
OpName %198 "test_textureStore_1d"
|
||||
OpName %209 "test_textureStore_2d"
|
||||
OpName %222 "test_textureStore_2d_array"
|
||||
OpName %235 "test_textureStore_3d"
|
||||
OpDecorate %20 DescriptorSet 0
|
||||
OpDecorate %20 Binding 0
|
||||
OpDecorate %22 DescriptorSet 0
|
||||
OpDecorate %22 Binding 0
|
||||
OpDecorate %24 DescriptorSet 0
|
||||
OpDecorate %24 Binding 0
|
||||
OpDecorate %26 DescriptorSet 0
|
||||
OpDecorate %26 Binding 0
|
||||
OpDecorate %28 DescriptorSet 0
|
||||
OpDecorate %28 Binding 0
|
||||
OpDecorate %30 DescriptorSet 0
|
||||
OpDecorate %30 Binding 0
|
||||
OpDecorate %32 DescriptorSet 0
|
||||
OpDecorate %32 Binding 0
|
||||
OpDecorate %34 DescriptorSet 0
|
||||
OpDecorate %34 Binding 0
|
||||
OpDecorate %36 NonReadable
|
||||
OpDecorate %36 DescriptorSet 0
|
||||
OpDecorate %36 Binding 0
|
||||
OpDecorate %38 NonReadable
|
||||
OpDecorate %38 DescriptorSet 0
|
||||
OpDecorate %38 Binding 0
|
||||
OpDecorate %40 NonReadable
|
||||
OpDecorate %40 DescriptorSet 0
|
||||
OpDecorate %40 Binding 0
|
||||
OpDecorate %42 NonReadable
|
||||
OpDecorate %42 DescriptorSet 0
|
||||
OpDecorate %42 Binding 0
|
||||
%2 = OpTypeVoid
|
||||
%4 = OpTypeFloat 32
|
||||
%3 = OpTypeImage %4 1D 0 0 0 1 Unknown
|
||||
%5 = OpTypeInt 32 1
|
||||
%6 = OpTypeVector %4 4
|
||||
%7 = OpTypeImage %4 2D 0 0 0 1 Unknown
|
||||
%8 = OpTypeVector %5 2
|
||||
%9 = OpTypeImage %4 2D 0 1 0 1 Unknown
|
||||
%10 = OpTypeImage %4 3D 0 0 0 1 Unknown
|
||||
%11 = OpTypeVector %5 3
|
||||
%12 = OpTypeImage %4 2D 0 0 1 1 Unknown
|
||||
%13 = OpTypeImage %4 2D 1 0 0 1 Unknown
|
||||
%14 = OpTypeImage %4 2D 1 1 0 1 Unknown
|
||||
%15 = OpTypeImage %4 2D 1 0 1 1 Unknown
|
||||
%16 = OpTypeImage %4 1D 0 0 0 2 Rgba8
|
||||
%17 = OpTypeImage %4 2D 0 0 0 2 Rgba8
|
||||
%18 = OpTypeImage %4 2D 0 1 0 2 Rgba8
|
||||
%19 = OpTypeImage %4 3D 0 0 0 2 Rgba8
|
||||
%21 = OpTypePointer UniformConstant %3
|
||||
%20 = OpVariable %21 UniformConstant
|
||||
%23 = OpTypePointer UniformConstant %7
|
||||
%22 = OpVariable %23 UniformConstant
|
||||
%25 = OpTypePointer UniformConstant %9
|
||||
%24 = OpVariable %25 UniformConstant
|
||||
%27 = OpTypePointer UniformConstant %10
|
||||
%26 = OpVariable %27 UniformConstant
|
||||
%29 = OpTypePointer UniformConstant %12
|
||||
%28 = OpVariable %29 UniformConstant
|
||||
%31 = OpTypePointer UniformConstant %13
|
||||
%30 = OpVariable %31 UniformConstant
|
||||
%33 = OpTypePointer UniformConstant %14
|
||||
%32 = OpVariable %33 UniformConstant
|
||||
%35 = OpTypePointer UniformConstant %15
|
||||
%34 = OpVariable %35 UniformConstant
|
||||
%37 = OpTypePointer UniformConstant %16
|
||||
%36 = OpVariable %37 UniformConstant
|
||||
%39 = OpTypePointer UniformConstant %17
|
||||
%38 = OpVariable %39 UniformConstant
|
||||
%41 = OpTypePointer UniformConstant %18
|
||||
%40 = OpVariable %41 UniformConstant
|
||||
%43 = OpTypePointer UniformConstant %19
|
||||
%42 = OpVariable %43 UniformConstant
|
||||
%48 = OpTypeFunction %6 %5 %5
|
||||
%51 = OpTypeBool
|
||||
%52 = OpConstantNull %6
|
||||
%66 = OpTypeFunction %6 %8 %5
|
||||
%69 = OpConstantNull %6
|
||||
%75 = OpTypeVector %51 2
|
||||
%86 = OpTypeFunction %6 %8 %5 %5
|
||||
%90 = OpConstantNull %6
|
||||
%96 = OpTypeVector %51 3
|
||||
%106 = OpTypeFunction %6 %11 %5
|
||||
%109 = OpConstantNull %6
|
||||
%126 = OpConstantNull %6
|
||||
%141 = OpTypeFunction %4 %8 %5
|
||||
%144 = OpConstantNull %6
|
||||
%161 = OpTypeFunction %4 %8 %5 %5
|
||||
%165 = OpConstantNull %6
|
||||
%183 = OpConstantNull %6
|
||||
%199 = OpTypeFunction %2 %5 %6
|
||||
%210 = OpTypeFunction %2 %8 %6
|
||||
%223 = OpTypeFunction %2 %8 %5 %6
|
||||
%236 = OpTypeFunction %2 %11 %6
|
||||
%47 = OpFunction %6 None %48
|
||||
%45 = OpFunctionParameter %5
|
||||
%46 = OpFunctionParameter %5
|
||||
%44 = OpLabel
|
||||
%49 = OpLoad %3 %20
|
||||
OpBranch %50
|
||||
%50 = OpLabel
|
||||
%53 = OpImageQueryLevels %5 %49
|
||||
%54 = OpULessThan %51 %46 %53
|
||||
OpSelectionMerge %55 None
|
||||
OpBranchConditional %54 %56 %55
|
||||
%56 = OpLabel
|
||||
%57 = OpImageQuerySizeLod %5 %49 %46
|
||||
%58 = OpULessThan %51 %45 %57
|
||||
OpBranchConditional %58 %59 %55
|
||||
%59 = OpLabel
|
||||
%60 = OpImageFetch %6 %49 %45 Lod %46
|
||||
OpBranch %55
|
||||
%55 = OpLabel
|
||||
%61 = OpPhi %6 %52 %50 %52 %56 %60 %59
|
||||
OpReturnValue %61
|
||||
OpFunctionEnd
|
||||
%65 = OpFunction %6 None %66
|
||||
%63 = OpFunctionParameter %8
|
||||
%64 = OpFunctionParameter %5
|
||||
%62 = OpLabel
|
||||
%67 = OpLoad %7 %22
|
||||
OpBranch %68
|
||||
%68 = OpLabel
|
||||
%70 = OpImageQueryLevels %5 %67
|
||||
%71 = OpULessThan %51 %64 %70
|
||||
OpSelectionMerge %72 None
|
||||
OpBranchConditional %71 %73 %72
|
||||
%73 = OpLabel
|
||||
%74 = OpImageQuerySizeLod %8 %67 %64
|
||||
%76 = OpULessThan %75 %63 %74
|
||||
%77 = OpAll %51 %76
|
||||
OpBranchConditional %77 %78 %72
|
||||
%78 = OpLabel
|
||||
%79 = OpImageFetch %6 %67 %63 Lod %64
|
||||
OpBranch %72
|
||||
%72 = OpLabel
|
||||
%80 = OpPhi %6 %69 %68 %69 %73 %79 %78
|
||||
OpReturnValue %80
|
||||
OpFunctionEnd
|
||||
%85 = OpFunction %6 None %86
|
||||
%82 = OpFunctionParameter %8
|
||||
%83 = OpFunctionParameter %5
|
||||
%84 = OpFunctionParameter %5
|
||||
%81 = OpLabel
|
||||
%87 = OpLoad %9 %24
|
||||
OpBranch %88
|
||||
%88 = OpLabel
|
||||
%89 = OpCompositeConstruct %11 %82 %83
|
||||
%91 = OpImageQueryLevels %5 %87
|
||||
%92 = OpULessThan %51 %84 %91
|
||||
OpSelectionMerge %93 None
|
||||
OpBranchConditional %92 %94 %93
|
||||
%94 = OpLabel
|
||||
%95 = OpImageQuerySizeLod %11 %87 %84
|
||||
%97 = OpULessThan %96 %89 %95
|
||||
%98 = OpAll %51 %97
|
||||
OpBranchConditional %98 %99 %93
|
||||
%99 = OpLabel
|
||||
%100 = OpImageFetch %6 %87 %89 Lod %84
|
||||
OpBranch %93
|
||||
%93 = OpLabel
|
||||
%101 = OpPhi %6 %90 %88 %90 %94 %100 %99
|
||||
OpReturnValue %101
|
||||
OpFunctionEnd
|
||||
%105 = OpFunction %6 None %106
|
||||
%103 = OpFunctionParameter %11
|
||||
%104 = OpFunctionParameter %5
|
||||
%102 = OpLabel
|
||||
%107 = OpLoad %10 %26
|
||||
OpBranch %108
|
||||
%108 = OpLabel
|
||||
%110 = OpImageQueryLevels %5 %107
|
||||
%111 = OpULessThan %51 %104 %110
|
||||
OpSelectionMerge %112 None
|
||||
OpBranchConditional %111 %113 %112
|
||||
%113 = OpLabel
|
||||
%114 = OpImageQuerySizeLod %11 %107 %104
|
||||
%115 = OpULessThan %96 %103 %114
|
||||
%116 = OpAll %51 %115
|
||||
OpBranchConditional %116 %117 %112
|
||||
%117 = OpLabel
|
||||
%118 = OpImageFetch %6 %107 %103 Lod %104
|
||||
OpBranch %112
|
||||
%112 = OpLabel
|
||||
%119 = OpPhi %6 %109 %108 %109 %113 %118 %117
|
||||
OpReturnValue %119
|
||||
OpFunctionEnd
|
||||
%123 = OpFunction %6 None %66
|
||||
%121 = OpFunctionParameter %8
|
||||
%122 = OpFunctionParameter %5
|
||||
%120 = OpLabel
|
||||
%124 = OpLoad %12 %28
|
||||
OpBranch %125
|
||||
%125 = OpLabel
|
||||
%127 = OpImageQuerySamples %5 %124
|
||||
%128 = OpULessThan %51 %122 %127
|
||||
OpSelectionMerge %129 None
|
||||
OpBranchConditional %128 %130 %129
|
||||
%130 = OpLabel
|
||||
%131 = OpImageQuerySize %8 %124
|
||||
%132 = OpULessThan %75 %121 %131
|
||||
%133 = OpAll %51 %132
|
||||
OpBranchConditional %133 %134 %129
|
||||
%134 = OpLabel
|
||||
%135 = OpImageFetch %6 %124 %121 Sample %122
|
||||
OpBranch %129
|
||||
%129 = OpLabel
|
||||
%136 = OpPhi %6 %126 %125 %126 %130 %135 %134
|
||||
OpReturnValue %136
|
||||
OpFunctionEnd
|
||||
%140 = OpFunction %4 None %141
|
||||
%138 = OpFunctionParameter %8
|
||||
%139 = OpFunctionParameter %5
|
||||
%137 = OpLabel
|
||||
%142 = OpLoad %13 %30
|
||||
OpBranch %143
|
||||
%143 = OpLabel
|
||||
%145 = OpImageQueryLevels %5 %142
|
||||
%146 = OpULessThan %51 %139 %145
|
||||
OpSelectionMerge %147 None
|
||||
OpBranchConditional %146 %148 %147
|
||||
%148 = OpLabel
|
||||
%149 = OpImageQuerySizeLod %8 %142 %139
|
||||
%150 = OpULessThan %75 %138 %149
|
||||
%151 = OpAll %51 %150
|
||||
OpBranchConditional %151 %152 %147
|
||||
%152 = OpLabel
|
||||
%153 = OpImageFetch %6 %142 %138 Lod %139
|
||||
OpBranch %147
|
||||
%147 = OpLabel
|
||||
%154 = OpPhi %6 %144 %143 %144 %148 %153 %152
|
||||
%155 = OpCompositeExtract %4 %154 0
|
||||
OpReturnValue %155
|
||||
OpFunctionEnd
|
||||
%160 = OpFunction %4 None %161
|
||||
%157 = OpFunctionParameter %8
|
||||
%158 = OpFunctionParameter %5
|
||||
%159 = OpFunctionParameter %5
|
||||
%156 = OpLabel
|
||||
%162 = OpLoad %14 %32
|
||||
OpBranch %163
|
||||
%163 = OpLabel
|
||||
%164 = OpCompositeConstruct %11 %157 %158
|
||||
%166 = OpImageQueryLevels %5 %162
|
||||
%167 = OpULessThan %51 %159 %166
|
||||
OpSelectionMerge %168 None
|
||||
OpBranchConditional %167 %169 %168
|
||||
%169 = OpLabel
|
||||
%170 = OpImageQuerySizeLod %11 %162 %159
|
||||
%171 = OpULessThan %96 %164 %170
|
||||
%172 = OpAll %51 %171
|
||||
OpBranchConditional %172 %173 %168
|
||||
%173 = OpLabel
|
||||
%174 = OpImageFetch %6 %162 %164 Lod %159
|
||||
OpBranch %168
|
||||
%168 = OpLabel
|
||||
%175 = OpPhi %6 %165 %163 %165 %169 %174 %173
|
||||
%176 = OpCompositeExtract %4 %175 0
|
||||
OpReturnValue %176
|
||||
OpFunctionEnd
|
||||
%180 = OpFunction %4 None %141
|
||||
%178 = OpFunctionParameter %8
|
||||
%179 = OpFunctionParameter %5
|
||||
%177 = OpLabel
|
||||
%181 = OpLoad %15 %34
|
||||
OpBranch %182
|
||||
%182 = OpLabel
|
||||
%184 = OpImageQuerySamples %5 %181
|
||||
%185 = OpULessThan %51 %179 %184
|
||||
OpSelectionMerge %186 None
|
||||
OpBranchConditional %185 %187 %186
|
||||
%187 = OpLabel
|
||||
%188 = OpImageQuerySize %8 %181
|
||||
%189 = OpULessThan %75 %178 %188
|
||||
%190 = OpAll %51 %189
|
||||
OpBranchConditional %190 %191 %186
|
||||
%191 = OpLabel
|
||||
%192 = OpImageFetch %6 %181 %178 Sample %179
|
||||
OpBranch %186
|
||||
%186 = OpLabel
|
||||
%193 = OpPhi %6 %183 %182 %183 %187 %192 %191
|
||||
%194 = OpCompositeExtract %4 %193 0
|
||||
OpReturnValue %194
|
||||
OpFunctionEnd
|
||||
%198 = OpFunction %2 None %199
|
||||
%196 = OpFunctionParameter %5
|
||||
%197 = OpFunctionParameter %6
|
||||
%195 = OpLabel
|
||||
%200 = OpLoad %16 %36
|
||||
OpBranch %201
|
||||
%201 = OpLabel
|
||||
%202 = OpImageQuerySize %5 %200
|
||||
%203 = OpULessThan %51 %196 %202
|
||||
OpSelectionMerge %204 None
|
||||
OpBranchConditional %203 %205 %204
|
||||
%205 = OpLabel
|
||||
OpImageWrite %200 %196 %197
|
||||
OpBranch %204
|
||||
%204 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%209 = OpFunction %2 None %210
|
||||
%207 = OpFunctionParameter %8
|
||||
%208 = OpFunctionParameter %6
|
||||
%206 = OpLabel
|
||||
%211 = OpLoad %17 %38
|
||||
OpBranch %212
|
||||
%212 = OpLabel
|
||||
%213 = OpImageQuerySize %8 %211
|
||||
%214 = OpULessThan %75 %207 %213
|
||||
%215 = OpAll %51 %214
|
||||
OpSelectionMerge %216 None
|
||||
OpBranchConditional %215 %217 %216
|
||||
%217 = OpLabel
|
||||
OpImageWrite %211 %207 %208
|
||||
OpBranch %216
|
||||
%216 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%222 = OpFunction %2 None %223
|
||||
%219 = OpFunctionParameter %8
|
||||
%220 = OpFunctionParameter %5
|
||||
%221 = OpFunctionParameter %6
|
||||
%218 = OpLabel
|
||||
%224 = OpLoad %18 %40
|
||||
OpBranch %225
|
||||
%225 = OpLabel
|
||||
%226 = OpCompositeConstruct %11 %219 %220
|
||||
%227 = OpImageQuerySize %11 %224
|
||||
%228 = OpULessThan %96 %226 %227
|
||||
%229 = OpAll %51 %228
|
||||
OpSelectionMerge %230 None
|
||||
OpBranchConditional %229 %231 %230
|
||||
%231 = OpLabel
|
||||
OpImageWrite %224 %226 %221
|
||||
OpBranch %230
|
||||
%230 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
%235 = OpFunction %2 None %236
|
||||
%233 = OpFunctionParameter %11
|
||||
%234 = OpFunctionParameter %6
|
||||
%232 = OpLabel
|
||||
%237 = OpLoad %19 %42
|
||||
OpBranch %238
|
||||
%238 = OpLabel
|
||||
%239 = OpImageQuerySize %11 %237
|
||||
%240 = OpULessThan %96 %233 %239
|
||||
%241 = OpAll %51 %240
|
||||
OpSelectionMerge %242 None
|
||||
OpBranchConditional %241 %243 %242
|
||||
%243 = OpLabel
|
||||
OpImageWrite %237 %233 %234
|
||||
OpBranch %242
|
||||
%242 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
@@ -31,6 +31,10 @@ struct Parameters {
|
||||
bounds_check_read_zero_skip_write: bool,
|
||||
#[serde(default)]
|
||||
bounds_check_restrict: bool,
|
||||
#[serde(default)]
|
||||
image_bounds_check_restrict: bool,
|
||||
#[serde(default)]
|
||||
image_bounds_check_read_zero_skip_write: bool,
|
||||
|
||||
#[cfg_attr(not(feature = "spv-out"), allow(dead_code))]
|
||||
spv_version: (u8, u8),
|
||||
@@ -172,6 +176,14 @@ fn write_output_spv(
|
||||
} else {
|
||||
naga::back::BoundsCheckPolicy::UndefinedBehavior
|
||||
},
|
||||
image_bounds_check_policy: if params.image_bounds_check_restrict {
|
||||
naga::back::BoundsCheckPolicy::Restrict
|
||||
} else if params.image_bounds_check_read_zero_skip_write {
|
||||
naga::back::BoundsCheckPolicy::ReadZeroSkipWrite
|
||||
} else {
|
||||
naga::back::BoundsCheckPolicy::UndefinedBehavior
|
||||
},
|
||||
..spv::Options::default()
|
||||
};
|
||||
|
||||
let spv = spv::write_vec(module, info, &options).unwrap();
|
||||
@@ -442,6 +454,8 @@ fn convert_wgsl() {
|
||||
Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL,
|
||||
),
|
||||
("bounds-check-zero", Targets::SPIRV),
|
||||
("bounds-check-image-restrict", Targets::SPIRV),
|
||||
("bounds-check-image-rzsw", Targets::SPIRV),
|
||||
(
|
||||
"texture-arg",
|
||||
Targets::SPIRV | Targets::METAL | Targets::GLSL | Targets::HLSL | Targets::WGSL,
|
||||
|
||||
Reference in New Issue
Block a user