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 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
12 pub struct Features: u32 {
13 const BUFFER_STORAGE = 1;
15 const ARRAY_OF_ARRAYS = 1 << 1;
16 const DOUBLE_TYPE = 1 << 2;
18 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 const IMAGE_LOAD_STORE = 1 << 8;
26 const CONSERVATIVE_DEPTH = 1 << 9;
27 const NOPERSPECTIVE_QUALIFIER = 1 << 11;
31 const SAMPLE_QUALIFIER = 1 << 12;
32 const CLIP_DISTANCE = 1 << 13;
33 const CULL_DISTANCE = 1 << 14;
34 const SAMPLE_VARIABLES = 1 << 15;
36 const DYNAMIC_ARRAY_SIZE = 1 << 16;
38 const MULTI_VIEW = 1 << 17;
39 const TEXTURE_SAMPLES = 1 << 18;
41 const TEXTURE_LEVELS = 1 << 19;
43 const IMAGE_SIZE = 1 << 20;
45 const DUAL_SOURCE_BLENDING = 1 << 21;
47 const INSTANCE_INDEX = 1 << 22;
51 const TEXTURE_SHADOW_LOD = 1 << 23;
53 const SUBGROUP_OPERATIONS = 1 << 24;
55 const TEXTURE_ATOMICS = 1 << 25;
57 }
58}
59
60pub struct FeaturesManager(Features);
65
66impl FeaturesManager {
67 pub const fn new() -> Self {
69 Self(Features::empty())
70 }
71
72 pub fn request(&mut self, features: Features) {
74 self.0 |= features
75 }
76
77 pub fn contains(&mut self, features: Features) -> bool {
79 self.0.contains(features)
80 }
81
82 pub fn check_availability(&self, version: Version) -> BackendResult {
85 let mut missing = Features::empty();
87
88 macro_rules! check_feature {
90 ($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 ($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 );
120 check_feature!(CULL_DISTANCE, 450, 300 );
121 check_feature!(SAMPLE_VARIABLES, 400, 300);
122 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
123 check_feature!(DUAL_SOURCE_BLENDING, 330, 300 );
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 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 if missing.is_empty() {
140 Ok(())
141 } else {
142 Err(Error::MissingFeatures(missing))
143 }
144 }
145
146 pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
152 if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
153 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
155 }
156
157 if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
158 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 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 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
174 } else if options.version < Version::Desktop(400) {
175 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 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 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 writeln!(out, "#extension GL_NV_image_formats : require")?;
197 }
198
199 if options.version < Version::Desktop(420) {
200 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 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
209 }
210
211 if options.version < Version::Desktop(420) {
212 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 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
222 }
223
224 if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
225 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 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
233 } else {
234 writeln!(out, "#extension GL_EXT_multiview : require")?;
236 }
237 }
238
239 if self.0.contains(Features::TEXTURE_SAMPLES) {
240 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 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
250 }
251 if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
252 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 writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
260 }
261 }
262
263 if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
264 writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
266 }
267
268 if self.0.contains(Features::SUBGROUP_OPERATIONS) {
269 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 writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
287 }
288
289 Ok(())
290 }
291}
292
293impl<W> Writer<'_, W> {
294 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 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 size == crate::ArraySize::Dynamic {
340 let mut is_used = false;
341
342 for (global_handle, global) in self.module.global_variables.iter() {
344 if ep_info[global_handle].is_empty() {
346 continue;
347 }
348
349 if global.ty == ty_handle {
351 is_used = true;
352 break;
353 }
354
355 if let TypeInner::Struct { ref members, .. } =
357 self.module.types[global.ty].inner
358 {
359 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 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 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 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 Expression::ImageQuery {
474 image,
475 query,
476 ..
477 } => match query {
478 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 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 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 ext_used |= (array2d || cube && arrayed) && bias;
533
534 ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
537
538 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 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}