diff --git a/src/front/glsl/ast.rs b/src/front/glsl/ast.rs index c31d1f9721..88f002d770 100644 --- a/src/front/glsl/ast.rs +++ b/src/front/glsl/ast.rs @@ -301,10 +301,12 @@ impl ExpressionRule { #[derive(Debug)] pub enum TypeQualifier { StorageQualifier(StorageQualifier), + Interpolation(Interpolation), ResourceBinding(ResourceBinding), Binding(Binding), - Interpolation(Interpolation), Sampling(Sampling), + Layout(StructLayout), + EarlyFragmentTests, } #[derive(Debug)] @@ -337,7 +339,5 @@ pub enum StorageQualifier { #[derive(Debug, Clone)] pub enum StructLayout { - Binding(Binding), - Resource(ResourceBinding), - PushConstant, + Std140, } diff --git a/src/front/glsl/error.rs b/src/front/glsl/error.rs index 87d00075b1..154556d317 100644 --- a/src/front/glsl/error.rs +++ b/src/front/glsl/error.rs @@ -15,6 +15,7 @@ pub enum ErrorKind { NotImplemented(&'static str), UnknownVariable(TokenMetadata, String), UnknownField(TokenMetadata, String), + UnknownLayoutQualifier(TokenMetadata, String), #[cfg(feature = "glsl-validate")] VariableAlreadyDeclared(String), ExpectedConstant, @@ -45,6 +46,9 @@ impl fmt::Display for ErrorKind { ErrorKind::UnknownField(ref meta, ref val) => { write!(f, "Unknown field {} at {:?}", val, meta) } + ErrorKind::UnknownLayoutQualifier(ref meta, ref val) => { + write!(f, "Unknown layout qualifier name {} at {:?}", val, meta) + } #[cfg(feature = "glsl-validate")] ErrorKind::VariableAlreadyDeclared(ref val) => { write!(f, "Variable {} already declared in current scope", val) diff --git a/src/front/glsl/parser.rs b/src/front/glsl/parser.rs index c80667eb4b..2e587fe55b 100644 --- a/src/front/glsl/parser.rs +++ b/src/front/glsl/parser.rs @@ -1,12 +1,16 @@ use super::{ - ast::{FunctionContext, Profile, StorageQualifier, TypeQualifier}, + ast::{FunctionContext, Profile, StorageQualifier, StructLayout, TypeQualifier}, error::ErrorKind, lex::Lexer, token::{Token, TokenMetadata, TokenValue}, Program, }; -use crate::{arena::Handle, ArraySize, Function, FunctionResult, StorageClass, Type, TypeInner}; -use std::iter::Peekable; +use crate::{ + arena::Handle, ArraySize, Binding, Function, FunctionResult, ResourceBinding, StorageClass, + Type, TypeInner, +}; +use core::convert::TryFrom; +use core::iter::Peekable; type Result = std::result::Result; @@ -46,6 +50,15 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { self.lexer.next().ok_or(ErrorKind::EndOfFile) } + /// Returns None on the end of the file rather than an error like other methods + fn bump_if(&mut self, value: TokenValue) -> Option { + if self.lexer.peek().filter(|t| t.value == value).is_some() { + self.bump().ok() + } else { + None + } + } + fn expect_peek(&mut self) -> Result<&Token> { self.lexer.peek().ok_or(ErrorKind::EndOfFile) } @@ -146,7 +159,8 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { | TokenValue::In | TokenValue::Out | TokenValue::InOut - | TokenValue::Uniform => true, + | TokenValue::Uniform + | TokenValue::Layout => true, _ => false, }) } @@ -154,8 +168,15 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { fn parse_type_qualifiers(&mut self) -> Result> { let mut qualifiers = Vec::new(); - if self.peek_type_qualifier() { + while self.peek_type_qualifier() { let token = self.bump()?; + + // Handle layout qualifiers outside the match since this can push multiple values + if token.value == TokenValue::Layout { + self.parse_layout_qualifier_id_list(&mut qualifiers)?; + continue; + } + qualifiers.push(match token.value { TokenValue::Interpolation(i) => TypeQualifier::Interpolation(i), TokenValue::Const => TypeQualifier::StorageQualifier(StorageQualifier::Const), @@ -165,6 +186,7 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { TokenValue::Uniform => TypeQualifier::StorageQualifier( StorageQualifier::StorageClass(StorageClass::Uniform), ), + _ => unreachable!(), }) } @@ -172,6 +194,105 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { Ok(qualifiers) } + fn parse_layout_qualifier_id_list( + &mut self, + qualifiers: &mut Vec, + ) -> Result<()> { + // We need both of these to produce a ResourceBinding + let mut group = None; + let mut binding = None; + + self.expect(TokenValue::LeftParen)?; + loop { + self.parse_layout_qualifier_id(qualifiers, &mut group, &mut binding)?; + + if self.bump_if(TokenValue::Comma).is_some() { + continue; + } + + break; + } + self.expect(TokenValue::RightParen)?; + + match (group, binding) { + (Some(group), Some(binding)) => { + qualifiers.push(TypeQualifier::ResourceBinding(ResourceBinding { + group, + binding, + })) + } + // Produce an error if we have one of group or binding but not the other + // TODO: include at least line info in errors? + (Some(_), None) => { + return Err(ErrorKind::SemanticError( + "set specified with no binding".into(), + )) + } + (None, Some(_)) => { + return Err(ErrorKind::SemanticError( + "binding specified with no set".into(), + )) + } + (None, None) => (), + } + + Ok(()) + } + + fn parse_layout_qualifier_id( + &mut self, + qualifiers: &mut Vec, + group: &mut Option, + binding: &mut Option, + ) -> Result<()> { + // layout_qualifier_id: + // IDENTIFIER + // IDENTIFIER EQUAL constant_expression + // SHARED + let token = self.bump()?; + match token.value { + TokenValue::Identifier(name) => { + if self.bump_if(TokenValue::Assign).is_some() { + let value = self.parse_constant_expression()?; + + match name.as_str() { + "location" => qualifiers.push(TypeQualifier::Binding(Binding::Location { + // TODO: use better error here + location: u32::try_from(value).map_err(|_| ErrorKind::InvalidInput)?, + interpolation: None, + sampling: None, + })), + // TODO: produce error when try_from fails + "set" => *group = u32::try_from(value).ok(), + "binding" => *binding = u32::try_from(value).ok(), + _ => return Err(ErrorKind::UnknownLayoutQualifier(token.meta, name)), + } + } else { + match name.as_str() { + "std140" => qualifiers.push(TypeQualifier::Layout(StructLayout::Std140)), + "early_fragment_tests" => { + qualifiers.push(TypeQualifier::EarlyFragmentTests) + } + _ => return Err(ErrorKind::UnknownLayoutQualifier(token.meta, name)), + } + }; + + Ok(()) + } + // TODO: handle Shared? + _ => Err(ErrorKind::InvalidToken(token)), + } + } + + // TODO: parse more than just integer literal + fn parse_constant_expression(&mut self) -> Result { + let token = self.bump()?; + match token.value { + TokenValue::IntConstant(value) => Ok(value), + _ => Err(ErrorKind::InvalidToken(token)), + } + } + fn parse_external_declaration(&mut self) -> Result<()> { if !self.parse_declaration(true)? { let token = self.bump()?; @@ -182,6 +303,7 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { } else { Ok(()) } + // TODO } fn peek_type_name(&mut self) -> bool { @@ -191,8 +313,17 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { }) } - /// `external` wheter or not we are in a global or local context + /// `external` whether or not we are in a global or local context fn parse_declaration(&mut self, external: bool) -> Result { + //declaration: + // function_prototype SEMICOLON + // init_declarator_list SEMICOLON + // PRECISION precision_qualifier type_specifier SEMICOLON + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE SEMICOLON + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER SEMICOLON + // type_qualifier IDENTIFIER LEFT_BRACE struct_declaration_list RIGHT_BRACE IDENTIFIER array_specifier SEMICOLON + // type_qualifier SEMICOLON type_qualifier IDENTIFIER SEMICOLON + // type_qualifier IDENTIFIER identifier_list SEMICOLON // TODO: Handle precision qualifiers if self.peek_type_qualifier() || self.peek_type_name() { // TODO: Use qualifiers @@ -236,7 +367,12 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { } } // Variable Declaration - TokenValue::Semicolon => todo!(), + TokenValue::Semicolon => { + // TODO: are we supposed to consume the semicolon here + self.bump()?; + // TODO: use parsed type & qualifiers + Ok(true) + } TokenValue::Comma => todo!(), _ => Err(ErrorKind::InvalidToken(self.bump()?)), }, @@ -276,8 +412,7 @@ impl<'source, 'program, 'options> Parser<'source, 'program, 'options> { context.add_function_arg(Some(name), ty); - if self.expect_peek()?.value == TokenValue::Comma { - self.bump()?; + if self.bump_if(TokenValue::Comma).is_some() { continue; } diff --git a/src/front/glsl/parser_tests.rs b/src/front/glsl/parser_tests.rs index a81edf8df8..42ee511a67 100644 --- a/src/front/glsl/parser_tests.rs +++ b/src/front/glsl/parser_tests.rs @@ -168,6 +168,41 @@ fn control_flow() { // .unwrap(); } +#[test] +fn declarations() { + let mut entry_points = crate::FastHashMap::default(); + entry_points.insert("".to_string(), ShaderStage::Fragment); + + let _program = parse_program( + r#" + #version 450 + layout(location = 0) in vec2 v_uv; + layout(location = 0) out vec4 o_color; + layout(set = 1, binding = 1) uniform texture2D tex; + layout(set = 1, binding = 2) uniform sampler tex_sampler; + + layout(early_fragment_tests) in; + "#, + &entry_points, + ) + .unwrap(); + + // TODO: Reenable tests later + // let _program = parse_program( + // r#" + // #version 450 + // layout(std140, set = 2, binding = 0) + // uniform u_locals { + // vec3 model_offs; + // float load_time; + // ivec4 atlas_offs; + // }; + // "#, + // &entry_points, + // ) + // .unwrap(); +} + #[test] fn textures() { // TODO: Reenable tests later