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, ir, proc::index, valid::ModuleInfo};
79
80mod keywords;
81pub mod sampler;
82mod writer;
83
84pub use writer::Writer;
85
86pub type Slot = u8;
87pub type InlineSamplerIndex = u8;
88
89#[derive(Clone, Debug, PartialEq, Eq, Hash)]
90#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
91#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
92pub enum BindSamplerTarget {
93    Resource(Slot),
94    Inline(InlineSamplerIndex),
95}
96
97/// Binding information for a Naga [`External`] image global variable.
98///
99/// See the module documentation's section on external textures for details.
100///
101/// [`External`]: crate::ir::ImageClass::External
102#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
104#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
105pub struct BindExternalTextureTarget {
106    pub planes: [Slot; 3],
107    pub params: Slot,
108}
109
110#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
111#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
112#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
113#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
114pub struct BindTarget {
115    pub buffer: Option<Slot>,
116    pub texture: Option<Slot>,
117    pub sampler: Option<BindSamplerTarget>,
118    pub external_texture: Option<BindExternalTextureTarget>,
119    pub mutable: bool,
120}
121
122#[cfg(feature = "deserialize")]
123#[derive(serde::Deserialize)]
124struct BindingMapSerialization {
125    resource_binding: crate::ResourceBinding,
126    bind_target: BindTarget,
127}
128
129#[cfg(feature = "deserialize")]
130fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
131where
132    D: serde::Deserializer<'de>,
133{
134    use serde::Deserialize;
135
136    let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
137    let mut map = BindingMap::default();
138    for item in vec {
139        map.insert(item.resource_binding, item.bind_target);
140    }
141    Ok(map)
142}
143
144// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
145pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
146
147#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
148#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
149#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
150#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
151pub struct EntryPointResources {
152    #[cfg_attr(
153        feature = "deserialize",
154        serde(deserialize_with = "deserialize_binding_map")
155    )]
156    pub resources: BindingMap,
157
158    pub push_constant_buffer: Option<Slot>,
159
160    /// The slot of a buffer that contains an array of `u32`,
161    /// one for the size of each bound buffer that contains a runtime array,
162    /// in order of [`crate::GlobalVariable`] declarations.
163    pub sizes_buffer: Option<Slot>,
164}
165
166pub type EntryPointResourceMap = alloc::collections::BTreeMap<String, EntryPointResources>;
167
168enum ResolvedBinding {
169    BuiltIn(crate::BuiltIn),
170    Attribute(u32),
171    Color {
172        location: u32,
173        blend_src: Option<u32>,
174    },
175    User {
176        prefix: &'static str,
177        index: u32,
178        interpolation: Option<ResolvedInterpolation>,
179    },
180    Resource(BindTarget),
181}
182
183#[derive(Copy, Clone)]
184enum ResolvedInterpolation {
185    CenterPerspective,
186    CenterNoPerspective,
187    CentroidPerspective,
188    CentroidNoPerspective,
189    SamplePerspective,
190    SampleNoPerspective,
191    Flat,
192}
193
194// Note: some of these should be removed in favor of proper IR validation.
195
196#[derive(Debug, thiserror::Error)]
197pub enum Error {
198    #[error(transparent)]
199    Format(#[from] FmtError),
200    #[error("bind target {0:?} is empty")]
201    UnimplementedBindTarget(BindTarget),
202    #[error("composing of {0:?} is not implemented yet")]
203    UnsupportedCompose(Handle<crate::Type>),
204    #[error("operation {0:?} is not implemented yet")]
205    UnsupportedBinaryOp(crate::BinaryOperator),
206    #[error("standard function '{0}' is not implemented yet")]
207    UnsupportedCall(String),
208    #[error("feature '{0}' is not implemented yet")]
209    FeatureNotImplemented(String),
210    #[error("internal naga error: module should not have validated: {0}")]
211    GenericValidation(String),
212    #[error("BuiltIn {0:?} is not supported")]
213    UnsupportedBuiltIn(crate::BuiltIn),
214    #[error("capability {0:?} is not supported")]
215    CapabilityNotSupported(crate::valid::Capabilities),
216    #[error("attribute '{0}' is not supported for target MSL version")]
217    UnsupportedAttribute(String),
218    #[error("function '{0}' is not supported for target MSL version")]
219    UnsupportedFunction(String),
220    #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")]
221    UnsupportedWriteableStorageBuffer,
222    #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")]
223    UnsupportedWriteableStorageTexture(ir::ShaderStage),
224    #[error("can not use read-write storage textures prior to MSL 1.2")]
225    UnsupportedRWStorageTexture,
226    #[error("array of '{0}' is not supported for target MSL version")]
227    UnsupportedArrayOf(String),
228    #[error("array of type '{0:?}' is not supported")]
229    UnsupportedArrayOfType(Handle<crate::Type>),
230    #[error("ray tracing is not supported prior to MSL 2.3")]
231    UnsupportedRayTracing,
232    #[error("overrides should not be present at this stage")]
233    Override,
234    #[error("bitcasting to {0:?} is not supported")]
235    UnsupportedBitCast(crate::TypeInner),
236    #[error(transparent)]
237    ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
238    #[error("entry point with stage {0:?} and name '{1}' not found")]
239    EntryPointNotFound(ir::ShaderStage, String),
240}
241
242#[derive(Clone, Debug, PartialEq, thiserror::Error)]
243#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
244#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
245pub enum EntryPointError {
246    #[error("global '{0}' doesn't have a binding")]
247    MissingBinding(String),
248    #[error("mapping of {0:?} is missing")]
249    MissingBindTarget(crate::ResourceBinding),
250    #[error("mapping for push constants is missing")]
251    MissingPushConstants,
252    #[error("mapping for sizes buffer is missing")]
253    MissingSizesBuffer,
254}
255
256/// Points in the MSL code where we might emit a pipeline input or output.
257///
258/// Note that, even though vertex shaders' outputs are always fragment
259/// shaders' inputs, we still need to distinguish `VertexOutput` and
260/// `FragmentInput`, since there are certain differences in the way
261/// [`ResolvedBinding`s] are represented on either side.
262///
263/// [`ResolvedBinding`s]: ResolvedBinding
264#[derive(Clone, Copy, Debug)]
265enum LocationMode {
266    /// Input to the vertex shader.
267    VertexInput,
268
269    /// Output from the vertex shader.
270    VertexOutput,
271
272    /// Input to the fragment shader.
273    FragmentInput,
274
275    /// Output from the fragment shader.
276    FragmentOutput,
277
278    /// Compute shader input or output.
279    Uniform,
280}
281
282#[derive(Clone, Debug, Hash, PartialEq, Eq)]
283#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
284#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
285#[cfg_attr(feature = "deserialize", serde(default))]
286pub struct Options {
287    /// (Major, Minor) target version of the Metal Shading Language.
288    pub lang_version: (u8, u8),
289    /// Map of entry-point resources, indexed by entry point function name, to slots.
290    pub per_entry_point_map: EntryPointResourceMap,
291    /// Samplers to be inlined into the code.
292    pub inline_samplers: Vec<sampler::InlineSampler>,
293    /// Make it possible to link different stages via SPIRV-Cross.
294    pub spirv_cross_compatibility: bool,
295    /// Don't panic on missing bindings, instead generate invalid MSL.
296    pub fake_missing_bindings: bool,
297    /// Bounds checking policies.
298    pub bounds_check_policies: index::BoundsCheckPolicies,
299    /// Should workgroup variables be zero initialized (by polyfilling)?
300    pub zero_initialize_workgroup_memory: bool,
301    /// If set, loops will have code injected into them, forcing the compiler
302    /// to think the number of iterations is bounded.
303    pub force_loop_bounding: bool,
304}
305
306impl Default for Options {
307    fn default() -> Self {
308        Options {
309            lang_version: (1, 0),
310            per_entry_point_map: EntryPointResourceMap::default(),
311            inline_samplers: Vec::new(),
312            spirv_cross_compatibility: false,
313            fake_missing_bindings: true,
314            bounds_check_policies: index::BoundsCheckPolicies::default(),
315            zero_initialize_workgroup_memory: true,
316            force_loop_bounding: true,
317        }
318    }
319}
320
321/// Corresponds to [WebGPU `GPUVertexFormat`](
322/// https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat).
323#[repr(u32)]
324#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
325#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
326#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
327pub enum VertexFormat {
328    /// One unsigned byte (u8). `u32` in shaders.
329    Uint8 = 0,
330    /// Two unsigned bytes (u8). `vec2<u32>` in shaders.
331    Uint8x2 = 1,
332    /// Four unsigned bytes (u8). `vec4<u32>` in shaders.
333    Uint8x4 = 2,
334    /// One signed byte (i8). `i32` in shaders.
335    Sint8 = 3,
336    /// Two signed bytes (i8). `vec2<i32>` in shaders.
337    Sint8x2 = 4,
338    /// Four signed bytes (i8). `vec4<i32>` in shaders.
339    Sint8x4 = 5,
340    /// One unsigned byte (u8). [0, 255] converted to float [0, 1] `f32` in shaders.
341    Unorm8 = 6,
342    /// Two unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec2<f32>` in shaders.
343    Unorm8x2 = 7,
344    /// Four unsigned bytes (u8). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
345    Unorm8x4 = 8,
346    /// One signed byte (i8). [-127, 127] converted to float [-1, 1] `f32` in shaders.
347    Snorm8 = 9,
348    /// Two signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec2<f32>` in shaders.
349    Snorm8x2 = 10,
350    /// Four signed bytes (i8). [-127, 127] converted to float [-1, 1] `vec4<f32>` in shaders.
351    Snorm8x4 = 11,
352    /// One unsigned short (u16). `u32` in shaders.
353    Uint16 = 12,
354    /// Two unsigned shorts (u16). `vec2<u32>` in shaders.
355    Uint16x2 = 13,
356    /// Four unsigned shorts (u16). `vec4<u32>` in shaders.
357    Uint16x4 = 14,
358    /// One signed short (u16). `i32` in shaders.
359    Sint16 = 15,
360    /// Two signed shorts (i16). `vec2<i32>` in shaders.
361    Sint16x2 = 16,
362    /// Four signed shorts (i16). `vec4<i32>` in shaders.
363    Sint16x4 = 17,
364    /// One unsigned short (u16). [0, 65535] converted to float [0, 1] `f32` in shaders.
365    Unorm16 = 18,
366    /// Two unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec2<f32>` in shaders.
367    Unorm16x2 = 19,
368    /// Four unsigned shorts (u16). [0, 65535] converted to float [0, 1] `vec4<f32>` in shaders.
369    Unorm16x4 = 20,
370    /// One signed short (i16). [-32767, 32767] converted to float [-1, 1] `f32` in shaders.
371    Snorm16 = 21,
372    /// Two signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec2<f32>` in shaders.
373    Snorm16x2 = 22,
374    /// Four signed shorts (i16). [-32767, 32767] converted to float [-1, 1] `vec4<f32>` in shaders.
375    Snorm16x4 = 23,
376    /// One half-precision float (no Rust equiv). `f32` in shaders.
377    Float16 = 24,
378    /// Two half-precision floats (no Rust equiv). `vec2<f32>` in shaders.
379    Float16x2 = 25,
380    /// Four half-precision floats (no Rust equiv). `vec4<f32>` in shaders.
381    Float16x4 = 26,
382    /// One single-precision float (f32). `f32` in shaders.
383    Float32 = 27,
384    /// Two single-precision floats (f32). `vec2<f32>` in shaders.
385    Float32x2 = 28,
386    /// Three single-precision floats (f32). `vec3<f32>` in shaders.
387    Float32x3 = 29,
388    /// Four single-precision floats (f32). `vec4<f32>` in shaders.
389    Float32x4 = 30,
390    /// One unsigned int (u32). `u32` in shaders.
391    Uint32 = 31,
392    /// Two unsigned ints (u32). `vec2<u32>` in shaders.
393    Uint32x2 = 32,
394    /// Three unsigned ints (u32). `vec3<u32>` in shaders.
395    Uint32x3 = 33,
396    /// Four unsigned ints (u32). `vec4<u32>` in shaders.
397    Uint32x4 = 34,
398    /// One signed int (i32). `i32` in shaders.
399    Sint32 = 35,
400    /// Two signed ints (i32). `vec2<i32>` in shaders.
401    Sint32x2 = 36,
402    /// Three signed ints (i32). `vec3<i32>` in shaders.
403    Sint32x3 = 37,
404    /// Four signed ints (i32). `vec4<i32>` in shaders.
405    Sint32x4 = 38,
406    /// Three unsigned 10-bit integers and one 2-bit integer, packed into a 32-bit integer (u32). [0, 1024] converted to float [0, 1] `vec4<f32>` in shaders.
407    #[cfg_attr(
408        any(feature = "serialize", feature = "deserialize"),
409        serde(rename = "unorm10-10-10-2")
410    )]
411    Unorm10_10_10_2 = 43,
412    /// Four unsigned 8-bit integers, packed into a 32-bit integer (u32). [0, 255] converted to float [0, 1] `vec4<f32>` in shaders.
413    #[cfg_attr(
414        any(feature = "serialize", feature = "deserialize"),
415        serde(rename = "unorm8x4-bgra")
416    )]
417    Unorm8x4Bgra = 44,
418}
419
420/// Defines how to advance the data in vertex buffers.
421#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
422#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
423#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
424pub enum VertexBufferStepMode {
425    Constant,
426    #[default]
427    ByVertex,
428    ByInstance,
429}
430
431/// A mapping of vertex buffers and their attributes to shader
432/// locations.
433#[derive(Debug, Clone, PartialEq, Eq, Hash)]
434#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
435#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
436pub struct AttributeMapping {
437    /// Shader location associated with this attribute
438    pub shader_location: u32,
439    /// Offset in bytes from start of vertex buffer structure
440    pub offset: u32,
441    /// Format code to help us unpack the attribute into the type
442    /// used by the shader. Codes correspond to a 0-based index of
443    /// <https://gpuweb.github.io/gpuweb/#enumdef-gpuvertexformat>.
444    /// The conversion process is described by
445    /// <https://gpuweb.github.io/gpuweb/#vertex-processing>.
446    pub format: VertexFormat,
447}
448
449/// A description of a vertex buffer with all the information we
450/// need to address the attributes within it.
451#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
452#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
453#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
454pub struct VertexBufferMapping {
455    /// Shader location associated with this buffer
456    pub id: u32,
457    /// Size of the structure in bytes
458    pub stride: u32,
459    /// Vertex buffer step mode
460    pub step_mode: VertexBufferStepMode,
461    /// Vec of the attributes within the structure
462    pub attributes: Vec<AttributeMapping>,
463}
464
465/// A subset of options that are meant to be changed per pipeline.
466#[derive(Debug, Default, Clone)]
467#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
468#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
469#[cfg_attr(feature = "deserialize", serde(default))]
470pub struct PipelineOptions {
471    /// The entry point to write.
472    ///
473    /// Entry points are identified by a shader stage specification,
474    /// and a name.
475    ///
476    /// If `None`, all entry points will be written. If `Some` and the entry
477    /// point is not found, an error will be thrown while writing.
478    pub entry_point: Option<(ir::ShaderStage, String)>,
479
480    /// Allow `BuiltIn::PointSize` and inject it if doesn't exist.
481    ///
482    /// Metal doesn't like this for non-point primitive topologies and requires it for
483    /// point primitive topologies.
484    ///
485    /// Enable this for vertex shaders with point primitive topologies.
486    pub allow_and_force_point_size: bool,
487
488    /// If set, when generating the Metal vertex shader, transform it
489    /// to receive the vertex buffers, lengths, and vertex id as args,
490    /// and bounds-check the vertex id and use the index into the
491    /// vertex buffers to access attributes, rather than using Metal's
492    /// [[stage-in]] assembled attribute data. This is true by default,
493    /// but remains configurable for use by tests via deserialization
494    /// of this struct. There is no user-facing way to set this value.
495    pub vertex_pulling_transform: bool,
496
497    /// vertex_buffer_mappings are used during shader translation to
498    /// support vertex pulling.
499    pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
500}
501
502impl Options {
503    fn resolve_local_binding(
504        &self,
505        binding: &crate::Binding,
506        mode: LocationMode,
507    ) -> Result<ResolvedBinding, Error> {
508        match *binding {
509            crate::Binding::BuiltIn(mut built_in) => {
510                match built_in {
511                    crate::BuiltIn::Position { ref mut invariant } => {
512                        if *invariant && self.lang_version < (2, 1) {
513                            return Err(Error::UnsupportedAttribute("invariant".to_string()));
514                        }
515
516                        // The 'invariant' attribute may only appear on vertex
517                        // shader outputs, not fragment shader inputs.
518                        if !matches!(mode, LocationMode::VertexOutput) {
519                            *invariant = false;
520                        }
521                    }
522                    crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
523                        return Err(Error::UnsupportedAttribute("base_instance".to_string()));
524                    }
525                    crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
526                        return Err(Error::UnsupportedAttribute("instance_id".to_string()));
527                    }
528                    // macOS: Since Metal 2.2
529                    // iOS: Since Metal 2.3 (check depends on https://github.com/gfx-rs/naga/issues/2164)
530                    crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 2) => {
531                        return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
532                    }
533                    _ => {}
534                }
535
536                Ok(ResolvedBinding::BuiltIn(built_in))
537            }
538            crate::Binding::Location {
539                location,
540                interpolation,
541                sampling,
542                blend_src,
543            } => match mode {
544                LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
545                LocationMode::FragmentOutput => {
546                    if blend_src.is_some() && self.lang_version < (1, 2) {
547                        return Err(Error::UnsupportedAttribute("blend_src".to_string()));
548                    }
549                    Ok(ResolvedBinding::Color {
550                        location,
551                        blend_src,
552                    })
553                }
554                LocationMode::VertexOutput | LocationMode::FragmentInput => {
555                    Ok(ResolvedBinding::User {
556                        prefix: if self.spirv_cross_compatibility {
557                            "locn"
558                        } else {
559                            "loc"
560                        },
561                        index: location,
562                        interpolation: {
563                            // unwrap: The verifier ensures that vertex shader outputs and fragment
564                            // shader inputs always have fully specified interpolation, and that
565                            // sampling is `None` only for Flat interpolation.
566                            let interpolation = interpolation.unwrap();
567                            let sampling = sampling.unwrap_or(crate::Sampling::Center);
568                            Some(ResolvedInterpolation::from_binding(interpolation, sampling))
569                        },
570                    })
571                }
572                LocationMode::Uniform => Err(Error::GenericValidation(format!(
573                    "Unexpected Binding::Location({location}) for the Uniform mode"
574                ))),
575            },
576        }
577    }
578
579    fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
580        self.per_entry_point_map.get(&ep.name)
581    }
582
583    fn get_resource_binding_target(
584        &self,
585        ep: &crate::EntryPoint,
586        res_binding: &crate::ResourceBinding,
587    ) -> Option<&BindTarget> {
588        self.get_entry_point_resources(ep)
589            .and_then(|res| res.resources.get(res_binding))
590    }
591
592    fn resolve_resource_binding(
593        &self,
594        ep: &crate::EntryPoint,
595        res_binding: &crate::ResourceBinding,
596    ) -> Result<ResolvedBinding, EntryPointError> {
597        let target = self.get_resource_binding_target(ep, res_binding);
598        match target {
599            Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
600            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
601                prefix: "fake",
602                index: 0,
603                interpolation: None,
604            }),
605            None => Err(EntryPointError::MissingBindTarget(*res_binding)),
606        }
607    }
608
609    fn resolve_push_constants(
610        &self,
611        ep: &crate::EntryPoint,
612    ) -> Result<ResolvedBinding, EntryPointError> {
613        let slot = self
614            .get_entry_point_resources(ep)
615            .and_then(|res| res.push_constant_buffer);
616        match slot {
617            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
618                buffer: Some(slot),
619                ..Default::default()
620            })),
621            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
622                prefix: "fake",
623                index: 0,
624                interpolation: None,
625            }),
626            None => Err(EntryPointError::MissingPushConstants),
627        }
628    }
629
630    fn resolve_sizes_buffer(
631        &self,
632        ep: &crate::EntryPoint,
633    ) -> Result<ResolvedBinding, EntryPointError> {
634        let slot = self
635            .get_entry_point_resources(ep)
636            .and_then(|res| res.sizes_buffer);
637        match slot {
638            Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
639                buffer: Some(slot),
640                ..Default::default()
641            })),
642            None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
643                prefix: "fake",
644                index: 0,
645                interpolation: None,
646            }),
647            None => Err(EntryPointError::MissingSizesBuffer),
648        }
649    }
650}
651
652impl ResolvedBinding {
653    fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
654        match *self {
655            Self::Resource(BindTarget {
656                sampler: Some(BindSamplerTarget::Inline(index)),
657                ..
658            }) => Some(&options.inline_samplers[index as usize]),
659            _ => None,
660        }
661    }
662
663    fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
664        write!(out, " [[")?;
665        match *self {
666            Self::BuiltIn(built_in) => {
667                use crate::BuiltIn as Bi;
668                let name = match built_in {
669                    Bi::Position { invariant: false } => "position",
670                    Bi::Position { invariant: true } => "position, invariant",
671                    // vertex
672                    Bi::BaseInstance => "base_instance",
673                    Bi::BaseVertex => "base_vertex",
674                    Bi::ClipDistance => "clip_distance",
675                    Bi::InstanceIndex => "instance_id",
676                    Bi::PointSize => "point_size",
677                    Bi::VertexIndex => "vertex_id",
678                    // fragment
679                    Bi::FragDepth => "depth(any)",
680                    Bi::PointCoord => "point_coord",
681                    Bi::FrontFacing => "front_facing",
682                    Bi::PrimitiveIndex => "primitive_id",
683                    Bi::SampleIndex => "sample_id",
684                    Bi::SampleMask => "sample_mask",
685                    // compute
686                    Bi::GlobalInvocationId => "thread_position_in_grid",
687                    Bi::LocalInvocationId => "thread_position_in_threadgroup",
688                    Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
689                    Bi::WorkGroupId => "threadgroup_position_in_grid",
690                    Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
691                    Bi::NumWorkGroups => "threadgroups_per_grid",
692                    // subgroup
693                    Bi::NumSubgroups => "simdgroups_per_threadgroup",
694                    Bi::SubgroupId => "simdgroup_index_in_threadgroup",
695                    Bi::SubgroupSize => "threads_per_simdgroup",
696                    Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
697                    Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => {
698                        return Err(Error::UnsupportedBuiltIn(built_in))
699                    }
700                };
701                write!(out, "{name}")?;
702            }
703            Self::Attribute(index) => write!(out, "attribute({index})")?,
704            Self::Color {
705                location,
706                blend_src,
707            } => {
708                if let Some(blend_src) = blend_src {
709                    write!(out, "color({location}) index({blend_src})")?
710                } else {
711                    write!(out, "color({location})")?
712                }
713            }
714            Self::User {
715                prefix,
716                index,
717                interpolation,
718            } => {
719                write!(out, "user({prefix}{index})")?;
720                if let Some(interpolation) = interpolation {
721                    write!(out, ", ")?;
722                    interpolation.try_fmt(out)?;
723                }
724            }
725            Self::Resource(ref target) => {
726                if let Some(id) = target.buffer {
727                    write!(out, "buffer({id})")?;
728                } else if let Some(id) = target.texture {
729                    write!(out, "texture({id})")?;
730                } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
731                    write!(out, "sampler({id})")?;
732                } else {
733                    return Err(Error::UnimplementedBindTarget(target.clone()));
734                }
735            }
736        }
737        write!(out, "]]")?;
738        Ok(())
739    }
740}
741
742impl ResolvedInterpolation {
743    const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
744        use crate::Interpolation as I;
745        use crate::Sampling as S;
746
747        match (interpolation, sampling) {
748            (I::Perspective, S::Center) => Self::CenterPerspective,
749            (I::Perspective, S::Centroid) => Self::CentroidPerspective,
750            (I::Perspective, S::Sample) => Self::SamplePerspective,
751            (I::Linear, S::Center) => Self::CenterNoPerspective,
752            (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
753            (I::Linear, S::Sample) => Self::SampleNoPerspective,
754            (I::Flat, _) => Self::Flat,
755            _ => unreachable!(),
756        }
757    }
758
759    fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
760        let identifier = match self {
761            Self::CenterPerspective => "center_perspective",
762            Self::CenterNoPerspective => "center_no_perspective",
763            Self::CentroidPerspective => "centroid_perspective",
764            Self::CentroidNoPerspective => "centroid_no_perspective",
765            Self::SamplePerspective => "sample_perspective",
766            Self::SampleNoPerspective => "sample_no_perspective",
767            Self::Flat => "flat",
768        };
769        out.write_str(identifier)?;
770        Ok(())
771    }
772}
773
774/// Information about a translated module that is required
775/// for the use of the result.
776pub struct TranslationInfo {
777    /// Mapping of the entry point names. Each item in the array
778    /// corresponds to an entry point index.
779    ///
780    ///Note: Some entry points may fail translation because of missing bindings.
781    pub entry_point_names: Vec<Result<String, EntryPointError>>,
782}
783
784pub fn write_string(
785    module: &crate::Module,
786    info: &ModuleInfo,
787    options: &Options,
788    pipeline_options: &PipelineOptions,
789) -> Result<(String, TranslationInfo), Error> {
790    let mut w = Writer::new(String::new());
791    let info = w.write(module, info, options, pipeline_options)?;
792    Ok((w.finish(), info))
793}
794
795#[test]
796fn test_error_size() {
797    assert_eq!(size_of::<Error>(), 40);
798}