/* Copyright (c) 2019 The Khronos Group Inc. Use of this source code is governed by an MIT-style license that can be found in the LICENSE.txt file. */ var GLSLConstructorTestsGenerator = (function() { var wtu = WebGLTestUtils; // Shader code templates var constructorVertexTemplate = [ "attribute vec4 vPosition;", "precision mediump int;", "precision mediump float;", // Colors used to signal correctness of component values comparison "const vec4 green = vec4(0.0, 1.0, 0.0, 1.0);", "const vec4 red = vec4(1.0, 0.0, 0.0, 1.0);", // Error bound used in comparison of floating point values "$(errorBound)", "varying vec4 vColor;", "void main() {", " $(argsList)", " $(type) v = $(type)($(argsConstr));", " if ($(checkCompVals))", " vColor = green;", " else", " vColor = red;", " gl_Position = vPosition;", "}" ].join("\n"); var passThroughColorFragmentShader = [ "precision mediump float;", "varying vec4 vColor;", "void main() {", " gl_FragColor = vColor;", "}" ].join('\n'); var constructorFragmentTemplate = [ "precision mediump int;", "precision mediump float;", // Colors used to signal correctness of component values comparison "const vec4 green = vec4(0.0, 1.0, 0.0, 1.0); ", "const vec4 red = vec4(1.0, 0.0, 0.0, 1.0); ", // Error bound used in comparison of floating point values "$(errorBound)", "void main() {", " $(argsList)", " $(type) v = $(type)($(argsConstr));", " if ($(checkCompVals))", " gl_FragColor = green;", " else", " gl_FragColor = red;", "}" ].join("\n"); // Coding of the different argument types // s : scalar // v2 : vec2 // v3 : vec3 // v4 : vec4 // m2 : mat2 // m3 : mat3 // m4 : mat4 // Returns the dimensions of the type // Count of columns, count of rows function getTypeCodeDimensions(typeCode) { switch (typeCode) { case "s": return [1, 1]; case "v2": return [1, 2]; case "v3": return [1, 3]; case "v4": return [1, 4]; case "m2": return [2, 2]; case "m3": return [3, 3]; case "m4": return [4, 4]; default: wtu.error("GLSLConstructorTestsGenerator.getTypeCodeDimensions(), unknown type code"); debugger; } }; // Returns the component count for the type code function getTypeCodeComponentCount(typeCode) { var dim = getTypeCodeDimensions(typeCode); return dim[0] * dim[1]; } // Returns glsl name of type code function getGLSLBaseTypeName(typeCode) { switch(typeCode) { case "s": return ""; case "v2": return "vec2"; case "v3": return "vec3"; case "v4": return "vec4"; case "m2": return "mat2"; case "m3": return "mat3"; case "m4": return "mat4"; default: wtu.error("GLSLConstructorTestsGenerator.getGLSLBaseTypeName(), unknown type code"); debugger; } } // Returns the scalar glsl type name related to the structured type function getGLSLScalarType(targetType) { switch(targetType[0]) { case 'i': return "int"; case 'b': return "bool"; case 'v': case 'm': return "float"; default: wtu.error("GLSLConstructorTestsGenerator.getGLSLScalarType(), unknown target type"); debugger; } } // Returns the scalar prefix for the associated scalar type function getGLSLScalarPrefix(targetType) { switch(targetType[0]) { case 'i': case 'b': return targetType[0]; case 'v': case 'm': return ''; default: wtu.error("GLSLConstructorTestsGenerator.getGLSLScalarPrefix(), unknown target type"); debugger; } } // Returns the type for a specified target type and argument type code function getGLSLArgumentType(typeCode, targetType) { var baseType = getGLSLBaseTypeName(typeCode); if (baseType !== "") { if (typeCode[0] === "v") { // Vectors come in different flavours return getGLSLScalarPrefix(targetType) + baseType; } else return baseType; } else return getGLSLScalarType(targetType); } // Returns the glsl type of the argument components function getGLSLArgumentComponentType(argTypeCode, targetType) { var scalarType; if (argTypeCode[0] === "m") { // Matrices are always floats scalarType = "float"; } else scalarType = getGLSLScalarType(targetType); return scalarType; } function getGLSLColumnSize(targetType) { colSize = parseInt(targetType.slice(-1)); if (!isNaN(colSize)) return colSize; wtu.error("GLSLConstructorTestsGenerator.getGLSLColumnSize(), invalid target type"); debugger; } // Returns correct string representation of scalar value function getScalarTypeValStr(val, scalarType) { if (val == null) debugger; switch (scalarType) { case "float": return val.toFixed(1); case "int": return val; case "bool": return (val === 0) ? "false" : "true"; default: wtu.error("GLSLConstructorTestsGenerator.getScalarTypeValStr(), unknown scalar type"); debugger; } } // Returns true if the glsl type name is a matrix function isGLSLTypeMatrix(type) { return (type.indexOf("mat") !== -1); } // Returns true if the glsl type name is a vector function isGLSLTypeVector(type) { return (type.indexOf("vec") !== -1); } // Returns the count of components function getGLSLTypeComponentCount(type) { var colSize = getGLSLColumnSize(type); if (isGLSLTypeMatrix(type)) return colSize * colSize; else return colSize; } // Returns the constructor expression with the components set to a sequence of scalar values // Like vec3(1.0, 2.0, 3.0) function getComponentSequenceConstructorExpression(typeCode, firstCompValue, targetType) { var scalarType = getGLSLArgumentComponentType(typeCode, targetType); if (typeCode === "s") { // Scalar return getScalarTypeValStr(firstCompValue, scalarType) + ";"; } else { // Structured typeargTypeCode[0] === "m" compCount = getTypeCodeComponentCount(typeCode); var constrExpParts = new Array(compCount); for (var aa = 0; aa < compCount; ++aa) constrExpParts[aa] = getScalarTypeValStr(firstCompValue + aa, scalarType); return getGLSLArgumentType(typeCode, targetType) + "(" + constrExpParts.join(", ") + ");"; } } // Returns the expression to select a component of the structured type function getComponentSelectorExpStr(targetType, compIx) { if (isGLSLTypeMatrix(targetType)) { var colRowIx = getColRowIndexFromLinearIndex(compIx, getGLSLColumnSize(targetType)); return "v[" + colRowIx.colIx + "][" + colRowIx.rowIx + "]"; } else return "v[" + compIx + "]"; } // Returns expression which validates the components set by the constructor expression function getComponentValidationExpression(refCompVals, targetType) { // Early out for invalid arguments if (refCompVals.length === 0) return "false"; var scalarType = getGLSLScalarType(targetType); var checkComponentValueParts = new Array(refCompVals.length); for (var cc = 0; cc < refCompVals.length; ++cc) { var val_str = getScalarTypeValStr(refCompVals[cc], scalarType); var comp_sel_exp = getComponentSelectorExpStr(targetType, cc); if (scalarType === "float") { // Comparison of floating point values with error bound checkComponentValueParts[cc] = "abs(" + comp_sel_exp + " - " + val_str + ") <= errorBound"; } else { // Simple comparison to expected value checkComponentValueParts[cc] = comp_sel_exp + " == " + val_str; } } return checkComponentValueParts.join(" && "); } // Returns substitution parts to turn the shader template into testable shader code function getTestShaderParts(targetType, argExp, firstCompValue) { // glsl code of declarations of arguments var argsListParts = new Array(argExp.length); // glsl code of constructor expression var argsConstrParts = new Array(argExp.length); // glsl type expression var typeExpParts = new Array(argExp.length); for (var aa = 0; aa < argExp.length; ++aa) { var typeCode = argExp[aa]; var argCompCount = getTypeCodeComponentCount(typeCode); var argName = "a" + aa; var argType = getGLSLArgumentType(typeCode, targetType); var argConstrExp = argType + " " + argName + " = " + getComponentSequenceConstructorExpression(typeCode, firstCompValue, targetType); // Add construction of one argument // Indent if not first argument argsListParts[aa] = ((aa > 0) ? " " : "") + argConstrExp; // Add argument name to target type argument list argsConstrParts[aa] = argName; // Add type name to type expression typeExpParts[aa] = argType; // Increment argument component value so all argument component arguments have a unique value firstCompValue += argCompCount; } return { argsList: argsListParts.join("\n") + "\n", argsConstr: argsConstrParts.join(", "), typeExp: targetType + "(" + typeExpParts.join(", ") + ")" }; } // Utility functions to manipulate the array of reference values // Returns array filled with identical values function getArrayWithIdenticalValues(size, val) { var matArray = new Array(size); for (var aa = 0; aa < size; ++aa) matArray[aa] = val; return matArray; } // Returns array filled with increasing values from a specified start value function getArrayWithIncreasingValues(size, start) { var matArray = new Array(size); for (var aa = 0; aa < size; ++aa) matArray[aa] = start + aa; return matArray; } // Utility functions to manipulate the array of reference values if the target type is a matrix // Returns an array which is the column order layout of a square matrix where the diagonal is set to a specified value function matCompArraySetDiagonal(matArray, diagVal) { // The entries for the diagonal start at array index 0 and increase // by column size + 1 var colSize = Math.round(Math.sqrt(matArray.length)); var dIx = 0; do { matArray[dIx] = diagVal; dIx += (colSize + 1); } while (dIx < colSize * colSize); return matArray; } // Returns an array which contains the values of an identity matrix read out in column order function matCompArrayCreateDiagonalMatrix(colSize, diagVal) { var size = colSize * colSize; var matArray = new Array(size); for (var aa = 0; aa < size; ++aa) matArray[aa] = 0; return matCompArraySetDiagonal(matArray, diagVal); } // Returns the column and row index from the linear index if the components of the matrix are stored in column order in an array // in a one dimensional array in column order function getColRowIndexFromLinearIndex(linIx, colSize) { return { colIx: Math.floor(linIx / colSize), rowIx: linIx % colSize }; } // Returns the linear index for matrix column and row index for a specified matrix size function getLinearIndexFromColRowIndex(rowColIx, colSize) { return rowColIx.colIx * colSize + rowColIx.rowIx; } // Returns a matrix set from another matrix function matCompArraySetMatrixFromMatrix(dstColSize, srcMatArray) { // Overwrite components from destination with the source component values at the same col, row coordinates var dstMatArray = matCompArrayCreateDiagonalMatrix(dstColSize, 1); var srcColSize = Math.round(Math.sqrt(srcMatArray.length)); for (var c_ix = 0; c_ix < srcMatArray.length; ++c_ix) { var srcMatIx = getColRowIndexFromLinearIndex(c_ix, srcColSize); if (srcMatIx.colIx < dstColSize && srcMatIx.rowIx < dstColSize) { // Source matrix coordinates are valid destination matrix coordinates dstMatArray[getLinearIndexFromColRowIndex(srcMatIx, dstColSize)] = srcMatArray[c_ix]; } } return dstMatArray; } // Returns the glsl code to verify if the components are set correctly // and the message to display for the test function getConstructorExpressionInfo(targetType, argExp, firstCompValue) { var argCompCountsSum = 0; var argCompCounts = new Array(argExp.length); for (var aa = 0; aa < argExp.length; ++aa) { argCompCounts[aa] = getTypeCodeComponentCount(argExp[aa]); argCompCountsSum += argCompCounts[aa]; } var targetCompCount = getGLSLTypeComponentCount(targetType); var refCompVals; var testMsg; var valid; if (argCompCountsSum === 0) { // A constructor needs at least one argument refCompVals = []; testMsg = "invalid (no arguments)"; valid = false; } else { if (isGLSLTypeVector(targetType)) { if (argCompCountsSum === 1) { // One scalar argument // Vector constructor with one scalar argument set all components to the same value refCompVals = getArrayWithIdenticalValues(targetCompCount, firstCompValue); testMsg = "valid (all components set to the same value)"; valid = true; } else { // Not one scalar argument if (argCompCountsSum < targetCompCount) { // Not all components set refCompVals = []; testMsg = "invalid (not enough arguments)"; valid = false; } else { // argCompCountsSum >= targetCompCount // All components set var lastArgFirstCompIx = argCompCountsSum - argCompCounts[argCompCounts.length - 1]; if (lastArgFirstCompIx < targetCompCount) { // First component of last argument is used refCompVals = getArrayWithIncreasingValues(targetCompCount, firstCompValue); testMsg = "valid"; valid = true; } else { // First component of last argument is not used refCompVals = []; testMsg = "invalid (unused argument)"; valid = false; } } } } else { // Matrix target type if (argCompCountsSum === 1) { // One scalar argument // Matrix constructors with one scalar set all components on the diagonal to the same value // All other components are set to zero refCompVals = matCompArrayCreateDiagonalMatrix(Math.round(Math.sqrt(targetCompCount)), firstCompValue); testMsg = "valid (diagonal components set to the same value, off-diagonal components set to zero)"; valid = true; } else { // Not one scalar argument if (argExp.length === 1 && argExp[0][0] === "m") { // One single matrix argument var dstColSize = getGLSLColumnSize(targetType); refCompVals = matCompArraySetMatrixFromMatrix(dstColSize, getArrayWithIncreasingValues(getTypeCodeComponentCount(argExp[0]), firstCompValue)); testMsg = "valid, components at corresponding col, row indices are set from argument, other components are set from identity matrix"; valid = true; } else { // More than one argument or one argument not of type matrix // Can be treated in the same manner // Arguments can not be of type matrix var matFound = false; for (var aa = 0; aa < argExp.length; ++aa) if (argExp[aa][0] === "m") matFound = true; if (matFound) { refCompVals = []; testMsg = "invalid, argument list greater than one contains matrix type"; valid = false; } else { if (argCompCountsSum < targetCompCount) { refCompVals = []; testMsg = "invalid (not enough arguments)"; valid = false; } else { // argCompCountsSum >= targetCompCount // All components set var lastArgFirstCompIx = argCompCountsSum - argCompCounts[argCompCounts.length - 1]; if (lastArgFirstCompIx < targetCompCount) { // First component of last argument is used refCompVals = getArrayWithIncreasingValues(targetCompCount, firstCompValue); testMsg = "valid"; valid = true; } else { // First component of last argument is not used refCompVals = []; testMsg = "invalid (unused argument)"; valid = false; } } } } } } } // Check if no case is missed if (testMsg == null || valid == null) { wtu.error("GLSLConstructorTestsGenerator.getConstructorExpressionInfo(), info not set"); debugger; } return { refCompVals: refCompVals, testMsg: testMsg, valid: valid }; } // Returns a vertex shader testcase and a fragment shader testcase function getVertexAndFragmentShaderTestCase(targetType, argExp) { var firstCompValue = 0; if (isGLSLTypeMatrix(targetType)) { // Use value different from 0 and 1 // 0 and 1 are values used by matrix constructed from a matrix or a single scalar firstCompValue = 2; } var argCode = getTestShaderParts (targetType, argExp, firstCompValue); var expInfo = getConstructorExpressionInfo(targetType, argExp, firstCompValue); var substitutions = { type: targetType, errorBound: (getGLSLScalarType(targetType) === "float") ? "const float errorBound = 1.0E-5;" : "", argsList: argCode.argsList, argsConstr: argCode.argsConstr, checkCompVals: getComponentValidationExpression(expInfo.refCompVals, targetType) }; return [ { // Test constructor argument list in vertex shader vShaderSource: wtu.replaceParams(constructorVertexTemplate, substitutions), vShaderSuccess: expInfo.valid, fShaderSource: passThroughColorFragmentShader, fShaderSuccess: true, linkSuccess: expInfo.valid, passMsg: "Vertex shader : " + argCode.typeExp + ", " + expInfo.testMsg, render: expInfo.valid }, { // Test constructor argument list in fragment shader fShaderSource: wtu.replaceParams(constructorFragmentTemplate, substitutions), fShaderSuccess: expInfo.valid, linkSuccess: expInfo.valid, passMsg: "Fragment shader : " + argCode.typeExp + ", " + expInfo.testMsg, render: expInfo.valid } ]; } // Incrementing the argument expressions // Utility object which defines the order of incrementing the argument types var typeCodeIncrementer = { s: { typeCode: "v2", order: 0 }, v2: { typeCode: "v3", order: 1 }, v3: { typeCode: "v4", order: 2 }, v4: { typeCode: "m2", order: 3 }, m2: { typeCode: "m3", order: 4 }, m3: { typeCode: "m4", order: 5 }, m4: { typeCode: "s", order: 6 }, first: "s" } // Returns the next argument sequence function getNextArgumentSequence(inSeq) { var nextSeq; if (inSeq.length === 0) { // Current argument sequence is empty, add first argument nextSeq = [typeCodeIncrementer.first]; } else { nextSeq = new Array(inSeq.length); var overflow = true; for (var aa = 0; aa < inSeq.length; ++aa) { var currArg = inSeq[aa]; if (overflow) { // Increment the current argument type var nextArg = typeCodeIncrementer[currArg].typeCode; nextSeq[aa] = nextArg; overflow = (nextArg === typeCodeIncrementer.first); } else { // Copy remainder of sequence nextSeq[aa] = currArg; } } if (overflow) { nextSeq.push(typeCodeIncrementer.first); } } return nextSeq; } // Returns true if two argument expressions are equal function areArgExpEqual(expA, expB) { if (expA.length !== expB.length) return false; for (var aa = 0; aa < expA.length; ++aa) if (expA[aa] !== expB[aa]) return false; return true; } // Returns true if first argument expression is smaller // (comes before the second one in iterating order) // compared to the second argument expression function isArgExpSmallerOrEqual(argExpA, argExpB) { var aLen = argExpA.length; var bLen = argExpB.length; if (aLen !== bLen) return (aLen < bLen); // Argument type expression lengths are equal for (var aa = aLen - 1; aa >= 0; --aa) { var argA = argExpA[aa]; var argB = argExpB[aa]; if (argA !== argB) { var aOrder = typeCodeIncrementer[argA].order; var bOrder = typeCodeIncrementer[argB].order; if (aOrder !== bOrder) return (aOrder < bOrder); } } // Argument type expressions are equal return true; } // Returns the next argument expression from sequence set // Returns null if end is reached function getNextArgumentExpression(testExp, testSet) { var testInterval = testSet[testExp.ix]; if (areArgExpEqual(testExp.argExp, testInterval[1])) { // End of current interval reached if (testExp.ix === testSet.length - 1) { // End of set reached return null; } else { // Return first argument expression of next interval var nextIx = testExp.ix + 1; return { ix: nextIx, argExp: testSet[nextIx][0] }; } } else { // Return next expression in current interval return { ix: testExp.ix, argExp: getNextArgumentSequence(testExp.argExp) }; } } // Returns an array of the parts in the string separated by commas and with the white space trimmed function convertCsvToArray(str) { // Checks type codes in input function checkInput(el, ix, arr) { var typeCode = el.trim(); if (!(typeCode in typeCodeIncrementer) && typeCode !== "first") { wtu.error("GLSLConstructorTestsGenerator.convertCsvToArray(), unknown type code" + typeCode); debugger; } arr[ix] = typeCode; } var spArr = str.split(","); // Convert empty string to empty array if (spArr.length === 1 && spArr[0].trim() === "") spArr = []; spArr.forEach(checkInput); return spArr; } // Processes the set of specified test sequences function processInputs(testSequences) { var testSet = new Array(testSequences.length); for (var tt = 0; tt < testSequences.length; ++tt) { var interval = testSequences[tt]; var bounds = interval.split("-"); var begin = convertCsvToArray(bounds[0]); var end = convertCsvToArray(bounds[bounds.length - 1]); // Check if interval is valid if (!isArgExpSmallerOrEqual(begin, end)) { wtu.error("GLSLConstructorTestsGenerator.processInputs(), interval not valid"); debugger; } testSet[tt] = [ begin, end ]; } return testSet; } /** * Returns list of test cases for vector types * All combinations of arguments up to one unused argument of one component are tested * @param {targetType} Name of target type to test the constructor expressions on * @param {testSet} Set of intervals of argument sequences to test */ function getConstructorTests(targetType, testSequences) { // List of tests to return var testInfos = []; // List of argument types var testSet = processInputs(testSequences); var testExp = { ix: 0, argExp: testSet[0][0] }; do { // Add one vertex shader test case and one fragment shader test case testInfos = testInfos.concat(getVertexAndFragmentShaderTestCase(targetType, testExp.argExp)); // Generate next argument expression testExp = getNextArgumentExpression(testExp, testSet); } while (testExp != null); return testInfos; } // Returns default test argument expression set // For details on input format : see bottom of file function getDefaultTestSet(targetType) { switch(targetType) { case "vec2": case "ivec2": case "bvec2": return [ // No arguments and all single argument expressions " - m4", // All two argument expressions with a scalar as second argument "s, s - m4, s", // All two arguments expressions with a scalar as first argument "s, v2", "s, v3", "s, v4", "s, m2", "s, m3", "s, m4", // Three argument expression "s, s, s" ]; case "vec3": case "ivec3": case "bvec3": return [ // No arguments and all single argument expressions " - m4", // All two argument expressions with a scalar as second argument "s, s - m4, s", // All two argument expressions with a scalar as first argument "s, v2", "s, v3", "s, v4", "s, m2", "s, m3", "s, m4", // All three argument expressions with two scalars as second and third argument "s, s, s - m4, s, s", // All three argument expressions with two scalars as first and second argument "s, s, v2", "s, s, v3", "s, s, v4", "s, s, m2", "s, s, m3", "s, s, m4", // Four argument expression "s, s, s, s" ]; case "vec4": case "ivec4": case "bvec4": case "mat2": return [ // No arguments and all single argument expressions " - m4", // All two argument expressions with a scalar as second argument "s, s - m4, s", // All two argument expressions with a scalar as first argument "s, v2", "s, v3", "s, v4", "s, m2", "s, m3", "s, m4", // All three argument expressions with two scalars as second and third argument "s, s, s - m4, s, s", // All three argument expressions with two scalars as first and second argument "s, s, v2", "s, s, v3", "s, s, v4", "s, s, m2", "s, s, m3", "s, s, m4", // All four argument expressions with three scalars as second, third and fourth argument "s, s, s, s - m4, s, s, s", // All four argument expressions with three scalars as first, second and third argument "s, s, s, v2", "s, s, s, v3", "s, s, s, v4", "s, s, s, m2", "s, s, s, m3", "s, s, s, m4", // Five argument expression "s, s, s, s, s" ]; case "mat3": case "mat4": return [ // No arguments and all single argument expressions " - m4", // All two argument expressions with a scalar as second argument "s, s - m4, s", // All two argument expressions with a scalar as first argument "s, v2", "s, v3", "s, v4", "s, m2", "s, m3", "s, m4", // Several argument sequences "v4, s, v4", "v4, s, v3, v2", "v4, v4, v3, v2", "v4, v4, v4, v4", "v2, v2, v2, v2, v2", "v2, v2, v2, v2, v2, v2, v2, v2", "v3, v3, v3", "v3, v3, v3, s", "v3, v3, v3, v3, v3, s", "v3, v3, v3, v3, v3, s, s", ]; } } // Return publics return { getConstructorTests: getConstructorTests, getDefaultTestSet: getDefaultTestSet }; }()); // Input is an array of intervals of argument types // The generated test argument sequences are from (including) the lower interval boundary // until (including) the upper boundary // Coding and order of the different argument types : // s : scalar // v2 : vec2 // v3 : vec3 // v4 : vec4 // m2 : mat2 // m3 : mat3 // m4 : mat4 // One interval is put in one string // Low and high bound are separated by a dash. // If there is no dash it is regarded as an interval of one expression // The individual argument codes are separated by commas // The individual arguments are incremented from left to right // The left most argument is the one which is incremented first // Once the left most arguments wraps the second argument is increased // Examples : // "s - m4" : All single arguments from scalar up to (including) mat4 // "m2, s - m4, s" : All two argument expressions with a matrix argument as first argument and a scalar as second argument // " - m4, m4" : The empty argument, all one arguments and all two argument expressions // "m2, s, v3, m4" : One 4 argument expression : mat2, scalar, vec3, mat4