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 push_constant_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.3")]
231    UnsupportedRayTracing,
232    #[error("overrides should not be present at this stage")]
233    Override,
234    #[error("bitcasting to {0:?} is not supported")]
235    UnsupportedBitCast(crate::TypeInner),
236    #[error(transparent)]
237    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
238    #[error("entry point with stage {0:?} and name '{1}' not found")]
239    EntryPointNotFound(ir::ShaderStage, String),
240}
241
242#[derive(Clone, Debug, PartialEq, thiserror::Error)]
243#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
244#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
245pub enum EntryPointError {
246    #[error("global '{0}' doesn't have a binding")]
247    MissingBinding(String),
248    #[error("mapping of {0:?} is missing")]
249    MissingBindTarget(crate::ResourceBinding),
250    #[error("mapping for push constants is missing")]
251    MissingPushConstants,
252    #[error("mapping for sizes buffer is missing")]
253    MissingSizesBuffer,
254}
255
256/// Points in the MSL code where we might emit a pipeline input or output.
257///
258/// Note that, even though vertex shaders' outputs are always fragment
259/// shaders' inputs, we still need to distinguish `VertexOutput` and
260/// `FragmentInput`, since there are certain differences in the way
261/// [`ResolvedBinding`s] are represented on either side.
262///
263/// [`ResolvedBinding`s]: ResolvedBinding
264#[derive(Clone, Copy, Debug)]
265enum LocationMode {
266    /// Input to the vertex shader.
267    VertexInput,
268
269    /// Output from the vertex shader.
270    VertexOutput,
271
272    /// Input to the fragment shader.
273    FragmentInput,
274
275    /// Output from the fragment shader.
276    FragmentOutput,
277
278    /// Compute shader input or output.
279    Uniform,
280}
281
282#[derive(Clone, Debug, Hash, PartialEq, Eq)]
283#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
284#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
285#[cfg_attr(feature = "deserialize", serde(default))]
286pub struct Options {
287    /// (Major, Minor) target version of the Metal Shading Language.
288    pub lang_version: (u8, u8),
289    /// Map of entry-point resources, indexed by entry point function name, to slots.
290    pub per_entry_point_map: EntryPointResourceMap,
291    /// Samplers to be inlined into the code.
292    pub inline_samplers: Vec<sampler::InlineSampler>,
293    /// Make it possible to link different stages via SPIRV-Cross.
294    pub spirv_cross_compatibility: bool,
295    /// Don't panic on missing bindings, instead generate invalid MSL.
296    pub fake_missing_bindings: bool,
297    /// Bounds checking policies.
298    pub bounds_check_policies: index::BoundsCheckPolicies,
299    /// Should workgroup variables be zero initialized (by polyfilling)?
300    pub zero_initialize_workgroup_memory: bool,
301    /// If set, loops will have code injected into them, forcing the compiler
302    /// to think the number of iterations is bounded.
303    pub force_loop_bounding: bool,
304}
305
306impl Default for Options {
307    fn default() -> Self {
308        Options {
309            lang_version: (1, 0),
310            per_entry_point_map: EntryPointResourceMap::default(),
311            inline_samplers: Vec::new(),
312            spirv_cross_compatibility: false,
313            fake_missing_bindings: true,
314            bounds_check_policies: index::BoundsCheckPolicies::default(),
315            zero_initialize_workgroup_memory: true,
316            force_loop_bounding: true,
317        }
318    }
319}
320
321/// Corresponds to [WebGPU `GPUVertexFormat`](
322/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat).
323#[repr(u32)]
324#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
325#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
326#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
327pub enum VertexFormat {
328    /// One unsigned byte (u8). `u32` in shaders.
329    Uint8 = 0,
330    /// Two unsigned bytes (u8). `vec2<u32>` in shaders.
331    Uint8x2 = 1,
332    /// Four unsigned bytes (u8). `vec4<u32>` in shaders.
333    Uint8x4 = 2,
334    /// One signed byte (i8). `i32` in shaders.
335    Sint8 = 3,
336    /// Two signed bytes (i8). `vec2<i32>` in shaders.
337    Sint8x2 = 4,
338    /// Four signed bytes (i8). `vec4<i32>` in shaders.
339    Sint8x4 = 5,
340    /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders.
341    Unorm8 = 6,
342    /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders.
343    Unorm8x2 = 7,
344    /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
345    Unorm8x4 = 8,
346    /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders.
347    Snorm8 = 9,
348    /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2<f32>` in shaders.
349    Snorm8x2 = 10,
350    /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4<f32>` in shaders.
351    Snorm8x4 = 11,
352    /// One unsigned short (u16). `u32` in shaders.
353    Uint16 = 12,
354    /// Two unsigned shorts (u16). `vec2<u32>` in shaders.
355    Uint16x2 = 13,
356    /// Four unsigned shorts (u16). `vec4<u32>` in shaders.
357    Uint16x4 = 14,
358    /// One signed short (u16). `i32` in shaders.
359    Sint16 = 15,
360    /// Two signed shorts (i16). `vec2<i32>` in shaders.
361    Sint16x2 = 16,
362    /// Four signed shorts (i16). `vec4<i32>` in shaders.
363    Sint16x4 = 17,
364    /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders.
365    Unorm16 = 18,
366    /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders.
367    Unorm16x2 = 19,
368    /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders.
369    Unorm16x4 = 20,
370    /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders.
371    Snorm16 = 21,
372    /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2<f32>` in shaders.
373    Snorm16x2 = 22,
374    /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4<f32>` in shaders.
375    Snorm16x4 = 23,
376    /// One half-precision float (no Rust equiv). `f32` in shaders.
377    Float16 = 24,
378    /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders.
379    Float16x2 = 25,
380    /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders.
381    Float16x4 = 26,
382    /// One single-precision float (f32). `f32` in shaders.
383    Float32 = 27,
384    /// Two single-precision floats (f32). `vec2<f32>` in shaders.
385    Float32x2 = 28,
386    /// Three single-precision floats (f32). `vec3<f32>` in shaders.
387    Float32x3 = 29,
388    /// Four single-precision floats (f32). `vec4<f32>` in shaders.
389    Float32x4 = 30,
390    /// One unsigned int (u32). `u32` in shaders.
391    Uint32 = 31,
392    /// Two unsigned ints (u32). `vec2<u32>` in shaders.
393    Uint32x2 = 32,
394    /// Three unsigned ints (u32). `vec3<u32>` in shaders.
395    Uint32x3 = 33,
396    /// Four unsigned ints (u32). `vec4<u32>` in shaders.
397    Uint32x4 = 34,
398    /// One signed int (i32). `i32` in shaders.
399    Sint32 = 35,
400    /// Two signed ints (i32). `vec2<i32>` in shaders.
401    Sint32x2 = 36,
402    /// Three signed ints (i32). `vec3<i32>` in shaders.
403    Sint32x3 = 37,
404    /// Four signed ints (i32). `vec4<i32>` in shaders.
405    Sint32x4 = 38,
406    /// 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.
407    #[cfg_attr(
408        any(feature = "serialize", feature = "deserialize"),
409        serde(rename = "unorm10-10-10-2")
410    )]
411    Unorm10_10_10_2 = 43,
412    /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
413    #[cfg_attr(
414        any(feature = "serialize", feature = "deserialize"),
415        serde(rename = "unorm8x4-bgra")
416    )]
417    Unorm8x4Bgra = 44,
418}
419
420/// Defines how to advance the data in vertex buffers.
421#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
422#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
423#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
424pub enum VertexBufferStepMode {
425    Constant,
426    #[default]
427    ByVertex,
428    ByInstance,
429}
430
431/// A mapping of vertex buffers and their attributes to shader
432/// locations.
433#[derive(Debug, Clone, PartialEq, Eq, Hash)]
434#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
435#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
436pub struct AttributeMapping {
437    /// Shader location associated with this attribute
438    pub shader_location: u32,
439    /// Offset in bytes from start of vertex buffer structure
440    pub offset: u32,
441    /// Format code to help us unpack the attribute into the type
442    /// used by the shader. Codes correspond to a 0-based index of
443    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
444    /// The conversion process is described by
445    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
446    pub format: VertexFormat,
447}
448
449/// A description of a vertex buffer with all the information we
450/// need to address the attributes within it.
451#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
452#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
453#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
454pub struct VertexBufferMapping {
455    /// Shader location associated with this buffer
456    pub id: u32,
457    /// Size of the structure in bytes
458    pub stride: u32,
459    /// Vertex buffer step mode
460    pub step_mode: VertexBufferStepMode,
461    /// Vec of the attributes within the structure
462    pub attributes: Vec<AttributeMapping>,
463}
464
465/// A subset of options that are meant to be changed per pipeline.
466#[derive(Debug, Default, Clone)]
467#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
468#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
469#[cfg_attr(feature = "deserialize", serde(default))]
470pub struct PipelineOptions {
471    /// The entry point to write.
472    ///
473    /// Entry points are identified by a shader stage specification,
474    /// and a name.
475    ///
476    /// If `None`, all entry points will be written. If `Some` and the entry
477    /// point is not found, an error will be thrown while writing.
478    pub entry_point: Option<(ir::ShaderStage, String)>,
479
480    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
481    ///
482    /// Metal doesn't like this for non-point primitive topologies and requires it for
483    /// point primitive topologies.
484    ///
485    /// Enable this for vertex shaders with point primitive topologies.
486    pub allow_and_force_point_size: bool,
487
488    /// If set, when generating the Metal vertex shader, transform it
489    /// to receive the vertex buffers, lengths, and vertex id as args,
490    /// and bounds-check the vertex id and use the index into the
491    /// vertex buffers to access attributes, rather than using Metal's
492    /// [[stage-in]] assembled attribute data. This is true by default,
493    /// but remains configurable for use by tests via deserialization
494    /// of this struct. There is no user-facing way to set this value.
495    pub vertex_pulling_transform: bool,
496
497    /// vertex_buffer_mappings are used during shader translation to
498    /// support vertex pulling.
499    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
500}
501
502impl Options {
503    fn resolve_local_binding(
504        &self,
505        binding: &crate::Binding,
506        mode: LocationMode,
507    ) -> Result<ResolvedBinding, Error> {
508        match *binding {
509            crate::Binding::BuiltIn(mut built_in) => {
510                match built_in {
511                    crate::BuiltIn::Position { ref mut invariant } => {
512                        if *invariant && self.lang_version < (2, 1) {
513                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
514                        }
515
516                        // The 'invariant' attribute may only appear on vertex
517                        // shader outputs, not fragment shader inputs.
518                        if !matches!(mode, LocationMode::VertexOutput) {
519                            *invariant = false;
520                        }
521                    }
522                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
523                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
524                    }
525                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
526                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
527                    }
528                    // macOS: Since Metal 2.2
529                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
530                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => {
531                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
532                    }
533                    // macOS: since Metal 2.3
534                    // iOS: Since Metal 2.2
535                    // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114
536                    crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
537                        return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
538                    }
539                    // macOS: Since Metal 2.2
540                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
541                    crate::BuiltIn::Barycentric if self.lang_version < (2, 3) => {
542                        return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
543                    }
544                    _ => {}
545                }
546
547                Ok(ResolvedBinding::BuiltIn(built_in))
548            }
549            crate::Binding::Location {
550                location,
551                interpolation,
552                sampling,
553                blend_src,
554                per_primitive: _,
555            } => match mode {
556                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
557                LocationMode::FragmentOutput => {
558                    if blend_src.is_some() && self.lang_version < (1, 2) {
559                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
560                    }
561                    Ok(ResolvedBinding::Color {
562                        location,
563                        blend_src,
564                    })
565                }
566                LocationMode::VertexOutput | LocationMode::FragmentInput => {
567                    Ok(ResolvedBinding::User {
568                        prefix: if self.spirv_cross_compatibility {
569                            "locn"
570                        } else {
571                            "loc"
572                        },
573                        index: location,
574                        interpolation: {
575                            // unwrap: The verifier ensures that vertex shader outputs and fragment
576                            // shader inputs always have fully specified interpolation, and that
577                            // sampling is `None` only for Flat interpolation.
578                            let interpolation = interpolation.unwrap();
579                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
580                            Some(ResolvedInterpolation::from_binding(interpolation, sampling))
581                        },
582                    })
583                }
584                LocationMode::Uniform => Err(Error::GenericValidation(format!(
585                    "Unexpected Binding::Location({location}) for the Uniform mode"
586                ))),
587            },
588        }
589    }
590
591    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
592        self.per_entry_point_map.get(&ep.name)
593    }
594
595    fn get_resource_binding_target(
596        &self,
597        ep: &crate::EntryPoint,
598        res_binding: &crate::ResourceBinding,
599    ) -> Option<&BindTarget> {
600        self.get_entry_point_resources(ep)
601            .and_then(|res| res.resources.get(res_binding))
602    }
603
604    fn resolve_resource_binding(
605        &self,
606        ep: &crate::EntryPoint,
607        res_binding: &crate::ResourceBinding,
608    ) -> Result<ResolvedBinding, EntryPointError> {
609        let target = self.get_resource_binding_target(ep, res_binding);
610        match target {
611            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
612            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
613                prefix: "fake",
614                index: 0,
615                interpolation: None,
616            }),
617            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
618        }
619    }
620
621    fn resolve_push_constants(
622        &self,
623        ep: &crate::EntryPoint,
624    ) -> Result<ResolvedBinding, EntryPointError> {
625        let slot = self
626            .get_entry_point_resources(ep)
627            .and_then(|res| res.push_constant_buffer);
628        match slot {
629            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
630                buffer: Some(slot),
631                ..Default::default()
632            })),
633            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
634                prefix: "fake",
635                index: 0,
636                interpolation: None,
637            }),
638            None => Err(EntryPointError::MissingPushConstants),
639        }
640    }
641
642    fn resolve_sizes_buffer(
643        &self,
644        ep: &crate::EntryPoint,
645    ) -> Result<ResolvedBinding, EntryPointError> {
646        let slot = self
647            .get_entry_point_resources(ep)
648            .and_then(|res| res.sizes_buffer);
649        match slot {
650            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
651                buffer: Some(slot),
652                ..Default::default()
653            })),
654            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
655                prefix: "fake",
656                index: 0,
657                interpolation: None,
658            }),
659            None => Err(EntryPointError::MissingSizesBuffer),
660        }
661    }
662}
663
664impl ResolvedBinding {
665    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
666        match *self {
667            Self::Resource(BindTarget {
668                sampler: Some(BindSamplerTarget::Inline(index)),
669                ..
670            }) => Some(&options.inline_samplers[index as usize]),
671            _ => None,
672        }
673    }
674
675    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
676        write!(out, " [[")?;
677        match *self {
678            Self::BuiltIn(built_in) => {
679                use crate::BuiltIn as Bi;
680                let name = match built_in {
681                    Bi::Position { invariant: false } => "position",
682                    Bi::Position { invariant: true } => "position, invariant",
683                    Bi::ViewIndex => "amplification_id",
684                    // vertex
685                    Bi::BaseInstance => "base_instance",
686                    Bi::BaseVertex => "base_vertex",
687                    Bi::ClipDistance => "clip_distance",
688                    Bi::InstanceIndex => "instance_id",
689                    Bi::PointSize => "point_size",
690                    Bi::VertexIndex => "vertex_id",
691                    // fragment
692                    Bi::FragDepth => "depth(any)",
693                    Bi::PointCoord => "point_coord",
694                    Bi::FrontFacing => "front_facing",
695                    Bi::PrimitiveIndex => "primitive_id",
696                    Bi::Barycentric => "barycentric_coord",
697                    Bi::SampleIndex => "sample_id",
698                    Bi::SampleMask => "sample_mask",
699                    // compute
700                    Bi::GlobalInvocationId => "thread_position_in_grid",
701                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
702                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
703                    Bi::WorkGroupId => "threadgroup_position_in_grid",
704                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
705                    Bi::NumWorkGroups => "threadgroups_per_grid",
706                    // subgroup
707                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
708                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
709                    Bi::SubgroupSize => "threads_per_simdgroup",
710                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
711                    Bi::CullDistance | Bi::DrawID => {
712                        return Err(Error::UnsupportedBuiltIn(built_in))
713                    }
714                    Bi::CullPrimitive => "primitive_culled",
715                    // TODO: figure out how to make this written as a function call
716                    Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
717                    Bi::MeshTaskSize => unreachable!(),
718                };
719                write!(out, "{name}")?;
720            }
721            Self::Attribute(index) => write!(out, "attribute({index})")?,
722            Self::Color {
723                location,
724                blend_src,
725            } => {
726                if let Some(blend_src) = blend_src {
727                    write!(out, "color({location}) index({blend_src})")?
728                } else {
729                    write!(out, "color({location})")?
730                }
731            }
732            Self::User {
733                prefix,
734                index,
735                interpolation,
736            } => {
737                write!(out, "user({prefix}{index})")?;
738                if let Some(interpolation) = interpolation {
739                    write!(out, ", ")?;
740                    interpolation.try_fmt(out)?;
741                }
742            }
743            Self::Resource(ref target) => {
744                if let Some(id) = target.buffer {
745                    write!(out, "buffer({id})")?;
746                } else if let Some(id) = target.texture {
747                    write!(out, "texture({id})")?;
748                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
749                    write!(out, "sampler({id})")?;
750                } else {
751                    return Err(Error::UnimplementedBindTarget(target.clone()));
752                }
753            }
754        }
755        write!(out, "]]")?;
756        Ok(())
757    }
758}
759
760impl ResolvedInterpolation {
761    const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
762        use crate::Interpolation as I;
763        use crate::Sampling as S;
764
765        match (interpolation, sampling) {
766            (I::Perspective, S::Center) => Self::CenterPerspective,
767            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
768            (I::Perspective, S::Sample) => Self::SamplePerspective,
769            (I::Linear, S::Center) => Self::CenterNoPerspective,
770            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
771            (I::Linear, S::Sample) => Self::SampleNoPerspective,
772            (I::Flat, _) => Self::Flat,
773            _ => unreachable!(),
774        }
775    }
776
777    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
778        let identifier = match self {
779            Self::CenterPerspective => "center_perspective",
780            Self::CenterNoPerspective => "center_no_perspective",
781            Self::CentroidPerspective => "centroid_perspective",
782            Self::CentroidNoPerspective => "centroid_no_perspective",
783            Self::SamplePerspective => "sample_perspective",
784            Self::SampleNoPerspective => "sample_no_perspective",
785            Self::Flat => "flat",
786        };
787        out.write_str(identifier)?;
788        Ok(())
789    }
790}
791
792/// Information about a translated module that is required
793/// for the use of the result.
794pub struct TranslationInfo {
795    /// Mapping of the entry point names. Each item in the array
796    /// corresponds to an entry point index.
797    ///
798    ///Note: Some entry points may fail translation because of missing bindings.
799    pub entry_point_names: Vec<Result<String, EntryPointError>>,
800}
801
802pub fn write_string(
803    module: &crate::Module,
804    info: &ModuleInfo,
805    options: &Options,
806    pipeline_options: &PipelineOptions,
807) -> Result<(String, TranslationInfo), Error> {
808    let mut w = Writer::new(String::new());
809    let info = w.write(module, info, options, pipeline_options)?;
810    Ok((w.finish(), info))
811}
812
813#[test]
814fn test_error_size() {
815    assert_eq!(size_of::<Error>(), 40);
816}