naga/back/glsl/
features.rs

1use core::fmt::Write;
2
3use super::{BackendResult, Error, Version, Writer};
4use crate::{
5    back::glsl::{Options, WriterFlags},
6    AddressSpace, Binding, Expression, Handle, ImageClass, ImageDimension, Interpolation,
7    SampleLevel, Sampling, Scalar, ScalarKind, ShaderStage, StorageFormat, Type, TypeInner,
8};
9
10bitflags::bitflags! {
11    /// Structure used to encode additions to GLSL that aren't supported by all versions.
12    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
13    pub struct Features: u32 {
14        /// Buffer address space support.
15        const BUFFER_STORAGE = 1;
16        const ARRAY_OF_ARRAYS = 1 << 1;
17        /// 8 byte floats.
18        const DOUBLE_TYPE = 1 << 2;
19        /// More image formats.
20        const FULL_IMAGE_FORMATS = 1 << 3;
21        const MULTISAMPLED_TEXTURES = 1 << 4;
22        const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5;
23        const CUBE_TEXTURES_ARRAY = 1 << 6;
24        const COMPUTE_SHADER = 1 << 7;
25        /// Image load and early depth tests.
26        const IMAGE_LOAD_STORE = 1 << 8;
27        const CONSERVATIVE_DEPTH = 1 << 9;
28        /// Interpolation and auxiliary qualifiers.
29        ///
30        /// Perspective, Flat, and Centroid are available in all GLSL versions we support.
31        const NOPERSPECTIVE_QUALIFIER = 1 << 11;
32        const SAMPLE_QUALIFIER = 1 << 12;
33        const CLIP_DISTANCE = 1 << 13;
34        const CULL_DISTANCE = 1 << 14;
35        /// Sample ID.
36        const SAMPLE_VARIABLES = 1 << 15;
37        /// Arrays with a dynamic length.
38        const DYNAMIC_ARRAY_SIZE = 1 << 16;
39        const MULTI_VIEW = 1 << 17;
40        /// Texture samples query
41        const TEXTURE_SAMPLES = 1 << 18;
42        /// Texture levels query
43        const TEXTURE_LEVELS = 1 << 19;
44        /// Image size query
45        const IMAGE_SIZE = 1 << 20;
46        /// Dual source blending
47        const DUAL_SOURCE_BLENDING = 1 << 21;
48        /// Instance index
49        ///
50        /// We can always support this, either through the language or a polyfill
51        const INSTANCE_INDEX = 1 << 22;
52        /// Sample specific LODs of cube / array shadow textures
53        const TEXTURE_SHADOW_LOD = 1 << 23;
54        /// Subgroup operations
55        const SUBGROUP_OPERATIONS = 1 << 24;
56        /// Image atomics
57        const TEXTURE_ATOMICS = 1 << 25;
58        /// Image atomics
59        const SHADER_BARYCENTRICS = 1 << 26;
60        /// Primitive index builtin
61        const PRIMITIVE_INDEX = 1 << 27;
62    }
63}
64
65impl core::fmt::Display for Features {
66    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
67        // `Debug` would print this as `Features(A | B)`; we only want `A | B`.
68        bitflags::parser::to_writer(self, f)
69    }
70}
71
72/// Helper structure used to store the required [`Features`] needed to output a
73/// [`Module`](crate::Module)
74///
75/// Provides helper methods to check for availability and writing required extensions
76pub(crate) struct FeaturesManager(Features);
77
78impl FeaturesManager {
79    /// Creates a new [`FeaturesManager`] instance
80    pub const fn new() -> Self {
81        Self(Features::empty())
82    }
83
84    /// Adds to the list of required [`Features`]
85    pub fn request(&mut self, features: Features) {
86        self.0 |= features
87    }
88
89    /// Checks if the list of features [`Features`] contains the specified [`Features`]
90    pub const fn contains(&mut self, features: Features) -> bool {
91        self.0.contains(features)
92    }
93
94    /// Checks that all required [`Features`] are available for the specified
95    /// [`Version`] otherwise returns an [`Error::MissingFeatures`].
96    pub fn check_availability(&self, version: Version) -> BackendResult {
97        // Will store all the features that are unavailable
98        let mut missing = Features::empty();
99
100        // Helper macro to check for feature availability
101        macro_rules! check_feature {
102            // Used when only core glsl supports the feature
103            ($feature:ident, $core:literal) => {
104                if self.0.contains(Features::$feature)
105                    && (version < Version::Desktop($core) || version.is_es())
106                {
107                    missing |= Features::$feature;
108                }
109            };
110            // Used when both core and es support the feature
111            ($feature:ident, $core:literal, $es:literal) => {
112                if self.0.contains(Features::$feature)
113                    && (version < Version::Desktop($core) || version < Version::new_gles($es))
114                {
115                    missing |= Features::$feature;
116                }
117            };
118        }
119
120        check_feature!(COMPUTE_SHADER, 420, 310);
121        check_feature!(BUFFER_STORAGE, 400, 310);
122        check_feature!(DOUBLE_TYPE, 150);
123        check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
124        check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
125        check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
126        check_feature!(ARRAY_OF_ARRAYS, 120, 310);
127        check_feature!(IMAGE_LOAD_STORE, 130, 310);
128        check_feature!(CONSERVATIVE_DEPTH, 130, 300);
129        check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
130        check_feature!(SAMPLE_QUALIFIER, 400, 320);
131        check_feature!(CLIP_DISTANCE, 130, 300 /* with extension */);
132        check_feature!(CULL_DISTANCE, 450, 300 /* with extension */);
133        check_feature!(SAMPLE_VARIABLES, 400, 300);
134        check_feature!(DYNAMIC_ARRAY_SIZE, 400 /* with extension */, 310);
135        check_feature!(DUAL_SOURCE_BLENDING, 330, 300 /* with extension */);
136        check_feature!(SUBGROUP_OPERATIONS, 430, 310);
137        check_feature!(TEXTURE_ATOMICS, 420, 310);
138        match version {
139            Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
140            _ => check_feature!(MULTI_VIEW, 140, 310),
141        };
142        // Only available on glsl core, this means that opengl es can't query the number
143        // of samples nor levels in a image and neither do bound checks on the sample nor
144        // the level argument of texelFetch
145        check_feature!(TEXTURE_SAMPLES, 150);
146        check_feature!(TEXTURE_LEVELS, 130);
147        check_feature!(IMAGE_SIZE, 430, 310);
148        check_feature!(TEXTURE_SHADOW_LOD, 200, 300);
149
150        // Return an error if there are missing features
151        if missing.is_empty() {
152            Ok(())
153        } else {
154            Err(Error::MissingFeatures(missing, version))
155        }
156    }
157
158    /// Helper method used to write all needed extensions
159    ///
160    /// # Notes
161    /// This won't check for feature availability so it might output extensions that aren't even
162    /// supported.[`check_availability`](Self::check_availability) will check feature availability
163    pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
164        if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
165            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_compute_shader.txt
166            writeln!(out, "#extension GL_ARB_compute_shader : require")?;
167        }
168
169        if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
170            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_storage_buffer_object.txt
171            writeln!(
172                out,
173                "#extension GL_ARB_shader_storage_buffer_object : require"
174            )?;
175        }
176
177        if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) {
178            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_gpu_shader_fp64.txt
179            writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
180        }
181
182        if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
183            if options.version.is_es() {
184                // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_texture_cube_map_array.txt
185                writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
186            } else if options.version < Version::Desktop(400) {
187                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_cube_map_array.txt
188                writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
189            }
190        }
191
192        if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() {
193            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_texture_storage_multisample_2d_array.txt
194            writeln!(
195                out,
196                "#extension GL_OES_texture_storage_multisample_2d_array : require"
197            )?;
198        }
199
200        if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) {
201            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_arrays_of_arrays.txt
202            writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
203        }
204
205        if self.0.contains(Features::IMAGE_LOAD_STORE) {
206            if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() {
207                // https://www.khronos.org/registry/OpenGL/extensions/NV/NV_image_formats.txt
208                writeln!(out, "#extension GL_NV_image_formats : require")?;
209            }
210
211            if options.version < Version::Desktop(420) {
212                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_image_load_store.txt
213                writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
214            }
215        }
216
217        if self.0.contains(Features::CONSERVATIVE_DEPTH) {
218            if options.version.is_es() {
219                // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_conservative_depth.txt
220                writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
221            }
222
223            if options.version < Version::Desktop(420) {
224                // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_conservative_depth.txt
225                writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
226            }
227        }
228
229        if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
230            && options.version.is_es()
231        {
232            // https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_clip_cull_distance.txt
233            writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
234        }
235
236        if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
237            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_sample_variables.txt
238            writeln!(out, "#extension GL_OES_sample_variables : require")?;
239        }
240
241        if self.0.contains(Features::MULTI_VIEW) {
242            if let Version::Embedded { is_webgl: true, .. } = options.version {
243                // https://www.khronos.org/registry/OpenGL/extensions/OVR/OVR_multiview2.txt
244                writeln!(out, "#extension GL_OVR_multiview2 : require")?;
245            } else {
246                // https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GL_EXT_multiview.txt
247                writeln!(out, "#extension GL_EXT_multiview : require")?;
248            }
249        }
250
251        if self.0.contains(Features::TEXTURE_SAMPLES) {
252            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_shader_texture_image_samples.txt
253            writeln!(
254                out,
255                "#extension GL_ARB_shader_texture_image_samples : require"
256            )?;
257        }
258
259        if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) {
260            // https://www.khronos.org/registry/OpenGL/extensions/ARB/ARB_texture_query_levels.txt
261            writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
262        }
263        if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
264            // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_blend_func_extended.txt
265            writeln!(out, "#extension GL_EXT_blend_func_extended : require")?;
266        }
267
268        if self.0.contains(Features::INSTANCE_INDEX) {
269            if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) {
270                // https://registry.khronos.org/OpenGL/extensions/ARB/ARB_shader_draw_parameters.txt
271                writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
272            }
273        }
274
275        if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
276            // https://registry.khronos.org/OpenGL/extensions/EXT/EXT_texture_shadow_lod.txt
277            writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
278        }
279
280        if self.0.contains(Features::SUBGROUP_OPERATIONS) {
281            // https://registry.khronos.org/OpenGL/extensions/KHR/KHR_shader_subgroup.txt
282            writeln!(out, "#extension GL_KHR_shader_subgroup_basic : require")?;
283            writeln!(out, "#extension GL_KHR_shader_subgroup_vote : require")?;
284            writeln!(
285                out,
286                "#extension GL_KHR_shader_subgroup_arithmetic : require"
287            )?;
288            writeln!(out, "#extension GL_KHR_shader_subgroup_ballot : require")?;
289            writeln!(out, "#extension GL_KHR_shader_subgroup_shuffle : require")?;
290            writeln!(
291                out,
292                "#extension GL_KHR_shader_subgroup_shuffle_relative : require"
293            )?;
294            writeln!(out, "#extension GL_KHR_shader_subgroup_quad : require")?;
295        }
296
297        if self.0.contains(Features::TEXTURE_ATOMICS) {
298            // https://www.khronos.org/registry/OpenGL/extensions/OES/OES_shader_image_atomic.txt
299            writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
300        }
301
302        if self.0.contains(Features::SHADER_BARYCENTRICS) {
303            // https://github.com/KhronosGroup/GLSL/blob/main/extensions/ext/GLSL_EXT_fragment_shader_barycentric.txt
304            writeln!(
305                out,
306                "#extension GL_EXT_fragment_shader_barycentric : require"
307            )?;
308        }
309
310        if self.0.contains(Features::PRIMITIVE_INDEX) {
311            match options.version {
312                Version::Embedded { version, .. } if version < 320 => {
313                    writeln!(out, "#extension GL_OES_geometry_shader : require")?;
314                }
315                Version::Desktop(version) if version < 150 => {
316                    writeln!(out, "#extension GL_ARB_geometry_shader4 : require")?;
317                }
318                _ => (),
319            }
320        }
321
322        Ok(())
323    }
324}
325
326impl<W> Writer<'_, W> {
327    /// Helper method that searches the module for all the needed [`Features`]
328    ///
329    /// # Errors
330    /// If the version doesn't support any of the needed [`Features`] a
331    /// [`Error::MissingFeatures`] will be returned
332    pub(super) fn collect_required_features(&mut self) -> BackendResult {
333        let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
334
335        if let Some(early_depth_test) = self.entry_point.early_depth_test {
336            match early_depth_test {
337                crate::EarlyDepthTest::Force => {
338                    if self.options.version.supports_early_depth_test() {
339                        self.features.request(Features::IMAGE_LOAD_STORE);
340                    }
341                }
342                crate::EarlyDepthTest::Allow { .. } => {
343                    self.features.request(Features::CONSERVATIVE_DEPTH);
344                }
345            }
346        }
347
348        for arg in self.entry_point.function.arguments.iter() {
349            self.varying_required_features(arg.binding.as_ref(), arg.ty);
350        }
351        if let Some(ref result) = self.entry_point.function.result {
352            self.varying_required_features(result.binding.as_ref(), result.ty);
353        }
354
355        if let ShaderStage::Compute = self.entry_point.stage {
356            self.features.request(Features::COMPUTE_SHADER)
357        }
358
359        if self.multiview.is_some() {
360            self.features.request(Features::MULTI_VIEW);
361        }
362
363        for (ty_handle, ty) in self.module.types.iter() {
364            match ty.inner {
365                TypeInner::Scalar(scalar)
366                | TypeInner::Vector { scalar, .. }
367                | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar),
368                TypeInner::Array { base, size, .. } => {
369                    if let TypeInner::Array { .. } = self.module.types[base].inner {
370                        self.features.request(Features::ARRAY_OF_ARRAYS)
371                    }
372
373                    // If the array is dynamically sized
374                    if size == crate::ArraySize::Dynamic {
375                        let mut is_used = false;
376
377                        // Check if this type is used in a global that is needed by the current entrypoint
378                        for (global_handle, global) in self.module.global_variables.iter() {
379                            // Skip unused globals
380                            if ep_info[global_handle].is_empty() {
381                                continue;
382                            }
383
384                            // If this array is the type of a global, then this array is used
385                            if global.ty == ty_handle {
386                                is_used = true;
387                                break;
388                            }
389
390                            // If the type of this global is a struct
391                            if let TypeInner::Struct { ref members, .. } =
392                                self.module.types[global.ty].inner
393                            {
394                                // Check the last element of the struct to see if it's type uses
395                                // this array
396                                if let Some(last) = members.last() {
397                                    if last.ty == ty_handle {
398                                        is_used = true;
399                                        break;
400                                    }
401                                }
402                            }
403                        }
404
405                        // If this dynamically size array is used, we need dynamic array size support
406                        if is_used {
407                            self.features.request(Features::DYNAMIC_ARRAY_SIZE);
408                        }
409                    }
410                }
411                TypeInner::Image {
412                    dim,
413                    arrayed,
414                    class,
415                } => {
416                    if arrayed && dim == ImageDimension::Cube {
417                        self.features.request(Features::CUBE_TEXTURES_ARRAY)
418                    }
419
420                    match class {
421                        ImageClass::Sampled { multi: true, .. }
422                        | ImageClass::Depth { multi: true } => {
423                            self.features.request(Features::MULTISAMPLED_TEXTURES);
424                            if arrayed {
425                                self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
426                            }
427                        }
428                        ImageClass::Storage { format, .. } => {
429                            // Storage images require `image_load_store`; the extension write
430                            // (and the `NV_image_formats` write nested under it) is otherwise
431                            // never emitted on targets that need it.
432                            self.features.request(Features::IMAGE_LOAD_STORE);
433                            match format {
434                                StorageFormat::R8Unorm
435                                | StorageFormat::R8Snorm
436                                | StorageFormat::R8Uint
437                                | StorageFormat::R8Sint
438                                | StorageFormat::R16Uint
439                                | StorageFormat::R16Sint
440                                | StorageFormat::R16Float
441                                | StorageFormat::R16Unorm
442                                | StorageFormat::R16Snorm
443                                | StorageFormat::Rg8Unorm
444                                | StorageFormat::Rg8Snorm
445                                | StorageFormat::Rg8Uint
446                                | StorageFormat::Rg8Sint
447                                | StorageFormat::Rg16Uint
448                                | StorageFormat::Rg16Sint
449                                | StorageFormat::Rg16Float
450                                | StorageFormat::Rg16Unorm
451                                | StorageFormat::Rg16Snorm
452                                | StorageFormat::Rgba16Unorm
453                                | StorageFormat::Rgba16Snorm
454                                | StorageFormat::Rgb10a2Uint
455                                | StorageFormat::Rgb10a2Unorm
456                                | StorageFormat::Rg11b10Ufloat
457                                | StorageFormat::R64Uint
458                                | StorageFormat::Rg32Uint
459                                | StorageFormat::Rg32Sint
460                                | StorageFormat::Rg32Float => {
461                                    self.features.request(Features::FULL_IMAGE_FORMATS)
462                                }
463                                _ => {}
464                            }
465                        }
466                        ImageClass::Sampled { multi: false, .. }
467                        | ImageClass::Depth { multi: false }
468                        | ImageClass::External => {}
469                    }
470                }
471                _ => {}
472            }
473        }
474
475        let mut immediates_used = false;
476
477        for (handle, global) in self.module.global_variables.iter() {
478            if ep_info[handle].is_empty() {
479                continue;
480            }
481            match global.space {
482                AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
483                AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
484                AddressSpace::Immediate => {
485                    if immediates_used {
486                        return Err(Error::MultipleImmediateData);
487                    }
488                    immediates_used = true;
489                }
490                _ => {}
491            }
492        }
493
494        // We will need to pass some of the members to a closure, so we need
495        // to separate them otherwise the borrow checker will complain, this
496        // shouldn't be needed in rust 2021
497        let &mut Self {
498            module,
499            info,
500            ref mut features,
501            entry_point,
502            entry_point_idx,
503            ref policies,
504            ..
505        } = self;
506
507        // Loop through all expressions in both functions and the entry point
508        // to check for needed features
509        for (expressions, info) in module
510            .functions
511            .iter()
512            .map(|(h, f)| (&f.expressions, &info[h]))
513            .chain(core::iter::once((
514                &entry_point.function.expressions,
515                info.get_entry_point(entry_point_idx as usize),
516            )))
517        {
518            for (_, expr) in expressions.iter() {
519                match *expr {
520                // Check for queries that need aditonal features
521                Expression::ImageQuery {
522                    image,
523                    query,
524                    ..
525                } => match query {
526                    // Storage images use `imageSize` which is only available
527                    // in glsl > 420
528                    //
529                    // layers queries are also implemented as size queries
530                    crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
531                        if let TypeInner::Image {
532                            class: ImageClass::Storage { .. }, ..
533                        } = *info[image].ty.inner_with(&module.types) {
534                            features.request(Features::IMAGE_SIZE)
535                        }
536                    },
537                    crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
538                    crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
539                }
540                ,
541                // Check for image loads that needs bound checking on the sample
542                // or level argument since this requires a feature
543                Expression::ImageLoad {
544                    sample, level, ..
545                } => {
546                    if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
547                        if sample.is_some() {
548                            features.request(Features::TEXTURE_SAMPLES)
549                        }
550
551                        if level.is_some() {
552                            features.request(Features::TEXTURE_LEVELS)
553                        }
554                    }
555                }
556                Expression::ImageSample { image, level, offset, .. } => {
557                    if let TypeInner::Image {
558                        dim,
559                        arrayed,
560                        class: ImageClass::Depth { .. },
561                    } = *info[image].ty.inner_with(&module.types) {
562                        let lod = matches!(level, SampleLevel::Zero | SampleLevel::Exact(_));
563                        let bias = matches!(level, SampleLevel::Bias(_));
564                        let auto = matches!(level, SampleLevel::Auto);
565                        let cube = dim == ImageDimension::Cube;
566                        let array2d = dim == ImageDimension::D2 && arrayed;
567                        let gles = self.options.version.is_es();
568
569                        // We have a workaround of using `textureGrad` instead of `textureLod` if the LOD is zero,
570                        // so we don't *need* this extension for those cases.
571                        // But if we're explicitly allowed to use the extension (`WriterFlags::TEXTURE_SHADOW_LOD`),
572                        // we always use it instead of the workaround.
573                        let grad_workaround_applicable = (array2d || (cube && !arrayed)) && level == SampleLevel::Zero;
574                        let prefer_grad_workaround = grad_workaround_applicable && !self.options.writer_flags.contains(WriterFlags::TEXTURE_SHADOW_LOD);
575
576                        let mut ext_used = false;
577
578                        // float texture(sampler2DArrayShadow sampler, vec4 P [, float bias])
579                        // float texture(samplerCubeArrayShadow sampler, vec4 P, float compare [, float bias])
580                        ext_used |= (array2d || cube && arrayed) && bias;
581
582                        // The non `bias` version of this was standardized in GL 4.3, but never in GLES.
583                        // float textureOffset(sampler2DArrayShadow sampler, vec4 P, ivec2 offset [, float bias])
584                        ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
585
586                        // float textureLod(sampler2DArrayShadow sampler, vec4 P, float lod)
587                        // float textureLodOffset(sampler2DArrayShadow sampler, vec4 P, float lod, ivec2 offset)
588                        // float textureLod(samplerCubeShadow sampler, vec4 P, float lod)
589                        // float textureLod(samplerCubeArrayShadow sampler, vec4 P, float compare, float lod)
590                        ext_used |= (cube || array2d) && lod && !prefer_grad_workaround;
591
592                        if ext_used {
593                            features.request(Features::TEXTURE_SHADOW_LOD);
594                        }
595                    }
596                }
597                Expression::SubgroupBallotResult |
598                Expression::SubgroupOperationResult { .. } => {
599                    features.request(Features::SUBGROUP_OPERATIONS)
600                }
601                _ => {}
602            }
603            }
604        }
605
606        for blocks in module
607            .functions
608            .iter()
609            .map(|(_, f)| &f.body)
610            .chain(core::iter::once(&entry_point.function.body))
611        {
612            for (stmt, _) in blocks.span_iter() {
613                match *stmt {
614                    crate::Statement::ImageAtomic { .. } => {
615                        features.request(Features::TEXTURE_ATOMICS)
616                    }
617                    _ => {}
618                }
619            }
620        }
621
622        self.features.check_availability(self.options.version)
623    }
624
625    /// Helper method that checks the [`Features`] needed by a scalar
626    fn scalar_required_features(&mut self, scalar: Scalar) {
627        if scalar.kind == ScalarKind::Float && scalar.width == 8 {
628            self.features.request(Features::DOUBLE_TYPE);
629        }
630    }
631
632    fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
633        if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner {
634            for member in members {
635                self.varying_required_features(member.binding.as_ref(), member.ty);
636            }
637        } else if let Some(binding) = binding {
638            match *binding {
639                Binding::BuiltIn(built_in) => match built_in {
640                    crate::BuiltIn::ClipDistances => self.features.request(Features::CLIP_DISTANCE),
641                    crate::BuiltIn::CullDistance => self.features.request(Features::CULL_DISTANCE),
642                    crate::BuiltIn::SampleIndex => {
643                        self.features.request(Features::SAMPLE_VARIABLES)
644                    }
645                    crate::BuiltIn::ViewIndex => self.features.request(Features::MULTI_VIEW),
646                    crate::BuiltIn::InstanceIndex | crate::BuiltIn::DrawIndex => {
647                        self.features.request(Features::INSTANCE_INDEX)
648                    }
649                    crate::BuiltIn::Barycentric { .. } => {
650                        self.features.request(Features::SHADER_BARYCENTRICS)
651                    }
652                    crate::BuiltIn::PrimitiveIndex => {
653                        self.features.request(Features::PRIMITIVE_INDEX)
654                    }
655                    _ => {}
656                },
657                Binding::Location {
658                    location: _,
659                    interpolation,
660                    sampling,
661                    blend_src,
662                    per_primitive: _,
663                } => {
664                    if interpolation == Some(Interpolation::Linear) {
665                        self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
666                    }
667                    if sampling == Some(Sampling::Sample) {
668                        self.features.request(Features::SAMPLE_QUALIFIER);
669                    }
670                    if blend_src.is_some() {
671                        self.features.request(Features::DUAL_SOURCE_BLENDING);
672                    }
673                }
674            }
675        }
676    }
677}