naga/back/glsl/writer.rs
1use super::*;
2
3/// Writer responsible for all code generation.
4#[expect(missing_debug_implementations, reason = "would be way too verbose?")]
5pub struct Writer<'a, W> {
6 // Inputs
7 /// The module being written.
8 pub(in crate::back::glsl) module: &'a crate::Module,
9 /// The module analysis.
10 pub(in crate::back::glsl) info: &'a valid::ModuleInfo,
11 /// The output writer.
12 out: W,
13 /// User defined configuration to be used.
14 pub(in crate::back::glsl) options: &'a Options,
15 /// The bound checking policies to be used
16 pub(in crate::back::glsl) policies: proc::BoundsCheckPolicies,
17
18 // Internal State
19 /// Features manager used to store all the needed features and write them.
20 pub(in crate::back::glsl) features: FeaturesManager,
21 namer: proc::Namer,
22 /// A map with all the names needed for writing the module
23 /// (generated by a [`Namer`](crate::proc::Namer)).
24 names: crate::FastHashMap<NameKey, String>,
25 /// A map with the names of global variables needed for reflections.
26 reflection_names_globals: crate::FastHashMap<Handle<crate::GlobalVariable>, String>,
27 /// The selected entry point.
28 pub(in crate::back::glsl) entry_point: &'a crate::EntryPoint,
29 /// The index of the selected entry point.
30 pub(in crate::back::glsl) entry_point_idx: proc::EntryPointIndex,
31 /// A generator for unique block numbers.
32 block_id: IdGenerator,
33 /// Set of expressions that have associated temporary variables.
34 named_expressions: crate::NamedExpressions,
35 /// Set of expressions that need to be baked to avoid unnecessary repetition in output
36 need_bake_expressions: back::NeedBakeExpressions,
37 /// Information about nesting of loops and switches.
38 ///
39 /// Used for forwarding continue statements in switches that have been
40 /// transformed to `do {} while(false);` loops.
41 continue_ctx: back::continue_forward::ContinueCtx,
42 /// How many views to render to, if doing multiview rendering.
43 pub(in crate::back::glsl) multiview: Option<core::num::NonZeroU32>,
44 /// Mapping of varying variables to their location. Needed for reflections.
45 varying: crate::FastHashMap<String, VaryingLocation>,
46 /// Number of user-defined clip planes. Only non-zero for vertex shaders.
47 clip_distance_count: u32,
48}
49
50impl<'a, W: Write> Writer<'a, W> {
51 /// Creates a new [`Writer`] instance.
52 ///
53 /// # Errors
54 /// - If the version specified is invalid or supported.
55 /// - If the entry point couldn't be found in the module.
56 /// - If the version specified doesn't support some used features.
57 pub fn new(
58 out: W,
59 module: &'a crate::Module,
60 info: &'a valid::ModuleInfo,
61 options: &'a Options,
62 pipeline_options: &'a PipelineOptions,
63 policies: proc::BoundsCheckPolicies,
64 ) -> Result<Self, Error> {
65 // Check if the requested version is supported
66 if !options.version.is_supported() {
67 log::error!("Version {}", options.version);
68 return Err(Error::VersionNotSupported);
69 }
70
71 // Try to find the entry point and corresponding index
72 let ep_idx = module
73 .entry_points
74 .iter()
75 .position(|ep| {
76 pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name
77 })
78 .ok_or(Error::EntryPointNotFound)?;
79
80 // Generate a map with names required to write the module
81 let mut names = crate::FastHashMap::default();
82 let mut namer = proc::Namer::default();
83 namer.reset(
84 module,
85 &keywords::RESERVED_KEYWORD_SET,
86 proc::KeywordSet::empty(),
87 proc::CaseInsensitiveKeywordSet::empty(),
88 &[
89 "gl_", // all GL built-in variables
90 "_group", // all normal bindings
91 "_immediates_binding_", // all immediate data bindings
92 ],
93 &mut names,
94 );
95
96 // Build the instance
97 let mut this = Self {
98 module,
99 info,
100 out,
101 options,
102 policies,
103
104 namer,
105 features: FeaturesManager::new(),
106 names,
107 reflection_names_globals: crate::FastHashMap::default(),
108 entry_point: &module.entry_points[ep_idx],
109 entry_point_idx: ep_idx as u16,
110 multiview: pipeline_options.multiview,
111 block_id: IdGenerator::default(),
112 named_expressions: Default::default(),
113 need_bake_expressions: Default::default(),
114 continue_ctx: back::continue_forward::ContinueCtx::default(),
115 varying: Default::default(),
116 clip_distance_count: 0,
117 };
118
119 // Find all features required to print this module
120 this.collect_required_features()?;
121
122 Ok(this)
123 }
124
125 /// Writes the [`Module`](crate::Module) as glsl to the output
126 ///
127 /// # Notes
128 /// If an error occurs while writing, the output might have been written partially
129 ///
130 /// # Panics
131 /// Might panic if the module is invalid
132 pub fn write(&mut self) -> Result<ReflectionInfo, Error> {
133 // We use `writeln!(self.out)` throughout the write to add newlines
134 // to make the output more readable
135
136 let es = self.options.version.is_es();
137
138 // Write the version (It must be the first thing or it isn't a valid glsl output)
139 writeln!(self.out, "#version {}", self.options.version)?;
140 // Write all the needed extensions
141 //
142 // This used to be the last thing being written as it allowed to search for features while
143 // writing the module saving some loops but some older versions (420 or less) required the
144 // extensions to appear before being used, even though extensions are part of the
145 // preprocessor not the processor ¯\_(ツ)_/¯
146 self.features.write(self.options, &mut self.out)?;
147
148 // glsl es requires a precision to be specified for floats and ints
149 // TODO: Should this be user configurable?
150 if es {
151 writeln!(self.out)?;
152 writeln!(self.out, "precision highp float;")?;
153 writeln!(self.out, "precision highp int;")?;
154 writeln!(self.out)?;
155 }
156
157 if self.entry_point.stage == ShaderStage::Compute {
158 let workgroup_size = self.entry_point.workgroup_size;
159 writeln!(
160 self.out,
161 "layout(local_size_x = {}, local_size_y = {}, local_size_z = {}) in;",
162 workgroup_size[0], workgroup_size[1], workgroup_size[2]
163 )?;
164 writeln!(self.out)?;
165 }
166
167 if self.entry_point.stage == ShaderStage::Vertex
168 && !self
169 .options
170 .writer_flags
171 .contains(WriterFlags::DRAW_PARAMETERS)
172 && self.features.contains(Features::INSTANCE_INDEX)
173 {
174 writeln!(self.out, "uniform uint {FIRST_INSTANCE_BINDING};")?;
175 writeln!(self.out)?;
176 }
177
178 // Enable early depth tests if needed
179 if let Some(early_depth_test) = self.entry_point.early_depth_test {
180 // If early depth test is supported for this version of GLSL
181 if self.options.version.supports_early_depth_test() {
182 match early_depth_test {
183 crate::EarlyDepthTest::Force => {
184 writeln!(self.out, "layout(early_fragment_tests) in;")?;
185 }
186 crate::EarlyDepthTest::Allow { conservative, .. } => {
187 use crate::ConservativeDepth as Cd;
188 let depth = match conservative {
189 Cd::GreaterEqual => "greater",
190 Cd::LessEqual => "less",
191 Cd::Unchanged => "unchanged",
192 };
193 writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?;
194 }
195 }
196 } else {
197 log::warn!(
198 "Early depth testing is not supported for this version of GLSL: {}",
199 self.options.version
200 );
201 }
202 }
203
204 if self.entry_point.stage == ShaderStage::Vertex && self.options.version.is_webgl() {
205 if let Some(multiview) = self.multiview.as_ref() {
206 writeln!(self.out, "layout(num_views = {multiview}) in;")?;
207 writeln!(self.out)?;
208 }
209 }
210
211 // Write struct types.
212 //
213 // This are always ordered because the IR is structured in a way that
214 // you can't make a struct without adding all of its members first.
215 for (handle, ty) in self.module.types.iter() {
216 if let TypeInner::Struct { ref members, .. } = ty.inner {
217 let struct_name = &self.names[&NameKey::Type(handle)];
218
219 // Structures ending with runtime-sized arrays can only be
220 // rendered as shader storage blocks in GLSL, not stand-alone
221 // struct types.
222 if !self.module.types[members.last().unwrap().ty]
223 .inner
224 .is_dynamically_sized(&self.module.types)
225 {
226 write!(self.out, "struct {struct_name} ")?;
227 self.write_struct_body(handle, members)?;
228 writeln!(self.out, ";")?;
229 }
230 }
231 }
232
233 // Write functions for special types.
234 for (type_key, struct_ty) in self.module.special_types.predeclared_types.iter() {
235 match type_key {
236 &crate::PredeclaredType::ModfResult { size, scalar }
237 | &crate::PredeclaredType::FrexpResult { size, scalar } => {
238 let struct_name = &self.names[&NameKey::Type(*struct_ty)];
239 let arg_type_name_owner;
240 let arg_type_name = if let Some(size) = size {
241 arg_type_name_owner = format!(
242 "{}vec{}",
243 if scalar.width == 8 { "d" } else { "" },
244 size as u8
245 );
246 &arg_type_name_owner
247 } else if scalar.width == 8 {
248 "double"
249 } else {
250 "float"
251 };
252
253 let other_type_name_owner;
254 let (defined_func_name, called_func_name, other_type_name) =
255 if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) {
256 (MODF_FUNCTION, "modf", arg_type_name)
257 } else {
258 let other_type_name = if let Some(size) = size {
259 other_type_name_owner = format!("ivec{}", size as u8);
260 &other_type_name_owner
261 } else {
262 "int"
263 };
264 (FREXP_FUNCTION, "frexp", other_type_name)
265 };
266
267 writeln!(self.out)?;
268 if !self.options.version.supports_frexp_function()
269 && matches!(type_key, &crate::PredeclaredType::FrexpResult { .. })
270 {
271 writeln!(
272 self.out,
273 "{struct_name} {defined_func_name}({arg_type_name} arg) {{
274 {other_type_name} other = arg == {arg_type_name}(0) ? {other_type_name}(0) : {other_type_name}({arg_type_name}(1) + log2(arg));
275 {arg_type_name} fract = arg * exp2({arg_type_name}(-other));
276 return {struct_name}(fract, other);
277}}",
278 )?;
279 } else {
280 writeln!(
281 self.out,
282 "{struct_name} {defined_func_name}({arg_type_name} arg) {{
283 {other_type_name} other;
284 {arg_type_name} fract = {called_func_name}(arg, other);
285 return {struct_name}(fract, other);
286}}",
287 )?;
288 }
289 }
290 &crate::PredeclaredType::AtomicCompareExchangeWeakResult(_) => {
291 // Handled by the general struct writing loop earlier.
292 }
293 }
294 }
295
296 // Write all named constants
297 let mut constants = self
298 .module
299 .constants
300 .iter()
301 .filter(|&(_, c)| c.name.is_some())
302 .peekable();
303 while let Some((handle, _)) = constants.next() {
304 self.write_global_constant(handle)?;
305 // Add extra newline for readability on last iteration
306 if constants.peek().is_none() {
307 writeln!(self.out)?;
308 }
309 }
310
311 let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
312
313 // Write the globals
314 //
315 // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS,
316 // we filter all globals that aren't used by the selected entry point as they might be
317 // interfere with each other (i.e. two globals with the same location but different with
318 // different classes)
319 let include_unused = self
320 .options
321 .writer_flags
322 .contains(WriterFlags::INCLUDE_UNUSED_ITEMS);
323 for (handle, global) in self.module.global_variables.iter() {
324 let is_unused = ep_info[handle].is_empty();
325 if !include_unused && is_unused {
326 continue;
327 }
328
329 match self.module.types[global.ty].inner {
330 // We treat images separately because they might require
331 // writing the storage format
332 TypeInner::Image {
333 mut dim,
334 arrayed,
335 class,
336 } => {
337 // Gather the storage format if needed
338 let storage_format_access = match self.module.types[global.ty].inner {
339 TypeInner::Image {
340 class: crate::ImageClass::Storage { format, access },
341 ..
342 } => Some((format, access)),
343 _ => None,
344 };
345
346 if dim == crate::ImageDimension::D1 && es {
347 dim = crate::ImageDimension::D2
348 }
349
350 // Gether the location if needed
351 let layout_binding = if self.options.version.supports_explicit_locations() {
352 let br = global.binding.as_ref().unwrap();
353 self.options.binding_map.get(br).cloned()
354 } else {
355 None
356 };
357
358 // Write all the layout qualifiers
359 if layout_binding.is_some() || storage_format_access.is_some() {
360 write!(self.out, "layout(")?;
361 if let Some(binding) = layout_binding {
362 write!(self.out, "binding = {binding}")?;
363 }
364 if let Some((format, _)) = storage_format_access {
365 let format_str = glsl_storage_format(format)?;
366 let separator = match layout_binding {
367 Some(_) => ",",
368 None => "",
369 };
370 write!(self.out, "{separator}{format_str}")?;
371 }
372 write!(self.out, ") ")?;
373 }
374
375 if let Some((_, access)) = storage_format_access {
376 self.write_storage_access(access)?;
377 }
378
379 // All images in glsl are `uniform`
380 // The trailing space is important
381 write!(self.out, "uniform ")?;
382
383 // write the type
384 //
385 // This is way we need the leading space because `write_image_type` doesn't add
386 // any spaces at the beginning or end
387 self.write_image_type(dim, arrayed, class)?;
388
389 // Finally write the name and end the global with a `;`
390 // The leading space is important
391 let global_name = self.get_global_name(handle, global);
392 writeln!(self.out, " {global_name};")?;
393 writeln!(self.out)?;
394
395 self.reflection_names_globals.insert(handle, global_name);
396 }
397 // glsl has no concept of samplers so we just ignore it
398 TypeInner::Sampler { .. } => continue,
399 // All other globals are written by `write_global`
400 _ => {
401 self.write_global(handle, global)?;
402 // Add a newline (only for readability)
403 writeln!(self.out)?;
404 }
405 }
406 }
407
408 for arg in self.entry_point.function.arguments.iter() {
409 self.write_varying(arg.binding.as_ref(), arg.ty, false)?;
410 }
411 if let Some(ref result) = self.entry_point.function.result {
412 self.write_varying(result.binding.as_ref(), result.ty, true)?;
413 }
414 writeln!(self.out)?;
415
416 // Write all regular functions
417 for (handle, function) in self.module.functions.iter() {
418 // Check that the function doesn't use globals that aren't supported
419 // by the current entry point
420 if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) {
421 continue;
422 }
423
424 let fun_info = &self.info[handle];
425
426 // Skip functions that that are not compatible with this entry point's stage.
427 //
428 // When validation is enabled, it rejects modules whose entry points try to call
429 // incompatible functions, so if we got this far, then any functions incompatible
430 // with our selected entry point must not be used.
431 //
432 // When validation is disabled, `fun_info.available_stages` is always just
433 // `ShaderStages::all()`, so this will write all functions in the module, and
434 // the downstream GLSL compiler will catch any problems.
435 if !fun_info.available_stages.contains(ep_info.available_stages) {
436 continue;
437 }
438
439 // Write the function
440 self.write_function(back::FunctionType::Function(handle), function, fun_info)?;
441
442 writeln!(self.out)?;
443 }
444
445 self.write_function(
446 back::FunctionType::EntryPoint(self.entry_point_idx),
447 &self.entry_point.function,
448 ep_info,
449 )?;
450
451 // Add newline at the end of file
452 writeln!(self.out)?;
453
454 // Collect all reflection info and return it to the user
455 self.collect_reflection_info()
456 }
457
458 fn write_array_size(
459 &mut self,
460 base: Handle<crate::Type>,
461 size: crate::ArraySize,
462 ) -> BackendResult {
463 write!(self.out, "[")?;
464
465 // Write the array size
466 // Writes nothing if `IndexableLength::Dynamic`
467 match size.resolve(self.module.to_ctx())? {
468 proc::IndexableLength::Known(size) => {
469 write!(self.out, "{size}")?;
470 }
471 proc::IndexableLength::Dynamic => (),
472 }
473
474 write!(self.out, "]")?;
475
476 if let TypeInner::Array {
477 base: next_base,
478 size: next_size,
479 ..
480 } = self.module.types[base].inner
481 {
482 self.write_array_size(next_base, next_size)?;
483 }
484
485 Ok(())
486 }
487
488 /// Helper method used to write value types
489 ///
490 /// # Notes
491 /// Adds no trailing or leading whitespace
492 fn write_value_type(&mut self, inner: &TypeInner) -> BackendResult {
493 match *inner {
494 // Scalars are simple we just get the full name from `glsl_scalar`
495 TypeInner::Scalar(scalar)
496 | TypeInner::Atomic(scalar)
497 | TypeInner::ValuePointer {
498 size: None,
499 scalar,
500 space: _,
501 } => write!(self.out, "{}", glsl_scalar(scalar)?.full)?,
502 // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size
503 TypeInner::Vector { size, scalar }
504 | TypeInner::ValuePointer {
505 size: Some(size),
506 scalar,
507 space: _,
508 } => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?,
509 // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and
510 // doubles are allowed), `M` is the columns count and `N` is the rows count
511 //
512 // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the
513 // extra branch to write matrices this way
514 TypeInner::Matrix {
515 columns,
516 rows,
517 scalar,
518 } => write!(
519 self.out,
520 "{}mat{}x{}",
521 glsl_scalar(scalar)?.prefix,
522 columns as u8,
523 rows as u8
524 )?,
525 // GLSL arrays are written as `type name[size]`
526 // Here we only write the size of the array i.e. `[size]`
527 // Base `type` and `name` should be written outside
528 TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?,
529 // Write all variants instead of `_` so that if new variants are added a
530 // no exhaustiveness error is thrown
531 TypeInner::Pointer { .. }
532 | TypeInner::Struct { .. }
533 | TypeInner::Image { .. }
534 | TypeInner::Sampler { .. }
535 | TypeInner::AccelerationStructure { .. }
536 | TypeInner::RayQuery { .. }
537 | TypeInner::BindingArray { .. }
538 | TypeInner::CooperativeMatrix { .. } => {
539 return Err(Error::Custom(format!("Unable to write type {inner:?}")))
540 }
541 }
542
543 Ok(())
544 }
545
546 /// Helper method used to write non image/sampler types
547 ///
548 /// # Notes
549 /// Adds no trailing or leading whitespace
550 fn write_type(&mut self, ty: Handle<crate::Type>) -> BackendResult {
551 match self.module.types[ty].inner {
552 // glsl has no pointer types so just write types as normal and loads are skipped
553 TypeInner::Pointer { base, .. } => self.write_type(base),
554 // glsl structs are written as just the struct name
555 TypeInner::Struct { .. } => {
556 // Get the struct name
557 let name = &self.names[&NameKey::Type(ty)];
558 write!(self.out, "{name}")?;
559 Ok(())
560 }
561 // glsl array has the size separated from the base type
562 TypeInner::Array { base, .. } => self.write_type(base),
563 ref other => self.write_value_type(other),
564 }
565 }
566
567 /// Helper method to write a image type
568 ///
569 /// # Notes
570 /// Adds no leading or trailing whitespace
571 fn write_image_type(
572 &mut self,
573 dim: crate::ImageDimension,
574 arrayed: bool,
575 class: crate::ImageClass,
576 ) -> BackendResult {
577 // glsl images consist of four parts the scalar prefix, the image "type", the dimensions
578 // and modifiers
579 //
580 // There exists two image types
581 // - sampler - for sampled images
582 // - image - for storage images
583 //
584 // There are three possible modifiers that can be used together and must be written in
585 // this order to be valid
586 // - MS - used if it's a multisampled image
587 // - Array - used if it's an image array
588 // - Shadow - used if it's a depth image
589 use crate::ImageClass as Ic;
590 use crate::Scalar as S;
591 let float = S {
592 kind: crate::ScalarKind::Float,
593 width: 4,
594 };
595 let (base, scalar, ms, comparison) = match class {
596 Ic::Sampled { kind, multi: true } => ("sampler", S { kind, width: 4 }, "MS", ""),
597 Ic::Sampled { kind, multi: false } => ("sampler", S { kind, width: 4 }, "", ""),
598 Ic::Depth { multi: true } => ("sampler", float, "MS", ""),
599 Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"),
600 Ic::Storage { format, .. } => ("image", format.into(), "", ""),
601 Ic::External => unimplemented!(),
602 };
603
604 let precision = if self.options.version.is_es() {
605 "highp "
606 } else {
607 ""
608 };
609
610 write!(
611 self.out,
612 "{}{}{}{}{}{}{}",
613 precision,
614 glsl_scalar(scalar)?.prefix,
615 base,
616 glsl_dimension(dim),
617 ms,
618 if arrayed { "Array" } else { "" },
619 comparison
620 )?;
621
622 Ok(())
623 }
624
625 /// Helper method used by [Self::write_global] to write just the layout part of
626 /// a non image/sampler global variable, if applicable.
627 ///
628 /// # Notes
629 ///
630 /// Adds trailing whitespace if any layout qualifier is written
631 fn write_global_layout(&mut self, global: &crate::GlobalVariable) -> BackendResult {
632 // Determine which (if any) explicit memory layout to use, and whether we support it
633 let layout = match global.space {
634 crate::AddressSpace::Uniform => {
635 if !self.options.version.supports_std140_layout() {
636 return Err(Error::Custom(
637 "Uniform address space requires std140 layout support".to_string(),
638 ));
639 }
640
641 Some("std140")
642 }
643 crate::AddressSpace::Storage { .. } => {
644 if !self.options.version.supports_std430_layout() {
645 return Err(Error::Custom(
646 "Storage address space requires std430 layout support".to_string(),
647 ));
648 }
649
650 Some("std430")
651 }
652 _ => None,
653 };
654
655 // If our version supports explicit layouts, we can also output the explicit binding
656 // if we have it
657 if self.options.version.supports_explicit_locations() {
658 if let Some(ref br) = global.binding {
659 match self.options.binding_map.get(br) {
660 Some(binding) => {
661 write!(self.out, "layout(")?;
662
663 if let Some(layout) = layout {
664 write!(self.out, "{layout}, ")?;
665 }
666
667 write!(self.out, "binding = {binding}) ")?;
668
669 return Ok(());
670 }
671 None => {
672 log::debug!("unassigned binding for {:?}", global.name);
673 }
674 }
675 }
676 }
677
678 // Either no explicit bindings are supported or we didn't have any.
679 // Write just the memory layout.
680 if let Some(layout) = layout {
681 write!(self.out, "layout({layout}) ")?;
682 }
683
684 Ok(())
685 }
686
687 /// Helper method used to write non images/sampler globals
688 ///
689 /// # Notes
690 /// Adds a newline
691 ///
692 /// # Panics
693 /// If the global has type sampler
694 fn write_global(
695 &mut self,
696 handle: Handle<crate::GlobalVariable>,
697 global: &crate::GlobalVariable,
698 ) -> BackendResult {
699 self.write_global_layout(global)?;
700
701 if let crate::AddressSpace::Storage { access } = global.space {
702 self.write_storage_access(access)?;
703 if global
704 .memory_decorations
705 .contains(crate::MemoryDecorations::COHERENT)
706 {
707 write!(self.out, "coherent ")?;
708 }
709 if global
710 .memory_decorations
711 .contains(crate::MemoryDecorations::VOLATILE)
712 {
713 write!(self.out, "volatile ")?;
714 }
715 }
716
717 if let Some(storage_qualifier) = glsl_storage_qualifier(global.space) {
718 write!(self.out, "{storage_qualifier} ")?;
719 }
720
721 match global.space {
722 crate::AddressSpace::Private => {
723 self.write_simple_global(handle, global)?;
724 }
725 crate::AddressSpace::WorkGroup => {
726 self.write_simple_global(handle, global)?;
727 }
728 crate::AddressSpace::Immediate => {
729 self.write_simple_global(handle, global)?;
730 }
731 crate::AddressSpace::Uniform => {
732 self.write_interface_block(handle, global)?;
733 }
734 crate::AddressSpace::Storage { .. } => {
735 self.write_interface_block(handle, global)?;
736 }
737 crate::AddressSpace::TaskPayload => {
738 self.write_interface_block(handle, global)?;
739 }
740 // A global variable in the `Function` address space is a
741 // contradiction in terms.
742 crate::AddressSpace::Function => unreachable!(),
743 // Textures and samplers are handled directly in `Writer::write`.
744 crate::AddressSpace::Handle => unreachable!(),
745 // ray tracing pipelines unsupported
746 crate::AddressSpace::RayPayload | crate::AddressSpace::IncomingRayPayload => {
747 unreachable!()
748 }
749 }
750
751 Ok(())
752 }
753
754 fn write_simple_global(
755 &mut self,
756 handle: Handle<crate::GlobalVariable>,
757 global: &crate::GlobalVariable,
758 ) -> BackendResult {
759 self.write_type(global.ty)?;
760 write!(self.out, " ")?;
761 self.write_global_name(handle, global)?;
762
763 if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner {
764 self.write_array_size(base, size)?;
765 }
766
767 if global.space.initializable() && is_value_init_supported(self.module, global.ty) {
768 write!(self.out, " = ")?;
769 if let Some(init) = global.init {
770 self.write_const_expr(init, &self.module.global_expressions)?;
771 } else {
772 self.write_zero_init_value(global.ty)?;
773 }
774 }
775
776 writeln!(self.out, ";")?;
777
778 if let crate::AddressSpace::Immediate = global.space {
779 let global_name = self.get_global_name(handle, global);
780 self.reflection_names_globals.insert(handle, global_name);
781 }
782
783 Ok(())
784 }
785
786 /// Write an interface block for a single Naga global.
787 ///
788 /// Write `block_name { members }`. Since `block_name` must be unique
789 /// between blocks and structs, we add `_block_ID` where `ID` is a
790 /// `IdGenerator` generated number. Write `members` in the same way we write
791 /// a struct's members.
792 fn write_interface_block(
793 &mut self,
794 handle: Handle<crate::GlobalVariable>,
795 global: &crate::GlobalVariable,
796 ) -> BackendResult {
797 // Write the block name, it's just the struct name appended with `_block_ID`
798 let ty_name = &self.names[&NameKey::Type(global.ty)];
799 let block_name = format!(
800 "{}_block_{}{:?}",
801 // avoid double underscores as they are reserved in GLSL
802 ty_name.trim_end_matches('_'),
803 self.block_id.generate(),
804 self.entry_point.stage,
805 );
806 write!(self.out, "{block_name} ")?;
807 self.reflection_names_globals.insert(handle, block_name);
808
809 match self.module.types[global.ty].inner {
810 TypeInner::Struct { ref members, .. }
811 if self.module.types[members.last().unwrap().ty]
812 .inner
813 .is_dynamically_sized(&self.module.types) =>
814 {
815 // Structs with dynamically sized arrays must have their
816 // members lifted up as members of the interface block. GLSL
817 // can't write such struct types anyway.
818 self.write_struct_body(global.ty, members)?;
819 write!(self.out, " ")?;
820 self.write_global_name(handle, global)?;
821 }
822 _ => {
823 // A global of any other type is written as the sole member
824 // of the interface block. Since the interface block is
825 // anonymous, this becomes visible in the global scope.
826 write!(self.out, "{{ ")?;
827 self.write_type(global.ty)?;
828 write!(self.out, " ")?;
829 self.write_global_name(handle, global)?;
830 if let TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner {
831 self.write_array_size(base, size)?;
832 }
833 write!(self.out, "; }}")?;
834 }
835 }
836
837 writeln!(self.out, ";")?;
838
839 Ok(())
840 }
841
842 /// Helper method used to find which expressions of a given function require baking
843 ///
844 /// # Notes
845 /// Clears `need_bake_expressions` set before adding to it
846 fn update_expressions_to_bake(&mut self, func: &crate::Function, info: &valid::FunctionInfo) {
847 use crate::Expression;
848 self.need_bake_expressions.clear();
849 for (fun_handle, expr) in func.expressions.iter() {
850 let expr_info = &info[fun_handle];
851 let min_ref_count = func.expressions[fun_handle].bake_ref_count();
852 if min_ref_count <= expr_info.ref_count {
853 self.need_bake_expressions.insert(fun_handle);
854 }
855
856 let inner = expr_info.ty.inner_with(&self.module.types);
857
858 if let Expression::Math {
859 fun,
860 arg,
861 arg1,
862 arg2,
863 ..
864 } = *expr
865 {
866 match fun {
867 crate::MathFunction::Dot => {
868 // if the expression is a Dot product with integer arguments,
869 // then the args needs baking as well
870 if let TypeInner::Scalar(crate::Scalar {
871 kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
872 ..
873 }) = *inner
874 {
875 self.need_bake_expressions.insert(arg);
876 self.need_bake_expressions.insert(arg1.unwrap());
877 }
878 }
879 crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => {
880 self.need_bake_expressions.insert(arg);
881 self.need_bake_expressions.insert(arg1.unwrap());
882 }
883 crate::MathFunction::Pack4xI8
884 | crate::MathFunction::Pack4xU8
885 | crate::MathFunction::Pack4xI8Clamp
886 | crate::MathFunction::Pack4xU8Clamp
887 | crate::MathFunction::Unpack4xI8
888 | crate::MathFunction::Unpack4xU8
889 | crate::MathFunction::QuantizeToF16 => {
890 self.need_bake_expressions.insert(arg);
891 }
892 /* crate::MathFunction::Pack4x8unorm | */
893 crate::MathFunction::Unpack4x8snorm
894 if !self.options.version.supports_pack_unpack_4x8() =>
895 {
896 // We have a fallback if the platform doesn't natively support these
897 self.need_bake_expressions.insert(arg);
898 }
899 /* crate::MathFunction::Pack4x8unorm | */
900 crate::MathFunction::Unpack4x8unorm
901 if !self.options.version.supports_pack_unpack_4x8() =>
902 {
903 self.need_bake_expressions.insert(arg);
904 }
905 /* crate::MathFunction::Pack2x16snorm | */
906 crate::MathFunction::Unpack2x16snorm
907 if !self.options.version.supports_pack_unpack_snorm_2x16() =>
908 {
909 self.need_bake_expressions.insert(arg);
910 }
911 /* crate::MathFunction::Pack2x16unorm | */
912 crate::MathFunction::Unpack2x16unorm
913 if !self.options.version.supports_pack_unpack_unorm_2x16() =>
914 {
915 self.need_bake_expressions.insert(arg);
916 }
917 crate::MathFunction::ExtractBits => {
918 // Only argument 1 is re-used.
919 self.need_bake_expressions.insert(arg1.unwrap());
920 }
921 crate::MathFunction::InsertBits => {
922 // Only argument 2 is re-used.
923 self.need_bake_expressions.insert(arg2.unwrap());
924 }
925 crate::MathFunction::CountLeadingZeros => {
926 if let Some(crate::ScalarKind::Sint) = inner.scalar_kind() {
927 self.need_bake_expressions.insert(arg);
928 }
929 }
930 _ => {}
931 }
932 }
933
934 if let Expression::Binary {
935 op: crate::BinaryOperator::Modulo,
936 left,
937 right,
938 } = *expr
939 {
940 // Integer `%` is lowered to `left - right * (left / right)` in
941 // write_expr (`BinaryOperation::ModuloInt`), which references each
942 // operand twice, so bake both to avoid re-evaluating them.
943 if let Some(crate::ScalarKind::Sint | crate::ScalarKind::Uint) = inner.scalar_kind()
944 {
945 self.need_bake_expressions.insert(left);
946 self.need_bake_expressions.insert(right);
947 }
948 }
949 }
950
951 for statement in func.body.iter() {
952 match *statement {
953 crate::Statement::Atomic {
954 fun: crate::AtomicFunction::Exchange { compare: Some(cmp) },
955 ..
956 } => {
957 self.need_bake_expressions.insert(cmp);
958 }
959 _ => {}
960 }
961 }
962 }
963
964 /// Helper method used to get a name for a global
965 ///
966 /// Globals have different naming schemes depending on their binding:
967 /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer)
968 /// - Globals with resource binding are named `_group_X_binding_Y` where `X`
969 /// is the group and `Y` is the binding
970 fn get_global_name(
971 &self,
972 handle: Handle<crate::GlobalVariable>,
973 global: &crate::GlobalVariable,
974 ) -> String {
975 match (&global.binding, global.space) {
976 (&Some(ref br), _) => {
977 format!(
978 "_group_{}_binding_{}_{}",
979 br.group,
980 br.binding,
981 shader_stage_to_str(self.entry_point.stage)
982 )
983 }
984 (&None, crate::AddressSpace::Immediate) => {
985 format!(
986 "_immediates_binding_{}",
987 shader_stage_to_str(self.entry_point.stage)
988 )
989 }
990 (&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(),
991 }
992 }
993
994 /// Helper method used to write a name for a global without additional heap allocation
995 fn write_global_name(
996 &mut self,
997 handle: Handle<crate::GlobalVariable>,
998 global: &crate::GlobalVariable,
999 ) -> BackendResult {
1000 match (&global.binding, global.space) {
1001 (&Some(ref br), _) => write!(
1002 self.out,
1003 "_group_{}_binding_{}_{}",
1004 br.group,
1005 br.binding,
1006 shader_stage_to_str(self.entry_point.stage)
1007 )?,
1008 (&None, crate::AddressSpace::Immediate) => write!(
1009 self.out,
1010 "_immediates_binding_{}",
1011 shader_stage_to_str(self.entry_point.stage)
1012 )?,
1013 (&None, _) => write!(
1014 self.out,
1015 "{}",
1016 &self.names[&NameKey::GlobalVariable(handle)]
1017 )?,
1018 }
1019
1020 Ok(())
1021 }
1022
1023 /// Write a GLSL global that will carry a Naga entry point's argument or return value.
1024 ///
1025 /// A Naga entry point's arguments and return value are rendered in GLSL as
1026 /// variables at global scope with the `in` and `out` storage qualifiers.
1027 /// The code we generate for `main` loads from all the `in` globals into
1028 /// appropriately named locals. Before it returns, `main` assigns the
1029 /// components of its return value into all the `out` globals.
1030 ///
1031 /// This function writes a declaration for one such GLSL global,
1032 /// representing a value passed into or returned from [`self.entry_point`]
1033 /// that has a [`Location`] binding. The global's name is generated based on
1034 /// the location index and the shader stages being connected; see
1035 /// [`VaryingName`]. This means we don't need to know the names of
1036 /// arguments, just their types and bindings.
1037 ///
1038 /// Emit nothing for entry point arguments or return values with [`BuiltIn`]
1039 /// bindings; `main` will read from or assign to the appropriate GLSL
1040 /// special variable; these are pre-declared. As an exception, we do declare
1041 /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if
1042 /// needed.
1043 ///
1044 /// Use `output` together with [`self.entry_point.stage`] to determine which
1045 /// shader stages are being connected, and choose the `in` or `out` storage
1046 /// qualifier.
1047 ///
1048 /// [`self.entry_point`]: Writer::entry_point
1049 /// [`self.entry_point.stage`]: crate::EntryPoint::stage
1050 /// [`Location`]: crate::Binding::Location
1051 /// [`BuiltIn`]: crate::Binding::BuiltIn
1052 fn write_varying(
1053 &mut self,
1054 binding: Option<&crate::Binding>,
1055 ty: Handle<crate::Type>,
1056 output: bool,
1057 ) -> Result<(), Error> {
1058 // For a struct, emit a separate global for each member with a binding.
1059 if let TypeInner::Struct { ref members, .. } = self.module.types[ty].inner {
1060 for member in members {
1061 self.write_varying(member.binding.as_ref(), member.ty, output)?;
1062 }
1063 return Ok(());
1064 }
1065
1066 let binding = match binding {
1067 None => return Ok(()),
1068 Some(binding) => binding,
1069 };
1070
1071 let (location, interpolation, sampling, blend_src) = match *binding {
1072 crate::Binding::Location {
1073 location,
1074 interpolation,
1075 sampling,
1076 blend_src,
1077 per_primitive: _,
1078 } => (location, interpolation, sampling, blend_src),
1079 crate::Binding::BuiltIn(built_in) => {
1080 match built_in {
1081 crate::BuiltIn::Position { invariant: true } => {
1082 match (self.options.version, self.entry_point.stage) {
1083 (
1084 Version::Embedded {
1085 version: 300,
1086 is_webgl: true,
1087 },
1088 ShaderStage::Fragment,
1089 ) => {
1090 // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly
1091 // OpenGL ES in general (waiting on confirmation).
1092 //
1093 // See https://github.com/KhronosGroup/WebGL/issues/3518
1094 }
1095 _ => {
1096 writeln!(
1097 self.out,
1098 "invariant {};",
1099 glsl_built_in(
1100 built_in,
1101 VaryingOptions::from_writer_options(self.options, output)
1102 )
1103 )?;
1104 }
1105 }
1106 }
1107 crate::BuiltIn::ClipDistances => {
1108 // Re-declare `gl_ClipDistance` with number of clip planes.
1109 let TypeInner::Array { size, .. } = self.module.types[ty].inner else {
1110 unreachable!();
1111 };
1112 let proc::IndexableLength::Known(size) =
1113 size.resolve(self.module.to_ctx())?
1114 else {
1115 unreachable!();
1116 };
1117 self.clip_distance_count = size;
1118 writeln!(self.out, "out float gl_ClipDistance[{size}];")?;
1119 }
1120 _ => {}
1121 }
1122 return Ok(());
1123 }
1124 };
1125
1126 // Write the interpolation modifier if needed
1127 //
1128 // We ignore all interpolation and auxiliary modifiers that aren't used in fragment
1129 // shaders' input globals or vertex shaders' output globals.
1130 let emit_interpolation_and_auxiliary = match self.entry_point.stage {
1131 ShaderStage::Vertex => output,
1132 ShaderStage::Fragment => !output,
1133 ShaderStage::Compute => false,
1134 ShaderStage::Task
1135 | ShaderStage::Mesh
1136 | ShaderStage::RayGeneration
1137 | ShaderStage::AnyHit
1138 | ShaderStage::ClosestHit
1139 | ShaderStage::Miss => unreachable!(),
1140 };
1141
1142 // Write the I/O locations, if allowed
1143 let io_location = if self.options.version.supports_explicit_locations()
1144 || !emit_interpolation_and_auxiliary
1145 {
1146 if self.options.version.supports_io_locations() {
1147 if let Some(blend_src) = blend_src {
1148 write!(
1149 self.out,
1150 "layout(location = {location}, index = {blend_src}) "
1151 )?;
1152 } else {
1153 write!(self.out, "layout(location = {location}) ")?;
1154 }
1155 None
1156 } else {
1157 Some(VaryingLocation {
1158 location,
1159 index: blend_src.unwrap_or(0),
1160 })
1161 }
1162 } else {
1163 None
1164 };
1165
1166 // Write the interpolation qualifier.
1167 if let Some(interp) = interpolation {
1168 if emit_interpolation_and_auxiliary {
1169 write!(self.out, "{} ", glsl_interpolation(interp))?;
1170 }
1171 }
1172
1173 // Write the sampling auxiliary qualifier.
1174 //
1175 // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear
1176 // immediately before the `in` / `out` qualifier, so we'll just follow that rule
1177 // here, regardless of the version.
1178 if let Some(sampling) = sampling {
1179 if emit_interpolation_and_auxiliary {
1180 if let Some(qualifier) = glsl_sampling(sampling)? {
1181 write!(self.out, "{qualifier} ")?;
1182 }
1183 }
1184 }
1185
1186 // Write the input/output qualifier.
1187 write!(self.out, "{} ", if output { "out" } else { "in" })?;
1188
1189 // Write the type
1190 // `write_type` adds no leading or trailing spaces
1191 self.write_type(ty)?;
1192
1193 // Finally write the global name and end the global with a `;` and a newline
1194 // Leading space is important
1195 let vname = VaryingName {
1196 binding: &crate::Binding::Location {
1197 location,
1198 interpolation: None,
1199 sampling: None,
1200 blend_src,
1201 per_primitive: false,
1202 },
1203 stage: self.entry_point.stage,
1204 options: VaryingOptions::from_writer_options(self.options, output),
1205 };
1206 writeln!(self.out, " {vname};")?;
1207
1208 if let Some(location) = io_location {
1209 self.varying.insert(vname.to_string(), location);
1210 }
1211
1212 Ok(())
1213 }
1214
1215 /// Helper method used to write functions (both entry points and regular functions)
1216 ///
1217 /// # Notes
1218 /// Adds a newline
1219 fn write_function(
1220 &mut self,
1221 ty: back::FunctionType,
1222 func: &crate::Function,
1223 info: &valid::FunctionInfo,
1224 ) -> BackendResult {
1225 // Create a function context for the function being written
1226 let ctx = back::FunctionCtx {
1227 ty,
1228 info,
1229 expressions: &func.expressions,
1230 named_expressions: &func.named_expressions,
1231 };
1232
1233 self.named_expressions.clear();
1234 self.update_expressions_to_bake(func, info);
1235
1236 // Write the function header
1237 //
1238 // glsl headers are the same as in c:
1239 // `ret_type name(args)`
1240 // `ret_type` is the return type
1241 // `name` is the function name
1242 // `args` is a comma separated list of `type name`
1243 // | - `type` is the argument type
1244 // | - `name` is the argument name
1245
1246 // Start by writing the return type if any otherwise write void
1247 // This is the only place where `void` is a valid type
1248 // (though it's more a keyword than a type)
1249 if let back::FunctionType::EntryPoint(_) = ctx.ty {
1250 write!(self.out, "void")?;
1251 } else if let Some(ref result) = func.result {
1252 self.write_type(result.ty)?;
1253 if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner {
1254 self.write_array_size(base, size)?
1255 }
1256 } else {
1257 write!(self.out, "void")?;
1258 }
1259
1260 // Write the function name and open parentheses for the argument list
1261 let function_name = match ctx.ty {
1262 back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)],
1263 back::FunctionType::EntryPoint(_) => "main",
1264 };
1265 write!(self.out, " {function_name}(")?;
1266
1267 // Write the comma separated argument list
1268 //
1269 // We need access to `Self` here so we use the reference passed to the closure as an
1270 // argument instead of capturing as that would cause a borrow checker error
1271 let arguments = match ctx.ty {
1272 back::FunctionType::EntryPoint(_) => &[][..],
1273 back::FunctionType::Function(_) => &func.arguments,
1274 };
1275 let arguments: Vec<_> = arguments
1276 .iter()
1277 .enumerate()
1278 .filter(|&(_, arg)| match self.module.types[arg.ty].inner {
1279 TypeInner::Sampler { .. } => false,
1280 _ => true,
1281 })
1282 .collect();
1283 self.write_slice(&arguments, |this, _, &(i, arg)| {
1284 // Write the argument type
1285 match this.module.types[arg.ty].inner {
1286 // We treat images separately because they might require
1287 // writing the storage format
1288 TypeInner::Image {
1289 dim,
1290 arrayed,
1291 class,
1292 } => {
1293 // Write the storage format if needed
1294 if let TypeInner::Image {
1295 class: crate::ImageClass::Storage { format, .. },
1296 ..
1297 } = this.module.types[arg.ty].inner
1298 {
1299 write!(this.out, "layout({}) ", glsl_storage_format(format)?)?;
1300 }
1301
1302 // write the type
1303 //
1304 // This is way we need the leading space because `write_image_type` doesn't add
1305 // any spaces at the beginning or end
1306 this.write_image_type(dim, arrayed, class)?;
1307 }
1308 TypeInner::Pointer { base, .. } => {
1309 // write parameter qualifiers
1310 write!(this.out, "inout ")?;
1311 this.write_type(base)?;
1312 }
1313 // All other types are written by `write_type`
1314 _ => {
1315 this.write_type(arg.ty)?;
1316 }
1317 }
1318
1319 // Write the argument name
1320 // The leading space is important
1321 write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?;
1322
1323 // Write array size
1324 match this.module.types[arg.ty].inner {
1325 TypeInner::Array { base, size, .. } => {
1326 this.write_array_size(base, size)?;
1327 }
1328 TypeInner::Pointer { base, .. } => {
1329 if let TypeInner::Array { base, size, .. } = this.module.types[base].inner {
1330 this.write_array_size(base, size)?;
1331 }
1332 }
1333 _ => {}
1334 }
1335
1336 Ok(())
1337 })?;
1338
1339 // Close the parentheses and open braces to start the function body
1340 writeln!(self.out, ") {{")?;
1341
1342 if self.options.zero_initialize_workgroup_memory
1343 && ctx.ty.is_compute_like_entry_point(self.module)
1344 {
1345 self.write_workgroup_variables_initialization(&ctx)?;
1346 }
1347
1348 // Compose the function arguments from globals, in case of an entry point.
1349 if let back::FunctionType::EntryPoint(ep_index) = ctx.ty {
1350 let stage = self.module.entry_points[ep_index as usize].stage;
1351 for (index, arg) in func.arguments.iter().enumerate() {
1352 write!(self.out, "{}", back::INDENT)?;
1353 self.write_type(arg.ty)?;
1354 let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)];
1355 write!(self.out, " {name}")?;
1356 write!(self.out, " = ")?;
1357 match self.module.types[arg.ty].inner {
1358 TypeInner::Struct { ref members, .. } => {
1359 self.write_type(arg.ty)?;
1360 write!(self.out, "(")?;
1361 for (index, member) in members.iter().enumerate() {
1362 let varying_name = VaryingName {
1363 binding: member.binding.as_ref().unwrap(),
1364 stage,
1365 options: VaryingOptions::from_writer_options(self.options, false),
1366 };
1367 if index != 0 {
1368 write!(self.out, ", ")?;
1369 }
1370 write!(self.out, "{varying_name}")?;
1371 }
1372 writeln!(self.out, ");")?;
1373 }
1374 _ => {
1375 let varying_name = VaryingName {
1376 binding: arg.binding.as_ref().unwrap(),
1377 stage,
1378 options: VaryingOptions::from_writer_options(self.options, false),
1379 };
1380 writeln!(self.out, "{varying_name};")?;
1381 }
1382 }
1383 }
1384 }
1385
1386 // Write all function locals
1387 // Locals are `type name (= init)?;` where the init part (including the =) are optional
1388 //
1389 // Always adds a newline
1390 for (handle, local) in func.local_variables.iter() {
1391 // Write indentation (only for readability) and the type
1392 // `write_type` adds no trailing space
1393 write!(self.out, "{}", back::INDENT)?;
1394 self.write_type(local.ty)?;
1395
1396 // Write the local name
1397 // The leading space is important
1398 write!(self.out, " {}", self.names[&ctx.name_key(handle)])?;
1399 // Write size for array type
1400 if let TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner {
1401 self.write_array_size(base, size)?;
1402 }
1403 // Write the local initializer if needed
1404 if let Some(init) = local.init {
1405 // Put the equal signal only if there's a initializer
1406 // The leading and trailing spaces aren't needed but help with readability
1407 write!(self.out, " = ")?;
1408
1409 // Write the constant
1410 // `write_constant` adds no trailing or leading space/newline
1411 self.write_expr(init, &ctx)?;
1412 } else if is_value_init_supported(self.module, local.ty) {
1413 write!(self.out, " = ")?;
1414 self.write_zero_init_value(local.ty)?;
1415 }
1416
1417 // Finish the local with `;` and add a newline (only for readability)
1418 writeln!(self.out, ";")?
1419 }
1420
1421 // Write the function body (statement list)
1422 for sta in func.body.iter() {
1423 // Write a statement, the indentation should always be 1 when writing the function body
1424 // `write_stmt` adds a newline
1425 self.write_stmt(sta, &ctx, back::Level(1))?;
1426 }
1427
1428 // Close braces and add a newline
1429 writeln!(self.out, "}}")?;
1430
1431 Ok(())
1432 }
1433
1434 fn write_workgroup_variables_initialization(
1435 &mut self,
1436 ctx: &back::FunctionCtx,
1437 ) -> BackendResult {
1438 let mut vars = self
1439 .module
1440 .global_variables
1441 .iter()
1442 .filter(|&(handle, var)| {
1443 !ctx.info[handle].is_empty() && var.space == crate::AddressSpace::WorkGroup
1444 })
1445 .peekable();
1446
1447 if vars.peek().is_some() {
1448 let level = back::Level(1);
1449
1450 writeln!(self.out, "{level}if (gl_LocalInvocationID == uvec3(0u)) {{")?;
1451
1452 for (handle, var) in vars {
1453 let name = &self.names[&NameKey::GlobalVariable(handle)];
1454 write!(self.out, "{}{} = ", level.next(), name)?;
1455 self.write_zero_init_value(var.ty)?;
1456 writeln!(self.out, ";")?;
1457 }
1458
1459 writeln!(self.out, "{level}}}")?;
1460 self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?;
1461 }
1462
1463 Ok(())
1464 }
1465
1466 /// Write a list of comma separated `T` values using a writer function `F`.
1467 ///
1468 /// The writer function `F` receives a mutable reference to `self` that if needed won't cause
1469 /// borrow checker issues (using for example a closure with `self` will cause issues), the
1470 /// second argument is the 0 based index of the element on the list, and the last element is
1471 /// a reference to the element `T` being written
1472 ///
1473 /// # Notes
1474 /// - Adds no newlines or leading/trailing whitespace
1475 /// - The last element won't have a trailing `,`
1476 fn write_slice<T, F: FnMut(&mut Self, u32, &T) -> BackendResult>(
1477 &mut self,
1478 data: &[T],
1479 mut f: F,
1480 ) -> BackendResult {
1481 // Loop through `data` invoking `f` for each element
1482 for (index, item) in data.iter().enumerate() {
1483 if index != 0 {
1484 write!(self.out, ", ")?;
1485 }
1486 f(self, index as u32, item)?;
1487 }
1488
1489 Ok(())
1490 }
1491
1492 /// Helper method used to write global constants
1493 fn write_global_constant(&mut self, handle: Handle<crate::Constant>) -> BackendResult {
1494 write!(self.out, "const ")?;
1495 let constant = &self.module.constants[handle];
1496 self.write_type(constant.ty)?;
1497 let name = &self.names[&NameKey::Constant(handle)];
1498 write!(self.out, " {name}")?;
1499 if let TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner {
1500 self.write_array_size(base, size)?;
1501 }
1502 write!(self.out, " = ")?;
1503 self.write_const_expr(constant.init, &self.module.global_expressions)?;
1504 writeln!(self.out, ";")?;
1505 Ok(())
1506 }
1507
1508 /// Helper method used to output a dot product as an arithmetic expression
1509 ///
1510 fn write_dot_product(
1511 &mut self,
1512 arg: Handle<crate::Expression>,
1513 arg1: Handle<crate::Expression>,
1514 size: usize,
1515 ctx: &back::FunctionCtx,
1516 ) -> BackendResult {
1517 // Write parentheses around the dot product expression to prevent operators
1518 // with different precedences from applying earlier.
1519 write!(self.out, "(")?;
1520
1521 // Cycle through all the components of the vector
1522 for index in 0..size {
1523 let component = back::COMPONENTS[index];
1524 // Write the addition to the previous product
1525 // This will print an extra '+' at the beginning but that is fine in glsl
1526 write!(self.out, " + ")?;
1527 // Write the first vector expression, this expression is marked to be
1528 // cached so unless it can't be cached (for example, it's a Constant)
1529 // it shouldn't produce large expressions.
1530 self.write_expr(arg, ctx)?;
1531 // Access the current component on the first vector
1532 write!(self.out, ".{component} * ")?;
1533 // Write the second vector expression, this expression is marked to be
1534 // cached so unless it can't be cached (for example, it's a Constant)
1535 // it shouldn't produce large expressions.
1536 self.write_expr(arg1, ctx)?;
1537 // Access the current component on the second vector
1538 write!(self.out, ".{component}")?;
1539 }
1540
1541 write!(self.out, ")")?;
1542 Ok(())
1543 }
1544
1545 /// Helper method used to write structs
1546 ///
1547 /// # Notes
1548 /// Ends in a newline
1549 fn write_struct_body(
1550 &mut self,
1551 handle: Handle<crate::Type>,
1552 members: &[crate::StructMember],
1553 ) -> BackendResult {
1554 // glsl structs are written as in C
1555 // `struct name() { members };`
1556 // | `struct` is a keyword
1557 // | `name` is the struct name
1558 // | `members` is a semicolon separated list of `type name`
1559 // | `type` is the member type
1560 // | `name` is the member name
1561 writeln!(self.out, "{{")?;
1562
1563 for (idx, member) in members.iter().enumerate() {
1564 // The indentation is only for readability
1565 write!(self.out, "{}", back::INDENT)?;
1566
1567 match self.module.types[member.ty].inner {
1568 TypeInner::Array {
1569 base,
1570 size,
1571 stride: _,
1572 } => {
1573 self.write_type(base)?;
1574 write!(
1575 self.out,
1576 " {}",
1577 &self.names[&NameKey::StructMember(handle, idx as u32)]
1578 )?;
1579 // Write [size]
1580 self.write_array_size(base, size)?;
1581 // Newline is important
1582 writeln!(self.out, ";")?;
1583 }
1584 _ => {
1585 // Write the member type
1586 // Adds no trailing space
1587 self.write_type(member.ty)?;
1588
1589 // Write the member name and put a semicolon
1590 // The leading space is important
1591 // All members must have a semicolon even the last one
1592 writeln!(
1593 self.out,
1594 " {};",
1595 &self.names[&NameKey::StructMember(handle, idx as u32)]
1596 )?;
1597 }
1598 }
1599 }
1600
1601 write!(self.out, "}}")?;
1602 Ok(())
1603 }
1604
1605 /// Helper method used to write statements
1606 ///
1607 /// # Notes
1608 /// Always adds a newline
1609 fn write_stmt(
1610 &mut self,
1611 sta: &crate::Statement,
1612 ctx: &back::FunctionCtx,
1613 level: back::Level,
1614 ) -> BackendResult {
1615 use crate::Statement;
1616
1617 match *sta {
1618 // This is where we can generate intermediate constants for some expression types.
1619 Statement::Emit(ref range) => {
1620 for handle in range.clone() {
1621 let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space();
1622 let expr_name = if ptr_class.is_some() {
1623 // GLSL can't save a pointer-valued expression in a variable,
1624 // but we shouldn't ever need to: they should never be named expressions,
1625 // and none of the expression types flagged by bake_ref_count can be pointer-valued.
1626 None
1627 } else if let Some(name) = ctx.named_expressions.get(&handle) {
1628 // Front end provides names for all variables at the start of writing.
1629 // But we write them to step by step. We need to recache them
1630 // Otherwise, we could accidentally write variable name instead of full expression.
1631 // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords.
1632 Some(self.namer.call(name))
1633 } else if self.need_bake_expressions.contains(&handle) {
1634 Some(Baked(handle).to_string())
1635 } else {
1636 None
1637 };
1638
1639 // If we are going to write an `ImageLoad` next and the target image
1640 // is sampled and we are using the `Restrict` policy for bounds
1641 // checking images we need to write a local holding the clamped lod.
1642 if let crate::Expression::ImageLoad {
1643 image,
1644 level: Some(level_expr),
1645 ..
1646 } = ctx.expressions[handle]
1647 {
1648 if let TypeInner::Image {
1649 class: crate::ImageClass::Sampled { .. },
1650 ..
1651 } = *ctx.resolve_type(image, &self.module.types)
1652 {
1653 if let proc::BoundsCheckPolicy::Restrict = self.policies.image_load {
1654 write!(self.out, "{level}")?;
1655 self.write_clamped_lod(ctx, handle, image, level_expr)?
1656 }
1657 }
1658 }
1659
1660 if let Some(name) = expr_name {
1661 write!(self.out, "{level}")?;
1662 self.write_named_expr(handle, name, handle, ctx)?;
1663 }
1664 }
1665 }
1666 // Blocks are simple we just need to write the block statements between braces
1667 // We could also just print the statements but this is more readable and maps more
1668 // closely to the IR
1669 Statement::Block(ref block) => {
1670 write!(self.out, "{level}")?;
1671 writeln!(self.out, "{{")?;
1672 for sta in block.iter() {
1673 // Increase the indentation to help with readability
1674 self.write_stmt(sta, ctx, level.next())?
1675 }
1676 writeln!(self.out, "{level}}}")?
1677 }
1678 // Ifs are written as in C:
1679 // ```
1680 // if(condition) {
1681 // accept
1682 // } else {
1683 // reject
1684 // }
1685 // ```
1686 Statement::If {
1687 condition,
1688 ref accept,
1689 ref reject,
1690 } => {
1691 write!(self.out, "{level}")?;
1692 write!(self.out, "if (")?;
1693 self.write_expr(condition, ctx)?;
1694 writeln!(self.out, ") {{")?;
1695
1696 for sta in accept {
1697 // Increase indentation to help with readability
1698 self.write_stmt(sta, ctx, level.next())?;
1699 }
1700
1701 // If there are no statements in the reject block we skip writing it
1702 // This is only for readability
1703 if !reject.is_empty() {
1704 writeln!(self.out, "{level}}} else {{")?;
1705
1706 for sta in reject {
1707 // Increase indentation to help with readability
1708 self.write_stmt(sta, ctx, level.next())?;
1709 }
1710 }
1711
1712 writeln!(self.out, "{level}}}")?
1713 }
1714 // Switch are written as in C:
1715 // ```
1716 // switch (selector) {
1717 // // Fallthrough
1718 // case label:
1719 // block
1720 // // Non fallthrough
1721 // case label:
1722 // block
1723 // break;
1724 // default:
1725 // block
1726 // }
1727 // ```
1728 // Where the `default` case happens isn't important but we put it last
1729 // so that we don't need to print a `break` for it
1730 Statement::Switch {
1731 selector,
1732 ref cases,
1733 } => {
1734 let l2 = level.next();
1735 // Some GLSL consumers may not handle switches with a single
1736 // body correctly: See wgpu#4514. Write such switch statements
1737 // as a `do {} while(false);` loop instead.
1738 //
1739 // Since doing so may inadvertently capture `continue`
1740 // statements in the switch body, we must apply continue
1741 // forwarding. See the `naga::back::continue_forward` module
1742 // docs for details.
1743 let one_body = cases
1744 .iter()
1745 .rev()
1746 .skip(1)
1747 .all(|case| case.fall_through && case.body.is_empty());
1748 if one_body {
1749 // Unlike HLSL, in GLSL `continue_ctx` only needs to know
1750 // about [`Switch`] statements that are being rendered as
1751 // `do-while` loops.
1752 if let Some(variable) = self.continue_ctx.enter_switch(&mut self.namer) {
1753 writeln!(self.out, "{level}bool {variable} = false;",)?;
1754 };
1755 writeln!(self.out, "{level}do {{")?;
1756 // Note: Expressions have no side-effects so we don't need to emit selector expression.
1757
1758 // Body
1759 if let Some(case) = cases.last() {
1760 for sta in case.body.iter() {
1761 self.write_stmt(sta, ctx, l2)?;
1762 }
1763 }
1764 // End do-while
1765 writeln!(self.out, "{level}}} while(false);")?;
1766
1767 // Handle any forwarded continue statements.
1768 use back::continue_forward::ExitControlFlow;
1769 let op = match self.continue_ctx.exit_switch() {
1770 ExitControlFlow::None => None,
1771 ExitControlFlow::Continue { variable } => Some(("continue", variable)),
1772 ExitControlFlow::Break { variable } => Some(("break", variable)),
1773 };
1774 if let Some((control_flow, variable)) = op {
1775 writeln!(self.out, "{level}if ({variable}) {{")?;
1776 writeln!(self.out, "{l2}{control_flow};")?;
1777 writeln!(self.out, "{level}}}")?;
1778 }
1779 } else {
1780 // Start the switch
1781 write!(self.out, "{level}")?;
1782 write!(self.out, "switch(")?;
1783 self.write_expr(selector, ctx)?;
1784 writeln!(self.out, ") {{")?;
1785
1786 // Write all cases
1787 for case in cases {
1788 match case.value {
1789 crate::SwitchValue::I32(value) => {
1790 write!(self.out, "{l2}case {value}:")?
1791 }
1792 crate::SwitchValue::U32(value) => {
1793 write!(self.out, "{l2}case {value}u:")?
1794 }
1795 crate::SwitchValue::Default => write!(self.out, "{l2}default:")?,
1796 }
1797
1798 let write_block_braces = !(case.fall_through && case.body.is_empty());
1799 if write_block_braces {
1800 writeln!(self.out, " {{")?;
1801 } else {
1802 writeln!(self.out)?;
1803 }
1804
1805 for sta in case.body.iter() {
1806 self.write_stmt(sta, ctx, l2.next())?;
1807 }
1808
1809 if !case.fall_through && case.body.last().is_none_or(|s| !s.is_terminator())
1810 {
1811 writeln!(self.out, "{}break;", l2.next())?;
1812 }
1813
1814 if write_block_braces {
1815 writeln!(self.out, "{l2}}}")?;
1816 }
1817 }
1818
1819 writeln!(self.out, "{level}}}")?
1820 }
1821 }
1822 // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a
1823 // while true loop and appending the continuing block to the body resulting on:
1824 // ```
1825 // bool loop_init = true;
1826 // while(true) {
1827 // if (!loop_init) { <continuing> }
1828 // loop_init = false;
1829 // <body>
1830 // }
1831 // ```
1832 Statement::Loop {
1833 ref body,
1834 ref continuing,
1835 break_if,
1836 } => {
1837 self.continue_ctx.enter_loop();
1838 if !continuing.is_empty() || break_if.is_some() {
1839 let gate_name = self.namer.call("loop_init");
1840 writeln!(self.out, "{level}bool {gate_name} = true;")?;
1841 writeln!(self.out, "{level}while(true) {{")?;
1842 let l2 = level.next();
1843 let l3 = l2.next();
1844 writeln!(self.out, "{l2}if (!{gate_name}) {{")?;
1845 for sta in continuing {
1846 self.write_stmt(sta, ctx, l3)?;
1847 }
1848 if let Some(condition) = break_if {
1849 write!(self.out, "{l3}if (")?;
1850 self.write_expr(condition, ctx)?;
1851 writeln!(self.out, ") {{")?;
1852 writeln!(self.out, "{}break;", l3.next())?;
1853 writeln!(self.out, "{l3}}}")?;
1854 }
1855 writeln!(self.out, "{l2}}}")?;
1856 writeln!(self.out, "{}{} = false;", level.next(), gate_name)?;
1857 } else {
1858 writeln!(self.out, "{level}while(true) {{")?;
1859 }
1860 for sta in body {
1861 self.write_stmt(sta, ctx, level.next())?;
1862 }
1863 writeln!(self.out, "{level}}}")?;
1864 self.continue_ctx.exit_loop();
1865 }
1866 // Break, continue and return as written as in C
1867 // `break;`
1868 Statement::Break => {
1869 write!(self.out, "{level}")?;
1870 writeln!(self.out, "break;")?
1871 }
1872 // `continue;`
1873 Statement::Continue => {
1874 // Sometimes we must render a `Continue` statement as a `break`.
1875 // See the docs for the `back::continue_forward` module.
1876 if let Some(variable) = self.continue_ctx.continue_encountered() {
1877 writeln!(self.out, "{level}{variable} = true;",)?;
1878 writeln!(self.out, "{level}break;")?
1879 } else {
1880 writeln!(self.out, "{level}continue;")?
1881 }
1882 }
1883 // `return expr;`, `expr` is optional
1884 Statement::Return { value } => {
1885 write!(self.out, "{level}")?;
1886 match ctx.ty {
1887 back::FunctionType::Function(_) => {
1888 write!(self.out, "return")?;
1889 // Write the expression to be returned if needed
1890 if let Some(expr) = value {
1891 write!(self.out, " ")?;
1892 self.write_expr(expr, ctx)?;
1893 }
1894 writeln!(self.out, ";")?;
1895 }
1896 back::FunctionType::EntryPoint(ep_index) => {
1897 let mut has_point_size = false;
1898 let ep = &self.module.entry_points[ep_index as usize];
1899 if let Some(ref result) = ep.function.result {
1900 let value = value.unwrap();
1901 match self.module.types[result.ty].inner {
1902 TypeInner::Struct { ref members, .. } => {
1903 let temp_struct_name = match ctx.expressions[value] {
1904 crate::Expression::Compose { .. } => {
1905 let return_struct = "_tmp_return";
1906 write!(
1907 self.out,
1908 "{} {} = ",
1909 &self.names[&NameKey::Type(result.ty)],
1910 return_struct
1911 )?;
1912 self.write_expr(value, ctx)?;
1913 writeln!(self.out, ";")?;
1914 write!(self.out, "{level}")?;
1915 Some(return_struct)
1916 }
1917 _ => None,
1918 };
1919
1920 for (index, member) in members.iter().enumerate() {
1921 if let Some(crate::Binding::BuiltIn(
1922 crate::BuiltIn::PointSize,
1923 )) = member.binding
1924 {
1925 has_point_size = true;
1926 }
1927
1928 let varying_name = VaryingName {
1929 binding: member.binding.as_ref().unwrap(),
1930 stage: ep.stage,
1931 options: VaryingOptions::from_writer_options(
1932 self.options,
1933 true,
1934 ),
1935 };
1936 write!(self.out, "{varying_name} = ")?;
1937
1938 if let Some(struct_name) = temp_struct_name {
1939 write!(self.out, "{struct_name}")?;
1940 } else {
1941 self.write_expr(value, ctx)?;
1942 }
1943
1944 // Write field name
1945 writeln!(
1946 self.out,
1947 ".{};",
1948 &self.names
1949 [&NameKey::StructMember(result.ty, index as u32)]
1950 )?;
1951 write!(self.out, "{level}")?;
1952 }
1953 }
1954 _ => {
1955 let name = VaryingName {
1956 binding: result.binding.as_ref().unwrap(),
1957 stage: ep.stage,
1958 options: VaryingOptions::from_writer_options(
1959 self.options,
1960 true,
1961 ),
1962 };
1963 write!(self.out, "{name} = ")?;
1964 self.write_expr(value, ctx)?;
1965 writeln!(self.out, ";")?;
1966 write!(self.out, "{level}")?;
1967 }
1968 }
1969 }
1970
1971 let is_vertex_stage = self.module.entry_points[ep_index as usize].stage
1972 == ShaderStage::Vertex;
1973 if is_vertex_stage
1974 && self
1975 .options
1976 .writer_flags
1977 .contains(WriterFlags::ADJUST_COORDINATE_SPACE)
1978 {
1979 writeln!(
1980 self.out,
1981 "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);",
1982 )?;
1983 write!(self.out, "{level}")?;
1984 }
1985
1986 if is_vertex_stage
1987 && self
1988 .options
1989 .writer_flags
1990 .contains(WriterFlags::FORCE_POINT_SIZE)
1991 && !has_point_size
1992 {
1993 writeln!(self.out, "gl_PointSize = 1.0;")?;
1994 write!(self.out, "{level}")?;
1995 }
1996 writeln!(self.out, "return;")?;
1997 }
1998 }
1999 }
2000 // This is one of the places were glsl adds to the syntax of C in this case the discard
2001 // keyword which ceases all further processing in a fragment shader, it's called OpKill
2002 // in spir-v that's why it's called `Statement::Kill`
2003 Statement::Kill => writeln!(self.out, "{level}discard;")?,
2004 Statement::ControlBarrier(flags) => {
2005 self.write_control_barrier(flags, level)?;
2006 }
2007 Statement::MemoryBarrier(flags) => {
2008 self.write_memory_barrier(flags, level)?;
2009 }
2010 // Stores in glsl are just variable assignments written as `pointer = value;`
2011 Statement::Store { pointer, value } => {
2012 write!(self.out, "{level}")?;
2013 let is_atomic_pointer = ctx
2014 .resolve_type(pointer, &self.module.types)
2015 .is_atomic_pointer(&self.module.types);
2016 if is_atomic_pointer {
2017 write!(self.out, "atomicExchange(")?;
2018 self.write_expr(pointer, ctx)?;
2019 write!(self.out, ", ")?;
2020 self.write_expr(value, ctx)?;
2021 writeln!(self.out, ");")?
2022 } else {
2023 self.write_expr(pointer, ctx)?;
2024 write!(self.out, " = ")?;
2025 self.write_expr(value, ctx)?;
2026 writeln!(self.out, ";")?
2027 }
2028 }
2029 Statement::WorkGroupUniformLoad { pointer, result } => {
2030 // GLSL doesn't have pointers, which means that this backend needs to ensure that
2031 // the actual "loading" is happening between the two barriers.
2032 // This is done in `Emit` by never emitting a variable name for pointer variables
2033 self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?;
2034
2035 let result_name = Baked(result).to_string();
2036 write!(self.out, "{level}")?;
2037 // Expressions cannot have side effects, so just writing the expression here is fine.
2038 self.write_named_expr(pointer, result_name, result, ctx)?;
2039
2040 self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?;
2041 }
2042 // Stores a value into an image.
2043 Statement::ImageStore {
2044 image,
2045 coordinate,
2046 array_index,
2047 value,
2048 } => {
2049 write!(self.out, "{level}")?;
2050 self.write_image_store(ctx, image, coordinate, array_index, value)?
2051 }
2052 // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list
2053 Statement::Call {
2054 function,
2055 ref arguments,
2056 result,
2057 } => {
2058 write!(self.out, "{level}")?;
2059 if let Some(expr) = result {
2060 let name = Baked(expr).to_string();
2061 let result = self.module.functions[function].result.as_ref().unwrap();
2062 self.write_type(result.ty)?;
2063 write!(self.out, " {name}")?;
2064 if let TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner
2065 {
2066 self.write_array_size(base, size)?
2067 }
2068 write!(self.out, " = ")?;
2069 self.named_expressions.insert(expr, name);
2070 }
2071 write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?;
2072 let arguments: Vec<_> = arguments
2073 .iter()
2074 .enumerate()
2075 .filter_map(|(i, arg)| {
2076 let arg_ty = self.module.functions[function].arguments[i].ty;
2077 match self.module.types[arg_ty].inner {
2078 TypeInner::Sampler { .. } => None,
2079 _ => Some(*arg),
2080 }
2081 })
2082 .collect();
2083 self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?;
2084 writeln!(self.out, ");")?
2085 }
2086 Statement::Atomic {
2087 pointer,
2088 ref fun,
2089 value,
2090 result,
2091 } => {
2092 write!(self.out, "{level}")?;
2093
2094 match *fun {
2095 crate::AtomicFunction::Exchange {
2096 compare: Some(compare_expr),
2097 } => {
2098 let result_handle = result.expect("CompareExchange must have a result");
2099 let res_name = Baked(result_handle).to_string();
2100 self.write_type(ctx.info[result_handle].ty.handle().unwrap())?;
2101 write!(self.out, " {res_name};")?;
2102 write!(self.out, " {res_name}.old_value = atomicCompSwap(")?;
2103 self.write_expr(pointer, ctx)?;
2104 write!(self.out, ", ")?;
2105 self.write_expr(compare_expr, ctx)?;
2106 write!(self.out, ", ")?;
2107 self.write_expr(value, ctx)?;
2108 writeln!(self.out, ");")?;
2109
2110 write!(
2111 self.out,
2112 "{level}{res_name}.exchanged = ({res_name}.old_value == "
2113 )?;
2114 self.write_expr(compare_expr, ctx)?;
2115 writeln!(self.out, ");")?;
2116 self.named_expressions.insert(result_handle, res_name);
2117 }
2118 _ => {
2119 if let Some(result) = result {
2120 let res_name = Baked(result).to_string();
2121 self.write_type(ctx.info[result].ty.handle().unwrap())?;
2122 write!(self.out, " {res_name} = ")?;
2123 self.named_expressions.insert(result, res_name);
2124 }
2125 let fun_str = fun.to_glsl();
2126 write!(self.out, "atomic{fun_str}(")?;
2127 self.write_expr(pointer, ctx)?;
2128 write!(self.out, ", ")?;
2129 if let crate::AtomicFunction::Subtract = *fun {
2130 // Emulate `atomicSub` with `atomicAdd` by negating the value.
2131 //
2132 // Use a space to avoid accidentally producing a prefix increment
2133 // (`--`).
2134 write!(self.out, "- ")?;
2135 }
2136 self.write_expr(value, ctx)?;
2137 writeln!(self.out, ");")?;
2138 }
2139 }
2140 }
2141 // Stores a value into an image.
2142 Statement::ImageAtomic {
2143 image,
2144 coordinate,
2145 array_index,
2146 fun,
2147 value,
2148 } => {
2149 write!(self.out, "{level}")?;
2150 self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)?
2151 }
2152 Statement::RayQuery { .. } => unreachable!(),
2153 Statement::SubgroupBallot { result, predicate } => {
2154 write!(self.out, "{level}")?;
2155 let res_name = Baked(result).to_string();
2156 let res_ty = ctx.info[result].ty.inner_with(&self.module.types);
2157 self.write_value_type(res_ty)?;
2158 write!(self.out, " {res_name} = ")?;
2159 self.named_expressions.insert(result, res_name);
2160
2161 write!(self.out, "subgroupBallot(")?;
2162 match predicate {
2163 Some(predicate) => self.write_expr(predicate, ctx)?,
2164 None => write!(self.out, "true")?,
2165 }
2166 writeln!(self.out, ");")?;
2167 }
2168 Statement::SubgroupCollectiveOperation {
2169 op,
2170 collective_op,
2171 argument,
2172 result,
2173 } => {
2174 write!(self.out, "{level}")?;
2175 let res_name = Baked(result).to_string();
2176 let res_ty = ctx.info[result].ty.inner_with(&self.module.types);
2177 self.write_value_type(res_ty)?;
2178 write!(self.out, " {res_name} = ")?;
2179 self.named_expressions.insert(result, res_name);
2180
2181 match (collective_op, op) {
2182 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::All) => {
2183 write!(self.out, "subgroupAll(")?
2184 }
2185 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Any) => {
2186 write!(self.out, "subgroupAny(")?
2187 }
2188 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Add) => {
2189 write!(self.out, "subgroupAdd(")?
2190 }
2191 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Mul) => {
2192 write!(self.out, "subgroupMul(")?
2193 }
2194 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Max) => {
2195 write!(self.out, "subgroupMax(")?
2196 }
2197 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Min) => {
2198 write!(self.out, "subgroupMin(")?
2199 }
2200 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::And) => {
2201 write!(self.out, "subgroupAnd(")?
2202 }
2203 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Or) => {
2204 write!(self.out, "subgroupOr(")?
2205 }
2206 (crate::CollectiveOperation::Reduce, crate::SubgroupOperation::Xor) => {
2207 write!(self.out, "subgroupXor(")?
2208 }
2209 (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Add) => {
2210 write!(self.out, "subgroupExclusiveAdd(")?
2211 }
2212 (crate::CollectiveOperation::ExclusiveScan, crate::SubgroupOperation::Mul) => {
2213 write!(self.out, "subgroupExclusiveMul(")?
2214 }
2215 (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Add) => {
2216 write!(self.out, "subgroupInclusiveAdd(")?
2217 }
2218 (crate::CollectiveOperation::InclusiveScan, crate::SubgroupOperation::Mul) => {
2219 write!(self.out, "subgroupInclusiveMul(")?
2220 }
2221 _ => unimplemented!(),
2222 }
2223 self.write_expr(argument, ctx)?;
2224 writeln!(self.out, ");")?;
2225 }
2226 Statement::SubgroupGather {
2227 mode,
2228 argument,
2229 result,
2230 } => {
2231 write!(self.out, "{level}")?;
2232 let res_name = Baked(result).to_string();
2233 let res_ty = ctx.info[result].ty.inner_with(&self.module.types);
2234 self.write_value_type(res_ty)?;
2235 write!(self.out, " {res_name} = ")?;
2236 self.named_expressions.insert(result, res_name);
2237
2238 match mode {
2239 crate::GatherMode::BroadcastFirst => {
2240 write!(self.out, "subgroupBroadcastFirst(")?;
2241 }
2242 crate::GatherMode::Broadcast(_) => {
2243 write!(self.out, "subgroupBroadcast(")?;
2244 }
2245 crate::GatherMode::Shuffle(_) => {
2246 write!(self.out, "subgroupShuffle(")?;
2247 }
2248 crate::GatherMode::ShuffleDown(_) => {
2249 write!(self.out, "subgroupShuffleDown(")?;
2250 }
2251 crate::GatherMode::ShuffleUp(_) => {
2252 write!(self.out, "subgroupShuffleUp(")?;
2253 }
2254 crate::GatherMode::ShuffleXor(_) => {
2255 write!(self.out, "subgroupShuffleXor(")?;
2256 }
2257 crate::GatherMode::QuadBroadcast(_) => {
2258 write!(self.out, "subgroupQuadBroadcast(")?;
2259 }
2260 crate::GatherMode::QuadSwap(direction) => match direction {
2261 crate::Direction::X => {
2262 write!(self.out, "subgroupQuadSwapHorizontal(")?;
2263 }
2264 crate::Direction::Y => {
2265 write!(self.out, "subgroupQuadSwapVertical(")?;
2266 }
2267 crate::Direction::Diagonal => {
2268 write!(self.out, "subgroupQuadSwapDiagonal(")?;
2269 }
2270 },
2271 }
2272 self.write_expr(argument, ctx)?;
2273 match mode {
2274 crate::GatherMode::BroadcastFirst => {}
2275 crate::GatherMode::Broadcast(index)
2276 | crate::GatherMode::Shuffle(index)
2277 | crate::GatherMode::ShuffleDown(index)
2278 | crate::GatherMode::ShuffleUp(index)
2279 | crate::GatherMode::ShuffleXor(index)
2280 | crate::GatherMode::QuadBroadcast(index) => {
2281 write!(self.out, ", ")?;
2282 self.write_expr(index, ctx)?;
2283 }
2284 crate::GatherMode::QuadSwap(_) => {}
2285 }
2286 writeln!(self.out, ");")?;
2287 }
2288 Statement::CooperativeStore { .. } => unimplemented!(),
2289 Statement::RayPipelineFunction(_) => unimplemented!(),
2290 Statement::DebugPrintf { .. } => unimplemented!(),
2291 }
2292
2293 Ok(())
2294 }
2295
2296 /// Write a const expression.
2297 ///
2298 /// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s
2299 /// constant expression arena, as GLSL expression.
2300 ///
2301 /// # Notes
2302 /// Adds no newlines or leading/trailing whitespace
2303 ///
2304 /// [`Expression`]: crate::Expression
2305 /// [`Module`]: crate::Module
2306 fn write_const_expr(
2307 &mut self,
2308 expr: Handle<crate::Expression>,
2309 arena: &crate::Arena<crate::Expression>,
2310 ) -> BackendResult {
2311 self.write_possibly_const_expr(
2312 expr,
2313 arena,
2314 |expr| &self.info[expr],
2315 |writer, expr| writer.write_const_expr(expr, arena),
2316 )
2317 }
2318
2319 /// Write [`Expression`] variants that can occur in both runtime and const expressions.
2320 ///
2321 /// Write `expr`, a handle to an [`Expression`] in the arena `expressions`,
2322 /// as as GLSL expression. This must be one of the [`Expression`] variants
2323 /// that is allowed to occur in constant expressions.
2324 ///
2325 /// Use `write_expression` to write subexpressions.
2326 ///
2327 /// This is the common code for `write_expr`, which handles arbitrary
2328 /// runtime expressions, and `write_const_expr`, which only handles
2329 /// const-expressions. Each of those callers passes itself (essentially) as
2330 /// the `write_expression` callback, so that subexpressions are restricted
2331 /// to the appropriate variants.
2332 ///
2333 /// # Notes
2334 /// Adds no newlines or leading/trailing whitespace
2335 ///
2336 /// [`Expression`]: crate::Expression
2337 fn write_possibly_const_expr<'w, I, E>(
2338 &'w mut self,
2339 expr: Handle<crate::Expression>,
2340 expressions: &crate::Arena<crate::Expression>,
2341 info: I,
2342 write_expression: E,
2343 ) -> BackendResult
2344 where
2345 I: Fn(Handle<crate::Expression>) -> &'w proc::TypeResolution,
2346 E: Fn(&mut Self, Handle<crate::Expression>) -> BackendResult,
2347 {
2348 use crate::Expression;
2349
2350 match expressions[expr] {
2351 Expression::Literal(literal) => {
2352 match literal {
2353 // Floats are written using `Debug` instead of `Display` because it always appends the
2354 // decimal part even it's zero which is needed for a valid glsl float constant
2355 crate::Literal::F64(value) => write!(self.out, "{value:?}LF")?,
2356 crate::Literal::F32(value) => write!(self.out, "{value:?}")?,
2357 crate::Literal::F16(_) => {
2358 return Err(Error::Custom("GLSL has no 16-bit float type".into()));
2359 }
2360 // Unsigned integers need a `u` at the end
2361 //
2362 // While `core` doesn't necessarily need it, it's allowed and since `es` needs it we
2363 // always write it as the extra branch wouldn't have any benefit in readability
2364 crate::Literal::U16(value) => write!(self.out, "uint16_t({value})")?,
2365 crate::Literal::I16(value) => write!(self.out, "int16_t({value})")?,
2366 crate::Literal::U32(value) => write!(self.out, "{value}u")?,
2367 crate::Literal::I32(value) => write!(self.out, "{value}")?,
2368 crate::Literal::Bool(value) => write!(self.out, "{value}")?,
2369 crate::Literal::I64(_) => {
2370 return Err(Error::Custom("GLSL has no 64-bit integer type".into()));
2371 }
2372 crate::Literal::U64(_) => {
2373 return Err(Error::Custom("GLSL has no 64-bit integer type".into()));
2374 }
2375 crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
2376 return Err(Error::Custom(
2377 "Abstract types should not appear in IR presented to backends".into(),
2378 ));
2379 }
2380 }
2381 }
2382 Expression::Constant(handle) => {
2383 let constant = &self.module.constants[handle];
2384 if constant.name.is_some() {
2385 write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?;
2386 } else {
2387 self.write_const_expr(constant.init, &self.module.global_expressions)?;
2388 }
2389 }
2390 Expression::ZeroValue(ty) => {
2391 self.write_zero_init_value(ty)?;
2392 }
2393 Expression::Compose { ty, ref components } => {
2394 self.write_type(ty)?;
2395
2396 if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner {
2397 self.write_array_size(base, size)?;
2398 }
2399
2400 write!(self.out, "(")?;
2401 for (index, component) in components.iter().enumerate() {
2402 if index != 0 {
2403 write!(self.out, ", ")?;
2404 }
2405 write_expression(self, *component)?;
2406 }
2407 write!(self.out, ")")?
2408 }
2409 // `Splat` needs to actually write down a vector, it's not always inferred in GLSL.
2410 Expression::Splat { size: _, value } => {
2411 let resolved = info(expr).inner_with(&self.module.types);
2412 self.write_value_type(resolved)?;
2413 write!(self.out, "(")?;
2414 write_expression(self, value)?;
2415 write!(self.out, ")")?
2416 }
2417 _ => {
2418 return Err(Error::Override);
2419 }
2420 }
2421
2422 Ok(())
2423 }
2424
2425 /// Helper method to write expressions
2426 ///
2427 /// # Notes
2428 /// Doesn't add any newlines or leading/trailing spaces
2429 #[allow(clippy::large_stack_frames)] // TODO(https://github.com/gfx-rs/wgpu/issues/9456)
2430 fn write_expr(
2431 &mut self,
2432 expr: Handle<crate::Expression>,
2433 ctx: &back::FunctionCtx,
2434 ) -> BackendResult {
2435 use crate::Expression;
2436
2437 if let Some(name) = self.named_expressions.get(&expr) {
2438 write!(self.out, "{name}")?;
2439 return Ok(());
2440 }
2441
2442 match ctx.expressions[expr] {
2443 Expression::Literal(_)
2444 | Expression::Constant(_)
2445 | Expression::ZeroValue(_)
2446 | Expression::Compose { .. }
2447 | Expression::Splat { .. } => {
2448 self.write_possibly_const_expr(
2449 expr,
2450 ctx.expressions,
2451 |expr| &ctx.info[expr].ty,
2452 |writer, expr| writer.write_expr(expr, ctx),
2453 )?;
2454 }
2455 Expression::Override(_) => return Err(Error::Override),
2456 // `Access` is applied to arrays, vectors and matrices and is written as indexing
2457 Expression::Access { base, index } => {
2458 self.write_expr(base, ctx)?;
2459 write!(self.out, "[")?;
2460 self.write_expr(index, ctx)?;
2461 write!(self.out, "]")?
2462 }
2463 // `AccessIndex` is the same as `Access` except that the index is a constant and it can
2464 // be applied to structs, in this case we need to find the name of the field at that
2465 // index and write `base.field_name`
2466 Expression::AccessIndex { base, index } => {
2467 self.write_expr(base, ctx)?;
2468
2469 let base_ty_res = &ctx.info[base].ty;
2470 let mut resolved = base_ty_res.inner_with(&self.module.types);
2471 let base_ty_handle = match *resolved {
2472 TypeInner::Pointer { base, space: _ } => {
2473 resolved = &self.module.types[base].inner;
2474 Some(base)
2475 }
2476 _ => base_ty_res.handle(),
2477 };
2478
2479 match *resolved {
2480 TypeInner::Vector { .. } => {
2481 // Write vector access as a swizzle
2482 write!(self.out, ".{}", back::COMPONENTS[index as usize])?
2483 }
2484 TypeInner::Matrix { .. }
2485 | TypeInner::Array { .. }
2486 | TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?,
2487 TypeInner::Struct { .. } => {
2488 // This will never panic in case the type is a `Struct`, this is not true
2489 // for other types so we can only check while inside this match arm
2490 let ty = base_ty_handle.unwrap();
2491
2492 write!(
2493 self.out,
2494 ".{}",
2495 &self.names[&NameKey::StructMember(ty, index)]
2496 )?
2497 }
2498 ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))),
2499 }
2500 }
2501 // `Swizzle` adds a few letters behind the dot.
2502 Expression::Swizzle {
2503 size,
2504 vector,
2505 pattern,
2506 } => {
2507 self.write_expr(vector, ctx)?;
2508 write!(self.out, ".")?;
2509 for &sc in pattern[..size as usize].iter() {
2510 self.out.write_char(back::COMPONENTS[sc as usize])?;
2511 }
2512 }
2513 // Function arguments are written as the argument name
2514 Expression::FunctionArgument(pos) => {
2515 write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])?
2516 }
2517 // Global variables need some special work for their name but
2518 // `get_global_name` does the work for us
2519 Expression::GlobalVariable(handle) => {
2520 let global = &self.module.global_variables[handle];
2521 self.write_global_name(handle, global)?
2522 }
2523 // A local is written as it's name
2524 Expression::LocalVariable(handle) => {
2525 write!(self.out, "{}", self.names[&ctx.name_key(handle)])?
2526 }
2527 // glsl has no pointers so there's no load operation, just write the pointer expression
2528 Expression::Load { pointer } => {
2529 let ty_inner = ctx.resolve_type(pointer, &self.module.types);
2530 if ty_inner.is_atomic_pointer(&self.module.types) {
2531 let mut suffix = "";
2532 if let TypeInner::Pointer { base, .. } = *ty_inner {
2533 if let TypeInner::Atomic(scalar) = self.module.types[base].inner {
2534 suffix = match (scalar.kind, scalar.width) {
2535 (crate::ScalarKind::Uint, 8) => "ul",
2536 (crate::ScalarKind::Sint, 8) => "l",
2537 (crate::ScalarKind::Uint, _) => "u",
2538 _ => "",
2539 };
2540 }
2541 }
2542 write!(self.out, "atomicOr(")?;
2543 self.write_expr(pointer, ctx)?;
2544 write!(self.out, ", 0{})", suffix)?
2545 } else {
2546 self.write_expr(pointer, ctx)?
2547 }
2548 }
2549 // `ImageSample` is a bit complicated compared to the rest of the IR.
2550 //
2551 // First there are three variations depending whether the sample level is explicitly set,
2552 // if it's automatic or it it's bias:
2553 // `texture(image, coordinate)` - Automatic sample level
2554 // `texture(image, coordinate, bias)` - Bias sample level
2555 // `textureLod(image, coordinate, level)` - Zero or Exact sample level
2556 //
2557 // Furthermore if `depth_ref` is some we need to append it to the coordinate vector
2558 Expression::ImageSample {
2559 image,
2560 sampler: _, //TODO?
2561 gather,
2562 coordinate,
2563 array_index,
2564 offset,
2565 level,
2566 depth_ref,
2567 clamp_to_edge: _,
2568 } => {
2569 let (dim, class, arrayed) = match *ctx.resolve_type(image, &self.module.types) {
2570 TypeInner::Image {
2571 dim,
2572 class,
2573 arrayed,
2574 ..
2575 } => (dim, class, arrayed),
2576 _ => unreachable!(),
2577 };
2578 let mut err = None;
2579 if dim == crate::ImageDimension::Cube {
2580 if offset.is_some() {
2581 err = Some("gsamplerCube[Array][Shadow] doesn't support texture sampling with offsets");
2582 }
2583 if arrayed
2584 && matches!(class, crate::ImageClass::Depth { .. })
2585 && matches!(level, crate::SampleLevel::Gradient { .. })
2586 {
2587 err = Some("samplerCubeArrayShadow don't support textureGrad");
2588 }
2589 }
2590 if gather.is_some() && level != crate::SampleLevel::Zero {
2591 err = Some("textureGather doesn't support LOD parameters");
2592 }
2593 if let Some(err) = err {
2594 return Err(Error::Custom(String::from(err)));
2595 }
2596
2597 // `textureLod[Offset]` on `sampler2DArrayShadow` and `samplerCubeShadow` does not exist in GLSL,
2598 // unless `GL_EXT_texture_shadow_lod` is present.
2599 // But if the target LOD is zero, we can emulate that by using `textureGrad[Offset]` with a constant gradient of 0.
2600 let workaround_lod_with_grad = ((dim == crate::ImageDimension::Cube && !arrayed)
2601 || (dim == crate::ImageDimension::D2 && arrayed))
2602 && level == crate::SampleLevel::Zero
2603 && matches!(class, crate::ImageClass::Depth { .. })
2604 && !self.features.contains(Features::TEXTURE_SHADOW_LOD);
2605
2606 // Write the function to be used depending on the sample level
2607 let fun_name = match level {
2608 crate::SampleLevel::Zero if gather.is_some() => "textureGather",
2609 crate::SampleLevel::Zero if workaround_lod_with_grad => "textureGrad",
2610 crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture",
2611 crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod",
2612 crate::SampleLevel::Gradient { .. } => "textureGrad",
2613 };
2614 let offset_name = match offset {
2615 Some(_) => "Offset",
2616 None => "",
2617 };
2618
2619 write!(self.out, "{fun_name}{offset_name}(")?;
2620
2621 // Write the image that will be used
2622 self.write_expr(image, ctx)?;
2623 // The space here isn't required but it helps with readability
2624 write!(self.out, ", ")?;
2625
2626 // TODO: handle clamp_to_edge
2627 // https://github.com/gfx-rs/wgpu/issues/7791
2628
2629 // We need to get the coordinates vector size to later build a vector that's `size + 1`
2630 // if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression
2631 let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) {
2632 TypeInner::Vector { size, .. } => size as u8,
2633 TypeInner::Scalar { .. } => 1,
2634 _ => unreachable!(),
2635 };
2636
2637 if array_index.is_some() {
2638 coord_dim += 1;
2639 }
2640 let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4;
2641 if merge_depth_ref {
2642 coord_dim += 1;
2643 }
2644
2645 let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es();
2646 let is_vec = tex_1d_hack || coord_dim != 1;
2647 // Compose a new texture coordinates vector
2648 if is_vec {
2649 write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?;
2650 }
2651 self.write_expr(coordinate, ctx)?;
2652 if tex_1d_hack {
2653 write!(self.out, ", 0.0")?;
2654 }
2655 if let Some(expr) = array_index {
2656 write!(self.out, ", ")?;
2657 self.write_expr(expr, ctx)?;
2658 }
2659 if merge_depth_ref {
2660 write!(self.out, ", ")?;
2661 self.write_expr(depth_ref.unwrap(), ctx)?;
2662 }
2663 if is_vec {
2664 write!(self.out, ")")?;
2665 }
2666
2667 if let (Some(expr), false) = (depth_ref, merge_depth_ref) {
2668 write!(self.out, ", ")?;
2669 self.write_expr(expr, ctx)?;
2670 }
2671
2672 match level {
2673 // Auto needs no more arguments
2674 crate::SampleLevel::Auto => (),
2675 // Zero needs level set to 0
2676 crate::SampleLevel::Zero => {
2677 if workaround_lod_with_grad {
2678 let vec_dim = match dim {
2679 crate::ImageDimension::Cube => 3,
2680 _ => 2,
2681 };
2682 write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?;
2683 } else if gather.is_none() {
2684 write!(self.out, ", 0.0")?;
2685 }
2686 }
2687 // Exact and bias require another argument
2688 crate::SampleLevel::Exact(expr) => {
2689 write!(self.out, ", ")?;
2690 self.write_expr(expr, ctx)?;
2691 }
2692 crate::SampleLevel::Bias(_) => {
2693 // This needs to be done after the offset writing
2694 }
2695 crate::SampleLevel::Gradient { x, y } => {
2696 // If we are using sampler2D to replace sampler1D, we also
2697 // need to make sure to use vec2 gradients
2698 if tex_1d_hack {
2699 write!(self.out, ", vec2(")?;
2700 self.write_expr(x, ctx)?;
2701 write!(self.out, ", 0.0)")?;
2702 write!(self.out, ", vec2(")?;
2703 self.write_expr(y, ctx)?;
2704 write!(self.out, ", 0.0)")?;
2705 } else {
2706 write!(self.out, ", ")?;
2707 self.write_expr(x, ctx)?;
2708 write!(self.out, ", ")?;
2709 self.write_expr(y, ctx)?;
2710 }
2711 }
2712 }
2713
2714 if let Some(constant) = offset {
2715 write!(self.out, ", ")?;
2716 if tex_1d_hack {
2717 write!(self.out, "ivec2(")?;
2718 }
2719 self.write_const_expr(constant, ctx.expressions)?;
2720 if tex_1d_hack {
2721 write!(self.out, ", 0)")?;
2722 }
2723 }
2724
2725 // Bias is always the last argument
2726 if let crate::SampleLevel::Bias(expr) = level {
2727 write!(self.out, ", ")?;
2728 self.write_expr(expr, ctx)?;
2729 }
2730
2731 if let (Some(component), None) = (gather, depth_ref) {
2732 write!(self.out, ", {}", component as usize)?;
2733 }
2734
2735 // End the function
2736 write!(self.out, ")")?
2737 }
2738 Expression::ImageLoad {
2739 image,
2740 coordinate,
2741 array_index,
2742 sample,
2743 level,
2744 } => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?,
2745 // Query translates into one of the:
2746 // - textureSize/imageSize
2747 // - textureQueryLevels
2748 // - textureSamples/imageSamples
2749 Expression::ImageQuery { image, query } => {
2750 use crate::ImageClass;
2751
2752 // This will only panic if the module is invalid
2753 let (dim, class) = match *ctx.resolve_type(image, &self.module.types) {
2754 TypeInner::Image {
2755 dim,
2756 arrayed: _,
2757 class,
2758 } => (dim, class),
2759 _ => unreachable!(),
2760 };
2761 let components = match dim {
2762 crate::ImageDimension::D1 => 1,
2763 crate::ImageDimension::D2 => 2,
2764 crate::ImageDimension::D3 => 3,
2765 crate::ImageDimension::Cube => 2,
2766 };
2767
2768 if let crate::ImageQuery::Size { .. } = query {
2769 match components {
2770 1 => write!(self.out, "uint(")?,
2771 _ => write!(self.out, "uvec{components}(")?,
2772 }
2773 } else {
2774 write!(self.out, "uint(")?;
2775 }
2776
2777 match query {
2778 crate::ImageQuery::Size { level } => {
2779 match class {
2780 ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => {
2781 write!(self.out, "textureSize(")?;
2782 self.write_expr(image, ctx)?;
2783 if let Some(expr) = level {
2784 let cast_to_int = matches!(
2785 *ctx.resolve_type(expr, &self.module.types),
2786 TypeInner::Scalar(crate::Scalar {
2787 kind: crate::ScalarKind::Uint,
2788 ..
2789 })
2790 );
2791
2792 write!(self.out, ", ")?;
2793
2794 if cast_to_int {
2795 write!(self.out, "int(")?;
2796 }
2797
2798 self.write_expr(expr, ctx)?;
2799
2800 if cast_to_int {
2801 write!(self.out, ")")?;
2802 }
2803 } else if !multi {
2804 // All textureSize calls requires an lod argument
2805 // except for multisampled samplers
2806 write!(self.out, ", 0")?;
2807 }
2808 }
2809 ImageClass::Storage { .. } => {
2810 write!(self.out, "imageSize(")?;
2811 self.write_expr(image, ctx)?;
2812 }
2813 ImageClass::External => unimplemented!(),
2814 }
2815 write!(self.out, ")")?;
2816 if components != 1 || self.options.version.is_es() {
2817 write!(self.out, ".{}", &"xyz"[..components])?;
2818 }
2819 }
2820 crate::ImageQuery::NumLevels => {
2821 write!(self.out, "textureQueryLevels(",)?;
2822 self.write_expr(image, ctx)?;
2823 write!(self.out, ")",)?;
2824 }
2825 crate::ImageQuery::NumLayers => {
2826 let fun_name = match class {
2827 ImageClass::Sampled { .. } | ImageClass::Depth { .. } => "textureSize",
2828 ImageClass::Storage { .. } => "imageSize",
2829 ImageClass::External => unimplemented!(),
2830 };
2831 write!(self.out, "{fun_name}(")?;
2832 self.write_expr(image, ctx)?;
2833 // All textureSize calls requires an lod argument
2834 // except for multisampled samplers
2835 if !class.is_multisampled() {
2836 write!(self.out, ", 0")?;
2837 }
2838 write!(self.out, ")")?;
2839 if components != 1 || self.options.version.is_es() {
2840 write!(self.out, ".{}", back::COMPONENTS[components])?;
2841 }
2842 }
2843 crate::ImageQuery::NumSamples => {
2844 let fun_name = match class {
2845 ImageClass::Sampled { .. } | ImageClass::Depth { .. } => {
2846 "textureSamples"
2847 }
2848 ImageClass::Storage { .. } => "imageSamples",
2849 ImageClass::External => unimplemented!(),
2850 };
2851 write!(self.out, "{fun_name}(")?;
2852 self.write_expr(image, ctx)?;
2853 write!(self.out, ")",)?;
2854 }
2855 }
2856
2857 write!(self.out, ")")?;
2858 }
2859 Expression::Unary { op, expr } => {
2860 let operator_or_fn = match op {
2861 crate::UnaryOperator::Negate => "-",
2862 crate::UnaryOperator::LogicalNot => {
2863 match *ctx.resolve_type(expr, &self.module.types) {
2864 TypeInner::Vector { .. } => "not",
2865 _ => "!",
2866 }
2867 }
2868 crate::UnaryOperator::BitwiseNot => "~",
2869 };
2870 write!(self.out, "{operator_or_fn}(")?;
2871
2872 self.write_expr(expr, ctx)?;
2873
2874 write!(self.out, ")")?
2875 }
2876 // `Binary` we just write `left op right`, except when dealing with
2877 // comparison operations on vectors as they are implemented with
2878 // builtin functions.
2879 // Once again we wrap everything in parentheses to avoid precedence issues
2880 Expression::Binary {
2881 mut op,
2882 left,
2883 right,
2884 } => {
2885 // Holds `Some(function_name)` if the binary operation is
2886 // implemented as a function call
2887 use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti};
2888
2889 let left_inner = ctx.resolve_type(left, &self.module.types);
2890 let right_inner = ctx.resolve_type(right, &self.module.types);
2891
2892 let function = match (left_inner, right_inner) {
2893 (&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op {
2894 Bo::Less
2895 | Bo::LessEqual
2896 | Bo::Greater
2897 | Bo::GreaterEqual
2898 | Bo::Equal
2899 | Bo::NotEqual => BinaryOperation::VectorCompare,
2900 Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo,
2901 Bo::Modulo if scalar.kind == Sk::Sint || scalar.kind == Sk::Uint => {
2902 BinaryOperation::ModuloInt
2903 }
2904 Bo::And if scalar.kind == Sk::Bool => {
2905 op = crate::BinaryOperator::LogicalAnd;
2906 BinaryOperation::VectorComponentWise
2907 }
2908 Bo::InclusiveOr if scalar.kind == Sk::Bool => {
2909 op = crate::BinaryOperator::LogicalOr;
2910 BinaryOperation::VectorComponentWise
2911 }
2912 _ => BinaryOperation::Other,
2913 },
2914 _ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) {
2915 (Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op {
2916 Bo::Modulo => BinaryOperation::Modulo,
2917 _ => BinaryOperation::Other,
2918 },
2919 (Some(Sk::Sint | Sk::Uint), _) | (_, Some(Sk::Sint | Sk::Uint))
2920 if op == Bo::Modulo =>
2921 {
2922 BinaryOperation::ModuloInt
2923 }
2924 (Some(Sk::Bool), Some(Sk::Bool)) => match op {
2925 Bo::InclusiveOr => {
2926 op = crate::BinaryOperator::LogicalOr;
2927 BinaryOperation::Other
2928 }
2929 Bo::And => {
2930 op = crate::BinaryOperator::LogicalAnd;
2931 BinaryOperation::Other
2932 }
2933 _ => BinaryOperation::Other,
2934 },
2935 _ => BinaryOperation::Other,
2936 },
2937 };
2938
2939 match function {
2940 BinaryOperation::VectorCompare => {
2941 let op_str = match op {
2942 Bo::Less => "lessThan(",
2943 Bo::LessEqual => "lessThanEqual(",
2944 Bo::Greater => "greaterThan(",
2945 Bo::GreaterEqual => "greaterThanEqual(",
2946 Bo::Equal => "equal(",
2947 Bo::NotEqual => "notEqual(",
2948 _ => unreachable!(),
2949 };
2950 write!(self.out, "{op_str}")?;
2951 self.write_expr(left, ctx)?;
2952 write!(self.out, ", ")?;
2953 self.write_expr(right, ctx)?;
2954 write!(self.out, ")")?;
2955 }
2956 BinaryOperation::VectorComponentWise => {
2957 self.write_value_type(left_inner)?;
2958 write!(self.out, "(")?;
2959
2960 let size = match *left_inner {
2961 Ti::Vector { size, .. } => size,
2962 _ => unreachable!(),
2963 };
2964
2965 for i in 0..size as usize {
2966 if i != 0 {
2967 write!(self.out, ", ")?;
2968 }
2969
2970 self.write_expr(left, ctx)?;
2971 write!(self.out, ".{}", back::COMPONENTS[i])?;
2972
2973 write!(self.out, " {} ", back::binary_operation_str(op))?;
2974
2975 self.write_expr(right, ctx)?;
2976 write!(self.out, ".{}", back::COMPONENTS[i])?;
2977 }
2978
2979 write!(self.out, ")")?;
2980 }
2981 // Signed/unsigned integer `%` with a negative operand is handled by
2982 // `BinaryOperation::ModuloInt` below. Remaining TODO: the degenerate
2983 // div-by-zero / `INT_MIN % -1` cases (this backend also leaves integer
2984 // `/` unguarded), and float `% 0` (see
2985 // https://github.com/gpuweb/gpuweb/issues/2798).
2986 BinaryOperation::Modulo => {
2987 write!(self.out, "(")?;
2988
2989 // write `e1 - e2 * trunc(e1 / e2)`
2990 self.write_expr(left, ctx)?;
2991 write!(self.out, " - ")?;
2992 self.write_expr(right, ctx)?;
2993 write!(self.out, " * ")?;
2994 write!(self.out, "trunc(")?;
2995 self.write_expr(left, ctx)?;
2996 write!(self.out, " / ")?;
2997 self.write_expr(right, ctx)?;
2998 write!(self.out, ")")?;
2999
3000 write!(self.out, ")")?;
3001 }
3002 BinaryOperation::ModuloInt => {
3003 // GLSL's `%` is undefined when either operand is negative.
3004 // Integer division truncates toward zero (which is well
3005 // defined), so reconstruct the remainder as `e1 - e2 * (e1 / e2)`.
3006 // This matches WGSL's truncated `%` for all operands; the
3007 // degenerate `x % 0` / `INT_MIN % -1` cases stay consistent
3008 // with this backend's unguarded integer `/`.
3009 write!(self.out, "(")?;
3010 self.write_expr(left, ctx)?;
3011 write!(self.out, " - ")?;
3012 self.write_expr(right, ctx)?;
3013 write!(self.out, " * (")?;
3014 self.write_expr(left, ctx)?;
3015 write!(self.out, " / ")?;
3016 self.write_expr(right, ctx)?;
3017 write!(self.out, "))")?;
3018 }
3019 BinaryOperation::Other => {
3020 write!(self.out, "(")?;
3021
3022 self.write_expr(left, ctx)?;
3023 write!(self.out, " {} ", back::binary_operation_str(op))?;
3024 self.write_expr(right, ctx)?;
3025
3026 write!(self.out, ")")?;
3027 }
3028 }
3029 }
3030 // `Select` is written as `condition ? accept : reject`
3031 // We wrap everything in parentheses to avoid precedence issues
3032 Expression::Select {
3033 condition,
3034 accept,
3035 reject,
3036 } => {
3037 let cond_ty = ctx.resolve_type(condition, &self.module.types);
3038 let vec_select = if let TypeInner::Vector { .. } = *cond_ty {
3039 true
3040 } else {
3041 false
3042 };
3043
3044 // TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix
3045 if vec_select {
3046 // Glsl defines that for mix when the condition is a boolean the first element
3047 // is picked if condition is false and the second if condition is true
3048 write!(self.out, "mix(")?;
3049 self.write_expr(reject, ctx)?;
3050 write!(self.out, ", ")?;
3051 self.write_expr(accept, ctx)?;
3052 write!(self.out, ", ")?;
3053 self.write_expr(condition, ctx)?;
3054 } else {
3055 write!(self.out, "(")?;
3056 self.write_expr(condition, ctx)?;
3057 write!(self.out, " ? ")?;
3058 self.write_expr(accept, ctx)?;
3059 write!(self.out, " : ")?;
3060 self.write_expr(reject, ctx)?;
3061 }
3062
3063 write!(self.out, ")")?
3064 }
3065 // `Derivative` is a function call to a glsl provided function
3066 Expression::Derivative { axis, ctrl, expr } => {
3067 use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl};
3068 let fun_name = if self.options.version.supports_derivative_control() {
3069 match (axis, ctrl) {
3070 (Axis::X, Ctrl::Coarse) => "dFdxCoarse",
3071 (Axis::X, Ctrl::Fine) => "dFdxFine",
3072 (Axis::X, Ctrl::None) => "dFdx",
3073 (Axis::Y, Ctrl::Coarse) => "dFdyCoarse",
3074 (Axis::Y, Ctrl::Fine) => "dFdyFine",
3075 (Axis::Y, Ctrl::None) => "dFdy",
3076 (Axis::Width, Ctrl::Coarse) => "fwidthCoarse",
3077 (Axis::Width, Ctrl::Fine) => "fwidthFine",
3078 (Axis::Width, Ctrl::None) => "fwidth",
3079 }
3080 } else {
3081 match axis {
3082 Axis::X => "dFdx",
3083 Axis::Y => "dFdy",
3084 Axis::Width => "fwidth",
3085 }
3086 };
3087 write!(self.out, "{fun_name}(")?;
3088 self.write_expr(expr, ctx)?;
3089 write!(self.out, ")")?
3090 }
3091 // `Relational` is a normal function call to some glsl provided functions
3092 Expression::Relational { fun, argument } => {
3093 use crate::RelationalFunction as Rf;
3094
3095 let fun_name = match fun {
3096 Rf::IsInf => "isinf",
3097 Rf::IsNan => "isnan",
3098 Rf::All => "all",
3099 Rf::Any => "any",
3100 };
3101 write!(self.out, "{fun_name}(")?;
3102
3103 self.write_expr(argument, ctx)?;
3104
3105 write!(self.out, ")")?
3106 }
3107 Expression::Math {
3108 fun,
3109 arg,
3110 arg1,
3111 arg2,
3112 arg3,
3113 } => {
3114 use crate::MathFunction as Mf;
3115
3116 let fun_name = match fun {
3117 // comparison
3118 Mf::Abs => "abs",
3119 Mf::Min => "min",
3120 Mf::Max => "max",
3121 Mf::Clamp => {
3122 let scalar_kind = ctx
3123 .resolve_type(arg, &self.module.types)
3124 .scalar_kind()
3125 .unwrap();
3126 match scalar_kind {
3127 crate::ScalarKind::Float => "clamp",
3128 // Clamp is undefined if min > max. In practice this means it can use a median-of-three
3129 // instruction to determine the value. This is fine according to the WGSL spec for float
3130 // clamp, but integer clamp _must_ use min-max. As such we write out min/max.
3131 _ => {
3132 write!(self.out, "min(max(")?;
3133 self.write_expr(arg, ctx)?;
3134 write!(self.out, ", ")?;
3135 self.write_expr(arg1.unwrap(), ctx)?;
3136 write!(self.out, "), ")?;
3137 self.write_expr(arg2.unwrap(), ctx)?;
3138 write!(self.out, ")")?;
3139
3140 return Ok(());
3141 }
3142 }
3143 }
3144 Mf::Saturate => {
3145 write!(self.out, "clamp(")?;
3146
3147 self.write_expr(arg, ctx)?;
3148
3149 match *ctx.resolve_type(arg, &self.module.types) {
3150 TypeInner::Vector { size, .. } => write!(
3151 self.out,
3152 ", vec{}(0.0), vec{0}(1.0)",
3153 common::vector_size_str(size)
3154 )?,
3155 _ => write!(self.out, ", 0.0, 1.0")?,
3156 }
3157
3158 write!(self.out, ")")?;
3159
3160 return Ok(());
3161 }
3162 // trigonometry
3163 Mf::Cos => "cos",
3164 Mf::Cosh => "cosh",
3165 Mf::Sin => "sin",
3166 Mf::Sinh => "sinh",
3167 Mf::Tan => "tan",
3168 Mf::Tanh => "tanh",
3169 Mf::Acos => "acos",
3170 Mf::Asin => "asin",
3171 Mf::Atan => "atan",
3172 Mf::Asinh => "asinh",
3173 Mf::Acosh => "acosh",
3174 Mf::Atanh => "atanh",
3175 Mf::Radians => "radians",
3176 Mf::Degrees => "degrees",
3177 // glsl doesn't have atan2 function
3178 // use two-argument variation of the atan function
3179 Mf::Atan2 => "atan",
3180 // decomposition
3181 Mf::Ceil => "ceil",
3182 Mf::Floor => "floor",
3183 Mf::Round => "roundEven",
3184 Mf::Fract => "fract",
3185 Mf::Trunc => "trunc",
3186 Mf::Modf => MODF_FUNCTION,
3187 Mf::Frexp => FREXP_FUNCTION,
3188 Mf::Ldexp => "ldexp",
3189 // exponent
3190 Mf::Exp => "exp",
3191 Mf::Exp2 => "exp2",
3192 Mf::Log => "log",
3193 Mf::Log2 => "log2",
3194 Mf::Pow => "pow",
3195 // geometry
3196 Mf::Dot => match *ctx.resolve_type(arg, &self.module.types) {
3197 TypeInner::Vector {
3198 scalar:
3199 crate::Scalar {
3200 kind: crate::ScalarKind::Float,
3201 ..
3202 },
3203 ..
3204 } => "dot",
3205 TypeInner::Vector { size, .. } => {
3206 return self.write_dot_product(arg, arg1.unwrap(), size as usize, ctx)
3207 }
3208 _ => unreachable!(
3209 "Correct TypeInner for dot product should be already validated"
3210 ),
3211 },
3212 fun @ (Mf::Dot4I8Packed | Mf::Dot4U8Packed) => {
3213 let conversion = match fun {
3214 Mf::Dot4I8Packed => "int",
3215 Mf::Dot4U8Packed => "",
3216 _ => unreachable!(),
3217 };
3218
3219 let arg1 = arg1.unwrap();
3220
3221 // Write parentheses around the dot product expression to prevent operators
3222 // with different precedences from applying earlier.
3223 write!(self.out, "(")?;
3224 for i in 0..4 {
3225 // Since `bitfieldExtract` only sign extends if the value is signed, we
3226 // need to convert the inputs to `int` in case of `Dot4I8Packed`. For
3227 // `Dot4U8Packed`, the code below only introduces parenthesis around
3228 // each factor, which aren't strictly needed because both operands are
3229 // baked, but which don't hurt either.
3230 write!(self.out, "bitfieldExtract({conversion}(")?;
3231 self.write_expr(arg, ctx)?;
3232 write!(self.out, "), {}, 8)", i * 8)?;
3233
3234 write!(self.out, " * bitfieldExtract({conversion}(")?;
3235 self.write_expr(arg1, ctx)?;
3236 write!(self.out, "), {}, 8)", i * 8)?;
3237
3238 if i != 3 {
3239 write!(self.out, " + ")?;
3240 }
3241 }
3242 write!(self.out, ")")?;
3243
3244 return Ok(());
3245 }
3246 Mf::Outer => "outerProduct",
3247 Mf::Cross => "cross",
3248 Mf::Distance => "distance",
3249 Mf::Length => "length",
3250 Mf::Normalize => "normalize",
3251 Mf::FaceForward => "faceforward",
3252 Mf::Reflect => "reflect",
3253 Mf::Refract => "refract",
3254 // computational
3255 Mf::Sign => "sign",
3256 Mf::Fma => {
3257 if self.options.version.supports_fma_function() {
3258 // Use the fma function when available
3259 "fma"
3260 } else {
3261 // No fma support. Transform the function call into an arithmetic expression
3262 write!(self.out, "(")?;
3263
3264 self.write_expr(arg, ctx)?;
3265 write!(self.out, " * ")?;
3266
3267 let arg1 =
3268 arg1.ok_or_else(|| Error::Custom("Missing fma arg1".to_owned()))?;
3269 self.write_expr(arg1, ctx)?;
3270 write!(self.out, " + ")?;
3271
3272 let arg2 =
3273 arg2.ok_or_else(|| Error::Custom("Missing fma arg2".to_owned()))?;
3274 self.write_expr(arg2, ctx)?;
3275 write!(self.out, ")")?;
3276
3277 return Ok(());
3278 }
3279 }
3280 Mf::Mix => "mix",
3281 Mf::Step => "step",
3282 Mf::SmoothStep => "smoothstep",
3283 Mf::Sqrt => "sqrt",
3284 Mf::InverseSqrt => "inversesqrt",
3285 Mf::Inverse => "inverse",
3286 Mf::Transpose => "transpose",
3287 Mf::Determinant => "determinant",
3288 Mf::QuantizeToF16 => match *ctx.resolve_type(arg, &self.module.types) {
3289 TypeInner::Scalar { .. } => {
3290 write!(self.out, "unpackHalf2x16(packHalf2x16(vec2(")?;
3291 self.write_expr(arg, ctx)?;
3292 write!(self.out, "))).x")?;
3293 return Ok(());
3294 }
3295 TypeInner::Vector {
3296 size: crate::VectorSize::Bi,
3297 ..
3298 } => {
3299 write!(self.out, "unpackHalf2x16(packHalf2x16(")?;
3300 self.write_expr(arg, ctx)?;
3301 write!(self.out, "))")?;
3302 return Ok(());
3303 }
3304 TypeInner::Vector {
3305 size: crate::VectorSize::Tri,
3306 ..
3307 } => {
3308 write!(self.out, "vec3(unpackHalf2x16(packHalf2x16(")?;
3309 self.write_expr(arg, ctx)?;
3310 write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?;
3311 self.write_expr(arg, ctx)?;
3312 write!(self.out, ".zz)).x)")?;
3313 return Ok(());
3314 }
3315 TypeInner::Vector {
3316 size: crate::VectorSize::Quad,
3317 ..
3318 } => {
3319 write!(self.out, "vec4(unpackHalf2x16(packHalf2x16(")?;
3320 self.write_expr(arg, ctx)?;
3321 write!(self.out, ".xy)), unpackHalf2x16(packHalf2x16(")?;
3322 self.write_expr(arg, ctx)?;
3323 write!(self.out, ".zw)))")?;
3324 return Ok(());
3325 }
3326 _ => unreachable!(
3327 "Correct TypeInner for QuantizeToF16 should be already validated"
3328 ),
3329 },
3330 // bits
3331 Mf::CountTrailingZeros => {
3332 match *ctx.resolve_type(arg, &self.module.types) {
3333 TypeInner::Vector { size, scalar, .. } => {
3334 let s = common::vector_size_str(size);
3335 if let crate::ScalarKind::Uint = scalar.kind {
3336 write!(self.out, "min(uvec{s}(findLSB(")?;
3337 self.write_expr(arg, ctx)?;
3338 write!(self.out, ")), uvec{s}(32u))")?;
3339 } else {
3340 write!(self.out, "ivec{s}(min(uvec{s}(findLSB(")?;
3341 self.write_expr(arg, ctx)?;
3342 write!(self.out, ")), uvec{s}(32u)))")?;
3343 }
3344 }
3345 TypeInner::Scalar(scalar) => {
3346 if let crate::ScalarKind::Uint = scalar.kind {
3347 write!(self.out, "min(uint(findLSB(")?;
3348 self.write_expr(arg, ctx)?;
3349 write!(self.out, ")), 32u)")?;
3350 } else {
3351 write!(self.out, "int(min(uint(findLSB(")?;
3352 self.write_expr(arg, ctx)?;
3353 write!(self.out, ")), 32u))")?;
3354 }
3355 }
3356 _ => unreachable!(),
3357 };
3358 return Ok(());
3359 }
3360 Mf::CountLeadingZeros => {
3361 if self.options.version.supports_integer_functions() {
3362 match *ctx.resolve_type(arg, &self.module.types) {
3363 TypeInner::Vector { size, scalar } => {
3364 let s = common::vector_size_str(size);
3365
3366 if let crate::ScalarKind::Uint = scalar.kind {
3367 write!(self.out, "uvec{s}(ivec{s}(31) - findMSB(")?;
3368 self.write_expr(arg, ctx)?;
3369 write!(self.out, "))")?;
3370 } else {
3371 write!(self.out, "mix(ivec{s}(31) - findMSB(")?;
3372 self.write_expr(arg, ctx)?;
3373 write!(self.out, "), ivec{s}(0), lessThan(")?;
3374 self.write_expr(arg, ctx)?;
3375 write!(self.out, ", ivec{s}(0)))")?;
3376 }
3377 }
3378 TypeInner::Scalar(scalar) => {
3379 if let crate::ScalarKind::Uint = scalar.kind {
3380 write!(self.out, "uint(31 - findMSB(")?;
3381 } else {
3382 write!(self.out, "(")?;
3383 self.write_expr(arg, ctx)?;
3384 write!(self.out, " < 0 ? 0 : 31 - findMSB(")?;
3385 }
3386
3387 self.write_expr(arg, ctx)?;
3388 write!(self.out, "))")?;
3389 }
3390 _ => unreachable!(),
3391 };
3392 } else {
3393 match *ctx.resolve_type(arg, &self.module.types) {
3394 TypeInner::Vector { size, scalar } => {
3395 let s = common::vector_size_str(size);
3396
3397 if let crate::ScalarKind::Uint = scalar.kind {
3398 write!(self.out, "uvec{s}(")?;
3399 write!(self.out, "vec{s}(31.0) - floor(log2(vec{s}(")?;
3400 self.write_expr(arg, ctx)?;
3401 write!(self.out, ") + 0.5)))")?;
3402 } else {
3403 write!(self.out, "ivec{s}(")?;
3404 write!(self.out, "mix(vec{s}(31.0) - floor(log2(vec{s}(")?;
3405 self.write_expr(arg, ctx)?;
3406 write!(self.out, ") + 0.5)), ")?;
3407 write!(self.out, "vec{s}(0.0), lessThan(")?;
3408 self.write_expr(arg, ctx)?;
3409 write!(self.out, ", ivec{s}(0u))))")?;
3410 }
3411 }
3412 TypeInner::Scalar(scalar) => {
3413 if let crate::ScalarKind::Uint = scalar.kind {
3414 write!(self.out, "uint(31.0 - floor(log2(float(")?;
3415 self.write_expr(arg, ctx)?;
3416 write!(self.out, ") + 0.5)))")?;
3417 } else {
3418 write!(self.out, "(")?;
3419 self.write_expr(arg, ctx)?;
3420 write!(self.out, " < 0 ? 0 : int(")?;
3421 write!(self.out, "31.0 - floor(log2(float(")?;
3422 self.write_expr(arg, ctx)?;
3423 write!(self.out, ") + 0.5))))")?;
3424 }
3425 }
3426 _ => unreachable!(),
3427 };
3428 }
3429
3430 return Ok(());
3431 }
3432 Mf::CountOneBits => "bitCount",
3433 Mf::ReverseBits => "bitfieldReverse",
3434 Mf::ExtractBits => {
3435 // The behavior of ExtractBits is undefined when offset + count > bit_width. We need
3436 // to first sanitize the offset and count first. If we don't do this, AMD and Intel chips
3437 // will return out-of-spec values if the extracted range is not within the bit width.
3438 //
3439 // This encodes the exact formula specified by the wgsl spec, without temporary values:
3440 // https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin
3441 //
3442 // w = sizeof(x) * 8
3443 // o = min(offset, w)
3444 // c = min(count, w - o)
3445 //
3446 // bitfieldExtract(x, o, c)
3447 //
3448 // extract_bits(e, min(offset, w), min(count, w - min(offset, w))))
3449 let scalar_bits = ctx
3450 .resolve_type(arg, &self.module.types)
3451 .scalar_width()
3452 .unwrap()
3453 * 8;
3454
3455 write!(self.out, "bitfieldExtract(")?;
3456 self.write_expr(arg, ctx)?;
3457 write!(self.out, ", int(min(")?;
3458 self.write_expr(arg1.unwrap(), ctx)?;
3459 write!(self.out, ", {scalar_bits}u)), int(min(",)?;
3460 self.write_expr(arg2.unwrap(), ctx)?;
3461 write!(self.out, ", {scalar_bits}u - min(")?;
3462 self.write_expr(arg1.unwrap(), ctx)?;
3463 write!(self.out, ", {scalar_bits}u))))")?;
3464
3465 return Ok(());
3466 }
3467 Mf::InsertBits => {
3468 // InsertBits has the same considerations as ExtractBits above
3469 let scalar_bits = ctx
3470 .resolve_type(arg, &self.module.types)
3471 .scalar_width()
3472 .unwrap()
3473 * 8;
3474
3475 write!(self.out, "bitfieldInsert(")?;
3476 self.write_expr(arg, ctx)?;
3477 write!(self.out, ", ")?;
3478 self.write_expr(arg1.unwrap(), ctx)?;
3479 write!(self.out, ", int(min(")?;
3480 self.write_expr(arg2.unwrap(), ctx)?;
3481 write!(self.out, ", {scalar_bits}u)), int(min(",)?;
3482 self.write_expr(arg3.unwrap(), ctx)?;
3483 write!(self.out, ", {scalar_bits}u - min(")?;
3484 self.write_expr(arg2.unwrap(), ctx)?;
3485 write!(self.out, ", {scalar_bits}u))))")?;
3486
3487 return Ok(());
3488 }
3489 Mf::FirstTrailingBit => "findLSB",
3490 Mf::FirstLeadingBit => "findMSB",
3491 // data packing
3492 Mf::Pack4x8snorm => {
3493 if self.options.version.supports_pack_unpack_4x8() {
3494 "packSnorm4x8"
3495 } else {
3496 // polyfill should go here. Needs a corresponding entry in `need_bake_expression`
3497 return Err(Error::UnsupportedExternal("packSnorm4x8".into()));
3498 }
3499 }
3500 Mf::Pack4x8unorm => {
3501 if self.options.version.supports_pack_unpack_4x8() {
3502 "packUnorm4x8"
3503 } else {
3504 return Err(Error::UnsupportedExternal("packUnorm4x8".to_owned()));
3505 }
3506 }
3507 Mf::Pack2x16snorm => {
3508 if self.options.version.supports_pack_unpack_snorm_2x16() {
3509 "packSnorm2x16"
3510 } else {
3511 return Err(Error::UnsupportedExternal("packSnorm2x16".to_owned()));
3512 }
3513 }
3514 Mf::Pack2x16unorm => {
3515 if self.options.version.supports_pack_unpack_unorm_2x16() {
3516 "packUnorm2x16"
3517 } else {
3518 return Err(Error::UnsupportedExternal("packUnorm2x16".to_owned()));
3519 }
3520 }
3521 Mf::Pack2x16float => {
3522 if self.options.version.supports_pack_unpack_half_2x16() {
3523 "packHalf2x16"
3524 } else {
3525 return Err(Error::UnsupportedExternal("packHalf2x16".to_owned()));
3526 }
3527 }
3528
3529 fun @ (Mf::Pack4xI8 | Mf::Pack4xU8 | Mf::Pack4xI8Clamp | Mf::Pack4xU8Clamp) => {
3530 let was_signed = matches!(fun, Mf::Pack4xI8 | Mf::Pack4xI8Clamp);
3531 let clamp_bounds = match fun {
3532 Mf::Pack4xI8Clamp => Some(("-128", "127")),
3533 Mf::Pack4xU8Clamp => Some(("0", "255")),
3534 _ => None,
3535 };
3536 let const_suffix = if was_signed { "" } else { "u" };
3537 if was_signed {
3538 write!(self.out, "uint(")?;
3539 }
3540 let write_arg = |this: &mut Self| -> BackendResult {
3541 if let Some((min, max)) = clamp_bounds {
3542 write!(this.out, "clamp(")?;
3543 this.write_expr(arg, ctx)?;
3544 write!(this.out, ", {min}{const_suffix}, {max}{const_suffix})")?;
3545 } else {
3546 this.write_expr(arg, ctx)?;
3547 }
3548 Ok(())
3549 };
3550 write!(self.out, "(")?;
3551 write_arg(self)?;
3552 write!(self.out, "[0] & 0xFF{const_suffix}) | ((")?;
3553 write_arg(self)?;
3554 write!(self.out, "[1] & 0xFF{const_suffix}) << 8) | ((")?;
3555 write_arg(self)?;
3556 write!(self.out, "[2] & 0xFF{const_suffix}) << 16) | ((")?;
3557 write_arg(self)?;
3558 write!(self.out, "[3] & 0xFF{const_suffix}) << 24)")?;
3559 if was_signed {
3560 write!(self.out, ")")?;
3561 }
3562
3563 return Ok(());
3564 }
3565 // data unpacking
3566 Mf::Unpack2x16float => {
3567 if self.options.version.supports_pack_unpack_half_2x16() {
3568 "unpackHalf2x16"
3569 } else {
3570 return Err(Error::UnsupportedExternal("unpackHalf2x16".into()));
3571 }
3572 }
3573 Mf::Unpack2x16snorm => {
3574 if self.options.version.supports_pack_unpack_snorm_2x16() {
3575 "unpackSnorm2x16"
3576 } else {
3577 let scale = 32767;
3578
3579 write!(self.out, "(vec2(ivec2(")?;
3580 self.write_expr(arg, ctx)?;
3581 write!(self.out, " << 16, ")?;
3582 self.write_expr(arg, ctx)?;
3583 write!(self.out, ") >> 16) / {scale}.0)")?;
3584 return Ok(());
3585 }
3586 }
3587 Mf::Unpack2x16unorm => {
3588 if self.options.version.supports_pack_unpack_unorm_2x16() {
3589 "unpackUnorm2x16"
3590 } else {
3591 let scale = 65535;
3592
3593 write!(self.out, "(vec2(")?;
3594 self.write_expr(arg, ctx)?;
3595 write!(self.out, " & 0xFFFFu, ")?;
3596 self.write_expr(arg, ctx)?;
3597 write!(self.out, " >> 16) / {scale}.0)")?;
3598 return Ok(());
3599 }
3600 }
3601 Mf::Unpack4x8snorm => {
3602 if self.options.version.supports_pack_unpack_4x8() {
3603 "unpackSnorm4x8"
3604 } else {
3605 let scale = 127;
3606
3607 write!(self.out, "(vec4(ivec4(")?;
3608 self.write_expr(arg, ctx)?;
3609 write!(self.out, " << 24, ")?;
3610 self.write_expr(arg, ctx)?;
3611 write!(self.out, " << 16, ")?;
3612 self.write_expr(arg, ctx)?;
3613 write!(self.out, " << 8, ")?;
3614 self.write_expr(arg, ctx)?;
3615 write!(self.out, ") >> 24) / {scale}.0)")?;
3616 return Ok(());
3617 }
3618 }
3619 Mf::Unpack4x8unorm => {
3620 if self.options.version.supports_pack_unpack_4x8() {
3621 "unpackUnorm4x8"
3622 } else {
3623 let scale = 255;
3624
3625 write!(self.out, "(vec4(")?;
3626 self.write_expr(arg, ctx)?;
3627 write!(self.out, " & 0xFFu, ")?;
3628 self.write_expr(arg, ctx)?;
3629 write!(self.out, " >> 8 & 0xFFu, ")?;
3630 self.write_expr(arg, ctx)?;
3631 write!(self.out, " >> 16 & 0xFFu, ")?;
3632 self.write_expr(arg, ctx)?;
3633 write!(self.out, " >> 24) / {scale}.0)")?;
3634 return Ok(());
3635 }
3636 }
3637 fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => {
3638 let sign_prefix = match fun {
3639 Mf::Unpack4xI8 => 'i',
3640 Mf::Unpack4xU8 => 'u',
3641 _ => unreachable!(),
3642 };
3643 write!(self.out, "{sign_prefix}vec4(")?;
3644 for i in 0..4 {
3645 write!(self.out, "bitfieldExtract(")?;
3646 // Since bitfieldExtract only sign extends if the value is signed, this
3647 // cast is needed
3648 match fun {
3649 Mf::Unpack4xI8 => {
3650 write!(self.out, "int(")?;
3651 self.write_expr(arg, ctx)?;
3652 write!(self.out, ")")?;
3653 }
3654 Mf::Unpack4xU8 => self.write_expr(arg, ctx)?,
3655 _ => unreachable!(),
3656 };
3657 write!(self.out, ", {}, 8)", i * 8)?;
3658 if i != 3 {
3659 write!(self.out, ", ")?;
3660 }
3661 }
3662 write!(self.out, ")")?;
3663
3664 return Ok(());
3665 }
3666 };
3667
3668 let extract_bits = fun == Mf::ExtractBits;
3669 let insert_bits = fun == Mf::InsertBits;
3670
3671 // Some GLSL functions always return signed integers (like findMSB),
3672 // so they need to be cast to uint if the argument is also an uint.
3673 let ret_might_need_int_to_uint = matches!(
3674 fun,
3675 Mf::FirstTrailingBit | Mf::FirstLeadingBit | Mf::CountOneBits | Mf::Abs
3676 );
3677
3678 // Some GLSL functions only accept signed integers (like abs),
3679 // so they need their argument cast from uint to int.
3680 let arg_might_need_uint_to_int = matches!(fun, Mf::Abs);
3681
3682 // Check if the argument is an unsigned integer and return the vector size
3683 // in case it's a vector
3684 let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) {
3685 TypeInner::Scalar(crate::Scalar {
3686 kind: crate::ScalarKind::Uint,
3687 ..
3688 }) => Some(None),
3689 TypeInner::Vector {
3690 scalar:
3691 crate::Scalar {
3692 kind: crate::ScalarKind::Uint,
3693 ..
3694 },
3695 size,
3696 } => Some(Some(size)),
3697 _ => None,
3698 };
3699
3700 // Cast to uint if the function needs it
3701 if ret_might_need_int_to_uint {
3702 if let Some(maybe_size) = maybe_uint_size {
3703 match maybe_size {
3704 Some(size) => write!(self.out, "uvec{}(", size as u8)?,
3705 None => write!(self.out, "uint(")?,
3706 }
3707 }
3708 }
3709
3710 write!(self.out, "{fun_name}(")?;
3711
3712 // Cast to int if the function needs it
3713 if arg_might_need_uint_to_int {
3714 if let Some(maybe_size) = maybe_uint_size {
3715 match maybe_size {
3716 Some(size) => write!(self.out, "ivec{}(", size as u8)?,
3717 None => write!(self.out, "int(")?,
3718 }
3719 }
3720 }
3721
3722 self.write_expr(arg, ctx)?;
3723
3724 // Close the cast from uint to int
3725 if arg_might_need_uint_to_int && maybe_uint_size.is_some() {
3726 write!(self.out, ")")?
3727 }
3728
3729 if let Some(arg) = arg1 {
3730 write!(self.out, ", ")?;
3731 if extract_bits {
3732 write!(self.out, "int(")?;
3733 self.write_expr(arg, ctx)?;
3734 write!(self.out, ")")?;
3735 } else {
3736 self.write_expr(arg, ctx)?;
3737 }
3738 }
3739 if let Some(arg) = arg2 {
3740 write!(self.out, ", ")?;
3741 if extract_bits || insert_bits {
3742 write!(self.out, "int(")?;
3743 self.write_expr(arg, ctx)?;
3744 write!(self.out, ")")?;
3745 } else {
3746 self.write_expr(arg, ctx)?;
3747 }
3748 }
3749 if let Some(arg) = arg3 {
3750 write!(self.out, ", ")?;
3751 if insert_bits {
3752 write!(self.out, "int(")?;
3753 self.write_expr(arg, ctx)?;
3754 write!(self.out, ")")?;
3755 } else {
3756 self.write_expr(arg, ctx)?;
3757 }
3758 }
3759 write!(self.out, ")")?;
3760
3761 // Close the cast from int to uint
3762 if ret_might_need_int_to_uint && maybe_uint_size.is_some() {
3763 write!(self.out, ")")?
3764 }
3765 }
3766 // `As` is always a call.
3767 // If `convert` is true the function name is the type
3768 // Else the function name is one of the glsl provided bitcast functions
3769 Expression::As {
3770 expr,
3771 kind: target_kind,
3772 convert,
3773 } => {
3774 let inner = ctx.resolve_type(expr, &self.module.types);
3775 match convert {
3776 Some(width) => {
3777 // this is similar to `write_type`, but with the target kind
3778 let scalar = glsl_scalar(crate::Scalar {
3779 kind: target_kind,
3780 width,
3781 })?;
3782 match *inner {
3783 TypeInner::Matrix { columns, rows, .. } => write!(
3784 self.out,
3785 "{}mat{}x{}",
3786 scalar.prefix, columns as u8, rows as u8
3787 )?,
3788 TypeInner::Vector { size, .. } => {
3789 write!(self.out, "{}vec{}", scalar.prefix, size as u8)?
3790 }
3791 _ => write!(self.out, "{}", scalar.full)?,
3792 }
3793
3794 write!(self.out, "(")?;
3795 self.write_expr(expr, ctx)?;
3796 write!(self.out, ")")?
3797 }
3798 None => {
3799 use crate::ScalarKind as Sk;
3800
3801 let target_vector_type = match *inner {
3802 TypeInner::Vector { size, scalar } => Some(TypeInner::Vector {
3803 size,
3804 scalar: crate::Scalar {
3805 kind: target_kind,
3806 width: scalar.width,
3807 },
3808 }),
3809 _ => None,
3810 };
3811
3812 let source_kind = inner.scalar_kind().unwrap();
3813
3814 match (source_kind, target_kind, target_vector_type) {
3815 // No conversion needed
3816 (Sk::Sint, Sk::Sint, _)
3817 | (Sk::Uint, Sk::Uint, _)
3818 | (Sk::Float, Sk::Float, _)
3819 | (Sk::Bool, Sk::Bool, _) => {
3820 self.write_expr(expr, ctx)?;
3821 return Ok(());
3822 }
3823
3824 // Cast to/from floats
3825 (Sk::Float, Sk::Sint, _) => write!(self.out, "floatBitsToInt")?,
3826 (Sk::Float, Sk::Uint, _) => write!(self.out, "floatBitsToUint")?,
3827 (Sk::Sint, Sk::Float, _) => write!(self.out, "intBitsToFloat")?,
3828 (Sk::Uint, Sk::Float, _) => write!(self.out, "uintBitsToFloat")?,
3829
3830 // Cast between vector types
3831 (_, _, Some(vector)) => {
3832 self.write_value_type(&vector)?;
3833 }
3834
3835 // There is no way to bitcast between Uint/Sint in glsl. Use constructor conversion
3836 (Sk::Uint | Sk::Bool, Sk::Sint, None) => write!(self.out, "int")?,
3837 (Sk::Sint | Sk::Bool, Sk::Uint, None) => write!(self.out, "uint")?,
3838 (Sk::Bool, Sk::Float, None) => write!(self.out, "float")?,
3839 (Sk::Sint | Sk::Uint | Sk::Float, Sk::Bool, None) => {
3840 write!(self.out, "bool")?
3841 }
3842
3843 (Sk::AbstractInt | Sk::AbstractFloat, _, _)
3844 | (_, Sk::AbstractInt | Sk::AbstractFloat, _) => unreachable!(),
3845 };
3846
3847 write!(self.out, "(")?;
3848 self.write_expr(expr, ctx)?;
3849 write!(self.out, ")")?;
3850 }
3851 }
3852 }
3853 // These expressions never show up in `Emit`.
3854 Expression::CallResult(_)
3855 | Expression::AtomicResult { .. }
3856 | Expression::RayQueryProceedResult
3857 | Expression::WorkGroupUniformLoadResult { .. }
3858 | Expression::SubgroupOperationResult { .. }
3859 | Expression::SubgroupBallotResult => unreachable!(),
3860 // `ArrayLength` is written as `expr.length()` and we convert it to a uint
3861 Expression::ArrayLength(expr) => {
3862 write!(self.out, "uint(")?;
3863 self.write_expr(expr, ctx)?;
3864 write!(self.out, ".length())")?
3865 }
3866 // not supported yet
3867 Expression::RayQueryGetIntersection { .. }
3868 | Expression::RayQueryVertexPositions { .. }
3869 | Expression::CooperativeLoad { .. }
3870 | Expression::CooperativeMultiplyAdd { .. } => unreachable!(),
3871 }
3872
3873 Ok(())
3874 }
3875
3876 /// Helper function to write the local holding the clamped lod
3877 fn write_clamped_lod(
3878 &mut self,
3879 ctx: &back::FunctionCtx,
3880 expr: Handle<crate::Expression>,
3881 image: Handle<crate::Expression>,
3882 level_expr: Handle<crate::Expression>,
3883 ) -> Result<(), Error> {
3884 // Define our local and start a call to `clamp`
3885 write!(
3886 self.out,
3887 "int {}{} = clamp(",
3888 Baked(expr),
3889 CLAMPED_LOD_SUFFIX
3890 )?;
3891 // Write the lod that will be clamped
3892 self.write_expr(level_expr, ctx)?;
3893 // Set the min value to 0 and start a call to `textureQueryLevels` to get
3894 // the maximum value
3895 write!(self.out, ", 0, textureQueryLevels(")?;
3896 // Write the target image as an argument to `textureQueryLevels`
3897 self.write_expr(image, ctx)?;
3898 // Close the call to `textureQueryLevels` subtract 1 from it since
3899 // the lod argument is 0 based, close the `clamp` call and end the
3900 // local declaration statement.
3901 writeln!(self.out, ") - 1);")?;
3902
3903 Ok(())
3904 }
3905
3906 // Helper method used to retrieve how many elements a coordinate vector
3907 // for the images operations need.
3908 fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 {
3909 // openGL es doesn't have 1D images so we need workaround it
3910 let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es();
3911 // Get how many components the coordinate vector needs for the dimensions only
3912 let tex_coord_size = match dim {
3913 crate::ImageDimension::D1 => 1,
3914 crate::ImageDimension::D2 => 2,
3915 crate::ImageDimension::D3 => 3,
3916 crate::ImageDimension::Cube => 2,
3917 };
3918 // Calculate the true size of the coordinate vector by adding 1 for arrayed images
3919 // and another 1 if we need to workaround 1D images by making them 2D
3920 tex_coord_size + tex_1d_hack as u8 + arrayed as u8
3921 }
3922
3923 /// Helper method to write the coordinate vector for image operations
3924 fn write_texture_coord(
3925 &mut self,
3926 ctx: &back::FunctionCtx,
3927 vector_size: u8,
3928 coordinate: Handle<crate::Expression>,
3929 array_index: Option<Handle<crate::Expression>>,
3930 // Emulate 1D images as 2D for profiles that don't support it (glsl es)
3931 tex_1d_hack: bool,
3932 ) -> Result<(), Error> {
3933 match array_index {
3934 // If the image needs an array indice we need to add it to the end of our
3935 // coordinate vector, to do so we will use the `ivec(ivec, scalar)`
3936 // constructor notation (NOTE: the inner `ivec` can also be a scalar, this
3937 // is important for 1D arrayed images).
3938 Some(layer_expr) => {
3939 write!(self.out, "ivec{vector_size}(")?;
3940 self.write_expr(coordinate, ctx)?;
3941 write!(self.out, ", ")?;
3942 // If we are replacing sampler1D with sampler2D we also need
3943 // to add another zero to the coordinates vector for the y component
3944 if tex_1d_hack {
3945 write!(self.out, "0, ")?;
3946 }
3947 self.write_expr(layer_expr, ctx)?;
3948 write!(self.out, ")")?;
3949 }
3950 // Otherwise write just the expression (and the 1D hack if needed)
3951 None => {
3952 let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) {
3953 TypeInner::Scalar(crate::Scalar {
3954 kind: crate::ScalarKind::Uint,
3955 ..
3956 }) => Some(None),
3957 TypeInner::Vector {
3958 size,
3959 scalar:
3960 crate::Scalar {
3961 kind: crate::ScalarKind::Uint,
3962 ..
3963 },
3964 } => Some(Some(size as u32)),
3965 _ => None,
3966 };
3967 if tex_1d_hack {
3968 write!(self.out, "ivec2(")?;
3969 } else if uvec_size.is_some() {
3970 match uvec_size {
3971 Some(None) => write!(self.out, "int(")?,
3972 Some(Some(size)) => write!(self.out, "ivec{size}(")?,
3973 _ => {}
3974 }
3975 }
3976 self.write_expr(coordinate, ctx)?;
3977 if tex_1d_hack {
3978 write!(self.out, ", 0)")?;
3979 } else if uvec_size.is_some() {
3980 write!(self.out, ")")?;
3981 }
3982 }
3983 }
3984
3985 Ok(())
3986 }
3987
3988 /// Helper method to write the `ImageStore` statement
3989 fn write_image_store(
3990 &mut self,
3991 ctx: &back::FunctionCtx,
3992 image: Handle<crate::Expression>,
3993 coordinate: Handle<crate::Expression>,
3994 array_index: Option<Handle<crate::Expression>>,
3995 value: Handle<crate::Expression>,
3996 ) -> Result<(), Error> {
3997 use crate::ImageDimension as IDim;
3998
3999 // NOTE: openGL requires that `imageStore`s have no effects when the texel is invalid
4000 // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20)
4001
4002 // This will only panic if the module is invalid
4003 let dim = match *ctx.resolve_type(image, &self.module.types) {
4004 TypeInner::Image { dim, .. } => dim,
4005 _ => unreachable!(),
4006 };
4007
4008 // Begin our call to `imageStore`
4009 write!(self.out, "imageStore(")?;
4010 self.write_expr(image, ctx)?;
4011 // Separate the image argument from the coordinates
4012 write!(self.out, ", ")?;
4013
4014 // openGL es doesn't have 1D images so we need workaround it
4015 let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
4016 // Write the coordinate vector
4017 self.write_texture_coord(
4018 ctx,
4019 // Get the size of the coordinate vector
4020 self.get_coordinate_vector_size(dim, array_index.is_some()),
4021 coordinate,
4022 array_index,
4023 tex_1d_hack,
4024 )?;
4025
4026 // Separate the coordinate from the value to write and write the expression
4027 // of the value to write.
4028 write!(self.out, ", ")?;
4029 self.write_expr(value, ctx)?;
4030 // End the call to `imageStore` and the statement.
4031 writeln!(self.out, ");")?;
4032
4033 Ok(())
4034 }
4035
4036 /// Helper method to write the `ImageAtomic` statement
4037 fn write_image_atomic(
4038 &mut self,
4039 ctx: &back::FunctionCtx,
4040 image: Handle<crate::Expression>,
4041 coordinate: Handle<crate::Expression>,
4042 array_index: Option<Handle<crate::Expression>>,
4043 fun: crate::AtomicFunction,
4044 value: Handle<crate::Expression>,
4045 ) -> Result<(), Error> {
4046 use crate::ImageDimension as IDim;
4047
4048 // NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid
4049 // so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20)
4050
4051 // This will only panic if the module is invalid
4052 let dim = match *ctx.resolve_type(image, &self.module.types) {
4053 TypeInner::Image { dim, .. } => dim,
4054 _ => unreachable!(),
4055 };
4056
4057 // Begin our call to `imageAtomic`
4058 let fun_str = fun.to_glsl();
4059 write!(self.out, "imageAtomic{fun_str}(")?;
4060 self.write_expr(image, ctx)?;
4061 // Separate the image argument from the coordinates
4062 write!(self.out, ", ")?;
4063
4064 // openGL es doesn't have 1D images so we need workaround it
4065 let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
4066 // Write the coordinate vector
4067 self.write_texture_coord(
4068 ctx,
4069 // Get the size of the coordinate vector
4070 self.get_coordinate_vector_size(dim, false),
4071 coordinate,
4072 array_index,
4073 tex_1d_hack,
4074 )?;
4075
4076 // Separate the coordinate from the value to write and write the expression
4077 // of the value to write.
4078 write!(self.out, ", ")?;
4079 self.write_expr(value, ctx)?;
4080 // End the call to `imageAtomic` and the statement.
4081 writeln!(self.out, ");")?;
4082
4083 Ok(())
4084 }
4085
4086 /// Helper method for writing an `ImageLoad` expression.
4087 #[allow(clippy::too_many_arguments)]
4088 fn write_image_load(
4089 &mut self,
4090 handle: Handle<crate::Expression>,
4091 ctx: &back::FunctionCtx,
4092 image: Handle<crate::Expression>,
4093 coordinate: Handle<crate::Expression>,
4094 array_index: Option<Handle<crate::Expression>>,
4095 sample: Option<Handle<crate::Expression>>,
4096 level: Option<Handle<crate::Expression>>,
4097 ) -> Result<(), Error> {
4098 use crate::ImageDimension as IDim;
4099
4100 // `ImageLoad` is a bit complicated.
4101 // There are two functions one for sampled
4102 // images another for storage images, the former uses `texelFetch` and the
4103 // latter uses `imageLoad`.
4104 //
4105 // Furthermore we have `level` which is always `Some` for sampled images
4106 // and `None` for storage images, so we end up with two functions:
4107 // - `texelFetch(image, coordinate, level)` for sampled images
4108 // - `imageLoad(image, coordinate)` for storage images
4109 //
4110 // Finally we also have to consider bounds checking, for storage images
4111 // this is easy since openGL requires that invalid texels always return
4112 // 0, for sampled images we need to either verify that all arguments are
4113 // in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`).
4114
4115 // This will only panic if the module is invalid
4116 let (dim, class) = match *ctx.resolve_type(image, &self.module.types) {
4117 TypeInner::Image {
4118 dim,
4119 arrayed: _,
4120 class,
4121 } => (dim, class),
4122 _ => unreachable!(),
4123 };
4124
4125 // Get the name of the function to be used for the load operation
4126 // and the policy to be used with it.
4127 let (fun_name, policy) = match class {
4128 // Sampled images inherit the policy from the user passed policies
4129 crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load),
4130 crate::ImageClass::Storage { .. } => {
4131 // OpenGL ES 3.1 mentions in Chapter "8.22 Texture Image Loads and Stores" that:
4132 // "Invalid image loads will return a vector where the value of R, G, and B components
4133 // is 0 and the value of the A component is undefined."
4134 //
4135 // OpenGL 4.2 Core mentions in Chapter "3.9.20 Texture Image Loads and Stores" that:
4136 // "Invalid image loads will return zero."
4137 //
4138 // So, we only inject bounds checks for ES
4139 let policy = if self.options.version.is_es() {
4140 self.policies.image_load
4141 } else {
4142 proc::BoundsCheckPolicy::Unchecked
4143 };
4144 ("imageLoad", policy)
4145 }
4146 // TODO: Is there even a function for this?
4147 crate::ImageClass::Depth { multi: _ } => {
4148 return Err(Error::Custom(
4149 "WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(),
4150 ))
4151 }
4152 crate::ImageClass::External => unimplemented!(),
4153 };
4154
4155 // openGL es doesn't have 1D images so we need workaround it
4156 let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
4157 // Get the size of the coordinate vector
4158 let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some());
4159
4160 if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy {
4161 // To write the bounds checks for `ReadZeroSkipWrite` we will use a
4162 // ternary operator since we are in the middle of an expression and
4163 // need to return a value.
4164 //
4165 // NOTE: glsl does short circuit when evaluating logical
4166 // expressions so we can be sure that after we test a
4167 // condition it will be true for the next ones
4168
4169 // Write parentheses around the ternary operator to prevent problems with
4170 // expressions emitted before or after it having more precedence
4171 write!(self.out, "(",)?;
4172
4173 // The lod check needs to precede the size check since we need
4174 // to use the lod to get the size of the image at that level.
4175 if let Some(level_expr) = level {
4176 self.write_expr(level_expr, ctx)?;
4177 write!(self.out, " < textureQueryLevels(",)?;
4178 self.write_expr(image, ctx)?;
4179 // Chain the next check
4180 write!(self.out, ") && ")?;
4181 }
4182
4183 // Check that the sample arguments doesn't exceed the number of samples
4184 if let Some(sample_expr) = sample {
4185 self.write_expr(sample_expr, ctx)?;
4186 write!(self.out, " < textureSamples(",)?;
4187 self.write_expr(image, ctx)?;
4188 // Chain the next check
4189 write!(self.out, ") && ")?;
4190 }
4191
4192 // We now need to write the size checks for the coordinates and array index
4193 // first we write the comparison function in case the image is 1D non arrayed
4194 // (and no 1D to 2D hack was needed) we are comparing scalars so the less than
4195 // operator will suffice, but otherwise we'll be comparing two vectors so we'll
4196 // need to use the `lessThan` function but it returns a vector of booleans (one
4197 // for each comparison) so we need to fold it all in one scalar boolean, since
4198 // we want all comparisons to pass we use the `all` function which will only
4199 // return `true` if all the elements of the boolean vector are also `true`.
4200 //
4201 // So we'll end with one of the following forms
4202 // - `coord < textureSize(image, lod)` for 1D images
4203 // - `all(lessThan(coord, textureSize(image, lod)))` for normal images
4204 // - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))`
4205 // for arrayed images
4206 // - `all(lessThan(coord, textureSize(image)))` for multi sampled images
4207
4208 if vector_size != 1 {
4209 write!(self.out, "all(lessThan(")?;
4210 }
4211
4212 // Write the coordinate vector
4213 self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?;
4214
4215 if vector_size != 1 {
4216 // If we used the `lessThan` function we need to separate the
4217 // coordinates from the image size.
4218 write!(self.out, ", ")?;
4219 } else {
4220 // If we didn't use it (ie. 1D images) we perform the comparison
4221 // using the less than operator.
4222 write!(self.out, " < ")?;
4223 }
4224
4225 // Call `textureSize` to get our image size
4226 write!(self.out, "textureSize(")?;
4227 self.write_expr(image, ctx)?;
4228 // `textureSize` uses the lod as a second argument for mipmapped images
4229 if let Some(level_expr) = level {
4230 // Separate the image from the lod
4231 write!(self.out, ", ")?;
4232 self.write_expr(level_expr, ctx)?;
4233 }
4234 // Close the `textureSize` call
4235 write!(self.out, ")")?;
4236
4237 if vector_size != 1 {
4238 // Close the `all` and `lessThan` calls
4239 write!(self.out, "))")?;
4240 }
4241
4242 // Finally end the condition part of the ternary operator
4243 write!(self.out, " ? ")?;
4244 }
4245
4246 // Begin the call to the function used to load the texel
4247 write!(self.out, "{fun_name}(")?;
4248 self.write_expr(image, ctx)?;
4249 write!(self.out, ", ")?;
4250
4251 // If we are using `Restrict` bounds checking we need to pass valid texel
4252 // coordinates, to do so we use the `clamp` function to get a value between
4253 // 0 and the image size - 1 (indexing begins at 0)
4254 if let proc::BoundsCheckPolicy::Restrict = policy {
4255 write!(self.out, "clamp(")?;
4256 }
4257
4258 // Write the coordinate vector
4259 self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?;
4260
4261 // If we are using `Restrict` bounds checking we need to write the rest of the
4262 // clamp we initiated before writing the coordinates.
4263 if let proc::BoundsCheckPolicy::Restrict = policy {
4264 // Write the min value 0
4265 if vector_size == 1 {
4266 write!(self.out, ", 0")?;
4267 } else {
4268 write!(self.out, ", ivec{vector_size}(0)")?;
4269 }
4270 // Start the `textureSize` call to use as the max value.
4271 write!(self.out, ", textureSize(")?;
4272 self.write_expr(image, ctx)?;
4273 // If the image is mipmapped we need to add the lod argument to the
4274 // `textureSize` call, but this needs to be the clamped lod, this should
4275 // have been generated earlier and put in a local.
4276 if class.is_mipmapped() {
4277 write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?;
4278 }
4279 // Close the `textureSize` call
4280 write!(self.out, ")")?;
4281
4282 // Subtract 1 from the `textureSize` call since the coordinates are zero based.
4283 if vector_size == 1 {
4284 write!(self.out, " - 1")?;
4285 } else {
4286 write!(self.out, " - ivec{vector_size}(1)")?;
4287 }
4288
4289 // Close the `clamp` call
4290 write!(self.out, ")")?;
4291
4292 // Add the clamped lod (if present) as the second argument to the
4293 // image load function.
4294 if level.is_some() {
4295 write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?;
4296 }
4297
4298 // If a sample argument is needed we need to clamp it between 0 and
4299 // the number of samples the image has.
4300 if let Some(sample_expr) = sample {
4301 write!(self.out, ", clamp(")?;
4302 self.write_expr(sample_expr, ctx)?;
4303 // Set the min value to 0 and start the call to `textureSamples`
4304 write!(self.out, ", 0, textureSamples(")?;
4305 self.write_expr(image, ctx)?;
4306 // Close the `textureSamples` call, subtract 1 from it since the sample
4307 // argument is zero based, and close the `clamp` call
4308 writeln!(self.out, ") - 1)")?;
4309 }
4310 } else if let Some(sample_or_level) = sample.or(level) {
4311 // GLSL only support SInt on this field while WGSL support also UInt
4312 let cast_to_int = matches!(
4313 *ctx.resolve_type(sample_or_level, &self.module.types),
4314 TypeInner::Scalar(crate::Scalar {
4315 kind: crate::ScalarKind::Uint,
4316 ..
4317 })
4318 );
4319
4320 // If no bounds checking is need just add the sample or level argument
4321 // after the coordinates
4322 write!(self.out, ", ")?;
4323
4324 if cast_to_int {
4325 write!(self.out, "int(")?;
4326 }
4327
4328 self.write_expr(sample_or_level, ctx)?;
4329
4330 if cast_to_int {
4331 write!(self.out, ")")?;
4332 }
4333 }
4334
4335 // Close the image load function.
4336 write!(self.out, ")")?;
4337
4338 // If we were using the `ReadZeroSkipWrite` policy we need to end the first branch
4339 // (which is taken if the condition is `true`) with a colon (`:`) and write the
4340 // second branch which is just a 0 value.
4341 if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy {
4342 // Get the kind of the output value.
4343 let kind = match class {
4344 // Only sampled images can reach here since storage images
4345 // don't need bounds checks and depth images aren't implemented
4346 crate::ImageClass::Sampled { kind, .. } => kind,
4347 _ => unreachable!(),
4348 };
4349
4350 // End the first branch
4351 write!(self.out, " : ")?;
4352 // Write the 0 value
4353 write!(
4354 self.out,
4355 "{}vec4(",
4356 glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix,
4357 )?;
4358 self.write_zero_init_scalar(kind)?;
4359 // Close the zero value constructor
4360 write!(self.out, ")")?;
4361 // Close the parentheses surrounding our ternary
4362 write!(self.out, ")")?;
4363 }
4364
4365 Ok(())
4366 }
4367
4368 fn write_named_expr(
4369 &mut self,
4370 handle: Handle<crate::Expression>,
4371 name: String,
4372 // The expression which is being named.
4373 // Generally, this is the same as handle, except in WorkGroupUniformLoad
4374 named: Handle<crate::Expression>,
4375 ctx: &back::FunctionCtx,
4376 ) -> BackendResult {
4377 match ctx.info[named].ty {
4378 proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner {
4379 TypeInner::Struct { .. } => {
4380 let ty_name = &self.names[&NameKey::Type(ty_handle)];
4381 write!(self.out, "{ty_name}")?;
4382 }
4383 _ => {
4384 self.write_type(ty_handle)?;
4385 }
4386 },
4387 proc::TypeResolution::Value(ref inner) => {
4388 self.write_value_type(inner)?;
4389 }
4390 }
4391
4392 let resolved = ctx.resolve_type(named, &self.module.types);
4393
4394 write!(self.out, " {name}")?;
4395 if let TypeInner::Array { base, size, .. } = *resolved {
4396 self.write_array_size(base, size)?;
4397 }
4398 write!(self.out, " = ")?;
4399 self.write_expr(handle, ctx)?;
4400 writeln!(self.out, ";")?;
4401 self.named_expressions.insert(named, name);
4402
4403 Ok(())
4404 }
4405
4406 /// Helper function that write string with default zero initialization for supported types
4407 fn write_zero_init_value(&mut self, ty: Handle<crate::Type>) -> BackendResult {
4408 let inner = &self.module.types[ty].inner;
4409 match *inner {
4410 TypeInner::Scalar(scalar) | TypeInner::Atomic(scalar) => {
4411 self.write_zero_init_scalar(scalar.kind)?;
4412 }
4413 TypeInner::Vector { scalar, .. } => {
4414 self.write_value_type(inner)?;
4415 write!(self.out, "(")?;
4416 self.write_zero_init_scalar(scalar.kind)?;
4417 write!(self.out, ")")?;
4418 }
4419 TypeInner::Matrix { .. } => {
4420 self.write_value_type(inner)?;
4421 write!(self.out, "(")?;
4422 self.write_zero_init_scalar(crate::ScalarKind::Float)?;
4423 write!(self.out, ")")?;
4424 }
4425 TypeInner::Array { base, size, .. } => {
4426 let count = match size.resolve(self.module.to_ctx())? {
4427 proc::IndexableLength::Known(count) => count,
4428 proc::IndexableLength::Dynamic => return Ok(()),
4429 };
4430 self.write_type(base)?;
4431 self.write_array_size(base, size)?;
4432 write!(self.out, "(")?;
4433 for _ in 1..count {
4434 self.write_zero_init_value(base)?;
4435 write!(self.out, ", ")?;
4436 }
4437 // write last parameter without comma and space
4438 self.write_zero_init_value(base)?;
4439 write!(self.out, ")")?;
4440 }
4441 TypeInner::Struct { ref members, .. } => {
4442 let name = &self.names[&NameKey::Type(ty)];
4443 write!(self.out, "{name}(")?;
4444 for (index, member) in members.iter().enumerate() {
4445 if index != 0 {
4446 write!(self.out, ", ")?;
4447 }
4448 self.write_zero_init_value(member.ty)?;
4449 }
4450 write!(self.out, ")")?;
4451 }
4452 _ => unreachable!(),
4453 }
4454
4455 Ok(())
4456 }
4457
4458 /// Helper function that write string with zero initialization for scalar
4459 fn write_zero_init_scalar(&mut self, kind: crate::ScalarKind) -> BackendResult {
4460 match kind {
4461 crate::ScalarKind::Bool => write!(self.out, "false")?,
4462 crate::ScalarKind::Uint => write!(self.out, "0u")?,
4463 crate::ScalarKind::Float => write!(self.out, "0.0")?,
4464 crate::ScalarKind::Sint => write!(self.out, "0")?,
4465 crate::ScalarKind::AbstractInt | crate::ScalarKind::AbstractFloat => {
4466 return Err(Error::Custom(
4467 "Abstract types should not appear in IR presented to backends".to_string(),
4468 ))
4469 }
4470 }
4471
4472 Ok(())
4473 }
4474
4475 /// Issue a control barrier.
4476 fn write_control_barrier(
4477 &mut self,
4478 flags: crate::Barrier,
4479 level: back::Level,
4480 ) -> BackendResult {
4481 self.write_memory_barrier(flags, level)?;
4482 writeln!(self.out, "{level}barrier();")?;
4483 Ok(())
4484 }
4485
4486 /// Issue a memory barrier.
4487 fn write_memory_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult {
4488 if flags.contains(crate::Barrier::STORAGE) {
4489 writeln!(self.out, "{level}memoryBarrierBuffer();")?;
4490 }
4491 if flags.contains(crate::Barrier::WORK_GROUP) {
4492 writeln!(self.out, "{level}memoryBarrierShared();")?;
4493 }
4494 if flags.contains(crate::Barrier::SUB_GROUP) {
4495 writeln!(self.out, "{level}subgroupMemoryBarrier();")?;
4496 }
4497 if flags.contains(crate::Barrier::TEXTURE) {
4498 writeln!(self.out, "{level}memoryBarrierImage();")?;
4499 }
4500 Ok(())
4501 }
4502
4503 /// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess)
4504 ///
4505 /// glsl allows adding both `readonly` and `writeonly` but this means that
4506 /// they can only be used to query information about the resource which isn't what
4507 /// we want here so when storage access is both `LOAD` and `STORE` add no modifiers
4508 fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult {
4509 if storage_access.contains(crate::StorageAccess::ATOMIC) {
4510 return Ok(());
4511 }
4512 if !storage_access.contains(crate::StorageAccess::STORE) {
4513 write!(self.out, "readonly ")?;
4514 }
4515 if !storage_access.contains(crate::StorageAccess::LOAD) {
4516 write!(self.out, "writeonly ")?;
4517 }
4518 Ok(())
4519 }
4520
4521 /// Helper method used to produce the reflection info that's returned to the user
4522 fn collect_reflection_info(&mut self) -> Result<ReflectionInfo, Error> {
4523 let info = self.info.get_entry_point(self.entry_point_idx as usize);
4524 let mut texture_mapping = crate::FastHashMap::default();
4525 let mut uniforms = crate::FastHashMap::default();
4526
4527 for sampling in info.sampling_set.iter() {
4528 let tex_name = self.reflection_names_globals[&sampling.image].clone();
4529
4530 match texture_mapping.entry(tex_name) {
4531 hash_map::Entry::Vacant(v) => {
4532 v.insert(TextureMapping {
4533 texture: sampling.image,
4534 sampler: Some(sampling.sampler),
4535 });
4536 }
4537 hash_map::Entry::Occupied(e) => {
4538 if e.get().sampler != Some(sampling.sampler) {
4539 log::error!("Conflicting samplers for {}", e.key());
4540 return Err(Error::ImageMultipleSamplers);
4541 }
4542 }
4543 }
4544 }
4545
4546 let mut immediates_info = None;
4547 for (handle, var) in self.module.global_variables.iter() {
4548 if info[handle].is_empty() {
4549 continue;
4550 }
4551 match self.module.types[var.ty].inner {
4552 TypeInner::Image { .. } => {
4553 let tex_name = self.reflection_names_globals[&handle].clone();
4554 match texture_mapping.entry(tex_name) {
4555 hash_map::Entry::Vacant(v) => {
4556 v.insert(TextureMapping {
4557 texture: handle,
4558 sampler: None,
4559 });
4560 }
4561 hash_map::Entry::Occupied(_) => {
4562 // already used with a sampler, do nothing
4563 }
4564 }
4565 }
4566 _ => match var.space {
4567 crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => {
4568 let name = self.reflection_names_globals[&handle].clone();
4569 uniforms.insert(handle, name);
4570 }
4571 crate::AddressSpace::Immediate => {
4572 let name = self.reflection_names_globals[&handle].clone();
4573 immediates_info = Some((name, var.ty));
4574 }
4575 _ => (),
4576 },
4577 }
4578 }
4579
4580 let mut immediates_segments = Vec::new();
4581 let mut immediates_items = vec![];
4582
4583 if let Some((name, ty)) = immediates_info {
4584 // We don't have a layouter available to us, so we need to create one.
4585 //
4586 // This is potentially a bit wasteful, but the set of types in the program
4587 // shouldn't be too large.
4588 let mut layouter = proc::Layouter::default();
4589 layouter.update(self.module.to_ctx()).unwrap();
4590
4591 // We start with the name of the binding itself.
4592 immediates_segments.push(name);
4593
4594 // We then recursively collect all the uniform fields of the immediate data.
4595 self.collect_immediates_items(
4596 ty,
4597 &mut immediates_segments,
4598 &layouter,
4599 &mut 0,
4600 &mut immediates_items,
4601 );
4602 }
4603
4604 Ok(ReflectionInfo {
4605 texture_mapping,
4606 uniforms,
4607 varying: mem::take(&mut self.varying),
4608 immediates_items,
4609 clip_distance_count: self.clip_distance_count,
4610 })
4611 }
4612
4613 fn collect_immediates_items(
4614 &mut self,
4615 ty: Handle<crate::Type>,
4616 segments: &mut Vec<String>,
4617 layouter: &proc::Layouter,
4618 offset: &mut u32,
4619 items: &mut Vec<ImmediateItem>,
4620 ) {
4621 // At this point in the recursion, `segments` contains the path
4622 // needed to access `ty` from the root.
4623
4624 let layout = &layouter[ty];
4625 *offset = layout.alignment.round_up(*offset);
4626 match self.module.types[ty].inner {
4627 // All these types map directly to GL uniforms.
4628 TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => {
4629 // Build the full name, by combining all current segments.
4630 let name: String = segments.iter().map(String::as_str).collect();
4631 items.push(ImmediateItem {
4632 access_path: name,
4633 offset: *offset,
4634 ty: (&self.module.types[ty].inner).try_into().unwrap(),
4635 size_bytes: layout.size,
4636 });
4637 *offset += layout.size;
4638 }
4639 // Arrays are recursed into.
4640 TypeInner::Array { base, size, .. } => {
4641 let crate::ArraySize::Constant(count) = size else {
4642 unreachable!("Cannot have dynamic arrays in immediates");
4643 };
4644
4645 for i in 0..count.get() {
4646 // Add the array accessor and recurse.
4647 segments.push(format!("[{i}]"));
4648 self.collect_immediates_items(base, segments, layouter, offset, items);
4649 segments.pop();
4650 }
4651
4652 // Ensure the stride is kept by rounding up to the alignment.
4653 *offset = layout.alignment.round_up(*offset)
4654 }
4655 TypeInner::Struct { ref members, .. } => {
4656 for (index, member) in members.iter().enumerate() {
4657 // Add struct accessor and recurse.
4658 segments.push(format!(
4659 ".{}",
4660 self.names[&NameKey::StructMember(ty, index as u32)]
4661 ));
4662 self.collect_immediates_items(member.ty, segments, layouter, offset, items);
4663 segments.pop();
4664 }
4665
4666 // Ensure ending padding is kept by rounding up to the alignment.
4667 *offset = layout.alignment.round_up(*offset)
4668 }
4669 _ => unreachable!(),
4670 }
4671 }
4672}