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    /// If true (the default), integer division and modulo operations use
323    /// wrapper functions that guard against division by zero and signed
324    /// overflow. Set to false to emit raw division for faster compute shaders
325    /// where the developer guarantees non-zero divisors.
326    pub emit_int_div_checks: bool,
327}
328
329impl Default for Options {
330    fn default() -> Self {
331        Options {
332            lang_version: (1, 0),
333            per_entry_point_map: EntryPointResourceMap::default(),
334            inline_samplers: Vec::new(),
335            spirv_cross_compatibility: false,
336            fake_missing_bindings: true,
337            bounds_check_policies: index::BoundsCheckPolicies::default(),
338            zero_initialize_workgroup_memory: true,
339            force_loop_bounding: true,
340            task_dispatch_limits: None,
341            mesh_shader_primitive_indices_clamp: true,
342            emit_int_div_checks: true,
343        }
344    }
345}
346
347/// Corresponds to [WebGPU `GPUVertexFormat`](
348/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat).
349#[repr(u32)]
350#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
351#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
352#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
353pub enum VertexFormat {
354    /// One unsigned byte (u8). `u32` in shaders.
355    Uint8 = 0,
356    /// Two unsigned bytes (u8). `vec2<u32>` in shaders.
357    Uint8x2 = 1,
358    /// Four unsigned bytes (u8). `vec4<u32>` in shaders.
359    Uint8x4 = 2,
360    /// One signed byte (i8). `i32` in shaders.
361    Sint8 = 3,
362    /// Two signed bytes (i8). `vec2<i32>` in shaders.
363    Sint8x2 = 4,
364    /// Four signed bytes (i8). `vec4<i32>` in shaders.
365    Sint8x4 = 5,
366    /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders.
367    Unorm8 = 6,
368    /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders.
369    Unorm8x2 = 7,
370    /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
371    Unorm8x4 = 8,
372    /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders.
373    Snorm8 = 9,
374    /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2<f32>` in shaders.
375    Snorm8x2 = 10,
376    /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4<f32>` in shaders.
377    Snorm8x4 = 11,
378    /// One unsigned short (u16). `u32` in shaders.
379    Uint16 = 12,
380    /// Two unsigned shorts (u16). `vec2<u32>` in shaders.
381    Uint16x2 = 13,
382    /// Four unsigned shorts (u16). `vec4<u32>` in shaders.
383    Uint16x4 = 14,
384    /// One signed short (u16). `i32` in shaders.
385    Sint16 = 15,
386    /// Two signed shorts (i16). `vec2<i32>` in shaders.
387    Sint16x2 = 16,
388    /// Four signed shorts (i16). `vec4<i32>` in shaders.
389    Sint16x4 = 17,
390    /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders.
391    Unorm16 = 18,
392    /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders.
393    Unorm16x2 = 19,
394    /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders.
395    Unorm16x4 = 20,
396    /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders.
397    Snorm16 = 21,
398    /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2<f32>` in shaders.
399    Snorm16x2 = 22,
400    /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4<f32>` in shaders.
401    Snorm16x4 = 23,
402    /// One half-precision float (no Rust equiv). `f32` in shaders.
403    Float16 = 24,
404    /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders.
405    Float16x2 = 25,
406    /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders.
407    Float16x4 = 26,
408    /// One single-precision float (f32). `f32` in shaders.
409    Float32 = 27,
410    /// Two single-precision floats (f32). `vec2<f32>` in shaders.
411    Float32x2 = 28,
412    /// Three single-precision floats (f32). `vec3<f32>` in shaders.
413    Float32x3 = 29,
414    /// Four single-precision floats (f32). `vec4<f32>` in shaders.
415    Float32x4 = 30,
416    /// One unsigned int (u32). `u32` in shaders.
417    Uint32 = 31,
418    /// Two unsigned ints (u32). `vec2<u32>` in shaders.
419    Uint32x2 = 32,
420    /// Three unsigned ints (u32). `vec3<u32>` in shaders.
421    Uint32x3 = 33,
422    /// Four unsigned ints (u32). `vec4<u32>` in shaders.
423    Uint32x4 = 34,
424    /// One signed int (i32). `i32` in shaders.
425    Sint32 = 35,
426    /// Two signed ints (i32). `vec2<i32>` in shaders.
427    Sint32x2 = 36,
428    /// Three signed ints (i32). `vec3<i32>` in shaders.
429    Sint32x3 = 37,
430    /// Four signed ints (i32). `vec4<i32>` in shaders.
431    Sint32x4 = 38,
432    /// 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.
433    #[cfg_attr(
434        any(feature = "serialize", feature = "deserialize"),
435        serde(rename = "unorm10-10-10-2")
436    )]
437    Unorm10_10_10_2 = 43,
438    /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
439    #[cfg_attr(
440        any(feature = "serialize", feature = "deserialize"),
441        serde(rename = "unorm8x4-bgra")
442    )]
443    Unorm8x4Bgra = 44,
444}
445
446/// Defines how to advance the data in vertex buffers.
447#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
448#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
449#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
450pub enum VertexBufferStepMode {
451    Constant,
452    #[default]
453    ByVertex,
454    ByInstance,
455}
456
457/// A mapping of vertex buffers and their attributes to shader
458/// locations.
459#[derive(Debug, Clone, PartialEq, Eq, Hash)]
460#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
461#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
462pub struct AttributeMapping {
463    /// Shader location associated with this attribute
464    pub shader_location: u32,
465    /// Offset in bytes from start of vertex buffer structure
466    pub offset: u32,
467    /// Format code to help us unpack the attribute into the type
468    /// used by the shader. Codes correspond to a 0-based index of
469    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
470    /// The conversion process is described by
471    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
472    pub format: VertexFormat,
473}
474
475/// A description of a vertex buffer with all the information we
476/// need to address the attributes within it.
477#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
478#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
479#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
480pub struct VertexBufferMapping {
481    /// Shader location associated with this buffer
482    pub id: u32,
483    /// Size of the structure in bytes
484    pub stride: u32,
485    /// Vertex buffer step mode
486    pub step_mode: VertexBufferStepMode,
487    /// Vec of the attributes within the structure
488    pub attributes: Vec<AttributeMapping>,
489}
490
491/// A subset of options that are meant to be changed per pipeline.
492#[derive(Debug, Default, Clone)]
493#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
494#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
495#[cfg_attr(feature = "deserialize", serde(default))]
496pub struct PipelineOptions {
497    /// The entry point to write.
498    ///
499    /// Entry points are identified by a shader stage specification,
500    /// and a name.
501    ///
502    /// If `None`, all entry points will be written. If `Some` and the entry
503    /// point is not found, an error will be thrown while writing.
504    pub entry_point: Option<(ir::ShaderStage, String)>,
505
506    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
507    ///
508    /// Metal doesn't like this for non-point primitive topologies and requires it for
509    /// point primitive topologies.
510    ///
511    /// Enable this for vertex/mesh shaders with point primitive topologies.
512    pub allow_and_force_point_size: bool,
513
514    /// If set, when generating the Metal vertex shader, transform it
515    /// to receive the vertex buffers, lengths, and vertex id as args,
516    /// and bounds-check the vertex id and use the index into the
517    /// vertex buffers to access attributes, rather than using Metal's
518    /// [[stage-in]] assembled attribute data. This is true by default,
519    /// but remains configurable for use by tests via deserialization
520    /// of this struct. There is no user-facing way to set this value.
521    pub vertex_pulling_transform: bool,
522
523    /// vertex_buffer_mappings are used during shader translation to
524    /// support vertex pulling.
525    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
526}
527
528impl Options {
529    fn resolve_local_binding(
530        &self,
531        binding: &crate::Binding,
532        mode: LocationMode,
533    ) -> Result<ResolvedBinding, Error> {
534        match *binding {
535            crate::Binding::BuiltIn(mut built_in) => {
536                match built_in {
537                    crate::BuiltIn::Position { ref mut invariant } => {
538                        if *invariant && self.lang_version < (2, 1) {
539                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
540                        }
541
542                        // The 'invariant' attribute may only appear on vertex
543                        // shader outputs, not fragment shader inputs.
544                        if !matches!(mode, LocationMode::VertexOutput) {
545                            *invariant = false;
546                        }
547                    }
548                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
549                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
550                    }
551                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
552                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
553                    }
554                    // macOS: Since Metal 2.2
555                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
556                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => {
557                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
558                    }
559                    // macOS: since Metal 2.3
560                    // iOS: Since Metal 2.2
561                    // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114
562                    crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
563                        return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
564                    }
565                    // macOS: Since Metal 2.2
566                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
567                    crate::BuiltIn::Barycentric { .. } if self.lang_version < (2, 3) => {
568                        return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
569                    }
570                    _ => {}
571                }
572
573                Ok(ResolvedBinding::BuiltIn(built_in))
574            }
575            crate::Binding::Location {
576                location,
577                interpolation,
578                sampling,
579                blend_src,
580                per_primitive,
581            } => match mode {
582                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
583                LocationMode::FragmentOutput => {
584                    if blend_src.is_some() && self.lang_version < (1, 2) {
585                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
586                    }
587                    Ok(ResolvedBinding::Color {
588                        location,
589                        blend_src,
590                    })
591                }
592                LocationMode::VertexOutput
593                | LocationMode::FragmentInput
594                | LocationMode::MeshOutput => {
595                    Ok(ResolvedBinding::User {
596                        prefix: if self.spirv_cross_compatibility {
597                            "locn"
598                        } else {
599                            "loc"
600                        },
601                        index: location,
602                        interpolation: {
603                            // unwrap: The verifier ensures that vertex shader outputs and fragment
604                            // shader inputs always have fully specified interpolation, and that
605                            // sampling is `None` only for Flat interpolation.
606                            let interpolation = interpolation.unwrap();
607                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
608                            Some(ResolvedInterpolation::from_binding(
609                                interpolation,
610                                sampling,
611                                per_primitive,
612                            ))
613                        },
614                    })
615                }
616                LocationMode::Uniform => Err(Error::GenericValidation(format!(
617                    "Unexpected Binding::Location({location}) for the Uniform mode"
618                ))),
619            },
620        }
621    }
622
623    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
624        self.per_entry_point_map.get(&ep.name)
625    }
626
627    fn get_resource_binding_target(
628        &self,
629        ep: &crate::EntryPoint,
630        res_binding: &crate::ResourceBinding,
631    ) -> Option<&BindTarget> {
632        self.get_entry_point_resources(ep)
633            .and_then(|res| res.resources.get(res_binding))
634    }
635
636    fn resolve_resource_binding(
637        &self,
638        ep: &crate::EntryPoint,
639        res_binding: &crate::ResourceBinding,
640    ) -> Result<ResolvedBinding, EntryPointError> {
641        let target = self.get_resource_binding_target(ep, res_binding);
642        match target {
643            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
644            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
645                prefix: "fake",
646                index: 0,
647                interpolation: None,
648            }),
649            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
650        }
651    }
652
653    fn resolve_immediates(
654        &self,
655        ep: &crate::EntryPoint,
656    ) -> Result<ResolvedBinding, EntryPointError> {
657        let slot = self
658            .get_entry_point_resources(ep)
659            .and_then(|res| res.immediates_buffer);
660        match slot {
661            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
662                buffer: Some(slot),
663                ..Default::default()
664            })),
665            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
666                prefix: "fake",
667                index: 0,
668                interpolation: None,
669            }),
670            None => Err(EntryPointError::MissingImmediateData),
671        }
672    }
673
674    fn resolve_sizes_buffer(
675        &self,
676        ep: &crate::EntryPoint,
677    ) -> Result<ResolvedBinding, EntryPointError> {
678        let slot = self
679            .get_entry_point_resources(ep)
680            .and_then(|res| res.sizes_buffer);
681        match slot {
682            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
683                buffer: Some(slot),
684                ..Default::default()
685            })),
686            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
687                prefix: "fake",
688                index: 0,
689                interpolation: None,
690            }),
691            None => Err(EntryPointError::MissingSizesBuffer),
692        }
693    }
694}
695
696impl ResolvedBinding {
697    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
698        match *self {
699            Self::Resource(BindTarget {
700                sampler: Some(BindSamplerTarget::Inline(index)),
701                ..
702            }) => Some(&options.inline_samplers[index as usize]),
703            _ => None,
704        }
705    }
706
707    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
708        write!(out, " [[")?;
709        match *self {
710            Self::BuiltIn(built_in) => {
711                use crate::BuiltIn as Bi;
712                let name = match built_in {
713                    Bi::Position { invariant: false } => "position",
714                    Bi::Position { invariant: true } => "position, invariant",
715                    Bi::ViewIndex => "amplification_id",
716                    // vertex
717                    Bi::BaseInstance => "base_instance",
718                    Bi::BaseVertex => "base_vertex",
719                    Bi::ClipDistances => "clip_distance",
720                    Bi::InstanceIndex => "instance_id",
721                    Bi::PointSize => "point_size",
722                    Bi::VertexIndex => "vertex_id",
723                    // fragment
724                    Bi::FragDepth => "depth(any)",
725                    Bi::PointCoord => "point_coord",
726                    Bi::FrontFacing => "front_facing",
727                    Bi::PrimitiveIndex => "primitive_id",
728                    Bi::Barycentric { perspective: true } => "barycentric_coord",
729                    Bi::Barycentric { perspective: false } => {
730                        "barycentric_coord, center_no_perspective"
731                    }
732                    Bi::SampleIndex => "sample_id",
733                    Bi::SampleMask => "sample_mask",
734                    // compute
735                    Bi::GlobalInvocationId => "thread_position_in_grid",
736                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
737                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
738                    Bi::WorkGroupId => "threadgroup_position_in_grid",
739                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
740                    Bi::NumWorkGroups => "threadgroups_per_grid",
741                    // subgroup
742                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
743                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
744                    Bi::SubgroupSize => "threads_per_simdgroup",
745                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
746                    Bi::CullDistance | Bi::DrawIndex => {
747                        return Err(Error::UnsupportedBuiltIn(built_in))
748                    }
749                    Bi::CullPrimitive => "primitive_culled",
750                    // TODO: figure out how to make this written as a function call
751                    Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
752                    // These aren't real builtins passed into MSL. They are extracted by the
753                    // wrapper function which actually sets the outputs.
754                    Bi::MeshTaskSize
755                    | Bi::VertexCount
756                    | Bi::PrimitiveCount
757                    | Bi::Vertices
758                    | Bi::Primitives
759                    | Bi::RayInvocationId
760                    | Bi::NumRayInvocations
761                    | Bi::InstanceCustomData
762                    | Bi::GeometryIndex
763                    | Bi::WorldRayOrigin
764                    | Bi::WorldRayDirection
765                    | Bi::ObjectRayOrigin
766                    | Bi::ObjectRayDirection
767                    | Bi::RayTmin
768                    | Bi::RayTCurrentMax
769                    | Bi::ObjectToWorld
770                    | Bi::WorldToObject
771                    | Bi::HitKind => unreachable!(),
772                };
773                write!(out, "{name}")?;
774            }
775            Self::Attribute(index) => write!(out, "attribute({index})")?,
776            Self::Color {
777                location,
778                blend_src,
779            } => {
780                if let Some(blend_src) = blend_src {
781                    write!(out, "color({location}) index({blend_src})")?
782                } else {
783                    write!(out, "color({location})")?
784                }
785            }
786            Self::User {
787                prefix,
788                index,
789                interpolation,
790            } => {
791                write!(out, "user({prefix}{index})")?;
792                if let Some(interpolation) = interpolation {
793                    write!(out, ", ")?;
794                    interpolation.try_fmt(out)?;
795                }
796            }
797            Self::Resource(ref target) => {
798                if let Some(id) = target.buffer {
799                    write!(out, "buffer({id})")?;
800                } else if let Some(id) = target.texture {
801                    write!(out, "texture({id})")?;
802                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
803                    write!(out, "sampler({id})")?;
804                } else {
805                    return Err(Error::UnimplementedBindTarget(target.clone()));
806                }
807            }
808            Self::Payload => write!(out, "payload")?,
809        }
810        write!(out, "]]")?;
811        Ok(())
812    }
813}
814
815impl ResolvedInterpolation {
816    const fn from_binding(
817        interpolation: crate::Interpolation,
818        sampling: crate::Sampling,
819        per_primitive: bool,
820    ) -> Self {
821        use crate::Interpolation as I;
822        use crate::Sampling as S;
823
824        if per_primitive {
825            return Self::Flat;
826        }
827
828        match (interpolation, sampling) {
829            (I::Perspective, S::Center) => Self::CenterPerspective,
830            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
831            (I::Perspective, S::Sample) => Self::SamplePerspective,
832            (I::Linear, S::Center) => Self::CenterNoPerspective,
833            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
834            (I::Linear, S::Sample) => Self::SampleNoPerspective,
835            (I::Flat, _) => Self::Flat,
836            (I::PerVertex, S::Center) => Self::PerVertex,
837            _ => unreachable!(),
838        }
839    }
840
841    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
842        let identifier = match self {
843            Self::CenterPerspective => "center_perspective",
844            Self::CenterNoPerspective => "center_no_perspective",
845            Self::CentroidPerspective => "centroid_perspective",
846            Self::CentroidNoPerspective => "centroid_no_perspective",
847            Self::SamplePerspective => "sample_perspective",
848            Self::SampleNoPerspective => "sample_no_perspective",
849            Self::Flat => "flat",
850            Self::PerVertex => unreachable!(),
851        };
852        out.write_str(identifier)?;
853        Ok(())
854    }
855}
856
857struct EntryPointArgument {
858    ty_name: String,
859    name: String,
860    binding: String,
861    init: Option<Handle<crate::Expression>>,
862}
863
864/// Shorthand result used internally by the backend
865type BackendResult = Result<(), Error>;
866
867const NAMESPACE: &str = "metal";
868
869// The name of the array member of the Metal struct types we generate to
870// represent Naga `Array` types. See the comments in `Writer::write_type_defs`
871// for details.
872const WRAPPED_ARRAY_FIELD: &str = "inner";
873
874/// Information about a translated module that is required
875/// for the use of the result.
876pub struct TranslationInfo {
877    /// Mapping of the entry point names. Each item in the array
878    /// corresponds to an entry point index.
879    ///
880    ///Note: Some entry points may fail translation because of missing bindings.
881    pub entry_point_names: Vec<Result<String, EntryPointError>>,
882}
883
884pub fn write_string(
885    module: &crate::Module,
886    info: &ModuleInfo,
887    options: &Options,
888    pipeline_options: &PipelineOptions,
889) -> Result<(String, TranslationInfo), Error> {
890    let mut w = Writer::new(String::new());
891    let info = w.write(module, info, options, pipeline_options)?;
892    Ok((w.finish(), info))
893}
894
895pub fn supported_capabilities() -> crate::valid::Capabilities {
896    use crate::valid::Capabilities as Caps;
897    Caps::IMMEDIATES
898        // No FLOAT64
899        | Caps::PRIMITIVE_INDEX
900        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY
901        // No BUFFER_BINDING_ARRAY
902        | Caps::STORAGE_TEXTURE_BINDING_ARRAY
903        | Caps::STORAGE_BUFFER_BINDING_ARRAY
904        | Caps::CLIP_DISTANCES
905        // No CULL_DISTANCE
906        | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
907        | Caps::MULTIVIEW
908        // No EARLY_DEPTH_TEST
909        | Caps::MULTISAMPLED_SHADING
910        | Caps::RAY_QUERY
911        | Caps::DUAL_SOURCE_BLENDING
912        | Caps::CUBE_ARRAY_TEXTURES
913        | Caps::SHADER_INT64
914        | Caps::SUBGROUP
915        | Caps::SUBGROUP_BARRIER
916        // No SUBGROUP_VERTEX_STAGE
917        | Caps::SHADER_INT64_ATOMIC_MIN_MAX
918        // No SHADER_INT64_ATOMIC_ALL_OPS
919        | Caps::SHADER_FLOAT32_ATOMIC
920        | Caps::TEXTURE_ATOMIC
921        | Caps::TEXTURE_INT64_ATOMIC
922        // No RAY_HIT_VERTEX_POSITION
923        | Caps::SHADER_FLOAT16
924        | Caps::SHADER_INT16
925        | Caps::TEXTURE_EXTERNAL
926        | Caps::SHADER_FLOAT16_IN_FLOAT32
927        | Caps::SHADER_BARYCENTRICS
928        | Caps::MESH_SHADER
929        | Caps::MESH_SHADER_POINT_TOPOLOGY
930        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
931        // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
932        | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
933        | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
934        | Caps::COOPERATIVE_MATRIX
935        | Caps::PER_VERTEX
936        // No RAY_TRACING_PIPELINE
937        // No DRAW_INDEX
938        // No MEMORY_DECORATION_VOLATILE
939        | Caps::MEMORY_DECORATION_COHERENT
940}
941
942#[test]
943fn test_error_size() {
944    assert_eq!(size_of::<Error>(), 40);
945}