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