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 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
13 pub struct Features: u32 {
14 const BUFFER_STORAGE = 1;
16 const ARRAY_OF_ARRAYS = 1 << 1;
17 const DOUBLE_TYPE = 1 << 2;
19 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 const IMAGE_LOAD_STORE = 1 << 8;
27 const CONSERVATIVE_DEPTH = 1 << 9;
28 const NOPERSPECTIVE_QUALIFIER = 1 << 11;
32 const SAMPLE_QUALIFIER = 1 << 12;
33 const CLIP_DISTANCE = 1 << 13;
34 const CULL_DISTANCE = 1 << 14;
35 const SAMPLE_VARIABLES = 1 << 15;
37 const DYNAMIC_ARRAY_SIZE = 1 << 16;
39 const MULTI_VIEW = 1 << 17;
40 const TEXTURE_SAMPLES = 1 << 18;
42 const TEXTURE_LEVELS = 1 << 19;
44 const IMAGE_SIZE = 1 << 20;
46 const DUAL_SOURCE_BLENDING = 1 << 21;
48 const INSTANCE_INDEX = 1 << 22;
52 const TEXTURE_SHADOW_LOD = 1 << 23;
54 const SUBGROUP_OPERATIONS = 1 << 24;
56 const TEXTURE_ATOMICS = 1 << 25;
58 const SHADER_BARYCENTRICS = 1 << 26;
60 const PRIMITIVE_INDEX = 1 << 27;
62 }
63}
64
65pub(crate) struct FeaturesManager(Features);
70
71impl FeaturesManager {
72 pub const fn new() -> Self {
74 Self(Features::empty())
75 }
76
77 pub fn request(&mut self, features: Features) {
79 self.0 |= features
80 }
81
82 pub const fn contains(&mut self, features: Features) -> bool {
84 self.0.contains(features)
85 }
86
87 pub fn check_availability(&self, version: Version) -> BackendResult {
90 let mut missing = Features::empty();
92
93 macro_rules! check_feature {
95 ($feature:ident, $core:literal) => {
97 if self.0.contains(Features::$feature)
98 && (version < Version::Desktop($core) || version.is_es())
99 {
100 missing |= Features::$feature;
101 }
102 };
103 ($feature:ident, $core:literal, $es:literal) => {
105 if self.0.contains(Features::$feature)
106 && (version < Version::Desktop($core) || version < Version::new_gles($es))
107 {
108 missing |= Features::$feature;
109 }
110 };
111 }
112
113 check_feature!(COMPUTE_SHADER, 420, 310);
114 check_feature!(BUFFER_STORAGE, 400, 310);
115 check_feature!(DOUBLE_TYPE, 150);
116 check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
117 check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
118 check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
119 check_feature!(ARRAY_OF_ARRAYS, 120, 310);
120 check_feature!(IMAGE_LOAD_STORE, 130, 310);
121 check_feature!(CONSERVATIVE_DEPTH, 130, 300);
122 check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
123 check_feature!(SAMPLE_QUALIFIER, 400, 320);
124 check_feature!(CLIP_DISTANCE, 130, 300 );
125 check_feature!(CULL_DISTANCE, 450, 300 );
126 check_feature!(SAMPLE_VARIABLES, 400, 300);
127 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
128 check_feature!(DUAL_SOURCE_BLENDING, 330, 300 );
129 check_feature!(SUBGROUP_OPERATIONS, 430, 310);
130 check_feature!(TEXTURE_ATOMICS, 420, 310);
131 match version {
132 Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
133 _ => check_feature!(MULTI_VIEW, 140, 310),
134 };
135 check_feature!(TEXTURE_SAMPLES, 150);
139 check_feature!(TEXTURE_LEVELS, 130);
140 check_feature!(IMAGE_SIZE, 430, 310);
141 check_feature!(TEXTURE_SHADOW_LOD, 200, 300);
142
143 if missing.is_empty() {
145 Ok(())
146 } else {
147 Err(Error::MissingFeatures(missing))
148 }
149 }
150
151 pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
157 if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
158 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
160 }
161
162 if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
163 writeln!(
165 out,
166 "#extension GL_ARB_shader_storage_buffer_object : require"
167 )?;
168 }
169
170 if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) {
171 writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
173 }
174
175 if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
176 if options.version.is_es() {
177 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
179 } else if options.version < Version::Desktop(400) {
180 writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
182 }
183 }
184
185 if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() {
186 writeln!(
188 out,
189 "#extension GL_OES_texture_storage_multisample_2d_array : require"
190 )?;
191 }
192
193 if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) {
194 writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
196 }
197
198 if self.0.contains(Features::IMAGE_LOAD_STORE) {
199 if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() {
200 writeln!(out, "#extension GL_NV_image_formats : require")?;
202 }
203
204 if options.version < Version::Desktop(420) {
205 writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
207 }
208 }
209
210 if self.0.contains(Features::CONSERVATIVE_DEPTH) {
211 if options.version.is_es() {
212 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
214 }
215
216 if options.version < Version::Desktop(420) {
217 writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
219 }
220 }
221
222 if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
223 && options.version.is_es()
224 {
225 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
227 }
228
229 if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
230 writeln!(out, "#extension GL_OES_sample_variables : require")?;
232 }
233
234 if self.0.contains(Features::MULTI_VIEW) {
235 if let Version::Embedded { is_webgl: true, .. } = options.version {
236 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
238 } else {
239 writeln!(out, "#extension GL_EXT_multiview : require")?;
241 }
242 }
243
244 if self.0.contains(Features::TEXTURE_SAMPLES) {
245 writeln!(
247 out,
248 "#extension GL_ARB_shader_texture_image_samples : require"
249 )?;
250 }
251
252 if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) {
253 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
255 }
256 if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
257 writeln!(out, "#extension GL_EXT_blend_func_extended : require")?;
259 }
260
261 if self.0.contains(Features::INSTANCE_INDEX) {
262 if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) {
263 writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
265 }
266 }
267
268 if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
269 writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
271 }
272
273 if self.0.contains(Features::SUBGROUP_OPERATIONS) {
274 writeln!(out, "#extension GL_KHR_shader_subgroup_basic : require")?;
276 writeln!(out, "#extension GL_KHR_shader_subgroup_vote : require")?;
277 writeln!(
278 out,
279 "#extension GL_KHR_shader_subgroup_arithmetic : require"
280 )?;
281 writeln!(out, "#extension GL_KHR_shader_subgroup_ballot : require")?;
282 writeln!(out, "#extension GL_KHR_shader_subgroup_shuffle : require")?;
283 writeln!(
284 out,
285 "#extension GL_KHR_shader_subgroup_shuffle_relative : require"
286 )?;
287 writeln!(out, "#extension GL_KHR_shader_subgroup_quad : require")?;
288 }
289
290 if self.0.contains(Features::TEXTURE_ATOMICS) {
291 writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
293 }
294
295 if self.0.contains(Features::SHADER_BARYCENTRICS) {
296 writeln!(
298 out,
299 "#extension GL_EXT_fragment_shader_barycentric : require"
300 )?;
301 }
302
303 if self.0.contains(Features::PRIMITIVE_INDEX) {
304 match options.version {
305 Version::Embedded { version, .. } if version < 320 => {
306 writeln!(out, "#extension GL_OES_geometry_shader : require")?;
307 }
308 Version::Desktop(version) if version < 150 => {
309 writeln!(out, "#extension GL_ARB_geometry_shader4 : require")?;
310 }
311 _ => (),
312 }
313 }
314
315 Ok(())
316 }
317}
318
319impl<W> Writer<'_, W> {
320 pub(super) fn collect_required_features(&mut self) -> BackendResult {
326 let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
327
328 if let Some(early_depth_test) = self.entry_point.early_depth_test {
329 match early_depth_test {
330 crate::EarlyDepthTest::Force => {
331 if self.options.version.supports_early_depth_test() {
332 self.features.request(Features::IMAGE_LOAD_STORE);
333 }
334 }
335 crate::EarlyDepthTest::Allow { .. } => {
336 self.features.request(Features::CONSERVATIVE_DEPTH);
337 }
338 }
339 }
340
341 for arg in self.entry_point.function.arguments.iter() {
342 self.varying_required_features(arg.binding.as_ref(), arg.ty);
343 }
344 if let Some(ref result) = self.entry_point.function.result {
345 self.varying_required_features(result.binding.as_ref(), result.ty);
346 }
347
348 if let ShaderStage::Compute = self.entry_point.stage {
349 self.features.request(Features::COMPUTE_SHADER)
350 }
351
352 if self.multiview.is_some() {
353 self.features.request(Features::MULTI_VIEW);
354 }
355
356 for (ty_handle, ty) in self.module.types.iter() {
357 match ty.inner {
358 TypeInner::Scalar(scalar)
359 | TypeInner::Vector { scalar, .. }
360 | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar),
361 TypeInner::Array { base, size, .. } => {
362 if let TypeInner::Array { .. } = self.module.types[base].inner {
363 self.features.request(Features::ARRAY_OF_ARRAYS)
364 }
365
366 if size == crate::ArraySize::Dynamic {
368 let mut is_used = false;
369
370 for (global_handle, global) in self.module.global_variables.iter() {
372 if ep_info[global_handle].is_empty() {
374 continue;
375 }
376
377 if global.ty == ty_handle {
379 is_used = true;
380 break;
381 }
382
383 if let TypeInner::Struct { ref members, .. } =
385 self.module.types[global.ty].inner
386 {
387 if let Some(last) = members.last() {
390 if last.ty == ty_handle {
391 is_used = true;
392 break;
393 }
394 }
395 }
396 }
397
398 if is_used {
400 self.features.request(Features::DYNAMIC_ARRAY_SIZE);
401 }
402 }
403 }
404 TypeInner::Image {
405 dim,
406 arrayed,
407 class,
408 } => {
409 if arrayed && dim == ImageDimension::Cube {
410 self.features.request(Features::CUBE_TEXTURES_ARRAY)
411 }
412
413 match class {
414 ImageClass::Sampled { multi: true, .. }
415 | ImageClass::Depth { multi: true } => {
416 self.features.request(Features::MULTISAMPLED_TEXTURES);
417 if arrayed {
418 self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
419 }
420 }
421 ImageClass::Storage { format, .. } => match format {
422 StorageFormat::R8Unorm
423 | StorageFormat::R8Snorm
424 | StorageFormat::R8Uint
425 | StorageFormat::R8Sint
426 | StorageFormat::R16Uint
427 | StorageFormat::R16Sint
428 | StorageFormat::R16Float
429 | StorageFormat::Rg8Unorm
430 | StorageFormat::Rg8Snorm
431 | StorageFormat::Rg8Uint
432 | StorageFormat::Rg8Sint
433 | StorageFormat::Rg16Uint
434 | StorageFormat::Rg16Sint
435 | StorageFormat::Rg16Float
436 | StorageFormat::Rgb10a2Uint
437 | StorageFormat::Rgb10a2Unorm
438 | StorageFormat::Rg11b10Ufloat
439 | StorageFormat::R64Uint
440 | StorageFormat::Rg32Uint
441 | StorageFormat::Rg32Sint
442 | StorageFormat::Rg32Float => {
443 self.features.request(Features::FULL_IMAGE_FORMATS)
444 }
445 _ => {}
446 },
447 ImageClass::Sampled { multi: false, .. }
448 | ImageClass::Depth { multi: false }
449 | ImageClass::External => {}
450 }
451 }
452 _ => {}
453 }
454 }
455
456 let mut immediates_used = false;
457
458 for (handle, global) in self.module.global_variables.iter() {
459 if ep_info[handle].is_empty() {
460 continue;
461 }
462 match global.space {
463 AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
464 AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
465 AddressSpace::Immediate => {
466 if immediates_used {
467 return Err(Error::MultipleImmediateData);
468 }
469 immediates_used = true;
470 }
471 _ => {}
472 }
473 }
474
475 let &mut Self {
479 module,
480 info,
481 ref mut features,
482 entry_point,
483 entry_point_idx,
484 ref policies,
485 ..
486 } = self;
487
488 for (expressions, info) in module
491 .functions
492 .iter()
493 .map(|(h, f)| (&f.expressions, &info[h]))
494 .chain(core::iter::once((
495 &entry_point.function.expressions,
496 info.get_entry_point(entry_point_idx as usize),
497 )))
498 {
499 for (_, expr) in expressions.iter() {
500 match *expr {
501 Expression::ImageQuery {
503 image,
504 query,
505 ..
506 } => match query {
507 crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
512 if let TypeInner::Image {
513 class: ImageClass::Storage { .. }, ..
514 } = *info[image].ty.inner_with(&module.types) {
515 features.request(Features::IMAGE_SIZE)
516 }
517 },
518 crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
519 crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
520 }
521 ,
522 Expression::ImageLoad {
525 sample, level, ..
526 } => {
527 if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
528 if sample.is_some() {
529 features.request(Features::TEXTURE_SAMPLES)
530 }
531
532 if level.is_some() {
533 features.request(Features::TEXTURE_LEVELS)
534 }
535 }
536 }
537 Expression::ImageSample { image, level, offset, .. } => {
538 if let TypeInner::Image {
539 dim,
540 arrayed,
541 class: ImageClass::Depth { .. },
542 } = *info[image].ty.inner_with(&module.types) {
543 let lod = matches!(level, SampleLevel::Zero | SampleLevel::Exact(_));
544 let bias = matches!(level, SampleLevel::Bias(_));
545 let auto = matches!(level, SampleLevel::Auto);
546 let cube = dim == ImageDimension::Cube;
547 let array2d = dim == ImageDimension::D2 && arrayed;
548 let gles = self.options.version.is_es();
549
550 let grad_workaround_applicable = (array2d || (cube && !arrayed)) && level == SampleLevel::Zero;
555 let prefer_grad_workaround = grad_workaround_applicable && !self.options.writer_flags.contains(WriterFlags::TEXTURE_SHADOW_LOD);
556
557 let mut ext_used = false;
558
559 ext_used |= (array2d || cube && arrayed) && bias;
562
563 ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
566
567 ext_used |= (cube || array2d) && lod && !prefer_grad_workaround;
572
573 if ext_used {
574 features.request(Features::TEXTURE_SHADOW_LOD);
575 }
576 }
577 }
578 Expression::SubgroupBallotResult |
579 Expression::SubgroupOperationResult { .. } => {
580 features.request(Features::SUBGROUP_OPERATIONS)
581 }
582 _ => {}
583 }
584 }
585 }
586
587 for blocks in module
588 .functions
589 .iter()
590 .map(|(_, f)| &f.body)
591 .chain(core::iter::once(&entry_point.function.body))
592 {
593 for (stmt, _) in blocks.span_iter() {
594 match *stmt {
595 crate::Statement::ImageAtomic { .. } => {
596 features.request(Features::TEXTURE_ATOMICS)
597 }
598 _ => {}
599 }
600 }
601 }
602
603 self.features.check_availability(self.options.version)
604 }
605
606 fn scalar_required_features(&mut self, scalar: Scalar) {
608 if scalar.kind == ScalarKind::Float && scalar.width == 8 {
609 self.features.request(Features::DOUBLE_TYPE);
610 }
611 }
612
613 fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
614 if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner {
615 for member in members {
616 self.varying_required_features(member.binding.as_ref(), member.ty);
617 }
618 } else if let Some(binding) = binding {
619 match *binding {
620 Binding::BuiltIn(built_in) => match built_in {
621 crate::BuiltIn::ClipDistance => self.features.request(Features::CLIP_DISTANCE),
622 crate::BuiltIn::CullDistance => self.features.request(Features::CULL_DISTANCE),
623 crate::BuiltIn::SampleIndex => {
624 self.features.request(Features::SAMPLE_VARIABLES)
625 }
626 crate::BuiltIn::ViewIndex => self.features.request(Features::MULTI_VIEW),
627 crate::BuiltIn::InstanceIndex | crate::BuiltIn::DrawIndex => {
628 self.features.request(Features::INSTANCE_INDEX)
629 }
630 crate::BuiltIn::Barycentric { .. } => {
631 self.features.request(Features::SHADER_BARYCENTRICS)
632 }
633 crate::BuiltIn::PrimitiveIndex => {
634 self.features.request(Features::PRIMITIVE_INDEX)
635 }
636 _ => {}
637 },
638 Binding::Location {
639 location: _,
640 interpolation,
641 sampling,
642 blend_src,
643 per_primitive: _,
644 } => {
645 if interpolation == Some(Interpolation::Linear) {
646 self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
647 }
648 if sampling == Some(Sampling::Sample) {
649 self.features.request(Features::SAMPLE_QUALIFIER);
650 }
651 if blend_src.is_some() {
652 self.features.request(Features::DUAL_SOURCE_BLENDING);
653 }
654 }
655 }
656 }
657 }
658}