use std::{borrow::Cow, fmt}; use super::{builtins::MacroCall, context::ExprPos, Span}; use crate::{ AddressSpace, BinaryOperator, Binding, Constant, Expression, Function, GlobalVariable, Handle, Interpolation, Literal, Sampling, StorageAccess, Type, UnaryOperator, }; #[derive(Debug, Clone, Copy)] pub enum GlobalLookupKind { Variable(Handle), Constant(Handle, Handle), BlockSelect(Handle, u32), } #[derive(Debug, Clone, Copy)] pub struct GlobalLookup { pub kind: GlobalLookupKind, pub entry_arg: Option, pub mutable: bool, } #[derive(Debug, Clone)] pub struct ParameterInfo { pub qualifier: ParameterQualifier, /// Whether the parameter should be treated as a depth image instead of a /// sampled image. pub depth: bool, } /// How the function is implemented #[derive(Clone, Copy)] pub enum FunctionKind { /// The function is user defined Call(Handle), /// The function is a builtin Macro(MacroCall), } impl fmt::Debug for FunctionKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { Self::Call(_) => write!(f, "Call"), Self::Macro(_) => write!(f, "Macro"), } } } #[derive(Debug)] pub struct Overload { /// Normalized function parameters, modifiers are not applied pub parameters: Vec>, pub parameters_info: Vec, /// How the function is implemented pub kind: FunctionKind, /// Whether this function was already defined or is just a prototype pub defined: bool, /// Whether this overload is the one provided by the language or has /// been redeclared by the user (builtins only) pub internal: bool, /// Whether or not this function returns void (nothing) pub void: bool, } bitflags::bitflags! { /// Tracks the variations of the builtin already generated, this is needed because some /// builtins overloads can't be generated unless explicitly used, since they might cause /// unneeded capabilities to be requested #[derive(Default)] #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct BuiltinVariations: u32 { /// Request the standard overloads const STANDARD = 1 << 0; /// Request overloads that use the double type const DOUBLE = 1 << 1; /// Request overloads that use samplerCubeArray(Shadow) const CUBE_TEXTURES_ARRAY = 1 << 2; /// Request overloads that use sampler2DMSArray const D2_MULTI_TEXTURES_ARRAY = 1 << 3; } } #[derive(Debug, Default)] pub struct FunctionDeclaration { pub overloads: Vec, /// Tracks the builtin overload variations that were already generated pub variations: BuiltinVariations, } #[derive(Debug)] pub struct EntryArg { pub name: Option, pub binding: Binding, pub handle: Handle, pub storage: StorageQualifier, } #[derive(Debug, Clone)] pub struct VariableReference { pub expr: Handle, /// Whether the variable is of a pointer type (and needs loading) or not pub load: bool, /// Whether the value of the variable can be changed or not pub mutable: bool, pub constant: Option<(Handle, Handle)>, pub entry_arg: Option, } #[derive(Debug, Clone)] pub struct HirExpr { pub kind: HirExprKind, pub meta: Span, } #[derive(Debug, Clone)] pub enum HirExprKind { Access { base: Handle, index: Handle, }, Select { base: Handle, field: String, }, Literal(Literal), Binary { left: Handle, op: BinaryOperator, right: Handle, }, Unary { op: UnaryOperator, expr: Handle, }, Variable(VariableReference), Call(FunctionCall), /// Represents the ternary operator in glsl (`:?`) Conditional { /// The expression that will decide which branch to take, must evaluate to a boolean condition: Handle, /// The expression that will be evaluated if [`condition`] returns `true` /// /// [`condition`]: Self::Conditional::condition accept: Handle, /// The expression that will be evaluated if [`condition`] returns `false` /// /// [`condition`]: Self::Conditional::condition reject: Handle, }, Assign { tgt: Handle, value: Handle, }, /// A prefix/postfix operator like `++` PrePostfix { /// The operation to be performed op: BinaryOperator, /// Whether this is a postfix or a prefix postfix: bool, /// The target expression expr: Handle, }, /// A method call like `what.something(a, b, c)` Method { /// expression the method call applies to (`what` in the example) expr: Handle, /// the method name (`something` in the example) name: String, /// the arguments to the method (`a`, `b`, and `c` in the example) args: Vec>, }, } #[derive(Debug, Hash, PartialEq, Eq)] pub enum QualifierKey<'a> { String(Cow<'a, str>), /// Used for `std140` and `std430` layout qualifiers Layout, /// Used for image formats Format, } #[derive(Debug)] pub enum QualifierValue { None, Uint(u32), Layout(StructLayout), Format(crate::StorageFormat), } #[derive(Debug, Default)] pub struct TypeQualifiers<'a> { pub span: Span, pub storage: (StorageQualifier, Span), pub invariant: Option, pub interpolation: Option<(Interpolation, Span)>, pub precision: Option<(Precision, Span)>, pub sampling: Option<(Sampling, Span)>, /// Memory qualifiers used in the declaration to set the storage access to be used /// in declarations that support it (storage images and buffers) pub storage_access: Option<(StorageAccess, Span)>, pub layout_qualifiers: crate::FastHashMap, (QualifierValue, Span)>, } impl<'a> TypeQualifiers<'a> { /// Appends `errors` with errors for all unused qualifiers pub fn unused_errors(&self, errors: &mut Vec) { if let Some(meta) = self.invariant { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Invariant qualifier can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.interpolation { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Interpolation qualifiers can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.sampling { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Sampling qualifiers can only be used in in/out variables".into(), ), meta, }); } if let Some((_, meta)) = self.storage_access { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Memory qualifiers can only be used in storage variables".into(), ), meta, }); } for &(_, meta) in self.layout_qualifiers.values() { errors.push(super::Error { kind: super::ErrorKind::SemanticError("Unexpected qualifier".into()), meta, }); } } /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't /// a [`QualifierValue::Uint`] pub fn uint_layout_qualifier( &mut self, name: &'a str, errors: &mut Vec, ) -> Option { match self .layout_qualifiers .remove(&QualifierKey::String(name.into())) { Some((QualifierValue::Uint(v), _)) => Some(v), Some((_, meta)) => { errors.push(super::Error { kind: super::ErrorKind::SemanticError("Qualifier expects a uint value".into()), meta, }); // Return a dummy value instead of `None` to differentiate from // the qualifier not existing, since some parts might require the // qualifier to exist and throwing another error that it doesn't // exist would be unhelpful Some(0) } _ => None, } } /// Removes the layout qualifier with `name`, if it exists and adds an error if it isn't /// a [`QualifierValue::None`] pub fn none_layout_qualifier(&mut self, name: &'a str, errors: &mut Vec) -> bool { match self .layout_qualifiers .remove(&QualifierKey::String(name.into())) { Some((QualifierValue::None, _)) => true, Some((_, meta)) => { errors.push(super::Error { kind: super::ErrorKind::SemanticError( "Qualifier doesn't expect a value".into(), ), meta, }); // Return a `true` to since the qualifier is defined and adding // another error for it not being defined would be unhelpful true } _ => false, } } } #[derive(Debug, Clone)] pub enum FunctionCallKind { TypeConstructor(Handle), Function(String), } #[derive(Debug, Clone)] pub struct FunctionCall { pub kind: FunctionCallKind, pub args: Vec>, } #[derive(Debug, Clone, Copy, PartialEq)] pub enum StorageQualifier { AddressSpace(AddressSpace), Input, Output, Const, } impl Default for StorageQualifier { fn default() -> Self { StorageQualifier::AddressSpace(AddressSpace::Function) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum StructLayout { Std140, Std430, } // TODO: Encode precision hints in the IR /// A precision hint used in GLSL declarations. /// /// Precision hints can be used to either speed up shader execution or control /// the precision of arithmetic operations. /// /// To use a precision hint simply add it before the type in the declaration. /// ```glsl /// mediump float a; /// ``` /// /// The default when no precision is declared is `highp` which means that all /// operations operate with the type defined width. /// /// For `mediump` and `lowp` operations follow the spir-v /// [`RelaxedPrecision`][RelaxedPrecision] decoration semantics. /// /// [RelaxedPrecision]: https://www.khronos.org/registry/SPIR-V/specs/unified1/SPIRV.html#_a_id_relaxedprecisionsection_a_relaxed_precision #[derive(Debug, Clone, PartialEq, Copy)] pub enum Precision { /// `lowp` precision Low, /// `mediump` precision Medium, /// `highp` precision High, } #[derive(Debug, Clone, PartialEq, Copy)] pub enum ParameterQualifier { In, Out, InOut, Const, } impl ParameterQualifier { /// Returns true if the argument should be passed as a lhs expression pub const fn is_lhs(&self) -> bool { match *self { ParameterQualifier::Out | ParameterQualifier::InOut => true, _ => false, } } /// Converts from a parameter qualifier into a [`ExprPos`] pub const fn as_pos(&self) -> ExprPos { match *self { ParameterQualifier::Out | ParameterQualifier::InOut => ExprPos::Lhs, _ => ExprPos::Rhs, } } } /// The GLSL profile used by a shader. #[derive(Debug, Clone, Copy, PartialEq)] pub enum Profile { /// The `core` profile, default when no profile is specified. Core, }