1use super::{BackendResult, Error, Version, Writer};
2use crate::{
3 AddressSpace, Binding, Bytes, Expression, Handle, ImageClass, ImageDimension, Interpolation,
4 Sampling, ScalarKind, ShaderStage, StorageFormat, Type, TypeInner,
5};
6use std::fmt::Write;
7
8bitflags::bitflags! {
9 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
11 pub struct Features: u32 {
12 const BUFFER_STORAGE = 1;
14 const ARRAY_OF_ARRAYS = 1 << 1;
15 const DOUBLE_TYPE = 1 << 2;
17 const FULL_IMAGE_FORMATS = 1 << 3;
19 const MULTISAMPLED_TEXTURES = 1 << 4;
20 const MULTISAMPLED_TEXTURE_ARRAYS = 1 << 5;
21 const CUBE_TEXTURES_ARRAY = 1 << 6;
22 const COMPUTE_SHADER = 1 << 7;
23 const IMAGE_LOAD_STORE = 1 << 8;
25 const CONSERVATIVE_DEPTH = 1 << 9;
26 const NOPERSPECTIVE_QUALIFIER = 1 << 11;
30 const SAMPLE_QUALIFIER = 1 << 12;
31 const CLIP_DISTANCE = 1 << 13;
32 const CULL_DISTANCE = 1 << 14;
33 const SAMPLE_VARIABLES = 1 << 15;
35 const DYNAMIC_ARRAY_SIZE = 1 << 16;
37 const MULTI_VIEW = 1 << 17;
38 const TEXTURE_SAMPLES = 1 << 18;
40 const TEXTURE_LEVELS = 1 << 19;
42 const IMAGE_SIZE = 1 << 20;
44 }
45}
46
47pub struct FeaturesManager(Features);
52
53impl FeaturesManager {
54 pub const fn new() -> Self {
56 Self(Features::empty())
57 }
58
59 pub fn request(&mut self, features: Features) {
61 self.0 |= features
62 }
63
64 pub fn check_availability(&self, version: Version) -> BackendResult {
68 let mut missing = Features::empty();
70
71 macro_rules! check_feature {
73 ($feature:ident, $core:literal) => {
75 if self.0.contains(Features::$feature)
76 && (version < Version::Desktop($core) || version.is_es())
77 {
78 missing |= Features::$feature;
79 }
80 };
81 ($feature:ident, $core:literal, $es:literal) => {
83 if self.0.contains(Features::$feature)
84 && (version < Version::Desktop($core) || version < Version::new_gles($es))
85 {
86 missing |= Features::$feature;
87 }
88 };
89 }
90
91 check_feature!(COMPUTE_SHADER, 420, 310);
92 check_feature!(BUFFER_STORAGE, 400, 310);
93 check_feature!(DOUBLE_TYPE, 150);
94 check_feature!(CUBE_TEXTURES_ARRAY, 130, 310);
95 check_feature!(MULTISAMPLED_TEXTURES, 150, 300);
96 check_feature!(MULTISAMPLED_TEXTURE_ARRAYS, 150, 310);
97 check_feature!(ARRAY_OF_ARRAYS, 120, 310);
98 check_feature!(IMAGE_LOAD_STORE, 130, 310);
99 check_feature!(CONSERVATIVE_DEPTH, 130, 300);
100 check_feature!(CONSERVATIVE_DEPTH, 130, 300);
101 check_feature!(NOPERSPECTIVE_QUALIFIER, 130);
102 check_feature!(SAMPLE_QUALIFIER, 400, 320);
103 check_feature!(CLIP_DISTANCE, 130, 300 );
104 check_feature!(CULL_DISTANCE, 450, 300 );
105 check_feature!(SAMPLE_VARIABLES, 400, 300);
106 check_feature!(DYNAMIC_ARRAY_SIZE, 430, 310);
107 match version {
108 Version::Embedded { is_webgl: true, .. } => check_feature!(MULTI_VIEW, 140, 300),
109 _ => check_feature!(MULTI_VIEW, 140, 310),
110 };
111 check_feature!(TEXTURE_SAMPLES, 150);
115 check_feature!(TEXTURE_LEVELS, 130);
116 check_feature!(IMAGE_SIZE, 430, 310);
117
118 if missing.is_empty() {
120 Ok(())
121 } else {
122 Err(Error::MissingFeatures(missing))
123 }
124 }
125
126 pub fn write(&self, version: Version, mut out: impl Write) -> BackendResult {
132 if self.0.contains(Features::COMPUTE_SHADER) && !version.is_es() {
133 writeln!(out, "#extension GL_ARB_compute_shader : require")?;
135 }
136
137 if self.0.contains(Features::BUFFER_STORAGE) && !version.is_es() {
138 writeln!(
140 out,
141 "#extension GL_ARB_shader_storage_buffer_object : require"
142 )?;
143 }
144
145 if self.0.contains(Features::DOUBLE_TYPE) && version < Version::Desktop(400) {
146 writeln!(out, "#extension GL_ARB_gpu_shader_fp64 : require")?;
148 }
149
150 if self.0.contains(Features::CUBE_TEXTURES_ARRAY) {
151 if version.is_es() {
152 writeln!(out, "#extension GL_EXT_texture_cube_map_array : require")?;
154 } else if version < Version::Desktop(400) {
155 writeln!(out, "#extension GL_ARB_texture_cube_map_array : require")?;
157 }
158 }
159
160 if self.0.contains(Features::MULTISAMPLED_TEXTURE_ARRAYS) && version.is_es() {
161 writeln!(
163 out,
164 "#extension GL_OES_texture_storage_multisample_2d_array : require"
165 )?;
166 }
167
168 if self.0.contains(Features::ARRAY_OF_ARRAYS) && version < Version::Desktop(430) {
169 writeln!(out, "#extension ARB_arrays_of_arrays : require")?;
171 }
172
173 if self.0.contains(Features::IMAGE_LOAD_STORE) {
174 if self.0.contains(Features::FULL_IMAGE_FORMATS) && version.is_es() {
175 writeln!(out, "#extension GL_NV_image_formats : require")?;
177 }
178
179 if version < Version::Desktop(420) {
180 writeln!(out, "#extension GL_ARB_shader_image_load_store : require")?;
182 }
183 }
184
185 if self.0.contains(Features::CONSERVATIVE_DEPTH) {
186 if version.is_es() {
187 writeln!(out, "#extension GL_EXT_conservative_depth : require")?;
189 }
190
191 if version < Version::Desktop(420) {
192 writeln!(out, "#extension GL_ARB_conservative_depth : require")?;
194 }
195 }
196
197 if (self.0.contains(Features::CLIP_DISTANCE) || self.0.contains(Features::CULL_DISTANCE))
198 && version.is_es()
199 {
200 writeln!(out, "#extension GL_EXT_clip_cull_distance : require")?;
202 }
203
204 if self.0.contains(Features::SAMPLE_VARIABLES) && version.is_es() {
205 writeln!(out, "#extension GL_OES_sample_variables : require")?;
207 }
208
209 if self.0.contains(Features::SAMPLE_VARIABLES) && version.is_es() {
210 writeln!(out, "#extension GL_OES_sample_variables : require")?;
212 }
213
214 if self.0.contains(Features::MULTI_VIEW) {
215 if let Version::Embedded { is_webgl: true, .. } = version {
216 writeln!(out, "#extension GL_OVR_multiview2 : require")?;
218 } else {
219 writeln!(out, "#extension GL_EXT_multiview : require")?;
221 }
222 }
223
224 if self.0.contains(Features::TEXTURE_SAMPLES) {
225 writeln!(
227 out,
228 "#extension GL_ARB_shader_texture_image_samples : require"
229 )?;
230 }
231
232 if self.0.contains(Features::TEXTURE_LEVELS) && version < Version::Desktop(430) {
233 writeln!(out, "#extension GL_ARB_texture_query_levels : require")?;
235 }
236
237 Ok(())
238 }
239}
240
241impl<'a, W> Writer<'a, W> {
242 pub(super) fn collect_required_features(&mut self) -> BackendResult {
248 let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
249
250 if let Some(depth_test) = self.entry_point.early_depth_test {
251 if self.options.version.supports_early_depth_test() {
253 self.features.request(Features::IMAGE_LOAD_STORE);
254 }
255
256 if depth_test.conservative.is_some() {
257 self.features.request(Features::CONSERVATIVE_DEPTH);
258 }
259 }
260
261 for arg in self.entry_point.function.arguments.iter() {
262 self.varying_required_features(arg.binding.as_ref(), arg.ty);
263 }
264 if let Some(ref result) = self.entry_point.function.result {
265 self.varying_required_features(result.binding.as_ref(), result.ty);
266 }
267
268 if let ShaderStage::Compute = self.entry_point.stage {
269 self.features.request(Features::COMPUTE_SHADER)
270 }
271
272 if self.multiview.is_some() {
273 self.features.request(Features::MULTI_VIEW);
274 }
275
276 for (ty_handle, ty) in self.module.types.iter() {
277 match ty.inner {
278 TypeInner::Scalar { kind, width } => self.scalar_required_features(kind, width),
279 TypeInner::Vector { kind, width, .. } => self.scalar_required_features(kind, width),
280 TypeInner::Matrix { width, .. } => {
281 self.scalar_required_features(ScalarKind::Float, width)
282 }
283 TypeInner::Array { base, size, .. } => {
284 if let TypeInner::Array { .. } = self.module.types[base].inner {
285 self.features.request(Features::ARRAY_OF_ARRAYS)
286 }
287
288 if size == crate::ArraySize::Dynamic {
290 let mut is_used = false;
291
292 for (global_handle, global) in self.module.global_variables.iter() {
294 if ep_info[global_handle].is_empty() {
296 continue;
297 }
298
299 if global.ty == ty_handle {
301 is_used = true;
302 break;
303 }
304
305 if let crate::TypeInner::Struct { ref members, .. } =
307 self.module.types[global.ty].inner
308 {
309 if let Some(last) = members.last() {
312 if last.ty == ty_handle {
313 is_used = true;
314 break;
315 }
316 }
317 }
318 }
319
320 if is_used {
322 self.features.request(Features::DYNAMIC_ARRAY_SIZE);
323 }
324 }
325 }
326 TypeInner::Image {
327 dim,
328 arrayed,
329 class,
330 } => {
331 if arrayed && dim == ImageDimension::Cube {
332 self.features.request(Features::CUBE_TEXTURES_ARRAY)
333 }
334
335 match class {
336 ImageClass::Sampled { multi: true, .. }
337 | ImageClass::Depth { multi: true } => {
338 self.features.request(Features::MULTISAMPLED_TEXTURES);
339 if arrayed {
340 self.features.request(Features::MULTISAMPLED_TEXTURE_ARRAYS);
341 }
342 }
343 ImageClass::Storage { format, .. } => match format {
344 StorageFormat::R8Unorm
345 | StorageFormat::R8Snorm
346 | StorageFormat::R8Uint
347 | StorageFormat::R8Sint
348 | StorageFormat::R16Uint
349 | StorageFormat::R16Sint
350 | StorageFormat::R16Float
351 | StorageFormat::Rg8Unorm
352 | StorageFormat::Rg8Snorm
353 | StorageFormat::Rg8Uint
354 | StorageFormat::Rg8Sint
355 | StorageFormat::Rg16Uint
356 | StorageFormat::Rg16Sint
357 | StorageFormat::Rg16Float
358 | StorageFormat::Rgb10a2Unorm
359 | StorageFormat::Rg11b10Float
360 | StorageFormat::Rg32Uint
361 | StorageFormat::Rg32Sint
362 | StorageFormat::Rg32Float => {
363 self.features.request(Features::FULL_IMAGE_FORMATS)
364 }
365 _ => {}
366 },
367 ImageClass::Sampled { multi: false, .. }
368 | ImageClass::Depth { multi: false } => {}
369 }
370 }
371 _ => {}
372 }
373 }
374
375 let mut push_constant_used = false;
376
377 for (handle, global) in self.module.global_variables.iter() {
378 if ep_info[handle].is_empty() {
379 continue;
380 }
381 match global.space {
382 AddressSpace::WorkGroup => self.features.request(Features::COMPUTE_SHADER),
383 AddressSpace::Storage { .. } => self.features.request(Features::BUFFER_STORAGE),
384 AddressSpace::PushConstant => {
385 if push_constant_used {
386 return Err(Error::MultiplePushConstants);
387 }
388 push_constant_used = true;
389 }
390 _ => {}
391 }
392 }
393
394 let &mut Self {
398 module,
399 info,
400 ref mut features,
401 entry_point,
402 entry_point_idx,
403 ref policies,
404 ..
405 } = self;
406
407 for (expressions, info) in module
410 .functions
411 .iter()
412 .map(|(h, f)| (&f.expressions, &info[h]))
413 .chain(std::iter::once((
414 &entry_point.function.expressions,
415 info.get_entry_point(entry_point_idx as usize),
416 )))
417 {
418 for (_, expr) in expressions.iter() {
419 match *expr {
420 Expression::ImageQuery {
422 image,
423 query,
424 ..
425 } => match query {
426 crate::ImageQuery::Size { .. } | crate::ImageQuery::NumLayers => {
431 if let TypeInner::Image {
432 class: crate::ImageClass::Storage { .. }, ..
433 } = *info[image].ty.inner_with(&module.types) {
434 features.request(Features::IMAGE_SIZE)
435 }
436 },
437 crate::ImageQuery::NumLevels => features.request(Features::TEXTURE_LEVELS),
438 crate::ImageQuery::NumSamples => features.request(Features::TEXTURE_SAMPLES),
439 }
440 ,
441 Expression::ImageLoad {
444 sample, level, ..
445 } => {
446 if policies.image_load != crate::proc::BoundsCheckPolicy::Unchecked {
447 if sample.is_some() {
448 features.request(Features::TEXTURE_SAMPLES)
449 }
450
451 if level.is_some() {
452 features.request(Features::TEXTURE_LEVELS)
453 }
454 }
455 }
456 _ => {}
457 }
458 }
459 }
460
461 self.features.check_availability(self.options.version)
462 }
463
464 fn scalar_required_features(&mut self, kind: ScalarKind, width: Bytes) {
466 if kind == ScalarKind::Float && width == 8 {
467 self.features.request(Features::DOUBLE_TYPE);
468 }
469 }
470
471 fn varying_required_features(&mut self, binding: Option<&Binding>, ty: Handle<Type>) {
472 match self.module.types[ty].inner {
473 crate::TypeInner::Struct { ref members, .. } => {
474 for member in members {
475 self.varying_required_features(member.binding.as_ref(), member.ty);
476 }
477 }
478 _ => {
479 if let Some(binding) = binding {
480 match *binding {
481 Binding::BuiltIn(built_in) => match built_in {
482 crate::BuiltIn::ClipDistance => {
483 self.features.request(Features::CLIP_DISTANCE)
484 }
485 crate::BuiltIn::CullDistance => {
486 self.features.request(Features::CULL_DISTANCE)
487 }
488 crate::BuiltIn::SampleIndex => {
489 self.features.request(Features::SAMPLE_VARIABLES)
490 }
491 crate::BuiltIn::ViewIndex => {
492 self.features.request(Features::MULTI_VIEW)
493 }
494 _ => {}
495 },
496 Binding::Location {
497 location: _,
498 interpolation,
499 sampling,
500 } => {
501 if interpolation == Some(Interpolation::Linear) {
502 self.features.request(Features::NOPERSPECTIVE_QUALIFIER);
503 }
504 if sampling == Some(Sampling::Sample) {
505 self.features.request(Features::SAMPLE_QUALIFIER);
506 }
507 }
508 }
509 }
510 }
511 }
512 }
513}