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