[spv-out] Implement BoundsCheckPolicy for image access

This commit is contained in:
Jim Blandy
2021-08-14 10:27:45 -07:00
committed by Dzmitry Malyshau
parent c6ecd973e7
commit 901e2c0694
15 changed files with 1634 additions and 109 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
(
image_bounds_check_restrict: true,
spv_version: (1, 1),
spv_debug: true,
)

View 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);
}

View File

@@ -0,0 +1,5 @@
(
image_bounds_check_read_zero_skip_write: true,
spv_version: (1, 1),
spv_debug: true,
)

View 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);
}

View 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

View 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

View File

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