Skip to main content

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("debugPrintf is not supported prior to MSL 3.2")]
239    UnsupportedDebugPrintf,
240    #[error("overrides should not be present at this stage")]
241    Override,
242    #[error("bitcasting to {0:?} is not supported")]
243    UnsupportedBitCast(crate::TypeInner),
244    #[error(transparent)]
245    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
246    #[error("entry point with stage {0:?} and name '{1}' not found")]
247    EntryPointNotFound(ir::ShaderStage, String),
248    #[error("Cannot use mesh shader syntax prior to MSL 3.0")]
249    UnsupportedMeshShader,
250    #[error("Per vertex fragment inputs are not supported prior to MSL 4.0")]
251    PerVertexNotSupported,
252}
253
254#[derive(Clone, Debug, PartialEq, thiserror::Error)]
255#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
256#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
257pub enum EntryPointError {
258    #[error("global '{0}' doesn't have a binding")]
259    MissingBinding(String),
260    #[error("mapping of {0:?} is missing")]
261    MissingBindTarget(crate::ResourceBinding),
262    #[error("mapping for immediates is missing")]
263    MissingImmediateData,
264    #[error("mapping for sizes buffer is missing")]
265    MissingSizesBuffer,
266}
267
268/// Points in the MSL code where we might emit a pipeline input or output.
269///
270/// Note that, even though vertex shaders' outputs are always fragment
271/// shaders' inputs, we still need to distinguish `VertexOutput` and
272/// `FragmentInput`, since there are certain differences in the way
273/// [`ResolvedBinding`s] are represented on either side.
274///
275/// [`ResolvedBinding`s]: ResolvedBinding
276#[derive(Clone, Copy, Debug)]
277enum LocationMode {
278    /// Input to the vertex shader.
279    VertexInput,
280
281    /// Output from the vertex shader.
282    VertexOutput,
283
284    /// Input to the fragment shader.
285    FragmentInput,
286
287    /// Output from the fragment shader.
288    FragmentOutput,
289
290    /// Output from the mesh shader.
291    MeshOutput,
292
293    /// Compute shader input or output.
294    Uniform,
295}
296
297#[derive(Clone, Debug, Hash, PartialEq, Eq)]
298#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
299#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
300#[cfg_attr(feature = "deserialize", serde(default))]
301pub struct Options {
302    /// (Major, Minor) target version of the Metal Shading Language.
303    pub lang_version: (u8, u8),
304    /// Map of entry-point resources, indexed by entry point function name, to slots.
305    pub per_entry_point_map: EntryPointResourceMap,
306    /// Samplers to be inlined into the code.
307    pub inline_samplers: Vec<sampler::InlineSampler>,
308    /// Make it possible to link different stages via SPIRV-Cross.
309    pub spirv_cross_compatibility: bool,
310    /// Don't panic on missing bindings, instead generate invalid MSL.
311    pub fake_missing_bindings: bool,
312    /// Bounds checking policies.
313    pub bounds_check_policies: index::BoundsCheckPolicies,
314    /// Should workgroup variables be zero initialized (by polyfilling)?
315    pub zero_initialize_workgroup_memory: bool,
316    /// If set, loops will have code injected into them, forcing the compiler
317    /// to think the number of iterations is bounded.
318    pub force_loop_bounding: bool,
319    /// Whether and how checks in the task shader should verify the dispatched
320    /// mesh grid size.
321    pub task_dispatch_limits: Option<TaskDispatchLimits>,
322    /// Whether to validate the output of a mesh shader workgroup.
323    pub mesh_shader_primitive_indices_clamp: bool,
324    /// If true (the default), integer division and modulo operations use
325    /// wrapper functions that guard against division by zero and signed
326    /// overflow. Set to false to emit raw division for faster compute shaders
327    /// where the developer guarantees non-zero divisors.
328    pub emit_int_div_checks: bool,
329    /// Whether to validate ray query calls
330    pub ray_query_initialization_tracking: bool,
331}
332
333impl Default for Options {
334    fn default() -> Self {
335        Options {
336            lang_version: (1, 0),
337            per_entry_point_map: EntryPointResourceMap::default(),
338            inline_samplers: Vec::new(),
339            spirv_cross_compatibility: false,
340            fake_missing_bindings: true,
341            bounds_check_policies: index::BoundsCheckPolicies::default(),
342            zero_initialize_workgroup_memory: true,
343            force_loop_bounding: true,
344            task_dispatch_limits: None,
345            mesh_shader_primitive_indices_clamp: true,
346            ray_query_initialization_tracking: true,
347            emit_int_div_checks: true,
348        }
349    }
350}
351
352/// Defines how to advance the data in vertex buffers.
353#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
354#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
355#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
356pub enum VertexBufferStepMode {
357    Constant,
358    #[default]
359    ByVertex,
360    ByInstance,
361}
362
363/// A mapping of vertex buffers and their attributes to shader
364/// locations.
365#[derive(Debug, Clone, PartialEq, Eq, Hash)]
366#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
367#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
368pub struct AttributeMapping {
369    /// Shader location associated with this attribute
370    pub shader_location: u32,
371    /// Offset in bytes from start of vertex buffer structure
372    pub offset: u32,
373    /// Format code to help us unpack the attribute into the type
374    /// used by the shader. Codes correspond to a 0-based index of
375    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
376    /// The conversion process is described by
377    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
378    pub format: nt::VertexFormat,
379}
380
381/// A description of a vertex buffer with all the information we
382/// need to address the attributes within it.
383#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
384#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
385#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
386pub struct VertexBufferMapping {
387    /// Shader location associated with this buffer
388    pub id: u32,
389    /// Size of the structure in bytes
390    pub stride: u32,
391    /// Vertex buffer step mode
392    pub step_mode: VertexBufferStepMode,
393    /// Vec of the attributes within the structure
394    pub attributes: Vec<AttributeMapping>,
395}
396
397/// A subset of options that are meant to be changed per pipeline.
398#[derive(Debug, Default, Clone)]
399#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
400#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
401#[cfg_attr(feature = "deserialize", serde(default))]
402pub struct PipelineOptions {
403    /// The entry point to write.
404    ///
405    /// Entry points are identified by a shader stage specification,
406    /// and a name.
407    ///
408    /// If `None`, all entry points will be written. If `Some` and the entry
409    /// point is not found, an error will be thrown while writing.
410    pub entry_point: Option<(ir::ShaderStage, String)>,
411
412    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
413    ///
414    /// Metal doesn't like this for non-point primitive topologies and requires it for
415    /// point primitive topologies.
416    ///
417    /// Enable this for vertex/mesh shaders with point primitive topologies.
418    pub allow_and_force_point_size: bool,
419
420    /// If set, when generating the Metal vertex shader, transform it
421    /// to receive the vertex buffers, lengths, and vertex id as args,
422    /// and bounds-check the vertex id and use the index into the
423    /// vertex buffers to access attributes, rather than using Metal's
424    /// [[stage-in]] assembled attribute data. This is true by default,
425    /// but remains configurable for use by tests via deserialization
426    /// of this struct. There is no user-facing way to set this value.
427    pub vertex_pulling_transform: bool,
428
429    /// vertex_buffer_mappings are used during shader translation to
430    /// support vertex pulling.
431    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
432
433    /// For each storage `binding_array` in the pipeline layout, the number of
434    /// elements that layout declares, keyed by `ResourceBinding`.
435    /// The MSL writer uses this to report the number of elements in an unbounded `binding_array`.
436    #[cfg_attr(
437        feature = "deserialize",
438        serde(deserialize_with = "deserialize_binding_array_length_map")
439    )]
440    pub binding_array_length_map: crate::FastHashMap<crate::ResourceBinding, u32>,
441}
442
443#[cfg(feature = "deserialize")]
444#[derive(serde::Deserialize)]
445struct BindingArrayLengthMapSerialization {
446    resource_binding: crate::ResourceBinding,
447    count: u32,
448}
449
450#[cfg(feature = "deserialize")]
451fn deserialize_binding_array_length_map<'de, D>(
452    deserializer: D,
453) -> Result<crate::FastHashMap<crate::ResourceBinding, u32>, D::Error>
454where
455    D: serde::Deserializer<'de>,
456{
457    use serde::Deserialize;
458
459    let vec = Vec::<BindingArrayLengthMapSerialization>::deserialize(deserializer)?;
460    let mut map = crate::FastHashMap::default();
461    for item in vec {
462        map.insert(item.resource_binding, item.count);
463    }
464    Ok(map)
465}
466
467impl Options {
468    fn resolve_local_binding(
469        &self,
470        binding: &crate::Binding,
471        mode: LocationMode,
472    ) -> Result<ResolvedBinding, Error> {
473        match *binding {
474            crate::Binding::BuiltIn(mut built_in) => {
475                match built_in {
476                    crate::BuiltIn::Position { ref mut invariant } => {
477                        if *invariant && self.lang_version < (2, 1) {
478                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
479                        }
480
481                        // The 'invariant' attribute may only appear on vertex
482                        // shader outputs, not fragment shader inputs.
483                        if !matches!(mode, LocationMode::VertexOutput) {
484                            *invariant = false;
485                        }
486                    }
487                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
488                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
489                    }
490                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
491                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
492                    }
493                    // macOS: Since Metal 2.2
494                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
495                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 3) => {
496                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
497                    }
498                    // macOS: since Metal 2.3
499                    // iOS: Since Metal 2.2
500                    // https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf#page=114
501                    crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
502                        return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
503                    }
504                    // macOS: Since Metal 2.2
505                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/wgpu/issues/4414)
506                    crate::BuiltIn::Barycentric { .. } if self.lang_version < (2, 3) => {
507                        return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
508                    }
509                    _ => {}
510                }
511
512                Ok(ResolvedBinding::BuiltIn(built_in))
513            }
514            crate::Binding::Location {
515                location,
516                interpolation,
517                sampling,
518                blend_src,
519                per_primitive,
520            } => match mode {
521                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
522                LocationMode::FragmentOutput => {
523                    if blend_src.is_some() && self.lang_version < (1, 2) {
524                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
525                    }
526                    Ok(ResolvedBinding::Color {
527                        location,
528                        blend_src,
529                    })
530                }
531                LocationMode::VertexOutput
532                | LocationMode::FragmentInput
533                | LocationMode::MeshOutput => {
534                    Ok(ResolvedBinding::User {
535                        prefix: if self.spirv_cross_compatibility {
536                            "locn"
537                        } else {
538                            "loc"
539                        },
540                        index: location,
541                        interpolation: {
542                            // unwrap: The verifier ensures that vertex shader outputs and fragment
543                            // shader inputs always have fully specified interpolation, and that
544                            // sampling is `None` only for Flat interpolation.
545                            let interpolation = interpolation.unwrap();
546                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
547                            Some(ResolvedInterpolation::from_binding(
548                                interpolation,
549                                sampling,
550                                per_primitive,
551                            ))
552                        },
553                    })
554                }
555                LocationMode::Uniform => Err(Error::GenericValidation(format!(
556                    "Unexpected Binding::Location({location}) for the Uniform mode"
557                ))),
558            },
559        }
560    }
561
562    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
563        self.per_entry_point_map.get(&ep.name)
564    }
565
566    fn get_resource_binding_target(
567        &self,
568        ep: &crate::EntryPoint,
569        res_binding: &crate::ResourceBinding,
570    ) -> Option<&BindTarget> {
571        self.get_entry_point_resources(ep)
572            .and_then(|res| res.resources.get(res_binding))
573    }
574
575    fn resolve_resource_binding(
576        &self,
577        ep: &crate::EntryPoint,
578        res_binding: &crate::ResourceBinding,
579    ) -> Result<ResolvedBinding, EntryPointError> {
580        let target = self.get_resource_binding_target(ep, res_binding);
581        match target {
582            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
583            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
584                prefix: "fake",
585                index: 0,
586                interpolation: None,
587            }),
588            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
589        }
590    }
591
592    fn resolve_immediates(
593        &self,
594        ep: &crate::EntryPoint,
595    ) -> Result<ResolvedBinding, EntryPointError> {
596        let slot = self
597            .get_entry_point_resources(ep)
598            .and_then(|res| res.immediates_buffer);
599        match slot {
600            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
601                buffer: Some(slot),
602                ..Default::default()
603            })),
604            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
605                prefix: "fake",
606                index: 0,
607                interpolation: None,
608            }),
609            None => Err(EntryPointError::MissingImmediateData),
610        }
611    }
612
613    fn resolve_sizes_buffer(
614        &self,
615        ep: &crate::EntryPoint,
616    ) -> Result<ResolvedBinding, EntryPointError> {
617        let slot = self
618            .get_entry_point_resources(ep)
619            .and_then(|res| res.sizes_buffer);
620        match slot {
621            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
622                buffer: Some(slot),
623                ..Default::default()
624            })),
625            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
626                prefix: "fake",
627                index: 0,
628                interpolation: None,
629            }),
630            None => Err(EntryPointError::MissingSizesBuffer),
631        }
632    }
633}
634
635impl ResolvedBinding {
636    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
637        match *self {
638            Self::Resource(BindTarget {
639                sampler: Some(BindSamplerTarget::Inline(index)),
640                ..
641            }) => Some(&options.inline_samplers[index as usize]),
642            _ => None,
643        }
644    }
645
646    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
647        write!(out, " [[")?;
648        match *self {
649            Self::BuiltIn(built_in) => {
650                use crate::BuiltIn as Bi;
651                let name = match built_in {
652                    Bi::Position { invariant: false } => "position",
653                    Bi::Position { invariant: true } => "position, invariant",
654                    Bi::ViewIndex => "amplification_id",
655                    // vertex
656                    Bi::BaseInstance => "base_instance",
657                    Bi::BaseVertex => "base_vertex",
658                    Bi::ClipDistances => "clip_distance",
659                    Bi::InstanceIndex => "instance_id",
660                    Bi::PointSize => "point_size",
661                    Bi::VertexIndex => "vertex_id",
662                    // fragment
663                    Bi::FragDepth => "depth(any)",
664                    Bi::PointCoord => "point_coord",
665                    Bi::FrontFacing => "front_facing",
666                    Bi::PrimitiveIndex => "primitive_id",
667                    Bi::Barycentric { perspective: true } => "barycentric_coord",
668                    Bi::Barycentric { perspective: false } => {
669                        "barycentric_coord, center_no_perspective"
670                    }
671                    Bi::SampleIndex => "sample_id",
672                    Bi::SampleMask => "sample_mask",
673                    // compute
674                    Bi::GlobalInvocationId => "thread_position_in_grid",
675                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
676                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
677                    Bi::WorkGroupId => "threadgroup_position_in_grid",
678                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
679                    Bi::NumWorkGroups => "threadgroups_per_grid",
680                    // subgroup
681                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
682                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
683                    Bi::SubgroupSize => "threads_per_simdgroup",
684                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
685                    Bi::CullDistance | Bi::DrawIndex => {
686                        return Err(Error::UnsupportedBuiltIn(built_in))
687                    }
688                    Bi::CullPrimitive => "primitive_culled",
689                    // TODO: figure out how to make this written as a function call
690                    Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
691                    // These aren't real builtins passed into MSL. They are extracted by the
692                    // wrapper function which actually sets the outputs.
693                    Bi::MeshTaskSize
694                    | Bi::VertexCount
695                    | Bi::PrimitiveCount
696                    | Bi::Vertices
697                    | Bi::Primitives
698                    | Bi::RayInvocationId
699                    | Bi::NumRayInvocations
700                    | Bi::InstanceCustomData
701                    | Bi::GeometryIndex
702                    | Bi::WorldRayOrigin
703                    | Bi::WorldRayDirection
704                    | Bi::ObjectRayOrigin
705                    | Bi::ObjectRayDirection
706                    | Bi::RayTmin
707                    | Bi::RayTCurrentMax
708                    | Bi::ObjectToWorld
709                    | Bi::WorldToObject
710                    | Bi::HitKind
711                    | Bi::HitBarycentrics => unreachable!(),
712                };
713                write!(out, "{name}")?;
714            }
715            Self::Attribute(index) => write!(out, "attribute({index})")?,
716            Self::Color {
717                location,
718                blend_src,
719            } => {
720                if let Some(blend_src) = blend_src {
721                    write!(out, "color({location}) index({blend_src})")?
722                } else {
723                    write!(out, "color({location})")?
724                }
725            }
726            Self::User {
727                prefix,
728                index,
729                interpolation,
730            } => {
731                write!(out, "user({prefix}{index})")?;
732                if let Some(interpolation) = interpolation {
733                    write!(out, ", ")?;
734                    interpolation.try_fmt(out)?;
735                }
736            }
737            Self::Resource(ref target) => {
738                if let Some(id) = target.buffer {
739                    write!(out, "buffer({id})")?;
740                } else if let Some(id) = target.texture {
741                    write!(out, "texture({id})")?;
742                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
743                    write!(out, "sampler({id})")?;
744                } else {
745                    return Err(Error::UnimplementedBindTarget(target.clone()));
746                }
747            }
748            Self::Payload => write!(out, "payload")?,
749        }
750        write!(out, "]]")?;
751        Ok(())
752    }
753}
754
755impl ResolvedInterpolation {
756    const fn from_binding(
757        interpolation: crate::Interpolation,
758        sampling: crate::Sampling,
759        per_primitive: bool,
760    ) -> Self {
761        use crate::Interpolation as I;
762        use crate::Sampling as S;
763
764        if per_primitive {
765            return Self::Flat;
766        }
767
768        match (interpolation, sampling) {
769            (I::Perspective, S::Center) => Self::CenterPerspective,
770            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
771            (I::Perspective, S::Sample) => Self::SamplePerspective,
772            (I::Linear, S::Center) => Self::CenterNoPerspective,
773            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
774            (I::Linear, S::Sample) => Self::SampleNoPerspective,
775            (I::Flat, _) => Self::Flat,
776            (I::PerVertex, S::Center) => Self::PerVertex,
777            _ => unreachable!(),
778        }
779    }
780
781    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
782        let identifier = match self {
783            Self::CenterPerspective => "center_perspective",
784            Self::CenterNoPerspective => "center_no_perspective",
785            Self::CentroidPerspective => "centroid_perspective",
786            Self::CentroidNoPerspective => "centroid_no_perspective",
787            Self::SamplePerspective => "sample_perspective",
788            Self::SampleNoPerspective => "sample_no_perspective",
789            Self::Flat => "flat",
790            Self::PerVertex => unreachable!(),
791        };
792        out.write_str(identifier)?;
793        Ok(())
794    }
795}
796
797struct EntryPointArgument {
798    ty_name: String,
799    name: String,
800    binding: String,
801    init: Option<Handle<crate::Expression>>,
802}
803
804/// Shorthand result used internally by the backend
805type BackendResult = Result<(), Error>;
806
807const NAMESPACE: &str = "metal";
808
809// The name of the array member of the Metal struct types we generate to
810// represent Naga `Array` types. See the comments in `Writer::write_type_defs`
811// for details.
812const WRAPPED_ARRAY_FIELD: &str = "inner";
813
814/// Information about a translated module that is required
815/// for the use of the result.
816#[derive(Debug)]
817pub struct TranslationInfo {
818    /// Mapping of the entry point names. Each item in the array
819    /// corresponds to an entry point index.
820    ///
821    ///Note: Some entry points may fail translation because of missing bindings.
822    pub entry_point_names: Vec<Result<String, EntryPointError>>,
823}
824
825pub fn write_string(
826    module: &crate::Module,
827    info: &ModuleInfo,
828    options: &Options,
829    pipeline_options: &PipelineOptions,
830) -> Result<(String, TranslationInfo), Error> {
831    let mut w = Writer::new(String::new());
832    let info = w.write(module, info, options, pipeline_options)?;
833    Ok((w.finish(), info))
834}
835
836pub fn supported_capabilities() -> crate::valid::Capabilities {
837    use crate::valid::Capabilities as Caps;
838    Caps::IMMEDIATES
839        // No FLOAT64
840        | Caps::PRIMITIVE_INDEX
841        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY
842        // No BUFFER_BINDING_ARRAY
843        | Caps::STORAGE_TEXTURE_BINDING_ARRAY
844        | Caps::STORAGE_BUFFER_BINDING_ARRAY
845        | Caps::CLIP_DISTANCES
846        // No CULL_DISTANCE
847        | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
848        | Caps::MULTIVIEW
849        // No EARLY_DEPTH_TEST
850        | Caps::MULTISAMPLED_SHADING
851        | Caps::RAY_QUERY
852        | Caps::DUAL_SOURCE_BLENDING
853        | Caps::CUBE_ARRAY_TEXTURES
854        | Caps::SHADER_INT64
855        | Caps::SUBGROUP
856        | Caps::SUBGROUP_BARRIER
857        // No SUBGROUP_VERTEX_STAGE
858        | Caps::SHADER_INT64_ATOMIC_MIN_MAX
859        // No SHADER_INT64_ATOMIC_ALL_OPS
860        | Caps::SHADER_FLOAT32_ATOMIC
861        | Caps::TEXTURE_ATOMIC
862        | Caps::TEXTURE_INT64_ATOMIC
863        // No RAY_HIT_VERTEX_POSITION
864        | Caps::SHADER_FLOAT16
865        | Caps::SHADER_INT16
866        | Caps::TEXTURE_EXTERNAL
867        | Caps::SHADER_FLOAT16_IN_FLOAT32
868        | Caps::SHADER_BARYCENTRICS
869        | Caps::MESH_SHADER
870        | Caps::MESH_SHADER_POINT_TOPOLOGY
871        | Caps::TEXTURE_AND_SAMPLER_BINDING_ARRAY_NON_UNIFORM_INDEXING
872        // No BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
873        | Caps::STORAGE_TEXTURE_BINDING_ARRAY_NON_UNIFORM_INDEXING
874        | Caps::STORAGE_BUFFER_BINDING_ARRAY_NON_UNIFORM_INDEXING
875        | Caps::COOPERATIVE_MATRIX
876        | Caps::PER_VERTEX
877        // No RAY_TRACING_PIPELINE
878        // No DRAW_INDEX
879        // No MEMORY_DECORATION_VOLATILE
880        | Caps::MEMORY_DECORATION_COHERENT
881        | Caps::LINEAR_INTERPOLATION
882        | Caps::DEBUG_PRINTF
883}
884
885#[test]
886fn test_error_size() {
887    assert_eq!(size_of::<Error>(), 40);
888}