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