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 }
61}
62
63pub struct FeaturesManager(Features);
68
69impl FeaturesManager {
70 pub const fn new() -> Self {
72 Self(Features::empty())
73 }
74
75 pub fn request(&mut self, features: Features) {
77 self.0 |= features
78 }
79
80 pub fn contains(&mut self, features: Features) -> bool {
82 self.0.contains(features)
83 }
84
85 pub fn check_availability(&self, version: Version) -> BackendResult {
88 let mut missing = Features::empty();
90
91 macro_rules! check_feature {
93 ($feature:ident, $core:literal) => {
95 if self.0.contains(Features::$feature)
96 && (version < Version::Desktop($core) || version.is_es())
97 {
98 missing |= Features::$feature;
99 }
100 };
101 ($feature:ident, $core:literal, $es:literal) => {
103 if self.0.contains(Features::$feature)
104 && (version < Version::Desktop($core) || version < Version::new_gles($es))
105 {
106 missing |= Features::$feature;
107 }
108 };
109 }
110
111 check_feature!(COMPUTE_SHADER, 420, 310);
112 check_feature!(BUFFER_STORAGE, 400, 310);
113 check_feature!(DOUBLE_TYPE, 150);
114 check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
115 check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
116 check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
117 check_feature!(ARRAY_OF_ARRAYS, 120, 310);
118 check_feature!(IMAGE_LOAD_STORE, 130, 310);
119 check_feature!(CONSERVATIVE_DEPTH, 130, 300);
120 check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
121 check_feature!(SAMPLE_QUALIFIER, 400, 320);
122 check_feature!(CLIP_DISTANCE, 130, 300 );
123 check_feature!(CULL_DISTANCE, 450, 300 );
124 check_feature!(SAMPLE_VARIABLES, 400, 300);
125 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
126 check_feature!(DUAL_SOURCE_BLENDING, 330, 300 );
127 check_feature!(SUBGROUP_OPERATIONS, 430, 310);
128 check_feature!(TEXTURE_ATOMICS, 420, 310);
129 match version {
130 Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
131 _ => check_feature!(MULTI_VIEW, 140, 310),
132 };
133 check_feature!(TEXTURE_SAMPLES, 150);
137 check_feature!(TEXTURE_LEVELS, 130);
138 check_feature!(IMAGE_SIZE, 430, 310);
139 check_feature!(TEXTURE_SHADOW_LOD, 200, 300);
140
141 if missing.is_empty() {
143 Ok(())
144 } else {
145 Err(Error::MissingFeatures(missing))
146 }
147 }
148
149 pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
155 if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
156 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
158 }
159
160 if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
161 writeln!(
163 out,
164 "#extension GL_ARB_shader_storage_buffer_object : require"
165 )?;
166 }
167
168 if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) {
169 writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
171 }
172
173 if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
174 if options.version.is_es() {
175 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
177 } else if options.version < Version::Desktop(400) {
178 writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
180 }
181 }
182
183 if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() {
184 writeln!(
186 out,
187 "#extension GL_OES_texture_storage_multisample_2d_array : require"
188 )?;
189 }
190
191 if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) {
192 writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
194 }
195
196 if self.0.contains(Features::IMAGE_LOAD_STORE) {
197 if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() {
198 writeln!(out, "#extension GL_NV_image_formats : require")?;
200 }
201
202 if options.version < Version::Desktop(420) {
203 writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
205 }
206 }
207
208 if self.0.contains(Features::CONSERVATIVE_DEPTH) {
209 if options.version.is_es() {
210 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
212 }
213
214 if options.version < Version::Desktop(420) {
215 writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
217 }
218 }
219
220 if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
221 && options.version.is_es()
222 {
223 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
225 }
226
227 if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
228 writeln!(out, "#extension GL_OES_sample_variables : require")?;
230 }
231
232 if self.0.contains(Features::MULTI_VIEW) {
233 if let Version::Embedded { is_webgl: true, .. } = options.version {
234 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
236 } else {
237 writeln!(out, "#extension GL_EXT_multiview : require")?;
239 }
240 }
241
242 if self.0.contains(Features::TEXTURE_SAMPLES) {
243 writeln!(
245 out,
246 "#extension GL_ARB_shader_texture_image_samples : require"
247 )?;
248 }
249
250 if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) {
251 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
253 }
254 if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
255 writeln!(out, "#extension GL_EXT_blend_func_extended : require")?;
257 }
258
259 if self.0.contains(Features::INSTANCE_INDEX) {
260 if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) {
261 writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
263 }
264 }
265
266 if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
267 writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
269 }
270
271 if self.0.contains(Features::SUBGROUP_OPERATIONS) {
272 writeln!(out, "#extension GL_KHR_shader_subgroup_basic : require")?;
274 writeln!(out, "#extension GL_KHR_shader_subgroup_vote : require")?;
275 writeln!(
276 out,
277 "#extension GL_KHR_shader_subgroup_arithmetic : require"
278 )?;
279 writeln!(out, "#extension GL_KHR_shader_subgroup_ballot : require")?;
280 writeln!(out, "#extension GL_KHR_shader_subgroup_shuffle : require")?;
281 writeln!(
282 out,
283 "#extension GL_KHR_shader_subgroup_shuffle_relative : require"
284 )?;
285 writeln!(out, "#extension GL_KHR_shader_subgroup_quad : require")?;
286 }
287
288 if self.0.contains(Features::TEXTURE_ATOMICS) {
289 writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
291 }
292
293 if self.0.contains(Features::SHADER_BARYCENTRICS) {
294 writeln!(
296 out,
297 "#extension GL_EXT_fragment_shader_barycentric : require"
298 )?;
299 }
300
301 Ok(())
302 }
303}
304
305impl<W> Writer<'_, W> {
306 pub(super) fn collect_required_features(&mut self) -> BackendResult {
312 let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
313
314 if let Some(early_depth_test) = self.entry_point.early_depth_test {
315 match early_depth_test {
316 crate::EarlyDepthTest::Force => {
317 if self.options.version.supports_early_depth_test() {
318 self.features.request(Features::IMAGE_LOAD_STORE);
319 }
320 }
321 crate::EarlyDepthTest::Allow { .. } => {
322 self.features.request(Features::CONSERVATIVE_DEPTH);
323 }
324 }
325 }
326
327 for arg in self.entry_point.function.arguments.iter() {
328 self.varying_required_features(arg.binding.as_ref(), arg.ty);
329 }
330 if let Some(ref result) = self.entry_point.function.result {
331 self.varying_required_features(result.binding.as_ref(), result.ty);
332 }
333
334 if let ShaderStage::Compute = self.entry_point.stage {
335 self.features.request(Features::COMPUTE_SHADER)
336 }
337
338 if self.multiview.is_some() {
339 self.features.request(Features::MULTI_VIEW);
340 }
341
342 for (ty_handle, ty) in self.module.types.iter() {
343 match ty.inner {
344 TypeInner::Scalar(scalar)
345 | TypeInner::Vector { scalar, .. }
346 | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar),
347 TypeInner::Array { base, size, .. } => {
348 if let TypeInner::Array { .. } = self.module.types[base].inner {
349 self.features.request(Features::ARRAY_OF_ARRAYS)
350 }
351
352 if size == crate::ArraySize::Dynamic {
354 let mut is_used = false;
355
356 for (global_handle, global) in self.module.global_variables.iter() {
358 if ep_info[global_handle].is_empty() {
360 continue;
361 }
362
363 if global.ty == ty_handle {
365 is_used = true;
366 break;
367 }
368
369 if let TypeInner::Struct { ref members, .. } =
371 self.module.types[global.ty].inner
372 {
373 if let Some(last) = members.last() {
376 if last.ty == ty_handle {
377 is_used = true;
378 break;
379 }
380 }
381 }
382 }
383
384 if is_used {
386 self.features.request(Features::DYNAMIC_ARRAY_SIZE);
387 }
388 }
389 }
390 TypeInner::Image {
391 dim,
392 arrayed,
393 class,
394 } => {
395 if arrayed && dim == ImageDimension::Cube {
396 self.features.request(Features::CUBE_TEXTURES_ARRAY)
397 }
398
399 match class {
400 ImageClass::Sampled { multi: true, .. }
401 | ImageClass::Depth { multi: true } => {
402 self.features.request(Features::MULTISAMPLED_TEXTURES);
403 if arrayed {
404 self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
405 }
406 }
407 ImageClass::Storage { format, .. } => match format {
408 StorageFormat::R8Unorm
409 | StorageFormat::R8Snorm
410 | StorageFormat::R8Uint
411 | StorageFormat::R8Sint
412 | StorageFormat::R16Uint
413 | StorageFormat::R16Sint
414 | StorageFormat::R16Float
415 | StorageFormat::Rg8Unorm
416 | StorageFormat::Rg8Snorm
417 | StorageFormat::Rg8Uint
418 | StorageFormat::Rg8Sint
419 | StorageFormat::Rg16Uint
420 | StorageFormat::Rg16Sint
421 | StorageFormat::Rg16Float
422 | StorageFormat::Rgb10a2Uint
423 | StorageFormat::Rgb10a2Unorm
424 | StorageFormat::Rg11b10Ufloat
425 | StorageFormat::R64Uint
426 | StorageFormat::Rg32Uint
427 | StorageFormat::Rg32Sint
428 | StorageFormat::Rg32Float => {
429 self.features.request(Features::FULL_IMAGE_FORMATS)
430 }
431 _ => {}
432 },
433 ImageClass::Sampled { multi: false, .. }
434 | ImageClass::Depth { multi: false }
435 | ImageClass::External => {}
436 }
437 }
438 _ => {}
439 }
440 }
441
442 let mut push_constant_used = false;
443
444 for (handle, global) in self.module.global_variables.iter() {
445 if ep_info[handle].is_empty() {
446 continue;
447 }
448 match global.space {
449 AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
450 AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
451 AddressSpace::PushConstant => {
452 if push_constant_used {
453 return Err(Error::MultiplePushConstants);
454 }
455 push_constant_used = true;
456 }
457 _ => {}
458 }
459 }
460
461 let &mut Self {
465 module,
466 info,
467 ref mut features,
468 entry_point,
469 entry_point_idx,
470 ref policies,
471 ..
472 } = self;
473
474 for (expressions, info) in module
477 .functions
478 .iter()
479 .map(|(h, f)| (&f.expressions, &info[h]))
480 .chain(core::iter::once((
481 &entry_point.function.expressions,
482 info.get_entry_point(entry_point_idx as usize),
483 )))
484 {
485 for (_, expr) in expressions.iter() {
486 match *expr {
487 Expression::ImageQuery {
489 image,
490 query,
491 ..
492 } => match query {
493 crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
498 if let TypeInner::Image {
499 class: ImageClass::Storage { .. }, ..
500 } = *info[image].ty.inner_with(&module.types) {
501 features.request(Features::IMAGE_SIZE)
502 }
503 },
504 crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
505 crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
506 }
507 ,
508 Expression::ImageLoad {
511 sample, level, ..
512 } => {
513 if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
514 if sample.is_some() {
515 features.request(Features::TEXTURE_SAMPLES)
516 }
517
518 if level.is_some() {
519 features.request(Features::TEXTURE_LEVELS)
520 }
521 }
522 }
523 Expression::ImageSample { image, level, offset, .. } => {
524 if let TypeInner::Image {
525 dim,
526 arrayed,
527 class: ImageClass::Depth { .. },
528 } = *info[image].ty.inner_with(&module.types) {
529 let lod = matches!(level, SampleLevel::Zero | SampleLevel::Exact(_));
530 let bias = matches!(level, SampleLevel::Bias(_));
531 let auto = matches!(level, SampleLevel::Auto);
532 let cube = dim == ImageDimension::Cube;
533 let array2d = dim == ImageDimension::D2 && arrayed;
534 let gles = self.options.version.is_es();
535
536 let grad_workaround_applicable = (array2d || (cube && !arrayed)) && level == SampleLevel::Zero;
541 let prefer_grad_workaround = grad_workaround_applicable && !self.options.writer_flags.contains(WriterFlags::TEXTURE_SHADOW_LOD);
542
543 let mut ext_used = false;
544
545 ext_used |= (array2d || cube && arrayed) && bias;
548
549 ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
552
553 ext_used |= (cube || array2d) && lod && !prefer_grad_workaround;
558
559 if ext_used {
560 features.request(Features::TEXTURE_SHADOW_LOD);
561 }
562 }
563 }
564 Expression::SubgroupBallotResult |
565 Expression::SubgroupOperationResult { .. } => {
566 features.request(Features::SUBGROUP_OPERATIONS)
567 }
568 _ => {}
569 }
570 }
571 }
572
573 for blocks in module
574 .functions
575 .iter()
576 .map(|(_, f)| &f.body)
577 .chain(core::iter::once(&entry_point.function.body))
578 {
579 for (stmt, _) in blocks.span_iter() {
580 match *stmt {
581 crate::Statement::ImageAtomic { .. } => {
582 features.request(Features::TEXTURE_ATOMICS)
583 }
584 _ => {}
585 }
586 }
587 }
588
589 self.features.check_availability(self.options.version)
590 }
591
592 fn scalar_required_features(&mut self, scalar: Scalar) {
594 if scalar.kind == ScalarKind::Float && scalar.width == 8 {
595 self.features.request(Features::DOUBLE_TYPE);
596 }
597 }
598
599 fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
600 if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner {
601 for member in members {
602 self.varying_required_features(member.binding.as_ref(), member.ty);
603 }
604 } else if let Some(binding) = binding {
605 match *binding {
606 Binding::BuiltIn(built_in) => match built_in {
607 crate::BuiltIn::ClipDistance => self.features.request(Features::CLIP_DISTANCE),
608 crate::BuiltIn::CullDistance => self.features.request(Features::CULL_DISTANCE),
609 crate::BuiltIn::SampleIndex => {
610 self.features.request(Features::SAMPLE_VARIABLES)
611 }
612 crate::BuiltIn::ViewIndex => self.features.request(Features::MULTI_VIEW),
613 crate::BuiltIn::InstanceIndex | crate::BuiltIn::DrawID => {
614 self.features.request(Features::INSTANCE_INDEX)
615 }
616 crate::BuiltIn::Barycentric => {
617 self.features.request(Features::SHADER_BARYCENTRICS)
618 }
619 _ => {}
620 },
621 Binding::Location {
622 location: _,
623 interpolation,
624 sampling,
625 blend_src,
626 per_primitive: _,
627 } => {
628 if interpolation == Some(Interpolation::Linear) {
629 self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
630 }
631 if sampling == Some(Sampling::Sample) {
632 self.features.request(Features::SAMPLE_QUALIFIER);
633 }
634 if blend_src.is_some() {
635 self.features.request(Features::DUAL_SOURCE_BLENDING);
636 }
637 }
638 }
639 }
640 }
641}