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