naga/back/glsl/
features.rs

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