/// Writer responsible for all code generation. pubstruct Writer<'a, W> { // Inputs /// The module being written. pub(incrate::back::glsl) module: &'a crate::Module, /// The module analysis. pub(incrate::back::glsl) info: &'a valid::ModuleInfo, /// The output writer.
out: W, /// User defined configuration to be used. pub(incrate::back::glsl) options: &'a Options, /// The bound checking policies to be used pub(incrate::back::glsl) policies: proc::BoundsCheckPolicies,
// Internal State /// Features manager used to store all the needed features and write them. pub(incrate::back::glsl) features: FeaturesManager,
namer: proc::Namer, /// A map with all the names needed for writing the module /// (generated by a [`Namer`](crate::proc::Namer)).
names: crate::FastHashMap<NameKey, String>, /// A map with the names of global variables needed for reflections.
reflection_names_globals: crate::FastHashMap<Handle<crate::GlobalVariable>, String>, /// The selected entry point. pub(incrate::back::glsl) entry_point: &'a crate::EntryPoint, /// The index of the selected entry point. pub(incrate::back::glsl) entry_point_idx: proc::EntryPointIndex, /// A generator for unique block numbers.
block_id: IdGenerator, /// Set of expressions that have associated temporary variables.
named_expressions: crate::NamedExpressions, /// Set of expressions that need to be baked to avoid unnecessary repetition in output
need_bake_expressions: back::NeedBakeExpressions, /// Information about nesting of loops and switches. /// /// Used for forwarding continue statements in switches that have been /// transformed to `do {} while(false);` loops.
continue_ctx: back::continue_forward::ContinueCtx, /// How many views to render to, if doing multiview rendering. pub(incrate::back::glsl) multiview: Option<core::num::NonZeroU32>, /// Mapping of varying variables to their location. Needed for reflections.
varying: crate::FastHashMap<String, VaryingLocation>, /// Number of user-defined clip planes. Only non-zero for vertex shaders.
clip_distance_count: u32,
}
impl<'a, W: Write> Writer<'a, W> { /// Creates a new [`Writer`] instance. /// /// # Errors /// - If the version specified is invalid or supported. /// - If the entry point couldn't be found in the module. /// - If the version specified doesn't support some used features. pubfn new(
out: W,
module: &'a crate::Module,
info: &'a valid::ModuleInfo,
options: &'a Options,
pipeline_options: &'a PipelineOptions,
policies: proc::BoundsCheckPolicies,
) -> Result<Self, Error> { // Check if the requested version is supported if !options.version.is_supported() {
log::error!("Version {}", options.version); return Err(Error::VersionNotSupported);
}
// Try to find the entry point and corresponding index let ep_idx = module
.entry_points
.iter()
.position(|ep| {
pipeline_options.shader_stage == ep.stage && pipeline_options.entry_point == ep.name
})
.ok_or(Error::EntryPointNotFound)?;
// Generate a map with names required to write the module letmut names = crate::FastHashMap::default(); letmut namer = proc::Namer::default();
namer.reset(
module,
&keywords::RESERVED_KEYWORD_SET,
proc::KeywordSet::empty(),
proc::CaseInsensitiveKeywordSet::empty(),
&[ "gl_", // all GL built-in variables "_group", // all normal bindings "_immediates_binding_", // all immediate data bindings
],
&mut names,
);
// Build the instance letmut this = Self {
module,
info,
out,
options,
policies,
// Find all features required to print this module
this.collect_required_features()?;
Ok(this)
}
/// Writes the [`Module`](crate::Module) as glsl to the output /// /// # Notes /// If an error occurs while writing, the output might have been written partially /// /// # Panics /// Might panic if the module is invalid pubfn write(&mutself) -> Result<ReflectionInfo, Error> { // We use `writeln!(self.out)` throughout the write to add newlines // to make the output more readable
let es = self.options.version.is_es();
// Write the version (It must be the first thing or it isn't a valid glsl output)
writeln!(self.out, "#version {}", self.options.version)?; // Write all the needed extensions // // This used to be the last thing being written as it allowed to search for features while // writing the module saving some loops but some older versions (420 or less) required the // extensions to appear before being used, even though extensions are part of the // preprocessor not the processor ¯\_(ツ)_/¯ self.features.write(self.options, &mutself.out)?;
// glsl es requires a precision to be specified for floats and ints // TODO: Should this be user configurable? if es {
writeln!(self.out)?;
writeln!(self.out, "precision highp float;")?;
writeln!(self.out, "precision highp int;")?;
writeln!(self.out)?;
}
// Enable early depth tests if needed iflet Some(early_depth_test) = self.entry_point.early_depth_test { // If early depth test is supported for this version of GLSL ifself.options.version.supports_early_depth_test() { match early_depth_test { crate::EarlyDepthTest::Force => {
writeln!(self.out, "layout(early_fragment_tests) in;")?;
} crate::EarlyDepthTest::Allow { conservative, .. } => { usecrate::ConservativeDepth as Cd; let depth = match conservative {
Cd::GreaterEqual => "greater",
Cd::LessEqual => "less",
Cd::Unchanged => "unchanged",
};
writeln!(self.out, "layout (depth_{depth}) out float gl_FragDepth;")?;
}
}
} else {
log::warn!( "Early depth testing is not supported for this version of GLSL: {}", self.options.version
);
}
}
// Write struct types. // // This are always ordered because the IR is structured in a way that // you can't make a struct without adding all of its members first. for (handle, ty) inself.module.types.iter() { iflet TypeInner::Struct { ref members, .. } = ty.inner { let struct_name = &self.names[&NameKey::Type(handle)];
// Structures ending with runtime-sized arrays can only be // rendered as shader storage blocks in GLSL, not stand-alone // struct types. if !self.module.types[members.last().unwrap().ty]
.inner
.is_dynamically_sized(&self.module.types)
{
write!(self.out, "struct {struct_name} ")?; self.write_struct_body(handle, members)?;
writeln!(self.out, ";")?;
}
}
}
// Write functions for special types. for (type_key, struct_ty) inself.module.special_types.predeclared_types.iter() { match type_key {
&crate::PredeclaredType::ModfResult { size, scalar }
| &crate::PredeclaredType::FrexpResult { size, scalar } => { let struct_name = &self.names[&NameKey::Type(*struct_ty)]; let arg_type_name_owner; let arg_type_name = iflet Some(size) = size {
arg_type_name_owner = format!( "{}vec{}", if scalar.width == 8 { "d" } else { "" },
size as u8
);
&arg_type_name_owner
} elseif scalar.width == 8 { "double"
} else { "float"
};
let other_type_name_owner; let (defined_func_name, called_func_name, other_type_name) = if matches!(type_key, &crate::PredeclaredType::ModfResult { .. }) {
(MODF_FUNCTION, "modf", arg_type_name)
} else { let other_type_name = iflet Some(size) = size {
other_type_name_owner = format!("ivec{}", size as u8);
&other_type_name_owner
} else { "int"
};
(FREXP_FUNCTION, "frexp", other_type_name)
};
// Write all named constants letmut constants = self
.module
.constants
.iter()
.filter(|&(_, c)| c.name.is_some())
.peekable(); whilelet Some((handle, _)) = constants.next() { self.write_global_constant(handle)?; // Add extra newline for readability on last iteration if constants.peek().is_none() {
writeln!(self.out)?;
}
}
let ep_info = self.info.get_entry_point(self.entry_point_idx as usize);
// Write the globals // // Unless explicitly disabled with WriterFlags::INCLUDE_UNUSED_ITEMS, // we filter all globals that aren't used by the selected entry point as they might be // interfere with each other (i.e. two globals with the same location but different with // different classes) let include_unused = self
.options
.writer_flags
.contains(WriterFlags::INCLUDE_UNUSED_ITEMS); for (handle, global) inself.module.global_variables.iter() { let is_unused = ep_info[handle].is_empty(); if !include_unused && is_unused { continue;
}
matchself.module.types[global.ty].inner { // We treat images separately because they might require // writing the storage format
TypeInner::Image { mut dim,
arrayed,
class,
} => { // Gather the storage format if needed let storage_format_access = matchself.module.types[global.ty].inner {
TypeInner::Image {
class: crate::ImageClass::Storage { format, access },
..
} => Some((format, access)),
_ => None,
};
if dim == crate::ImageDimension::D1 && es {
dim = crate::ImageDimension::D2
}
// Gether the location if needed let layout_binding = ifself.options.version.supports_explicit_locations() { let br = global.binding.as_ref().unwrap(); self.options.binding_map.get(br).cloned()
} else {
None
};
// Write all the layout qualifiers if layout_binding.is_some() || storage_format_access.is_some() {
write!(self.out, "layout(")?; iflet Some(binding) = layout_binding {
write!(self.out, "binding = {binding}")?;
} iflet Some((format, _)) = storage_format_access { let format_str = glsl_storage_format(format)?; let separator = match layout_binding {
Some(_) => ",",
None => "",
};
write!(self.out, "{separator}{format_str}")?;
}
write!(self.out, ") ")?;
}
// All images in glsl are `uniform` // The trailing space is important
write!(self.out, "uniform ")?;
// write the type // // This is way we need the leading space because `write_image_type` doesn't add // any spaces at the beginning or end self.write_image_type(dim, arrayed, class)?;
// Finally write the name and end the global with a `;` // The leading space is important let global_name = self.get_global_name(handle, global);
writeln!(self.out, " {global_name};")?;
writeln!(self.out)?;
self.reflection_names_globals.insert(handle, global_name);
} // glsl has no concept of samplers so we just ignore it
TypeInner::Sampler { .. } => continue, // All other globals are written by `write_global`
_ => { self.write_global(handle, global)?; // Add a newline (only for readability)
writeln!(self.out)?;
}
}
}
// Write all regular functions for (handle, function) inself.module.functions.iter() { // Check that the function doesn't use globals that aren't supported // by the current entry point if !include_unused && !ep_info.dominates_global_use(&self.info[handle]) { continue;
}
let fun_info = &self.info[handle];
// Skip functions that that are not compatible with this entry point's stage. // // When validation is enabled, it rejects modules whose entry points try to call // incompatible functions, so if we got this far, then any functions incompatible // with our selected entry point must not be used. // // When validation is disabled, `fun_info.available_stages` is always just // `ShaderStages::all()`, so this will write all functions in the module, and // the downstream GLSL compiler will catch any problems. if !fun_info.available_stages.contains(ep_info.available_stages) { continue;
}
// Write the function self.write_function(back::FunctionType::Function(handle), function, fun_info)?;
/// Helper method used to write value types /// /// # Notes /// Adds no trailing or leading whitespace fn write_value_type(&mutself, inner: &TypeInner) -> BackendResult { match *inner { // Scalars are simple we just get the full name from `glsl_scalar`
TypeInner::Scalar(scalar)
| TypeInner::Atomic(scalar)
| TypeInner::ValuePointer {
size: None,
scalar,
space: _,
} => write!(self.out, "{}", glsl_scalar(scalar)?.full)?, // Vectors are just `gvecN` where `g` is the scalar prefix and `N` is the vector size
TypeInner::Vector { size, scalar }
| TypeInner::ValuePointer {
size: Some(size),
scalar,
space: _,
} => write!(self.out, "{}vec{}", glsl_scalar(scalar)?.prefix, size as u8)?, // Matrices are written with `gmatMxN` where `g` is the scalar prefix (only floats and // doubles are allowed), `M` is the columns count and `N` is the rows count // // glsl supports a matrix shorthand `gmatN` where `N` = `M` but it doesn't justify the // extra branch to write matrices this way
TypeInner::Matrix {
columns,
rows,
scalar,
} => write!( self.out, "{}mat{}x{}",
glsl_scalar(scalar)?.prefix,
columns as u8,
rows as u8
)?, // GLSL arrays are written as `type name[size]` // Here we only write the size of the array i.e. `[size]` // Base `type` and `name` should be written outside
TypeInner::Array { base, size, .. } => self.write_array_size(base, size)?, // Write all variants instead of `_` so that if new variants are added a // no exhaustiveness error is thrown
TypeInner::Pointer { .. }
| TypeInner::Struct { .. }
| TypeInner::Image { .. }
| TypeInner::Sampler { .. }
| TypeInner::AccelerationStructure { .. }
| TypeInner::RayQuery { .. }
| TypeInner::BindingArray { .. }
| TypeInner::CooperativeMatrix { .. } => { return Err(Error::Custom(format!("Unable to write type {inner:?}")))
}
}
Ok(())
}
/// Helper method used to write non image/sampler types /// /// # Notes /// Adds no trailing or leading whitespace fn write_type(&mutself, ty: Handle<crate::Type>) -> BackendResult { matchself.module.types[ty].inner { // glsl has no pointer types so just write types as normal and loads are skipped
TypeInner::Pointer { base, .. } => self.write_type(base), // glsl structs are written as just the struct name
TypeInner::Struct { .. } => { // Get the struct name let name = &self.names[&NameKey::Type(ty)];
write!(self.out, "{name}")?;
Ok(())
} // glsl array has the size separated from the base type
TypeInner::Array { base, .. } => self.write_type(base), ref other => self.write_value_type(other),
}
}
/// Helper method to write a image type /// /// # Notes /// Adds no leading or trailing whitespace fn write_image_type(
&mutself,
dim: crate::ImageDimension,
arrayed: bool,
class: crate::ImageClass,
) -> BackendResult { // glsl images consist of four parts the scalar prefix, the image "type", the dimensions // and modifiers // // There exists two image types // - sampler - for sampled images // - image - for storage images // // There are three possible modifiers that can be used together and must be written in // this order to be valid // - MS - used if it's a multisampled image // - Array - used if it's an image array // - Shadow - used if it's a depth image usecrate::ImageClass as Ic; usecrate::Scalar as S; let float = S {
kind: crate::ScalarKind::Float,
width: 4,
}; let (base, scalar, ms, comparison) = match class {
Ic::Sampled { kind, multi: true } => ("sampler", S { kind, width: 4 }, "MS", ""),
Ic::Sampled { kind, multi: false } => ("sampler", S { kind, width: 4 }, "", ""),
Ic::Depth { multi: true } => ("sampler", float, "MS", ""),
Ic::Depth { multi: false } => ("sampler", float, "", "Shadow"),
Ic::Storage { format, .. } => ("image", format.into(), "", ""),
Ic::External => unimplemented!(),
};
/// Helper method used by [Self::write_global] to write just the layout part of /// a non image/sampler global variable, if applicable. /// /// # Notes /// /// Adds trailing whitespace if any layout qualifier is written fn write_global_layout(&mutself, global: &crate::GlobalVariable) -> BackendResult { // Determine which (if any) explicit memory layout to use, and whether we support it let layout = match global.space { crate::AddressSpace::Uniform => { if !self.options.version.supports_std140_layout() { return Err(Error::Custom( "Uniform address space requires std140 layout support".to_string(),
));
}
// If our version supports explicit layouts, we can also output the explicit binding // if we have it ifself.options.version.supports_explicit_locations() { iflet Some(ref br) = global.binding { matchself.options.binding_map.get(br) {
Some(binding) => {
write!(self.out, "layout(")?;
// Either no explicit bindings are supported or we didn't have any. // Write just the memory layout. iflet Some(layout) = layout {
write!(self.out, "layout({layout}) ")?;
}
Ok(())
}
/// Helper method used to write non images/sampler globals /// /// # Notes /// Adds a newline /// /// # Panics /// If the global has type sampler fn write_global(
&mutself,
handle: Handle<crate::GlobalVariable>,
global: &crate::GlobalVariable,
) -> BackendResult { self.write_global_layout(global)?;
ifletcrate::AddressSpace::Storage { access } = global.space { self.write_storage_access(access)?; if global
.memory_decorations
.contains(crate::MemoryDecorations::COHERENT)
{
write!(self.out, "coherent ")?;
} if global
.memory_decorations
.contains(crate::MemoryDecorations::VOLATILE)
{
write!(self.out, "volatile ")?;
}
}
/// Write an interface block for a single Naga global. /// /// Write `block_name { members }`. Since `block_name` must be unique /// between blocks and structs, we add `_block_ID` where `ID` is a /// `IdGenerator` generated number. Write `members` in the same way we write /// a struct's members. fn write_interface_block(
&mutself,
handle: Handle<crate::GlobalVariable>,
global: &crate::GlobalVariable,
) -> BackendResult { // Write the block name, it's just the struct name appended with `_block_ID` let ty_name = &self.names[&NameKey::Type(global.ty)]; let block_name = format!( "{}_block_{}{:?}", // avoid double underscores as they are reserved in GLSL
ty_name.trim_end_matches('_'), self.block_id.generate(), self.entry_point.stage,
);
write!(self.out, "{block_name} ")?; self.reflection_names_globals.insert(handle, block_name);
matchself.module.types[global.ty].inner {
TypeInner::Struct { ref members, .. } ifself.module.types[members.last().unwrap().ty]
.inner
.is_dynamically_sized(&self.module.types) =>
{ // Structs with dynamically sized arrays must have their // members lifted up as members of the interface block. GLSL // can't write such struct types anyway. self.write_struct_body(global.ty, members)?;
write!(self.out, " ")?; self.write_global_name(handle, global)?;
}
_ => { // A global of any other type is written as the sole member // of the interface block. Since the interface block is // anonymous, this becomes visible in the global scope.
write!(self.out, "{{ ")?; self.write_type(global.ty)?;
write!(self.out, " ")?; self.write_global_name(handle, global)?; iflet TypeInner::Array { base, size, .. } = self.module.types[global.ty].inner { self.write_array_size(base, size)?;
}
write!(self.out, "; }}")?;
}
}
writeln!(self.out, ";")?;
Ok(())
}
/// Helper method used to find which expressions of a given function require baking /// /// # Notes /// Clears `need_bake_expressions` set before adding to it fn update_expressions_to_bake(&mutself, func: &crate::Function, info: &valid::FunctionInfo) { usecrate::Expression; self.need_bake_expressions.clear(); for (fun_handle, expr) in func.expressions.iter() { let expr_info = &info[fun_handle]; let min_ref_count = func.expressions[fun_handle].bake_ref_count(); if min_ref_count <= expr_info.ref_count { self.need_bake_expressions.insert(fun_handle);
}
let inner = expr_info.ty.inner_with(&self.module.types);
iflet Expression::Math {
fun,
arg,
arg1,
arg2,
..
} = *expr
{ match fun { crate::MathFunction::Dot => { // if the expression is a Dot product with integer arguments, // then the args needs baking as well iflet TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Sint | crate::ScalarKind::Uint,
..
}) = *inner
{ self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap());
}
} crate::MathFunction::Dot4U8Packed | crate::MathFunction::Dot4I8Packed => { self.need_bake_expressions.insert(arg); self.need_bake_expressions.insert(arg1.unwrap());
} crate::MathFunction::Pack4xI8
| crate::MathFunction::Pack4xU8
| crate::MathFunction::Pack4xI8Clamp
| crate::MathFunction::Pack4xU8Clamp
| crate::MathFunction::Unpack4xI8
| crate::MathFunction::Unpack4xU8
| crate::MathFunction::QuantizeToF16 => { self.need_bake_expressions.insert(arg);
} /* crate::MathFunction::Pack4x8unorm | */ crate::MathFunction::Unpack4x8snorm if !self.options.version.supports_pack_unpack_4x8() =>
{ // We have a fallback if the platform doesn't natively support these self.need_bake_expressions.insert(arg);
} /* crate::MathFunction::Pack4x8unorm | */ crate::MathFunction::Unpack4x8unorm if !self.options.version.supports_pack_unpack_4x8() =>
{ self.need_bake_expressions.insert(arg);
} /* crate::MathFunction::Pack2x16snorm | */ crate::MathFunction::Unpack2x16snorm if !self.options.version.supports_pack_unpack_snorm_2x16() =>
{ self.need_bake_expressions.insert(arg);
} /* crate::MathFunction::Pack2x16unorm | */ crate::MathFunction::Unpack2x16unorm if !self.options.version.supports_pack_unpack_unorm_2x16() =>
{ self.need_bake_expressions.insert(arg);
} crate::MathFunction::ExtractBits => { // Only argument 1 is re-used. self.need_bake_expressions.insert(arg1.unwrap());
} crate::MathFunction::InsertBits => { // Only argument 2 is re-used. self.need_bake_expressions.insert(arg2.unwrap());
} crate::MathFunction::CountLeadingZeros => { iflet Some(crate::ScalarKind::Sint) = inner.scalar_kind() { self.need_bake_expressions.insert(arg);
}
}
_ => {}
}
}
}
for statement in func.body.iter() { match *statement { crate::Statement::Atomic {
fun: crate::AtomicFunction::Exchange { compare: Some(cmp) },
..
} => { self.need_bake_expressions.insert(cmp);
}
_ => {}
}
}
}
/// Helper method used to get a name for a global /// /// Globals have different naming schemes depending on their binding: /// - Globals without bindings use the name from the [`Namer`](crate::proc::Namer) /// - Globals with resource binding are named `_group_X_binding_Y` where `X` /// is the group and `Y` is the binding fn get_global_name(
&self,
handle: Handle<crate::GlobalVariable>,
global: &crate::GlobalVariable,
) -> String { match (&global.binding, global.space) {
(&Some(ref br), _) => {
format!( "_group_{}_binding_{}_{}",
br.group,
br.binding, self.entry_point.stage.to_str()
)
}
(&None, crate::AddressSpace::Immediate) => {
format!("_immediates_binding_{}", self.entry_point.stage.to_str())
}
(&None, _) => self.names[&NameKey::GlobalVariable(handle)].clone(),
}
}
/// Helper method used to write a name for a global without additional heap allocation fn write_global_name(
&mutself,
handle: Handle<crate::GlobalVariable>,
global: &crate::GlobalVariable,
) -> BackendResult { match (&global.binding, global.space) {
(&Some(ref br), _) => write!( self.out, "_group_{}_binding_{}_{}",
br.group,
br.binding, self.entry_point.stage.to_str()
)?,
(&None, crate::AddressSpace::Immediate) => write!( self.out, "_immediates_binding_{}", self.entry_point.stage.to_str()
)?,
(&None, _) => write!( self.out, "{}",
&self.names[&NameKey::GlobalVariable(handle)]
)?,
}
Ok(())
}
/// Write a GLSL global that will carry a Naga entry point's argument or return value. /// /// A Naga entry point's arguments and return value are rendered in GLSL as /// variables at global scope with the `in` and `out` storage qualifiers. /// The code we generate for `main` loads from all the `in` globals into /// appropriately named locals. Before it returns, `main` assigns the /// components of its return value into all the `out` globals. /// /// This function writes a declaration for one such GLSL global, /// representing a value passed into or returned from [`self.entry_point`] /// that has a [`Location`] binding. The global's name is generated based on /// the location index and the shader stages being connected; see /// [`VaryingName`]. This means we don't need to know the names of /// arguments, just their types and bindings. /// /// Emit nothing for entry point arguments or return values with [`BuiltIn`] /// bindings; `main` will read from or assign to the appropriate GLSL /// special variable; these are pre-declared. As an exception, we do declare /// `gl_Position` or `gl_FragCoord` with the `invariant` qualifier if /// needed. /// /// Use `output` together with [`self.entry_point.stage`] to determine which /// shader stages are being connected, and choose the `in` or `out` storage /// qualifier. /// /// [`self.entry_point`]: Writer::entry_point /// [`self.entry_point.stage`]: crate::EntryPoint::stage /// [`Location`]: crate::Binding::Location /// [`BuiltIn`]: crate::Binding::BuiltIn fn write_varying(
&mutself,
binding: Option<&crate::Binding>,
ty: Handle<crate::Type>,
output: bool,
) -> Result<(), Error> { // For a struct, emit a separate global for each member with a binding. iflet TypeInner::Struct { ref members, .. } = self.module.types[ty].inner { for member in members { self.write_varying(member.binding.as_ref(), member.ty, output)?;
} return Ok(());
}
let binding = match binding {
None => return Ok(()),
Some(binding) => binding,
};
let (location, interpolation, sampling, blend_src) = match *binding { crate::Binding::Location {
location,
interpolation,
sampling,
blend_src,
per_primitive: _,
} => (location, interpolation, sampling, blend_src), crate::Binding::BuiltIn(built_in) => { match built_in { crate::BuiltIn::Position { invariant: true } => { match (self.options.version, self.entry_point.stage) {
(
Version::Embedded {
version: 300,
is_webgl: true,
},
ShaderStage::Fragment,
) => { // `invariant gl_FragCoord` is not allowed in WebGL2 and possibly // OpenGL ES in general (waiting on confirmation). // // See https://github.com/KhronosGroup/WebGL/issues/3518
}
_ => {
writeln!( self.out, "invariant {};",
glsl_built_in(
built_in,
VaryingOptions::from_writer_options(self.options, output)
)
)?;
}
}
} crate::BuiltIn::ClipDistances => { // Re-declare `gl_ClipDistance` with number of clip planes. let TypeInner::Array { size, .. } = self.module.types[ty].inner else {
unreachable!();
}; let proc::IndexableLength::Known(size) =
size.resolve(self.module.to_ctx())? else {
unreachable!();
}; self.clip_distance_count = size;
writeln!(self.out, "out float gl_ClipDistance[{size}];")?;
}
_ => {}
} return Ok(());
}
};
// Write the interpolation modifier if needed // // We ignore all interpolation and auxiliary modifiers that aren't used in fragment // shaders' input globals or vertex shaders' output globals. let emit_interpolation_and_auxiliary = matchself.entry_point.stage {
ShaderStage::Vertex => output,
ShaderStage::Fragment => !output,
ShaderStage::Compute => false,
ShaderStage::Task
| ShaderStage::Mesh
| ShaderStage::RayGeneration
| ShaderStage::AnyHit
| ShaderStage::ClosestHit
| ShaderStage::Miss => unreachable!(),
};
// Write the interpolation qualifier. iflet Some(interp) = interpolation { if emit_interpolation_and_auxiliary {
write!(self.out, "{} ", glsl_interpolation(interp))?;
}
}
// Write the sampling auxiliary qualifier. // // Before GLSL 4.2, the `centroid` and `sample` qualifiers were required to appear // immediately before the `in` / `out` qualifier, so we'll just follow that rule // here, regardless of the version. iflet Some(sampling) = sampling { if emit_interpolation_and_auxiliary { iflet Some(qualifier) = glsl_sampling(sampling)? {
write!(self.out, "{qualifier} ")?;
}
}
}
// Write the input/output qualifier.
write!(self.out, "{} ", if output { "out" } else { "in" })?;
// Write the type // `write_type` adds no leading or trailing spaces self.write_type(ty)?;
// Finally write the global name and end the global with a `;` and a newline // Leading space is important let vname = VaryingName {
binding: &crate::Binding::Location {
location,
interpolation: None,
sampling: None,
blend_src,
per_primitive: false,
},
stage: self.entry_point.stage,
options: VaryingOptions::from_writer_options(self.options, output),
};
writeln!(self.out, " {vname};")?;
/// Helper method used to write functions (both entry points and regular functions) /// /// # Notes /// Adds a newline fn write_function(
&mutself,
ty: back::FunctionType,
func: &crate::Function,
info: &valid::FunctionInfo,
) -> BackendResult { // Create a function context for the function being written let ctx = back::FunctionCtx {
ty,
info,
expressions: &func.expressions,
named_expressions: &func.named_expressions,
};
// Write the function header // // glsl headers are the same as in c: // `ret_type name(args)` // `ret_type` is the return type // `name` is the function name // `args` is a comma separated list of `type name` // | - `type` is the argument type // | - `name` is the argument name
// Start by writing the return type if any otherwise write void // This is the only place where `void` is a valid type // (though it's more a keyword than a type) iflet back::FunctionType::EntryPoint(_) = ctx.ty {
write!(self.out, "void")?;
} elseiflet Some(ref result) = func.result { self.write_type(result.ty)?; iflet TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner { self.write_array_size(base, size)?
}
} else {
write!(self.out, "void")?;
}
// Write the function name and open parentheses for the argument list let function_name = match ctx.ty {
back::FunctionType::Function(handle) => &self.names[&NameKey::Function(handle)],
back::FunctionType::EntryPoint(_) => "main",
};
write!(self.out, " {function_name}(")?;
// Write the comma separated argument list // // We need access to `Self` here so we use the reference passed to the closure as an // argument instead of capturing as that would cause a borrow checker error let arguments = match ctx.ty {
back::FunctionType::EntryPoint(_) => &[][..],
back::FunctionType::Function(_) => &func.arguments,
}; let arguments: Vec<_> = arguments
.iter()
.enumerate()
.filter(|&(_, arg)| matchself.module.types[arg.ty].inner {
TypeInner::Sampler { .. } => false,
_ => true,
})
.collect(); self.write_slice(&arguments, |this, _, &(i, arg)| { // Write the argument type match this.module.types[arg.ty].inner { // We treat images separately because they might require // writing the storage format
TypeInner::Image {
dim,
arrayed,
class,
} => { // Write the storage format if needed iflet TypeInner::Image {
class: crate::ImageClass::Storage { format, .. },
..
} = this.module.types[arg.ty].inner
{
write!(this.out, "layout({}) ", glsl_storage_format(format)?)?;
}
// write the type // // This is way we need the leading space because `write_image_type` doesn't add // any spaces at the beginning or end
this.write_image_type(dim, arrayed, class)?;
}
TypeInner::Pointer { base, .. } => { // write parameter qualifiers
write!(this.out, "inout ")?;
this.write_type(base)?;
} // All other types are written by `write_type`
_ => {
this.write_type(arg.ty)?;
}
}
// Write the argument name // The leading space is important
write!(this.out, " {}", &this.names[&ctx.argument_key(i as u32)])?;
// Compose the function arguments from globals, in case of an entry point. iflet back::FunctionType::EntryPoint(ep_index) = ctx.ty { let stage = self.module.entry_points[ep_index as usize].stage; for (index, arg) in func.arguments.iter().enumerate() {
write!(self.out, "{}", back::INDENT)?; self.write_type(arg.ty)?; let name = &self.names[&NameKey::EntryPointArgument(ep_index, index as u32)];
write!(self.out, " {name}")?;
write!(self.out, " = ")?; matchself.module.types[arg.ty].inner {
TypeInner::Struct { ref members, .. } => { self.write_type(arg.ty)?;
write!(self.out, "(")?; for (index, member) in members.iter().enumerate() { let varying_name = VaryingName {
binding: member.binding.as_ref().unwrap(),
stage,
options: VaryingOptions::from_writer_options(self.options, false),
}; if index != 0 {
write!(self.out, ", ")?;
}
write!(self.out, "{varying_name}")?;
}
writeln!(self.out, ");")?;
}
_ => { let varying_name = VaryingName {
binding: arg.binding.as_ref().unwrap(),
stage,
options: VaryingOptions::from_writer_options(self.options, false),
};
writeln!(self.out, "{varying_name};")?;
}
}
}
}
// Write all function locals // Locals are `type name (= init)?;` where the init part (including the =) are optional // // Always adds a newline for (handle, local) in func.local_variables.iter() { // Write indentation (only for readability) and the type // `write_type` adds no trailing space
write!(self.out, "{}", back::INDENT)?; self.write_type(local.ty)?;
// Write the local name // The leading space is important
write!(self.out, " {}", self.names[&ctx.name_key(handle)])?; // Write size for array type iflet TypeInner::Array { base, size, .. } = self.module.types[local.ty].inner { self.write_array_size(base, size)?;
} // Write the local initializer if needed iflet Some(init) = local.init { // Put the equal signal only if there's a initializer // The leading and trailing spaces aren't needed but help with readability
write!(self.out, " = ")?;
// Write the constant // `write_constant` adds no trailing or leading space/newline self.write_expr(init, &ctx)?;
} elseif is_value_init_supported(self.module, local.ty) {
write!(self.out, " = ")?; self.write_zero_init_value(local.ty)?;
}
// Finish the local with `;` and add a newline (only for readability)
writeln!(self.out, ";")?
}
// Write the function body (statement list) for sta in func.body.iter() { // Write a statement, the indentation should always be 1 when writing the function body // `write_stmt` adds a newline self.write_stmt(sta, &ctx, back::Level(1))?;
}
// Close braces and add a newline
writeln!(self.out, "}}")?;
for (handle, var) in vars { let name = &self.names[&NameKey::GlobalVariable(handle)];
write!(self.out, "{}{} = ", level.next(), name)?; self.write_zero_init_value(var.ty)?;
writeln!(self.out, ";")?;
}
/// Write a list of comma separated `T` values using a writer function `F`. /// /// The writer function `F` receives a mutable reference to `self` that if needed won't cause /// borrow checker issues (using for example a closure with `self` will cause issues), the /// second argument is the 0 based index of the element on the list, and the last element is /// a reference to the element `T` being written /// /// # Notes /// - Adds no newlines or leading/trailing whitespace /// - The last element won't have a trailing `,` fn write_slice<T, F: FnMut(&mutSelf, u32, &T) -> BackendResult>(
&mutself,
data: &[T], mut f: F,
) -> BackendResult { // Loop through `data` invoking `f` for each element for (index, item) in data.iter().enumerate() { if index != 0 {
write!(self.out, ", ")?;
}
f(self, index as u32, item)?;
}
Ok(())
}
/// Helper method used to write global constants fn write_global_constant(&mutself, handle: Handle<crate::Constant>) -> BackendResult {
write!(self.out, "const ")?; let constant = &self.module.constants[handle]; self.write_type(constant.ty)?; let name = &self.names[&NameKey::Constant(handle)];
write!(self.out, " {name}")?; iflet TypeInner::Array { base, size, .. } = self.module.types[constant.ty].inner { self.write_array_size(base, size)?;
}
write!(self.out, " = ")?; self.write_const_expr(constant.init, &self.module.global_expressions)?;
writeln!(self.out, ";")?;
Ok(())
}
/// Helper method used to output a dot product as an arithmetic expression /// fn write_dot_product(
&mutself,
arg: Handle<crate::Expression>,
arg1: Handle<crate::Expression>,
size: usize,
ctx: &back::FunctionCtx,
) -> BackendResult { // Write parentheses around the dot product expression to prevent operators // with different precedences from applying earlier.
write!(self.out, "(")?;
// Cycle through all the components of the vector for index in0..size { let component = back::COMPONENTS[index]; // Write the addition to the previous product // This will print an extra '+' at the beginning but that is fine in glsl
write!(self.out, " + ")?; // Write the first vector expression, this expression is marked to be // cached so unless it can't be cached (for example, it's a Constant) // it shouldn't produce large expressions. self.write_expr(arg, ctx)?; // Access the current component on the first vector
write!(self.out, ".{component} * ")?; // Write the second vector expression, this expression is marked to be // cached so unless it can't be cached (for example, it's a Constant) // it shouldn't produce large expressions. self.write_expr(arg1, ctx)?; // Access the current component on the second vector
write!(self.out, ".{component}")?;
}
write!(self.out, ")")?;
Ok(())
}
/// Helper method used to write structs /// /// # Notes /// Ends in a newline fn write_struct_body(
&mutself,
handle: Handle<crate::Type>,
members: &[crate::StructMember],
) -> BackendResult { // glsl structs are written as in C // `struct name() { members };` // | `struct` is a keyword // | `name` is the struct name // | `members` is a semicolon separated list of `type name` // | `type` is the member type // | `name` is the member name
writeln!(self.out, "{{")?;
for (idx, member) in members.iter().enumerate() { // The indentation is only for readability
write!(self.out, "{}", back::INDENT)?;
matchself.module.types[member.ty].inner {
TypeInner::Array {
base,
size,
stride: _,
} => { self.write_type(base)?;
write!( self.out, " {}",
&self.names[&NameKey::StructMember(handle, idx as u32)]
)?; // Write [size] self.write_array_size(base, size)?; // Newline is important
writeln!(self.out, ";")?;
}
_ => { // Write the member type // Adds no trailing space self.write_type(member.ty)?;
// Write the member name and put a semicolon // The leading space is important // All members must have a semicolon even the last one
writeln!( self.out, " {};",
&self.names[&NameKey::StructMember(handle, idx as u32)]
)?;
}
}
}
write!(self.out, "}}")?;
Ok(())
}
/// Helper method used to write statements /// /// # Notes /// Always adds a newline fn write_stmt(
&mutself,
sta: &crate::Statement,
ctx: &back::FunctionCtx,
level: back::Level,
) -> BackendResult { usecrate::Statement;
match *sta { // This is where we can generate intermediate constants for some expression types.
Statement::Emit(ref range) => { for handle in range.clone() { let ptr_class = ctx.resolve_type(handle, &self.module.types).pointer_space(); let expr_name = if ptr_class.is_some() { // GLSL can't save a pointer-valued expression in a variable, // but we shouldn't ever need to: they should never be named expressions, // and none of the expression types flagged by bake_ref_count can be pointer-valued.
None
} elseiflet Some(name) = ctx.named_expressions.get(&handle) { // Front end provides names for all variables at the start of writing. // But we write them to step by step. We need to recache them // Otherwise, we could accidentally write variable name instead of full expression. // Also, we use sanitized names! It defense backend from generating variable with name from reserved keywords.
Some(self.namer.call(name))
} elseifself.need_bake_expressions.contains(&handle) {
Some(Baked(handle).to_string())
} else {
None
};
// If we are going to write an `ImageLoad` next and the target image // is sampled and we are using the `Restrict` policy for bounds // checking images we need to write a local holding the clamped lod. ifletcrate::Expression::ImageLoad {
image,
level: Some(level_expr),
..
} = ctx.expressions[handle]
{ iflet TypeInner::Image {
class: crate::ImageClass::Sampled { .. },
..
} = *ctx.resolve_type(image, &self.module.types)
{ iflet proc::BoundsCheckPolicy::Restrict = self.policies.image_load {
write!(self.out, "{level}")?; self.write_clamped_lod(ctx, handle, image, level_expr)?
}
}
}
iflet Some(name) = expr_name {
write!(self.out, "{level}")?; self.write_named_expr(handle, name, handle, ctx)?;
}
}
} // Blocks are simple we just need to write the block statements between braces // We could also just print the statements but this is more readable and maps more // closely to the IR
Statement::Block(ref block) => {
write!(self.out, "{level}")?;
writeln!(self.out, "{{")?; for sta in block.iter() { // Increase the indentation to help with readability self.write_stmt(sta, ctx, level.next())?
}
writeln!(self.out, "{level}}}")?
} // Ifs are written as in C: // ``` // if(condition) { // accept // } else { // reject // } // ```
Statement::If {
condition, ref accept, ref reject,
} => {
write!(self.out, "{level}")?;
write!(self.out, "if (")?; self.write_expr(condition, ctx)?;
writeln!(self.out, ") {{")?;
for sta in accept { // Increase indentation to help with readability self.write_stmt(sta, ctx, level.next())?;
}
// If there are no statements in the reject block we skip writing it // This is only for readability if !reject.is_empty() {
writeln!(self.out, "{level}}} else {{")?;
for sta in reject { // Increase indentation to help with readability self.write_stmt(sta, ctx, level.next())?;
}
}
writeln!(self.out, "{level}}}")?
} // Switch are written as in C: // ``` // switch (selector) { // // Fallthrough // case label: // block // // Non fallthrough // case label: // block // break; // default: // block // } // ``` // Where the `default` case happens isn't important but we put it last // so that we don't need to print a `break` for it
Statement::Switch {
selector, ref cases,
} => { let l2 = level.next(); // Some GLSL consumers may not handle switches with a single // body correctly: See wgpu#4514. Write such switch statements // as a `do {} while(false);` loop instead. // // Since doing so may inadvertently capture `continue` // statements in the switch body, we must apply continue // forwarding. See the `naga::back::continue_forward` module // docs for details. let one_body = cases
.iter()
.rev()
.skip(1)
.all(|case| case.fall_through && case.body.is_empty()); if one_body { // Unlike HLSL, in GLSL `continue_ctx` only needs to know // about [`Switch`] statements that are being rendered as // `do-while` loops. iflet Some(variable) = self.continue_ctx.enter_switch(&mutself.namer) {
writeln!(self.out, "{level}bool {variable} = false;",)?;
};
writeln!(self.out, "{level}do {{")?; // Note: Expressions have no side-effects so we don't need to emit selector expression.
// Body iflet Some(case) = cases.last() { for sta in case.body.iter() { self.write_stmt(sta, ctx, l2)?;
}
} // End do-while
writeln!(self.out, "{level}}} while(false);")?;
// Write field name
writeln!( self.out, ".{};",
&self.names
[&NameKey::StructMember(result.ty, index as u32)]
)?;
write!(self.out, "{level}")?;
}
}
_ => { let name = VaryingName {
binding: result.binding.as_ref().unwrap(),
stage: ep.stage,
options: VaryingOptions::from_writer_options( self.options, true,
),
};
write!(self.out, "{name} = ")?; self.write_expr(value, ctx)?;
writeln!(self.out, ";")?;
write!(self.out, "{level}")?;
}
}
}
let is_vertex_stage = self.module.entry_points[ep_index as usize].stage
== ShaderStage::Vertex; if is_vertex_stage
&& self
.options
.writer_flags
.contains(WriterFlags::ADJUST_COORDINATE_SPACE)
{
writeln!( self.out, "gl_Position.yz = vec2(-gl_Position.y, gl_Position.z * 2.0 - gl_Position.w);",
)?;
write!(self.out, "{level}")?;
}
if is_vertex_stage
&& self
.options
.writer_flags
.contains(WriterFlags::FORCE_POINT_SIZE)
&& !has_point_size
{
writeln!(self.out, "gl_PointSize = 1.0;")?;
write!(self.out, "{level}")?;
}
writeln!(self.out, "return;")?;
}
}
} // This is one of the places were glsl adds to the syntax of C in this case the discard // keyword which ceases all further processing in a fragment shader, it's called OpKill // in spir-v that's why it's called `Statement::Kill`
Statement::Kill => writeln!(self.out, "{level}discard;")?,
Statement::ControlBarrier(flags) => { self.write_control_barrier(flags, level)?;
}
Statement::MemoryBarrier(flags) => { self.write_memory_barrier(flags, level)?;
} // Stores in glsl are just variable assignments written as `pointer = value;`
Statement::Store { pointer, value } => {
write!(self.out, "{level}")?; let is_atomic_pointer = ctx
.resolve_type(pointer, &self.module.types)
.is_atomic_pointer(&self.module.types); if is_atomic_pointer {
write!(self.out, "atomicExchange(")?; self.write_expr(pointer, ctx)?;
write!(self.out, ", ")?; self.write_expr(value, ctx)?;
writeln!(self.out, ");")?
} else { self.write_expr(pointer, ctx)?;
write!(self.out, " = ")?; self.write_expr(value, ctx)?;
writeln!(self.out, ";")?
}
}
Statement::WorkGroupUniformLoad { pointer, result } => { // GLSL doesn't have pointers, which means that this backend needs to ensure that // the actual "loading" is happening between the two barriers. // This is done in `Emit` by never emitting a variable name for pointer variables self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?;
let result_name = Baked(result).to_string();
write!(self.out, "{level}")?; // Expressions cannot have side effects, so just writing the expression here is fine. self.write_named_expr(pointer, result_name, result, ctx)?;
self.write_control_barrier(crate::Barrier::WORK_GROUP, level)?;
} // Stores a value into an image.
Statement::ImageStore {
image,
coordinate,
array_index,
value,
} => {
write!(self.out, "{level}")?; self.write_image_store(ctx, image, coordinate, array_index, value)?
} // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list
Statement::Call {
function, ref arguments,
result,
} => {
write!(self.out, "{level}")?; iflet Some(expr) = result { let name = Baked(expr).to_string(); let result = self.module.functions[function].result.as_ref().unwrap(); self.write_type(result.ty)?;
write!(self.out, " {name}")?; iflet TypeInner::Array { base, size, .. } = self.module.types[result.ty].inner
{ self.write_array_size(base, size)?
}
write!(self.out, " = ")?; self.named_expressions.insert(expr, name);
}
write!(self.out, "{}(", &self.names[&NameKey::Function(function)])?; let arguments: Vec<_> = arguments
.iter()
.enumerate()
.filter_map(|(i, arg)| { let arg_ty = self.module.functions[function].arguments[i].ty; matchself.module.types[arg_ty].inner {
TypeInner::Sampler { .. } => None,
_ => Some(*arg),
}
})
.collect(); self.write_slice(&arguments, |this, _, arg| this.write_expr(*arg, ctx))?;
writeln!(self.out, ");")?
}
Statement::Atomic {
pointer,
ref fun,
value,
result,
} => {
write!(self.out, "{level}")?;
match *fun {
crate::AtomicFunction::Exchange {
compare: Some(compare_expr),
} => {
let result_handle = result.expect("CompareExchange must have a result");
let res_name = Baked(result_handle).to_string();
self.write_type(ctx.info[result_handle].ty.handle().unwrap())?;
write!(self.out, " {res_name};")?;
write!(self.out, " {res_name}.old_value = atomicCompSwap(")?;
self.write_expr(pointer, ctx)?;
write!(self.out, ", ")?;
self.write_expr(compare_expr, ctx)?;
write!(self.out, ", ")?;
self.write_expr(value, ctx)?;
writeln!(self.out, ");")?;
write!(
self.out,
"{level}{res_name}.exchanged = ({res_name}.old_value == "
)?;
self.write_expr(compare_expr, ctx)?;
writeln!(self.out, ");")?;
self.named_expressions.insert(result_handle, res_name);
}
_ => {
if let Some(result) = result {
let res_name = Baked(result).to_string();
self.write_type(ctx.info[result].ty.handle().unwrap())?;
write!(self.out, " {res_name} = ")?;
self.named_expressions.insert(result, res_name);
}
let fun_str = fun.to_glsl();
write!(self.out, "atomic{fun_str}(")?;
self.write_expr(pointer, ctx)?;
write!(self.out, ", ")?;
if let crate::AtomicFunction::Subtract = *fun {
// Emulate `atomicSub` with `atomicAdd` by negating the value.
write!(self.out, "-")?;
}
self.write_expr(value, ctx)?;
writeln!(self.out, ");")?;
}
}
}
// Stores a value into an image.
Statement::ImageAtomic {
image,
coordinate,
array_index,
fun,
value,
} => {
write!(self.out, "{level}")?;
self.write_image_atomic(ctx, image, coordinate, array_index, fun, value)?
}
Statement::RayQuery { .. } => unreachable!(),
Statement::SubgroupBallot { result, predicate } => {
write!(self.out, "{level}")?;
let res_name = Baked(result).to_string();
let res_ty = ctx.info[result].ty.inner_with(&self.module.types);
self.write_value_type(res_ty)?;
write!(self.out, " {res_name} = ")?;
self.named_expressions.insert(result, res_name);
/// Write a const expression.
///
/// Write `expr`, a handle to an [`Expression`] in the current [`Module`]'s
/// constant expression arena, as GLSL expression.
///
/// # Notes
/// Adds no newlines or leading/trailing whitespace
///
/// [`Expression`]: crate::Expression
/// [`Module`]: crate::Module
fn write_const_expr(
&mut self,
expr: Handle<crate::Expression>,
arena: &crate::Arena<crate::Expression>,
) -> BackendResult {
self.write_possibly_const_expr(
expr,
arena,
|expr| &self.info[expr],
|writer, expr| writer.write_const_expr(expr, arena),
)
}
/// Write [`Expression`] variants that can occur in both runtime and const expressions.
///
/// Write `expr`, a handle to an [`Expression`] in the arena `expressions`,
/// as as GLSL expression. This must be one of the [`Expression`] variants
/// that is allowed to occur in constant expressions.
///
/// Use `write_expression` to write subexpressions.
///
/// This is the common code for `write_expr`, which handles arbitrary
/// runtime expressions, and `write_const_expr`, which only handles
/// const-expressions. Each of those callers passes itself (essentially) as
/// the `write_expression` callback, so that subexpressions are restricted
/// to the appropriate variants.
///
/// # Notes
/// Adds no newlines or leading/trailing whitespace
///
/// [`Expression`]: crate::Expression
fn write_possibly_const_expr<'w, I, E>(
&'w mut self,
expr: Handle<crate::Expression>,
expressions: &crate::Arena<crate::Expression>,
info: I,
write_expression: E,
) -> BackendResult
where
I: Fn(Handle<crate::Expression>) -> &'w proc::TypeResolution,
E: Fn(&mut Self, Handle<crate::Expression>) -> BackendResult,
{
use crate::Expression;
match expressions[expr] {
Expression::Literal(literal) => {
match literal {
// Floats are written using `Debug` instead of `Display` because it always appends the
// decimal part even it's zero which is needed for a valid glsl float constant
crate::Literal::F64(value) => write!(self.out, "{value:?}LF")?,
crate::Literal::F32(value) => write!(self.out, "{value:?}")?,
crate::Literal::F16(_) => {
return Err(Error::Custom("GLSL has no 16-bit float type".into()));
}
// Unsigned integers need a `u` at the end
//
// While `core` doesn't necessarily need it, it's allowed and since `es` needs it we
// always write it as the extra branch wouldn't have any benefit in readability
crate::Literal::U16(value) => write!(self.out, "uint16_t({value})")?,
crate::Literal::I16(value) => write!(self.out, "int16_t({value})")?,
crate::Literal::U32(value) => write!(self.out, "{value}u")?,
crate::Literal::I32(value) => write!(self.out, "{value}")?,
crate::Literal::Bool(value) => write!(self.out, "{value}")?,
crate::Literal::I64(_) => {
return Err(Error::Custom("GLSL has no 64-bit integer type".into()));
}
crate::Literal::U64(_) => {
return Err(Error::Custom("GLSL has no 64-bit integer type".into()));
}
crate::Literal::AbstractInt(_) | crate::Literal::AbstractFloat(_) => {
return Err(Error::Custom(
"Abstract types should not appear in IR presented to backends".into(),
));
}
}
}
Expression::Constant(handle) => {
let constant = &self.module.constants[handle];
if constant.name.is_some() {
write!(self.out, "{}", self.names[&NameKey::Constant(handle)])?;
} else {
self.write_const_expr(constant.init, &self.module.global_expressions)?;
}
}
Expression::ZeroValue(ty) => {
self.write_zero_init_value(ty)?;
}
Expression::Compose { ty, ref components } => {
self.write_type(ty)?;
if let TypeInner::Array { base, size, .. } = self.module.types[ty].inner {
self.write_array_size(base, size)?;
}
write!(self.out, "(")?;
for (index, component) in components.iter().enumerate() {
if index != 0 {
write!(self.out, ", ")?;
}
write_expression(self, *component)?;
}
write!(self.out, ")")?
}
// `Splat` needs to actually write down a vector, it's not always inferred in GLSL.
Expression::Splat { size: _, value } => {
let resolved = info(expr).inner_with(&self.module.types);
self.write_value_type(resolved)?;
write!(self.out, "(")?;
write_expression(self, value)?;
write!(self.out, ")")?
}
_ => {
return Err(Error::Override);
}
}
Ok(())
}
/// Helper method to write expressions
///
/// # Notes
/// Doesn't add any newlines or leading/trailing spaces
fn write_expr(
&mut self,
expr: Handle<crate::Expression>,
ctx: &back::FunctionCtx,
) -> BackendResult {
use crate::Expression;
if let Some(name) = self.named_expressions.get(&expr) {
write!(self.out, "{name}")?;
return Ok(());
}
match ctx.expressions[expr] {
Expression::Literal(_)
| Expression::Constant(_)
| Expression::ZeroValue(_)
| Expression::Compose { .. }
| Expression::Splat { .. } => {
self.write_possibly_const_expr(
expr,
ctx.expressions,
|expr| &ctx.info[expr].ty,
|writer, expr| writer.write_expr(expr, ctx),
)?;
}
Expression::Override(_) => return Err(Error::Override),
// `Access` is applied to arrays, vectors and matrices and is written as indexing
Expression::Access { base, index } => {
self.write_expr(base, ctx)?;
write!(self.out, "[")?;
self.write_expr(index, ctx)?;
write!(self.out, "]")?
}
// `AccessIndex` is the same as `Access` except that the index is a constant and it can
// be applied to structs, in this case we need to find the name of the field at that
// index and write `base.field_name`
Expression::AccessIndex { base, index } => {
self.write_expr(base, ctx)?;
let base_ty_res = &ctx.info[base].ty;
let mut resolved = base_ty_res.inner_with(&self.module.types);
let base_ty_handle = match *resolved {
TypeInner::Pointer { base, space: _ } => {
resolved = &self.module.types[base].inner;
Some(base)
}
_ => base_ty_res.handle(),
};
match *resolved {
TypeInner::Vector { .. } => {
// Write vector access as a swizzle
write!(self.out, ".{}", back::COMPONENTS[index as usize])?
}
TypeInner::Matrix { .. }
| TypeInner::Array { .. }
| TypeInner::ValuePointer { .. } => write!(self.out, "[{index}]")?,
TypeInner::Struct { .. } => {
// This will never panic in case the type is a `Struct`, this is not true
// for other types so we can only check while inside this match arm
let ty = base_ty_handle.unwrap();
write!(
self.out,
".{}",
&self.names[&NameKey::StructMember(ty, index)]
)?
}
ref other => return Err(Error::Custom(format!("Cannot index {other:?}"))),
}
}
// `Swizzle` adds a few letters behind the dot.
Expression::Swizzle {
size,
vector,
pattern,
} => {
self.write_expr(vector, ctx)?;
write!(self.out, ".")?;
for &sc in pattern[..size as usize].iter() {
self.out.write_char(back::COMPONENTS[sc as usize])?;
}
}
// Function arguments are written as the argument name
Expression::FunctionArgument(pos) => {
write!(self.out, "{}", &self.names[&ctx.argument_key(pos)])?
}
// Global variables need some special work for their name but
// `get_global_name` does the work for us
Expression::GlobalVariable(handle) => {
let global = &self.module.global_variables[handle];
self.write_global_name(handle, global)?
}
// A local is written as it's name
Expression::LocalVariable(handle) => {
write!(self.out, "{}", self.names[&ctx.name_key(handle)])?
}
// glsl has no pointers so there's no load operation, just write the pointer expression
Expression::Load { pointer } => {
let ty_inner = ctx.resolve_type(pointer, &self.module.types);
if ty_inner.is_atomic_pointer(&self.module.types) {
let mut suffix = "";
if let TypeInner::Pointer { base, .. } = *ty_inner {
if let TypeInner::Atomic(scalar) = self.module.types[base].inner {
suffix = match (scalar.kind, scalar.width) {
(crate::ScalarKind::Uint, 8) => "ul",
(crate::ScalarKind::Sint, 8) => "l",
(crate::ScalarKind::Uint, _) => "u",
_ => "",
};
}
}
write!(self.out, "atomicOr(")?;
self.write_expr(pointer, ctx)?;
write!(self.out, ", 0{})", suffix)?
} else {
self.write_expr(pointer, ctx)?
}
}
// `ImageSample` is a bit complicated compared to the rest of the IR.
//
// First there are three variations depending whether the sample level is explicitly set,
// if it's automatic or it it's bias:
// `texture(image, coordinate)` - Automatic sample level
// `texture(image, coordinate, bias)` - Bias sample level
// `textureLod(image, coordinate, level)` - Zero or Exact sample level
//
// Furthermore if `depth_ref` is some we need to append it to the coordinate vector
Expression::ImageSample {
image,
sampler: _, //TODO?
gather,
coordinate,
array_index,
offset,
level,
depth_ref,
clamp_to_edge: _,
} => {
let (dim, class, arrayed) = match *ctx.resolve_type(image, &self.module.types) {
TypeInner::Image {
dim,
class,
arrayed,
..
} => (dim, class, arrayed),
_ => unreachable!(),
};
let mut err = None;
if dim == crate::ImageDimension::Cube {
if offset.is_some() {
err = Some("gsamplerCube[Array][Shadow] doesn't support texture sampling with offsets");
}
if arrayed
&& matches!(class, crate::ImageClass::Depth { .. })
&& matches!(level, crate::SampleLevel::Gradient { .. })
{
err = Some("samplerCubeArrayShadow don't support textureGrad");
}
}
if gather.is_some() && level != crate::SampleLevel::Zero {
err = Some("textureGather doesn't support LOD parameters");
}
if let Some(err) = err {
return Err(Error::Custom(String::from(err)));
}
// `textureLod[Offset]` on `sampler2DArrayShadow` and `samplerCubeShadow` does not exist in GLSL,
// unless `GL_EXT_texture_shadow_lod` is present.
// But if the target LOD is zero, we can emulate that by using `textureGrad[Offset]` with a constant gradient of 0.
let workaround_lod_with_grad = ((dim == crate::ImageDimension::Cube && !arrayed)
|| (dim == crate::ImageDimension::D2 && arrayed))
&& level == crate::SampleLevel::Zero
&& matches!(class, crate::ImageClass::Depth { .. })
&& !self.features.contains(Features::TEXTURE_SHADOW_LOD);
// Write the function to be used depending on the sample level
let fun_name = match level {
crate::SampleLevel::Zero if gather.is_some() => "textureGather",
crate::SampleLevel::Zero if workaround_lod_with_grad => "textureGrad",
crate::SampleLevel::Auto | crate::SampleLevel::Bias(_) => "texture",
crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod",
crate::SampleLevel::Gradient { .. } => "textureGrad",
};
let offset_name = match offset {
Some(_) => "Offset",
None => "",
};
write!(self.out, "{fun_name}{offset_name}(")?;
// Write the image that will be used
self.write_expr(image, ctx)?;
// The space here isn't required but it helps with readability
write!(self.out, ", ")?;
// We need to get the coordinates vector size to later build a vector that's `size + 1`
// if `depth_ref` is some, if it isn't a vector we panic as that's not a valid expression
let mut coord_dim = match *ctx.resolve_type(coordinate, &self.module.types) {
TypeInner::Vector { size, .. } => size as u8,
TypeInner::Scalar { .. } => 1,
_ => unreachable!(),
};
if array_index.is_some() {
coord_dim += 1;
}
let merge_depth_ref = depth_ref.is_some() && gather.is_none() && coord_dim < 4;
if merge_depth_ref {
coord_dim += 1;
}
let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es();
let is_vec = tex_1d_hack || coord_dim != 1;
// Compose a new texture coordinates vector
if is_vec {
write!(self.out, "vec{}(", coord_dim + tex_1d_hack as u8)?;
}
self.write_expr(coordinate, ctx)?;
if tex_1d_hack {
write!(self.out, ", 0.0")?;
}
if let Some(expr) = array_index {
write!(self.out, ", ")?;
self.write_expr(expr, ctx)?;
}
if merge_depth_ref {
write!(self.out, ", ")?;
self.write_expr(depth_ref.unwrap(), ctx)?;
}
if is_vec {
write!(self.out, ")")?;
}
if let (Some(expr), false) = (depth_ref, merge_depth_ref) {
write!(self.out, ", ")?;
self.write_expr(expr, ctx)?;
}
match level {
// Auto needs no more arguments
crate::SampleLevel::Auto => (),
// Zero needs level set to 0
crate::SampleLevel::Zero => {
if workaround_lod_with_grad {
let vec_dim = match dim {
crate::ImageDimension::Cube => 3,
_ => 2,
};
write!(self.out, ", vec{vec_dim}(0.0), vec{vec_dim}(0.0)")?;
} else if gather.is_none() {
write!(self.out, ", 0.0")?;
}
}
// Exact and bias require another argument
crate::SampleLevel::Exact(expr) => {
write!(self.out, ", ")?;
self.write_expr(expr, ctx)?;
}
crate::SampleLevel::Bias(_) => {
// This needs to be done after the offset writing
}
crate::SampleLevel::Gradient { x, y } => {
// If we are using sampler2D to replace sampler1D, we also
// need to make sure to use vec2 gradients
if tex_1d_hack {
write!(self.out, ", vec2(")?;
self.write_expr(x, ctx)?;
write!(self.out, ", 0.0)")?;
write!(self.out, ", vec2(")?;
self.write_expr(y, ctx)?;
write!(self.out, ", 0.0)")?;
} else {
write!(self.out, ", ")?;
self.write_expr(x, ctx)?;
write!(self.out, ", ")?;
self.write_expr(y, ctx)?;
}
}
}
if let Some(constant) = offset {
write!(self.out, ", ")?;
if tex_1d_hack {
write!(self.out, "ivec2(")?;
}
self.write_const_expr(constant, ctx.expressions)?;
if tex_1d_hack {
write!(self.out, ", 0)")?;
}
}
// Bias is always the last argument
if let crate::SampleLevel::Bias(expr) = level {
write!(self.out, ", ")?;
self.write_expr(expr, ctx)?;
}
if let (Some(component), None) = (gather, depth_ref) {
write!(self.out, ", {}", component as usize)?;
}
// End the function
write!(self.out, ")")?
}
Expression::ImageLoad {
image,
coordinate,
array_index,
sample,
level,
} => self.write_image_load(expr, ctx, image, coordinate, array_index, sample, level)?,
// Query translates into one of the:
// - textureSize/imageSize
// - textureQueryLevels
// - textureSamples/imageSamples
Expression::ImageQuery { image, query } => {
use crate::ImageClass;
// This will only panic if the module is invalid
let (dim, class) = match *ctx.resolve_type(image, &self.module.types) {
TypeInner::Image {
dim,
arrayed: _,
class,
} => (dim, class),
_ => unreachable!(),
};
let components = match dim {
crate::ImageDimension::D1 => 1,
crate::ImageDimension::D2 => 2,
crate::ImageDimension::D3 => 3,
crate::ImageDimension::Cube => 2,
};
if let crate::ImageQuery::Size { .. } = query {
match components { 1 => write!(self.out, "uint(")?,
_ => write!(self.out, "uvec{components}(")?,
}
} else {
write!(self.out, "uint(")?;
}
match query {
crate::ImageQuery::Size { level } => {
match class {
ImageClass::Sampled { multi, .. } | ImageClass::Depth { multi } => {
write!(self.out, "textureSize(")?;
self.write_expr(image, ctx)?;
if let Some(expr) = level {
let cast_to_int = matches!(
*ctx.resolve_type(expr, &self.module.types),
TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
..
})
);
write!(self.out, ")")?
}
// `Binary` we just write `left op right`, except when dealing with
// comparison operations on vectors as they are implemented with
// builtin functions.
// Once again we wrap everything in parentheses to avoid precedence issues
Expression::Binary {
mut op,
left,
right,
} => {
// Holds `Some(function_name)` if the binary operation is
// implemented as a function call
use crate::{BinaryOperator as Bo, ScalarKind as Sk, TypeInner as Ti};
let left_inner = ctx.resolve_type(left, &self.module.types);
let right_inner = ctx.resolve_type(right, &self.module.types);
let function = match (left_inner, right_inner) {
(&Ti::Vector { scalar, .. }, &Ti::Vector { .. }) => match op {
Bo::Less
| Bo::LessEqual
| Bo::Greater
| Bo::GreaterEqual
| Bo::Equal
| Bo::NotEqual => BinaryOperation::VectorCompare,
Bo::Modulo if scalar.kind == Sk::Float => BinaryOperation::Modulo,
Bo::And if scalar.kind == Sk::Bool => {
op = crate::BinaryOperator::LogicalAnd;
BinaryOperation::VectorComponentWise
}
Bo::InclusiveOr if scalar.kind == Sk::Bool => {
op = crate::BinaryOperator::LogicalOr;
BinaryOperation::VectorComponentWise
}
_ => BinaryOperation::Other,
},
_ => match (left_inner.scalar_kind(), right_inner.scalar_kind()) {
(Some(Sk::Float), _) | (_, Some(Sk::Float)) => match op {
Bo::Modulo => BinaryOperation::Modulo,
_ => BinaryOperation::Other,
},
(Some(Sk::Bool), Some(Sk::Bool)) => match op {
Bo::InclusiveOr => {
op = crate::BinaryOperator::LogicalOr;
BinaryOperation::Other
}
Bo::And => {
op = crate::BinaryOperator::LogicalAnd;
BinaryOperation::Other
}
_ => BinaryOperation::Other,
},
_ => BinaryOperation::Other,
},
};
write!(self.out, ")")?;
}
// TODO: handle undefined behavior of BinaryOperator::Modulo
//
// sint:
// if right == 0 return 0
// if left == min(type_of(left)) && right == -1 return 0
// if sign(left) == -1 || sign(right) == -1 return result as defined by WGSL
//
// uint:
// if right == 0 return 0
//
// float:
// if right == 0 return ? see https://github.com/gpuweb/gpuweb/issues/2798
BinaryOperation::Modulo => {
write!(self.out, "(")?;
write!(self.out, ")")?;
}
}
}
// `Select` is written as `condition ? accept : reject`
// We wrap everything in parentheses to avoid precedence issues
Expression::Select {
condition,
accept,
reject,
} => {
let cond_ty = ctx.resolve_type(condition, &self.module.types);
let vec_select = if let TypeInner::Vector { .. } = *cond_ty {
true
} else {
false
};
// TODO: Boolean mix on desktop required GL_EXT_shader_integer_mix
if vec_select {
// Glsl defines that for mix when the condition is a boolean the first element
// is picked if condition is false and the second if condition is true
write!(self.out, "mix(")?;
self.write_expr(reject, ctx)?;
write!(self.out, ", ")?;
self.write_expr(accept, ctx)?;
write!(self.out, ", ")?;
self.write_expr(condition, ctx)?;
} else {
write!(self.out, "(")?;
self.write_expr(condition, ctx)?;
write!(self.out, " ? ")?;
self.write_expr(accept, ctx)?;
write!(self.out, " : ")?;
self.write_expr(reject, ctx)?;
}
write!(self.out, ")")?
}
// `Derivative` is a function call to a glsl provided function
Expression::Derivative { axis, ctrl, expr } => {
use crate::{DerivativeAxis as Axis, DerivativeControl as Ctrl};
let fun_name = if self.options.version.supports_derivative_control() {
match (axis, ctrl) {
(Axis::X, Ctrl::Coarse) => "dFdxCoarse",
(Axis::X, Ctrl::Fine) => "dFdxFine",
(Axis::X, Ctrl::None) => "dFdx",
(Axis::Y, Ctrl::Coarse) => "dFdyCoarse",
(Axis::Y, Ctrl::Fine) => "dFdyFine",
(Axis::Y, Ctrl::None) => "dFdy",
(Axis::Width, Ctrl::Coarse) => "fwidthCoarse",
(Axis::Width, Ctrl::Fine) => "fwidthFine",
(Axis::Width, Ctrl::None) => "fwidth",
}
} else {
match axis {
Axis::X => "dFdx",
Axis::Y => "dFdy",
Axis::Width => "fwidth",
}
};
write!(self.out, "{fun_name}(")?;
self.write_expr(expr, ctx)?;
write!(self.out, ")")?
}
// `Relational` is a normal function call to some glsl provided functions
Expression::Relational { fun, argument } => {
use crate::RelationalFunction as Rf;
let fun_name = match fun {
Rf::IsInf => "isinf",
Rf::IsNan => "isnan",
Rf::All => "all",
Rf::Any => "any",
};
write!(self.out, "{fun_name}(")?;
self.write_expr(argument, ctx)?;
write!(self.out, ")")?
}
Expression::Math {
fun,
arg,
arg1,
arg2,
arg3,
} => {
use crate::MathFunction as Mf;
let fun_name = match fun {
// comparison
Mf::Abs => "abs",
Mf::Min => "min",
Mf::Max => "max",
Mf::Clamp => {
let scalar_kind = ctx
.resolve_type(arg, &self.module.types)
.scalar_kind()
.unwrap();
match scalar_kind {
crate::ScalarKind::Float => "clamp",
// Clamp is undefined if min > max. In practice this means it can use a median-of-three
// instruction to determine the value. This is fine according to the WGSL spec for float
// clamp, but integer clamp _must_ use min-max. As such we write out min/max.
_ => {
write!(self.out, "min(max(")?;
self.write_expr(arg, ctx)?;
write!(self.out, ", ")?;
self.write_expr(arg1.unwrap(), ctx)?;
write!(self.out, "), ")?;
self.write_expr(arg2.unwrap(), ctx)?;
write!(self.out, ")")?;
// Write parentheses around the dot product expression to prevent operators
// with different precedences from applying earlier.
write!(self.out, "(")?;
for i in 0..4 {
// Since `bitfieldExtract` only sign extends if the value is signed, we
// need to convert the inputs to `int` in case of `Dot4I8Packed`. For
// `Dot4U8Packed`, the code below only introduces parenthesis around
// each factor, which aren't strictly needed because both operands are
// baked, but which don't hurt either.
write!(self.out, "bitfieldExtract({conversion}(")?;
self.write_expr(arg, ctx)?;
write!(self.out, "), {}, 8)", i * 8)?;
return Ok(());
}
Mf::CountOneBits => "bitCount",
Mf::ReverseBits => "bitfieldReverse",
Mf::ExtractBits => {
// The behavior of ExtractBits is undefined when offset + count > bit_width. We need
// to first sanitize the offset and count first. If we don't do this, AMD and Intel chips
// will return out-of-spec values if the extracted range is not within the bit width.
//
// This encodes the exact formula specified by the wgsl spec, without temporary values:
// https://gpuweb.github.io/gpuweb/wgsl/#extractBits-unsigned-builtin
//
// w = sizeof(x) * 8
// o = min(offset, w)
// c = min(count, w - o)
//
// bitfieldExtract(x, o, c)
//
// extract_bits(e, min(offset, w), min(count, w - min(offset, w))))
let scalar_bits = ctx
.resolve_type(arg, &self.module.types)
.scalar_width()
.unwrap()
* 8;
return Ok(());
}
Mf::InsertBits => {
// InsertBits has the same considerations as ExtractBits above
let scalar_bits = ctx
.resolve_type(arg, &self.module.types)
.scalar_width()
.unwrap()
* 8;
write!(self.out, "(vec4(")?;
self.write_expr(arg, ctx)?;
write!(self.out, " & 0xFFu, ")?;
self.write_expr(arg, ctx)?;
write!(self.out, " >> 8 & 0xFFu, ")?;
self.write_expr(arg, ctx)?;
write!(self.out, " >> 16 & 0xFFu, ")?;
self.write_expr(arg, ctx)?;
write!(self.out, " >> 24) / {scale}.0)")?;
return Ok(());
}
}
fun @ (Mf::Unpack4xI8 | Mf::Unpack4xU8) => {
let sign_prefix = match fun {
Mf::Unpack4xI8 => 'i',
Mf::Unpack4xU8 => 'u',
_ => unreachable!(),
};
write!(self.out, "{sign_prefix}vec4(")?;
for i in 0..4 {
write!(self.out, "bitfieldExtract(")?;
// Since bitfieldExtract only sign extends if the value is signed, this
// cast is needed
match fun {
Mf::Unpack4xI8 => {
write!(self.out, "int(")?;
self.write_expr(arg, ctx)?;
write!(self.out, ")")?;
}
Mf::Unpack4xU8 => self.write_expr(arg, ctx)?,
_ => unreachable!(),
};
write!(self.out, ", {}, 8)", i * 8)?;
if i != 3 {
write!(self.out, ", ")?;
}
}
write!(self.out, ")")?;
return Ok(());
}
};
let extract_bits = fun == Mf::ExtractBits;
let insert_bits = fun == Mf::InsertBits;
// Some GLSL functions always return signed integers (like findMSB),
// so they need to be cast to uint if the argument is also an uint.
let ret_might_need_int_to_uint = matches!(
fun,
Mf::FirstTrailingBit | Mf::FirstLeadingBit | Mf::CountOneBits | Mf::Abs
);
// Some GLSL functions only accept signed integers (like abs),
// so they need their argument cast from uint to int.
let arg_might_need_uint_to_int = matches!(fun, Mf::Abs);
// Check if the argument is an unsigned integer and return the vector size
// in case it's a vector
let maybe_uint_size = match *ctx.resolve_type(arg, &self.module.types) {
TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
..
}) => Some(None),
TypeInner::Vector {
scalar:
crate::Scalar {
kind: crate::ScalarKind::Uint,
..
},
size,
} => Some(Some(size)),
_ => None,
};
// Cast to uint if the function needs it
if ret_might_need_int_to_uint {
if let Some(maybe_size) = maybe_uint_size {
match maybe_size {
Some(size) => write!(self.out, "uvec{}(", size as u8)?,
None => write!(self.out, "uint(")?,
}
}
}
write!(self.out, "{fun_name}(")?;
// Cast to int if the function needs it
if arg_might_need_uint_to_int {
if let Some(maybe_size) = maybe_uint_size {
match maybe_size {
Some(size) => write!(self.out, "ivec{}(", size as u8)?,
None => write!(self.out, "int(")?,
}
}
}
self.write_expr(arg, ctx)?;
// Close the cast from uint to int
if arg_might_need_uint_to_int && maybe_uint_size.is_some() {
write!(self.out, ")")?
}
if let Some(arg) = arg1 {
write!(self.out, ", ")?;
if extract_bits {
write!(self.out, "int(")?;
self.write_expr(arg, ctx)?;
write!(self.out, ")")?;
} else {
self.write_expr(arg, ctx)?;
}
}
if let Some(arg) = arg2 {
write!(self.out, ", ")?;
if extract_bits || insert_bits {
write!(self.out, "int(")?;
self.write_expr(arg, ctx)?;
write!(self.out, ")")?;
} else {
self.write_expr(arg, ctx)?;
}
}
if let Some(arg) = arg3 {
write!(self.out, ", ")?;
if insert_bits {
write!(self.out, "int(")?;
self.write_expr(arg, ctx)?;
write!(self.out, ")")?;
} else {
self.write_expr(arg, ctx)?;
}
}
write!(self.out, ")")?;
// Close the cast from int to uint
if ret_might_need_int_to_uint && maybe_uint_size.is_some() {
write!(self.out, ")")?
}
}
// `As` is always a call.
// If `convert` is true the function name is the type
// Else the function name is one of the glsl provided bitcast functions
Expression::As {
expr,
kind: target_kind,
convert,
} => {
let inner = ctx.resolve_type(expr, &self.module.types);
match convert {
Some(width) => {
// this is similar to `write_type`, but with the target kind
let scalar = glsl_scalar(crate::Scalar {
kind: target_kind,
width,
})?;
match *inner {
TypeInner::Matrix { columns, rows, .. } => write!(
self.out,
"{}mat{}x{}",
scalar.prefix, columns as u8, rows as u8
)?,
TypeInner::Vector { size, .. } => {
write!(self.out, "{}vec{}", scalar.prefix, size as u8)?
}
_ => write!(self.out, "{}", scalar.full)?,
}
write!(self.out, "(")?;
self.write_expr(expr, ctx)?;
write!(self.out, ")")?
}
None => {
use crate::ScalarKind as Sk;
write!(self.out, "(")?;
self.write_expr(expr, ctx)?;
write!(self.out, ")")?;
}
}
}
// These expressions never show up in `Emit`.
Expression::CallResult(_)
| Expression::AtomicResult { .. }
| Expression::RayQueryProceedResult
| Expression::WorkGroupUniformLoadResult { .. }
| Expression::SubgroupOperationResult { .. }
| Expression::SubgroupBallotResult => unreachable!(),
// `ArrayLength` is written as `expr.length()` and we convert it to a uint
Expression::ArrayLength(expr) => {
write!(self.out, "uint(")?;
self.write_expr(expr, ctx)?;
write!(self.out, ".length())")?
}
// not supported yet
Expression::RayQueryGetIntersection { .. }
| Expression::RayQueryVertexPositions { .. }
| Expression::CooperativeLoad { .. }
| Expression::CooperativeMultiplyAdd { .. } => unreachable!(),
}
Ok(())
}
/// Helper function to write the local holding the clamped lod
fn write_clamped_lod(
&mut self,
ctx: &back::FunctionCtx,
expr: Handle<crate::Expression>,
image: Handle<crate::Expression>,
level_expr: Handle<crate::Expression>,
) -> Result<(), Error> {
// Define our local and start a call to `clamp`
write!(
self.out,
"int {}{} = clamp(",
Baked(expr),
CLAMPED_LOD_SUFFIX
)?;
// Write the lod that will be clamped
self.write_expr(level_expr, ctx)?;
// Set the min value to 0 and start a call to `textureQueryLevels` to get
// the maximum value
write!(self.out, ", 0, textureQueryLevels(")?;
// Write the target image as an argument to `textureQueryLevels`
self.write_expr(image, ctx)?;
// Close the call to `textureQueryLevels` subtract 1 from it since
// the lod argument is 0 based, close the `clamp` call and end the
// local declaration statement.
writeln!(self.out, ") - 1);")?;
Ok(())
}
// Helper method used to retrieve how many elements a coordinate vector
// for the images operations need.
fn get_coordinate_vector_size(&self, dim: crate::ImageDimension, arrayed: bool) -> u8 {
// openGL es doesn't have 1D images so we need workaround it
let tex_1d_hack = dim == crate::ImageDimension::D1 && self.options.version.is_es();
// Get how many components the coordinate vector needs for the dimensions only
let tex_coord_size = match dim {
crate::ImageDimension::D1 => 1,
crate::ImageDimension::D2 => 2,
crate::ImageDimension::D3 => 3,
crate::ImageDimension::Cube => 2,
};
// Calculate the true size of the coordinate vector by adding 1 for arrayed images
// and another 1 if we need to workaround 1D images by making them 2D
tex_coord_size + tex_1d_hack as u8 + arrayed as u8
}
/// Helper method to write the coordinate vector for image operations
fn write_texture_coord(
&mut self,
ctx: &back::FunctionCtx,
vector_size: u8,
coordinate: Handle<crate::Expression>,
array_index: Option<Handle<crate::Expression>>,
// Emulate 1D images as 2D for profiles that don't support it (glsl es)
tex_1d_hack: bool,
) -> Result<(), Error> {
match array_index {
// If the image needs an array indice we need to add it to the end of our
// coordinate vector, to do so we will use the `ivec(ivec, scalar)`
// constructor notation (NOTE: the inner `ivec` can also be a scalar, this
// is important for 1D arrayed images).
Some(layer_expr) => {
write!(self.out, "ivec{vector_size}(")?;
self.write_expr(coordinate, ctx)?;
write!(self.out, ", ")?;
// If we are replacing sampler1D with sampler2D we also need
// to add another zero to the coordinates vector for the y component
if tex_1d_hack {
write!(self.out, "0, ")?;
}
self.write_expr(layer_expr, ctx)?;
write!(self.out, ")")?;
}
// Otherwise write just the expression (and the 1D hack if needed)
None => {
let uvec_size = match *ctx.resolve_type(coordinate, &self.module.types) {
TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
..
}) => Some(None),
TypeInner::Vector {
size,
scalar:
crate::Scalar {
kind: crate::ScalarKind::Uint,
..
},
} => Some(Some(size as u32)),
_ => None,
};
if tex_1d_hack {
write!(self.out, "ivec2(")?;
} else if uvec_size.is_some() {
match uvec_size {
Some(None) => write!(self.out, "int(")?,
Some(Some(size)) => write!(self.out, "ivec{size}(")?,
_ => {}
}
}
self.write_expr(coordinate, ctx)?;
if tex_1d_hack {
write!(self.out, ", 0)")?;
} else if uvec_size.is_some() {
write!(self.out, ")")?;
}
}
}
Ok(())
}
/// Helper method to write the `ImageStore` statement
fn write_image_store(
&mut self,
ctx: &back::FunctionCtx,
image: Handle<crate::Expression>,
coordinate: Handle<crate::Expression>,
array_index: Option<Handle<crate::Expression>>,
value: Handle<crate::Expression>,
) -> Result<(), Error> {
use crate::ImageDimension as IDim;
// NOTE: openGL requires that `imageStore`s have no effects when the texel is invalid
// so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20)
// This will only panic if the module is invalid
let dim = match *ctx.resolve_type(image, &self.module.types) {
TypeInner::Image { dim, .. } => dim,
_ => unreachable!(),
};
// Begin our call to `imageStore`
write!(self.out, "imageStore(")?;
self.write_expr(image, ctx)?;
// Separate the image argument from the coordinates
write!(self.out, ", ")?;
// openGL es doesn't have 1D images so we need workaround it
let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
// Write the coordinate vector
self.write_texture_coord(
ctx,
// Get the size of the coordinate vector
self.get_coordinate_vector_size(dim, array_index.is_some()),
coordinate,
array_index,
tex_1d_hack,
)?;
// Separate the coordinate from the value to write and write the expression
// of the value to write.
write!(self.out, ", ")?;
self.write_expr(value, ctx)?;
// End the call to `imageStore` and the statement.
writeln!(self.out, ");")?;
Ok(())
}
/// Helper method to write the `ImageAtomic` statement
fn write_image_atomic(
&mut self,
ctx: &back::FunctionCtx,
image: Handle<crate::Expression>,
coordinate: Handle<crate::Expression>,
array_index: Option<Handle<crate::Expression>>,
fun: crate::AtomicFunction,
value: Handle<crate::Expression>,
) -> Result<(), Error> {
use crate::ImageDimension as IDim;
// NOTE: openGL requires that `imageAtomic`s have no effects when the texel is invalid
// so we don't need to generate bounds checks (OpenGL 4.2 Core §3.9.20)
// This will only panic if the module is invalid
let dim = match *ctx.resolve_type(image, &self.module.types) {
TypeInner::Image { dim, .. } => dim,
_ => unreachable!(),
};
// Begin our call to `imageAtomic`
let fun_str = fun.to_glsl();
write!(self.out, "imageAtomic{fun_str}(")?;
self.write_expr(image, ctx)?;
// Separate the image argument from the coordinates
write!(self.out, ", ")?;
// openGL es doesn't have 1D images so we need workaround it
let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
// Write the coordinate vector
self.write_texture_coord(
ctx,
// Get the size of the coordinate vector
self.get_coordinate_vector_size(dim, false),
coordinate,
array_index,
tex_1d_hack,
)?;
// Separate the coordinate from the value to write and write the expression
// of the value to write.
write!(self.out, ", ")?;
self.write_expr(value, ctx)?;
// End the call to `imageAtomic` and the statement.
writeln!(self.out, ");")?;
Ok(())
}
/// Helper method for writing an `ImageLoad` expression.
#[allow(clippy::too_many_arguments)]
fn write_image_load(
&mut self,
handle: Handle<crate::Expression>,
ctx: &back::FunctionCtx,
image: Handle<crate::Expression>,
coordinate: Handle<crate::Expression>,
array_index: Option<Handle<crate::Expression>>,
sample: Option<Handle<crate::Expression>>,
level: Option<Handle<crate::Expression>>,
) -> Result<(), Error> {
use crate::ImageDimension as IDim;
// `ImageLoad` is a bit complicated.
// There are two functions one for sampled
// images another for storage images, the former uses `texelFetch` and the
// latter uses `imageLoad`.
//
// Furthermore we have `level` which is always `Some` for sampled images
// and `None` for storage images, so we end up with two functions:
// - `texelFetch(image, coordinate, level)` for sampled images
// - `imageLoad(image, coordinate)` for storage images
//
// Finally we also have to consider bounds checking, for storage images
// this is easy since openGL requires that invalid texels always return
// 0, for sampled images we need to either verify that all arguments are
// in bounds (`ReadZeroSkipWrite`) or make them a valid texel (`Restrict`).
// This will only panic if the module is invalid
let (dim, class) = match *ctx.resolve_type(image, &self.module.types) {
TypeInner::Image {
dim,
arrayed: _,
class,
} => (dim, class),
_ => unreachable!(),
};
// Get the name of the function to be used for the load operation
// and the policy to be used with it.
let (fun_name, policy) = match class {
// Sampled images inherit the policy from the user passed policies
crate::ImageClass::Sampled { .. } => ("texelFetch", self.policies.image_load),
crate::ImageClass::Storage { .. } => {
// OpenGL ES 3.1 mentions in Chapter "8.22 Texture Image Loads and Stores" that:
// "Invalid image loads will return a vector where the value of R, G, and B components
// is 0 and the value of the A component is undefined."
//
// OpenGL 4.2 Core mentions in Chapter "3.9.20 Texture Image Loads and Stores" that:
// "Invalid image loads will return zero."
//
// So, we only inject bounds checks for ES
let policy = if self.options.version.is_es() {
self.policies.image_load
} else {
proc::BoundsCheckPolicy::Unchecked
};
("imageLoad", policy)
}
// TODO: Is there even a function for this?
crate::ImageClass::Depth { multi: _ } => {
return Err(Error::Custom(
"WGSL `textureLoad` from depth textures is not supported in GLSL".to_string(),
))
}
crate::ImageClass::External => unimplemented!(),
};
// openGL es doesn't have 1D images so we need workaround it
let tex_1d_hack = dim == IDim::D1 && self.options.version.is_es();
// Get the size of the coordinate vector
let vector_size = self.get_coordinate_vector_size(dim, array_index.is_some());
if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy {
// To write the bounds checks for `ReadZeroSkipWrite` we will use a
// ternary operator since we are in the middle of an expression and
// need to return a value.
//
// NOTE: glsl does short circuit when evaluating logical
// expressions so we can be sure that after we test a
// condition it will be true for the next ones
// Write parentheses around the ternary operator to prevent problems with
// expressions emitted before or after it having more precedence
write!(self.out, "(",)?;
// The lod check needs to precede the size check since we need
// to use the lod to get the size of the image at that level.
if let Some(level_expr) = level {
self.write_expr(level_expr, ctx)?;
write!(self.out, " < textureQueryLevels(",)?;
self.write_expr(image, ctx)?;
// Chain the next check
write!(self.out, ") && ")?;
}
// Check that the sample arguments doesn't exceed the number of samples
if let Some(sample_expr) = sample {
self.write_expr(sample_expr, ctx)?;
write!(self.out, " < textureSamples(",)?;
self.write_expr(image, ctx)?;
// Chain the next check
write!(self.out, ") && ")?;
}
// We now need to write the size checks for the coordinates and array index
// first we write the comparison function in case the image is 1D non arrayed
// (and no 1D to 2D hack was needed) we are comparing scalars so the less than
// operator will suffice, but otherwise we'll be comparing two vectors so we'll
// need to use the `lessThan` function but it returns a vector of booleans (one
// for each comparison) so we need to fold it all in one scalar boolean, since
// we want all comparisons to pass we use the `all` function which will only
// return `true` if all the elements of the boolean vector are also `true`.
//
// So we'll end with one of the following forms
// - `coord < textureSize(image, lod)` for 1D images
// - `all(lessThan(coord, textureSize(image, lod)))` for normal images
// - `all(lessThan(ivec(coord, array_index), textureSize(image, lod)))`
// for arrayed images
// - `all(lessThan(coord, textureSize(image)))` for multi sampled images
if vector_size != 1 {
write!(self.out, "all(lessThan(")?;
}
// Write the coordinate vector
self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?;
if vector_size != 1 {
// If we used the `lessThan` function we need to separate the
// coordinates from the image size.
write!(self.out, ", ")?;
} else {
// If we didn't use it (ie. 1D images) we perform the comparison
// using the less than operator.
write!(self.out, " < ")?;
}
// Call `textureSize` to get our image size
write!(self.out, "textureSize(")?;
self.write_expr(image, ctx)?;
// `textureSize` uses the lod as a second argument for mipmapped images
if let Some(level_expr) = level {
// Separate the image from the lod
write!(self.out, ", ")?;
self.write_expr(level_expr, ctx)?;
}
// Close the `textureSize` call
write!(self.out, ")")?;
if vector_size != 1 {
// Close the `all` and `lessThan` calls
write!(self.out, "))")?;
}
// Finally end the condition part of the ternary operator
write!(self.out, " ? ")?;
}
// Begin the call to the function used to load the texel
write!(self.out, "{fun_name}(")?;
self.write_expr(image, ctx)?;
write!(self.out, ", ")?;
// If we are using `Restrict` bounds checking we need to pass valid texel
// coordinates, to do so we use the `clamp` function to get a value between
// 0 and the image size - 1 (indexing begins at 0)
if let proc::BoundsCheckPolicy::Restrict = policy {
write!(self.out, "clamp(")?;
}
// Write the coordinate vector
self.write_texture_coord(ctx, vector_size, coordinate, array_index, tex_1d_hack)?;
// If we are using `Restrict` bounds checking we need to write the rest of the
// clamp we initiated before writing the coordinates.
if let proc::BoundsCheckPolicy::Restrict = policy {
// Write the min value 0
if vector_size == 1 {
write!(self.out, ", 0")?;
} else {
write!(self.out, ", ivec{vector_size}(0)")?;
}
// Start the `textureSize` call to use as the max value.
write!(self.out, ", textureSize(")?;
self.write_expr(image, ctx)?;
// If the image is mipmapped we need to add the lod argument to the
// `textureSize` call, but this needs to be the clamped lod, this should
// have been generated earlier and put in a local.
if class.is_mipmapped() {
write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?;
}
// Close the `textureSize` call
write!(self.out, ")")?;
// Subtract 1 from the `textureSize` call since the coordinates are zero based.
if vector_size == 1 {
write!(self.out, " - 1")?;
} else {
write!(self.out, " - ivec{vector_size}(1)")?;
}
// Close the `clamp` call
write!(self.out, ")")?;
// Add the clamped lod (if present) as the second argument to the
// image load function.
if level.is_some() {
write!(self.out, ", {}{}", Baked(handle), CLAMPED_LOD_SUFFIX)?;
}
// If a sample argument is needed we need to clamp it between 0 and
// the number of samples the image has.
if let Some(sample_expr) = sample {
write!(self.out, ", clamp(")?;
self.write_expr(sample_expr, ctx)?;
// Set the min value to 0 and start the call to `textureSamples`
write!(self.out, ", 0, textureSamples(")?;
self.write_expr(image, ctx)?;
// Close the `textureSamples` call, subtract 1 from it since the sample
// argument is zero based, and close the `clamp` call
writeln!(self.out, ") - 1)")?;
}
} else if let Some(sample_or_level) = sample.or(level) {
// GLSL only support SInt on this field while WGSL support also UInt
let cast_to_int = matches!(
*ctx.resolve_type(sample_or_level, &self.module.types),
TypeInner::Scalar(crate::Scalar {
kind: crate::ScalarKind::Uint,
..
})
);
// If no bounds checking is need just add the sample or level argument
// after the coordinates
write!(self.out, ", ")?;
if cast_to_int {
write!(self.out, "int(")?;
}
self.write_expr(sample_or_level, ctx)?;
if cast_to_int {
write!(self.out, ")")?;
}
}
// Close the image load function.
write!(self.out, ")")?;
// If we were using the `ReadZeroSkipWrite` policy we need to end the first branch
// (which is taken if the condition is `true`) with a colon (`:`) and write the
// second branch which is just a 0 value.
if let proc::BoundsCheckPolicy::ReadZeroSkipWrite = policy {
// Get the kind of the output value.
let kind = match class {
// Only sampled images can reach here since storage images
// don't need bounds checks and depth images aren't implemented
crate::ImageClass::Sampled { kind, .. } => kind,
_ => unreachable!(),
};
// End the first branch
write!(self.out, " : ")?;
// Write the 0 value
write!(
self.out,
"{}vec4(",
glsl_scalar(crate::Scalar { kind, width: 4 })?.prefix,
)?;
self.write_zero_init_scalar(kind)?;
// Close the zero value constructor
write!(self.out, ")")?;
// Close the parentheses surrounding our ternary
write!(self.out, ")")?;
}
Ok(())
}
fn write_named_expr(
&mut self,
handle: Handle<crate::Expression>,
name: String,
// The expression which is being named.
// Generally, this is the same as handle, except in WorkGroupUniformLoad
named: Handle<crate::Expression>,
ctx: &back::FunctionCtx,
) -> BackendResult {
match ctx.info[named].ty {
proc::TypeResolution::Handle(ty_handle) => match self.module.types[ty_handle].inner {
TypeInner::Struct { .. } => {
let ty_name = &self.names[&NameKey::Type(ty_handle)];
write!(self.out, "{ty_name}")?;
}
_ => {
self.write_type(ty_handle)?;
}
},
proc::TypeResolution::Value(ref inner) => {
self.write_value_type(inner)?;
}
}
let resolved = ctx.resolve_type(named, &self.module.types);
/// Issue a memory barrier.
fn write_memory_barrier(&mut self, flags: crate::Barrier, level: back::Level) -> BackendResult {
if flags.contains(crate::Barrier::STORAGE) {
writeln!(self.out, "{level}memoryBarrierBuffer();")?;
}
if flags.contains(crate::Barrier::WORK_GROUP) {
writeln!(self.out, "{level}memoryBarrierShared();")?;
}
if flags.contains(crate::Barrier::SUB_GROUP) {
writeln!(self.out, "{level}subgroupMemoryBarrier();")?;
}
if flags.contains(crate::Barrier::TEXTURE) {
writeln!(self.out, "{level}memoryBarrierImage();")?;
}
Ok(())
}
/// Helper function that return the glsl storage access string of [`StorageAccess`](crate::StorageAccess)
///
/// glsl allows adding both `readonly` and `writeonly` but this means that
/// they can only be used to query information about the resource which isn't what
/// we want here so when storage access is both `LOAD` and `STORE` add no modifiers
fn write_storage_access(&mut self, storage_access: crate::StorageAccess) -> BackendResult {
if storage_access.contains(crate::StorageAccess::ATOMIC) {
return Ok(());
}
if !storage_access.contains(crate::StorageAccess::STORE) {
write!(self.out, "readonly ")?;
}
if !storage_access.contains(crate::StorageAccess::LOAD) {
write!(self.out, "writeonly ")?;
}
Ok(())
}
/// Helper method used to produce the reflection info that's returned to the user
fn collect_reflection_info(&mut self) -> Result<ReflectionInfo, Error> {
let info = self.info.get_entry_point(self.entry_point_idx as usize);
let mut texture_mapping = crate::FastHashMap::default();
let mut uniforms = crate::FastHashMap::default();
for sampling in info.sampling_set.iter() {
let tex_name = self.reflection_names_globals[&sampling.image].clone();
let mut immediates_info = None;
for (handle, var) in self.module.global_variables.iter() {
if info[handle].is_empty() {
continue;
}
match self.module.types[var.ty].inner {
TypeInner::Image { .. } => {
let tex_name = self.reflection_names_globals[&handle].clone();
match texture_mapping.entry(tex_name) {
hash_map::Entry::Vacant(v) => {
v.insert(TextureMapping {
texture: handle,
sampler: None,
});
}
hash_map::Entry::Occupied(_) => {
// already used with a sampler, do nothing
}
}
}
_ => match var.space {
crate::AddressSpace::Uniform | crate::AddressSpace::Storage { .. } => {
let name = self.reflection_names_globals[&handle].clone();
uniforms.insert(handle, name);
}
crate::AddressSpace::Immediate => {
let name = self.reflection_names_globals[&handle].clone();
immediates_info = Some((name, var.ty));
}
_ => (),
},
}
}
let mut immediates_segments = Vec::new();
let mut immediates_items = vec![];
if let Some((name, ty)) = immediates_info {
// We don't have a layouter available to us, so we need to create one.
//
// This is potentially a bit wasteful, but the set of types in the program
// shouldn't be too large.
let mut layouter = proc::Layouter::default();
layouter.update(self.module.to_ctx()).unwrap();
// We start with the name of the binding itself.
immediates_segments.push(name);
// We then recursively collect all the uniform fields of the immediate data.
self.collect_immediates_items(
ty,
&mut immediates_segments,
&layouter,
&mut 0,
&mut immediates_items,
);
}
fn collect_immediates_items(
&mut self,
ty: Handle<crate::Type>,
segments: &mut Vec<String>,
layouter: &proc::Layouter,
offset: &mut u32,
items: &mut Vec<ImmediateItem>,
) {
// At this point in the recursion, `segments` contains the path
// needed to access `ty` from the root.
let layout = &layouter[ty];
*offset = layout.alignment.round_up(*offset);
match self.module.types[ty].inner {
// All these types map directly to GL uniforms.
TypeInner::Scalar { .. } | TypeInner::Vector { .. } | TypeInner::Matrix { .. } => {
// Build the full name, by combining all current segments.
let name: String = segments.iter().map(String::as_str).collect();
items.push(ImmediateItem {
access_path: name,
offset: *offset,
ty: self.module.types[ty].inner.clone(),
size_bytes: layout.size,
});
*offset += layout.size;
}
// Arrays are recursed into.
TypeInner::Array { base, size, .. } => {
let crate::ArraySize::Constant(count) = size else {
unreachable!("Cannot have dynamic arrays in immediates");
};
for i in 0..count.get() {
// Add the array accessor and recurse.
segments.push(format!("[{i}]"));
self.collect_immediates_items(base, segments, layouter, offset, items);
segments.pop();
}
// Ensure the stride is kept by rounding up to the alignment.
*offset = layout.alignment.round_up(*offset)
}
TypeInner::Struct { ref members, .. } => {
for (index, member) in members.iter().enumerate() {
// Add struct accessor and recurse.
segments.push(format!(
".{}",
self.names[&NameKey::StructMember(ty, index as u32)]
));
self.collect_immediates_items(member.ty, segments, layouter, offset, items);
segments.pop();
}
// Ensure ending padding is kept by rounding up to the alignment.
*offset = layout.alignment.round_up(*offset)
}
_ => unreachable!(),
}
}
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.218 Sekunden
(vorverarbeitet am 2026-08-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.