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, back::TaskDispatchLimits, ir, proc::index, valid::ModuleInfo};
79
80mod keywords;
81mod mesh_shader;
82mod ray;
83pub mod sampler;
84mod writer;
85
86pub use writer::Writer;
87
88pub type Slot = u8;
89pub type InlineSamplerIndex = u8;
90
91#[derive(Clone, Debug, PartialEq, Eq, Hash)]
92#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
93#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
94pub enum BindSamplerTarget {
95    Resource(Slot),
96    Inline(InlineSamplerIndex),
97}
98
99/// Binding information for a Naga [`External`] image global variable.
100///
101/// See the module documentation's section on external textures for details.
102///
103/// [`External`]: crate::ir::ImageClass::External
104#[derive(Clone, Debug, PartialEq, Eq, Hash)]
105#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
106#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
107pub struct BindExternalTextureTarget {
108    pub planes: [Slot; 3],
109    pub params: Slot,
110}
111
112#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
113#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
114#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
115#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
116pub struct BindTarget {
117    pub buffer: Option<Slot>,
118    pub texture: Option<Slot>,
119    pub sampler: Option<BindSamplerTarget>,
120    pub external_texture: Option<BindExternalTextureTarget>,
121    pub mutable: bool,
122}
123
124#[cfg(feature = "deserialize")]
125#[derive(serde::Deserialize)]
126struct BindingMapSerialization {
127    resource_binding: crate::ResourceBinding,
128    bind_target: BindTarget,
129}
130
131#[cfg(feature = "deserialize")]
132fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
133where
134    D: serde::Deserializer<'de>,
135{
136    use serde::Deserialize;
137
138    let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
139    let mut map = BindingMap::default();
140    for item in vec {
141        map.insert(item.resource_binding, item.bind_target);
142    }
143    Ok(map)
144}
145
146// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
147pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
148
149#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
150#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
151#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
152#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
153pub struct EntryPointResources {
154    #[cfg_attr(
155        feature = "deserialize",
156        serde(deserialize_with = "deserialize_binding_map")
157    )]
158    pub resources: BindingMap,
159
160    pub immediates_buffer: Option<Slot>,
161
162    /// The slot of a buffer that contains an array of `u32`,
163    /// one for the size of each bound buffer that contains a runtime array,
164    /// in order of [`crate::GlobalVariable`] declarations.
165    pub sizes_buffer: Option<Slot>,
166}
167
168pub type EntryPointResourceMap = alloc::collections::BTreeMap<String, EntryPointResources>;
169
170enum ResolvedBinding {
171    BuiltIn(crate::BuiltIn),
172    Attribute(u32),
173    Color {
174        location: u32,
175        blend_src: Option<u32>,
176    },
177    User {
178        prefix: &'static str,
179        index: u32,
180        interpolation: Option<ResolvedInterpolation>,
181    },
182    Resource(BindTarget),
183    Payload,
184}
185
186#[derive(Copy, Clone)]
187enum ResolvedInterpolation {
188    CenterPerspective,
189    CenterNoPerspective,
190    CentroidPerspective,
191    CentroidNoPerspective,
192    SamplePerspective,
193    SampleNoPerspective,
194    Flat,
195    PerVertex,
196}
197
198// Note: some of these should be removed in favor of proper IR validation.
199
200#[derive(Debug, thiserror::Error)]
201pub enum Error {
202    #[error(transparent)]
203    Format(#[from] FmtError),
204    #[error("bind target {0:?} is empty")]
205    UnimplementedBindTarget(BindTarget),
206    #[error("composing of {0:?} is not implemented yet")]
207    UnsupportedCompose(Handle<crate::Type>),
208    #[error("operation {0:?} is not implemented yet")]
209    UnsupportedBinaryOp(crate::BinaryOperator),
210    #[error("standard function '{0}' is not implemented yet")]
211    UnsupportedCall(String),
212    #[error("feature '{0}' is not implemented yet")]
213    FeatureNotImplemented(String),
214    #[error("internal naga error: module should not have validated: {0}")]
215    GenericValidation(String),
216    #[error("BuiltIn {0:?} is not supported")]
217    UnsupportedBuiltIn(crate::BuiltIn),
218    #[error("capability {0:?} is not supported")]
219    CapabilityNotSupported(crate::valid::Capabilities),
220    #[error("attribute '{0}' is not supported for target MSL version")]
221    UnsupportedAttribute(String),
222    #[error("function '{0}' is not supported for target MSL version")]
223    UnsupportedFunction(String),
224    #[error("can not use writable storage buffers in fragment stage prior to MSL 1.2")]
225    UnsupportedWritableStorageBuffer,
226    #[error("can not use writable storage textures in {0:?} stage prior to MSL 1.2")]
227    UnsupportedWritableStorageTexture(ir::ShaderStage),
228    #[error("can not use read-write storage textures prior to MSL 1.2")]
229    UnsupportedRWStorageTexture,
230    #[error("array of '{0}' is not supported for target MSL version")]
231    UnsupportedArrayOf(String),
232    #[error("array of type '{0:?}' is not supported")]
233    UnsupportedArrayOfType(Handle<crate::Type>),
234    #[error("ray tracing is not supported prior to MSL 2.4")]
235    UnsupportedRayTracing,
236    #[error("cooperative matrix is not supported prior to MSL 2.3")]
237    UnsupportedCooperativeMatrix,
238    #[error("overrides should not be present at this stage")]
239    Override,
240    #[error("bitcasting to {0:?} is not supported")]
241    UnsupportedBitCast(crate::TypeInner),
242    #[error(transparent)]
243    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
244    #[error("entry point with stage {0:?} and name '{1}' not found")]
245    EntryPointNotFound(ir::ShaderStage, String),
246    #[error("Cannot use mesh shader syntax prior to MSL 3.0")]
247    UnsupportedMeshShader,
248    #[error("Per vertex fragment inputs are not supported prior to MSL 4.0")]
249    PerVertexNotSupported,
250}
251
252#[derive(Clone, Debug, PartialEq, thiserror::Error)]
253#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
254#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
255pub enum EntryPointError {
256    #[error("global '{0}' doesn't have a binding")]
257    MissingBinding(String),
258    #[error("mapping of {0:?} is missing")]
259    MissingBindTarget(crate::ResourceBinding),
260    #[error("mapping for immediates is missing")]
261    MissingImmediateData,
262    #[error("mapping for sizes buffer is missing")]
263    MissingSizesBuffer,
264}
265
266/// Points in the MSL code where we might emit a pipeline input or output.
267///
268/// Note that, even though vertex shaders' outputs are always fragment
269/// shaders' inputs, we still need to distinguish `VertexOutput` and
270/// `FragmentInput`, since there are certain differences in the way
271/// [`ResolvedBinding`s] are represented on either side.
272///
273/// [`ResolvedBinding`s]: ResolvedBinding
274#[derive(Clone, Copy, Debug)]
275enum LocationMode {
276    /// Input to the vertex shader.
277    VertexInput,
278
279    /// Output from the vertex shader.
280    VertexOutput,
281
282    /// Input to the fragment shader.
283    FragmentInput,
284
285    /// Output from the fragment shader.
286    FragmentOutput,
287
288    /// Output from the mesh shader.
289    MeshOutput,
290
291    /// Compute shader input or output.
292    Uniform,
293}
294
295#[derive(Clone, Debug, Hash, PartialEq, Eq)]
296#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
297#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
298#[cfg_attr(feature = "deserialize", serde(default))]
299pub struct Options {
300    /// (Major, Minor) target version of the Metal Shading Language.
301    pub lang_version: (u8, u8),
302    /// Map of entry-point resources, indexed by entry point function name, to slots.
303    pub per_entry_point_map: EntryPointResourceMap,
304    /// Samplers to be inlined into the code.
305    pub inline_samplers: Vec<sampler::InlineSampler>,
306    /// Make it possible to link different stages via SPIRV-Cross.
307    pub spirv_cross_compatibility: bool,
308    /// Don't panic on missing bindings, instead generate invalid MSL.
309    pub fake_missing_bindings: bool,
310    /// Bounds checking policies.
311    pub bounds_check_policies: index::BoundsCheckPolicies,
312    /// Should workgroup variables be zero initialized (by polyfilling)?
313    pub zero_initialize_workgroup_memory: bool,
314    /// If set, loops will have code injected into them, forcing the compiler
315    /// to think the number of iterations is bounded.
316    pub force_loop_bounding: bool,
317    /// Whether and how checks in the task shader should verify the dispatched
318    /// mesh grid size.
319    pub task_dispatch_limits: Option<TaskDispatchLimits>,
320    /// Whether to validate the output of a mesh shader workgroup.
321    pub mesh_shader_primitive_indices_clamp: bool,
322}
323
324impl Default for Options {
325    fn default() -> Self {
326        Options {
327            lang_version: (1, 0),
328            per_entry_point_map: EntryPointResourceMap::default(),
329            inline_samplers: Vec::new(),
330            spirv_cross_compatibility: false,
331            fake_missing_bindings: true,
332            bounds_check_policies: index::BoundsCheckPolicies::default(),
333            zero_initialize_workgroup_memory: true,
334            force_loop_bounding: true,
335            task_dispatch_limits: None,
336            mesh_shader_primitive_indices_clamp: true,
337        }
338    }
339}
340
341/// Corresponds to [WebGPU `GPUVertexFormat`](
342/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat).
343#[repr(u32)]
344#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
345#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
346#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
347pub enum VertexFormat {
348    /// One unsigned byte (u8). `u32` in shaders.
349    Uint8 = 0,
350    /// Two unsigned bytes (u8). `vec2<u32>` in shaders.
351    Uint8x2 = 1,
352    /// Four unsigned bytes (u8). `vec4<u32>` in shaders.
353    Uint8x4 = 2,
354    /// One signed byte (i8). `i32` in shaders.
355    Sint8 = 3,
356    /// Two signed bytes (i8). `vec2<i32>` in shaders.
357    Sint8x2 = 4,
358    /// Four signed bytes (i8). `vec4<i32>` in shaders.
359    Sint8x4 = 5,
360    /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders.
361    Unorm8 = 6,
362    /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders.
363    Unorm8x2 = 7,
364    /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
365    Unorm8x4 = 8,
366    /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders.
367    Snorm8 = 9,
368    /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2<f32>` in shaders.
369    Snorm8x2 = 10,
370    /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4<f32>` in shaders.
371    Snorm8x4 = 11,
372    /// One unsigned short (u16). `u32` in shaders.
373    Uint16 = 12,
374    /// Two unsigned shorts (u16). `vec2<u32>` in shaders.
375    Uint16x2 = 13,
376    /// Four unsigned shorts (u16). `vec4<u32>` in shaders.
377    Uint16x4 = 14,
378    /// One signed short (u16). `i32` in shaders.
379    Sint16 = 15,
380    /// Two signed shorts (i16). `vec2<i32>` in shaders.
381    Sint16x2 = 16,
382    /// Four signed shorts (i16). `vec4<i32>` in shaders.
383    Sint16x4 = 17,
384    /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders.
385    Unorm16 = 18,
386    /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders.
387    Unorm16x2 = 19,
388    /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders.
389    Unorm16x4 = 20,
390    /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders.
391    Snorm16 = 21,
392    /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2<f32>` in shaders.
393    Snorm16x2 = 22,
394    /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4<f32>` in shaders.
395    Snorm16x4 = 23,
396    /// One half-precision float (no Rust equiv). `f32` in shaders.
397    Float16 = 24,
398    /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders.
399    Float16x2 = 25,
400    /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders.
401    Float16x4 = 26,
402    /// One single-precision float (f32). `f32` in shaders.
403    Float32 = 27,
404    /// Two single-precision floats (f32). `vec2<f32>` in shaders.
405    Float32x2 = 28,
406    /// Three single-precision floats (f32). `vec3<f32>` in shaders.
407    Float32x3 = 29,
408    /// Four single-precision floats (f32). `vec4<f32>` in shaders.
409    Float32x4 = 30,
410    /// One unsigned int (u32). `u32` in shaders.
411    Uint32 = 31,
412    /// Two unsigned ints (u32). `vec2<u32>` in shaders.
413    Uint32x2 = 32,
414    /// Three unsigned ints (u32). `vec3<u32>` in shaders.
415    Uint32x3 = 33,
416    /// Four unsigned ints (u32). `vec4<u32>` in shaders.
417    Uint32x4 = 34,
418    /// One signed int (i32). `i32` in shaders.
419    Sint32 = 35,
420    /// Two signed ints (i32). `vec2<i32>` in shaders.
421    Sint32x2 = 36,
422    /// Three signed ints (i32). `vec3<i32>` in shaders.
423    Sint32x3 = 37,
424    /// Four signed ints (i32). `vec4<i32>` in shaders.
425    Sint32x4 = 38,
426    /// 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.
427    #[cfg_attr(
428        any(feature = "serialize", feature = "deserialize"),
429        serde(rename = "unorm10-10-10-2")
430    )]
431    Unorm10_10_10_2 = 43,
432    /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
433    #[cfg_attr(
434        any(feature = "serialize", feature = "deserialize"),
435        serde(rename = "unorm8x4-bgra")
436    )]
437    Unorm8x4Bgra = 44,
438}
439
440/// Defines how to advance the data in vertex buffers.
441#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
442#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
443#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
444pub enum VertexBufferStepMode {
445    Constant,
446    #[default]
447    ByVertex,
448    ByInstance,
449}
450
451/// A mapping of vertex buffers and their attributes to shader
452/// locations.
453#[derive(Debug, Clone, PartialEq, Eq, Hash)]
454#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
455#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
456pub struct AttributeMapping {
457    /// Shader location associated with this attribute
458    pub shader_location: u32,
459    /// Offset in bytes from start of vertex buffer structure
460    pub offset: u32,
461    /// Format code to help us unpack the attribute into the type
462    /// used by the shader. Codes correspond to a 0-based index of
463    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
464    /// The conversion process is described by
465    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
466    pub format: VertexFormat,
467}
468
469/// A description of a vertex buffer with all the information we
470/// need to address the attributes within it.
471#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
472#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
473#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
474pub struct VertexBufferMapping {
475    /// Shader location associated with this buffer
476    pub id: u32,
477    /// Size of the structure in bytes
478    pub stride: u32,
479    /// Vertex buffer step mode
480    pub step_mode: VertexBufferStepMode,
481    /// Vec of the attributes within the structure
482    pub attributes: Vec<AttributeMapping>,
483}
484
485/// A subset of options that are meant to be changed per pipeline.
486#[derive(Debug, Default, Clone)]
487#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
488#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
489#[cfg_attr(feature = "deserialize", serde(default))]
490pub struct PipelineOptions {
491    /// The entry point to write.
492    ///
493    /// Entry points are identified by a shader stage specification,
494    /// and a name.
495    ///
496    /// If `None`, all entry points will be written. If `Some` and the entry
497    /// point is not found, an error will be thrown while writing.
498    pub entry_point: Option<(ir::ShaderStage, String)>,
499
500    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
501    ///
502    /// Metal doesn't like this for non-point primitive topologies and requires it for
503    /// point primitive topologies.
504    ///
505    /// Enable this for vertex/mesh shaders with point primitive topologies.
506    pub allow_and_force_point_size: bool,
507
508    /// If set, when generating the Metal vertex shader, transform it
509    /// to receive the vertex buffers, lengths, and vertex id as args,
510    /// and bounds-check the vertex id and use the index into the
511    /// vertex buffers to access attributes, rather than using Metal's
512    /// [[stage-in]] assembled attribute data. This is true by default,
513    /// but remains configurable for use by tests via deserialization
514    /// of this struct. There is no user-facing way to set this value.
515    pub vertex_pulling_transform: bool,
516
517    /// vertex_buffer_mappings are used during shader translation to
518    /// support vertex pulling.
519    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
520}
521
522impl Options {
523    fn resolve_local_binding(
524        &self,
525        binding: &crate::Binding,
526        mode: LocationMode,
527    ) -> Result<ResolvedBinding, Error> {
528        match *binding {
529            crate::Binding::BuiltIn(mut built_in) => {
530                match built_in {
531                    crate::BuiltIn::Position { ref mut invariant } => {
532                        if *invariant && self.lang_version < (2, 1) {
533                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
534                        }
535
536                        // The 'invariant' attribute may only appear on vertex
537                        // shader outputs, not fragment shader inputs.
538                        if !matches!(mode, LocationMode::VertexOutput) {
539                            *invariant = false;
540                        }
541                    }
542                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
543                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
544                    }
545                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
546                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
547                    }
548                    // macOS: Since Metal 2.2
549                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
550                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => {
551                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
552                    }
553                    // macOS: since Metal 2.3
554                    // iOS: Since Metal 2.2
555                    // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114
556                    crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
557                        return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
558                    }
559                    // macOS: Since Metal 2.2
560                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
561                    crate::BuiltIn::Barycentric { .. } if self.lang_version < (2, 3) => {
562                        return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
563                    }
564                    _ => {}
565                }
566
567                Ok(ResolvedBinding::BuiltIn(built_in))
568            }
569            crate::Binding::Location {
570                location,
571                interpolation,
572                sampling,
573                blend_src,
574                per_primitive,
575            } => match mode {
576                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
577                LocationMode::FragmentOutput => {
578                    if blend_src.is_some() && self.lang_version < (1, 2) {
579                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
580                    }
581                    Ok(ResolvedBinding::Color {
582                        location,
583                        blend_src,
584                    })
585                }
586                LocationMode::VertexOutput
587                | LocationMode::FragmentInput
588                | LocationMode::MeshOutput => {
589                    Ok(ResolvedBinding::User {
590                        prefix: if self.spirv_cross_compatibility {
591                            "locn"
592                        } else {
593                            "loc"
594                        },
595                        index: location,
596                        interpolation: {
597                            // unwrap: The verifier ensures that vertex shader outputs and fragment
598                            // shader inputs always have fully specified interpolation, and that
599                            // sampling is `None` only for Flat interpolation.
600                            let interpolation = interpolation.unwrap();
601                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
602                            Some(ResolvedInterpolation::from_binding(
603                                interpolation,
604                                sampling,
605                                per_primitive,
606                            ))
607                        },
608                    })
609                }
610                LocationMode::Uniform => Err(Error::GenericValidation(format!(
611                    "Unexpected Binding::Location({location}) for the Uniform mode"
612                ))),
613            },
614        }
615    }
616
617    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
618        self.per_entry_point_map.get(&ep.name)
619    }
620
621    fn get_resource_binding_target(
622        &self,
623        ep: &crate::EntryPoint,
624        res_binding: &crate::ResourceBinding,
625    ) -> Option<&BindTarget> {
626        self.get_entry_point_resources(ep)
627            .and_then(|res| res.resources.get(res_binding))
628    }
629
630    fn resolve_resource_binding(
631        &self,
632        ep: &crate::EntryPoint,
633        res_binding: &crate::ResourceBinding,
634    ) -> Result<ResolvedBinding, EntryPointError> {
635        let target = self.get_resource_binding_target(ep, res_binding);
636        match target {
637            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
638            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
639                prefix: "fake",
640                index: 0,
641                interpolation: None,
642            }),
643            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
644        }
645    }
646
647    fn resolve_immediates(
648        &self,
649        ep: &crate::EntryPoint,
650    ) -> Result<ResolvedBinding, EntryPointError> {
651        let slot = self
652            .get_entry_point_resources(ep)
653            .and_then(|res| res.immediates_buffer);
654        match slot {
655            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
656                buffer: Some(slot),
657                ..Default::default()
658            })),
659            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
660                prefix: "fake",
661                index: 0,
662                interpolation: None,
663            }),
664            None => Err(EntryPointError::MissingImmediateData),
665        }
666    }
667
668    fn resolve_sizes_buffer(
669        &self,
670        ep: &crate::EntryPoint,
671    ) -> Result<ResolvedBinding, EntryPointError> {
672        let slot = self
673            .get_entry_point_resources(ep)
674            .and_then(|res| res.sizes_buffer);
675        match slot {
676            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
677                buffer: Some(slot),
678                ..Default::default()
679            })),
680            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
681                prefix: "fake",
682                index: 0,
683                interpolation: None,
684            }),
685            None => Err(EntryPointError::MissingSizesBuffer),
686        }
687    }
688}
689
690impl ResolvedBinding {
691    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
692        match *self {
693            Self::Resource(BindTarget {
694                sampler: Some(BindSamplerTarget::Inline(index)),
695                ..
696            }) => Some(&options.inline_samplers[index as usize]),
697            _ => None,
698        }
699    }
700
701    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
702        write!(out, " [[")?;
703        match *self {
704            Self::BuiltIn(built_in) => {
705                use crate::BuiltIn as Bi;
706                let name = match built_in {
707                    Bi::Position { invariant: false } => "position",
708                    Bi::Position { invariant: true } => "position, invariant",
709                    Bi::ViewIndex => "amplification_id",
710                    // vertex
711                    Bi::BaseInstance => "base_instance",
712                    Bi::BaseVertex => "base_vertex",
713                    Bi::ClipDistances => "clip_distance",
714                    Bi::InstanceIndex => "instance_id",
715                    Bi::PointSize => "point_size",
716                    Bi::VertexIndex => "vertex_id",
717                    // fragment
718                    Bi::FragDepth => "depth(any)",
719                    Bi::PointCoord => "point_coord",
720                    Bi::FrontFacing => "front_facing",
721                    Bi::PrimitiveIndex => "primitive_id",
722                    Bi::Barycentric { perspective: true } => "barycentric_coord",
723                    Bi::Barycentric { perspective: false } => {
724                        "barycentric_coord, center_no_perspective"
725                    }
726                    Bi::SampleIndex => "sample_id",
727                    Bi::SampleMask => "sample_mask",
728                    // compute
729                    Bi::GlobalInvocationId => "thread_position_in_grid",
730                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
731                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
732                    Bi::WorkGroupId => "threadgroup_position_in_grid",
733                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
734                    Bi::NumWorkGroups => "threadgroups_per_grid",
735                    // subgroup
736                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
737                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
738                    Bi::SubgroupSize => "threads_per_simdgroup",
739                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
740                    Bi::CullDistance | Bi::DrawIndex => {
741                        return Err(Error::UnsupportedBuiltIn(built_in))
742                    }
743                    Bi::CullPrimitive => "primitive_culled",
744                    // TODO: figure out how to make this written as a function call
745                    Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
746                    // These aren't real builtins passed into MSL. They are extracted by the
747                    // wrapper function which actually sets the outputs.
748                    Bi::MeshTaskSize
749                    | Bi::VertexCount
750                    | Bi::PrimitiveCount
751                    | Bi::Vertices
752                    | Bi::Primitives
753                    | Bi::RayInvocationId
754                    | Bi::NumRayInvocations
755                    | Bi::InstanceCustomData
756                    | Bi::GeometryIndex
757                    | Bi::WorldRayOrigin
758                    | Bi::WorldRayDirection
759                    | Bi::ObjectRayOrigin
760                    | Bi::ObjectRayDirection
761                    | Bi::RayTmin
762                    | Bi::RayTCurrentMax
763                    | Bi::ObjectToWorld
764                    | Bi::WorldToObject
765                    | Bi::HitKind => unreachable!(),
766                };
767                write!(out, "{name}")?;
768            }
769            Self::Attribute(index) => write!(out, "attribute({index})")?,
770            Self::Color {
771                location,
772                blend_src,
773            } => {
774                if let Some(blend_src) = blend_src {
775                    write!(out, "color({location}) index({blend_src})")?
776                } else {
777                    write!(out, "color({location})")?
778                }
779            }
780            Self::User {
781                prefix,
782                index,
783                interpolation,
784            } => {
785                write!(out, "user({prefix}{index})")?;
786                if let Some(interpolation) = interpolation {
787                    write!(out, ", ")?;
788                    interpolation.try_fmt(out)?;
789                }
790            }
791            Self::Resource(ref target) => {
792                if let Some(id) = target.buffer {
793                    write!(out, "buffer({id})")?;
794                } else if let Some(id) = target.texture {
795                    write!(out, "texture({id})")?;
796                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
797                    write!(out, "sampler({id})")?;
798                } else {
799                    return Err(Error::UnimplementedBindTarget(target.clone()));
800                }
801            }
802            Self::Payload => write!(out, "payload")?,
803        }
804        write!(out, "]]")?;
805        Ok(())
806    }
807}
808
809impl ResolvedInterpolation {
810    const fn from_binding(
811        interpolation: crate::Interpolation,
812        sampling: crate::Sampling,
813        per_primitive: bool,
814    ) -> Self {
815        use crate::Interpolation as I;
816        use crate::Sampling as S;
817
818        if per_primitive {
819            return Self::Flat;
820        }
821
822        match (interpolation, sampling) {
823            (I::Perspective, S::Center) => Self::CenterPerspective,
824            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
825            (I::Perspective, S::Sample) => Self::SamplePerspective,
826            (I::Linear, S::Center) => Self::CenterNoPerspective,
827            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
828            (I::Linear, S::Sample) => Self::SampleNoPerspective,
829            (I::Flat, _) => Self::Flat,
830            (I::PerVertex, S::Center) => Self::PerVertex,
831            _ => unreachable!(),
832        }
833    }
834
835    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
836        let identifier = match self {
837            Self::CenterPerspective => "center_perspective",
838            Self::CenterNoPerspective => "center_no_perspective",
839            Self::CentroidPerspective => "centroid_perspective",
840            Self::CentroidNoPerspective => "centroid_no_perspective",
841            Self::SamplePerspective => "sample_perspective",
842            Self::SampleNoPerspective => "sample_no_perspective",
843            Self::Flat => "flat",
844            Self::PerVertex => unreachable!(),
845        };
846        out.write_str(identifier)?;
847        Ok(())
848    }
849}
850
851struct EntryPointArgument {
852    ty_name: String,
853    name: String,
854    binding: String,
855    init: Option<Handle<crate::Expression>>,
856}
857
858/// Shorthand result used internally by the backend
859type BackendResult = Result<(), Error>;
860
861const NAMESPACE: &str = "metal";
862
863// The name of the array member of the Metal struct types we generate to
864// represent Naga `Array` types. See the comments in `Writer::write_type_defs`
865// for details.
866const WRAPPED_ARRAY_FIELD: &str = "inner";
867
868/// Information about a translated module that is required
869/// for the use of the result.
870pub struct TranslationInfo {
871    /// Mapping of the entry point names. Each item in the array
872    /// corresponds to an entry point index.
873    ///
874    ///Note: Some entry points may fail translation because of missing bindings.
875    pub entry_point_names: Vec<Result<String, EntryPointError>>,
876}
877
878pub fn write_string(
879    module: &crate::Module,
880    info: &ModuleInfo,
881    options: &Options,
882    pipeline_options: &PipelineOptions,
883) -> Result<(String, TranslationInfo), Error> {
884    let mut w = Writer::new(String::new());
885    let info = w.write(module, info, options, pipeline_options)?;
886    Ok((w.finish(), info))
887}
888
889pub fn supported_capabilities() -> crate::valid::Capabilities {
890    use crate::valid::Capabilities as Caps;
891    Caps::IMMEDIATES
892        // No FLOAT64
893        | Caps::PRIMITIVE_INDEX
894        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY
895        // No BUFFER_BINDING_ARRAY
896        | Caps::STORAGE_TEXTURE_BINDING_ARRAY
897        | Caps::STORAGE_BUFFER_BINDING_ARRAY
898        | Caps::CLIP_DISTANCES
899        // No CULL_DISTANCE
900        | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
901        | Caps::MULTIVIEW
902        // No EARLY_DEPTH_TEST
903        | Caps::MULTISAMPLED_SHADING
904        | Caps::RAY_QUERY
905        | Caps::DUAL_SOURCE_BLENDING
906        | Caps::CUBE_ARRAY_TEXTURES
907        | Caps::SHADER_INT64
908        | Caps::SUBGROUP
909        | Caps::SUBGROUP_BARRIER
910        // No SUBGROUP_VERTEX_STAGE
911        | Caps::SHADER_INT64_ATOMIC_MIN_MAX
912        // No SHADER_INT64_ATOMIC_ALL_OPS
913        | Caps::SHADER_FLOAT32_ATOMIC
914        | Caps::TEXTURE_ATOMIC
915        | Caps::TEXTURE_INT64_ATOMIC
916        // No RAY_HIT_VERTEX_POSITION
917        | Caps::SHADER_FLOAT16
918        | Caps::SHADER_INT16
919        | Caps::TEXTURE_EXTERNAL
920        | Caps::SHADER_FLOAT16_IN_FLOAT32
921        | Caps::SHADER_BARYCENTRICS
922        | Caps::MESH_SHADER
923        | Caps::MESH_SHADER_POINT_TOPOLOGY
924        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
925        // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
926        | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
927        | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
928        | Caps::COOPERATIVE_MATRIX
929        | Caps::PER_VERTEX
930        // No RAY_TRACING_PIPELINE
931        // No DRAW_INDEX
932        // No MEMORY_DECORATION_VOLATILE
933        | Caps::MEMORY_DECORATION_COHERENT
934}
935
936#[test]
937fn test_error_size() {
938    assert_eq!(size_of::<Error>(), 40);
939}