naga/back/glsl/
features.rs

1use super::{BackendResult, Error, Version, Writer};
2use crate::{
3    AddressSpace, Binding, Bytes, Expression, Handle, ImageClass, ImageDimension, Interpolation,
4    Sampling, ScalarKind, ShaderStage, StorageFormat, Type, TypeInner,
5};
6use std::fmt::Write;
7
8bitflags::bitflags! {
9    /// Structure used to encode additions to GLSL that aren't supported by all versions.
10    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
11    pub struct Features: u32 {
12        /// Buffer address space support.
13        const BUFFER_STORAGE = 1;
14        const ARRAY_OF_ARRAYS = 1 << 1;
15        /// 8 byte floats.
16        const DOUBLE_TYPE = 1 << 2;
17        /// More image formats.
18        const FULL_IMAGE_FORMATS = 1 << 3;
19        const MULTISAMPLED_TEXTURES = 1 << 4;
20        const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5;
21        const CUBE_TEXTURES_ARRAY = 1 << 6;
22        const COMPUTE_SHADER = 1 << 7;
23        /// Image load and early depth tests.
24        const IMAGE_LOAD_STORE = 1 << 8;
25        const CONSERVATIVE_DEPTH = 1 << 9;
26        /// Interpolation and auxiliary qualifiers.
27        ///
28        /// Perspective, Flat, and Centroid are available in all GLSL versions we support.
29        const NOPERSPECTIVE_QUALIFIER = 1 << 11;
30        const SAMPLE_QUALIFIER = 1 << 12;
31        const CLIP_DISTANCE = 1 << 13;
32        const CULL_DISTANCE = 1 << 14;
33        /// Sample ID.
34        const SAMPLE_VARIABLES = 1 << 15;
35        /// Arrays with a dynamic length.
36        const DYNAMIC_ARRAY_SIZE = 1 << 16;
37        const MULTI_VIEW = 1 << 17;
38        /// Texture samples query
39        const TEXTURE_SAMPLES = 1 << 18;
40        /// Texture levels query
41        const TEXTURE_LEVELS = 1 << 19;
42        /// Image size query
43        const IMAGE_SIZE = 1 << 20;
44    }
45}
46
47/// Helper structure used to store the required [`Features`] needed to output a
48/// [`Module`](crate::Module)
49///
50/// Provides helper methods to check for availability and writing required extensions
51pub struct FeaturesManager(Features);
52
53impl FeaturesManager {
54    /// Creates a new [`FeaturesManager`] instance
55    pub const fn new() -> Self {
56        Self(Features::empty())
57    }
58
59    /// Adds to the list of required [`Features`]
60    pub fn request(&mut self, features: Features) {
61        self.0 |= features
62    }
63
64    /// Checks that all required [`Features`] are available for the specified
65    /// [`Version`](super::Version) otherwise returns an
66    /// [`Error::MissingFeatures`](super::Error::MissingFeatures)
67    pub fn check_availability(&self, version: Version) -> BackendResult {
68        // Will store all the features that are unavailable
69        let mut missing = Features::empty();
70
71        // Helper macro to check for feature availability
72        macro_rules! check_feature {
73            // Used when only core glsl supports the feature
74            ($feature:ident, $core:literal) => {
75                if self.0.contains(Features::$feature)
76                    && (version < Version::Desktop($core) || version.is_es())
77                {
78                    missing |= Features::$feature;
79                }
80            };
81            // Used when both core and es support the feature
82            ($feature:ident, $core:literal, $es:literal) => {
83                if self.0.contains(Features::$feature)
84                    && (version < Version::Desktop($core) || version < Version::new_gles($es))
85                {
86                    missing |= Features::$feature;
87                }
88            };
89        }
90
91        check_feature!(COMPUTE_SHADER, 420, 310);
92        check_feature!(BUFFER_STORAGE, 400, 310);
93        check_feature!(DOUBLE_TYPE, 150);
94        check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
95        check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
96        check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
97        check_feature!(ARRAY_OF_ARRAYS, 120, 310);
98        check_feature!(IMAGE_LOAD_STORE, 130, 310);
99        check_feature!(CONSERVATIVE_DEPTH, 130, 300);
100        check_feature!(CONSERVATIVE_DEPTH, 130, 300);
101        check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
102        check_feature!(SAMPLE_QUALIFIER, 400, 320);
103        check_feature!(CLIP_DISTANCE, 130, 300 /* with extension */);
104        check_feature!(CULL_DISTANCE, 450, 300 /* with extension */);
105        check_feature!(SAMPLE_VARIABLES, 400, 300);
106        check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
107        match version {
108            Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
109            _ => check_feature!(MULTI_VIEW, 140, 310),
110        };
111        // Only available on glsl core, this means that opengl es can't query the number
112        // of samples nor levels in a image and neither do bound checks on the sample nor
113        // the level argument of texelFecth
114        check_feature!(TEXTURE_SAMPLES, 150);
115        check_feature!(TEXTURE_LEVELS, 130);
116        check_feature!(IMAGE_SIZE, 430, 310);
117
118        // Return an error if there are missing features
119        if missing.is_empty() {
120            Ok(())
121        } else {
122            Err(Error::MissingFeatures(missing))
123        }
124    }
125
126    /// Helper method used to write all needed extensions
127    ///
128    /// # Notes
129    /// This won't check for feature availability so it might output extensions that aren't even
130    /// supported.[`check_availability`](Self::check_availability) will check feature availability
131    pub fn write(&self, version: Version, mut out: impl Write) -> BackendResult {
132        if self.0.contains(Features::COMPUTE_SHADER) && !version.is_es() {
133            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_compute_shader.txt
134            writeln!(out, "#extension GL_ARB_compute_shader : require")?;
135        }
136
137        if self.0.contains(Features::BUFFER_STORAGE) && !version.is_es() {
138            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_storage_buffer_object.txt
139            writeln!(
140                out,
141                "#extension GL_ARB_shader_storage_buffer_object : require"
142            )?;
143        }
144
145        if self.0.contains(Features::DOUBLE_TYPE) && version < Version::Desktop(400) {
146            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gpu_shader_fp64.txt
147            writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
148        }
149
150        if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
151            if version.is_es() {
152                // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_cube_map_array.txt
153                writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
154            } else if version < Version::Desktop(400) {
155                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_cube_map_array.txt
156                writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
157            }
158        }
159
160        if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && version.is_es() {
161            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_texture_storage_multisample_2d_array.txt
162            writeln!(
163                out,
164                "#extension GL_OES_texture_storage_multisample_2d_array : require"
165            )?;
166        }
167
168        if self.0.contains(Features::ARRAY_OF_ARRAYS) && version < Version::Desktop(430) {
169            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_arrays_of_arrays.txt
170            writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
171        }
172
173        if self.0.contains(Features::IMAGE_LOAD_STORE) {
174            if self.0.contains(Features::FULL_IMAGE_FORMATS) && version.is_es() {
175                // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt
176                writeln!(out, "#extension GL_NV_image_formats : require")?;
177            }
178
179            if version < Version::Desktop(420) {
180                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_image_load_store.txt
181                writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
182            }
183        }
184
185        if self.0.contains(Features::CONSERVATIVE_DEPTH) {
186            if version.is_es() {
187                // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt
188                writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
189            }
190
191            if version < Version::Desktop(420) {
192                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_conservative_depth.txt
193                writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
194            }
195        }
196
197        if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
198            && version.is_es()
199        {
200            // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_clip_cull_distance.txt
201            writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
202        }
203
204        if self.0.contains(Features::SAMPLE_VARIABLES) && version.is_es() {
205            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_variables.txt
206            writeln!(out, "#extension GL_OES_sample_variables : require")?;
207        }
208
209        if self.0.contains(Features::SAMPLE_VARIABLES) && version.is_es() {
210            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_variables.txt
211            writeln!(out, "#extension GL_OES_sample_variables : require")?;
212        }
213
214        if self.0.contains(Features::MULTI_VIEW) {
215            if let Version::Embedded { is_webgl: true, .. } = version {
216                // https://www.khronos.org/registry/OpenGL/extensions/OVR/OVR_multiview2.txt
217                writeln!(out, "#extension GL_OVR_multiview2 : require")?;
218            } else {
219                // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_multiview.txt
220                writeln!(out, "#extension GL_EXT_multiview : require")?;
221            }
222        }
223
224        if self.0.contains(Features::TEXTURE_SAMPLES) {
225            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_texture_image_samples.txt
226            writeln!(
227                out,
228                "#extension GL_ARB_shader_texture_image_samples : require"
229            )?;
230        }
231
232        if self.0.contains(Features::TEXTURE_LEVELS) && version < Version::Desktop(430) {
233            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt
234            writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
235        }
236
237        Ok(())
238    }
239}
240
241impl<'a, W> Writer<'a, W> {
242    /// Helper method that searches the module for all the needed [`Features`]
243    ///
244    /// # Errors
245    /// If the version doesn't support any of the needed [`Features`] a
246    /// [`Error::MissingFeatures`](super::Error::MissingFeatures) will be returned
247    pub(super) fn collect_required_features(&mut self) -> BackendResult {
248        let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
249
250        if let Some(depth_test) = self.entry_point.early_depth_test {
251            // If IMAGE_LOAD_STORE is supported for this version of GLSL
252            if self.options.version.supports_early_depth_test() {
253                self.features.request(Features::IMAGE_LOAD_STORE);
254            }
255
256            if depth_test.conservative.is_some() {
257                self.features.request(Features::CONSERVATIVE_DEPTH);
258            }
259        }
260
261        for arg in self.entry_point.function.arguments.iter() {
262            self.varying_required_features(arg.binding.as_ref(), arg.ty);
263        }
264        if let Some(ref result) = self.entry_point.function.result {
265            self.varying_required_features(result.binding.as_ref(), result.ty);
266        }
267
268        if let ShaderStage::Compute = self.entry_point.stage {
269            self.features.request(Features::COMPUTE_SHADER)
270        }
271
272        if self.multiview.is_some() {
273            self.features.request(Features::MULTI_VIEW);
274        }
275
276        for (ty_handle, ty) in self.module.types.iter() {
277            match ty.inner {
278                TypeInner::Scalar { kind, width } => self.scalar_required_features(kind, width),
279                TypeInner::Vector { kind, width, .. } => self.scalar_required_features(kind, width),
280                TypeInner::Matrix { width, .. } => {
281                    self.scalar_required_features(ScalarKind::Float, width)
282                }
283                TypeInner::Array { base, size, .. } => {
284                    if let TypeInner::Array { .. } = self.module.types[base].inner {
285                        self.features.request(Features::ARRAY_OF_ARRAYS)
286                    }
287
288                    // If the array is dynamically sized
289                    if size == crate::ArraySize::Dynamic {
290                        let mut is_used = false;
291
292                        // Check if this type is used in a global that is needed by the current entrypoint
293                        for (global_handle, global) in self.module.global_variables.iter() {
294                            // Skip unused globals
295                            if ep_info[global_handle].is_empty() {
296                                continue;
297                            }
298
299                            // If this array is the type of a global, then this array is used
300                            if global.ty == ty_handle {
301                                is_used = true;
302                                break;
303                            }
304
305                            // If the type of this global is a struct
306                            if let crate::TypeInner::Struct { ref members, .. } =
307                                self.module.types[global.ty].inner
308                            {
309                                // Check the last element of the struct to see if it's type uses
310                                // this array
311                                if let Some(last) = members.last() {
312                                    if last.ty == ty_handle {
313                                        is_used = true;
314                                        break;
315                                    }
316                                }
317                            }
318                        }
319
320                        // If this dynamically size array is used, we need dynamic array size support
321                        if is_used {
322                            self.features.request(Features::DYNAMIC_ARRAY_SIZE);
323                        }
324                    }
325                }
326                TypeInner::Image {
327                    dim,
328                    arrayed,
329                    class,
330                } => {
331                    if arrayed && dim == ImageDimension::Cube {
332                        self.features.request(Features::CUBE_TEXTURES_ARRAY)
333                    }
334
335                    match class {
336                        ImageClass::Sampled { multi: true, .. }
337                        | ImageClass::Depth { multi: true } => {
338                            self.features.request(Features::MULTISAMPLED_TEXTURES);
339                            if arrayed {
340                                self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
341                            }
342                        }
343                        ImageClass::Storage { format, .. } => match format {
344                            StorageFormat::R8Unorm
345                            | StorageFormat::R8Snorm
346                            | StorageFormat::R8Uint
347                            | StorageFormat::R8Sint
348                            | StorageFormat::R16Uint
349                            | StorageFormat::R16Sint
350                            | StorageFormat::R16Float
351                            | StorageFormat::Rg8Unorm
352                            | StorageFormat::Rg8Snorm
353                            | StorageFormat::Rg8Uint
354                            | StorageFormat::Rg8Sint
355                            | StorageFormat::Rg16Uint
356                            | StorageFormat::Rg16Sint
357                            | StorageFormat::Rg16Float
358                            | StorageFormat::Rgb10a2Unorm
359                            | StorageFormat::Rg11b10Float
360                            | StorageFormat::Rg32Uint
361                            | StorageFormat::Rg32Sint
362                            | StorageFormat::Rg32Float => {
363                                self.features.request(Features::FULL_IMAGE_FORMATS)
364                            }
365                            _ => {}
366                        },
367                        ImageClass::Sampled { multi: false, .. }
368                        | ImageClass::Depth { multi: false } => {}
369                    }
370                }
371                _ => {}
372            }
373        }
374
375        let mut push_constant_used = false;
376
377        for (handle, global) in self.module.global_variables.iter() {
378            if ep_info[handle].is_empty() {
379                continue;
380            }
381            match global.space {
382                AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
383                AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
384                AddressSpace::PushConstant => {
385                    if push_constant_used {
386                        return Err(Error::MultiplePushConstants);
387                    }
388                    push_constant_used = true;
389                }
390                _ => {}
391            }
392        }
393
394        // We will need to pass some of the members to a closure, so we need
395        // to separate them otherwise the borrow checker will complain, this
396        // shouldn't be needed in rust 2021
397        let &mut Self {
398            module,
399            info,
400            ref mut features,
401            entry_point,
402            entry_point_idx,
403            ref policies,
404            ..
405        } = self;
406
407        // Loop trough all expressions in both functions and the entry point
408        // to check for needed features
409        for (expressions, info) in module
410            .functions
411            .iter()
412            .map(|(h, f)| (&f.expressions, &info[h]))
413            .chain(std::iter::once((
414                &entry_point.function.expressions,
415                info.get_entry_point(entry_point_idx as usize),
416            )))
417        {
418            for (_, expr) in expressions.iter() {
419                match *expr {
420                // Check for queries that neeed aditonal features
421                Expression::ImageQuery {
422                    image,
423                    query,
424                    ..
425                } => match query {
426                    // Storage images use `imageSize` which is only available
427                    // in glsl > 420
428                    //
429                    // layers queries are also implemented as size queries
430                    crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
431                        if let TypeInner::Image {
432                            class: crate::ImageClass::Storage { .. }, ..
433                        } = *info[image].ty.inner_with(&module.types) {
434                            features.request(Features::IMAGE_SIZE)
435                        }
436                    },
437                    crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
438                    crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
439                }
440                ,
441                // Check for image loads that needs bound checking on the sample
442                // or level argument since this requires a feature
443                Expression::ImageLoad {
444                    sample, level, ..
445                } => {
446                    if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
447                        if sample.is_some() {
448                            features.request(Features::TEXTURE_SAMPLES)
449                        }
450
451                        if level.is_some() {
452                            features.request(Features::TEXTURE_LEVELS)
453                        }
454                    }
455                }
456                _ => {}
457            }
458            }
459        }
460
461        self.features.check_availability(self.options.version)
462    }
463
464    /// Helper method that checks the [`Features`] needed by a scalar
465    fn scalar_required_features(&mut self, kind: ScalarKind, width: Bytes) {
466        if kind == ScalarKind::Float && width == 8 {
467            self.features.request(Features::DOUBLE_TYPE);
468        }
469    }
470
471    fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
472        match self.module.types[ty].inner {
473            crate::TypeInner::Struct { ref members, .. } => {
474                for member in members {
475                    self.varying_required_features(member.binding.as_ref(), member.ty);
476                }
477            }
478            _ => {
479                if let Some(binding) = binding {
480                    match *binding {
481                        Binding::BuiltIn(built_in) => match built_in {
482                            crate::BuiltIn::ClipDistance => {
483                                self.features.request(Features::CLIP_DISTANCE)
484                            }
485                            crate::BuiltIn::CullDistance => {
486                                self.features.request(Features::CULL_DISTANCE)
487                            }
488                            crate::BuiltIn::SampleIndex => {
489                                self.features.request(Features::SAMPLE_VARIABLES)
490                            }
491                            crate::BuiltIn::ViewIndex => {
492                                self.features.request(Features::MULTI_VIEW)
493                            }
494                            _ => {}
495                        },
496                        Binding::Location {
497                            location: _,
498                            interpolation,
499                            sampling,
500                        } => {
501                            if interpolation == Some(Interpolation::Linear) {
502                                self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
503                            }
504                            if sampling == Some(Sampling::Sample) {
505                                self.features.request(Features::SAMPLE_QUALIFIER);
506                            }
507                        }
508                    }
509                }
510            }
511        }
512    }
513}