naga/back/msl/
mod.rs

1/*!
2Backend for [MSL][msl] (Metal Shading Language).
3
4This backend does not support the [`SHADER_INT64_ATOMIC_ALL_OPS`][all-atom]
5capability.
6
7## Binding model
8
9Metal's bindings are flat per resource. Since there isn't an obvious mapping
10from SPIR-V's descriptor sets, we require a separate mapping provided in the options.
11This mapping may have one or more resource end points for each descriptor set + index
12pair.
13
14## Entry points
15
16Even though MSL and our IR appear to be similar in that the entry points in both can
17accept arguments and return values, the restrictions are different.
18MSL allows the varyings to be either in separate arguments, or inside a single
19`[[stage_in]]` struct. We gather input varyings and form this artificial structure.
20We also add all the (non-Private) globals into the arguments.
21
22At the beginning of the entry point, we assign the local constants and re-compose
23the arguments as they are declared on IR side, so that the rest of the logic can
24pretend that MSL doesn't have all the restrictions it has.
25
26For the result type, if it's a structure, we re-compose it with a temporary value
27holding the result.
28
29[msl]: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf
30[all-atom]: crate::valid::Capabilities::SHADER_INT64_ATOMIC_ALL_OPS
31
32## Pointer-typed bounds-checked expressions and OOB locals
33
34MSL (unlike HLSL and GLSL) has native support for pointer-typed function
35arguments. When the [`BoundsCheckPolicy`] is `ReadZeroSkipWrite` and an
36out-of-bounds index expression is used for such an argument, our strategy is to
37pass a pointer to a dummy variable. These dummy variables are called "OOB
38locals". We emit at most one OOB local per function for each type, since all
39expressions producing a result of that type can share the same OOB local. (Note
40that the OOB local mechanism is not actually implementing "skip write", nor even
41"read zero" in some cases of read-after-write, but doing so would require
42additional effort and the difference is unlikely to matter.)
43
44[`BoundsCheckPolicy`]: crate::proc::BoundsCheckPolicy
45
46## External textures
47
48Support for [`crate::ImageClass::External`] textures is implemented by lowering
49each external texture global variable to 3 `texture2d<float, sample>`s, and a
50constant buffer of type `NagaExternalTextureParams`. This provides up to 3
51planes of texture data (for example single planar RGBA, or separate Y, Cb, and
52Cr planes), and the parameters buffer containing information describing how to
53handle these correctly. The bind target to use for each of these globals is
54specified via the [`BindTarget::external_texture`] field of the relevant
55entries in [`EntryPointResources::resources`].
56
57External textures are supported by WGSL's `textureDimensions()`,
58`textureLoad()`, and `textureSampleBaseClampToEdge()` built-in functions. These
59are implemented using helper functions. See the following functions for how
60these are generated:
61 * `Writer::write_wrapped_image_query`
62 * `Writer::write_wrapped_image_load`
63 * `Writer::write_wrapped_image_sample`
64
65The lowered global variables for each external texture global are passed to the
66entry point as separate arguments (see "Entry points" above). However, they are
67then wrapped in a struct to allow them to be conveniently passed to user
68defined and helper functions. See `writer::EXTERNAL_TEXTURE_WRAPPER_STRUCT`.
69*/
70
71use alloc::{
72    format,
73    string::{String, ToString},
74    vec::Vec,
75};
76use core::fmt::{Error as FmtError, Write};
77
78use crate::{arena::Handle, ir, proc::index, valid::ModuleInfo};
79
80mod keywords;
81pub mod sampler;
82mod writer;
83
84pub use writer::Writer;
85
86pub type Slot = u8;
87pub type InlineSamplerIndex = u8;
88
89#[derive(Clone, Debug, PartialEq, Eq, Hash)]
90#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
91#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
92pub enum BindSamplerTarget {
93    Resource(Slot),
94    Inline(InlineSamplerIndex),
95}
96
97/// Binding information for a Naga [`External`] image global variable.
98///
99/// See the module documentation's section on external textures for details.
100///
101/// [`External`]: crate::ir::ImageClass::External
102#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
104#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
105pub struct BindExternalTextureTarget {
106    pub planes: [Slot; 3],
107    pub params: Slot,
108}
109
110#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
111#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
112#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
113#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
114pub struct BindTarget {
115    pub buffer: Option<Slot>,
116    pub texture: Option<Slot>,
117    pub sampler: Option<BindSamplerTarget>,
118    pub external_texture: Option<BindExternalTextureTarget>,
119    pub mutable: bool,
120}
121
122#[cfg(feature = "deserialize")]
123#[derive(serde::Deserialize)]
124struct BindingMapSerialization {
125    resource_binding: crate::ResourceBinding,
126    bind_target: BindTarget,
127}
128
129#[cfg(feature = "deserialize")]
130fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
131where
132    D: serde::Deserializer<'de>,
133{
134    use serde::Deserialize;
135
136    let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
137    let mut map = BindingMap::default();
138    for item in vec {
139        map.insert(item.resource_binding, item.bind_target);
140    }
141    Ok(map)
142}
143
144// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
145pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
146
147#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
148#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
149#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
150#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
151pub struct EntryPointResources {
152    #[cfg_attr(
153        feature = "deserialize",
154        serde(deserialize_with = "deserialize_binding_map")
155    )]
156    pub resources: BindingMap,
157
158    pub immediates_buffer: Option<Slot>,
159
160    /// The slot of a buffer that contains an array of `u32`,
161    /// one for the size of each bound buffer that contains a runtime array,
162    /// in order of [`crate::GlobalVariable`] declarations.
163    pub sizes_buffer: Option<Slot>,
164}
165
166pub type EntryPointResourceMap = alloc::collections::BTreeMap<String, EntryPointResources>;
167
168enum ResolvedBinding {
169    BuiltIn(crate::BuiltIn),
170    Attribute(u32),
171    Color {
172        location: u32,
173        blend_src: Option<u32>,
174    },
175    User {
176        prefix: &'static str,
177        index: u32,
178        interpolation: Option<ResolvedInterpolation>,
179    },
180    Resource(BindTarget),
181}
182
183#[derive(Copy, Clone)]
184enum ResolvedInterpolation {
185    CenterPerspective,
186    CenterNoPerspective,
187    CentroidPerspective,
188    CentroidNoPerspective,
189    SamplePerspective,
190    SampleNoPerspective,
191    Flat,
192}
193
194// Note: some of these should be removed in favor of proper IR validation.
195
196#[derive(Debug, thiserror::Error)]
197pub enum Error {
198    #[error(transparent)]
199    Format(#[from] FmtError),
200    #[error("bind target {0:?} is empty")]
201    UnimplementedBindTarget(BindTarget),
202    #[error("composing of {0:?} is not implemented yet")]
203    UnsupportedCompose(Handle<crate::Type>),
204    #[error("operation {0:?} is not implemented yet")]
205    UnsupportedBinaryOp(crate::BinaryOperator),
206    #[error("standard function '{0}' is not implemented yet")]
207    UnsupportedCall(String),
208    #[error("feature '{0}' is not implemented yet")]
209    FeatureNotImplemented(String),
210    #[error("internal naga error: module should not have validated: {0}")]
211    GenericValidation(String),
212    #[error("BuiltIn {0:?} is not supported")]
213    UnsupportedBuiltIn(crate::BuiltIn),
214    #[error("capability {0:?} is not supported")]
215    CapabilityNotSupported(crate::valid::Capabilities),
216    #[error("attribute '{0}' is not supported for target MSL version")]
217    UnsupportedAttribute(String),
218    #[error("function '{0}' is not supported for target MSL version")]
219    UnsupportedFunction(String),
220    #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")]
221    UnsupportedWriteableStorageBuffer,
222    #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")]
223    UnsupportedWriteableStorageTexture(ir::ShaderStage),
224    #[error("can not use read-write storage textures prior to MSL 1.2")]
225    UnsupportedRWStorageTexture,
226    #[error("array of '{0}' is not supported for target MSL version")]
227    UnsupportedArrayOf(String),
228    #[error("array of type '{0:?}' is not supported")]
229    UnsupportedArrayOfType(Handle<crate::Type>),
230    #[error("ray tracing is not supported prior to MSL 2.4")]
231    UnsupportedRayTracing,
232    #[error("cooperative matrix is not supported prior to MSL 2.3")]
233    UnsupportedCooperativeMatrix,
234    #[error("overrides should not be present at this stage")]
235    Override,
236    #[error("bitcasting to {0:?} is not supported")]
237    UnsupportedBitCast(crate::TypeInner),
238    #[error(transparent)]
239    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
240    #[error("entry point with stage {0:?} and name '{1}' not found")]
241    EntryPointNotFound(ir::ShaderStage, String),
242}
243
244#[derive(Clone, Debug, PartialEq, thiserror::Error)]
245#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
246#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
247pub enum EntryPointError {
248    #[error("global '{0}' doesn't have a binding")]
249    MissingBinding(String),
250    #[error("mapping of {0:?} is missing")]
251    MissingBindTarget(crate::ResourceBinding),
252    #[error("mapping for immediates is missing")]
253    MissingImmediateData,
254    #[error("mapping for sizes buffer is missing")]
255    MissingSizesBuffer,
256}
257
258/// Points in the MSL code where we might emit a pipeline input or output.
259///
260/// Note that, even though vertex shaders' outputs are always fragment
261/// shaders' inputs, we still need to distinguish `VertexOutput` and
262/// `FragmentInput`, since there are certain differences in the way
263/// [`ResolvedBinding`s] are represented on either side.
264///
265/// [`ResolvedBinding`s]: ResolvedBinding
266#[derive(Clone, Copy, Debug)]
267enum LocationMode {
268    /// Input to the vertex shader.
269    VertexInput,
270
271    /// Output from the vertex shader.
272    VertexOutput,
273
274    /// Input to the fragment shader.
275    FragmentInput,
276
277    /// Output from the fragment shader.
278    FragmentOutput,
279
280    /// Compute shader input or output.
281    Uniform,
282}
283
284#[derive(Clone, Debug, Hash, PartialEq, Eq)]
285#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
286#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
287#[cfg_attr(feature = "deserialize", serde(default))]
288pub struct Options {
289    /// (Major, Minor) target version of the Metal Shading Language.
290    pub lang_version: (u8, u8),
291    /// Map of entry-point resources, indexed by entry point function name, to slots.
292    pub per_entry_point_map: EntryPointResourceMap,
293    /// Samplers to be inlined into the code.
294    pub inline_samplers: Vec<sampler::InlineSampler>,
295    /// Make it possible to link different stages via SPIRV-Cross.
296    pub spirv_cross_compatibility: bool,
297    /// Don't panic on missing bindings, instead generate invalid MSL.
298    pub fake_missing_bindings: bool,
299    /// Bounds checking policies.
300    pub bounds_check_policies: index::BoundsCheckPolicies,
301    /// Should workgroup variables be zero initialized (by polyfilling)?
302    pub zero_initialize_workgroup_memory: bool,
303    /// If set, loops will have code injected into them, forcing the compiler
304    /// to think the number of iterations is bounded.
305    pub force_loop_bounding: bool,
306}
307
308impl Default for Options {
309    fn default() -> Self {
310        Options {
311            lang_version: (1, 0),
312            per_entry_point_map: EntryPointResourceMap::default(),
313            inline_samplers: Vec::new(),
314            spirv_cross_compatibility: false,
315            fake_missing_bindings: true,
316            bounds_check_policies: index::BoundsCheckPolicies::default(),
317            zero_initialize_workgroup_memory: true,
318            force_loop_bounding: true,
319        }
320    }
321}
322
323/// Corresponds to [WebGPU `GPUVertexFormat`](
324/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat).
325#[repr(u32)]
326#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
327#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
328#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
329pub enum VertexFormat {
330    /// One unsigned byte (u8). `u32` in shaders.
331    Uint8 = 0,
332    /// Two unsigned bytes (u8). `vec2<u32>` in shaders.
333    Uint8x2 = 1,
334    /// Four unsigned bytes (u8). `vec4<u32>` in shaders.
335    Uint8x4 = 2,
336    /// One signed byte (i8). `i32` in shaders.
337    Sint8 = 3,
338    /// Two signed bytes (i8). `vec2<i32>` in shaders.
339    Sint8x2 = 4,
340    /// Four signed bytes (i8). `vec4<i32>` in shaders.
341    Sint8x4 = 5,
342    /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders.
343    Unorm8 = 6,
344    /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders.
345    Unorm8x2 = 7,
346    /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
347    Unorm8x4 = 8,
348    /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders.
349    Snorm8 = 9,
350    /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2<f32>` in shaders.
351    Snorm8x2 = 10,
352    /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4<f32>` in shaders.
353    Snorm8x4 = 11,
354    /// One unsigned short (u16). `u32` in shaders.
355    Uint16 = 12,
356    /// Two unsigned shorts (u16). `vec2<u32>` in shaders.
357    Uint16x2 = 13,
358    /// Four unsigned shorts (u16). `vec4<u32>` in shaders.
359    Uint16x4 = 14,
360    /// One signed short (u16). `i32` in shaders.
361    Sint16 = 15,
362    /// Two signed shorts (i16). `vec2<i32>` in shaders.
363    Sint16x2 = 16,
364    /// Four signed shorts (i16). `vec4<i32>` in shaders.
365    Sint16x4 = 17,
366    /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders.
367    Unorm16 = 18,
368    /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders.
369    Unorm16x2 = 19,
370    /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders.
371    Unorm16x4 = 20,
372    /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders.
373    Snorm16 = 21,
374    /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2<f32>` in shaders.
375    Snorm16x2 = 22,
376    /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4<f32>` in shaders.
377    Snorm16x4 = 23,
378    /// One half-precision float (no Rust equiv). `f32` in shaders.
379    Float16 = 24,
380    /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders.
381    Float16x2 = 25,
382    /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders.
383    Float16x4 = 26,
384    /// One single-precision float (f32). `f32` in shaders.
385    Float32 = 27,
386    /// Two single-precision floats (f32). `vec2<f32>` in shaders.
387    Float32x2 = 28,
388    /// Three single-precision floats (f32). `vec3<f32>` in shaders.
389    Float32x3 = 29,
390    /// Four single-precision floats (f32). `vec4<f32>` in shaders.
391    Float32x4 = 30,
392    /// One unsigned int (u32). `u32` in shaders.
393    Uint32 = 31,
394    /// Two unsigned ints (u32). `vec2<u32>` in shaders.
395    Uint32x2 = 32,
396    /// Three unsigned ints (u32). `vec3<u32>` in shaders.
397    Uint32x3 = 33,
398    /// Four unsigned ints (u32). `vec4<u32>` in shaders.
399    Uint32x4 = 34,
400    /// One signed int (i32). `i32` in shaders.
401    Sint32 = 35,
402    /// Two signed ints (i32). `vec2<i32>` in shaders.
403    Sint32x2 = 36,
404    /// Three signed ints (i32). `vec3<i32>` in shaders.
405    Sint32x3 = 37,
406    /// Four signed ints (i32). `vec4<i32>` in shaders.
407    Sint32x4 = 38,
408    /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4<f32>` in shaders.
409    #[cfg_attr(
410        any(feature = "serialize", feature = "deserialize"),
411        serde(rename = "unorm10-10-10-2")
412    )]
413    Unorm10_10_10_2 = 43,
414    /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
415    #[cfg_attr(
416        any(feature = "serialize", feature = "deserialize"),
417        serde(rename = "unorm8x4-bgra")
418    )]
419    Unorm8x4Bgra = 44,
420}
421
422/// Defines how to advance the data in vertex buffers.
423#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
424#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
425#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
426pub enum VertexBufferStepMode {
427    Constant,
428    #[default]
429    ByVertex,
430    ByInstance,
431}
432
433/// A mapping of vertex buffers and their attributes to shader
434/// locations.
435#[derive(Debug, Clone, PartialEq, Eq, Hash)]
436#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
437#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
438pub struct AttributeMapping {
439    /// Shader location associated with this attribute
440    pub shader_location: u32,
441    /// Offset in bytes from start of vertex buffer structure
442    pub offset: u32,
443    /// Format code to help us unpack the attribute into the type
444    /// used by the shader. Codes correspond to a 0-based index of
445    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
446    /// The conversion process is described by
447    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
448    pub format: VertexFormat,
449}
450
451/// A description of a vertex buffer with all the information we
452/// need to address the attributes within it.
453#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
454#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
455#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
456pub struct VertexBufferMapping {
457    /// Shader location associated with this buffer
458    pub id: u32,
459    /// Size of the structure in bytes
460    pub stride: u32,
461    /// Vertex buffer step mode
462    pub step_mode: VertexBufferStepMode,
463    /// Vec of the attributes within the structure
464    pub attributes: Vec<AttributeMapping>,
465}
466
467/// A subset of options that are meant to be changed per pipeline.
468#[derive(Debug, Default, Clone)]
469#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
470#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
471#[cfg_attr(feature = "deserialize", serde(default))]
472pub struct PipelineOptions {
473    /// The entry point to write.
474    ///
475    /// Entry points are identified by a shader stage specification,
476    /// and a name.
477    ///
478    /// If `None`, all entry points will be written. If `Some` and the entry
479    /// point is not found, an error will be thrown while writing.
480    pub entry_point: Option<(ir::ShaderStage, String)>,
481
482    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
483    ///
484    /// Metal doesn't like this for non-point primitive topologies and requires it for
485    /// point primitive topologies.
486    ///
487    /// Enable this for vertex shaders with point primitive topologies.
488    pub allow_and_force_point_size: bool,
489
490    /// If set, when generating the Metal vertex shader, transform it
491    /// to receive the vertex buffers, lengths, and vertex id as args,
492    /// and bounds-check the vertex id and use the index into the
493    /// vertex buffers to access attributes, rather than using Metal's
494    /// [[stage-in]] assembled attribute data. This is true by default,
495    /// but remains configurable for use by tests via deserialization
496    /// of this struct. There is no user-facing way to set this value.
497    pub vertex_pulling_transform: bool,
498
499    /// vertex_buffer_mappings are used during shader translation to
500    /// support vertex pulling.
501    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
502}
503
504impl Options {
505    fn resolve_local_binding(
506        &self,
507        binding: &crate::Binding,
508        mode: LocationMode,
509    ) -> Result<ResolvedBinding, Error> {
510        match *binding {
511            crate::Binding::BuiltIn(mut built_in) => {
512                match built_in {
513                    crate::BuiltIn::Position { ref mut invariant } => {
514                        if *invariant && self.lang_version < (2, 1) {
515                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
516                        }
517
518                        // The 'invariant' attribute may only appear on vertex
519                        // shader outputs, not fragment shader inputs.
520                        if !matches!(mode, LocationMode::VertexOutput) {
521                            *invariant = false;
522                        }
523                    }
524                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
525                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
526                    }
527                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
528                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
529                    }
530                    // macOS: Since Metal 2.2
531                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
532                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => {
533                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
534                    }
535                    // macOS: since Metal 2.3
536                    // iOS: Since Metal 2.2
537                    // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114
538                    crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
539                        return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
540                    }
541                    // macOS: Since Metal 2.2
542                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
543                    crate::BuiltIn::Barycentric { .. } if self.lang_version < (2, 3) => {
544                        return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
545                    }
546                    _ => {}
547                }
548
549                Ok(ResolvedBinding::BuiltIn(built_in))
550            }
551            crate::Binding::Location {
552                location,
553                interpolation,
554                sampling,
555                blend_src,
556                per_primitive: _,
557            } => match mode {
558                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
559                LocationMode::FragmentOutput => {
560                    if blend_src.is_some() && self.lang_version < (1, 2) {
561                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
562                    }
563                    Ok(ResolvedBinding::Color {
564                        location,
565                        blend_src,
566                    })
567                }
568                LocationMode::VertexOutput | LocationMode::FragmentInput => {
569                    Ok(ResolvedBinding::User {
570                        prefix: if self.spirv_cross_compatibility {
571                            "locn"
572                        } else {
573                            "loc"
574                        },
575                        index: location,
576                        interpolation: {
577                            // unwrap: The verifier ensures that vertex shader outputs and fragment
578                            // shader inputs always have fully specified interpolation, and that
579                            // sampling is `None` only for Flat interpolation.
580                            let interpolation = interpolation.unwrap();
581                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
582                            Some(ResolvedInterpolation::from_binding(interpolation, sampling))
583                        },
584                    })
585                }
586                LocationMode::Uniform => Err(Error::GenericValidation(format!(
587                    "Unexpected Binding::Location({location}) for the Uniform mode"
588                ))),
589            },
590        }
591    }
592
593    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
594        self.per_entry_point_map.get(&ep.name)
595    }
596
597    fn get_resource_binding_target(
598        &self,
599        ep: &crate::EntryPoint,
600        res_binding: &crate::ResourceBinding,
601    ) -> Option<&BindTarget> {
602        self.get_entry_point_resources(ep)
603            .and_then(|res| res.resources.get(res_binding))
604    }
605
606    fn resolve_resource_binding(
607        &self,
608        ep: &crate::EntryPoint,
609        res_binding: &crate::ResourceBinding,
610    ) -> Result<ResolvedBinding, EntryPointError> {
611        let target = self.get_resource_binding_target(ep, res_binding);
612        match target {
613            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
614            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
615                prefix: "fake",
616                index: 0,
617                interpolation: None,
618            }),
619            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
620        }
621    }
622
623    fn resolve_immediates(
624        &self,
625        ep: &crate::EntryPoint,
626    ) -> Result<ResolvedBinding, EntryPointError> {
627        let slot = self
628            .get_entry_point_resources(ep)
629            .and_then(|res| res.immediates_buffer);
630        match slot {
631            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
632                buffer: Some(slot),
633                ..Default::default()
634            })),
635            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
636                prefix: "fake",
637                index: 0,
638                interpolation: None,
639            }),
640            None => Err(EntryPointError::MissingImmediateData),
641        }
642    }
643
644    fn resolve_sizes_buffer(
645        &self,
646        ep: &crate::EntryPoint,
647    ) -> Result<ResolvedBinding, EntryPointError> {
648        let slot = self
649            .get_entry_point_resources(ep)
650            .and_then(|res| res.sizes_buffer);
651        match slot {
652            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
653                buffer: Some(slot),
654                ..Default::default()
655            })),
656            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
657                prefix: "fake",
658                index: 0,
659                interpolation: None,
660            }),
661            None => Err(EntryPointError::MissingSizesBuffer),
662        }
663    }
664}
665
666impl ResolvedBinding {
667    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
668        match *self {
669            Self::Resource(BindTarget {
670                sampler: Some(BindSamplerTarget::Inline(index)),
671                ..
672            }) => Some(&options.inline_samplers[index as usize]),
673            _ => None,
674        }
675    }
676
677    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
678        write!(out, " [[")?;
679        match *self {
680            Self::BuiltIn(built_in) => {
681                use crate::BuiltIn as Bi;
682                let name = match built_in {
683                    Bi::Position { invariant: false } => "position",
684                    Bi::Position { invariant: true } => "position, invariant",
685                    Bi::ViewIndex => "amplification_id",
686                    // vertex
687                    Bi::BaseInstance => "base_instance",
688                    Bi::BaseVertex => "base_vertex",
689                    Bi::ClipDistance => "clip_distance",
690                    Bi::InstanceIndex => "instance_id",
691                    Bi::PointSize => "point_size",
692                    Bi::VertexIndex => "vertex_id",
693                    // fragment
694                    Bi::FragDepth => "depth(any)",
695                    Bi::PointCoord => "point_coord",
696                    Bi::FrontFacing => "front_facing",
697                    Bi::PrimitiveIndex => "primitive_id",
698                    Bi::Barycentric { perspective: true } => "barycentric_coord",
699                    Bi::Barycentric { perspective: false } => {
700                        "barycentric_coord, center_no_perspective"
701                    }
702                    Bi::SampleIndex => "sample_id",
703                    Bi::SampleMask => "sample_mask",
704                    // compute
705                    Bi::GlobalInvocationId => "thread_position_in_grid",
706                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
707                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
708                    Bi::WorkGroupId => "threadgroup_position_in_grid",
709                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
710                    Bi::NumWorkGroups => "threadgroups_per_grid",
711                    // subgroup
712                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
713                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
714                    Bi::SubgroupSize => "threads_per_simdgroup",
715                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
716                    Bi::CullDistance | Bi::DrawID => {
717                        return Err(Error::UnsupportedBuiltIn(built_in))
718                    }
719                    Bi::CullPrimitive => "primitive_culled",
720                    // TODO: figure out how to make this written as a function call
721                    Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
722                    Bi::MeshTaskSize
723                    | Bi::VertexCount
724                    | Bi::PrimitiveCount
725                    | Bi::Vertices
726                    | Bi::Primitives => unreachable!(),
727                };
728                write!(out, "{name}")?;
729            }
730            Self::Attribute(index) => write!(out, "attribute({index})")?,
731            Self::Color {
732                location,
733                blend_src,
734            } => {
735                if let Some(blend_src) = blend_src {
736                    write!(out, "color({location}) index({blend_src})")?
737                } else {
738                    write!(out, "color({location})")?
739                }
740            }
741            Self::User {
742                prefix,
743                index,
744                interpolation,
745            } => {
746                write!(out, "user({prefix}{index})")?;
747                if let Some(interpolation) = interpolation {
748                    write!(out, ", ")?;
749                    interpolation.try_fmt(out)?;
750                }
751            }
752            Self::Resource(ref target) => {
753                if let Some(id) = target.buffer {
754                    write!(out, "buffer({id})")?;
755                } else if let Some(id) = target.texture {
756                    write!(out, "texture({id})")?;
757                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
758                    write!(out, "sampler({id})")?;
759                } else {
760                    return Err(Error::UnimplementedBindTarget(target.clone()));
761                }
762            }
763        }
764        write!(out, "]]")?;
765        Ok(())
766    }
767}
768
769impl ResolvedInterpolation {
770    const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
771        use crate::Interpolation as I;
772        use crate::Sampling as S;
773
774        match (interpolation, sampling) {
775            (I::Perspective, S::Center) => Self::CenterPerspective,
776            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
777            (I::Perspective, S::Sample) => Self::SamplePerspective,
778            (I::Linear, S::Center) => Self::CenterNoPerspective,
779            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
780            (I::Linear, S::Sample) => Self::SampleNoPerspective,
781            (I::Flat, _) => Self::Flat,
782            _ => unreachable!(),
783        }
784    }
785
786    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
787        let identifier = match self {
788            Self::CenterPerspective => "center_perspective",
789            Self::CenterNoPerspective => "center_no_perspective",
790            Self::CentroidPerspective => "centroid_perspective",
791            Self::CentroidNoPerspective => "centroid_no_perspective",
792            Self::SamplePerspective => "sample_perspective",
793            Self::SampleNoPerspective => "sample_no_perspective",
794            Self::Flat => "flat",
795        };
796        out.write_str(identifier)?;
797        Ok(())
798    }
799}
800
801/// Information about a translated module that is required
802/// for the use of the result.
803pub struct TranslationInfo {
804    /// Mapping of the entry point names. Each item in the array
805    /// corresponds to an entry point index.
806    ///
807    ///Note: Some entry points may fail translation because of missing bindings.
808    pub entry_point_names: Vec<Result<String, EntryPointError>>,
809}
810
811pub fn write_string(
812    module: &crate::Module,
813    info: &ModuleInfo,
814    options: &Options,
815    pipeline_options: &PipelineOptions,
816) -> Result<(String, TranslationInfo), Error> {
817    let mut w = Writer::new(String::new());
818    let info = w.write(module, info, options, pipeline_options)?;
819    Ok((w.finish(), info))
820}
821
822#[test]
823fn test_error_size() {
824    assert_eq!(size_of::<Error>(), 40);
825}