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 }
56}
57
58pub struct FeaturesManager(Features);
63
64impl FeaturesManager {
65 pub const fn new() -> Self {
67 Self(Features::empty())
68 }
69
70 pub fn request(&mut self, features: Features) {
72 self.0 |= features
73 }
74
75 pub fn contains(&mut self, features: Features) -> bool {
77 self.0.contains(features)
78 }
79
80 pub fn check_availability(&self, version: Version) -> BackendResult {
83 let mut missing = Features::empty();
85
86 macro_rules! check_feature {
88 ($feature:ident, $core:literal) => {
90 if self.0.contains(Features::$feature)
91 && (version < Version::Desktop($core) || version.is_es())
92 {
93 missing |= Features::$feature;
94 }
95 };
96 ($feature:ident, $core:literal, $es:literal) => {
98 if self.0.contains(Features::$feature)
99 && (version < Version::Desktop($core) || version < Version::new_gles($es))
100 {
101 missing |= Features::$feature;
102 }
103 };
104 }
105
106 check_feature!(COMPUTE_SHADER, 420, 310);
107 check_feature!(BUFFER_STORAGE, 400, 310);
108 check_feature!(DOUBLE_TYPE, 150);
109 check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
110 check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
111 check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
112 check_feature!(ARRAY_OF_ARRAYS, 120, 310);
113 check_feature!(IMAGE_LOAD_STORE, 130, 310);
114 check_feature!(CONSERVATIVE_DEPTH, 130, 300);
115 check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
116 check_feature!(SAMPLE_QUALIFIER, 400, 320);
117 check_feature!(CLIP_DISTANCE, 130, 300 );
118 check_feature!(CULL_DISTANCE, 450, 300 );
119 check_feature!(SAMPLE_VARIABLES, 400, 300);
120 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
121 check_feature!(DUAL_SOURCE_BLENDING, 330, 300 );
122 check_feature!(SUBGROUP_OPERATIONS, 430, 310);
123 match version {
124 Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
125 _ => check_feature!(MULTI_VIEW, 140, 310),
126 };
127 check_feature!(TEXTURE_SAMPLES, 150);
131 check_feature!(TEXTURE_LEVELS, 130);
132 check_feature!(IMAGE_SIZE, 430, 310);
133 check_feature!(TEXTURE_SHADOW_LOD, 200, 300);
134
135 if missing.is_empty() {
137 Ok(())
138 } else {
139 Err(Error::MissingFeatures(missing))
140 }
141 }
142
143 pub fn write(&self, options: &Options, mut out: impl Write) -> BackendResult {
149 if self.0.contains(Features::COMPUTE_SHADER) && !options.version.is_es() {
150 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
152 }
153
154 if self.0.contains(Features::BUFFER_STORAGE) && !options.version.is_es() {
155 writeln!(
157 out,
158 "#extension GL_ARB_shader_storage_buffer_object : require"
159 )?;
160 }
161
162 if self.0.contains(Features::DOUBLE_TYPE) && options.version < Version::Desktop(400) {
163 writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
165 }
166
167 if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
168 if options.version.is_es() {
169 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
171 } else if options.version < Version::Desktop(400) {
172 writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
174 }
175 }
176
177 if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && options.version.is_es() {
178 writeln!(
180 out,
181 "#extension GL_OES_texture_storage_multisample_2d_array : require"
182 )?;
183 }
184
185 if self.0.contains(Features::ARRAY_OF_ARRAYS) && options.version < Version::Desktop(430) {
186 writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
188 }
189
190 if self.0.contains(Features::IMAGE_LOAD_STORE) {
191 if self.0.contains(Features::FULL_IMAGE_FORMATS) && options.version.is_es() {
192 writeln!(out, "#extension GL_NV_image_formats : require")?;
194 }
195
196 if options.version < Version::Desktop(420) {
197 writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
199 }
200 }
201
202 if self.0.contains(Features::CONSERVATIVE_DEPTH) {
203 if options.version.is_es() {
204 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
206 }
207
208 if options.version < Version::Desktop(420) {
209 writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
211 }
212 }
213
214 if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
215 && options.version.is_es()
216 {
217 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
219 }
220
221 if self.0.contains(Features::SAMPLE_VARIABLES) && options.version.is_es() {
222 writeln!(out, "#extension GL_OES_sample_variables : require")?;
224 }
225
226 if self.0.contains(Features::MULTI_VIEW) {
227 if let Version::Embedded { is_webgl: true, .. } = options.version {
228 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
230 } else {
231 writeln!(out, "#extension GL_EXT_multiview : require")?;
233 }
234 }
235
236 if self.0.contains(Features::TEXTURE_SAMPLES) {
237 writeln!(
239 out,
240 "#extension GL_ARB_shader_texture_image_samples : require"
241 )?;
242 }
243
244 if self.0.contains(Features::TEXTURE_LEVELS) && options.version < Version::Desktop(430) {
245 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
247 }
248 if self.0.contains(Features::DUAL_SOURCE_BLENDING) && options.version.is_es() {
249 writeln!(out, "#extension GL_EXT_blend_func_extended : require")?;
251 }
252
253 if self.0.contains(Features::INSTANCE_INDEX) {
254 if options.writer_flags.contains(WriterFlags::DRAW_PARAMETERS) {
255 writeln!(out, "#extension GL_ARB_shader_draw_parameters : require")?;
257 }
258 }
259
260 if self.0.contains(Features::TEXTURE_SHADOW_LOD) {
261 writeln!(out, "#extension GL_EXT_texture_shadow_lod : require")?;
263 }
264
265 if self.0.contains(Features::SUBGROUP_OPERATIONS) {
266 writeln!(out, "#extension GL_KHR_shader_subgroup_basic : require")?;
268 writeln!(out, "#extension GL_KHR_shader_subgroup_vote : require")?;
269 writeln!(
270 out,
271 "#extension GL_KHR_shader_subgroup_arithmetic : require"
272 )?;
273 writeln!(out, "#extension GL_KHR_shader_subgroup_ballot : require")?;
274 writeln!(out, "#extension GL_KHR_shader_subgroup_shuffle : require")?;
275 writeln!(
276 out,
277 "#extension GL_KHR_shader_subgroup_shuffle_relative : require"
278 )?;
279 }
280
281 Ok(())
282 }
283}
284
285impl<'a, W> Writer<'a, W> {
286 pub(super) fn collect_required_features(&mut self) -> BackendResult {
292 let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
293
294 if let Some(depth_test) = self.entry_point.early_depth_test {
295 if self.options.version.supports_early_depth_test() {
297 self.features.request(Features::IMAGE_LOAD_STORE);
298 }
299
300 if depth_test.conservative.is_some() {
301 self.features.request(Features::CONSERVATIVE_DEPTH);
302 }
303 }
304
305 for arg in self.entry_point.function.arguments.iter() {
306 self.varying_required_features(arg.binding.as_ref(), arg.ty);
307 }
308 if let Some(ref result) = self.entry_point.function.result {
309 self.varying_required_features(result.binding.as_ref(), result.ty);
310 }
311
312 if let ShaderStage::Compute = self.entry_point.stage {
313 self.features.request(Features::COMPUTE_SHADER)
314 }
315
316 if self.multiview.is_some() {
317 self.features.request(Features::MULTI_VIEW);
318 }
319
320 for (ty_handle, ty) in self.module.types.iter() {
321 match ty.inner {
322 TypeInner::Scalar(scalar)
323 | TypeInner::Vector { scalar, .. }
324 | TypeInner::Matrix { scalar, .. } => self.scalar_required_features(scalar),
325 TypeInner::Array { base, size, .. } => {
326 if let TypeInner::Array { .. } = self.module.types[base].inner {
327 self.features.request(Features::ARRAY_OF_ARRAYS)
328 }
329
330 if size == crate::ArraySize::Dynamic {
332 let mut is_used = false;
333
334 for (global_handle, global) in self.module.global_variables.iter() {
336 if ep_info[global_handle].is_empty() {
338 continue;
339 }
340
341 if global.ty == ty_handle {
343 is_used = true;
344 break;
345 }
346
347 if let TypeInner::Struct { ref members, .. } =
349 self.module.types[global.ty].inner
350 {
351 if let Some(last) = members.last() {
354 if last.ty == ty_handle {
355 is_used = true;
356 break;
357 }
358 }
359 }
360 }
361
362 if is_used {
364 self.features.request(Features::DYNAMIC_ARRAY_SIZE);
365 }
366 }
367 }
368 TypeInner::Image {
369 dim,
370 arrayed,
371 class,
372 } => {
373 if arrayed && dim == ImageDimension::Cube {
374 self.features.request(Features::CUBE_TEXTURES_ARRAY)
375 }
376
377 match class {
378 ImageClass::Sampled { multi: true, .. }
379 | ImageClass::Depth { multi: true } => {
380 self.features.request(Features::MULTISAMPLED_TEXTURES);
381 if arrayed {
382 self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
383 }
384 }
385 ImageClass::Storage { format, .. } => match format {
386 StorageFormat::R8Unorm
387 | StorageFormat::R8Snorm
388 | StorageFormat::R8Uint
389 | StorageFormat::R8Sint
390 | StorageFormat::R16Uint
391 | StorageFormat::R16Sint
392 | StorageFormat::R16Float
393 | StorageFormat::Rg8Unorm
394 | StorageFormat::Rg8Snorm
395 | StorageFormat::Rg8Uint
396 | StorageFormat::Rg8Sint
397 | StorageFormat::Rg16Uint
398 | StorageFormat::Rg16Sint
399 | StorageFormat::Rg16Float
400 | StorageFormat::Rgb10a2Uint
401 | StorageFormat::Rgb10a2Unorm
402 | StorageFormat::Rg11b10Ufloat
403 | StorageFormat::Rg32Uint
404 | StorageFormat::Rg32Sint
405 | StorageFormat::Rg32Float => {
406 self.features.request(Features::FULL_IMAGE_FORMATS)
407 }
408 _ => {}
409 },
410 ImageClass::Sampled { multi: false, .. }
411 | ImageClass::Depth { multi: false } => {}
412 }
413 }
414 _ => {}
415 }
416 }
417
418 let mut push_constant_used = false;
419
420 for (handle, global) in self.module.global_variables.iter() {
421 if ep_info[handle].is_empty() {
422 continue;
423 }
424 match global.space {
425 AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
426 AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
427 AddressSpace::PushConstant => {
428 if push_constant_used {
429 return Err(Error::MultiplePushConstants);
430 }
431 push_constant_used = true;
432 }
433 _ => {}
434 }
435 }
436
437 let &mut Self {
441 module,
442 info,
443 ref mut features,
444 entry_point,
445 entry_point_idx,
446 ref policies,
447 ..
448 } = self;
449
450 for (expressions, info) in module
453 .functions
454 .iter()
455 .map(|(h, f)| (&f.expressions, &info[h]))
456 .chain(std::iter::once((
457 &entry_point.function.expressions,
458 info.get_entry_point(entry_point_idx as usize),
459 )))
460 {
461 for (_, expr) in expressions.iter() {
462 match *expr {
463 Expression::ImageQuery {
465 image,
466 query,
467 ..
468 } => match query {
469 crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
474 if let TypeInner::Image {
475 class: ImageClass::Storage { .. }, ..
476 } = *info[image].ty.inner_with(&module.types) {
477 features.request(Features::IMAGE_SIZE)
478 }
479 },
480 crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
481 crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
482 }
483 ,
484 Expression::ImageLoad {
487 sample, level, ..
488 } => {
489 if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
490 if sample.is_some() {
491 features.request(Features::TEXTURE_SAMPLES)
492 }
493
494 if level.is_some() {
495 features.request(Features::TEXTURE_LEVELS)
496 }
497 }
498 }
499 Expression::ImageSample { image, level, offset, .. } => {
500 if let TypeInner::Image {
501 dim,
502 arrayed,
503 class: ImageClass::Depth { .. },
504 } = *info[image].ty.inner_with(&module.types) {
505 let lod = matches!(level, SampleLevel::Zero | SampleLevel::Exact(_));
506 let bias = matches!(level, SampleLevel::Bias(_));
507 let auto = matches!(level, SampleLevel::Auto);
508 let cube = dim == ImageDimension::Cube;
509 let array2d = dim == ImageDimension::D2 && arrayed;
510 let gles = self.options.version.is_es();
511
512 let grad_workaround_applicable = (array2d || (cube && !arrayed)) && level == SampleLevel::Zero;
517 let prefer_grad_workaround = grad_workaround_applicable && !self.options.writer_flags.contains(WriterFlags::TEXTURE_SHADOW_LOD);
518
519 let mut ext_used = false;
520
521 ext_used |= (array2d || cube && arrayed) && bias;
524
525 ext_used |= array2d && (bias || (gles && auto)) && offset.is_some();
528
529 ext_used |= (cube || array2d) && lod && !prefer_grad_workaround;
534
535 if ext_used {
536 features.request(Features::TEXTURE_SHADOW_LOD);
537 }
538 }
539 }
540 Expression::SubgroupBallotResult |
541 Expression::SubgroupOperationResult { .. } => {
542 features.request(Features::SUBGROUP_OPERATIONS)
543 }
544 _ => {}
545 }
546 }
547 }
548
549 self.features.check_availability(self.options.version)
550 }
551
552 fn scalar_required_features(&mut self, scalar: Scalar) {
554 if scalar.kind == ScalarKind::Float && scalar.width == 8 {
555 self.features.request(Features::DOUBLE_TYPE);
556 }
557 }
558
559 fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
560 if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner {
561 for member in members {
562 self.varying_required_features(member.binding.as_ref(), member.ty);
563 }
564 } else if let Some(binding) = binding {
565 match *binding {
566 Binding::BuiltIn(built_in) => match built_in {
567 crate::BuiltIn::ClipDistance => self.features.request(Features::CLIP_DISTANCE),
568 crate::BuiltIn::CullDistance => self.features.request(Features::CULL_DISTANCE),
569 crate::BuiltIn::SampleIndex => {
570 self.features.request(Features::SAMPLE_VARIABLES)
571 }
572 crate::BuiltIn::ViewIndex => self.features.request(Features::MULTI_VIEW),
573 crate::BuiltIn::InstanceIndex | crate::BuiltIn::DrawID => {
574 self.features.request(Features::INSTANCE_INDEX)
575 }
576 _ => {}
577 },
578 Binding::Location {
579 location: _,
580 interpolation,
581 sampling,
582 second_blend_source,
583 } => {
584 if interpolation == Some(Interpolation::Linear) {
585 self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
586 }
587 if sampling == Some(Sampling::Sample) {
588 self.features.request(Features::SAMPLE_QUALIFIER);
589 }
590 if second_blend_source {
591 self.features.request(Features::DUAL_SOURCE_BLENDING);
592 }
593 }
594 }
595 }
596 }
597}