/*------------------------------------------------------------------------- * drawElements Quality Program OpenGL ES Utilities * ------------------------------------------------ * * Copyright 2014 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. * */ 'use strict'; goog.provide('modules.shared.glsShaderLibrary'); goog.require('framework.common.tcuTestCase'); goog.require('framework.opengl.gluShaderUtil'); goog.require('modules.shared.glsShaderLibraryCase'); goog.scope(function() { var glsShaderLibrary = modules.shared.glsShaderLibrary; var tcuTestCase = framework.common.tcuTestCase; var glsShaderLibraryCase = modules.shared.glsShaderLibraryCase; var gluShaderUtil = framework.opengl.gluShaderUtil; glsShaderLibrary.generateTestCases = function() { /** @type {glsShaderLibrary.Parser} */ var parser = new glsShaderLibrary.Parser(); try { /** @type {Object} */ var state = tcuTestCase.runner; var tree = parser.parse(state.testFile); var rootTest = tcuTestCase.newTest(state.testName, 'Top level'); rootTest.setChildren(tree); state.setRoot(rootTest); } catch (err) { bufferedLogToConsole(err); testFailed('Failed to parse shader test case file'); return false; } return true; }; glsShaderLibrary.processTestFile = function() { if (glsShaderLibrary.generateTestCases()) { tcuTestCase.runner.runCallback(glsShaderLibraryCase.runTestCases); } else { tcuTestCase.runner.terminate(); } }; glsShaderLibrary.isWhitespace = function(value) { return /^[ \t\r\n]+$/.test(value); }; glsShaderLibrary.isEOL = function(value) { return /^[\r\n]+$/.test(value); }; glsShaderLibrary.isAlpha = function(value) { return /^[a-zA-Z]$/.test(value); }; glsShaderLibrary.isNumeric = function(value) { return /^[0-9]$/.test(value); }; glsShaderLibrary.isCaseNameChar = function(value) { return /^[a-zA-Z0-9_\-\.]$/.test(value); }; /** * Removes however many indents there are on the first line from all lines. * @param {string} str * @return {string} output */ glsShaderLibrary.removeExtraIndentation = function(str) { return glsShaderLibrary.removeExtraIndentationArray( str.split(/\r\n|\r|\n/) ).join('\n'); }; /** * Returns an array of strings without indentation. * @param {Array} arr * @return {Array} output */ glsShaderLibrary.removeExtraIndentationArray = function(arr) { /** @type {Array} */ var output = []; if (arr.length) { /** @type {number} */ var numIndentChars = 0; for (var i = 0; i < arr[0].length && glsShaderLibrary.isWhitespace(arr[0].charAt(i)); ++i) { numIndentChars += arr[0].charAt(i) === '\t' ? 4 : 1; } for (var i = 0; i < arr.length; ++i) { /** @type {number} */ var removed = 0; /** @type {number} */ var j; // Some tests are indented inconsistently, so we have to check for non-whitespace characters here. for (j = 0; removed < numIndentChars && j < arr[i].length && glsShaderLibrary.isWhitespace(arr[i].charAt(j)); ++j) { removed += (arr[i].charAt(j) === '\t' ? 4 : 1); } output.push(arr[i].substr(j, arr[i].length - j)); } } return output; }; glsShaderLibrary.de_assert = function(condition) { if (!condition) { throw Error(); } }; /** * @param {string} str * @param {string} endstr end of string character * @param {boolean=} trimFront trim leading whitespace * @return {string} str * @private */ glsShaderLibrary.parseStringLiteralHelper = function(str, endstr, trimFront) { trimFront = trimFront || false; /** @type {number} */ var index_end = 0; // isolate the string do { index_end = str.indexOf(endstr, index_end + 1); } while (index_end >= 0 && str.charAt(index_end - 1) === '\\'); if (index_end <= 0) { index_end = str.length; } // strip quotes, replace \n and \t with nl and tabs respectively str = str.substr(endstr.length, index_end - endstr.length); if (trimFront) str = str.replace(/^\s*\n/, ''); var result = ''; var i = 0; while (str[i] != undefined) { if (str[i] == '\\') { switch (str[i + 1]) { case undefined: break; case 'n': result += '\n'; break; case 't': result += '\t'; break; default: result += str[i + 1]; break; } i += 2; } else { result += str[i]; i++; } } return result; }; /** * glsShaderLibrary.Parser class * @constructor */ glsShaderLibrary.Parser = function() { /* data members */ /** * The Token constants * @enum {number} */ var Token = { TOKEN_INVALID: 0, TOKEN_EOF: 1, TOKEN_STRING: 2, TOKEN_SHADER_SOURCE: 3, TOKEN_INT_LITERAL: 4, TOKEN_FLOAT_LITERAL: 5, // identifiers TOKEN_IDENTIFIER: 6, TOKEN_TRUE: 7, TOKEN_FALSE: 8, TOKEN_DESC: 9, TOKEN_EXPECT: 10, TOKEN_GROUP: 11, TOKEN_CASE: 12, TOKEN_END: 13, TOKEN_VALUES: 14, TOKEN_BOTH: 15, TOKEN_VERTEX: 26, TOKEN_FRAGMENT: 17, TOKEN_UNIFORM: 18, TOKEN_INPUT: 19, TOKEN_OUTPUT: 20, TOKEN_FLOAT: 21, TOKEN_FLOAT_VEC2: 22, TOKEN_FLOAT_VEC3: 23, TOKEN_FLOAT_VEC4: 24, TOKEN_FLOAT_MAT2: 25, TOKEN_FLOAT_MAT2X3: 26, TOKEN_FLOAT_MAT2X4: 27, TOKEN_FLOAT_MAT3X2: 28, TOKEN_FLOAT_MAT3: 29, TOKEN_FLOAT_MAT3X4: 30, TOKEN_FLOAT_MAT4X2: 31, TOKEN_FLOAT_MAT4X3: 32, TOKEN_FLOAT_MAT4: 33, TOKEN_INT: 34, TOKEN_INT_VEC2: 35, TOKEN_INT_VEC3: 36, TOKEN_INT_VEC4: 37, TOKEN_UINT: 38, TOKEN_UINT_VEC2: 39, TOKEN_UINT_VEC3: 40, TOKEN_UINT_VEC4: 41, TOKEN_BOOL: 42, TOKEN_BOOL_VEC2: 43, TOKEN_BOOL_VEC3: 44, TOKEN_BOOL_VEC4: 45, TOKEN_VERSION: 46, // symbols TOKEN_ASSIGN: 47, TOKEN_PLUS: 48, TOKEN_MINUS: 49, TOKEN_COMMA: 50, TOKEN_VERTICAL_BAR: 51, TOKEN_SEMI_COLON: 52, TOKEN_LEFT_PAREN: 53, TOKEN_RIGHT_PAREN: 54, TOKEN_LEFT_BRACKET: 55, TOKEN_RIGHT_BRACKET: 56, TOKEN_LEFT_BRACE: 57, TOKEN_RIGHT_BRACE: 58, TOKEN_LAST: 59 }; /** @type {string} */ var m_input = ''; /** @type {number} */ var m_curPtr = 0; /** @type {number} */ var m_curToken;// = Token.TOKEN_INVALID; /** @type {string} */ var m_curTokenStr = ''; /* function members */ this.parse = function(input) { // initialise parser m_input = input; m_curPtr = 0; m_curToken = Token.TOKEN_INVALID; m_curTokenStr = ''; advanceToken(); /** @type {Array} */ var nodeList = []; for (;;) { if (m_curToken === Token.TOKEN_CASE) { parseShaderCase(nodeList); } else if (m_curToken === Token.TOKEN_GROUP) { parseShaderGroup(nodeList); } else if (m_curToken === Token.TOKEN_EOF) { break; } else { // throw Error("invalid token encountered at main level: '" + m_curTokenStr + "'"); testFailed("invalid token encountered at main level: '" + m_curTokenStr + "'"); tcuTestCase.runner.terminate(); } } return nodeList; }; /** * ensures that the token exists * otherwise it returns the corresponding token's name depending on enum number value * @param {number} id * @return {string} name */ var resolveTokenName = function(id) { for (var name in Token) { if (Token[name] === id) return name; } return 'TOKEN_UNKNOWN'; }; /** * Throws an error which contains the passed string * @param {string} errorStr that contains an error to notify * @return {string} error */ var parseError = function(errorStr) { // abort throw 'glsShaderLibrary.Parser error: ' + errorStr + ' near ' + m_input.substr(m_curPtr, m_curPtr + 80); }; /** * Converts string into float * @param {string} str * @return {number} */ var parseFloatLiteral = function(str) { return parseFloat(str); }; /** * Converts string into integer * @param {string} str * @return {number} */ var parseIntLiteral = function(str) { return parseInt(str, 10); }; var parseStringLiteral = function(str) { /** * @type {string} * find delimitor */ var endchar = str.substr(0, 1); return glsShaderLibrary.parseStringLiteralHelper(str, endchar); }; var parseShaderSource = function(str) { // similar to parse literal, delimitors are two double quotes ("") return glsShaderLibrary.removeExtraIndentation( glsShaderLibrary.parseStringLiteralHelper(str, '""', true) ); }; var advanceTokenWorker = function() { // Skip old token m_curPtr += m_curTokenStr.length; // Reset token (for safety). m_curToken = Token.TOKEN_INVALID; m_curTokenStr = ''; // Eat whitespace & comments while they last. for (;;) { while (glsShaderLibrary.isWhitespace(m_input.charAt(m_curPtr))) ++m_curPtr; // check for EOL comment if (m_input.charAt(m_curPtr) === '#') { // if m_input is to be an array of lines then this probably wont work very well while ( m_curPtr < m_input.length && !glsShaderLibrary.isEOL(m_input.charAt(m_curPtr)) ) ++m_curPtr; } else { break; } } if (m_curPtr >= m_input.length) { m_curToken = Token.TOKEN_EOF; m_curTokenStr = ''; } else if (glsShaderLibrary.isAlpha(m_input.charAt(m_curPtr))) { /** @type {number} */ var end = m_curPtr + 1; while (glsShaderLibrary.isCaseNameChar(m_input.charAt(end))) ++end; m_curTokenStr = m_input.substr(m_curPtr, end - m_curPtr); m_curToken = (function() { // consider reimplementing with a binary search switch (m_curTokenStr) { case 'true': return Token.TOKEN_TRUE; case 'false': return Token.TOKEN_FALSE; case 'desc': return Token.TOKEN_DESC; case 'expect': return Token.TOKEN_EXPECT; case 'group': return Token.TOKEN_GROUP; case 'case': return Token.TOKEN_CASE; case 'end': return Token.TOKEN_END; case 'values': return Token.TOKEN_VALUES; case 'both': return Token.TOKEN_BOTH; case 'vertex': return Token.TOKEN_VERTEX; case 'fragment': return Token.TOKEN_FRAGMENT; case 'uniform': return Token.TOKEN_UNIFORM; case 'input': return Token.TOKEN_INPUT; case 'output': return Token.TOKEN_OUTPUT; case 'float': return Token.TOKEN_FLOAT; case 'vec2': return Token.TOKEN_FLOAT_VEC2; case 'vec3': return Token.TOKEN_FLOAT_VEC3; case 'vec4': return Token.TOKEN_FLOAT_VEC4; case 'mat2': return Token.TOKEN_FLOAT_MAT2; case 'mat2x3': return Token.TOKEN_FLOAT_MAT2X3; case 'mat2x4': return Token.TOKEN_FLOAT_MAT2X4; case 'mat3x2': return Token.TOKEN_FLOAT_MAT3X2; case 'mat3': return Token.TOKEN_FLOAT_MAT3; case 'mat3x4': return Token.TOKEN_FLOAT_MAT3X4; case 'mat4x2': return Token.TOKEN_FLOAT_MAT4X2; case 'mat4x3': return Token.TOKEN_FLOAT_MAT4X3; case 'mat4': return Token.TOKEN_FLOAT_MAT4; case 'int': return Token.TOKEN_INT; case 'ivec2': return Token.TOKEN_INT_VEC2; case 'ivec3': return Token.TOKEN_INT_VEC3; case 'ivec4': return Token.TOKEN_INT_VEC4; case 'uint': return Token.TOKEN_UINT; case 'uvec2': return Token.TOKEN_UINT_VEC2; case 'uvec3': return Token.TOKEN_UINT_VEC3; case 'uvec4': return Token.TOKEN_UINT_VEC4; case 'bool': return Token.TOKEN_BOOL; case 'bvec2': return Token.TOKEN_BOOL_VEC2; case 'bvec3': return Token.TOKEN_BOOL_VEC3; case 'bvec4': return Token.TOKEN_BOOL_VEC4; case 'version': return Token.TOKEN_VERSION; default: return Token.TOKEN_IDENTIFIER; } }()); } else if (glsShaderLibrary.isNumeric(m_input.charAt(m_curPtr))) { /** @type {number} */ var p = m_curPtr; while (glsShaderLibrary.isNumeric(m_input.charAt(p))) ++p; if (m_input.charAt(p) === '.') { // float ++p; while (glsShaderLibrary.isNumeric(m_input.charAt(p))) ++p; if (m_input.charAt(p) === 'e' || m_input.charAt(p) === 'E') { ++p; if (m_input.charAt(p) === '+' || m_input.charAt(p) === '-') ++p; glsShaderLibrary.de_assert(p < m_input.length && glsShaderLibrary.isNumeric(m_input.charAt(p))); while (glsShaderLibrary.isNumeric(m_input.charAt(p))) ++p; } m_curToken = Token.TOKEN_FLOAT_LITERAL; m_curTokenStr = m_input.substr(m_curPtr, p - m_curPtr); } else { m_curToken = Token.TOKEN_INT_LITERAL; m_curTokenStr = m_input.substr(m_curPtr, p - m_curPtr); } } else if (m_input.charAt(m_curPtr) === '"' && m_input.charAt(m_curPtr + 1) === '"') { // shader source var p = m_curPtr + 2; while (m_input.charAt(p) != '"' || m_input.charAt(p + 1) != '"') { glsShaderLibrary.de_assert(p < m_input.length); if (m_input.charAt(p) === '\\') { glsShaderLibrary.de_assert(p + 1 < m_input.length); p += 2; } else { ++p; } } p += 2; m_curToken = Token.TOKEN_SHADER_SOURCE; m_curTokenStr = m_input.substr(m_curPtr, p - m_curPtr); } else if (m_input.charAt(m_curPtr) === '"' || m_input.charAt(m_curPtr) === "'") { /** @type {string} */ var delimitor = m_input.charAt(m_curPtr); var p = m_curPtr + 1; while (m_input.charAt(p) != delimitor) { glsShaderLibrary.de_assert(p < m_input.length); if (m_input.charAt(p) === '\\') { glsShaderLibrary.de_assert(p + 1 < m_input.length); p += 2; } else { ++p; } } ++p; m_curToken = Token.TOKEN_STRING; m_curTokenStr = m_input.substr(m_curPtr, p - m_curPtr); } else { m_curTokenStr = m_input.charAt(m_curPtr); m_curToken = (function() { // consider reimplementing with a binary search switch (m_curTokenStr) { case '=': return Token.TOKEN_ASSIGN; case '+': return Token.TOKEN_PLUS; case '-': return Token.TOKEN_MINUS; case ',': return Token.TOKEN_COMMA; case '|': return Token.TOKEN_VERTICAL_BAR; case ';': return Token.TOKEN_SEMI_COLON; case '(': return Token.TOKEN_LEFT_PAREN; case ')': return Token.TOKEN_RIGHT_PAREN; case '[': return Token.TOKEN_LEFT_BRACKET; case ']': return Token.TOKEN_RIGHT_BRACKET; case '{': return Token.TOKEN_LEFT_BRACE; case '}': return Token.TOKEN_RIGHT_BRACE; default: return Token.TOKEN_INVALID; } }()); } }; /** * @return {Object.} */ var advanceTokenTester = function(input, current_index) { m_input = input; m_curPtr = current_index; m_curTokenStr = ''; advanceTokenWorker(); return { /** @type {number} */ idType: m_curToken, /** @type {string} */ name: resolveTokenName(m_curToken), /** @type {string} */ value: m_curTokenStr }; }; /** * @param {Token=} tokenAssumed */ var advanceToken = function(tokenAssumed) { if (typeof(tokenAssumed) !== 'undefined') { assumeToken(tokenAssumed); } advanceTokenWorker(); }; var assumeToken = function(token) { if (m_curToken != token) { // parse error /** @type {string} */ var msg = "unexpected token '" + m_curTokenStr + "', expecting '" + getTokenName(token) + "'"; throw Error('Parse Error. ' + msg + ' near ' + m_curPtr + ' ...'); } }; var mapDataTypeToken = function(token) { switch (token) { case Token.TOKEN_FLOAT: return gluShaderUtil.DataType.FLOAT; case Token.TOKEN_FLOAT_VEC2: return gluShaderUtil.DataType.FLOAT_VEC2; case Token.TOKEN_FLOAT_VEC3: return gluShaderUtil.DataType.FLOAT_VEC3; case Token.TOKEN_FLOAT_VEC4: return gluShaderUtil.DataType.FLOAT_VEC4; case Token.TOKEN_FLOAT_MAT2: return gluShaderUtil.DataType.FLOAT_MAT2; case Token.TOKEN_FLOAT_MAT2X3: return gluShaderUtil.DataType.FLOAT_MAT2X3; case Token.TOKEN_FLOAT_MAT2X4: return gluShaderUtil.DataType.FLOAT_MAT2X4; case Token.TOKEN_FLOAT_MAT3X2: return gluShaderUtil.DataType.FLOAT_MAT3X2; case Token.TOKEN_FLOAT_MAT3: return gluShaderUtil.DataType.FLOAT_MAT3; case Token.TOKEN_FLOAT_MAT3X4: return gluShaderUtil.DataType.FLOAT_MAT3X4; case Token.TOKEN_FLOAT_MAT4X2: return gluShaderUtil.DataType.FLOAT_MAT4X2; case Token.TOKEN_FLOAT_MAT4X3: return gluShaderUtil.DataType.FLOAT_MAT4X3; case Token.TOKEN_FLOAT_MAT4: return gluShaderUtil.DataType.FLOAT_MAT4; case Token.TOKEN_INT: return gluShaderUtil.DataType.INT; case Token.TOKEN_INT_VEC2: return gluShaderUtil.DataType.INT_VEC2; case Token.TOKEN_INT_VEC3: return gluShaderUtil.DataType.INT_VEC3; case Token.TOKEN_INT_VEC4: return gluShaderUtil.DataType.INT_VEC4; case Token.TOKEN_UINT: return gluShaderUtil.DataType.UINT; case Token.TOKEN_UINT_VEC2: return gluShaderUtil.DataType.UINT_VEC2; case Token.TOKEN_UINT_VEC3: return gluShaderUtil.DataType.UINT_VEC3; case Token.TOKEN_UINT_VEC4: return gluShaderUtil.DataType.UINT_VEC4; case Token.TOKEN_BOOL: return gluShaderUtil.DataType.BOOL; case Token.TOKEN_BOOL_VEC2: return gluShaderUtil.DataType.BOOL_VEC2; case Token.TOKEN_BOOL_VEC3: return gluShaderUtil.DataType.BOOL_VEC3; case Token.TOKEN_BOOL_VEC4: return gluShaderUtil.DataType.BOOL_VEC4; default: return gluShaderUtil.DataType.INVALID; } }; /** * Returns the corresponding token's name depending on enum number value * @param {number} token * @return {string} */ var getTokenName = function(token) { switch (token) { case Token.TOKEN_INVALID: return ''; case Token.TOKEN_EOF: return ''; case Token.TOKEN_STRING: return ''; case Token.TOKEN_SHADER_SOURCE: return 'source'; case Token.TOKEN_INT_LITERAL: return ''; case Token.TOKEN_FLOAT_LITERAL: return ''; // identifiers case Token.TOKEN_IDENTIFIER: return ''; case Token.TOKEN_TRUE: return 'true'; case Token.TOKEN_FALSE: return 'false'; case Token.TOKEN_DESC: return 'desc'; case Token.TOKEN_EXPECT: return 'expect'; case Token.TOKEN_GROUP: return 'group'; case Token.TOKEN_CASE: return 'case'; case Token.TOKEN_END: return 'end'; case Token.TOKEN_VALUES: return 'values'; case Token.TOKEN_BOTH: return 'both'; case Token.TOKEN_VERTEX: return 'vertex'; case Token.TOKEN_FRAGMENT: return 'fragment'; case Token.TOKEN_UNIFORM: return 'uniform'; case Token.TOKEN_INPUT: return 'input'; case Token.TOKEN_OUTPUT: return 'output'; case Token.TOKEN_FLOAT: return 'float'; case Token.TOKEN_FLOAT_VEC2: return 'vec2'; case Token.TOKEN_FLOAT_VEC3: return 'vec3'; case Token.TOKEN_FLOAT_VEC4: return 'vec4'; case Token.TOKEN_FLOAT_MAT2: return 'mat2'; case Token.TOKEN_FLOAT_MAT2X3: return 'mat2x3'; case Token.TOKEN_FLOAT_MAT2X4: return 'mat2x4'; case Token.TOKEN_FLOAT_MAT3X2: return 'mat3x2'; case Token.TOKEN_FLOAT_MAT3: return 'mat3'; case Token.TOKEN_FLOAT_MAT3X4: return 'mat3x4'; case Token.TOKEN_FLOAT_MAT4X2: return 'mat4x2'; case Token.TOKEN_FLOAT_MAT4X3: return 'mat4x3'; case Token.TOKEN_FLOAT_MAT4: return 'mat4'; case Token.TOKEN_INT: return 'int'; case Token.TOKEN_INT_VEC2: return 'ivec2'; case Token.TOKEN_INT_VEC3: return 'ivec3'; case Token.TOKEN_INT_VEC4: return 'ivec4'; case Token.TOKEN_UINT: return 'uint'; case Token.TOKEN_UINT_VEC2: return 'uvec2'; case Token.TOKEN_UINT_VEC3: return 'uvec3'; case Token.TOKEN_UINT_VEC4: return 'uvec4'; case Token.TOKEN_BOOL: return 'bool'; case Token.TOKEN_BOOL_VEC2: return 'bvec2'; case Token.TOKEN_BOOL_VEC3: return 'bvec3'; case Token.TOKEN_BOOL_VEC4: return 'bvec4'; case Token.TOKEN_ASSIGN: return '='; case Token.TOKEN_PLUS: return '+'; case Token.TOKEN_MINUS: return '-'; case Token.TOKEN_COMMA: return ','; case Token.TOKEN_VERTICAL_BAR: return '|'; case Token.TOKEN_SEMI_COLON: return ';'; case Token.TOKEN_LEFT_PAREN: return '('; case Token.TOKEN_RIGHT_PAREN: return ')'; case Token.TOKEN_LEFT_BRACKET: return '['; case Token.TOKEN_RIGHT_BRACKET: return ']'; case Token.TOKEN_LEFT_BRACE: return ' {'; case Token.TOKEN_RIGHT_BRACE: return '}'; default: return ''; } }; /** * @param {?gluShaderUtil.DataType} expectedDataType * @param {Object} result */ var parseValueElement = function(expectedDataType, result) { /** @type {?string} */ var scalarType = null; /** @type {number} */ var scalarSize = 0; if (expectedDataType) { scalarType = gluShaderUtil.getDataTypeScalarType(expectedDataType); scalarSize = gluShaderUtil.getDataTypeScalarSize(expectedDataType); } /** @type {Array} */ var elems = []; if (scalarSize > 1) { glsShaderLibrary.de_assert(mapDataTypeToken(m_curToken) === expectedDataType); advanceToken(); // data type(float, vec2, etc.) advanceToken(Token.TOKEN_LEFT_PAREN); } for (var i = 0; i < scalarSize; ++i) { if (scalarType === 'float') { /** @type {number} */ var signMult = 1.0; if (m_curToken === Token.TOKEN_MINUS) { signMult = -1.0; advanceToken(); } assumeToken(Token.TOKEN_FLOAT_LITERAL); elems.push(signMult * parseFloatLiteral(m_curTokenStr)); advanceToken(Token.TOKEN_FLOAT_LITERAL); } else if (scalarType === 'int' || scalarType === 'uint') { var signMult = 1; if (m_curToken === Token.TOKEN_MINUS) { signMult = -1; advanceToken(); } assumeToken(Token.TOKEN_INT_LITERAL); elems.push(signMult * parseIntLiteral(m_curTokenStr)); advanceToken(Token.TOKEN_INT_LITERAL); } else { glsShaderLibrary.de_assert(scalarType === 'bool'); elems.push(m_curToken === Token.TOKEN_TRUE); if (m_curToken != Token.TOKEN_TRUE && m_curToken != Token.TOKEN_FALSE) { throw Error('unexpected token, expecting bool: ' + m_curTokenStr); } advanceToken(); // true/false } if (i != (scalarSize - 1)) { advanceToken(Token.TOKEN_COMMA); } } if (scalarSize > 1) { advanceToken(Token.TOKEN_RIGHT_PAREN); } for (var i = 0; i < elems.length; i++) result.elements.push(elems[i]); }; /** * @param {Object.} valueBlock */ var parseValue = function(valueBlock) { /** * @type {Object} */ var result = { /** @type {?gluShaderUtil.DataType} */ dataType: null, /** @type {?glsShaderLibraryCase.shaderCase} */ storageType: null, /** @type {?string} */ valueName: null, /** @type {Array} */ elements: [] }; // parse storage switch (m_curToken) { case Token.TOKEN_UNIFORM: result.storageType = glsShaderLibraryCase.shaderCase.STORAGE_UNIFORM; break; case Token.TOKEN_INPUT: result.storageType = glsShaderLibraryCase.shaderCase.STORAGE_INPUT; break; case Token.TOKEN_OUTPUT: result.storageType = glsShaderLibraryCase.shaderCase.STORAGE_OUTPUT; break; default: throw Error('unexpected token encountered when parsing value classifier'); break; } advanceToken(); // parse data type result.dataType = mapDataTypeToken(m_curToken); if (result.dataType === gluShaderUtil.DataType.INVALID) { throw Error('unexpected token when parsing value data type: ' + m_curTokenStr); } advanceToken(); // parse value name if (m_curToken === Token.TOKEN_IDENTIFIER) { result.valueName = m_curTokenStr; } else if (m_curToken === Token.TOKEN_STRING) { result.valueName = parseStringLiteral(m_curTokenStr); } else { throw Error('unexpected token when parsing value name: ' + m_curTokenStr); } advanceToken(); // parse assignment operator. advanceToken(Token.TOKEN_ASSIGN); // parse actual value if (m_curToken === Token.TOKEN_LEFT_BRACKET) { // value list advanceToken(Token.TOKEN_LEFT_BRACKET); result.arrayLength = 0; for (;;) { parseValueElement(result.dataType, result); result.arrayLength += 1; if (m_curToken === Token.TOKEN_RIGHT_BRACKET) { break; } else if (m_curToken === Token.TOKEN_VERTICAL_BAR) { // pipe? advanceToken(); continue; } else { throw Error('unexpected token in value element array: ' + m_curTokenStr); } } advanceToken(Token.TOKEN_RIGHT_BRACKET); } else { // arrays, single elements parseValueElement(result.dataType, result); result.arrayLength = 1; } advanceToken(Token.TOKEN_SEMI_COLON); valueBlock.values.push(result); }; /** * @param {Object.} valueBlock */ var parseValueBlock = function(valueBlock) { advanceToken(Token.TOKEN_VALUES); advanceToken(Token.TOKEN_LEFT_BRACE); for (;;) { if ( m_curToken === Token.TOKEN_UNIFORM || m_curToken === Token.TOKEN_INPUT || m_curToken === Token.TOKEN_OUTPUT ) { parseValue(valueBlock); } else if (m_curToken === Token.TOKEN_RIGHT_BRACE) { break; } else { throw Error('unexpected( token when parsing a value block: ' + m_curTokenStr); } } advanceToken(Token.TOKEN_RIGHT_BRACE); /** @type {number} */ var arrayLength = 1; // compute combined array length of value block. for (var i = 0; i < valueBlock.values.length; ++i) { if (valueBlock.values[i].arrayLength > 1) { glsShaderLibrary.de_assert(arrayLength === 1 || arrayLength === valueBlock.values[i].arrayLength); arrayLength = valueBlock.values[i].arrayLength; } } valueBlock.arrayLength = arrayLength; }; /** * @param {Array} shaderNodeList */ var parseShaderCase = function(shaderNodeList) { // parse case advanceToken(Token.TOKEN_CASE); /** * @type {string} * parse case name */ var caseName = m_curTokenStr; advanceToken(); // \note [pyry] All token types are allowed here. /** * @type {Array} * setup case */ var valueBlockList = []; /** TODO: Should the default version be defined elsewhere? */ /** @type {string} */ var version = '100'; /** @type {number} */ var expectResult = glsShaderLibraryCase.expectResult.EXPECT_PASS; /** @type {string} */ var description; /** @type {string} */ var bothSource = ''; /** @type {string} */ var vertexSource = ''; /** @type {string} */ var fragmentSource = ''; for (;;) { if (m_curToken === Token.TOKEN_END) { break; } else if (m_curToken === Token.TOKEN_DESC) { advanceToken(); assumeToken(Token.TOKEN_STRING); description = parseStringLiteral(m_curTokenStr); advanceToken(); } else if (m_curToken === Token.TOKEN_EXPECT) { advanceToken(); assumeToken(Token.TOKEN_IDENTIFIER); expectResult = (function(token) { switch (token) { case 'pass': return glsShaderLibraryCase.expectResult.EXPECT_PASS; case 'compile_fail': return glsShaderLibraryCase.expectResult.EXPECT_COMPILE_FAIL; case 'link_fail': return glsShaderLibraryCase.expectResult.EXPECT_LINK_FAIL; case 'compile_or_link_fail': return glsShaderLibraryCase.expectResult.EXPECT_COMPILE_LINK_FAIL; case 'build_successful': return glsShaderLibraryCase.expectResult.EXPECT_BUILD_SUCCESSFUL; default: throw Error('invalid expected result value: ' + m_curTokenStr); } }(m_curTokenStr)); advanceToken(); } else if (m_curToken === Token.TOKEN_VALUES) { /** @type {Object.} */ var block = glsShaderLibraryCase.genValueBlock(); parseValueBlock(block); valueBlockList.push(block); } else if ( m_curToken === Token.TOKEN_BOTH || m_curToken === Token.TOKEN_VERTEX || m_curToken === Token.TOKEN_FRAGMENT ) { /** @type {number} */ var token = m_curToken; advanceToken(); assumeToken(Token.TOKEN_SHADER_SOURCE); /** @type {string} */ var source = parseShaderSource(m_curTokenStr); advanceToken(); switch (token) { case Token.TOKEN_BOTH: bothSource = source; break; case Token.TOKEN_VERTEX: vertexSource = source; break; case Token.TOKEN_FRAGMENT: fragmentSource = source; break; default: glsShaderLibrary.de_assert(false); break; } } else if (m_curToken === Token.TOKEN_VERSION) { advanceToken(); /** @type {number} */ var versionNum = 0; /** @type {string} */ var postfix = ''; assumeToken(Token.TOKEN_INT_LITERAL); versionNum = parseIntLiteral(m_curTokenStr); advanceToken(); if (m_curToken === Token.TOKEN_IDENTIFIER) { postfix = m_curTokenStr; advanceToken(); } // TODO: need to fix these constants, we dont have glu if (versionNum === 100 && postfix === 'es') version = '100'; else if (versionNum === 300 && postfix === 'es') version = '300 es'; else if (versionNum === 310 && postfix === 'es') version = '310 es'; else if (versionNum === 130) version = '130'; else if (versionNum === 140) version = '140'; else if (versionNum === 150) version = '150'; else if (versionNum === 330) version = '330'; else if (versionNum === 400) version = '400'; else if (versionNum === 410) version = '410'; else if (versionNum === 420) version = '420'; else if (versionNum === 430) version = '430'; else if (versionNum === 440) version = '440'; else if (versionNum === 450) version = '450'; else { throw Error('Unknown GLSL version'); } } else { throw Error('unexpected token while parsing shader case: ' + m_curTokenStr); } } advanceToken(Token.TOKEN_END); // case end /** * no ShaderCase yet? * @param {?string} vert * @param {?string} frag * @param {glsShaderLibraryCase.caseType} type * @return {Object} */ var getShaderSpec = function(vert, frag, type) { return { /** @type {glsShaderLibraryCase.expectResult} */ expectResult: expectResult, /** @type {glsShaderLibraryCase.caseType} */ caseType: type, /** @type {Array} */ valueBlockList: valueBlockList, /** @type {string} */ targetVersion: version, /** @type {?string} */ vertexSource: vert, /** @type {?string} */ fragmentSource: frag }; }; getShaderSpec.bind(this); if (bothSource.length) { glsShaderLibrary.de_assert(!vertexSource); glsShaderLibrary.de_assert(!fragmentSource); shaderNodeList.push(tcuTestCase.newTest(caseName + '_vertex', description, getShaderSpec(bothSource, null, glsShaderLibraryCase.caseType.CASETYPE_VERTEX_ONLY))); shaderNodeList.push(tcuTestCase.newTest(caseName + '_fragment', description, getShaderSpec(null, bothSource, glsShaderLibraryCase.caseType.CASETYPE_FRAGMENT_ONLY))); } else { glsShaderLibrary.de_assert(vertexSource); glsShaderLibrary.de_assert(fragmentSource); shaderNodeList.push(tcuTestCase.newTest(caseName, description, getShaderSpec(vertexSource, fragmentSource, glsShaderLibraryCase.caseType.CASETYPE_COMPLETE))); } }; /** * @param {Array} shaderNodeList */ var parseShaderGroup = function(shaderNodeList) { // parse 'case' advanceToken(Token.TOKEN_GROUP); /** @type {string} * parse case name */ var 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.TOKEN_STRING); /** @type {string} */ var description = parseStringLiteral(m_curTokenStr); advanceToken(Token.TOKEN_STRING); /** @type {Array} */ var children = []; for (;;) { if (m_curToken === Token.TOKEN_END) { break; } else if (m_curToken === Token.TOKEN_GROUP) { parseShaderGroup(children); } else if (m_curToken === Token.TOKEN_CASE) { parseShaderCase(children); } else { testFailed('unexpected token while parsing shader group: ' + m_curTokenStr); tcuTestCase.runner.terminate(); } } advanceToken(Token.TOKEN_END); // group end /** @type {tcuTestCase.DeqpTest} */ var groupNode = tcuTestCase.newTest(name, description, null); groupNode.setChildren(children); shaderNodeList.push(groupNode); }; // uncomment to expose private functions (function(obj) { obj.priv = { m_curPtr: m_curPtr, parseError: parseError, parseFloatLiteral: parseFloatLiteral, parseIntLiteral: parseIntLiteral, parseStringLiteral: parseStringLiteral, parseShaderSource: parseShaderSource, advanceTokenTester: advanceTokenTester, assumeToken: assumeToken, mapDataTypeToken: mapDataTypeToken, getTokenName: getTokenName, Token: Token, parseValueElement: parseValueElement, parseValue: parseValue, parseValueBlock: parseValueBlock, parseShaderCase: parseShaderCase, parseShaderGroup: parseShaderGroup, none: false }; }(this)); //*/ }; /** * Parse the test file and execute the test cases * @param {string} testName Name of the test file (without extension) * @param {string} filter Optional filter. Common substring of the names of the tests that should be glsShaderLibrary.run. */ glsShaderLibrary.run = function(testName, filter) { WebGLTestUtils.loadTextFileAsync(testName + '.test', function(success, content) { if (success) { tcuTestCase.runner.testFile = content; tcuTestCase.runner.testName = testName; tcuTestCase.runner.runCallback(glsShaderLibrary.processTestFile); } else { testFailed('Failed to load test file: ' + testName); tcuTestCase.runner.terminate(); } }); }; });