diff --git a/cli/src/main.rs b/cli/src/main.rs index 8930791d3d..372a8303bf 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -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, + /// what policy to use for texture bounds checking. + /// + /// May be `Restrict`, `ReadZeroSkipWrite`, or `UndefinedBehavior` + #[argh(option)] + image_bounds_check_policy: Option, + /// directory to dump the SPIR-V flow dump to #[argh(option)] flow_dir: Option, @@ -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, spv_adjust_coordinate_space: bool, spv_flow_dump_prefix: Option, @@ -186,6 +194,9 @@ fn run() -> Result<(), Box> { 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> { 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, diff --git a/src/back/spv/image.rs b/src/back/spv/image.rs index 1d5e4385bd..cf9cf802a7 100644 --- a/src/back/spv/image.rs +++ b/src/back/spv/image.rs @@ -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, +} + +/// 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, + sample_id: Option, + 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 { + 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 + // 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, + sample_id: Option, + 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, + _sample_id: Option, + 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, array_index: Option>, block: &mut Block, - ) -> Result { + ) -> Result { 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) -> 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 { + 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 { + 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, + 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, + sample_id: Option, + block: &mut Block, + ) -> Result<(Word, Option, Option), 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( + &mut self, + image_id: Word, + coordinates: ImageCoordinates, + level_id: Option, + sample_id: Option, + block: &mut Block, + access: &A, + ) -> Result { + 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, coordinate: Handle, array_index: Option>, - index: Option>, + level_or_sample: Option>, block: &mut Block, ) -> Result { 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`, - // 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`, - // so we need to grab the first component out of it. + // SPIR-V doesn't know about our `Depth` class, and it returns + // `vec4`, 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(()) } diff --git a/src/back/spv/index.rs b/src/back/spv/index.rs index 91bafbb01e..5176dd0d54 100644 --- a/src/back/spv/index.rs +++ b/src/back/spv/index.rs @@ -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 { +pub(super) enum MaybeKnown { /// The value is known at shader translation time. Known(T), diff --git a/src/back/spv/instructions.rs b/src/back/spv/instructions.rs index 6530160e5f..ff7b3c5d37 100644 --- a/src/back/spv/instructions.rs +++ b/src/back/spv/instructions.rs @@ -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); diff --git a/src/back/spv/mod.rs b/src/back/spv/mod.rs index 42e51059a9..9da70e0ce8 100644 --- a/src/back/spv/mod.rs +++ b/src/back/spv/mod.rs @@ -418,6 +418,7 @@ pub struct Writer { annotations: Vec, 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, @@ -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(), } } } diff --git a/src/back/spv/selection.rs b/src/back/spv/selection.rs index a5558a05e3..5922deddea 100644 --- a/src/back/spv/selection.rs +++ b/src/back/spv/selection.rs @@ -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; diff --git a/src/back/spv/writer.rs b/src/back/spv/writer.rs index 5a82bae5b3..0ebf6fb322 100644 --- a/src/back/spv/writer.rs +++ b/src/back/spv/writer.rs @@ -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: diff --git a/src/lib.rs b/src/lib.rs index d5167f3729..0b7f02ddd2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1044,7 +1044,22 @@ pub enum Expression { level: SampleLevel, depth_ref: Option>, }, + /// 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>, }, + /// Query information from an image. ImageQuery { image: Handle, diff --git a/tests/in/bounds-check-image-restrict.param.ron b/tests/in/bounds-check-image-restrict.param.ron new file mode 100644 index 0000000000..30ade34f54 --- /dev/null +++ b/tests/in/bounds-check-image-restrict.param.ron @@ -0,0 +1,5 @@ +( + image_bounds_check_restrict: true, + spv_version: (1, 1), + spv_debug: true, +) diff --git a/tests/in/bounds-check-image-restrict.wgsl b/tests/in/bounds-check-image-restrict.wgsl new file mode 100644 index 0000000000..e2f9131832 --- /dev/null +++ b/tests/in/bounds-check-image-restrict.wgsl @@ -0,0 +1,83 @@ +[[group(0), binding(0)]] +var image_1d: texture_1d; + +fn test_textureLoad_1d(coords: i32, level: i32) -> vec4 { + return textureLoad(image_1d, coords, level); +} + +[[group(0), binding(0)]] +var image_2d: texture_2d; + +fn test_textureLoad_2d(coords: vec2, level: i32) -> vec4 { + return textureLoad(image_2d, coords, level); +} + +[[group(0), binding(0)]] +var image_2d_array: texture_2d_array; + +fn test_textureLoad_2d_array(coords: vec2, index: i32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +[[group(0), binding(0)]] +var image_3d: texture_3d; + +fn test_textureLoad_3d(coords: vec3, level: i32) -> vec4 { + return textureLoad(image_3d, coords, level); +} + +[[group(0), binding(0)]] +var image_multisampled_2d: texture_multisampled_2d; + +fn test_textureLoad_multisampled_2d(coords: vec2, sample: i32) -> vec4 { + 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, 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, 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, sample: i32) -> f32 { + return textureLoad(image_depth_multisampled_2d, coords, sample); +} + +[[group(0), binding(0)]] +var image_storage_1d: texture_storage_1d; + +fn test_textureStore_1d(coords: i32, value: vec4) { + textureStore(image_storage_1d, coords, value); +} + +[[group(0), binding(0)]] +var image_storage_2d: texture_storage_2d; + +fn test_textureStore_2d(coords: vec2, value: vec4) { + textureStore(image_storage_2d, coords, value); +} + +[[group(0), binding(0)]] +var image_storage_2d_array: texture_storage_2d_array; + +fn test_textureStore_2d_array(coords: vec2, array_index: i32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +[[group(0), binding(0)]] +var image_storage_3d: texture_storage_3d; + +fn test_textureStore_3d(coords: vec3, value: vec4) { + textureStore(image_storage_3d, coords, value); +} diff --git a/tests/in/bounds-check-image-rzsw.param.ron b/tests/in/bounds-check-image-rzsw.param.ron new file mode 100644 index 0000000000..3922919f55 --- /dev/null +++ b/tests/in/bounds-check-image-rzsw.param.ron @@ -0,0 +1,5 @@ +( + image_bounds_check_read_zero_skip_write: true, + spv_version: (1, 1), + spv_debug: true, +) diff --git a/tests/in/bounds-check-image-rzsw.wgsl b/tests/in/bounds-check-image-rzsw.wgsl new file mode 100644 index 0000000000..e2f9131832 --- /dev/null +++ b/tests/in/bounds-check-image-rzsw.wgsl @@ -0,0 +1,83 @@ +[[group(0), binding(0)]] +var image_1d: texture_1d; + +fn test_textureLoad_1d(coords: i32, level: i32) -> vec4 { + return textureLoad(image_1d, coords, level); +} + +[[group(0), binding(0)]] +var image_2d: texture_2d; + +fn test_textureLoad_2d(coords: vec2, level: i32) -> vec4 { + return textureLoad(image_2d, coords, level); +} + +[[group(0), binding(0)]] +var image_2d_array: texture_2d_array; + +fn test_textureLoad_2d_array(coords: vec2, index: i32, level: i32) -> vec4 { + return textureLoad(image_2d_array, coords, index, level); +} + +[[group(0), binding(0)]] +var image_3d: texture_3d; + +fn test_textureLoad_3d(coords: vec3, level: i32) -> vec4 { + return textureLoad(image_3d, coords, level); +} + +[[group(0), binding(0)]] +var image_multisampled_2d: texture_multisampled_2d; + +fn test_textureLoad_multisampled_2d(coords: vec2, sample: i32) -> vec4 { + 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, 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, 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, sample: i32) -> f32 { + return textureLoad(image_depth_multisampled_2d, coords, sample); +} + +[[group(0), binding(0)]] +var image_storage_1d: texture_storage_1d; + +fn test_textureStore_1d(coords: i32, value: vec4) { + textureStore(image_storage_1d, coords, value); +} + +[[group(0), binding(0)]] +var image_storage_2d: texture_storage_2d; + +fn test_textureStore_2d(coords: vec2, value: vec4) { + textureStore(image_storage_2d, coords, value); +} + +[[group(0), binding(0)]] +var image_storage_2d_array: texture_storage_2d_array; + +fn test_textureStore_2d_array(coords: vec2, array_index: i32, value: vec4) { + textureStore(image_storage_2d_array, coords, array_index, value); +} + +[[group(0), binding(0)]] +var image_storage_3d: texture_storage_3d; + +fn test_textureStore_3d(coords: vec3, value: vec4) { + textureStore(image_storage_3d, coords, value); +} diff --git a/tests/out/spv/bounds-check-image-restrict.spvasm b/tests/out/spv/bounds-check-image-restrict.spvasm new file mode 100644 index 0000000000..d99451d2f8 --- /dev/null +++ b/tests/out/spv/bounds-check-image-restrict.spvasm @@ -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 \ No newline at end of file diff --git a/tests/out/spv/bounds-check-image-rzsw.spvasm b/tests/out/spv/bounds-check-image-rzsw.spvasm new file mode 100644 index 0000000000..faf74b49cc --- /dev/null +++ b/tests/out/spv/bounds-check-image-rzsw.spvasm @@ -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 \ No newline at end of file diff --git a/tests/snapshots.rs b/tests/snapshots.rs index 9d917f47d2..f92ddebdcd 100644 --- a/tests/snapshots.rs +++ b/tests/snapshots.rs @@ -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,