From 488e7cd8d02bf9e1d37b3e76fad6cb5ced6d8878 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Tue, 30 Mar 2021 08:38:33 -0700 Subject: Add a streamlined syntax for TEST_INPUT lines (#1768) This change allows the `TEST_INPUT` syntax used by `render-test` to support aggregate values with a single input line more easily. The test writer can now use a syntax like: ``` //TEST_INPUT:set someVar = 3.0 ``` Input lines that start with the `set` keyword will now use a simpler `dst = src` format (instead of `dst:name=src` as the existing syntax used). The right-hand side expression can include: * Numeric literals, both integer and floating-point (currently only supporting 32-bit scalar types; we could fix this later) * Arrays, consisting of zero or more comma-separated expressions inside `[]` * Aggregates, consisting of zero or more comma-separated "fields" inside `{}`. A field can either be `name: ` or just `` * Objects, which can be written as either `new SomeType{ }` or `new{ }` in the case where the type is know-able from context With this approach is should be possible to support almost arbitrary-type inputs on a single line. For now, I have used this support to re-enable an existing test that had been disabled due to lack of support for setting up arrays of objects. Major things left to do: * The new syntax doesn't support the existing cases we had for `Texture2D`, etc. Those should probably be supported but I'd like to find a way to do it without duplicating the parsing logic (ideally the value cases from the existing code should Just Work in the new model) * There is no support right now for non-32-bit scalar types * It would be good if this support (and the shader cursor system) supported treating vectors like aggregates * The actual value-setting logic doesn't currently handle aggregates without field names, so `{ a:0, b:1 }` will work but `{ 0, 1 }` will parse but fail when it comes time to set values * While this approach lets complicated values be set with a single line, that isn't always what a user will want to do: in the future we should provide a way to break up an aggregate value over multiple lines that is consistent with this approach * Once we port all of the relvant tests over, it would be great to drop the `set` prefix and have these lines look as simple and conventional as possible --- tools/render-test/render-test-main.cpp | 14 ++ tools/render-test/shader-input-layout.cpp | 232 ++++++++++++++++++++++++------ 2 files changed, 205 insertions(+), 41 deletions(-) (limited to 'tools') diff --git a/tools/render-test/render-test-main.cpp b/tools/render-test/render-test-main.cpp index 325f12e49..87d9a246d 100644 --- a/tools/render-test/render-test-main.cpp +++ b/tools/render-test/render-test-main.cpp @@ -334,6 +334,17 @@ struct AssignValsFromLayoutContext return SLANG_OK; } + SlangResult assignArray(ShaderCursor const& dstCursor, ShaderInputLayout::ArrayVal* srcVal) + { + Index elementCounter = 0; + for(auto elementVal : srcVal->vals) + { + Index elementIndex = elementCounter++; + SLANG_RETURN_ON_FAIL(assign(dstCursor[elementIndex], elementVal)); + } + return SLANG_OK; + } + SlangResult assign(ShaderCursor const& dstCursor, ShaderInputLayout::ValPtr const& srcVal) { auto& entryCursor = dstCursor; @@ -360,6 +371,9 @@ struct AssignValsFromLayoutContext case ShaderInputType::Aggregate: return assignAggregate(dstCursor, (ShaderInputLayout::AggVal*) srcVal.Ptr()); + case ShaderInputType::Array: + return assignArray(dstCursor, (ShaderInputLayout::ArrayVal*) srcVal.Ptr()); + default: assert(!"Unhandled type"); return SLANG_FAIL; diff --git a/tools/render-test/shader-input-layout.cpp b/tools/render-test/shader-input-layout.cpp index 5783c6eb8..c5f1cb6dd 100644 --- a/tools/render-test/shader-input-layout.cpp +++ b/tools/render-test/shader-input-layout.cpp @@ -381,6 +381,142 @@ namespace renderer_test } } + RefPtr parseNumericValExpr(TokenReader& parser, bool negate = false) + { + switch(parser.NextToken().Type) + { + case TokenType::IntLiteral: + { + RefPtr val = new ShaderInputLayout::DataVal; + + uint32_t value = parser.ReadUInt(); + if(negate) value = uint32_t(-int32_t(value)); + val->bufferData.add(value); + + return val; + } + break; + + case TokenType::DoubleLiteral: + { + RefPtr val = new ShaderInputLayout::DataVal; + + float floatValue = parser.ReadFloat(); + if(negate) floatValue = -floatValue; + + uint32_t value = 0; + memcpy(&value, &floatValue, sizeof(floatValue)); + val->bufferData.add(value); + + return val; + } + break; + + default: + throw ShaderInputLayoutFormatException(String("Expected a numeric literal but found '") + parser.NextToken().Content + String("' at line") + String(parser.NextToken().Position.Line)); + } + } + + String parseTypeName(TokenReader& parser) + { + return parser.ReadWord(); + } + + RefPtr parseValExpr(TokenReader& parser) + { + switch(parser.NextToken().Type) + { + case TokenType::OpSub: + { + parser.ReadToken(); + return parseNumericValExpr(parser, true); + } + break; + + case TokenType::IntLiteral: + case TokenType::DoubleLiteral: + return parseNumericValExpr(parser); + + case TokenType::LBrace: + { + // aggregate + parser.ReadToken(); + RefPtr val = new ShaderInputLayout::AggVal; + + while( !parser.IsEnd() && !parser.LookAhead(TokenType::RBrace) ) + { + ShaderInputLayout::Field field; + + if( parser.LookAhead(TokenType::Identifier) && parser.NextToken(1).Type == TokenType::Colon ) + { + field.name = parser.ReadWord(); + parser.Read(TokenType::Colon); + } + + field.val = parseValExpr(parser); + + val->fields.add(field); + + if(parser.LookAhead(TokenType::RBrace)) + break; + + parser.Read(TokenType::Comma); + } + parser.Read(TokenType::RBrace); + + + return val; + } + break; + + case TokenType::LBracket: + { + // array + parser.ReadToken(); + RefPtr val = new ShaderInputLayout::ArrayVal; + + while( !parser.IsEnd() && !parser.LookAhead(TokenType::RBracket) ) + { + val->vals.add(parseValExpr(parser)); + + if(parser.LookAhead(TokenType::RBracket)) + break; + + parser.Read(TokenType::Comma); + } + parser.Read(TokenType::RBracket); + + return val; + } + break; + + case TokenType::Identifier: + { + if( parser.AdvanceIf("new") ) + { + RefPtr val = new ShaderInputLayout::ObjectVal; + + if( parser.NextToken().Type == TokenType::Identifier ) + { + val->typeName = parseTypeName(parser); + } + + val->contentVal = parseValExpr(parser); + return val; + } + else + { + // TODO: other named cases + throw ShaderInputLayoutFormatException(String("Unexpected '") + parser.NextToken().Content + String("' at line") + String(parser.NextToken().Position.Line)); + } + } + break; + + default: + throw ShaderInputLayoutFormatException(String("Unexpected '") + parser.NextToken().Content + String("' at line") + String(parser.NextToken().Position.Line)); + } + } + RefPtr parseVal(TokenReader& parser) { auto word = parser.NextToken().Content; @@ -507,6 +643,44 @@ namespace renderer_test parser.ReadToken(); } + String parseName(TokenReader& parser) + { + StringBuilder builder; + + Token nameToken = parser.ReadToken(); + if (nameToken.Type != TokenType::Identifier) + { + throw ShaderInputLayoutFormatException(StringBuilder() << "Invalid input syntax at line " << parser.NextToken().Position.Line); + } + builder << nameToken.Content; + + for(;;) + { + Token token = parser.NextToken(0); + + if (token.Type == TokenType::LBracket) + { + parser.ReadToken(); + int index = parser.ReadInt(); + SLANG_ASSERT(index >= 0); + parser.ReadMatchingToken(TokenType::RBracket); + + builder << "[" << index << "]"; + } + else if (token.Type == TokenType::Dot) + { + parser.ReadToken(); + Token identifierToken = parser.ReadMatchingToken(TokenType::Identifier); + + builder << "." << identifierToken.Content; + } + else + { + return builder; + } + } + } + void parseFieldBindings(TokenReader& parser, ShaderInputLayout::Field& ioField) { // parse bindings @@ -527,47 +701,7 @@ namespace renderer_test parser.ReadToken(); } - StringBuilder builder; - - Token nameToken = parser.ReadToken(); - if (nameToken.Type != TokenType::Identifier) - { - throw ShaderInputLayoutFormatException(StringBuilder() << "Invalid input syntax at line " << parser.NextToken().Position.Line); - } - builder << nameToken.Content; - - while (!parser.IsEnd()) - { - Token token = parser.NextToken(0); - - if (token.Type == TokenType::LBracket) - { - parser.ReadToken(); - int index = parser.ReadInt(); - SLANG_ASSERT(index >= 0); - parser.ReadMatchingToken(TokenType::RBracket); - - builder << "[" << index << "]"; - } - else if (token.Type == TokenType::Dot) - { - parser.ReadToken(); - Token identifierToken = parser.ReadMatchingToken(TokenType::Identifier); - - builder << "." << identifierToken.Content; - } - else if (token.Type == TokenType::Comma) - { - // Break out - break; - } - else - { - throw ShaderInputLayoutFormatException(StringBuilder() << "Invalid input syntax at line " << parser.NextToken().Position.Line); - } - } - - ioField.name = builder; + ioField.name = parseName(parser); } else { @@ -598,6 +732,18 @@ namespace renderer_test parentForNewVal->addField(field); } + void parseSetEntry(TokenReader& parser) + { + auto parentForNewVal = parentVal; + + ShaderInputLayout::Field field; + field.name = parseName(parser); + parser.Read(TokenType::OpAssign); + field.val = parseValExpr(parser); + + parentForNewVal->addField(field); + } + void parseLine(TokenReader& parser) { if (parser.LookAhead("entryPointSpecializationArg") @@ -629,6 +775,10 @@ namespace renderer_test parentVal = parentValStack.getLast(); parentValStack.removeLast(); } + else if( parser.AdvanceIf("set") ) + { + parseSetEntry(parser); + } else { parseValEntry(parser); -- cgit v1.2.3