naga/back/spv/
image.rs

1/*!
2Generating SPIR-V for image operations.
3*/
4
5use spirv::Word;
6
7use super::{
8    selection::{MergeTuple, Selection},
9    Block, BlockContext, Error, IdGenerator, Instruction, LocalType, LookupType, NumericType,
10};
11use crate::arena::Handle;
12
13/// Information about a vector of coordinates.
14///
15/// The coordinate vectors expected by SPIR-V `OpImageRead` and `OpImageFetch`
16/// supply the array index for arrayed images as an additional component at
17/// the end, whereas Naga's `ImageLoad`, `ImageStore`, and `ImageSample` carry
18/// the array index as a separate field.
19///
20/// In the process of generating code to compute the combined vector, we also
21/// produce SPIR-V types and vector lengths that are useful elsewhere. This
22/// struct gathers that information into one place, with standard names.
23struct ImageCoordinates {
24    /// The SPIR-V id of the combined coordinate/index vector value.
25    ///
26    /// Note: when indexing a non-arrayed 1D image, this will be a scalar.
27    value_id: Word,
28
29    /// The SPIR-V id of the type of `value`.
30    type_id: Word,
31
32    /// The number of components in `value`, if it is a vector, or `None` if it
33    /// is a scalar.
34    size: Option<crate::VectorSize>,
35}
36
37/// A trait for image access (load or store) code generators.
38///
39/// Types implementing this trait hold information about an `ImageStore` or
40/// `ImageLoad` operation that is not affected by the bounds check policy. The
41/// `generate` method emits code for the access, given the results of bounds
42/// checking.
43///
44/// The [`image`] bounds checks policy affects access coordinates, level of
45/// detail, and sample index, but never the image id, result type (if any), or
46/// the specific SPIR-V instruction used. Types that implement this trait gather
47/// together the latter category, so we don't have to plumb them through the
48/// bounds-checking code.
49///
50/// [`image`]: crate::proc::BoundsCheckPolicies::index
51trait Access {
52    /// The Rust type that represents SPIR-V values and types for this access.
53    ///
54    /// For operations like loads, this is `Word`. For operations like stores,
55    /// this is `()`.
56    ///
57    /// For `ReadZeroSkipWrite`, this will be the type of the selection
58    /// construct that performs the bounds checks, so it must implement
59    /// `MergeTuple`.
60    type Output: MergeTuple + Copy + Clone;
61
62    /// Write an image access to `block`.
63    ///
64    /// Access the texel at `coordinates_id`. The optional `level_id` indicates
65    /// the level of detail, and `sample_id` is the index of the sample to
66    /// access in a multisampled texel.
67    ///
68    /// This method assumes that `coordinates_id` has already had the image array
69    /// index, if any, folded in, as done by `write_image_coordinates`.
70    ///
71    /// Return the value id produced by the instruction, if any.
72    ///
73    /// Use `id_gen` to generate SPIR-V ids as necessary.
74    fn generate(
75        &self,
76        id_gen: &mut IdGenerator,
77        coordinates_id: Word,
78        level_id: Option<Word>,
79        sample_id: Option<Word>,
80        block: &mut Block,
81    ) -> Self::Output;
82
83    /// Return the SPIR-V type of the value produced by the code written by
84    /// `generate`. If the access does not produce a value, `Self::Output`
85    /// should be `()`.
86    fn result_type(&self) -> Self::Output;
87
88    /// Construct the SPIR-V 'zero' value to be returned for an out-of-bounds
89    /// access under the `ReadZeroSkipWrite` policy. If the access does not
90    /// produce a value, `Self::Output` should be `()`.
91    fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Self::Output;
92}
93
94/// Texel access information for an [`ImageLoad`] expression.
95///
96/// [`ImageLoad`]: crate::Expression::ImageLoad
97struct Load {
98    /// The specific opcode we'll use to perform the fetch. Storage images
99    /// require `OpImageRead`, while sampled images require `OpImageFetch`.
100    opcode: spirv::Op,
101
102    /// The type id produced by the actual image access instruction.
103    type_id: Word,
104
105    /// The id of the image being accessed.
106    image_id: Word,
107}
108
109impl Load {
110    fn from_image_expr(
111        ctx: &mut BlockContext<'_>,
112        image_id: Word,
113        image_class: crate::ImageClass,
114        result_type_id: Word,
115    ) -> Result<Load, Error> {
116        let opcode = match image_class {
117            crate::ImageClass::Storage { .. } => spirv::Op::ImageRead,
118            crate::ImageClass::Depth { .. } | crate::ImageClass::Sampled { .. } => {
119                spirv::Op::ImageFetch
120            }
121        };
122
123        // `OpImageRead` and `OpImageFetch` instructions produce vec4<f32>
124        // values. Most of the time, we can just use `result_type_id` for
125        // this. The exception is that `Expression::ImageLoad` from a depth
126        // image produces a scalar `f32`, so in that case we need to find
127        // the right SPIR-V type for the access instruction here.
128        let type_id = match image_class {
129            crate::ImageClass::Depth { .. } => ctx.get_numeric_type_id(NumericType::Vector {
130                size: crate::VectorSize::Quad,
131                scalar: crate::Scalar::F32,
132            }),
133            _ => result_type_id,
134        };
135
136        Ok(Load {
137            opcode,
138            type_id,
139            image_id,
140        })
141    }
142}
143
144impl Access for Load {
145    type Output = Word;
146
147    /// Write an instruction to access a given texel of this image.
148    fn generate(
149        &self,
150        id_gen: &mut IdGenerator,
151        coordinates_id: Word,
152        level_id: Option<Word>,
153        sample_id: Option<Word>,
154        block: &mut Block,
155    ) -> Word {
156        let texel_id = id_gen.next();
157        let mut instruction = Instruction::image_fetch_or_read(
158            self.opcode,
159            self.type_id,
160            texel_id,
161            self.image_id,
162            coordinates_id,
163        );
164
165        match (level_id, sample_id) {
166            (None, None) => {}
167            (Some(level_id), None) => {
168                instruction.add_operand(spirv::ImageOperands::LOD.bits());
169                instruction.add_operand(level_id);
170            }
171            (None, Some(sample_id)) => {
172                instruction.add_operand(spirv::ImageOperands::SAMPLE.bits());
173                instruction.add_operand(sample_id);
174            }
175            // There's no such thing as a multi-sampled mipmap.
176            (Some(_), Some(_)) => unreachable!(),
177        }
178
179        block.body.push(instruction);
180
181        texel_id
182    }
183
184    fn result_type(&self) -> Word {
185        self.type_id
186    }
187
188    fn out_of_bounds_value(&self, ctx: &mut BlockContext<'_>) -> Word {
189        ctx.writer.get_constant_null(self.type_id)
190    }
191}
192
193/// Texel access information for a [`Store`] statement.
194///
195/// [`Store`]: crate::Statement::Store
196struct Store {
197    /// The id of the image being written to.
198    image_id: Word,
199
200    /// The value we're going to write to the texel.
201    value_id: Word,
202}
203
204impl Access for Store {
205    /// Stores don't generate any value.
206    type Output = ();
207
208    fn generate(
209        &self,
210        _id_gen: &mut IdGenerator,
211        coordinates_id: Word,
212        _level_id: Option<Word>,
213        _sample_id: Option<Word>,
214        block: &mut Block,
215    ) {
216        block.body.push(Instruction::image_write(
217            self.image_id,
218            coordinates_id,
219            self.value_id,
220        ));
221    }
222
223    /// Stores don't generate any value, so this just returns `()`.
224    fn result_type(&self) {}
225
226    /// Stores don't generate any value, so this just returns `()`.
227    fn out_of_bounds_value(&self, _ctx: &mut BlockContext<'_>) {}
228}
229
230impl BlockContext<'_> {
231    /// Extend image coordinates with an array index, if necessary.
232    ///
233    /// Whereas [`Expression::ImageLoad`] and [`ImageSample`] treat the array
234    /// index as a separate operand from the coordinates, SPIR-V image access
235    /// instructions include the array index in the `coordinates` operand. This
236    /// function builds a SPIR-V coordinate vector from a Naga coordinate vector
237    /// and array index, if one is supplied, and returns a `ImageCoordinates`
238    /// struct describing what it built.
239    ///
240    /// If `array_index` is `Some(expr)`, then this function constructs a new
241    /// vector that is `coordinates` with `array_index` concatenated onto the
242    /// end: a `vec2` becomes a `vec3`, a scalar becomes a `vec2`, and so on.
243    ///
244    /// If `array_index` is `None`, then the return value uses `coordinates`
245    /// unchanged. Note that, when indexing a non-arrayed 1D image, this will be
246    /// a scalar value.
247    ///
248    /// If needed, this function generates code to convert the array index,
249    /// always an integer scalar, to match the component type of `coordinates`.
250    /// Naga's `ImageLoad` and SPIR-V's `OpImageRead`, `OpImageFetch`, and
251    /// `OpImageWrite` all use integer coordinates, while Naga's `ImageSample`
252    /// and SPIR-V's `OpImageSample...` instructions all take floating-point
253    /// coordinate vectors.
254    ///
255    /// [`Expression::ImageLoad`]: crate::Expression::ImageLoad
256    /// [`ImageSample`]: crate::Expression::ImageSample
257    fn write_image_coordinates(
258        &mut self,
259        coordinates: Handle<crate::Expression>,
260        array_index: Option<Handle<crate::Expression>>,
261        block: &mut Block,
262    ) -> Result<ImageCoordinates, Error> {
263        use crate::TypeInner as Ti;
264        use crate::VectorSize as Vs;
265
266        let coordinates_id = self.cached[coordinates];
267        let ty = &self.fun_info[coordinates].ty;
268        let inner_ty = ty.inner_with(&self.ir_module.types);
269
270        // If there's no array index, the image coordinates are exactly the
271        // `coordinate` field of the `Expression::ImageLoad`. No work is needed.
272        let array_index = match array_index {
273            None => {
274                let value_id = coordinates_id;
275                let type_id = self.get_expression_type_id(ty);
276                let size = match *inner_ty {
277                    Ti::Scalar { .. } => None,
278                    Ti::Vector { size, .. } => Some(size),
279                    _ => return Err(Error::Validation("coordinate type")),
280                };
281                return Ok(ImageCoordinates {
282                    value_id,
283                    type_id,
284                    size,
285                });
286            }
287            Some(ix) => ix,
288        };
289
290        // Find the component type of `coordinates`, and figure out the size the
291        // combined coordinate vector will have.
292        let (component_scalar, size) = match *inner_ty {
293            Ti::Scalar(scalar @ crate::Scalar { width: 4, .. }) => (scalar, Vs::Bi),
294            Ti::Vector {
295                scalar: scalar @ crate::Scalar { width: 4, .. },
296                size: Vs::Bi,
297            } => (scalar, Vs::Tri),
298            Ti::Vector {
299                scalar: scalar @ crate::Scalar { width: 4, .. },
300                size: Vs::Tri,
301            } => (scalar, Vs::Quad),
302            Ti::Vector { size: Vs::Quad, .. } => {
303                return Err(Error::Validation("extending vec4 coordinate"));
304            }
305            ref other => {
306                log::error!("wrong coordinate type {:?}", other);
307                return Err(Error::Validation("coordinate type"));
308            }
309        };
310
311        // Convert the index to the coordinate component type, if necessary.
312        let array_index_id = self.cached[array_index];
313        let ty = &self.fun_info[array_index].ty;
314        let inner_ty = ty.inner_with(&self.ir_module.types);
315        let array_index_scalar = match *inner_ty {
316            Ti::Scalar(
317                scalar @ crate::Scalar {
318                    kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
319                    width: 4,
320                },
321            ) => scalar,
322            _ => unreachable!("we only allow i32 and u32"),
323        };
324        let cast = match (component_scalar.kind, array_index_scalar.kind) {
325            (crate::ScalarKind::Sint, crate::ScalarKind::Sint)
326            | (crate::ScalarKind::Uint, crate::ScalarKind::Uint) => None,
327            (crate::ScalarKind::Sint, crate::ScalarKind::Uint)
328            | (crate::ScalarKind::Uint, crate::ScalarKind::Sint) => Some(spirv::Op::Bitcast),
329            (crate::ScalarKind::Float, crate::ScalarKind::Sint) => Some(spirv::Op::ConvertSToF),
330            (crate::ScalarKind::Float, crate::ScalarKind::Uint) => Some(spirv::Op::ConvertUToF),
331            (crate::ScalarKind::Bool, _) => unreachable!("we don't allow bool for component"),
332            (_, crate::ScalarKind::Bool | crate::ScalarKind::Float) => {
333                unreachable!("we don't allow bool or float for array index")
334            }
335            (crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat, _)
336            | (_, crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat) => {
337                unreachable!("abstract types should never reach backends")
338            }
339        };
340        let reconciled_array_index_id = if let Some(cast) = cast {
341            let component_ty_id = self.get_numeric_type_id(NumericType::Scalar(component_scalar));
342            let reconciled_id = self.gen_id();
343            block.body.push(Instruction::unary(
344                cast,
345                component_ty_id,
346                reconciled_id,
347                array_index_id,
348            ));
349            reconciled_id
350        } else {
351            array_index_id
352        };
353
354        // Find the SPIR-V type for the combined coordinates/index vector.
355        let type_id = self.get_numeric_type_id(NumericType::Vector {
356            size,
357            scalar: component_scalar,
358        });
359
360        // Schmear the coordinates and index together.
361        let value_id = self.gen_id();
362        block.body.push(Instruction::composite_construct(
363            type_id,
364            value_id,
365            &[coordinates_id, reconciled_array_index_id],
366        ));
367        Ok(ImageCoordinates {
368            value_id,
369            type_id,
370            size: Some(size),
371        })
372    }
373
374    pub(super) fn get_handle_id(&mut self, expr_handle: Handle<crate::Expression>) -> Word {
375        let id = match self.ir_function.expressions[expr_handle] {
376            crate::Expression::GlobalVariable(handle) => {
377                self.writer.global_variables[handle].handle_id
378            }
379            crate::Expression::FunctionArgument(i) => {
380                self.function.parameters[i as usize].handle_id
381            }
382            crate::Expression::Access { .. } | crate::Expression::AccessIndex { .. } => {
383                self.cached[expr_handle]
384            }
385            ref other => unreachable!("Unexpected image expression {:?}", other),
386        };
387
388        if id == 0 {
389            unreachable!(
390                "Image expression {:?} doesn't have a handle ID",
391                expr_handle
392            );
393        }
394
395        id
396    }
397
398    /// Generate a vector or scalar 'one' for arithmetic on `coordinates`.
399    ///
400    /// If `coordinates` is a scalar, return a scalar one. Otherwise, return
401    /// a vector of ones.
402    fn write_coordinate_one(&mut self, coordinates: &ImageCoordinates) -> Result<Word, Error> {
403        let one = self.get_scope_constant(1);
404        match coordinates.size {
405            None => Ok(one),
406            Some(vector_size) => {
407                let ones = [one; 4];
408                let id = self.gen_id();
409                Instruction::constant_composite(
410                    coordinates.type_id,
411                    id,
412                    &ones[..vector_size as usize],
413                )
414                .to_words(&mut self.writer.logical_layout.declarations);
415                Ok(id)
416            }
417        }
418    }
419
420    /// Generate code to restrict `input` to fall between zero and one less than
421    /// `size_id`.
422    ///
423    /// Both must be 32-bit scalar integer values, whose type is given by
424    /// `type_id`. The computed value is also of type `type_id`.
425    fn restrict_scalar(
426        &mut self,
427        type_id: Word,
428        input_id: Word,
429        size_id: Word,
430        block: &mut Block,
431    ) -> Result<Word, Error> {
432        let i32_one_id = self.get_scope_constant(1);
433
434        // Subtract one from `size` to get the largest valid value.
435        let limit_id = self.gen_id();
436        block.body.push(Instruction::binary(
437            spirv::Op::ISub,
438            type_id,
439            limit_id,
440            size_id,
441            i32_one_id,
442        ));
443
444        // Use an unsigned minimum, to handle both positive out-of-range values
445        // and negative values in a single instruction: negative values of
446        // `input_id` get treated as very large positive values.
447        let restricted_id = self.gen_id();
448        block.body.push(Instruction::ext_inst(
449            self.writer.gl450_ext_inst_id,
450            spirv::GLOp::UMin,
451            type_id,
452            restricted_id,
453            &[input_id, limit_id],
454        ));
455
456        Ok(restricted_id)
457    }
458
459    /// Write instructions to query the size of an image.
460    ///
461    /// This takes care of selecting the right instruction depending on whether
462    /// a level of detail parameter is present.
463    fn write_coordinate_bounds(
464        &mut self,
465        type_id: Word,
466        image_id: Word,
467        level_id: Option<Word>,
468        block: &mut Block,
469    ) -> Word {
470        let coordinate_bounds_id = self.gen_id();
471        match level_id {
472            Some(level_id) => {
473                // A level of detail was provided, so fetch the image size for
474                // that level.
475                let mut inst = Instruction::image_query(
476                    spirv::Op::ImageQuerySizeLod,
477                    type_id,
478                    coordinate_bounds_id,
479                    image_id,
480                );
481                inst.add_operand(level_id);
482                block.body.push(inst);
483            }
484            _ => {
485                // No level of detail was given.
486                block.body.push(Instruction::image_query(
487                    spirv::Op::ImageQuerySize,
488                    type_id,
489                    coordinate_bounds_id,
490                    image_id,
491                ));
492            }
493        }
494
495        coordinate_bounds_id
496    }
497
498    /// Write code to restrict coordinates for an image reference.
499    ///
500    /// First, clamp the level of detail or sample index to fall within bounds.
501    /// Then, obtain the image size, possibly using the clamped level of detail.
502    /// Finally, use an unsigned minimum instruction to force all coordinates
503    /// into range.
504    ///
505    /// Return a triple `(COORDS, LEVEL, SAMPLE)`, where `COORDS` is a coordinate
506    /// vector (including the array index, if any), `LEVEL` is an optional level
507    /// of detail, and `SAMPLE` is an optional sample index, all guaranteed to
508    /// be in-bounds for `image_id`.
509    ///
510    /// The result is usually a vector, but it is a scalar when indexing
511    /// non-arrayed 1D images.
512    fn write_restricted_coordinates(
513        &mut self,
514        image_id: Word,
515        coordinates: ImageCoordinates,
516        level_id: Option<Word>,
517        sample_id: Option<Word>,
518        block: &mut Block,
519    ) -> Result<(Word, Option<Word>, Option<Word>), Error> {
520        self.writer.require_any(
521            "the `Restrict` image bounds check policy",
522            &[spirv::Capability::ImageQuery],
523        )?;
524
525        let i32_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::I32));
526
527        // If `level` is `Some`, clamp it to fall within bounds. This must
528        // happen first, because we'll use it to query the image size for
529        // clamping the actual coordinates.
530        let level_id = level_id
531            .map(|level_id| {
532                // Find the number of mipmap levels in this image.
533                let num_levels_id = self.gen_id();
534                block.body.push(Instruction::image_query(
535                    spirv::Op::ImageQueryLevels,
536                    i32_type_id,
537                    num_levels_id,
538                    image_id,
539                ));
540
541                self.restrict_scalar(i32_type_id, level_id, num_levels_id, block)
542            })
543            .transpose()?;
544
545        // If `sample_id` is `Some`, clamp it to fall within bounds.
546        let sample_id = sample_id
547            .map(|sample_id| {
548                // Find the number of samples per texel.
549                let num_samples_id = self.gen_id();
550                block.body.push(Instruction::image_query(
551                    spirv::Op::ImageQuerySamples,
552                    i32_type_id,
553                    num_samples_id,
554                    image_id,
555                ));
556
557                self.restrict_scalar(i32_type_id, sample_id, num_samples_id, block)
558            })
559            .transpose()?;
560
561        // Obtain the image bounds, including the array element count.
562        let coordinate_bounds_id =
563            self.write_coordinate_bounds(coordinates.type_id, image_id, level_id, block);
564
565        // Compute maximum valid values from the bounds.
566        let ones = self.write_coordinate_one(&coordinates)?;
567        let coordinate_limit_id = self.gen_id();
568        block.body.push(Instruction::binary(
569            spirv::Op::ISub,
570            coordinates.type_id,
571            coordinate_limit_id,
572            coordinate_bounds_id,
573            ones,
574        ));
575
576        // Restrict the coordinates to fall within those bounds.
577        //
578        // Use an unsigned minimum, to handle both positive out-of-range values
579        // and negative values in a single instruction: negative values of
580        // `coordinates` get treated as very large positive values.
581        let restricted_coordinates_id = self.gen_id();
582        block.body.push(Instruction::ext_inst(
583            self.writer.gl450_ext_inst_id,
584            spirv::GLOp::UMin,
585            coordinates.type_id,
586            restricted_coordinates_id,
587            &[coordinates.value_id, coordinate_limit_id],
588        ));
589
590        Ok((restricted_coordinates_id, level_id, sample_id))
591    }
592
593    fn write_conditional_image_access<A: Access>(
594        &mut self,
595        image_id: Word,
596        coordinates: ImageCoordinates,
597        level_id: Option<Word>,
598        sample_id: Option<Word>,
599        block: &mut Block,
600        access: &A,
601    ) -> Result<A::Output, Error> {
602        self.writer.require_any(
603            "the `ReadZeroSkipWrite` image bounds check policy",
604            &[spirv::Capability::ImageQuery],
605        )?;
606
607        let bool_type_id = self.writer.get_bool_type_id();
608        let i32_type_id = self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::I32));
609
610        let null_id = access.out_of_bounds_value(self);
611
612        let mut selection = Selection::start(block, access.result_type());
613
614        // If `level_id` is `Some`, check whether it is within bounds. This must
615        // happen first, because we'll be supplying this as an argument when we
616        // query the image size.
617        if let Some(level_id) = level_id {
618            // Find the number of mipmap levels in this image.
619            let num_levels_id = self.gen_id();
620            selection.block().body.push(Instruction::image_query(
621                spirv::Op::ImageQueryLevels,
622                i32_type_id,
623                num_levels_id,
624                image_id,
625            ));
626
627            let lod_cond_id = self.gen_id();
628            selection.block().body.push(Instruction::binary(
629                spirv::Op::ULessThan,
630                bool_type_id,
631                lod_cond_id,
632                level_id,
633                num_levels_id,
634            ));
635
636            selection.if_true(self, lod_cond_id, null_id);
637        }
638
639        // If `sample_id` is `Some`, check whether it is in bounds.
640        if let Some(sample_id) = sample_id {
641            // Find the number of samples per texel.
642            let num_samples_id = self.gen_id();
643            selection.block().body.push(Instruction::image_query(
644                spirv::Op::ImageQuerySamples,
645                i32_type_id,
646                num_samples_id,
647                image_id,
648            ));
649
650            let samples_cond_id = self.gen_id();
651            selection.block().body.push(Instruction::binary(
652                spirv::Op::ULessThan,
653                bool_type_id,
654                samples_cond_id,
655                sample_id,
656                num_samples_id,
657            ));
658
659            selection.if_true(self, samples_cond_id, null_id);
660        }
661
662        // Obtain the image bounds, including any array element count.
663        let coordinate_bounds_id = self.write_coordinate_bounds(
664            coordinates.type_id,
665            image_id,
666            level_id,
667            selection.block(),
668        );
669
670        // Compare the coordinates against the bounds.
671        let coords_numeric_type = match coordinates.size {
672            Some(size) => NumericType::Vector {
673                size,
674                scalar: crate::Scalar::BOOL,
675            },
676            None => NumericType::Scalar(crate::Scalar::BOOL),
677        };
678        let coords_bool_type_id = self.get_numeric_type_id(coords_numeric_type);
679        let coords_conds_id = self.gen_id();
680        selection.block().body.push(Instruction::binary(
681            spirv::Op::ULessThan,
682            coords_bool_type_id,
683            coords_conds_id,
684            coordinates.value_id,
685            coordinate_bounds_id,
686        ));
687
688        // If the comparison above was a vector comparison, then we need to
689        // check that all components of the comparison are true.
690        let coords_cond_id = if coords_bool_type_id != bool_type_id {
691            let id = self.gen_id();
692            selection.block().body.push(Instruction::relational(
693                spirv::Op::All,
694                bool_type_id,
695                id,
696                coords_conds_id,
697            ));
698            id
699        } else {
700            coords_conds_id
701        };
702
703        selection.if_true(self, coords_cond_id, null_id);
704
705        // All conditions are met. We can carry out the access.
706        let texel_id = access.generate(
707            &mut self.writer.id_gen,
708            coordinates.value_id,
709            level_id,
710            sample_id,
711            selection.block(),
712        );
713
714        // This, then, is the value of the 'true' branch.
715        Ok(selection.finish(self, texel_id))
716    }
717
718    /// Generate code for an `ImageLoad` expression.
719    ///
720    /// The arguments are the components of an `Expression::ImageLoad` variant.
721    #[allow(clippy::too_many_arguments)]
722    pub(super) fn write_image_load(
723        &mut self,
724        result_type_id: Word,
725        image: Handle<crate::Expression>,
726        coordinate: Handle<crate::Expression>,
727        array_index: Option<Handle<crate::Expression>>,
728        level: Option<Handle<crate::Expression>>,
729        sample: Option<Handle<crate::Expression>>,
730        block: &mut Block,
731    ) -> Result<Word, Error> {
732        let image_id = self.get_handle_id(image);
733        let image_type = self.fun_info[image].ty.inner_with(&self.ir_module.types);
734        let image_class = match *image_type {
735            crate::TypeInner::Image { class, .. } => class,
736            _ => return Err(Error::Validation("image type")),
737        };
738
739        let access = Load::from_image_expr(self, image_id, image_class, result_type_id)?;
740        let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
741
742        let level_id = level.map(|expr| self.cached[expr]);
743        let sample_id = sample.map(|expr| self.cached[expr]);
744
745        // Perform the access, according to the bounds check policy.
746        let access_id = match self.writer.bounds_check_policies.image_load {
747            crate::proc::BoundsCheckPolicy::Restrict => {
748                let (coords, level_id, sample_id) = self.write_restricted_coordinates(
749                    image_id,
750                    coordinates,
751                    level_id,
752                    sample_id,
753                    block,
754                )?;
755                access.generate(&mut self.writer.id_gen, coords, level_id, sample_id, block)
756            }
757            crate::proc::BoundsCheckPolicy::ReadZeroSkipWrite => self
758                .write_conditional_image_access(
759                    image_id,
760                    coordinates,
761                    level_id,
762                    sample_id,
763                    block,
764                    &access,
765                )?,
766            crate::proc::BoundsCheckPolicy::Unchecked => access.generate(
767                &mut self.writer.id_gen,
768                coordinates.value_id,
769                level_id,
770                sample_id,
771                block,
772            ),
773        };
774
775        // For depth images, `ImageLoad` expressions produce a single f32,
776        // whereas the SPIR-V instructions always produce a vec4. So we may have
777        // to pull out the component we need.
778        let result_id = if result_type_id == access.result_type() {
779            // The instruction produced the type we expected. We can use
780            // its result as-is.
781            access_id
782        } else {
783            // For `ImageClass::Depth` images, SPIR-V gave us four components,
784            // but we only want the first one.
785            let component_id = self.gen_id();
786            block.body.push(Instruction::composite_extract(
787                result_type_id,
788                component_id,
789                access_id,
790                &[0],
791            ));
792            component_id
793        };
794
795        Ok(result_id)
796    }
797
798    /// Generate code for an `ImageSample` expression.
799    ///
800    /// The arguments are the components of an `Expression::ImageSample` variant.
801    #[allow(clippy::too_many_arguments)]
802    pub(super) fn write_image_sample(
803        &mut self,
804        result_type_id: Word,
805        image: Handle<crate::Expression>,
806        sampler: Handle<crate::Expression>,
807        gather: Option<crate::SwizzleComponent>,
808        coordinate: Handle<crate::Expression>,
809        array_index: Option<Handle<crate::Expression>>,
810        offset: Option<Handle<crate::Expression>>,
811        level: crate::SampleLevel,
812        depth_ref: Option<Handle<crate::Expression>>,
813        clamp_to_edge: bool,
814        block: &mut Block,
815    ) -> Result<Word, Error> {
816        use super::instructions::SampleLod;
817        // image
818        let image_id = self.get_handle_id(image);
819        let image_type = self.fun_info[image].ty.handle().unwrap();
820        // SPIR-V doesn't know about our `Depth` class, and it returns
821        // `vec4<f32>`, so we need to grab the first component out of it.
822        let needs_sub_access = match self.ir_module.types[image_type].inner {
823            crate::TypeInner::Image {
824                class: crate::ImageClass::Depth { .. },
825                ..
826            } => depth_ref.is_none() && gather.is_none(),
827            _ => false,
828        };
829        let sample_result_type_id = if needs_sub_access {
830            self.get_numeric_type_id(NumericType::Vector {
831                size: crate::VectorSize::Quad,
832                scalar: crate::Scalar::F32,
833            })
834        } else {
835            result_type_id
836        };
837
838        // OpTypeSampledImage
839        let image_type_id = self.get_handle_type_id(image_type);
840        let sampled_image_type_id =
841            self.get_type_id(LookupType::Local(LocalType::SampledImage { image_type_id }));
842
843        let sampler_id = self.get_handle_id(sampler);
844
845        let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
846        let coordinates_id = if clamp_to_edge {
847            self.writer.require_any(
848                "clamp sample coordinates to edge",
849                &[spirv::Capability::ImageQuery],
850            )?;
851
852            // clamp_to_edge can only be used with Level 0, and no array offset, offset,
853            // depth_ref or gather. This should have been caught by validation. Rather
854            // than entirely duplicate validation code here just ensure the level is
855            // zero, as we rely on that to query the texture size in order to calculate
856            // the clamped coordinates.
857            if level != crate::SampleLevel::Zero {
858                return Err(Error::Validation(
859                    "ImageSample::clamp_to_edge requires SampleLevel::Zero",
860                ));
861            }
862
863            // Query the size of level 0 of the texture.
864            let image_size_id = self.gen_id();
865            let vec2u_type_id = self.writer.get_vec2u_type_id();
866            let const_zero_uint_id = self.writer.get_constant_scalar(crate::Literal::U32(0));
867            let mut query_inst = Instruction::image_query(
868                spirv::Op::ImageQuerySizeLod,
869                vec2u_type_id,
870                image_size_id,
871                image_id,
872            );
873            query_inst.add_operand(const_zero_uint_id);
874            block.body.push(query_inst);
875
876            let image_size_f_id = self.gen_id();
877            let vec2f_type_id = self.writer.get_vec2f_type_id();
878            block.body.push(Instruction::unary(
879                spirv::Op::ConvertUToF,
880                vec2f_type_id,
881                image_size_f_id,
882                image_size_id,
883            ));
884
885            // Calculate the top-left and bottom-right margin for clamping to. I.e. a
886            // half-texel from each side.
887            let const_0_5_f32_id = self.writer.get_constant_scalar(crate::Literal::F32(0.5));
888            let const_0_5_vec2f_id = self.writer.get_constant_composite(
889                LookupType::Local(LocalType::Numeric(NumericType::Vector {
890                    size: crate::VectorSize::Bi,
891                    scalar: crate::Scalar::F32,
892                })),
893                &[const_0_5_f32_id, const_0_5_f32_id],
894            );
895
896            let margin_left_id = self.gen_id();
897            block.body.push(Instruction::binary(
898                spirv::Op::FDiv,
899                vec2f_type_id,
900                margin_left_id,
901                const_0_5_vec2f_id,
902                image_size_f_id,
903            ));
904
905            let const_1_f32_id = self.writer.get_constant_scalar(crate::Literal::F32(1.0));
906            let const_1_vec2f_id = self.writer.get_constant_composite(
907                LookupType::Local(LocalType::Numeric(NumericType::Vector {
908                    size: crate::VectorSize::Bi,
909                    scalar: crate::Scalar::F32,
910                })),
911                &[const_1_f32_id, const_1_f32_id],
912            );
913
914            let margin_right_id = self.gen_id();
915            block.body.push(Instruction::binary(
916                spirv::Op::FSub,
917                vec2f_type_id,
918                margin_right_id,
919                const_1_vec2f_id,
920                margin_left_id,
921            ));
922
923            // Clamp the coords to the calculated margins
924            let clamped_coords_id = self.gen_id();
925            block.body.push(Instruction::ext_inst(
926                self.writer.gl450_ext_inst_id,
927                spirv::GLOp::NClamp,
928                vec2f_type_id,
929                clamped_coords_id,
930                &[coordinates.value_id, margin_left_id, margin_right_id],
931            ));
932
933            clamped_coords_id
934        } else {
935            coordinates.value_id
936        };
937
938        let sampled_image_id = self.gen_id();
939        block.body.push(Instruction::sampled_image(
940            sampled_image_type_id,
941            sampled_image_id,
942            image_id,
943            sampler_id,
944        ));
945        let id = self.gen_id();
946
947        let depth_id = depth_ref.map(|handle| self.cached[handle]);
948        let mut mask = spirv::ImageOperands::empty();
949        mask.set(spirv::ImageOperands::CONST_OFFSET, offset.is_some());
950
951        let mut main_instruction = match (level, gather) {
952            (_, Some(component)) => {
953                let component_id = self.get_index_constant(component as u32);
954                let mut inst = Instruction::image_gather(
955                    sample_result_type_id,
956                    id,
957                    sampled_image_id,
958                    coordinates_id,
959                    component_id,
960                    depth_id,
961                );
962                if !mask.is_empty() {
963                    inst.add_operand(mask.bits());
964                }
965                inst
966            }
967            (crate::SampleLevel::Zero, None) => {
968                let mut inst = Instruction::image_sample(
969                    sample_result_type_id,
970                    id,
971                    SampleLod::Explicit,
972                    sampled_image_id,
973                    coordinates_id,
974                    depth_id,
975                );
976
977                let zero_id = self.writer.get_constant_scalar(crate::Literal::F32(0.0));
978
979                mask |= spirv::ImageOperands::LOD;
980                inst.add_operand(mask.bits());
981                inst.add_operand(zero_id);
982
983                inst
984            }
985            (crate::SampleLevel::Auto, None) => {
986                let mut inst = Instruction::image_sample(
987                    sample_result_type_id,
988                    id,
989                    SampleLod::Implicit,
990                    sampled_image_id,
991                    coordinates_id,
992                    depth_id,
993                );
994                if !mask.is_empty() {
995                    inst.add_operand(mask.bits());
996                }
997                inst
998            }
999            (crate::SampleLevel::Exact(lod_handle), None) => {
1000                let mut inst = Instruction::image_sample(
1001                    sample_result_type_id,
1002                    id,
1003                    SampleLod::Explicit,
1004                    sampled_image_id,
1005                    coordinates_id,
1006                    depth_id,
1007                );
1008
1009                let mut lod_id = self.cached[lod_handle];
1010                // SPIR-V expects the LOD to be a float for all image classes.
1011                // lod_id, however, will be an integer for depth images,
1012                // therefore we must do a conversion.
1013                if matches!(
1014                    self.ir_module.types[image_type].inner,
1015                    crate::TypeInner::Image {
1016                        class: crate::ImageClass::Depth { .. },
1017                        ..
1018                    }
1019                ) {
1020                    let lod_f32_id = self.gen_id();
1021                    let f32_type_id =
1022                        self.get_numeric_type_id(NumericType::Scalar(crate::Scalar::F32));
1023                    let convert_op = match *self.fun_info[lod_handle]
1024                        .ty
1025                        .inner_with(&self.ir_module.types)
1026                    {
1027                        crate::TypeInner::Scalar(crate::Scalar {
1028                            kind: crate::ScalarKind::Uint,
1029                            width: 4,
1030                        }) => spirv::Op::ConvertUToF,
1031                        crate::TypeInner::Scalar(crate::Scalar {
1032                            kind: crate::ScalarKind::Sint,
1033                            width: 4,
1034                        }) => spirv::Op::ConvertSToF,
1035                        _ => unreachable!(),
1036                    };
1037                    block.body.push(Instruction::unary(
1038                        convert_op,
1039                        f32_type_id,
1040                        lod_f32_id,
1041                        lod_id,
1042                    ));
1043                    lod_id = lod_f32_id;
1044                }
1045                mask |= spirv::ImageOperands::LOD;
1046                inst.add_operand(mask.bits());
1047                inst.add_operand(lod_id);
1048
1049                inst
1050            }
1051            (crate::SampleLevel::Bias(bias_handle), None) => {
1052                let mut inst = Instruction::image_sample(
1053                    sample_result_type_id,
1054                    id,
1055                    SampleLod::Implicit,
1056                    sampled_image_id,
1057                    coordinates_id,
1058                    depth_id,
1059                );
1060
1061                let bias_id = self.cached[bias_handle];
1062                mask |= spirv::ImageOperands::BIAS;
1063                inst.add_operand(mask.bits());
1064                inst.add_operand(bias_id);
1065
1066                inst
1067            }
1068            (crate::SampleLevel::Gradient { x, y }, None) => {
1069                let mut inst = Instruction::image_sample(
1070                    sample_result_type_id,
1071                    id,
1072                    SampleLod::Explicit,
1073                    sampled_image_id,
1074                    coordinates_id,
1075                    depth_id,
1076                );
1077
1078                let x_id = self.cached[x];
1079                let y_id = self.cached[y];
1080                mask |= spirv::ImageOperands::GRAD;
1081                inst.add_operand(mask.bits());
1082                inst.add_operand(x_id);
1083                inst.add_operand(y_id);
1084
1085                inst
1086            }
1087        };
1088
1089        if let Some(offset_const) = offset {
1090            let offset_id = self.cached[offset_const];
1091            main_instruction.add_operand(offset_id);
1092        }
1093
1094        block.body.push(main_instruction);
1095
1096        let id = if needs_sub_access {
1097            let sub_id = self.gen_id();
1098            block.body.push(Instruction::composite_extract(
1099                result_type_id,
1100                sub_id,
1101                id,
1102                &[0],
1103            ));
1104            sub_id
1105        } else {
1106            id
1107        };
1108
1109        Ok(id)
1110    }
1111
1112    /// Generate code for an `ImageQuery` expression.
1113    ///
1114    /// The arguments are the components of an `Expression::ImageQuery` variant.
1115    pub(super) fn write_image_query(
1116        &mut self,
1117        result_type_id: Word,
1118        image: Handle<crate::Expression>,
1119        query: crate::ImageQuery,
1120        block: &mut Block,
1121    ) -> Result<Word, Error> {
1122        use crate::{ImageClass as Ic, ImageDimension as Id, ImageQuery as Iq};
1123
1124        let image_id = self.get_handle_id(image);
1125        let image_type = self.fun_info[image].ty.handle().unwrap();
1126        let (dim, arrayed, class) = match self.ir_module.types[image_type].inner {
1127            crate::TypeInner::Image {
1128                dim,
1129                arrayed,
1130                class,
1131            } => (dim, arrayed, class),
1132            _ => {
1133                return Err(Error::Validation("image type"));
1134            }
1135        };
1136
1137        self.writer
1138            .require_any("image queries", &[spirv::Capability::ImageQuery])?;
1139
1140        let id = match query {
1141            Iq::Size { level } => {
1142                let dim_coords = match dim {
1143                    Id::D1 => 1,
1144                    Id::D2 | Id::Cube => 2,
1145                    Id::D3 => 3,
1146                };
1147                let array_coords = usize::from(arrayed);
1148                let vector_size = match dim_coords + array_coords {
1149                    2 => Some(crate::VectorSize::Bi),
1150                    3 => Some(crate::VectorSize::Tri),
1151                    4 => Some(crate::VectorSize::Quad),
1152                    _ => None,
1153                };
1154                let vector_numeric_type = match vector_size {
1155                    Some(size) => NumericType::Vector {
1156                        size,
1157                        scalar: crate::Scalar::U32,
1158                    },
1159                    None => NumericType::Scalar(crate::Scalar::U32),
1160                };
1161
1162                let extended_size_type_id = self.get_numeric_type_id(vector_numeric_type);
1163
1164                let (query_op, level_id) = match class {
1165                    Ic::Sampled { multi: true, .. }
1166                    | Ic::Depth { multi: true }
1167                    | Ic::Storage { .. } => (spirv::Op::ImageQuerySize, None),
1168                    _ => {
1169                        let level_id = match level {
1170                            Some(expr) => self.cached[expr],
1171                            None => self.get_index_constant(0),
1172                        };
1173                        (spirv::Op::ImageQuerySizeLod, Some(level_id))
1174                    }
1175                };
1176
1177                // The ID of the vector returned by SPIR-V, which contains the dimensions
1178                // as well as the layer count.
1179                let id_extended = self.gen_id();
1180                let mut inst = Instruction::image_query(
1181                    query_op,
1182                    extended_size_type_id,
1183                    id_extended,
1184                    image_id,
1185                );
1186                if let Some(expr_id) = level_id {
1187                    inst.add_operand(expr_id);
1188                }
1189                block.body.push(inst);
1190
1191                if result_type_id != extended_size_type_id {
1192                    let id = self.gen_id();
1193                    let components = match dim {
1194                        // always pick the first component, and duplicate it for all 3 dimensions
1195                        Id::Cube => &[0u32, 0][..],
1196                        _ => &[0u32, 1, 2, 3][..dim_coords],
1197                    };
1198                    block.body.push(Instruction::vector_shuffle(
1199                        result_type_id,
1200                        id,
1201                        id_extended,
1202                        id_extended,
1203                        components,
1204                    ));
1205
1206                    id
1207                } else {
1208                    id_extended
1209                }
1210            }
1211            Iq::NumLevels => {
1212                let query_id = self.gen_id();
1213                block.body.push(Instruction::image_query(
1214                    spirv::Op::ImageQueryLevels,
1215                    result_type_id,
1216                    query_id,
1217                    image_id,
1218                ));
1219
1220                query_id
1221            }
1222            Iq::NumLayers => {
1223                let vec_size = match dim {
1224                    Id::D1 => crate::VectorSize::Bi,
1225                    Id::D2 | Id::Cube => crate::VectorSize::Tri,
1226                    Id::D3 => crate::VectorSize::Quad,
1227                };
1228                let extended_size_type_id = self.get_numeric_type_id(NumericType::Vector {
1229                    size: vec_size,
1230                    scalar: crate::Scalar::U32,
1231                });
1232                let id_extended = self.gen_id();
1233                let mut inst = Instruction::image_query(
1234                    spirv::Op::ImageQuerySizeLod,
1235                    extended_size_type_id,
1236                    id_extended,
1237                    image_id,
1238                );
1239                inst.add_operand(self.get_index_constant(0));
1240                block.body.push(inst);
1241
1242                let extract_id = self.gen_id();
1243                block.body.push(Instruction::composite_extract(
1244                    result_type_id,
1245                    extract_id,
1246                    id_extended,
1247                    &[vec_size as u32 - 1],
1248                ));
1249
1250                extract_id
1251            }
1252            Iq::NumSamples => {
1253                let query_id = self.gen_id();
1254                block.body.push(Instruction::image_query(
1255                    spirv::Op::ImageQuerySamples,
1256                    result_type_id,
1257                    query_id,
1258                    image_id,
1259                ));
1260
1261                query_id
1262            }
1263        };
1264
1265        Ok(id)
1266    }
1267
1268    pub(super) fn write_image_store(
1269        &mut self,
1270        image: Handle<crate::Expression>,
1271        coordinate: Handle<crate::Expression>,
1272        array_index: Option<Handle<crate::Expression>>,
1273        value: Handle<crate::Expression>,
1274        block: &mut Block,
1275    ) -> Result<(), Error> {
1276        let image_id = self.get_handle_id(image);
1277        let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
1278        let value_id = self.cached[value];
1279
1280        let write = Store { image_id, value_id };
1281
1282        match *self.fun_info[image].ty.inner_with(&self.ir_module.types) {
1283            crate::TypeInner::Image {
1284                class:
1285                    crate::ImageClass::Storage {
1286                        format: crate::StorageFormat::Bgra8Unorm,
1287                        ..
1288                    },
1289                ..
1290            } => self.writer.require_any(
1291                "Bgra8Unorm storage write",
1292                &[spirv::Capability::StorageImageWriteWithoutFormat],
1293            )?,
1294            _ => {}
1295        }
1296
1297        write.generate(
1298            &mut self.writer.id_gen,
1299            coordinates.value_id,
1300            None,
1301            None,
1302            block,
1303        );
1304
1305        Ok(())
1306    }
1307
1308    pub(super) fn write_image_atomic(
1309        &mut self,
1310        image: Handle<crate::Expression>,
1311        coordinate: Handle<crate::Expression>,
1312        array_index: Option<Handle<crate::Expression>>,
1313        fun: crate::AtomicFunction,
1314        value: Handle<crate::Expression>,
1315        block: &mut Block,
1316    ) -> Result<(), Error> {
1317        let image_id = match self.ir_function.originating_global(image) {
1318            Some(handle) => self.writer.global_variables[handle].var_id,
1319            _ => return Err(Error::Validation("Unexpected image type")),
1320        };
1321        let crate::TypeInner::Image { class, .. } =
1322            *self.fun_info[image].ty.inner_with(&self.ir_module.types)
1323        else {
1324            return Err(Error::Validation("Invalid image type"));
1325        };
1326        let crate::ImageClass::Storage { format, .. } = class else {
1327            return Err(Error::Validation("Invalid image class"));
1328        };
1329        let scalar = format.into();
1330        let scalar_type_id = self.get_numeric_type_id(NumericType::Scalar(scalar));
1331        let pointer_type_id = self.get_pointer_type_id(scalar_type_id, spirv::StorageClass::Image);
1332        let signed = scalar.kind == crate::ScalarKind::Sint;
1333        if scalar.width == 8 {
1334            self.writer
1335                .require_any("64 bit image atomics", &[spirv::Capability::Int64Atomics])?;
1336        }
1337        let pointer_id = self.gen_id();
1338        let coordinates = self.write_image_coordinates(coordinate, array_index, block)?;
1339        let sample_id = self.writer.get_constant_scalar(crate::Literal::U32(0));
1340        block.body.push(Instruction::image_texel_pointer(
1341            pointer_type_id,
1342            pointer_id,
1343            image_id,
1344            coordinates.value_id,
1345            sample_id,
1346        ));
1347
1348        let op = match fun {
1349            crate::AtomicFunction::Add => spirv::Op::AtomicIAdd,
1350            crate::AtomicFunction::Subtract => spirv::Op::AtomicISub,
1351            crate::AtomicFunction::And => spirv::Op::AtomicAnd,
1352            crate::AtomicFunction::ExclusiveOr => spirv::Op::AtomicXor,
1353            crate::AtomicFunction::InclusiveOr => spirv::Op::AtomicOr,
1354            crate::AtomicFunction::Min if signed => spirv::Op::AtomicSMin,
1355            crate::AtomicFunction::Min => spirv::Op::AtomicUMin,
1356            crate::AtomicFunction::Max if signed => spirv::Op::AtomicSMax,
1357            crate::AtomicFunction::Max => spirv::Op::AtomicUMax,
1358            crate::AtomicFunction::Exchange { .. } => {
1359                return Err(Error::Validation("Exchange atomics are not supported yet"))
1360            }
1361        };
1362        let result_type_id = self.get_expression_type_id(&self.fun_info[value].ty);
1363        let id = self.gen_id();
1364        let space = crate::AddressSpace::Handle;
1365        let (semantics, scope) = space.to_spirv_semantics_and_scope();
1366        let scope_constant_id = self.get_scope_constant(scope as u32);
1367        let semantics_id = self.get_index_constant(semantics.bits());
1368        let value_id = self.cached[value];
1369
1370        block.body.push(Instruction::image_atomic(
1371            op,
1372            result_type_id,
1373            id,
1374            pointer_id,
1375            scope_constant_id,
1376            semantics_id,
1377            value_id,
1378        ));
1379
1380        Ok(())
1381    }
1382}