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 features::Features;
47pub use writer::Writer;
48
49use alloc::{
50    borrow::ToOwned,
51    format,
52    string::{String, ToString},
53    vec,
54    vec::Vec,
55};
56use core::{
57    cmp::Ordering,
58    fmt::{self, Error as FmtError, Write},
59    mem,
60};
61
62use hashbrown::hash_map;
63use thiserror::Error;
64
65use crate::{
66    back::{self, Baked},
67    common,
68    proc::{self, NameKey},
69    valid, Handle, ShaderStage, TypeInner,
70};
71use conv::*;
72use features::FeaturesManager;
73
74/// Contains simple 1:1 conversion functions.
75mod conv;
76/// Contains the features related code and the features querying method
77mod features;
78/// Contains a constant with a slice of all the reserved keywords RESERVED_KEYWORDS
79mod keywords;
80/// Contains the [`Writer`] type.
81mod writer;
82
83/// List of supported `core` GLSL versions.
84pub const SUPPORTED_CORE_VERSIONS: &[u16] = &[140, 150, 330, 400, 410, 420, 430, 440, 450, 460];
85/// List of supported `es` GLSL versions.
86pub const SUPPORTED_ES_VERSIONS: &[u16] = &[300, 310, 320];
87
88/// The suffix of the variable that will hold the calculated clamped level
89/// of detail for bounds checking in `ImageLoad`
90const CLAMPED_LOD_SUFFIX: &str = "_clamped_lod";
91
92pub(crate) const MODF_FUNCTION: &str = "naga_modf";
93pub(crate) const FREXP_FUNCTION: &str = "naga_frexp";
94
95// Must match code in glsl_built_in
96pub const FIRST_INSTANCE_BINDING: &str = "naga_vs_first_instance";
97
98#[cfg(feature = "deserialize")]
99#[derive(serde::Deserialize)]
100struct BindingMapSerialization {
101    resource_binding: crate::ResourceBinding,
102    bind_target: u8,
103}
104
105#[cfg(feature = "deserialize")]
106fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
107where
108    D: serde::Deserializer<'de>,
109{
110    use serde::Deserialize;
111
112    let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
113    let mut map = BindingMap::default();
114    for item in vec {
115        map.insert(item.resource_binding, item.bind_target);
116    }
117    Ok(map)
118}
119
120/// Mapping between resources and bindings.
121pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, u8>;
122
123impl crate::AtomicFunction {
124    const fn to_glsl(self) -> &'static str {
125        match self {
126            Self::Add | Self::Subtract => "Add",
127            Self::And => "And",
128            Self::InclusiveOr => "Or",
129            Self::ExclusiveOr => "Xor",
130            Self::Min => "Min",
131            Self::Max => "Max",
132            Self::Exchange { compare: None } => "Exchange",
133            Self::Exchange { compare: Some(_) } => "", //TODO
134        }
135    }
136}
137
138impl crate::AddressSpace {
139    /// Whether a variable with this address space can be initialized
140    const fn initializable(&self) -> bool {
141        match *self {
142            crate::AddressSpace::Function | crate::AddressSpace::Private => true,
143            crate::AddressSpace::WorkGroup
144            | crate::AddressSpace::Uniform
145            | crate::AddressSpace::Storage { .. }
146            | crate::AddressSpace::Handle
147            | crate::AddressSpace::Immediate
148            | crate::AddressSpace::TaskPayload => false,
149
150            crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => {
151                unreachable!()
152            }
153        }
154    }
155}
156
157/// A GLSL version.
158#[derive(Debug, Copy, Clone, PartialEq)]
159#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
160#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
161pub enum Version {
162    /// `core` GLSL.
163    Desktop(u16),
164    /// `es` GLSL.
165    Embedded { version: u16, is_webgl: bool },
166}
167
168impl Version {
169    /// Create a new gles version
170    pub const fn new_gles(version: u16) -> Self {
171        Self::Embedded {
172            version,
173            is_webgl: false,
174        }
175    }
176
177    /// Returns true if self is `Version::Embedded` (i.e. is a es version)
178    const fn is_es(&self) -> bool {
179        match *self {
180            Version::Desktop(_) => false,
181            Version::Embedded { .. } => true,
182        }
183    }
184
185    /// Returns true if targeting WebGL
186    const fn is_webgl(&self) -> bool {
187        match *self {
188            Version::Desktop(_) => false,
189            Version::Embedded { is_webgl, .. } => is_webgl,
190        }
191    }
192
193    /// Checks the list of currently supported versions and returns true if it contains the
194    /// specified version
195    ///
196    /// # Notes
197    /// As an invalid version number will never be added to the supported version list
198    /// so this also checks for version validity
199    fn is_supported(&self) -> bool {
200        match *self {
201            Version::Desktop(v) => SUPPORTED_CORE_VERSIONS.contains(&v),
202            Version::Embedded { version: v, .. } => SUPPORTED_ES_VERSIONS.contains(&v),
203        }
204    }
205
206    fn supports_io_locations(&self) -> bool {
207        *self >= Version::Desktop(330) || *self >= Version::new_gles(300)
208    }
209
210    /// Checks if the version supports all of the explicit layouts:
211    /// - `location=` qualifiers for bindings
212    /// - `binding=` qualifiers for resources
213    ///
214    /// Note: `location=` for vertex inputs and fragment outputs is supported
215    /// unconditionally for GLES 300.
216    fn supports_explicit_locations(&self) -> bool {
217        *self >= Version::Desktop(420) || *self >= Version::new_gles(310)
218    }
219
220    fn supports_early_depth_test(&self) -> bool {
221        *self >= Version::Desktop(130) || *self >= Version::new_gles(310)
222    }
223
224    fn supports_std140_layout(&self) -> bool {
225        *self >= Version::Desktop(140) || *self >= Version::new_gles(300)
226    }
227
228    fn supports_std430_layout(&self) -> bool {
229        // std430 is available from 400 via GL_ARB_shader_storage_buffer_object.
230        *self >= Version::Desktop(400) || *self >= Version::new_gles(310)
231    }
232
233    fn supports_fma_function(&self) -> bool {
234        *self >= Version::Desktop(400) || *self >= Version::new_gles(320)
235    }
236
237    fn supports_integer_functions(&self) -> bool {
238        *self >= Version::Desktop(400) || *self >= Version::new_gles(310)
239    }
240
241    fn supports_frexp_function(&self) -> bool {
242        *self >= Version::Desktop(400) || *self >= Version::new_gles(310)
243    }
244
245    fn supports_derivative_control(&self) -> bool {
246        *self >= Version::Desktop(450)
247    }
248
249    // For supports_pack_unpack_4x8, supports_pack_unpack_snorm_2x16, supports_pack_unpack_unorm_2x16
250    // see:
251    // https://registry.khronos.org/OpenGL-Refpages/gl4/html/unpackUnorm.xhtml
252    // https://registry.khronos.org/OpenGL-Refpages/es3/html/unpackUnorm.xhtml
253    // https://registry.khronos.org/OpenGL-Refpages/gl4/html/packUnorm.xhtml
254    // https://registry.khronos.org/OpenGL-Refpages/es3/html/packUnorm.xhtml
255    fn supports_pack_unpack_4x8(&self) -> bool {
256        *self >= Version::Desktop(400) || *self >= Version::new_gles(310)
257    }
258    fn supports_pack_unpack_snorm_2x16(&self) -> bool {
259        *self >= Version::Desktop(420) || *self >= Version::new_gles(300)
260    }
261    fn supports_pack_unpack_unorm_2x16(&self) -> bool {
262        *self >= Version::Desktop(400) || *self >= Version::new_gles(300)
263    }
264
265    // https://registry.khronos.org/OpenGL-Refpages/gl4/html/unpackHalf2x16.xhtml
266    // https://registry.khronos.org/OpenGL-Refpages/gl4/html/packHalf2x16.xhtml
267    // https://registry.khronos.org/OpenGL-Refpages/es3/html/unpackHalf2x16.xhtml
268    // https://registry.khronos.org/OpenGL-Refpages/es3/html/packHalf2x16.xhtml
269    fn supports_pack_unpack_half_2x16(&self) -> bool {
270        *self >= Version::Desktop(420) || *self >= Version::new_gles(300)
271    }
272}
273
274impl PartialOrd for Version {
275    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
276        match (*self, *other) {
277            (Version::Desktop(x), Version::Desktop(y)) => Some(x.cmp(&y)),
278            (Version::Embedded { version: x, .. }, Version::Embedded { version: y, .. }) => {
279                Some(x.cmp(&y))
280            }
281            _ => None,
282        }
283    }
284}
285
286impl fmt::Display for Version {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        match *self {
289            Version::Desktop(v) => write!(f, "{v} core"),
290            Version::Embedded { version: v, .. } => write!(f, "{v} es"),
291        }
292    }
293}
294
295bitflags::bitflags! {
296    /// Configuration flags for the [`Writer`].
297    #[cfg_attr(feature = "serialize", derive(serde::Serialize))]
298    #[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
299    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
300    pub struct WriterFlags: u32 {
301        /// Flip output Y and extend Z from (0, 1) to (-1, 1).
302        const ADJUST_COORDINATE_SPACE = 0x1;
303        /// Supports GL_EXT_texture_shadow_lod on the host, which provides
304        /// additional functions on shadows and arrays of shadows.
305        const TEXTURE_SHADOW_LOD = 0x2;
306        /// Supports ARB_shader_draw_parameters on the host, which provides
307        /// support for `gl_BaseInstanceARB`, `gl_BaseVertexARB`, `gl_DrawIDARB`, and `gl_DrawID`.
308        const DRAW_PARAMETERS = 0x4;
309        /// Include unused global variables, constants and functions. By default the output will exclude
310        /// global variables that are not used in the specified entrypoint (including indirect use),
311        /// all constant declarations, and functions that use excluded global variables.
312        const INCLUDE_UNUSED_ITEMS = 0x10;
313        /// Emit `PointSize` output builtin to vertex shaders, which is
314        /// required for drawing with `PointList` topology.
315        ///
316        /// https://registry.khronos.org/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.html#built-in-language-variables
317        /// The variable gl_PointSize is intended for a shader to write the size of the point to be rasterized. It is measured in pixels.
318        /// If gl_PointSize is not written to, its value is undefined in subsequent pipe stages.
319        const FORCE_POINT_SIZE = 0x20;
320    }
321}
322
323/// Configuration used in the [`Writer`].
324#[derive(Debug, Clone)]
325#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
326#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
327#[cfg_attr(feature = "deserialize", serde(default))]
328pub struct Options {
329    /// The GLSL version to be used.
330    pub version: Version,
331    /// Configuration flags for the [`Writer`].
332    pub writer_flags: WriterFlags,
333    /// Map of resources association to binding locations.
334    #[cfg_attr(
335        feature = "deserialize",
336        serde(deserialize_with = "deserialize_binding_map")
337    )]
338    pub binding_map: BindingMap,
339    /// Should workgroup variables be zero initialized (by polyfilling)?
340    pub zero_initialize_workgroup_memory: bool,
341}
342
343impl Default for Options {
344    fn default() -> Self {
345        Options {
346            version: Version::new_gles(310),
347            writer_flags: WriterFlags::ADJUST_COORDINATE_SPACE,
348            binding_map: BindingMap::default(),
349            zero_initialize_workgroup_memory: true,
350        }
351    }
352}
353
354/// A subset of options meant to be changed per pipeline.
355#[derive(Debug, Clone)]
356#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
357#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
358pub struct PipelineOptions {
359    /// The stage of the entry point.
360    pub shader_stage: ShaderStage,
361    /// The name of the entry point.
362    ///
363    /// If no entry point that matches is found while creating a [`Writer`], an
364    /// error will be thrown.
365    pub entry_point: String,
366    /// How many views to render to, if doing multiview rendering.
367    pub multiview: Option<core::num::NonZeroU32>,
368}
369
370#[derive(Debug)]
371pub struct VaryingLocation {
372    /// The location of the global.
373    /// This corresponds to `layout(location = ..)` in GLSL.
374    pub location: u32,
375    /// The index which can be used for dual source blending.
376    /// This corresponds to `layout(index = ..)` in GLSL.
377    pub index: u32,
378}
379
380/// Reflection info for texture mappings and uniforms.
381#[derive(Debug)]
382pub struct ReflectionInfo {
383    /// Mapping between texture names and variables/samplers.
384    pub texture_mapping: crate::FastHashMap<String, TextureMapping>,
385    /// Mapping between uniform variables and names.
386    pub uniforms: crate::FastHashMap<Handle<crate::GlobalVariable>, String>,
387    /// Mapping between names and attribute locations.
388    pub varying: crate::FastHashMap<String, VaryingLocation>,
389    /// List of immediate data items in the shader.
390    pub immediates_items: Vec<ImmediateItem>,
391    /// Number of user-defined clip planes. Only applicable to vertex shaders.
392    pub clip_distance_count: u32,
393}
394
395/// Mapping between a texture and its sampler, if it exists.
396///
397/// GLSL pre-Vulkan has no concept of separate textures and samplers. Instead, everything is a
398/// `gsamplerN` where `g` is the scalar type and `N` is the dimension. But naga uses separate textures
399/// and samplers in the IR, so the backend produces a [`FastHashMap`](crate::FastHashMap) with the texture name
400/// as a key and a [`TextureMapping`] as a value. This way, the user knows where to bind.
401///
402/// [`Storage`](crate::ImageClass::Storage) images produce `gimageN` and don't have an associated sampler,
403/// so the [`sampler`](Self::sampler) field will be [`None`].
404#[derive(Debug, Clone)]
405pub struct TextureMapping {
406    /// Handle to the image global variable.
407    pub texture: Handle<crate::GlobalVariable>,
408    /// Handle to the associated sampler global variable, if it exists.
409    pub sampler: Option<Handle<crate::GlobalVariable>>,
410}
411
412/// All information to bind a single uniform value to the shader.
413///
414/// Immediates are emulated using traditional uniforms in OpenGL.
415///
416/// These are composed of a set of primitives (scalar, vector, matrix) that
417/// are given names. Because they are not backed by the concept of a buffer,
418/// we must do the work of calculating the offset of each primitive in the
419/// immediate data block.
420#[derive(Debug, Clone)]
421pub struct ImmediateItem {
422    /// GL uniform name for the item. This name is the same as if you were
423    /// to access it directly from a GLSL shader.
424    ///
425    /// The with the following example, the following names will be generated,
426    /// one name per GLSL uniform.
427    ///
428    /// ```glsl
429    /// struct InnerStruct {
430    ///     value: f32,
431    /// }
432    ///
433    /// struct ImmediateData {
434    ///     InnerStruct inner;
435    ///     vec4 array[2];
436    /// }
437    ///
438    /// uniform ImmediateData _immediates_binding_cs;
439    /// ```
440    ///
441    /// ```text
442    /// - _immediates_binding_cs.inner.value
443    /// - _immediates_binding_cs.array[0]
444    /// - _immediates_binding_cs.array[1]
445    /// ```
446    ///
447    pub access_path: String,
448    /// Type of the uniform. This will only ever be a scalar, vector, or matrix.
449    pub ty: Handle<crate::Type>,
450    /// The offset in the immediate data memory block this uniform maps to.
451    ///
452    /// The size of the uniform can be derived from the type.
453    pub offset: u32,
454}
455
456/// Helper structure that generates a number
457#[derive(Default)]
458struct IdGenerator(u32);
459
460impl IdGenerator {
461    /// Generates a number that's guaranteed to be unique for this `IdGenerator`
462    const fn generate(&mut self) -> u32 {
463        // It's just an increasing number but it does the job
464        let ret = self.0;
465        self.0 += 1;
466        ret
467    }
468}
469
470/// Assorted options needed for generating varyings.
471#[derive(Clone, Copy)]
472struct VaryingOptions {
473    output: bool,
474    targeting_webgl: bool,
475    draw_parameters: bool,
476}
477
478impl VaryingOptions {
479    const fn from_writer_options(options: &Options, output: bool) -> Self {
480        Self {
481            output,
482            targeting_webgl: options.version.is_webgl(),
483            draw_parameters: options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS),
484        }
485    }
486}
487
488/// Helper wrapper used to get a name for a varying
489///
490/// Varying have different naming schemes depending on their binding:
491/// - Varyings with builtin bindings get their name from [`glsl_built_in`].
492/// - Varyings with location bindings are named `_S_location_X` where `S` is a
493///   prefix identifying which pipeline stage the varying connects, and `X` is
494///   the location.
495struct VaryingName<'a> {
496    binding: &'a crate::Binding,
497    stage: ShaderStage,
498    options: VaryingOptions,
499}
500impl fmt::Display for VaryingName<'_> {
501    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
502        match *self.binding {
503            crate::Binding::Location {
504                blend_src: Some(1), ..
505            } => {
506                write!(f, "_fs2p_location1",)
507            }
508            crate::Binding::Location { location, .. } => {
509                let prefix = match (self.stage, self.options.output) {
510                    (ShaderStage::Compute, _) => unreachable!(),
511                    // pipeline to vertex
512                    (ShaderStage::Vertex, false) => "p2vs",
513                    // vertex to fragment
514                    (ShaderStage::Vertex, true) | (ShaderStage::Fragment, false) => "vs2fs",
515                    // fragment to pipeline
516                    (ShaderStage::Fragment, true) => "fs2p",
517                    (
518                        ShaderStage::Task
519                        | ShaderStage::Mesh
520                        | ShaderStage::RayGeneration
521                        | ShaderStage::AnyHit
522                        | ShaderStage::ClosestHit
523                        | ShaderStage::Miss,
524                        _,
525                    ) => unreachable!(),
526                };
527                write!(f, "_{prefix}_location{location}",)
528            }
529            crate::Binding::BuiltIn(built_in) => {
530                write!(f, "{}", glsl_built_in(built_in, self.options))
531            }
532        }
533    }
534}
535
536impl ShaderStage {
537    const fn to_str(self) -> &'static str {
538        match self {
539            ShaderStage::Compute => "cs",
540            ShaderStage::Fragment => "fs",
541            ShaderStage::Vertex => "vs",
542            ShaderStage::Task
543            | ShaderStage::Mesh
544            | ShaderStage::RayGeneration
545            | ShaderStage::AnyHit
546            | ShaderStage::ClosestHit
547            | ShaderStage::Miss => unreachable!(),
548        }
549    }
550}
551
552/// Shorthand result used internally by the backend
553type BackendResult<T = ()> = Result<T, Error>;
554
555/// A GLSL compilation error.
556#[derive(Debug, Error)]
557pub enum Error {
558    /// A error occurred while writing to the output.
559    #[error("Format error")]
560    FmtError(#[from] FmtError),
561    /// The specified [`Version`] doesn't have all required [`Features`].
562    ///
563    /// Contains the missing [`Features`].
564    #[error("The selected version doesn't support {0:?}")]
565    MissingFeatures(Features),
566    /// [`AddressSpace::Immediate`](crate::AddressSpace::Immediate) was used more than
567    /// once in the entry point, which isn't supported.
568    #[error("Multiple immediates aren't supported")]
569    MultipleImmediateData,
570    /// The specified [`Version`] isn't supported.
571    #[error("The specified version isn't supported")]
572    VersionNotSupported,
573    /// The entry point couldn't be found.
574    #[error("The requested entry point couldn't be found")]
575    EntryPointNotFound,
576    /// A call was made to an unsupported external.
577    #[error("A call was made to an unsupported external: {0}")]
578    UnsupportedExternal(String),
579    /// A scalar with an unsupported width was requested.
580    #[error("A scalar with an unsupported width was requested: {0:?}")]
581    UnsupportedScalar(crate::Scalar),
582    /// A image was used with multiple samplers, which isn't supported.
583    #[error("A image was used with multiple samplers")]
584    ImageMultipleSamplers,
585    #[error("{0}")]
586    Custom(String),
587    #[error("overrides should not be present at this stage")]
588    Override,
589    /// [`crate::Sampling::First`] is unsupported.
590    #[error("`{:?}` sampling is unsupported", crate::Sampling::First)]
591    FirstSamplingNotSupported,
592    #[error(transparent)]
593    ResolveArraySizeError(#[from] proc::ResolveArraySizeError),
594}
595
596/// Binary operation with a different logic on the GLSL side.
597enum BinaryOperation {
598    /// Vector comparison should use the function like `greaterThan()`, etc.
599    VectorCompare,
600    /// Vector component wise operation; used to polyfill unsupported ops like `|` and `&` for `bvecN`'s
601    VectorComponentWise,
602    /// GLSL `%` is SPIR-V `OpUMod/OpSMod` and `mod()` is `OpFMod`, but [`BinaryOperator::Modulo`](crate::BinaryOperator::Modulo) is `OpFRem`.
603    Modulo,
604    /// Any plain operation. No additional logic required.
605    Other,
606}
607
608fn is_value_init_supported(module: &crate::Module, ty: Handle<crate::Type>) -> bool {
609    match module.types[ty].inner {
610        TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => true,
611        TypeInner::Array { base, size, .. } => {
612            size != crate::ArraySize::Dynamic && is_value_init_supported(module, base)
613        }
614        TypeInner::Struct { ref members, .. } => members
615            .iter()
616            .all(|member| is_value_init_supported(module, member.ty)),
617        _ => false,
618    }
619}
620
621pub fn supported_capabilities() -> valid::Capabilities {
622    use valid::Capabilities as Caps;
623
624    // Lots of these aren't supported on GLES in general, but naga is able to write them without panicking.
625
626    Caps::IMMEDIATES
627        | Caps::FLOAT64
628        | Caps::PRIMITIVE_INDEX
629        | Caps::CLIP_DISTANCES
630        | Caps::MULTIVIEW
631        | Caps::EARLY_DEPTH_TEST
632        | Caps::MULTISAMPLED_SHADING
633        | Caps::DUAL_SOURCE_BLENDING
634        | Caps::CUBE_ARRAY_TEXTURES
635        | Caps::SHADER_INT64
636        | Caps::SHADER_INT64_ATOMIC_ALL_OPS
637        | Caps::TEXTURE_ATOMIC
638        | Caps::TEXTURE_INT64_ATOMIC
639        | Caps::SUBGROUP
640        | Caps::SUBGROUP_BARRIER
641        | Caps::SHADER_FLOAT16
642        | Caps::SHADER_FLOAT16_IN_FLOAT32
643        | Caps::SHADER_BARYCENTRICS
644        | Caps::DRAW_INDEX
645        | Caps::MEMORY_DECORATION_COHERENT
646        | Caps::MEMORY_DECORATION_VOLATILE
647}