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