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