naga/back/glsl/
mod.rs

1/*!
2Backend for [GLSL][glsl] (OpenGL Shading Language).
3
4The main structure is [`Writer`], it maintains internal state that is used
5to output a [`Module`](crate::Module) into glsl
6
7# Supported versions
8### Core
9- 330
10- 400
11- 410
12- 420
13- 430
14- 450
15
16### ES
17- 300
18- 310
19
20[glsl]: https://www.khronos.org/registry/OpenGL/index_gl.php
21*/
22
23// GLSL is mostly a superset of C but it also removes some parts of it this is a list of relevant
24// aspects for this backend.
25//
26// The most notable change is the introduction of the version preprocessor directive that must
27// always be the first line of a glsl file and is written as
28// `#version number profile`
29// `number` is the version itself (i.e. 300) and `profile` is the
30// shader profile we only support "core" and "es", the former is used in desktop applications and
31// the later is used in embedded contexts, mobile devices and browsers. Each one as it's own
32// versions (at the time of writing this the latest version for "core" is 460 and for "es" is 320)
33//
34// Other important preprocessor addition is the extension directive which is written as
35// `#extension name: behaviour`
36// Extensions provide increased features in a plugin fashion but they aren't required to be
37// supported hence why they are called extensions, that's why `behaviour` is used it specifies
38// whether the extension is strictly required or if it should only be enabled if needed. In our case
39// when we use extensions we set behaviour to `require` always.
40//
41// The only thing that glsl removes that makes a difference are pointers.
42//
43// Additions that are relevant for the backend are the discard keyword, the introduction of
44// vector, matrices, samplers, image types and functions that provide common shader operations
45
46pub use nt::glsl::*;
47
48pub use features::Features;
49pub use writer::Writer;
50
51use alloc::{
52    borrow::ToOwned,
53    format,
54    string::{String, ToString},
55    vec,
56    vec::Vec,
57};
58use core::{
59    fmt::{self, Error as FmtError, Write},
60    mem,
61};
62
63use hashbrown::hash_map;
64use thiserror::Error;
65
66use crate::{
67    back::{self, Baked},
68    common,
69    proc::{self, NameKey},
70    valid, Handle, ShaderStage, TypeInner,
71};
72use conv::*;
73use features::FeaturesManager;
74
75/// Contains simple 1:1 conversion functions.
76mod conv;
77/// Contains the features related code and the features querying method
78mod features;
79/// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS
80mod keywords;
81/// Contains the [`Writer`] type.
82mod writer;
83
84/// The suffix of the variable that will hold the calculated clamped level
85/// of detail for bounds checking in `ImageLoad`
86const CLAMPED_LOD_SUFFIX: &str = "_clamped_lod";
87
88pub(crate) const MODF_FUNCTION: &str = "naga_modf";
89pub(crate) const FREXP_FUNCTION: &str = "naga_frexp";
90
91#[cfg(feature = "deserialize")]
92#[derive(serde::Deserialize)]
93struct BindingMapSerialization {
94    resource_binding: crate::ResourceBinding,
95    bind_target: u8,
96}
97
98#[cfg(feature = "deserialize")]
99fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
100where
101    D: serde::Deserializer<'de>,
102{
103    use serde::Deserialize;
104
105    let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
106    let mut map = BindingMap::default();
107    for item in vec {
108        map.insert(item.resource_binding, item.bind_target);
109    }
110    Ok(map)
111}
112
113impl crate::AtomicFunction {
114    const fn to_glsl(self) -> &'static str {
115        match self {
116            Self::Add | Self::Subtract => "Add",
117            Self::And => "And",
118            Self::InclusiveOr => "Or",
119            Self::ExclusiveOr => "Xor",
120            Self::Min => "Min",
121            Self::Max => "Max",
122            Self::Exchange { compare: None } => "Exchange",
123            Self::Exchange { compare: Some(_) } => "", //TODO
124        }
125    }
126}
127
128impl crate::AddressSpace {
129    /// Whether a variable with this address space can be initialized
130    const fn initializable(&self) -> bool {
131        match *self {
132            crate::AddressSpace::Function | crate::AddressSpace::Private => true,
133            crate::AddressSpace::WorkGroup
134            | crate::AddressSpace::Uniform
135            | crate::AddressSpace::Storage { .. }
136            | crate::AddressSpace::Handle
137            | crate::AddressSpace::Immediate
138            | crate::AddressSpace::TaskPayload => false,
139
140            crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => {
141                unreachable!()
142            }
143        }
144    }
145}
146
147bitflags::bitflags! {
148    /// Configuration flags for the [`Writer`].
149    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
150    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
151    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
152    pub struct WriterFlags: u32 {
153        /// Flip output Y and extend Z from (0, 1) to (-1, 1).
154        const ADJUST_COORDINATE_SPACE = 0x1;
155        /// Supports GL_EXT_texture_shadow_lod on the host, which provides
156        /// additional functions on shadows and arrays of shadows.
157        const TEXTURE_SHADOW_LOD = 0x2;
158        /// Supports ARB_shader_draw_parameters on the host, which provides
159        /// support for `gl_BaseInstanceARB`, `gl_BaseVertexARB`, `gl_DrawIDARB`, and `gl_DrawID`.
160        const DRAW_PARAMETERS = 0x4;
161        /// Include unused global variables, constants and functions. By default the output will exclude
162        /// global variables that are not used in the specified entrypoint (including indirect use),
163        /// all constant declarations, and functions that use excluded global variables.
164        const INCLUDE_UNUSED_ITEMS = 0x10;
165        /// Emit `PointSize` output builtin to vertex shaders, which is
166        /// required for drawing with `PointList` topology.
167        ///
168        /// https://registry.khronos.org/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.html#built-in-language-variables
169        /// The variable gl_PointSize is intended for a shader to write the size of the point to be rasterized. It is measured in pixels.
170        /// If gl_PointSize is not written to, its value is undefined in subsequent pipe stages.
171        const FORCE_POINT_SIZE = 0x20;
172    }
173}
174
175/// Configuration used in the [`Writer`].
176#[derive(Debug, Clone)]
177#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
178#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
179#[cfg_attr(feature = "deserialize", serde(default))]
180pub struct Options {
181    /// The GLSL version to be used.
182    pub version: Version,
183    /// Configuration flags for the [`Writer`].
184    pub writer_flags: WriterFlags,
185    /// Map of resources association to binding locations.
186    #[cfg_attr(
187        feature = "deserialize",
188        serde(deserialize_with = "deserialize_binding_map")
189    )]
190    pub binding_map: BindingMap,
191    /// Should workgroup variables be zero initialized (by polyfilling)?
192    pub zero_initialize_workgroup_memory: bool,
193}
194
195impl Default for Options {
196    fn default() -> Self {
197        Options {
198            version: Version::new_gles(310),
199            writer_flags: WriterFlags::ADJUST_COORDINATE_SPACE,
200            binding_map: BindingMap::default(),
201            zero_initialize_workgroup_memory: true,
202        }
203    }
204}
205
206/// A subset of options meant to be changed per pipeline.
207#[derive(Debug, Clone)]
208#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
209#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
210pub struct PipelineOptions {
211    /// The stage of the entry point.
212    pub shader_stage: ShaderStage,
213    /// The name of the entry point.
214    ///
215    /// If no entry point that matches is found while creating a [`Writer`], an
216    /// error will be thrown.
217    pub entry_point: String,
218    /// How many views to render to, if doing multiview rendering.
219    pub multiview: Option<core::num::NonZeroU32>,
220}
221
222#[derive(Debug)]
223pub struct VaryingLocation {
224    /// The location of the global.
225    /// This corresponds to `layout(location = ..)` in GLSL.
226    pub location: u32,
227    /// The index which can be used for dual source blending.
228    /// This corresponds to `layout(index = ..)` in GLSL.
229    pub index: u32,
230}
231
232/// Reflection info for texture mappings and uniforms.
233#[derive(Debug)]
234pub struct ReflectionInfo {
235    /// Mapping between texture names and variables/samplers.
236    pub texture_mapping: crate::FastHashMap<String, TextureMapping>,
237    /// Mapping between uniform variables and names.
238    pub uniforms: crate::FastHashMap<Handle<crate::GlobalVariable>, String>,
239    /// Mapping between names and attribute locations.
240    pub varying: crate::FastHashMap<String, VaryingLocation>,
241    /// List of immediate data items in the shader.
242    pub immediates_items: Vec<ImmediateItem>,
243    /// Number of user-defined clip planes. Only applicable to vertex shaders.
244    pub clip_distance_count: u32,
245}
246
247/// Mapping between a texture and its sampler, if it exists.
248///
249/// GLSL pre-Vulkan has no concept of separate textures and samplers. Instead, everything is a
250/// `gsamplerN` where `g` is the scalar type and `N` is the dimension. But naga uses separate textures
251/// and samplers in the IR, so the backend produces a [`FastHashMap`](crate::FastHashMap) with the texture name
252/// as a key and a [`TextureMapping`] as a value. This way, the user knows where to bind.
253///
254/// [`Storage`](crate::ImageClass::Storage) images produce `gimageN` and don't have an associated sampler,
255/// so the [`sampler`](Self::sampler) field will be [`None`].
256#[derive(Debug, Clone)]
257pub struct TextureMapping {
258    /// Handle to the image global variable.
259    pub texture: Handle<crate::GlobalVariable>,
260    /// Handle to the associated sampler global variable, if it exists.
261    pub sampler: Option<Handle<crate::GlobalVariable>>,
262}
263
264/// All information to bind a single uniform value to the shader.
265///
266/// Immediates are emulated using traditional uniforms in OpenGL.
267///
268/// These are composed of a set of primitives (scalar, vector, matrix) that
269/// are given names. Because they are not backed by the concept of a buffer,
270/// we must do the work of calculating the offset of each primitive in the
271/// immediate data block.
272#[derive(Debug, Clone)]
273pub struct ImmediateItem {
274    /// GL uniform name for the item. This name is the same as if you were
275    /// to access it directly from a GLSL shader.
276    ///
277    /// The with the following example, the following names will be generated,
278    /// one name per GLSL uniform.
279    ///
280    /// ```glsl
281    /// struct InnerStruct {
282    ///     value: f32,
283    /// }
284    ///
285    /// struct ImmediateData {
286    ///     InnerStruct inner;
287    ///     vec4 array[2];
288    /// }
289    ///
290    /// uniform ImmediateData _immediates_binding_cs;
291    /// ```
292    ///
293    /// ```text
294    /// - _immediates_binding_cs.inner.value
295    /// - _immediates_binding_cs.array[0]
296    /// - _immediates_binding_cs.array[1]
297    /// ```
298    ///
299    pub access_path: String,
300    /// Type of the uniform. This will only ever be a scalar, vector, or matrix.
301    ///
302    /// Stored as a [`GlslUniformType`] rather than a [`Handle<Type>`] because
303    /// `process_overrides` may compact the module, renumbering type handles.
304    /// Leaf types don't reference other types, so a `GlslUniformType` is self-contained.
305    ///
306    /// [`Handle<Type>`]: Handle
307    pub ty: GlslUniformType,
308    /// The offset in the immediate data memory block this uniform maps to.
309    pub offset: u32,
310    /// Size of this uniform in bytes.
311    pub size_bytes: u32,
312}
313
314/// Helper structure that generates a number
315#[derive(Default)]
316struct IdGenerator(u32);
317
318impl IdGenerator {
319    /// Generates a number that's guaranteed to be unique for this `IdGenerator`
320    const fn generate(&mut self) -> u32 {
321        // It's just an increasing number but it does the job
322        let ret = self.0;
323        self.0 += 1;
324        ret
325    }
326}
327
328/// Assorted options needed for generating varyings.
329#[derive(Clone, Copy)]
330struct VaryingOptions {
331    output: bool,
332    targeting_webgl: bool,
333    draw_parameters: bool,
334}
335
336impl VaryingOptions {
337    const fn from_writer_options(options: &Options, output: bool) -> Self {
338        Self {
339            output,
340            targeting_webgl: options.version.is_webgl(),
341            draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS),
342        }
343    }
344}
345
346/// Helper wrapper used to get a name for a varying
347///
348/// Varying have different naming schemes depending on their binding:
349/// - Varyings with builtin bindings get their name from [`glsl_built_in`].
350/// - Varyings with location bindings are named `_S_location_X` where `S` is a
351///   prefix identifying which pipeline stage the varying connects, and `X` is
352///   the location.
353struct VaryingName<'a> {
354    binding: &'a crate::Binding,
355    stage: ShaderStage,
356    options: VaryingOptions,
357}
358impl fmt::Display for VaryingName<'_> {
359    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
360        match *self.binding {
361            crate::Binding::Location {
362                blend_src: Some(1), ..
363            } => {
364                write!(f, "_fs2p_location1",)
365            }
366            crate::Binding::Location { location, .. } => {
367                let prefix = match (self.stage, self.options.output) {
368                    (ShaderStage::Compute, _) => unreachable!(),
369                    // pipeline to vertex
370                    (ShaderStage::Vertex, false) => "p2vs",
371                    // vertex to fragment
372                    (ShaderStage::Vertex, true) | (ShaderStage::Fragment, false) => "vs2fs",
373                    // fragment to pipeline
374                    (ShaderStage::Fragment, true) => "fs2p",
375                    (
376                        ShaderStage::Task
377                        | ShaderStage::Mesh
378                        | ShaderStage::RayGeneration
379                        | ShaderStage::AnyHit
380                        | ShaderStage::ClosestHit
381                        | ShaderStage::Miss,
382                        _,
383                    ) => unreachable!(),
384                };
385                write!(f, "_{prefix}_location{location}",)
386            }
387            crate::Binding::BuiltIn(built_in) => {
388                write!(f, "{}", glsl_built_in(built_in, self.options))
389            }
390        }
391    }
392}
393
394const fn shader_stage_to_str(st: ShaderStage) -> &'static str {
395    match st {
396        ShaderStage::Compute => "cs",
397        ShaderStage::Fragment => "fs",
398        ShaderStage::Vertex => "vs",
399        ShaderStage::Task
400        | ShaderStage::Mesh
401        | ShaderStage::RayGeneration
402        | ShaderStage::AnyHit
403        | ShaderStage::ClosestHit
404        | ShaderStage::Miss => unreachable!(),
405    }
406}
407
408/// Shorthand result used internally by the backend
409type BackendResult<T = ()> = Result<T, Error>;
410
411/// A GLSL compilation error.
412#[derive(Debug, Error)]
413pub enum Error {
414    /// A error occurred while writing to the output.
415    #[error("Format error")]
416    FmtError(#[from] FmtError),
417    /// The specified [`Version`] doesn't have all required [`Features`].
418    ///
419    /// Contains the missing [`Features`].
420    #[error("The selected version doesn't support {0:?}")]
421    MissingFeatures(Features),
422    /// [`AddressSpace::Immediate`](crate::AddressSpace::Immediate) was used more than
423    /// once in the entry point, which isn't supported.
424    #[error("Multiple immediates aren't supported")]
425    MultipleImmediateData,
426    /// The specified [`Version`] isn't supported.
427    #[error("The specified version isn't supported")]
428    VersionNotSupported,
429    /// The entry point couldn't be found.
430    #[error("The requested entry point couldn't be found")]
431    EntryPointNotFound,
432    /// A call was made to an unsupported external.
433    #[error("A call was made to an unsupported external: {0}")]
434    UnsupportedExternal(String),
435    /// A scalar with an unsupported width was requested.
436    #[error("A scalar with an unsupported width was requested: {0:?}")]
437    UnsupportedScalar(crate::Scalar),
438    /// A image was used with multiple samplers, which isn't supported.
439    #[error("A image was used with multiple samplers")]
440    ImageMultipleSamplers,
441    #[error("{0}")]
442    Custom(String),
443    #[error("overrides should not be present at this stage")]
444    Override,
445    /// [`crate::Sampling::First`] is unsupported.
446    #[error("`{:?}` sampling is unsupported", crate::Sampling::First)]
447    FirstSamplingNotSupported,
448    #[error(transparent)]
449    ResolveArraySizeError(#[from] proc::ResolveArraySizeError),
450}
451
452/// Binary operation with a different logic on the GLSL side.
453enum BinaryOperation {
454    /// Vector comparison should use the function like `greaterThan()`, etc.
455    VectorCompare,
456    /// Vector component wise operation; used to polyfill unsupported ops like `|` and `&` for `bvecN`'s
457    VectorComponentWise,
458    /// GLSL `%` is SPIR-V `OpUMod/OpSMod` and `mod()` is `OpFMod`, but [`BinaryOperator::Modulo`](crate::BinaryOperator::Modulo) is `OpFRem`.
459    Modulo,
460    /// Any plain operation. No additional logic required.
461    Other,
462}
463
464fn is_value_init_supported(module: &crate::Module, ty: Handle<crate::Type>) -> bool {
465    match module.types[ty].inner {
466        TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => true,
467        TypeInner::Array { base, size, .. } => {
468            size != crate::ArraySize::Dynamic && is_value_init_supported(module, base)
469        }
470        TypeInner::Struct { ref members, .. } => members
471            .iter()
472            .all(|member| is_value_init_supported(module, member.ty)),
473        _ => false,
474    }
475}
476
477pub fn supported_capabilities() -> valid::Capabilities {
478    use valid::Capabilities as Caps;
479
480    // Lots of these aren't supported on GLES in general, but naga is able to write them without panicking.
481
482    Caps::IMMEDIATES
483        | Caps::FLOAT64
484        | Caps::PRIMITIVE_INDEX
485        | Caps::CLIP_DISTANCES
486        | Caps::MULTIVIEW
487        | Caps::EARLY_DEPTH_TEST
488        | Caps::MULTISAMPLED_SHADING
489        | Caps::DUAL_SOURCE_BLENDING
490        | Caps::CUBE_ARRAY_TEXTURES
491        | Caps::SHADER_INT64
492        | Caps::SHADER_INT64_ATOMIC_ALL_OPS
493        | Caps::TEXTURE_ATOMIC
494        | Caps::TEXTURE_INT64_ATOMIC
495        | Caps::SUBGROUP
496        | Caps::SUBGROUP_BARRIER
497        | Caps::SHADER_FLOAT16
498        | Caps::SHADER_INT16
499        | Caps::SHADER_FLOAT16_IN_FLOAT32
500        | Caps::SHADER_BARYCENTRICS
501        | Caps::DRAW_INDEX
502        | Caps::MEMORY_DECORATION_COHERENT
503        | Caps::MEMORY_DECORATION_VOLATILE
504        | Caps::STORAGE_TEXTURE_16BIT_NORM_FORMATS
505}