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