/// 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 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(
&mutself,
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(&mutSelf, Handle<crate::Expression>) -> BackendResult,
{ usecrate::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)?;
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)?;
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) { letmut suffix = ""; iflet TypeInner::Pointer { base, .. } = *ty_inner { iflet 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!(),
}; letmut 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");
} iflet 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 letmut 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")?;
} iflet 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, ")")?;
}
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)")?;
} elseif 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)?;
}
}
}
// 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 } => { usecrate::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,
};
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 usecrate::{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 = iflet 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 } => { usecrate::{DerivativeAxis as Axis, DerivativeControl as Ctrl}; let fun_name = ifself.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 } => { usecrate::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}(")?;
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 in0..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 in0..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 { iflet 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 { iflet 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, ")")?
}
// 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, ")")?;
}
}
} // 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(
&mutself,
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(
&mutself,
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(")?;
} elseif 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)")?;
} elseif uvec_size.is_some() {
write!(self.out, ")")?;
}
}
}
Ok(())
}
/// Helper method to write the `ImageStore` statement fn write_image_store(
&mutself,
ctx: &back::FunctionCtx,
image: Handle<crate::Expression>,
coordinate: Handle<crate::Expression>,
array_index: Option<Handle<crate::Expression>>,
value: Handle<crate::Expression>,
) -> Result<(), Error> { usecrate::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, ");")?;
// 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, ");")?;
// `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 = ifself.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());
iflet 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. iflet 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 iflet 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 iflet 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) iflet 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. iflet 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. iflet 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)")?;
}
} elseiflet 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. iflet 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(
&mutself,
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) => matchself.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(&mutself, 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(&mutself, 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(&mutself) -> Result<ReflectionInfo, Error> { let info = self.info.get_entry_point(self.entry_point_idx as usize); letmut texture_mapping = crate::FastHashMap::default(); letmut uniforms = crate::FastHashMap::default();
for sampling in info.sampling_set.iter() { let tex_name = self.reflection_names_globals[&sampling.image].clone();
iflet 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. letmut 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,
&mut0,
&mut immediates_items,
);
}
fn collect_immediates_items(
&mutself,
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); matchself.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, .. } => { letcrate::ArraySize::Constant(count) = size else {
unreachable!("Cannot have dynamic arrays in immediates");
};
for i in0..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
¤ 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.0.484Bemerkung:
(vorverarbeitet am 2026-10-05)
¤
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.