Skip to main content

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