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 }
59}
60
61pub struct FeaturesManager(Features);
66
67impl FeaturesManager {
68 pub const fn new() -> Self {
70 Self(Features::empty())
71 }
72
73 pub fn request(&mut self, features: Features) {
75 self.0 |= features
76 }
77
78 pub fn contains(&mut self, features: Features) -> bool {
80 self.0.contains(features)
81 }
82
83 pub fn check_availability(&self, version: Version) -> BackendResult {
86 let mut missing = Features::empty();
88
89 macro_rules! check_feature {
91 ($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 ($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 );
121 check_feature!(CULL_DISTANCE, 450, 300 );
122 check_feature!(SAMPLE_VARIABLES, 400, 300);
123 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
124 check_feature!(DUAL_SOURCE_BLENDING, 330, 300 );
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 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 if missing.is_empty() {
141 Ok(())
142 } else {
143 Err(Error::MissingFeatures(missing))
144 }
145 }
146
147 pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
153 if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
154 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
156 }
157
158 if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
159 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 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 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
175 } else if options.version < Version::Desktop(400) {
176 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 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 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 writeln!(out, "#extension GL_NV_image_formats : require")?;
198 }
199
200 if options.version < Version::Desktop(420) {
201 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 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
210 }
211
212 if options.version < Version::Desktop(420) {
213 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 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
223 }
224
225 if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
226 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 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
234 } else {
235 writeln!(out, "#extension GL_EXT_multiview : require")?;
237 }
238 }
239
240 if self.0.contains(Features::TEXTURE_SAMPLES) {
241 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 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
251 }
252 if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
253 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 writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
261 }
262 }
263
264 if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
265 writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
267 }
268
269 if self.0.contains(Features::SUBGROUP_OPERATIONS) {
270 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 writeln!(out, "#extension GL_OES_shader_image_atomic : require")?;
289 }
290
291 Ok(())
292 }
293}
294
295impl<W> Writer<'_, W> {
296 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 size == crate::ArraySize::Dynamic {
344 let mut is_used = false;
345
346 for (global_handle, global) in self.module.global_variables.iter() {
348 if ep_info[global_handle].is_empty() {
350 continue;
351 }
352
353 if global.ty == ty_handle {
355 is_used = true;
356 break;
357 }
358
359 if let TypeInner::Struct { ref members, .. } =
361 self.module.types[global.ty].inner
362 {
363 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 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 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 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 Expression::ImageQuery {
479 image,
480 query,
481 ..
482 } => match query {
483 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 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 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 ext_used |= (array2d || cube && arrayed) && bias;
538
539 ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
542
543 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 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}