From 3fbc801b800c64865e1b65dd2653468577196a7e Mon Sep 17 00:00:00 2001 From: Capucho Date: Tue, 24 Nov 2020 22:20:59 +0000 Subject: [PATCH] [glsl-out] Wrote more documentation --- src/back/glsl/mod.rs | 233 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 220 insertions(+), 13 deletions(-) diff --git a/src/back/glsl/mod.rs b/src/back/glsl/mod.rs index cf4a5b5eaf..89f0455f61 100644 --- a/src/back/glsl/mod.rs +++ b/src/back/glsl/mod.rs @@ -18,6 +18,29 @@ //! - 310 //! +// GLSL is mostly a superset of C but it also removes some parts of it this is a list of relevant +// aspects for this backend. +// +// The most notable change is the introduction of the version preprocessor directive that must +// always be the first line of a glsl file and is written as +// `#version number profile` +// `number` is the version itself (i.e. 300) and `profile` is the +// shader profile we only support "core" and "es", the former is used in desktop applications and +// the later is used in embedded contexts, mobile devices and browsers. Each one as it's own +// versions (at the time of writing this the latest version for "core" is 460 and for "es" is 320) +// +// Other important preprocessor addition is the extension directive which is written as +// `#extension name: behaviour` +// Extensions provide increased features in a plugin fashion but they aren't required to be +// supported hence why they are called extensions, that's why `behaviour` is used it specifies +// wether the extension is strictly required or if it should only be enabled if needed. In our case +// when we use extensions we set behaviour to `require` always. +// +// The only thing that glsl removes that makes a difference are pointers. +// +// Addititions that are relevant for the backend are the discard keyword, the introduction of +// vector, matrices, samplers, image types and functions that provide common shader operations + pub use error::Error; pub use features::Features; @@ -831,12 +854,32 @@ impl<'a, W: Write> Writer<'a, W> { Ok(()) } + /// Helper method used to write structs + /// + /// # Notes + /// Ends in a newline fn write_struct(&mut self, handle: Handle, members: &[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, "struct {} {{", self.names[&NameKey::Type(handle)])?; for (idx, member) in members.iter().enumerate() { + // The identation is only for readability write!(self.out, "\t")?; + + // Write the member type + // Adds no trailing space self.write_type(member.ty, false)?; + + // 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, " {};", @@ -848,22 +891,39 @@ impl<'a, W: Write> Writer<'a, W> { Ok(()) } + /// Helper method used to write statements + /// + /// # Notes + /// Always adds a newline fn write_stmt( &mut self, sta: &Statement, ctx: &FunctionCtx<'_, '_>, indent: usize, ) -> BackendResult { + // The indentation is only for readability write!(self.out, "{}", "\t".repeat(indent))?; match sta { + // 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(block) => { writeln!(self.out, "{{")?; for sta in block.iter() { + // Increase the indentation to help with readability self.write_stmt(sta, ctx, indent + 1)? } writeln!(self.out, "{}}}", "\t".repeat(indent))? } + // Ifs are written as in C: + // ``` + // if(condition) { + // accept + // } else { + // reject + // } + // ``` Statement::If { condition, accept, @@ -874,28 +934,50 @@ impl<'a, W: Write> Writer<'a, W> { writeln!(self.out, ") {{")?; for sta in accept { + // Increase indentation to help with readability self.write_stmt(sta, ctx, indent + 1)?; } + // 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, "{}}} else {{", "\t".repeat(indent))?; for sta in reject { + // Increase identation to help with readability self.write_stmt(sta, ctx, indent + 1)?; } } writeln!(self.out, "{}}}", "\t".repeat(indent))? } + // Switch are written as in C: + // ``` + // switch (selector) { + // // Falltrough + // 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, cases, default, } => { + // Start the switch write!(self.out, "switch(")?; self.write_expr(*selector, ctx)?; writeln!(self.out, ") {{")?; + // Write all cases for (label, (block, fallthrough)) in cases { writeln!(self.out, "{}case {}:", "\t".repeat(indent + 1), label)?; @@ -903,11 +985,14 @@ impl<'a, W: Write> Writer<'a, W> { self.write_stmt(sta, ctx, indent + 2)?; } + // Write `break;` if the block isn't fallthrough if fallthrough.is_none() { writeln!(self.out, "{}break;", "\t".repeat(indent + 2))?; } } + // Only write the default block if the block isn't empty + // Writing default without a block is valid but it's more readable this way if !default.is_empty() { writeln!(self.out, "{}default:", "\t".repeat(indent + 1))?; @@ -918,6 +1003,14 @@ impl<'a, W: Write> Writer<'a, W> { writeln!(self.out, "{}}}", "\t".repeat(indent))? } + // Loops in naga IR are based on wgsl loops, glsl can emulate the behaviour by using a + // while true loop and appending the continuing block to the body resulting on: + // ``` + // while(true) { + // body + // continuing + // } + // ``` Statement::Loop { body, continuing } => { writeln!(self.out, "while(true) {{")?; @@ -927,17 +1020,26 @@ impl<'a, W: Write> Writer<'a, W> { writeln!(self.out, "{}}}", "\t".repeat(indent))? } + // Break, continue and return as written as in C + // `break;` Statement::Break => writeln!(self.out, "break;")?, + // `continue;` Statement::Continue => writeln!(self.out, "continue;")?, + // `return expr;`, `expr` is optional Statement::Return { value } => { write!(self.out, "return")?; + // Write the expression to be returned if needed if let Some(expr) = value { write!(self.out, " ")?; self.write_expr(*expr, ctx)?; } writeln!(self.out, ";")?; } + // 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, "discard;")?, + // Stores in glsl are just variable assignments written as `pointer = value;` Statement::Store { pointer, value } => { self.write_expr(*pointer, ctx)?; write!(self.out, " = ")?; @@ -949,14 +1051,22 @@ impl<'a, W: Write> Writer<'a, W> { Ok(()) } + /// Helper method to write expressions + /// + /// # Notes + /// Doesn't add any newlines or leading/trailing spaces fn write_expr(&mut self, expr: Handle, ctx: &FunctionCtx<'_, '_>) -> BackendResult { match ctx.expressions[expr] { + // `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)?; @@ -965,6 +1075,8 @@ impl<'a, W: Write> Writer<'a, W> { | TypeInner::Matrix { .. } | TypeInner::Array { .. } => 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 = ctx.typifier.get_handle(base).unwrap(); write!( @@ -976,31 +1088,34 @@ impl<'a, W: Write> Writer<'a, W> { ref other => return Err(Error::Custom(format!("Cannot index {:?}", other))), } } + // Constants are delegated to `write_constant` Expression::Constant(constant) => { self.write_constant(&self.module.constants[constant])? } + // `Compose` is pretty simple we just write `type(components)` where `components` is a + // comma separated list of expressions Expression::Compose { ty, ref components } => { - match self.module.types[ty].inner { - TypeInner::Vector { .. } - | TypeInner::Matrix { .. } - | TypeInner::Array { .. } - | TypeInner::Struct { .. } => self.write_type(ty, false)?, - _ => unreachable!(), - } + self.write_type(ty, false)?; write!(self.out, "(")?; self.write_slice(components, |this, _, arg| this.write_expr(*arg, ctx))?; write!(self.out, ")")? } + // Function arguments are written as the argument name Expression::FunctionArgument(pos) => { write!(self.out, "{}", ctx.get_arg(pos, &self.names))? } + // Global variables need some special work, if they are a builtin we write the glsl + // variable name produced by `glsl_built_in` otherwise we write the global name + // produced by the `Namer` Expression::GlobalVariable(handle) => { if let Some(crate::Binding::BuiltIn(built_in)) = self.module.global_variables[handle].binding { + // Global is a builtin so get the glsl variable name write!(self.out, "{}", glsl_built_in(built_in))? } else { + // Global isn't a builtin so just write the name write!( self.out, "{}", @@ -1008,10 +1123,21 @@ impl<'a, W: Write> Writer<'a, W> { )? } } + // 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 } => self.write_expr(pointer, ctx)?, + // `ImageSample` is a bit complicated compared to the rest of the IR. + // + // First there are three variations depending wether 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, coordinate, @@ -1020,6 +1146,8 @@ impl<'a, W: Write> Writer<'a, W> { .. } => { //TODO: handle MS + + //Write the function to be used depending on the sample level write!( self.out, "{}(", @@ -1028,45 +1156,61 @@ impl<'a, W: Write> Writer<'a, W> { crate::SampleLevel::Zero | crate::SampleLevel::Exact(_) => "textureLod", } )?; + + // 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 size = match *ctx.typifier.get(coordinate, &self.module.types) { TypeInner::Vector { size, .. } => size, - ref other => { - return Err(Error::Custom(format!( - "Cannot sample with coordinates of type {:?}", - other - ))) - } + _ => unreachable!(), }; if let Some(depth_ref) = depth_ref { + // Compose a new vector with the coordinates and `depth_ref` write!(self.out, "vec{}(", size as u8 + 1)?; self.write_expr(coordinate, ctx)?; + // The space here isn't required but it helps with readability write!(self.out, ", ")?; self.write_expr(depth_ref, ctx)?; write!(self.out, ")")? } else { + // There's no `depth_ref` so just write the coordinate expression self.write_expr(coordinate, ctx)? } match level { + // Auto needs no more arguments crate::SampleLevel::Auto => (), + // Zero needs level set to 0 crate::SampleLevel::Zero => write!(self.out, ", 0")?, + // Exact and bias require another argument crate::SampleLevel::Exact(expr) | crate::SampleLevel::Bias(expr) => { write!(self.out, ", ")?; self.write_expr(expr, ctx)?; } } + // End the function write!(self.out, ")")? } + // `ImageLoad` is also 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 `index` which is always `Some` for sampled images + // and `None` for storage images, so we end up with two functions: + // `texelFetch(image, coordinate, index)` - for sampled images + // `imageLoad(image, coordinate)` - for storage images Expression::ImageLoad { image, coordinate, index, } => { + // This will only panic if the module is invalid let class = match ctx.typifier.get(image, &self.module.types) { TypeInner::Image { class, .. } => class, _ => unreachable!(), @@ -1075,6 +1219,7 @@ impl<'a, W: Write> Writer<'a, W> { match class { ImageClass::Sampled { .. } => write!(self.out, "texelFetch(")?, ImageClass::Storage(_) => write!(self.out, "imageLoad(")?, + // TODO: Is there even a function for this? ImageClass::Depth => todo!(), } @@ -1085,6 +1230,7 @@ impl<'a, W: Write> Writer<'a, W> { match class { ImageClass::Sampled { .. } => { write!(self.out, ", ")?; + // This will only panic if the module is invalid self.write_expr(index.unwrap(), ctx)?; write!(self.out, ")")? } @@ -1092,6 +1238,12 @@ impl<'a, W: Write> Writer<'a, W> { ImageClass::Depth => todo!(), } } + // `Unary` is pretty straightforward + // "-" - for `Negate` + // "~" - for `Not` if it's an integer + // "!" - for `Not` if it's a boolean + // + // We also wrap the everything in parantheses to avoid precedence issues Expression::Unary { op, expr } => { write!( self.out, @@ -1124,6 +1276,8 @@ impl<'a, W: Write> Writer<'a, W> { write!(self.out, ")")? } + // `Binary` we just write `left op right` + // Once again we wrap everything in parantheses to avoid precedence issues Expression::Binary { op, left, right } => { write!(self.out, "(")?; self.write_expr(left, ctx)?; @@ -1157,6 +1311,8 @@ impl<'a, W: Write> Writer<'a, W> { write!(self.out, ")")? } + // `Select` is written as `condition ? accept : reject` + // We wrap everything in parantheses to avoid precedence issues Expression::Select { condition, accept, @@ -1170,14 +1326,17 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(reject, ctx)?; write!(self.out, ")")? } + // `Intrinsic` is a normal function call to some glsl provided functions Expression::Intrinsic { fun, argument } => { write!( self.out, "{}(", match fun { + // There's no specific function for this but we can invert the result of `isinf` IntrinsicFunction::IsFinite => "!isinf", IntrinsicFunction::IsInf => "isinf", IntrinsicFunction::IsNan => "isnan", + // There's also no function for this but we can invert `isnan` IntrinsicFunction::IsNormal => "!isnan", IntrinsicFunction::All => "all", IntrinsicFunction::Any => "any", @@ -1188,11 +1347,14 @@ impl<'a, W: Write> Writer<'a, W> { write!(self.out, ")")? } + // `Transpose` is a call to the glsl function `transpose` Expression::Transpose(matrix) => { write!(self.out, "transpose(")?; self.write_expr(matrix, ctx)?; write!(self.out, ")")? } + // Both `Dot` and `Cross` products are a call to a glsl provide functions with `left` + // and `right` as arguments Expression::DotProduct(left, right) => { write!(self.out, "dot(")?; self.write_expr(left, ctx)?; @@ -1207,6 +1369,9 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(right, ctx)?; 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, @@ -1247,6 +1412,7 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(expr, ctx)?; write!(self.out, ")")? } + // `Derivative` is a function call to a glsl provided function Expression::Derivative { axis, expr } => { write!( self.out, @@ -1260,6 +1426,7 @@ impl<'a, W: Write> Writer<'a, W> { self.write_expr(expr, ctx)?; write!(self.out, ")")? } + // A `Call` is written `name(arguments)` where `arguments` is a comma separated expressions list Expression::Call { origin: FunctionOrigin::Local(ref function), ref arguments, @@ -1284,6 +1451,7 @@ impl<'a, W: Write> Writer<'a, W> { self.write_slice(arguments, |this, _, arg| this.write_expr(*arg, ctx))?; write!(self.out, ")")? } + // `atan2` is implemented as `atan(y,x)` so we must handle it separately "atan2" => { write!(self.out, "atan(")?; self.write_expr(arguments[1], ctx)?; @@ -1298,6 +1466,7 @@ impl<'a, W: Write> Writer<'a, W> { ))) } }, + // `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)?; @@ -1308,12 +1477,18 @@ impl<'a, W: Write> Writer<'a, W> { Ok(()) } + /// Helper method used to produce the images mapping that's returned to the user + /// + /// It takes an iterator of [`Function`](crate::Function) references instead of + /// [`Handle`](crate::arena::Handle) because [`EntryPoint`](crate::EntryPoint) isn't in any + /// [`Arena`](crate::arena::Arena) and we need to traverse it fn collect_texture_mapping( &self, functions: impl Iterator, ) -> Result, Error> { let mut mappings = FastHashMap::default(); + // Traverse each function in the `functions` iterator with `TextureMappingVisitor` for func in functions { let mut interface = Interface { expressions: &func.expressions, @@ -1327,6 +1502,7 @@ impl<'a, W: Write> Writer<'a, W> { }; interface.traverse(&func.body); + // Check if any error occurred if let Some(error) = interface.visitor.error { return Err(error); } @@ -1336,11 +1512,22 @@ impl<'a, W: Write> Writer<'a, W> { } } +/// Structure returned by [`glsl_scalar`](glsl_scalar) +/// +/// It contains both a prefix used in other types and the full type name struct ScalarString<'a> { + /// The prefix used to compose other types prefix: &'a str, + /// The name of the scalar type full: &'a str, } +/// Helper function that returns scalar related strings +/// +/// Check [`ScalarString`](ScalarString) for the information provided +/// +/// # Errors +/// If a [`Float`](crate::ScalarKind::Float) with an width that isn't 4 or 8 fn glsl_scalar(kind: ScalarKind, width: crate::Bytes) -> Result, Error> { Ok(match kind { ScalarKind::Sint => ScalarString { @@ -1374,6 +1561,7 @@ fn glsl_scalar(kind: ScalarKind, width: crate::Bytes) -> Result &'static str { match built_in { BuiltIn::Position => "gl_Position", @@ -1394,6 +1582,7 @@ fn glsl_built_in(built_in: BuiltIn) -> &'static str { } } +/// Helper function that returns the string correspoding to the storage class fn glsl_storage_class(class: StorageClass) -> &'static str { match class { StorageClass::Function => "", @@ -1408,6 +1597,10 @@ fn glsl_storage_class(class: StorageClass) -> &'static str { } } +/// Helper function that returns the string correspoding to the glsl interpolation qualifier +/// +/// # Errors +/// If [`Patch`](crate::Interpolation::Patch) is passed, as it isn't supported in glsl fn glsl_interpolation(interpolation: Interpolation) -> Result<&'static str, Error> { Ok(match interpolation { Interpolation::Perspective => "smooth", @@ -1423,6 +1616,7 @@ fn glsl_interpolation(interpolation: Interpolation) -> Result<&'static str, Erro }) } +/// Helper function that returns the glsl dimension string of [`ImageDimension`](crate::ImageDimension) fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { match dim { crate::ImageDimension::D1 => "1D", @@ -1432,6 +1626,7 @@ fn glsl_dimension(dim: crate::ImageDimension) -> &'static str { } } +/// Helper function that returns the glsl storage format string of [`StorageFormat`](crate::StorageFormat) fn glsl_storage_format(format: StorageFormat) -> &'static str { match format { StorageFormat::R8Unorm => "r8", @@ -1469,15 +1664,27 @@ fn glsl_storage_format(format: StorageFormat) -> &'static str { } } +/// [`Visitor`](crate::proc::Visitor) used by [`collect_texture_mapping`](Writer::collect_texture_mapping) struct TextureMappingVisitor<'a> { + /// Names needed by the module (used to get the image name) names: &'a FastHashMap, + /// [`Arena`](crate::arena::Arena) of expressions of the function being currently visited expressions: &'a Arena, + /// The mapping that we are building map: &'a mut FastHashMap, + /// Used to return errors since [`visit_expr`](crate::proc::Visitor::visit_expr) doesn't allow + /// us to return anything error: Option, } impl<'a> Visitor for TextureMappingVisitor<'a> { fn visit_expr(&mut self, expr: &crate::Expression) { + // We only care about `ImageSample` and `ImageLoad` + // + // Both `image` and `sampler` are `Expression::GlobalVariable` otherwise the module is + // invalid so it's okay to panic + // + // A image can only be used with one sampler or with none at all match expr { Expression::ImageSample { image, sampler, .. } => { let tex_handle = match self.expressions[*image] {