/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES Utilities * ------------------------------------------------ * * Copyright 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * *//*! * \file * \brief Shader .test file utilities. *//*--------------------------------------------------------------------*/ #include "gluShaderLibrary.hpp" #include "tcuStringTemplate.hpp" #include "tcuResource.hpp" #include "tcuTestLog.hpp" #include "deStringUtil.hpp" #include "deUniquePtr.hpp" #include "deFilePath.hpp" #include "glwEnums.hpp" #include <sstream> #include <map> #include <cstdlib> #if 0 # define PARSE_DBG(X) printf X #else # define PARSE_DBG(X) DE_NULL_STATEMENT #endif namespace glu { namespace sl { using namespace tcu; using std::vector; using std::string; using std::map; using std::ostringstream; using std::pair; using de::UniquePtr; // Specification bool isValid (const ValueBlock& block) { for (size_t storageNdx = 0; storageNdx < 3; ++storageNdx) { const vector<Value>& values = storageNdx == 0 ? block.inputs : storageNdx == 1 ? block.outputs : block.uniforms; const size_t refArrayLen = values.empty() ? 0 : (values[0].elements.size() / (size_t)values[0].type.getScalarSize()); for (size_t valNdx = 0; valNdx < values.size(); ++valNdx) { const Value& value = values[valNdx]; if (!value.type.isBasicType()) { print("ERROR: Value '%s' is of unsupported type!\n", value.name.c_str()); return false; } if (value.elements.size() != refArrayLen*(size_t)value.type.getScalarSize()) { print("ERROR: Value '%s' has invalid number of scalars!\n", value.name.c_str()); return false; } } } return true; } bool isValid (const ShaderCaseSpecification& spec) { const deUint32 vtxFragMask = (1u << SHADERTYPE_VERTEX) | (1u << SHADERTYPE_FRAGMENT); const deUint32 tessCtrlEvalMask = (1u << SHADERTYPE_TESSELLATION_CONTROL) | (1u << SHADERTYPE_TESSELLATION_EVALUATION); const deUint32 supportedStageMask = vtxFragMask | tessCtrlEvalMask | (1u << SHADERTYPE_GEOMETRY); const bool isSeparable = !spec.programs.empty() && spec.programs[0].sources.separable; if (spec.programs.empty()) { print("ERROR: No programs specified!\n"); return false; } if (spec.fullGLSLES100Required) { if (spec.targetVersion != GLSL_VERSION_100_ES) { print("ERROR: Full GLSL ES 1.00 support requested for other GLSL version!\n"); return false; } if (spec.expectResult != EXPECT_PASS && spec.expectResult != EXPECT_VALIDATION_FAIL && spec.expectResult != EXPECT_BUILD_SUCCESSFUL) { print("ERROR: Full GLSL ES 1.00 support doesn't make sense when expecting compile/link failure!\n"); return false; } } if (!de::inBounds(spec.caseType, (CaseType)0, CASETYPE_LAST)) { print("ERROR: Invalid case type!\n"); return false; } if (!de::inBounds(spec.expectResult, (ExpectResult)0, EXPECT_LAST)) { print("ERROR: Invalid expected result!\n"); return false; } if (!isValid(spec.values)) return false; if (!spec.values.inputs.empty() && !spec.values.outputs.empty() && spec.values.inputs[0].elements.size() / spec.values.inputs[0].type.getScalarSize() != spec.values.outputs[0].elements.size() / spec.values.outputs[0].type.getScalarSize()) { print("ERROR: Number of input and output elements don't match!\n"); return false; } if (isSeparable) { deUint32 usedStageMask = 0u; if (spec.caseType != CASETYPE_COMPLETE) { print("ERROR: Separable shaders supported only for complete cases!\n"); return false; } for (size_t progNdx = 0; progNdx < spec.programs.size(); ++progNdx) { for (int shaderStageNdx = 0; shaderStageNdx < SHADERTYPE_LAST; ++shaderStageNdx) { const deUint32 curStageMask = (1u << shaderStageNdx); if (supportedStageMask & curStageMask) { const bool hasShader = !spec.programs[progNdx].sources.sources[shaderStageNdx].empty(); const bool isEnabled = (spec.programs[progNdx].activeStages & curStageMask) != 0; if (hasShader != isEnabled) { print("ERROR: Inconsistent source/enable for shader stage %s!\n", getShaderTypeName((ShaderType)shaderStageNdx)); return false; } if (hasShader && (usedStageMask & curStageMask) != 0) { print("ERROR: Stage %s enabled on multiple programs!\n", getShaderTypeName((ShaderType)shaderStageNdx)); return false; } if (isEnabled) usedStageMask |= curStageMask; } else if (!spec.programs[progNdx].sources.sources[shaderStageNdx].empty()) { print("ERROR: Source specified for unsupported shader stage %s!\n", getShaderTypeName((ShaderType)shaderStageNdx)); return false; } } } if ((usedStageMask & vtxFragMask) != vtxFragMask) { print("ERROR: Vertex and fragment shaders are mandatory!\n"); return false; } if ((usedStageMask & tessCtrlEvalMask) != 0 && (usedStageMask & tessCtrlEvalMask) != tessCtrlEvalMask) { print("ERROR: Both tessellation control and eval shaders must be either enabled or disabled!\n"); return false; } } else { const bool hasVertex = !spec.programs[0].sources.sources[SHADERTYPE_VERTEX].empty(); const bool hasFragment = !spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].empty(); if (spec.programs.size() != 1) { print("ERROR: Only cases using separable programs can have multiple programs!\n"); return false; } if (spec.caseType == CASETYPE_VERTEX_ONLY && (!hasVertex || hasFragment)) { print("ERROR: Vertex-only case must have only vertex shader!\n"); return false; } if (spec.caseType == CASETYPE_FRAGMENT_ONLY && (hasVertex || !hasFragment)) { print("ERROR: Fragment-only case must have only fragment shader!\n"); return false; } if (spec.caseType == CASETYPE_COMPLETE && (!hasVertex || !hasFragment)) { print("ERROR: Complete case must have at least vertex and fragment shaders\n"); return false; } } return true; } // Parser static const glu::GLSLVersion DEFAULT_GLSL_VERSION = glu::GLSL_VERSION_100_ES; DE_INLINE deBool isWhitespace (char c) { return (c == ' ') || (c == '\t') || (c == '\r') || (c == '\n'); } DE_INLINE deBool isEOL (char c) { return (c == '\r') || (c == '\n'); } DE_INLINE deBool isNumeric (char c) { return deInRange32(c, '0', '9'); } DE_INLINE deBool isAlpha (char c) { return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z'); } DE_INLINE deBool isCaseNameChar (char c) { return deInRange32(c, 'a', 'z') || deInRange32(c, 'A', 'Z') || deInRange32(c, '0', '9') || (c == '_') || (c == '-') || (c == '.'); } struct CaseRequirement { enum Type { TYPE_EXTENSION = 0, TYPE_FULL_GLSL_ES_100_SUPPORT, TYPE_IMPLEMENTATION_LIMIT, TYPE_LAST }; Type type; // TYPE_EXTENSION: RequiredExtension extension; // TYPE_IMPLEMENTATION_LIMIT RequiredCapability requiredCap; CaseRequirement (void) : type(TYPE_LAST) {} static CaseRequirement createFullGLSLES100SpecificationRequirement (void) { CaseRequirement req; req.type = TYPE_FULL_GLSL_ES_100_SUPPORT; return req; } static CaseRequirement createAnyExtensionRequirement (const vector<string>& alternatives, deUint32 effectiveStages) { CaseRequirement req; req.type = TYPE_EXTENSION; req.extension = RequiredExtension(alternatives, effectiveStages); return req; } static CaseRequirement createLimitRequirement (deUint32 enumName, int referenceValue) { CaseRequirement req; req.type = TYPE_IMPLEMENTATION_LIMIT; req.requiredCap = RequiredCapability(enumName, referenceValue); return req; } }; class ShaderParser { public: ShaderParser (const tcu::Archive& archive, const std::string& filename, ShaderCaseFactory* caseFactory); ~ShaderParser (void); vector<tcu::TestNode*> parse (void); private: enum Token { TOKEN_INVALID = 0, TOKEN_EOF, TOKEN_STRING, TOKEN_SHADER_SOURCE, TOKEN_INT_LITERAL, TOKEN_FLOAT_LITERAL, // identifiers TOKEN_IDENTIFIER, TOKEN_TRUE, TOKEN_FALSE, TOKEN_DESC, TOKEN_EXPECT, TOKEN_GROUP, TOKEN_CASE, TOKEN_END, TOKEN_VALUES, TOKEN_BOTH, TOKEN_VERTEX, TOKEN_FRAGMENT, TOKEN_UNIFORM, TOKEN_INPUT, TOKEN_OUTPUT, TOKEN_FLOAT, TOKEN_FLOAT_VEC2, TOKEN_FLOAT_VEC3, TOKEN_FLOAT_VEC4, TOKEN_FLOAT_MAT2, TOKEN_FLOAT_MAT2X3, TOKEN_FLOAT_MAT2X4, TOKEN_FLOAT_MAT3X2, TOKEN_FLOAT_MAT3, TOKEN_FLOAT_MAT3X4, TOKEN_FLOAT_MAT4X2, TOKEN_FLOAT_MAT4X3, TOKEN_FLOAT_MAT4, TOKEN_INT, TOKEN_INT_VEC2, TOKEN_INT_VEC3, TOKEN_INT_VEC4, TOKEN_UINT, TOKEN_UINT_VEC2, TOKEN_UINT_VEC3, TOKEN_UINT_VEC4, TOKEN_BOOL, TOKEN_BOOL_VEC2, TOKEN_BOOL_VEC3, TOKEN_BOOL_VEC4, TOKEN_VERSION, TOKEN_TESSELLATION_CONTROL, TOKEN_TESSELLATION_EVALUATION, TOKEN_GEOMETRY, TOKEN_REQUIRE, TOKEN_IN, TOKEN_IMPORT, TOKEN_PIPELINE_PROGRAM, TOKEN_ACTIVE_STAGES, // symbols TOKEN_ASSIGN, TOKEN_PLUS, TOKEN_MINUS, TOKEN_COMMA, TOKEN_VERTICAL_BAR, TOKEN_SEMI_COLON, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_LEFT_BRACKET, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_BRACE, TOKEN_RIGHT_BRACE, TOKEN_GREATER, TOKEN_LAST }; void parseError (const std::string& errorStr); float parseFloatLiteral (const char* str); int parseIntLiteral (const char* str); string parseStringLiteral (const char* str); string parseShaderSource (const char* str); void advanceToken (void); void advanceToken (Token assumed); void assumeToken (Token token); DataType mapDataTypeToken (Token token); const char* getTokenName (Token token); deUint32 getShaderStageLiteralFlag (void); deUint32 getGLEnumFromName (const std::string& enumName); void parseValueElement (DataType dataType, Value& result); void parseValue (ValueBlock& valueBlock); void parseValueBlock (ValueBlock& valueBlock); deUint32 parseShaderStageList (void); void parseRequirement (CaseRequirement& valueBlock); void parseExpectResult (ExpectResult& expectResult); void parseGLSLVersion (glu::GLSLVersion& version); void parsePipelineProgram (ProgramSpecification& program); void parseShaderCase (vector<tcu::TestNode*>& shaderNodeList); void parseShaderGroup (vector<tcu::TestNode*>& shaderNodeList); void parseImport (vector<tcu::TestNode*>& shaderNodeList); const tcu::Archive& m_archive; const string m_filename; ShaderCaseFactory* const m_caseFactory; UniquePtr<tcu::Resource> m_resource; vector<char> m_input; const char* m_curPtr; Token m_curToken; std::string m_curTokenStr; }; ShaderParser::ShaderParser (const tcu::Archive& archive, const string& filename, ShaderCaseFactory* caseFactroy) : m_archive (archive) , m_filename (filename) , m_caseFactory (caseFactroy) , m_resource (archive.getResource(m_filename.c_str())) , m_curPtr (DE_NULL) , m_curToken (TOKEN_LAST) { } ShaderParser::~ShaderParser (void) { } void ShaderParser::parseError (const std::string& errorStr) { string atStr = string(m_curPtr, 80); throw tcu::InternalError((string("Parser error: ") + errorStr + " near '" + atStr + " ...'").c_str(), DE_NULL, __FILE__, __LINE__); } float ShaderParser::parseFloatLiteral (const char* str) { return (float)atof(str); } int ShaderParser::parseIntLiteral (const char* str) { return atoi(str); } string ShaderParser::parseStringLiteral (const char* str) { const char* p = str; char endChar = *p++; ostringstream o; while (*p != endChar && *p) { if (*p == '\\') { switch (p[1]) { case 0: DE_ASSERT(DE_FALSE); break; case 'n': o << '\n'; break; case 't': o << '\t'; break; default: o << p[1]; break; } p += 2; } else o << *p++; } return o.str(); } static string removeExtraIndentation (const string& source) { // Detect indentation from first line. int numIndentChars = 0; for (int ndx = 0; ndx < (int)source.length() && isWhitespace(source[ndx]); ndx++) numIndentChars += source[ndx] == '\t' ? 4 : 1; // Process all lines and remove preceding indentation. ostringstream processed; { bool atLineStart = true; int indentCharsOmitted = 0; for (int pos = 0; pos < (int)source.length(); pos++) { char c = source[pos]; if (atLineStart && indentCharsOmitted < numIndentChars && (c == ' ' || c == '\t')) { indentCharsOmitted += c == '\t' ? 4 : 1; } else if (isEOL(c)) { if (source[pos] == '\r' && source[pos+1] == '\n') { pos += 1; processed << '\n'; } else processed << c; atLineStart = true; indentCharsOmitted = 0; } else { processed << c; atLineStart = false; } } } return processed.str(); } string ShaderParser::parseShaderSource (const char* str) { const char* p = str+2; ostringstream o; // Eat first empty line from beginning. while (*p == ' ') p++; if (*p == '\r') p++; if (*p == '\n') p++; while ((p[0] != '"') || (p[1] != '"')) { if (*p == '\\') { switch (p[1]) { case 0: DE_ASSERT(DE_FALSE); break; case 'n': o << '\n'; break; case 't': o << '\t'; break; default: o << p[1]; break; } p += 2; } else o << *p++; } return removeExtraIndentation(o.str()); } void ShaderParser::advanceToken (void) { // Skip old token. m_curPtr += m_curTokenStr.length(); // Reset token (for safety). m_curToken = TOKEN_INVALID; m_curTokenStr = ""; // Eat whitespace & comments while they last. for (;;) { while (isWhitespace(*m_curPtr)) m_curPtr++; // Check for EOL comment. if (*m_curPtr == '#') { while (*m_curPtr && !isEOL(*m_curPtr)) m_curPtr++; } else break; } if (!*m_curPtr) { m_curToken = TOKEN_EOF; m_curTokenStr = "<EOF>"; } else if (isAlpha(*m_curPtr)) { struct Named { const char* str; Token token; }; static const Named s_named[] = { { "true", TOKEN_TRUE }, { "false", TOKEN_FALSE }, { "desc", TOKEN_DESC }, { "expect", TOKEN_EXPECT }, { "group", TOKEN_GROUP }, { "case", TOKEN_CASE }, { "end", TOKEN_END }, { "values", TOKEN_VALUES }, { "both", TOKEN_BOTH }, { "vertex", TOKEN_VERTEX }, { "fragment", TOKEN_FRAGMENT }, { "uniform", TOKEN_UNIFORM }, { "input", TOKEN_INPUT }, { "output", TOKEN_OUTPUT }, { "float", TOKEN_FLOAT }, { "vec2", TOKEN_FLOAT_VEC2 }, { "vec3", TOKEN_FLOAT_VEC3 }, { "vec4", TOKEN_FLOAT_VEC4 }, { "mat2", TOKEN_FLOAT_MAT2 }, { "mat2x3", TOKEN_FLOAT_MAT2X3 }, { "mat2x4", TOKEN_FLOAT_MAT2X4 }, { "mat3x2", TOKEN_FLOAT_MAT3X2 }, { "mat3", TOKEN_FLOAT_MAT3 }, { "mat3x4", TOKEN_FLOAT_MAT3X4 }, { "mat4x2", TOKEN_FLOAT_MAT4X2 }, { "mat4x3", TOKEN_FLOAT_MAT4X3 }, { "mat4", TOKEN_FLOAT_MAT4 }, { "int", TOKEN_INT }, { "ivec2", TOKEN_INT_VEC2 }, { "ivec3", TOKEN_INT_VEC3 }, { "ivec4", TOKEN_INT_VEC4 }, { "uint", TOKEN_UINT }, { "uvec2", TOKEN_UINT_VEC2 }, { "uvec3", TOKEN_UINT_VEC3 }, { "uvec4", TOKEN_UINT_VEC4 }, { "bool", TOKEN_BOOL }, { "bvec2", TOKEN_BOOL_VEC2 }, { "bvec3", TOKEN_BOOL_VEC3 }, { "bvec4", TOKEN_BOOL_VEC4 }, { "version", TOKEN_VERSION }, { "tessellation_control", TOKEN_TESSELLATION_CONTROL }, { "tessellation_evaluation", TOKEN_TESSELLATION_EVALUATION }, { "geometry", TOKEN_GEOMETRY }, { "require", TOKEN_REQUIRE }, { "in", TOKEN_IN }, { "import", TOKEN_IMPORT }, { "pipeline_program", TOKEN_PIPELINE_PROGRAM }, { "active_stages", TOKEN_ACTIVE_STAGES }, }; const char* end = m_curPtr + 1; while (isCaseNameChar(*end)) end++; m_curTokenStr = string(m_curPtr, end - m_curPtr); m_curToken = TOKEN_IDENTIFIER; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_named); ndx++) { if (m_curTokenStr == s_named[ndx].str) { m_curToken = s_named[ndx].token; break; } } } else if (isNumeric(*m_curPtr)) { /* \todo [2010-03-31 petri] Hex? */ const char* p = m_curPtr; while (isNumeric(*p)) p++; if (*p == '.') { p++; while (isNumeric(*p)) p++; if (*p == 'e' || *p == 'E') { p++; if (*p == '+' || *p == '-') p++; DE_ASSERT(isNumeric(*p)); while (isNumeric(*p)) p++; } m_curToken = TOKEN_FLOAT_LITERAL; m_curTokenStr = string(m_curPtr, p - m_curPtr); } else { m_curToken = TOKEN_INT_LITERAL; m_curTokenStr = string(m_curPtr, p - m_curPtr); } } else if (*m_curPtr == '"' && m_curPtr[1] == '"') { const char* p = m_curPtr + 2; while ((p[0] != '"') || (p[1] != '"')) { DE_ASSERT(*p); if (*p == '\\') { DE_ASSERT(p[1] != 0); p += 2; } else p++; } p += 2; m_curToken = TOKEN_SHADER_SOURCE; m_curTokenStr = string(m_curPtr, (int)(p - m_curPtr)); } else if (*m_curPtr == '"' || *m_curPtr == '\'') { char endChar = *m_curPtr; const char* p = m_curPtr + 1; while (*p != endChar) { DE_ASSERT(*p); if (*p == '\\') { DE_ASSERT(p[1] != 0); p += 2; } else p++; } p++; m_curToken = TOKEN_STRING; m_curTokenStr = string(m_curPtr, (int)(p - m_curPtr)); } else { struct SimpleToken { const char* str; Token token; }; static const SimpleToken s_simple[] = { { "=", TOKEN_ASSIGN }, { "+", TOKEN_PLUS }, { "-", TOKEN_MINUS }, { ",", TOKEN_COMMA }, { "|", TOKEN_VERTICAL_BAR }, { ";", TOKEN_SEMI_COLON }, { "(", TOKEN_LEFT_PAREN }, { ")", TOKEN_RIGHT_PAREN }, { "[", TOKEN_LEFT_BRACKET }, { "]", TOKEN_RIGHT_BRACKET }, { "{", TOKEN_LEFT_BRACE }, { "}", TOKEN_RIGHT_BRACE }, { ">", TOKEN_GREATER }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(s_simple); ndx++) { if (strncmp(s_simple[ndx].str, m_curPtr, strlen(s_simple[ndx].str)) == 0) { m_curToken = s_simple[ndx].token; m_curTokenStr = s_simple[ndx].str; return; } } // Otherwise invalid token. m_curToken = TOKEN_INVALID; m_curTokenStr = *m_curPtr; } } void ShaderParser::advanceToken (Token assumed) { assumeToken(assumed); advanceToken(); } void ShaderParser::assumeToken (Token token) { if (m_curToken != token) parseError((string("unexpected token '") + m_curTokenStr + "', expecting '" + getTokenName(token) + "'").c_str()); DE_TEST_ASSERT(m_curToken == token); } DataType ShaderParser::mapDataTypeToken (Token token) { switch (token) { case TOKEN_FLOAT: return TYPE_FLOAT; case TOKEN_FLOAT_VEC2: return TYPE_FLOAT_VEC2; case TOKEN_FLOAT_VEC3: return TYPE_FLOAT_VEC3; case TOKEN_FLOAT_VEC4: return TYPE_FLOAT_VEC4; case TOKEN_FLOAT_MAT2: return TYPE_FLOAT_MAT2; case TOKEN_FLOAT_MAT2X3: return TYPE_FLOAT_MAT2X3; case TOKEN_FLOAT_MAT2X4: return TYPE_FLOAT_MAT2X4; case TOKEN_FLOAT_MAT3X2: return TYPE_FLOAT_MAT3X2; case TOKEN_FLOAT_MAT3: return TYPE_FLOAT_MAT3; case TOKEN_FLOAT_MAT3X4: return TYPE_FLOAT_MAT3X4; case TOKEN_FLOAT_MAT4X2: return TYPE_FLOAT_MAT4X2; case TOKEN_FLOAT_MAT4X3: return TYPE_FLOAT_MAT4X3; case TOKEN_FLOAT_MAT4: return TYPE_FLOAT_MAT4; case TOKEN_INT: return TYPE_INT; case TOKEN_INT_VEC2: return TYPE_INT_VEC2; case TOKEN_INT_VEC3: return TYPE_INT_VEC3; case TOKEN_INT_VEC4: return TYPE_INT_VEC4; case TOKEN_UINT: return TYPE_UINT; case TOKEN_UINT_VEC2: return TYPE_UINT_VEC2; case TOKEN_UINT_VEC3: return TYPE_UINT_VEC3; case TOKEN_UINT_VEC4: return TYPE_UINT_VEC4; case TOKEN_BOOL: return TYPE_BOOL; case TOKEN_BOOL_VEC2: return TYPE_BOOL_VEC2; case TOKEN_BOOL_VEC3: return TYPE_BOOL_VEC3; case TOKEN_BOOL_VEC4: return TYPE_BOOL_VEC4; default: return TYPE_INVALID; } } const char* ShaderParser::getTokenName (Token token) { switch (token) { case TOKEN_INVALID: return "<invalid>"; case TOKEN_EOF: return "<eof>"; case TOKEN_STRING: return "<string>"; case TOKEN_SHADER_SOURCE: return "source"; case TOKEN_INT_LITERAL: return "<int>"; case TOKEN_FLOAT_LITERAL: return "<float>"; // identifiers case TOKEN_IDENTIFIER: return "<identifier>"; case TOKEN_TRUE: return "true"; case TOKEN_FALSE: return "false"; case TOKEN_DESC: return "desc"; case TOKEN_EXPECT: return "expect"; case TOKEN_GROUP: return "group"; case TOKEN_CASE: return "case"; case TOKEN_END: return "end"; case TOKEN_VALUES: return "values"; case TOKEN_BOTH: return "both"; case TOKEN_VERTEX: return "vertex"; case TOKEN_FRAGMENT: return "fragment"; case TOKEN_TESSELLATION_CONTROL: return "tessellation_control"; case TOKEN_TESSELLATION_EVALUATION: return "tessellation_evaluation"; case TOKEN_GEOMETRY: return "geometry"; case TOKEN_REQUIRE: return "require"; case TOKEN_UNIFORM: return "uniform"; case TOKEN_INPUT: return "input"; case TOKEN_OUTPUT: return "output"; case TOKEN_FLOAT: return "float"; case TOKEN_FLOAT_VEC2: return "vec2"; case TOKEN_FLOAT_VEC3: return "vec3"; case TOKEN_FLOAT_VEC4: return "vec4"; case TOKEN_FLOAT_MAT2: return "mat2"; case TOKEN_FLOAT_MAT2X3: return "mat2x3"; case TOKEN_FLOAT_MAT2X4: return "mat2x4"; case TOKEN_FLOAT_MAT3X2: return "mat3x2"; case TOKEN_FLOAT_MAT3: return "mat3"; case TOKEN_FLOAT_MAT3X4: return "mat3x4"; case TOKEN_FLOAT_MAT4X2: return "mat4x2"; case TOKEN_FLOAT_MAT4X3: return "mat4x3"; case TOKEN_FLOAT_MAT4: return "mat4"; case TOKEN_INT: return "int"; case TOKEN_INT_VEC2: return "ivec2"; case TOKEN_INT_VEC3: return "ivec3"; case TOKEN_INT_VEC4: return "ivec4"; case TOKEN_UINT: return "uint"; case TOKEN_UINT_VEC2: return "uvec2"; case TOKEN_UINT_VEC3: return "uvec3"; case TOKEN_UINT_VEC4: return "uvec4"; case TOKEN_BOOL: return "bool"; case TOKEN_BOOL_VEC2: return "bvec2"; case TOKEN_BOOL_VEC3: return "bvec3"; case TOKEN_BOOL_VEC4: return "bvec4"; case TOKEN_IN: return "in"; case TOKEN_IMPORT: return "import"; case TOKEN_PIPELINE_PROGRAM: return "pipeline_program"; case TOKEN_ACTIVE_STAGES: return "active_stages"; case TOKEN_ASSIGN: return "="; case TOKEN_PLUS: return "+"; case TOKEN_MINUS: return "-"; case TOKEN_COMMA: return ","; case TOKEN_VERTICAL_BAR: return "|"; case TOKEN_SEMI_COLON: return ";"; case TOKEN_LEFT_PAREN: return "("; case TOKEN_RIGHT_PAREN: return ")"; case TOKEN_LEFT_BRACKET: return "["; case TOKEN_RIGHT_BRACKET: return "]"; case TOKEN_LEFT_BRACE: return "{"; case TOKEN_RIGHT_BRACE: return "}"; case TOKEN_GREATER: return ">"; default: return "<unknown>"; } } deUint32 ShaderParser::getShaderStageLiteralFlag (void) { switch (m_curToken) { case TOKEN_VERTEX: return (1 << glu::SHADERTYPE_VERTEX); case TOKEN_FRAGMENT: return (1 << glu::SHADERTYPE_FRAGMENT); case TOKEN_GEOMETRY: return (1 << glu::SHADERTYPE_GEOMETRY); case TOKEN_TESSELLATION_CONTROL: return (1 << glu::SHADERTYPE_TESSELLATION_CONTROL); case TOKEN_TESSELLATION_EVALUATION: return (1 << glu::SHADERTYPE_TESSELLATION_EVALUATION); default: parseError(std::string() + "invalid shader stage name, got " + m_curTokenStr); return 0; } } deUint32 ShaderParser::getGLEnumFromName (const std::string& enumName) { static const struct { const char* name; deUint32 value; } names[] = { { "GL_MAX_VERTEX_IMAGE_UNIFORMS", GL_MAX_VERTEX_IMAGE_UNIFORMS }, { "GL_MAX_VERTEX_ATOMIC_COUNTERS", GL_MAX_VERTEX_ATOMIC_COUNTERS }, { "GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS", GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS }, { "GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS", GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS }, }; for (int ndx = 0; ndx < DE_LENGTH_OF_ARRAY(names); ++ndx) if (names[ndx].name == enumName) return names[ndx].value; parseError(std::string() + "unknown enum name, got " + enumName); return 0; } void ShaderParser::parseValueElement (DataType expectedDataType, Value& result) { DataType scalarType = getDataTypeScalarType(expectedDataType); int scalarSize = getDataTypeScalarSize(expectedDataType); /* \todo [2010-04-19 petri] Support arrays. */ Value::Element elems[16]; if (scalarSize > 1) { DE_ASSERT(mapDataTypeToken(m_curToken) == expectedDataType); advanceToken(); // data type (float, vec2, etc.) advanceToken(TOKEN_LEFT_PAREN); } for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++) { if (scalarType == TYPE_FLOAT) { float signMult = 1.0f; if (m_curToken == TOKEN_MINUS) { signMult = -1.0f; advanceToken(); } assumeToken(TOKEN_FLOAT_LITERAL); elems[scalarNdx].float32 = signMult * parseFloatLiteral(m_curTokenStr.c_str()); advanceToken(TOKEN_FLOAT_LITERAL); } else if (scalarType == TYPE_INT || scalarType == TYPE_UINT) { int signMult = 1; if (m_curToken == TOKEN_MINUS) { signMult = -1; advanceToken(); } assumeToken(TOKEN_INT_LITERAL); elems[scalarNdx].int32 = signMult * parseIntLiteral(m_curTokenStr.c_str()); advanceToken(TOKEN_INT_LITERAL); } else { DE_ASSERT(scalarType == TYPE_BOOL); elems[scalarNdx].bool32 = (m_curToken == TOKEN_TRUE); if (m_curToken != TOKEN_TRUE && m_curToken != TOKEN_FALSE) parseError(string("unexpected token, expecting bool: " + m_curTokenStr)); advanceToken(); // true/false } if (scalarNdx != (scalarSize - 1)) advanceToken(TOKEN_COMMA); } if (scalarSize > 1) advanceToken(TOKEN_RIGHT_PAREN); // Store results. for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++) result.elements.push_back(elems[scalarNdx]); } void ShaderParser::parseValue (ValueBlock& valueBlock) { PARSE_DBG((" parseValue()\n")); // Parsed results. vector<Value>* dstBlock = DE_NULL; DataType basicType = TYPE_LAST; std::string valueName; // Parse storage. if (m_curToken == TOKEN_UNIFORM) dstBlock = &valueBlock.uniforms; else if (m_curToken == TOKEN_INPUT) dstBlock = &valueBlock.inputs; else if (m_curToken == TOKEN_OUTPUT) dstBlock = &valueBlock.outputs; else parseError(string("unexpected token encountered when parsing value classifier")); advanceToken(); // Parse data type. basicType = mapDataTypeToken(m_curToken); if (basicType == TYPE_INVALID) parseError(string("unexpected token when parsing value data type: " + m_curTokenStr)); advanceToken(); // Parse value name. if (m_curToken == TOKEN_IDENTIFIER || m_curToken == TOKEN_STRING) { if (m_curToken == TOKEN_IDENTIFIER) valueName = m_curTokenStr; else valueName = parseStringLiteral(m_curTokenStr.c_str()); } else parseError(string("unexpected token when parsing value name: " + m_curTokenStr)); advanceToken(); // Parse assignment operator. advanceToken(TOKEN_ASSIGN); { Value value; value.name = valueName; value.type = VarType(basicType, PRECISION_LAST); dstBlock->push_back(value); } // Parse actual value. if (m_curToken == TOKEN_LEFT_BRACKET) // value list { int arrayLength = 0; // \todo [2015-08-03 pyry] Currently unused advanceToken(TOKEN_LEFT_BRACKET); for (;;) { parseValueElement(basicType, dstBlock->back()); arrayLength++; if (m_curToken == TOKEN_RIGHT_BRACKET) break; else if (m_curToken == TOKEN_VERTICAL_BAR) { advanceToken(); continue; } else parseError(string("unexpected token in value element array: " + m_curTokenStr)); } advanceToken(TOKEN_RIGHT_BRACKET); } else // single elements { parseValueElement(basicType, dstBlock->back()); } advanceToken(TOKEN_SEMI_COLON); // end of declaration } void ShaderParser::parseValueBlock (ValueBlock& valueBlock) { PARSE_DBG((" parseValueBlock()\n")); advanceToken(TOKEN_VALUES); advanceToken(TOKEN_LEFT_BRACE); for (;;) { if (m_curToken == TOKEN_UNIFORM || m_curToken == TOKEN_INPUT || m_curToken == TOKEN_OUTPUT) parseValue(valueBlock); else if (m_curToken == TOKEN_RIGHT_BRACE) break; else parseError(string("unexpected token when parsing a value block: " + m_curTokenStr)); } advanceToken(TOKEN_RIGHT_BRACE); } deUint32 ShaderParser::parseShaderStageList (void) { deUint32 mask = 0; assumeToken(TOKEN_LEFT_BRACE); // don't allow 0-sized lists advanceToken(); mask |= getShaderStageLiteralFlag(); advanceToken(); for (;;) { if (m_curToken == TOKEN_RIGHT_BRACE) break; else if (m_curToken == TOKEN_COMMA) { deUint32 stageFlag; advanceToken(); stageFlag = getShaderStageLiteralFlag(); if (stageFlag & mask) parseError(string("stage already set in the shader stage set: " + m_curTokenStr)); mask |= stageFlag; advanceToken(); } else parseError(string("invalid shader stage set token: " + m_curTokenStr)); } advanceToken(TOKEN_RIGHT_BRACE); return mask; } void ShaderParser::parseRequirement (CaseRequirement& valueBlock) { PARSE_DBG((" parseRequirement()\n")); advanceToken(); assumeToken(TOKEN_IDENTIFIER); if (m_curTokenStr == "extension") { std::vector<std::string> anyExtensionStringList; deUint32 affectedCasesFlags = -1; // by default all stages advanceToken(); assumeToken(TOKEN_LEFT_BRACE); advanceToken(); assumeToken(TOKEN_STRING); anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str())); advanceToken(); for (;;) { if (m_curToken == TOKEN_RIGHT_BRACE) break; else if (m_curToken == TOKEN_VERTICAL_BAR) { advanceToken(); assumeToken(TOKEN_STRING); anyExtensionStringList.push_back(parseStringLiteral(m_curTokenStr.c_str())); advanceToken(); } else parseError(string("invalid extension list token: " + m_curTokenStr)); } advanceToken(TOKEN_RIGHT_BRACE); if (m_curToken == TOKEN_IN) { advanceToken(); affectedCasesFlags = parseShaderStageList(); } valueBlock = CaseRequirement::createAnyExtensionRequirement(anyExtensionStringList, affectedCasesFlags); } else if (m_curTokenStr == "limit") { deUint32 limitEnum; int limitValue; advanceToken(); assumeToken(TOKEN_STRING); limitEnum = getGLEnumFromName(parseStringLiteral(m_curTokenStr.c_str())); advanceToken(); assumeToken(TOKEN_GREATER); advanceToken(); assumeToken(TOKEN_INT_LITERAL); limitValue = parseIntLiteral(m_curTokenStr.c_str()); advanceToken(); valueBlock = CaseRequirement::createLimitRequirement(limitEnum, limitValue); } else if (m_curTokenStr == "full_glsl_es_100_support") { advanceToken(); valueBlock = CaseRequirement::createFullGLSLES100SpecificationRequirement(); } else parseError(string("invalid requirement value: " + m_curTokenStr)); } void ShaderParser::parseExpectResult (ExpectResult& expectResult) { assumeToken(TOKEN_IDENTIFIER); if (m_curTokenStr == "pass") expectResult = EXPECT_PASS; else if (m_curTokenStr == "compile_fail") expectResult = EXPECT_COMPILE_FAIL; else if (m_curTokenStr == "link_fail") expectResult = EXPECT_LINK_FAIL; else if (m_curTokenStr == "compile_or_link_fail") expectResult = EXPECT_COMPILE_LINK_FAIL; else if (m_curTokenStr == "validation_fail") expectResult = EXPECT_VALIDATION_FAIL; else if (m_curTokenStr == "build_successful") expectResult = EXPECT_BUILD_SUCCESSFUL; else parseError(string("invalid expected result value: " + m_curTokenStr)); advanceToken(); } void ShaderParser::parseGLSLVersion (glu::GLSLVersion& version) { int versionNum = 0; std::string postfix = ""; assumeToken(TOKEN_INT_LITERAL); versionNum = parseIntLiteral(m_curTokenStr.c_str()); advanceToken(); if (m_curToken == TOKEN_IDENTIFIER) { postfix = m_curTokenStr; advanceToken(); } DE_STATIC_ASSERT(glu::GLSL_VERSION_LAST == 15); if (versionNum == 100 && postfix == "es") version = glu::GLSL_VERSION_100_ES; else if (versionNum == 300 && postfix == "es") version = glu::GLSL_VERSION_300_ES; else if (versionNum == 310 && postfix == "es") version = glu::GLSL_VERSION_310_ES; else if (versionNum == 320 && postfix == "es") version = glu::GLSL_VERSION_320_ES; else if (versionNum == 130) version = glu::GLSL_VERSION_130; else if (versionNum == 140) version = glu::GLSL_VERSION_140; else if (versionNum == 150) version = glu::GLSL_VERSION_150; else if (versionNum == 330) version = glu::GLSL_VERSION_330; else if (versionNum == 400) version = glu::GLSL_VERSION_400; else if (versionNum == 410) version = glu::GLSL_VERSION_410; else if (versionNum == 420) version = glu::GLSL_VERSION_420; else if (versionNum == 430) version = glu::GLSL_VERSION_430; else if (versionNum == 440) version = glu::GLSL_VERSION_440; else if (versionNum == 450) version = glu::GLSL_VERSION_450; else if (versionNum == 460) version = glu::GLSL_VERSION_460; else parseError("Unknown GLSL version"); } void ShaderParser::parsePipelineProgram (ProgramSpecification& program) { advanceToken(TOKEN_PIPELINE_PROGRAM); for (;;) { if (m_curToken == TOKEN_END) break; else if (m_curToken == TOKEN_ACTIVE_STAGES) { advanceToken(); program.activeStages = parseShaderStageList(); } else if (m_curToken == TOKEN_REQUIRE) { CaseRequirement requirement; parseRequirement(requirement); if (requirement.type == CaseRequirement::TYPE_EXTENSION) program.requiredExtensions.push_back(requirement.extension); else parseError("only extension requirements are allowed inside pipeline program"); } else if (m_curToken == TOKEN_VERTEX || m_curToken == TOKEN_FRAGMENT || m_curToken == TOKEN_TESSELLATION_CONTROL || m_curToken == TOKEN_TESSELLATION_EVALUATION || m_curToken == TOKEN_GEOMETRY) { const Token token = m_curToken; string source; advanceToken(); assumeToken(TOKEN_SHADER_SOURCE); source = parseShaderSource(m_curTokenStr.c_str()); advanceToken(); switch (token) { case TOKEN_VERTEX: program.sources.sources[SHADERTYPE_VERTEX].push_back(source); break; case TOKEN_FRAGMENT: program.sources.sources[SHADERTYPE_FRAGMENT].push_back(source); break; case TOKEN_TESSELLATION_CONTROL: program.sources.sources[SHADERTYPE_TESSELLATION_CONTROL].push_back(source); break; case TOKEN_TESSELLATION_EVALUATION: program.sources.sources[SHADERTYPE_TESSELLATION_EVALUATION].push_back(source); break; case TOKEN_GEOMETRY: program.sources.sources[SHADERTYPE_GEOMETRY].push_back(source); break; default: parseError(DE_FALSE); } } else parseError(string("invalid pipeline program value: " + m_curTokenStr)); } advanceToken(TOKEN_END); if (program.activeStages == 0) parseError("program pipeline object must have active stages"); } void ShaderParser::parseShaderCase (vector<tcu::TestNode*>& shaderNodeList) { // Parse 'case'. PARSE_DBG((" parseShaderCase()\n")); advanceToken(TOKEN_CASE); // Parse case name. string caseName = m_curTokenStr; advanceToken(); // \note [pyry] All token types are allowed here. // \todo [pyry] Optimize by parsing most stuff directly to ShaderCaseSpecification // Setup case. GLSLVersion version = DEFAULT_GLSL_VERSION; ExpectResult expectResult = EXPECT_PASS; string description; string bothSource; vector<string> vertexSources; vector<string> fragmentSources; vector<string> tessellationCtrlSources; vector<string> tessellationEvalSources; vector<string> geometrySources; ValueBlock valueBlock; bool valueBlockSeen = false; vector<CaseRequirement> requirements; vector<ProgramSpecification> pipelinePrograms; for (;;) { if (m_curToken == TOKEN_END) break; else if (m_curToken == TOKEN_DESC) { advanceToken(); assumeToken(TOKEN_STRING); description = parseStringLiteral(m_curTokenStr.c_str()); advanceToken(); } else if (m_curToken == TOKEN_EXPECT) { advanceToken(); parseExpectResult(expectResult); } else if (m_curToken == TOKEN_VALUES) { if (valueBlockSeen) parseError("multiple value blocks"); parseValueBlock(valueBlock); valueBlockSeen = true; } else if (m_curToken == TOKEN_BOTH || m_curToken == TOKEN_VERTEX || m_curToken == TOKEN_FRAGMENT || m_curToken == TOKEN_TESSELLATION_CONTROL || m_curToken == TOKEN_TESSELLATION_EVALUATION || m_curToken == TOKEN_GEOMETRY) { const Token token = m_curToken; string source; advanceToken(); assumeToken(TOKEN_SHADER_SOURCE); source = parseShaderSource(m_curTokenStr.c_str()); advanceToken(); switch (token) { case TOKEN_VERTEX: vertexSources.push_back(source); break; case TOKEN_FRAGMENT: fragmentSources.push_back(source); break; case TOKEN_TESSELLATION_CONTROL: tessellationCtrlSources.push_back(source); break; case TOKEN_TESSELLATION_EVALUATION: tessellationEvalSources.push_back(source); break; case TOKEN_GEOMETRY: geometrySources.push_back(source); break; case TOKEN_BOTH: { if (!bothSource.empty()) parseError("multiple 'both' blocks"); bothSource = source; break; } default: parseError(DE_FALSE); } } else if (m_curToken == TOKEN_VERSION) { advanceToken(); parseGLSLVersion(version); } else if (m_curToken == TOKEN_REQUIRE) { CaseRequirement requirement; parseRequirement(requirement); requirements.push_back(requirement); } else if (m_curToken == TOKEN_PIPELINE_PROGRAM) { ProgramSpecification pipelineProgram; parsePipelineProgram(pipelineProgram); pipelineProgram.sources.separable = true; pipelinePrograms.push_back(pipelineProgram); } else parseError(string("unexpected token while parsing shader case: " + m_curTokenStr)); } advanceToken(TOKEN_END); // case end // \todo [pyry] Clean up vector<RequiredCapability> requiredCaps; vector<RequiredExtension> requiredExts; bool fullGLSLES100Required = false; for (size_t reqNdx = 0; reqNdx < requirements.size(); ++reqNdx) { const CaseRequirement& requirement = requirements[reqNdx]; if (requirement.type == CaseRequirement::TYPE_EXTENSION) requiredExts.push_back(requirement.extension); else if (requirement.type == CaseRequirement::TYPE_IMPLEMENTATION_LIMIT) requiredCaps.push_back(requirement.requiredCap); else if (requirement.type == CaseRequirement::TYPE_FULL_GLSL_ES_100_SUPPORT) fullGLSLES100Required = true; else DE_ASSERT(false); } if (!bothSource.empty()) { if (!vertexSources.empty() || !fragmentSources.empty() || !tessellationCtrlSources.empty() || !tessellationEvalSources.empty() || !geometrySources.empty() || !pipelinePrograms.empty()) { parseError("'both' cannot be mixed with other shader stages"); } // vertex { ShaderCaseSpecification spec; spec.caseType = CASETYPE_VERTEX_ONLY; spec.expectResult = expectResult; spec.targetVersion = version; spec.fullGLSLES100Required = fullGLSLES100Required; spec.requiredCaps = requiredCaps; spec.values = valueBlock; spec.programs.resize(1); spec.programs[0].sources << VertexSource(bothSource); spec.programs[0].requiredExtensions = requiredExts; shaderNodeList.push_back(m_caseFactory->createCase(caseName + "_vertex", description, ShaderCaseSpecification(spec))); } // fragment { ShaderCaseSpecification spec; spec.caseType = CASETYPE_FRAGMENT_ONLY; spec.expectResult = expectResult; spec.targetVersion = version; spec.fullGLSLES100Required = fullGLSLES100Required; spec.requiredCaps = requiredCaps; spec.values = valueBlock; spec.programs.resize(1); spec.programs[0].sources << FragmentSource(bothSource); spec.programs[0].requiredExtensions = requiredExts; shaderNodeList.push_back(m_caseFactory->createCase(caseName + "_fragment", description, ShaderCaseSpecification(spec))); } } else if (pipelinePrograms.empty()) { ShaderCaseSpecification spec; spec.caseType = CASETYPE_COMPLETE; spec.expectResult = expectResult; spec.targetVersion = version; spec.fullGLSLES100Required = fullGLSLES100Required; spec.requiredCaps = requiredCaps; spec.values = valueBlock; spec.programs.resize(1); spec.programs[0].sources.sources[SHADERTYPE_VERTEX].swap(vertexSources); spec.programs[0].sources.sources[SHADERTYPE_FRAGMENT].swap(fragmentSources); spec.programs[0].sources.sources[SHADERTYPE_TESSELLATION_CONTROL].swap(tessellationCtrlSources); spec.programs[0].sources.sources[SHADERTYPE_TESSELLATION_EVALUATION].swap(tessellationEvalSources); spec.programs[0].sources.sources[SHADERTYPE_GEOMETRY].swap(geometrySources); spec.programs[0].requiredExtensions.swap(requiredExts); shaderNodeList.push_back(m_caseFactory->createCase(caseName, description, ShaderCaseSpecification(spec))); } else { if (!vertexSources.empty() || !fragmentSources.empty() || !tessellationCtrlSources.empty() || !tessellationEvalSources.empty() || !geometrySources.empty()) { parseError("pipeline programs cannot be mixed with complete programs"); } if (!requiredExts.empty()) parseError("global extension requirements cannot be mixed with pipeline programs"); // Pipeline case, multiple programs { ShaderCaseSpecification spec; spec.caseType = CASETYPE_COMPLETE; spec.expectResult = expectResult; spec.targetVersion = version; spec.fullGLSLES100Required = fullGLSLES100Required; spec.requiredCaps = requiredCaps; spec.values = valueBlock; spec.programs.swap(pipelinePrograms); shaderNodeList.push_back(m_caseFactory->createCase(caseName, description, ShaderCaseSpecification(spec))); } } } void ShaderParser::parseShaderGroup (vector<tcu::TestNode*>& shaderNodeList) { // Parse 'case'. PARSE_DBG((" parseShaderGroup()\n")); advanceToken(TOKEN_GROUP); // Parse case name. string name = m_curTokenStr; advanceToken(); // \note [pyry] We don't want to check token type here (for instance to allow "uniform") group. // Parse description. assumeToken(TOKEN_STRING); string description = parseStringLiteral(m_curTokenStr.c_str()); advanceToken(TOKEN_STRING); std::vector<tcu::TestNode*> children; // Parse group children. for (;;) { if (m_curToken == TOKEN_END) break; else if (m_curToken == TOKEN_GROUP) parseShaderGroup(children); else if (m_curToken == TOKEN_CASE) parseShaderCase(children); else if (m_curToken == TOKEN_IMPORT) parseImport(children); else parseError(string("unexpected token while parsing shader group: " + m_curTokenStr)); } advanceToken(TOKEN_END); // group end // Create group node. tcu::TestCaseGroup* groupNode = m_caseFactory->createGroup(name, description, children); shaderNodeList.push_back(groupNode); } void ShaderParser::parseImport (vector<tcu::TestNode*>& shaderNodeList) { std::string importFileName; advanceToken(TOKEN_IMPORT); assumeToken(TOKEN_STRING); importFileName = parseStringLiteral(m_curTokenStr.c_str()); advanceToken(TOKEN_STRING); { ShaderParser subParser (m_archive, de::FilePath::join(de::FilePath(m_filename).getDirName(), importFileName).getPath(), m_caseFactory); const vector<tcu::TestNode*> importedCases = subParser.parse(); // \todo [2015-08-03 pyry] Not exception safe shaderNodeList.insert(shaderNodeList.end(), importedCases.begin(), importedCases.end()); } } vector<tcu::TestNode*> ShaderParser::parse (void) { const int dataLen = m_resource->getSize(); m_input.resize(dataLen+1); m_resource->setPosition(0); m_resource->read((deUint8*)&m_input[0], dataLen); m_input[dataLen] = '\0'; // Initialize parser. m_curPtr = &m_input[0]; m_curToken = TOKEN_INVALID; m_curTokenStr = ""; advanceToken(); vector<tcu::TestNode*> nodeList; // Parse all cases. PARSE_DBG(("parse()\n")); for (;;) { if (m_curToken == TOKEN_CASE) parseShaderCase(nodeList); else if (m_curToken == TOKEN_GROUP) parseShaderGroup(nodeList); else if (m_curToken == TOKEN_IMPORT) parseImport(nodeList); else if (m_curToken == TOKEN_EOF) break; else parseError(string("invalid token encountered at main level: '") + m_curTokenStr + "'"); } assumeToken(TOKEN_EOF); // printf(" parsed %d test cases.\n", caseList.size()); return nodeList; } std::vector<tcu::TestNode*> parseFile (const tcu::Archive& archive, const std::string& filename, ShaderCaseFactory* caseFactory) { sl::ShaderParser parser (archive, filename, caseFactory); return parser.parse(); } // Execution utilities static void dumpValue (tcu::TestLog& log, const Value& val, const char* storageName, int arrayNdx) { const char* const valueName = val.name.c_str(); const DataType dataType = val.type.getBasicType(); int scalarSize = getDataTypeScalarSize(dataType); ostringstream result; result << " " << storageName << " "; result << getDataTypeName(dataType) << " " << valueName << ":"; if (isDataTypeScalar(dataType)) result << " "; if (isDataTypeVector(dataType)) result << " [ "; else if (isDataTypeMatrix(dataType)) result << "\n"; if (isDataTypeScalarOrVector(dataType)) { for (int scalarNdx = 0; scalarNdx < scalarSize; scalarNdx++) { int elemNdx = arrayNdx; const Value::Element& e = val.elements[elemNdx*scalarSize + scalarNdx]; result << ((scalarNdx != 0) ? ", " : ""); if (isDataTypeFloatOrVec(dataType)) result << e.float32; else if (isDataTypeIntOrIVec(dataType)) result << e.int32; else if (isDataTypeUintOrUVec(dataType)) result << (deUint32)e.int32; else if (isDataTypeBoolOrBVec(dataType)) result << (e.bool32 ? "true" : "false"); } } else if (isDataTypeMatrix(dataType)) { int numRows = getDataTypeMatrixNumRows(dataType); int numCols = getDataTypeMatrixNumColumns(dataType); for (int rowNdx = 0; rowNdx < numRows; rowNdx++) { result << " [ "; for (int colNdx = 0; colNdx < numCols; colNdx++) { int elemNdx = arrayNdx; float v = val.elements[elemNdx*scalarSize + rowNdx*numCols + colNdx].float32; result << ((colNdx==0) ? "" : ", ") << v; } result << " ]\n"; } } if (isDataTypeScalar(dataType)) result << "\n"; else if (isDataTypeVector(dataType)) result << " ]\n"; log << TestLog::Message << result.str() << TestLog::EndMessage; } static void dumpValues (tcu::TestLog& log, const vector<Value>& values, const char* storageName, int arrayNdx) { for (size_t valNdx = 0; valNdx < values.size(); valNdx++) dumpValue(log, values[valNdx], storageName, arrayNdx); } void dumpValues (tcu::TestLog& log, const ValueBlock& values, int arrayNdx) { dumpValues(log, values.inputs, "input", arrayNdx); dumpValues(log, values.outputs, "expected", arrayNdx); dumpValues(log, values.uniforms, "uniform", arrayNdx); } static void generateExtensionStatements (std::ostringstream& buf, const std::vector<RequiredExtension>& extensions, glu::ShaderType type) { for (size_t ndx = 0; ndx < extensions.size(); ++ndx) { DE_ASSERT(extensions[ndx].effectiveStages != 0u && extensions[ndx].alternatives.size() == 1); if ((extensions[ndx].effectiveStages & (1u << (deUint32)type)) != 0) buf << "#extension " << extensions[ndx].alternatives[0] << " : require\n"; } } // Injects #extension XXX : require lines after the last preprocessor directive in the shader code. Does not support line continuations std::string injectExtensionRequirements (const std::string& baseCode, const std::vector<RequiredExtension>& extensions, glu::ShaderType shaderType) { std::istringstream baseCodeBuf (baseCode); std::ostringstream resultBuf; std::string line; bool firstNonPreprocessorLine = true; std::ostringstream extStr; generateExtensionStatements(extStr, extensions, shaderType); // skip if no requirements if (extStr.str().empty()) return baseCode; while (std::getline(baseCodeBuf, line)) { // begins with '#'? const std::string::size_type firstNonWhitespace = line.find_first_not_of("\t "); const bool isPreprocessorDirective = (firstNonWhitespace != std::string::npos && line.at(firstNonWhitespace) == '#'); // Inject #extensions if (!isPreprocessorDirective && firstNonPreprocessorLine) { firstNonPreprocessorLine = false; resultBuf << extStr.str(); } resultBuf << line << "\n"; } return resultBuf.str(); } void genCompareFunctions (ostringstream& stream, const ValueBlock& valueBlock, bool useFloatTypes) { bool cmpTypeFound[TYPE_LAST]; for (int i = 0; i < TYPE_LAST; i++) cmpTypeFound[i] = false; for (size_t valueNdx = 0; valueNdx < valueBlock.outputs.size(); valueNdx++) { const Value& val = valueBlock.outputs[valueNdx]; cmpTypeFound[(size_t)val.type.getBasicType()] = true; } if (useFloatTypes) { if (cmpTypeFound[TYPE_BOOL]) stream << "bool isOk (float a, bool b) { return ((a > 0.5) == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC2]) stream << "bool isOk (vec2 a, bvec2 b) { return (greaterThan(a, vec2(0.5)) == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC3]) stream << "bool isOk (vec3 a, bvec3 b) { return (greaterThan(a, vec3(0.5)) == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC4]) stream << "bool isOk (vec4 a, bvec4 b) { return (greaterThan(a, vec4(0.5)) == b); }\n"; if (cmpTypeFound[TYPE_INT]) stream << "bool isOk (float a, int b) { float atemp = a+0.5; return (float(b) <= atemp && atemp <= float(b+1)); }\n"; if (cmpTypeFound[TYPE_INT_VEC2]) stream << "bool isOk (vec2 a, ivec2 b) { return (ivec2(floor(a + 0.5)) == b); }\n"; if (cmpTypeFound[TYPE_INT_VEC3]) stream << "bool isOk (vec3 a, ivec3 b) { return (ivec3(floor(a + 0.5)) == b); }\n"; if (cmpTypeFound[TYPE_INT_VEC4]) stream << "bool isOk (vec4 a, ivec4 b) { return (ivec4(floor(a + 0.5)) == b); }\n"; if (cmpTypeFound[TYPE_UINT]) stream << "bool isOk (float a, uint b) { float atemp = a+0.5; return (float(b) <= atemp && atemp <= float(b+1u)); }\n"; if (cmpTypeFound[TYPE_UINT_VEC2]) stream << "bool isOk (vec2 a, uvec2 b) { return (uvec2(floor(a + 0.5)) == b); }\n"; if (cmpTypeFound[TYPE_UINT_VEC3]) stream << "bool isOk (vec3 a, uvec3 b) { return (uvec3(floor(a + 0.5)) == b); }\n"; if (cmpTypeFound[TYPE_UINT_VEC4]) stream << "bool isOk (vec4 a, uvec4 b) { return (uvec4(floor(a + 0.5)) == b); }\n"; } else { if (cmpTypeFound[TYPE_BOOL]) stream << "bool isOk (bool a, bool b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC2]) stream << "bool isOk (bvec2 a, bvec2 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC3]) stream << "bool isOk (bvec3 a, bvec3 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_BOOL_VEC4]) stream << "bool isOk (bvec4 a, bvec4 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_INT]) stream << "bool isOk (int a, int b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_INT_VEC2]) stream << "bool isOk (ivec2 a, ivec2 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_INT_VEC3]) stream << "bool isOk (ivec3 a, ivec3 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_INT_VEC4]) stream << "bool isOk (ivec4 a, ivec4 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_UINT]) stream << "bool isOk (uint a, uint b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_UINT_VEC2]) stream << "bool isOk (uvec2 a, uvec2 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_UINT_VEC3]) stream << "bool isOk (uvec3 a, uvec3 b) { return (a == b); }\n"; if (cmpTypeFound[TYPE_UINT_VEC4]) stream << "bool isOk (uvec4 a, uvec4 b) { return (a == b); }\n"; } if (cmpTypeFound[TYPE_FLOAT]) stream << "bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }\n"; if (cmpTypeFound[TYPE_FLOAT_VEC2]) stream << "bool isOk (vec2 a, vec2 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_VEC3]) stream << "bool isOk (vec3 a, vec3 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_VEC4]) stream << "bool isOk (vec4 a, vec4 b, float eps) { return all(lessThanEqual(abs(a-b), (eps*abs(b) + eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT2]) stream << "bool isOk (mat2 a, mat2 b, float eps) { vec2 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec2(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT2X3]) stream << "bool isOk (mat2x3 a, mat2x3 b, float eps) { vec3 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec3(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT2X4]) stream << "bool isOk (mat2x4 a, mat2x4 b, float eps) { vec4 diff = max(abs(a[0]-b[0]), abs(a[1]-b[1])); return all(lessThanEqual(diff, vec4(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT3X2]) stream << "bool isOk (mat3x2 a, mat3x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec2(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT3]) stream << "bool isOk (mat3 a, mat3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec3(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT3X4]) stream << "bool isOk (mat3x4 a, mat3x4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), abs(a[2]-b[2])); return all(lessThanEqual(diff, vec4(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT4X2]) stream << "bool isOk (mat4x2 a, mat4x2 b, float eps) { vec2 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec2(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT4X3]) stream << "bool isOk (mat4x3 a, mat4x3 b, float eps) { vec3 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec3(eps))); }\n"; if (cmpTypeFound[TYPE_FLOAT_MAT4]) stream << "bool isOk (mat4 a, mat4 b, float eps) { vec4 diff = max(max(abs(a[0]-b[0]), abs(a[1]-b[1])), max(abs(a[2]-b[2]), abs(a[3]-b[3]))); return all(lessThanEqual(diff, vec4(eps))); }\n"; } } // sl } // glu