Skip to main content

naga/common/wgsl/
to_wgsl.rs

1//! Generating WGSL source code for Naga IR types.
2
3use alloc::format;
4use alloc::string::{String, ToString};
5
6/// Types that can return the WGSL source representation of their
7/// values as a `'static` string.
8///
9/// This trait is specifically for types whose WGSL forms are simple
10/// enough that they can always be returned as a static string.
11///
12/// - If only some values have a WGSL representation, consider
13///   implementing [`TryToWgsl`] instead.
14///
15/// - If a type's WGSL form requires dynamic formatting, so that
16///   returning a `&'static str` isn't feasible, consider implementing
17///   [`core::fmt::Display`] on some wrapper type instead.
18pub trait ToWgsl: Sized {
19    /// Return WGSL source code representation of `self`.
20    fn to_wgsl(self) -> &'static str;
21}
22
23/// Types that may be able to return the WGSL source representation
24/// for their values as a `'static` string.
25///
26/// This trait is specifically for types whose values are either
27/// simple enough that their WGSL form can be represented a static
28/// string, or aren't representable in WGSL at all.
29///
30/// - If all values in the type have `&'static str` representations in
31///   WGSL, consider implementing [`ToWgsl`] instead.
32///
33/// - If a type's WGSL form requires dynamic formatting, so that
34///   returning a `&'static str` isn't feasible, consider implementing
35///   [`core::fmt::Display`] on some wrapper type instead.
36pub trait TryToWgsl: Sized {
37    /// Return the WGSL form of `self` as a `'static` string.
38    ///
39    /// If `self` doesn't have a representation in WGSL (standard or
40    /// as extended by Naga), then return `None`.
41    fn try_to_wgsl(self) -> Option<&'static str>;
42
43    /// What kind of WGSL thing `Self` represents.
44    const DESCRIPTION: &'static str;
45
46    /// Return the WGSL form of `self` as appropriate for diagnostics.
47    ///
48    /// If `self` can be expressed in WGSL, return that form as a
49    /// [`String`]. Otherwise, return some representation of `self`
50    /// that is appropriate for use in diagnostic messages.
51    ///
52    /// The default implementation of this function falls back to
53    /// `self`'s [`Debug`] form.
54    ///
55    /// [`Debug`]: core::fmt::Debug
56    fn to_wgsl_for_diagnostics(self) -> String
57    where
58        Self: core::fmt::Debug + Copy,
59    {
60        match self.try_to_wgsl() {
61            Some(static_string) => static_string.to_string(),
62            None => format!("{{non-WGSL {} {self:?}}}", Self::DESCRIPTION),
63        }
64    }
65}
66
67impl TryToWgsl for crate::MathFunction {
68    const DESCRIPTION: &'static str = "math function";
69
70    fn try_to_wgsl(self) -> Option<&'static str> {
71        use crate::MathFunction as Mf;
72
73        Some(match self {
74            Mf::Abs => "abs",
75            Mf::Min => "min",
76            Mf::Max => "max",
77            Mf::Clamp => "clamp",
78            Mf::Saturate => "saturate",
79            Mf::Cos => "cos",
80            Mf::Cosh => "cosh",
81            Mf::Sin => "sin",
82            Mf::Sinh => "sinh",
83            Mf::Tan => "tan",
84            Mf::Tanh => "tanh",
85            Mf::Acos => "acos",
86            Mf::Asin => "asin",
87            Mf::Atan => "atan",
88            Mf::Atan2 => "atan2",
89            Mf::Asinh => "asinh",
90            Mf::Acosh => "acosh",
91            Mf::Atanh => "atanh",
92            Mf::Radians => "radians",
93            Mf::Degrees => "degrees",
94            Mf::Ceil => "ceil",
95            Mf::Floor => "floor",
96            Mf::Round => "round",
97            Mf::Fract => "fract",
98            Mf::Trunc => "trunc",
99            Mf::Modf => "modf",
100            Mf::Frexp => "frexp",
101            Mf::Ldexp => "ldexp",
102            Mf::Exp => "exp",
103            Mf::Exp2 => "exp2",
104            Mf::Log => "log",
105            Mf::Log2 => "log2",
106            Mf::Pow => "pow",
107            Mf::Dot => "dot",
108            Mf::Dot4I8Packed => "dot4I8Packed",
109            Mf::Dot4U8Packed => "dot4U8Packed",
110            Mf::Cross => "cross",
111            Mf::Distance => "distance",
112            Mf::Length => "length",
113            Mf::Normalize => "normalize",
114            Mf::FaceForward => "faceForward",
115            Mf::Reflect => "reflect",
116            Mf::Refract => "refract",
117            Mf::Sign => "sign",
118            Mf::Fma => "fma",
119            Mf::Mix => "mix",
120            Mf::Step => "step",
121            Mf::SmoothStep => "smoothstep",
122            Mf::Sqrt => "sqrt",
123            Mf::InverseSqrt => "inverseSqrt",
124            Mf::Transpose => "transpose",
125            Mf::Determinant => "determinant",
126            Mf::QuantizeToF16 => "quantizeToF16",
127            Mf::CountTrailingZeros => "countTrailingZeros",
128            Mf::CountLeadingZeros => "countLeadingZeros",
129            Mf::CountOneBits => "countOneBits",
130            Mf::ReverseBits => "reverseBits",
131            Mf::ExtractBits => "extractBits",
132            Mf::InsertBits => "insertBits",
133            Mf::FirstTrailingBit => "firstTrailingBit",
134            Mf::FirstLeadingBit => "firstLeadingBit",
135            Mf::Pack4x8snorm => "pack4x8snorm",
136            Mf::Pack4x8unorm => "pack4x8unorm",
137            Mf::Pack2x16snorm => "pack2x16snorm",
138            Mf::Pack2x16unorm => "pack2x16unorm",
139            Mf::Pack2x16float => "pack2x16float",
140            Mf::Pack4xI8 => "pack4xI8",
141            Mf::Pack4xU8 => "pack4xU8",
142            Mf::Pack4xI8Clamp => "pack4xI8Clamp",
143            Mf::Pack4xU8Clamp => "pack4xU8Clamp",
144            Mf::Unpack4x8snorm => "unpack4x8snorm",
145            Mf::Unpack4x8unorm => "unpack4x8unorm",
146            Mf::Unpack2x16snorm => "unpack2x16snorm",
147            Mf::Unpack2x16unorm => "unpack2x16unorm",
148            Mf::Unpack2x16float => "unpack2x16float",
149            Mf::Unpack4xI8 => "unpack4xI8",
150            Mf::Unpack4xU8 => "unpack4xU8",
151
152            // Non-standard math functions.
153            Mf::Inverse | Mf::Outer => return None,
154        })
155    }
156}
157
158impl TryToWgsl for crate::BuiltIn {
159    const DESCRIPTION: &'static str = "builtin value";
160
161    fn try_to_wgsl(self) -> Option<&'static str> {
162        use crate::BuiltIn as Bi;
163        Some(match self {
164            Bi::Position { .. } => "position",
165            Bi::ViewIndex => "view_index",
166            Bi::InstanceIndex => "instance_index",
167            Bi::VertexIndex => "vertex_index",
168            Bi::ClipDistances => "clip_distances",
169            Bi::FragDepth => "frag_depth",
170            Bi::FrontFacing => "front_facing",
171            Bi::PrimitiveIndex => "primitive_index",
172            Bi::DrawIndex => "draw_index",
173            Bi::Barycentric { perspective: true } => "barycentric",
174            Bi::Barycentric { perspective: false } => "barycentric_no_perspective",
175            Bi::SampleIndex => "sample_index",
176            Bi::SampleMask => "sample_mask",
177            Bi::GlobalInvocationId => "global_invocation_id",
178            Bi::LocalInvocationId => "local_invocation_id",
179            Bi::LocalInvocationIndex => "local_invocation_index",
180            Bi::WorkGroupId => "workgroup_id",
181            Bi::NumWorkGroups => "num_workgroups",
182            Bi::NumSubgroups => "num_subgroups",
183            Bi::SubgroupId => "subgroup_id",
184            Bi::SubgroupSize => "subgroup_size",
185            Bi::SubgroupInvocationId => "subgroup_invocation_id",
186
187            // Non-standard built-ins.
188            Bi::MeshTaskSize => "mesh_task_size",
189            Bi::TriangleIndices => "triangle_indices",
190            Bi::LineIndices => "line_indices",
191            Bi::PointIndex => "point_index",
192            Bi::Vertices => "vertices",
193            Bi::Primitives => "primitives",
194            Bi::VertexCount => "vertex_count",
195            Bi::PrimitiveCount => "primitive_count",
196            Bi::CullPrimitive => "cull_primitive",
197
198            Bi::RayInvocationId => "ray_invocation_id",
199            Bi::NumRayInvocations => "num_ray_invocations",
200            Bi::InstanceCustomData => "instance_custom_data",
201            Bi::GeometryIndex => "geometry_index",
202            Bi::WorldRayOrigin => "world_ray_origin",
203            Bi::WorldRayDirection => "world_ray_direction",
204            Bi::ObjectRayOrigin => "object_ray_origin",
205            Bi::ObjectRayDirection => "object_ray_direction",
206            Bi::RayTmin => "ray_t_min",
207            Bi::RayTCurrentMax => "ray_t_current_max",
208            Bi::ObjectToWorld => "object_to_world",
209            Bi::WorldToObject => "world_to_object",
210            Bi::HitKind => "hit_kind",
211            Bi::HitBarycentrics => "hit_barycentrics",
212
213            Bi::BaseInstance
214            | Bi::BaseVertex
215            | Bi::CullDistance
216            | Bi::PointSize
217            | Bi::PointCoord
218            | Bi::WorkGroupSize => return None,
219        })
220    }
221}
222
223impl ToWgsl for crate::Interpolation {
224    fn to_wgsl(self) -> &'static str {
225        match self {
226            crate::Interpolation::Perspective => "perspective",
227            crate::Interpolation::Linear => "linear",
228            crate::Interpolation::Flat => "flat",
229            crate::Interpolation::PerVertex => "per_vertex",
230        }
231    }
232}
233
234impl ToWgsl for crate::Sampling {
235    fn to_wgsl(self) -> &'static str {
236        match self {
237            crate::Sampling::Center => "center",
238            crate::Sampling::Centroid => "centroid",
239            crate::Sampling::Sample => "sample",
240            crate::Sampling::First => "first",
241            crate::Sampling::Either => "either",
242        }
243    }
244}
245
246impl ToWgsl for crate::StorageFormat {
247    fn to_wgsl(self) -> &'static str {
248        use crate::StorageFormat as Sf;
249
250        match self {
251            Sf::R8Unorm => "r8unorm",
252            Sf::R8Snorm => "r8snorm",
253            Sf::R8Uint => "r8uint",
254            Sf::R8Sint => "r8sint",
255            Sf::R16Uint => "r16uint",
256            Sf::R16Sint => "r16sint",
257            Sf::R16Float => "r16float",
258            Sf::Rg8Unorm => "rg8unorm",
259            Sf::Rg8Snorm => "rg8snorm",
260            Sf::Rg8Uint => "rg8uint",
261            Sf::Rg8Sint => "rg8sint",
262            Sf::R32Uint => "r32uint",
263            Sf::R32Sint => "r32sint",
264            Sf::R32Float => "r32float",
265            Sf::Rg16Uint => "rg16uint",
266            Sf::Rg16Sint => "rg16sint",
267            Sf::Rg16Float => "rg16float",
268            Sf::Rgba8Unorm => "rgba8unorm",
269            Sf::Rgba8Snorm => "rgba8snorm",
270            Sf::Rgba8Uint => "rgba8uint",
271            Sf::Rgba8Sint => "rgba8sint",
272            Sf::Bgra8Unorm => "bgra8unorm",
273            Sf::Rgb10a2Uint => "rgb10a2uint",
274            Sf::Rgb10a2Unorm => "rgb10a2unorm",
275            Sf::Rg11b10Ufloat => "rg11b10ufloat",
276            Sf::R64Uint => "r64uint",
277            Sf::Rg32Uint => "rg32uint",
278            Sf::Rg32Sint => "rg32sint",
279            Sf::Rg32Float => "rg32float",
280            Sf::Rgba16Uint => "rgba16uint",
281            Sf::Rgba16Sint => "rgba16sint",
282            Sf::Rgba16Float => "rgba16float",
283            Sf::Rgba32Uint => "rgba32uint",
284            Sf::Rgba32Sint => "rgba32sint",
285            Sf::Rgba32Float => "rgba32float",
286            Sf::R16Unorm => "r16unorm",
287            Sf::R16Snorm => "r16snorm",
288            Sf::Rg16Unorm => "rg16unorm",
289            Sf::Rg16Snorm => "rg16snorm",
290            Sf::Rgba16Unorm => "rgba16unorm",
291            Sf::Rgba16Snorm => "rgba16snorm",
292        }
293    }
294}
295
296impl TryToWgsl for crate::Scalar {
297    const DESCRIPTION: &'static str = "scalar type";
298
299    fn try_to_wgsl(self) -> Option<&'static str> {
300        use crate::Scalar;
301
302        Some(match self {
303            Scalar::F16 => "f16",
304            Scalar::F32 => "f32",
305            Scalar::F64 => "f64",
306            Scalar::I16 => "i16",
307            Scalar::U16 => "u16",
308            Scalar::I32 => "i32",
309            Scalar::U32 => "u32",
310            Scalar::I64 => "i64",
311            Scalar::U64 => "u64",
312            Scalar::BOOL => "bool",
313            _ => return None,
314        })
315    }
316
317    fn to_wgsl_for_diagnostics(self) -> String {
318        match self.try_to_wgsl() {
319            Some(static_string) => static_string.to_string(),
320            None => match self.kind {
321                crate::ScalarKind::Sint
322                | crate::ScalarKind::Uint
323                | crate::ScalarKind::Float
324                | crate::ScalarKind::Bool => format!("{{non-WGSL scalar {self:?}}}"),
325                crate::ScalarKind::AbstractInt => "{AbstractInt}".to_string(),
326                crate::ScalarKind::AbstractFloat => "{AbstractFloat}".to_string(),
327            },
328        }
329    }
330}
331
332impl ToWgsl for crate::CooperativeRole {
333    fn to_wgsl(self) -> &'static str {
334        match self {
335            Self::A => "A",
336            Self::B => "B",
337            Self::C => "C",
338        }
339    }
340}
341
342impl ToWgsl for crate::ImageDimension {
343    fn to_wgsl(self) -> &'static str {
344        match self {
345            Self::D1 => "1d",
346            Self::D2 => "2d",
347            Self::D3 => "3d",
348            Self::Cube => "cube",
349        }
350    }
351}
352
353/// Return the WGSL address space and access mode strings for `space`.
354///
355/// Why don't we implement [`ToWgsl`] for [`AddressSpace`]?
356///
357/// In WGSL, the full form of a pointer type is `ptr<AS, T, AM>`, where:
358/// - `AS` is the address space,
359/// - `T` is the store type, and
360/// - `AM` is the access mode.
361///
362/// Since the type `T` intervenes between the address space and the
363/// access mode, there isn't really any individual WGSL grammar
364/// production that corresponds to an [`AddressSpace`], so [`ToWgsl`]
365/// is too simple-minded for this case.
366///
367/// Furthermore, we want to write `var<AS[, AM]>` for most address
368/// spaces, but we want to just write `var foo: T` for handle types.
369///
370/// [`AddressSpace`]: crate::AddressSpace
371pub const fn address_space_str(
372    space: crate::AddressSpace,
373) -> (Option<&'static str>, Option<&'static str>) {
374    use crate::AddressSpace as As;
375
376    (
377        Some(match space {
378            As::Private => "private",
379            As::Uniform => "uniform",
380            As::Storage { access } => {
381                if access.contains(crate::StorageAccess::ATOMIC) {
382                    return (Some("storage"), Some("atomic"));
383                } else if access.contains(crate::StorageAccess::STORE) {
384                    return (Some("storage"), Some("read_write"));
385                } else {
386                    "storage"
387                }
388            }
389            As::Immediate => "immediate",
390            As::WorkGroup => "workgroup",
391            As::Handle => return (None, None),
392            As::Function => "function",
393            As::TaskPayload => "task_payload",
394            As::IncomingRayPayload => "incoming_ray_payload",
395            As::RayPayload => "ray_payload",
396        }),
397        None,
398    )
399}