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