| /* |
| ** Copyright (c) 2012 The Khronos Group Inc. |
| ** |
| ** Permission is hereby granted, free of charge, to any person obtaining a |
| ** copy of this software and/or associated documentation files (the |
| ** "Materials"), to deal in the Materials without restriction, including |
| ** without limitation the rights to use, copy, modify, merge, publish, |
| ** distribute, sublicense, and/or sell copies of the Materials, and to |
| ** permit persons to whom the Materials are furnished to do so, subject to |
| ** the following conditions: |
| ** |
| ** The above copyright notice and this permission notice shall be included |
| ** in all copies or substantial portions of the Materials. |
| ** |
| ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
| ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
| ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, |
| ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. |
| */ |
| GLSLGenerator = (function() { |
| |
| var vertexShaderTemplate = [ |
| "attribute vec4 aPosition;", |
| "", |
| "varying vec4 vColor;", |
| "", |
| "$(extra)", |
| "$(emu)", |
| "", |
| "void main()", |
| "{", |
| " gl_Position = aPosition;", |
| " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", |
| " vec4 color = vec4(", |
| " texcoord,", |
| " texcoord.x * texcoord.y,", |
| " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", |
| " $(test)", |
| "}" |
| ].join("\n"); |
| |
| var fragmentShaderTemplate = [ |
| "precision mediump float;", |
| "", |
| "varying vec4 vColor;", |
| "", |
| "$(extra)", |
| "$(emu)", |
| "", |
| "void main()", |
| "{", |
| " $(test)", |
| "}" |
| ].join("\n"); |
| |
| var baseVertexShader = [ |
| "attribute vec4 aPosition;", |
| "", |
| "varying vec4 vColor;", |
| "", |
| "void main()", |
| "{", |
| " gl_Position = aPosition;", |
| " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", |
| " vColor = vec4(", |
| " texcoord,", |
| " texcoord.x * texcoord.y,", |
| " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", |
| "}" |
| ].join("\n"); |
| |
| var baseVertexShaderWithColor = [ |
| "attribute vec4 aPosition;", |
| "attribute vec4 aColor;", |
| "", |
| "varying vec4 vColor;", |
| "", |
| "void main()", |
| "{", |
| " gl_Position = aPosition;", |
| " vColor = aColor;", |
| "}" |
| ].join("\n"); |
| |
| var baseFragmentShader = [ |
| "precision mediump float;", |
| "varying vec4 vColor;", |
| "", |
| "void main()", |
| "{", |
| " gl_FragColor = vColor;", |
| "}" |
| ].join("\n"); |
| |
| var types = [ |
| { type: "float", |
| code: [ |
| "float $(func)_emu($(args)) {", |
| " return $(func)_base($(baseArgs));", |
| "}"].join("\n") |
| }, |
| { type: "vec2", |
| code: [ |
| "vec2 $(func)_emu($(args)) {", |
| " return vec2(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)));", |
| "}"].join("\n") |
| }, |
| { type: "vec3", |
| code: [ |
| "vec3 $(func)_emu($(args)) {", |
| " return vec3(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)),", |
| " $(func)_base($(baseArgsZ)));", |
| "}"].join("\n") |
| }, |
| { type: "vec4", |
| code: [ |
| "vec4 $(func)_emu($(args)) {", |
| " return vec4(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)),", |
| " $(func)_base($(baseArgsZ)),", |
| " $(func)_base($(baseArgsW)));", |
| "}"].join("\n") |
| } |
| ]; |
| |
| var bvecTypes = [ |
| { type: "bvec2", |
| code: [ |
| "bvec2 $(func)_emu($(args)) {", |
| " return bvec2(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)));", |
| "}"].join("\n") |
| }, |
| { type: "bvec3", |
| code: [ |
| "bvec3 $(func)_emu($(args)) {", |
| " return bvec3(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)),", |
| " $(func)_base($(baseArgsZ)));", |
| "}"].join("\n") |
| }, |
| { type: "bvec4", |
| code: [ |
| "vec4 $(func)_emu($(args)) {", |
| " return bvec4(", |
| " $(func)_base($(baseArgsX)),", |
| " $(func)_base($(baseArgsY)),", |
| " $(func)_base($(baseArgsZ)),", |
| " $(func)_base($(baseArgsW)));", |
| "}"].join("\n") |
| } |
| ]; |
| |
| var replaceRE = /\$\((\w+)\)/g; |
| |
| var replaceParams = function(str) { |
| var args = arguments; |
| return str.replace(replaceRE, function(str, p1, offset, s) { |
| for (var ii = 1; ii < args.length; ++ii) { |
| if (args[ii][p1] !== undefined) { |
| return args[ii][p1]; |
| } |
| } |
| throw "unknown string param '" + p1 + "'"; |
| }); |
| }; |
| |
| var generateReferenceShader = function( |
| shaderInfo, template, params, typeInfo, test) { |
| var input = shaderInfo.input; |
| var output = shaderInfo.output; |
| var feature = params.feature; |
| var testFunc = params.testFunc; |
| var emuFunc = params.emuFunc || ""; |
| var extra = params.extra || ''; |
| var args = params.args || "$(type) value"; |
| var type = typeInfo.type; |
| var typeCode = typeInfo.code; |
| |
| var baseArgs = params.baseArgs || "value$(field)"; |
| var baseArgsX = replaceParams(baseArgs, {field: ".x"}); |
| var baseArgsY = replaceParams(baseArgs, {field: ".y"}); |
| var baseArgsZ = replaceParams(baseArgs, {field: ".z"}); |
| var baseArgsW = replaceParams(baseArgs, {field: ".w"}); |
| var baseArgs = replaceParams(baseArgs, {field: ""}); |
| |
| test = replaceParams(test, { |
| input: input, |
| output: output, |
| func: feature + "_emu" |
| }); |
| emuFunc = replaceParams(emuFunc, { |
| func: feature |
| }); |
| args = replaceParams(args, { |
| type: type |
| }); |
| typeCode = replaceParams(typeCode, { |
| func: feature, |
| type: type, |
| args: args, |
| baseArgs: baseArgs, |
| baseArgsX: baseArgsX, |
| baseArgsY: baseArgsY, |
| baseArgsZ: baseArgsZ, |
| baseArgsW: baseArgsW |
| }); |
| var shader = replaceParams(template, { |
| extra: extra, |
| emu: emuFunc + "\n\n" + typeCode, |
| test: test |
| }); |
| return shader; |
| }; |
| |
| var generateTestShader = function( |
| shaderInfo, template, params, test) { |
| var input = shaderInfo.input; |
| var output = shaderInfo.output; |
| var feature = params.feature; |
| var testFunc = params.testFunc; |
| var extra = params.extra || ''; |
| |
| test = replaceParams(test, { |
| input: input, |
| output: output, |
| func: feature |
| }); |
| var shader = replaceParams(template, { |
| extra: extra, |
| emu: '', |
| test: test |
| }); |
| return shader; |
| }; |
| |
| function _reportResults(refData, refImg, testData, testImg, tolerance, |
| width, height, ctx, imgData, wtu, canvas2d, consoleDiv) { |
| var same = true; |
| var firstFailure = null; |
| for (var yy = 0; yy < height; ++yy) { |
| for (var xx = 0; xx < width; ++xx) { |
| var offset = (yy * width + xx) * 4; |
| var imgOffset = ((height - yy - 1) * width + xx) * 4; |
| imgData.data[imgOffset + 0] = 0; |
| imgData.data[imgOffset + 1] = 0; |
| imgData.data[imgOffset + 2] = 0; |
| imgData.data[imgOffset + 3] = 255; |
| if (Math.abs(refData[offset + 0] - testData[offset + 0]) > tolerance || |
| Math.abs(refData[offset + 1] - testData[offset + 1]) > tolerance || |
| Math.abs(refData[offset + 2] - testData[offset + 2]) > tolerance || |
| Math.abs(refData[offset + 3] - testData[offset + 3]) > tolerance) { |
| var detail = 'at (' + xx + ',' + yy + '): ref=(' + |
| refData[offset + 0] + ',' + |
| refData[offset + 1] + ',' + |
| refData[offset + 2] + ',' + |
| refData[offset + 3] + ') test=(' + |
| testData[offset + 0] + ',' + |
| testData[offset + 1] + ',' + |
| testData[offset + 2] + ',' + |
| testData[offset + 3] + ') tolerance=' + tolerance; |
| consoleDiv.appendChild(document.createTextNode(detail)); |
| consoleDiv.appendChild(document.createElement('br')); |
| if (!firstFailure) { |
| firstFailure = ": " + detail; |
| } |
| imgData.data[imgOffset] = 255; |
| same = false; |
| } |
| } |
| } |
| |
| var diffImg = null; |
| if (!same) { |
| ctx.putImageData(imgData, 0, 0); |
| diffImg = wtu.makeImageFromCanvas(canvas2d); |
| } |
| |
| var div = document.createElement("div"); |
| div.className = "testimages"; |
| wtu.insertImage(div, "ref", refImg); |
| wtu.insertImage(div, "test", testImg); |
| if (diffImg) { |
| wtu.insertImage(div, "diff", diffImg); |
| } |
| div.appendChild(document.createElement('br')); |
| |
| consoleDiv.appendChild(div); |
| |
| if (!same) { |
| testFailed("images are different" + (firstFailure ? firstFailure : "")); |
| } else { |
| testPassed("images are the same"); |
| } |
| |
| consoleDiv.appendChild(document.createElement('hr')); |
| } |
| |
| var runFeatureTest = function(params) { |
| var wtu = WebGLTestUtils; |
| var gridRes = params.gridRes; |
| var vertexTolerance = params.tolerance || 0; |
| var fragmentTolerance = params.tolerance || 1; |
| if ('fragmentTolerance' in params) |
| fragmentTolerance = params.fragmentTolerance; |
| |
| description("Testing GLSL feature: " + params.feature); |
| |
| var width = 32; |
| var height = 32; |
| |
| var consoleDiv = document.getElementById("console"); |
| var canvas = document.createElement('canvas'); |
| canvas.width = width; |
| canvas.height = height; |
| var gl = wtu.create3DContext(canvas, { premultipliedAlpha: false }); |
| if (!gl) { |
| testFailed("context does not exist"); |
| finishTest(); |
| return; |
| } |
| |
| var canvas2d = document.createElement('canvas'); |
| canvas2d.width = width; |
| canvas2d.height = height; |
| var ctx = canvas2d.getContext("2d"); |
| var imgData = ctx.getImageData(0, 0, width, height); |
| |
| var shaderInfos = [ |
| { type: "vertex", |
| input: "color", |
| output: "vColor", |
| vertexShaderTemplate: vertexShaderTemplate, |
| fragmentShaderTemplate: baseFragmentShader, |
| tolerance: vertexTolerance |
| }, |
| { type: "fragment", |
| input: "vColor", |
| output: "gl_FragColor", |
| vertexShaderTemplate: baseVertexShader, |
| fragmentShaderTemplate: fragmentShaderTemplate, |
| tolerance: fragmentTolerance |
| } |
| ]; |
| for (var ss = 0; ss < shaderInfos.length; ++ss) { |
| var shaderInfo = shaderInfos[ss]; |
| var tests = params.tests; |
| var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); |
| // Test vertex shaders |
| for (var ii = 0; ii < tests.length; ++ii) { |
| var type = testTypes[ii]; |
| if (params.simpleEmu) { |
| type = { |
| type: type.type, |
| code: params.simpleEmu |
| }; |
| } |
| debug(""); |
| var str = replaceParams(params.testFunc, { |
| func: params.feature, |
| type: type.type, |
| arg0: type.type |
| }); |
| var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader"; |
| debug(passMsg); |
| |
| var referenceVertexShaderSource = generateReferenceShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| params, |
| type, |
| tests[ii]); |
| var referenceFragmentShaderSource = generateReferenceShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| params, |
| type, |
| tests[ii]); |
| var testVertexShaderSource = generateTestShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| params, |
| tests[ii]); |
| var testFragmentShaderSource = generateTestShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| params, |
| tests[ii]); |
| |
| |
| debug(""); |
| var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'reference'); |
| var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'reference'); |
| var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'test'); |
| var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'test'); |
| debug(""); |
| |
| if (parseInt(wtu.getUrlOptions().dumpShaders)) { |
| var vRefInfo = { |
| shader: referenceVertexShader, |
| shaderSuccess: true, |
| label: "reference vertex shader", |
| source: referenceVertexShaderSource |
| }; |
| var fRefInfo = { |
| shader: referenceFragmentShader, |
| shaderSuccess: true, |
| label: "reference fragment shader", |
| source: referenceFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); |
| |
| var vTestInfo = { |
| shader: testVertexShader, |
| shaderSuccess: true, |
| label: "test vertex shader", |
| source: testVertexShaderSource |
| }; |
| var fTestInfo = { |
| shader: testFragmentShader, |
| shaderSuccess: true, |
| label: "test fragment shader", |
| source: testFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); |
| } |
| |
| var refData = draw( |
| referenceVertexShader, referenceFragmentShader); |
| var refImg = wtu.makeImageFromCanvas(canvas); |
| if (ss == 0) { |
| var testData = draw( |
| testVertexShader, referenceFragmentShader); |
| } else { |
| var testData = draw( |
| referenceVertexShader, testFragmentShader); |
| } |
| var testImg = wtu.makeImageFromCanvas(canvas); |
| |
| _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance, |
| width, height, ctx, imgData, wtu, canvas2d, consoleDiv); |
| } |
| } |
| |
| finishTest(); |
| |
| function draw(vertexShader, fragmentShader) { |
| var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); |
| |
| var posLoc = gl.getAttribLocation(program, "aPosition"); |
| wtu.setupIndexedQuad(gl, gridRes, posLoc); |
| |
| gl.useProgram(program); |
| wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); |
| |
| var img = new Uint8Array(width * height * 4); |
| gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); |
| return img; |
| } |
| |
| }; |
| |
| var runBasicTest = function(params) { |
| var wtu = WebGLTestUtils; |
| var gridRes = params.gridRes; |
| var vertexTolerance = params.tolerance || 0; |
| var fragmentTolerance = vertexTolerance; |
| if ('fragmentTolerance' in params) |
| fragmentTolerance = params.fragmentTolerance || 0; |
| |
| description("Testing : " + document.getElementsByTagName("title")[0].innerText); |
| |
| var width = 32; |
| var height = 32; |
| |
| var consoleDiv = document.getElementById("console"); |
| var canvas = document.createElement('canvas'); |
| canvas.width = width; |
| canvas.height = height; |
| var gl = wtu.create3DContext(canvas); |
| if (!gl) { |
| testFailed("context does not exist"); |
| finishTest(); |
| return; |
| } |
| |
| var canvas2d = document.createElement('canvas'); |
| canvas2d.width = width; |
| canvas2d.height = height; |
| var ctx = canvas2d.getContext("2d"); |
| var imgData = ctx.getImageData(0, 0, width, height); |
| |
| var shaderInfos = [ |
| { type: "vertex", |
| input: "color", |
| output: "vColor", |
| vertexShaderTemplate: vertexShaderTemplate, |
| fragmentShaderTemplate: baseFragmentShader, |
| tolerance: vertexTolerance |
| }, |
| { type: "fragment", |
| input: "vColor", |
| output: "gl_FragColor", |
| vertexShaderTemplate: baseVertexShader, |
| fragmentShaderTemplate: fragmentShaderTemplate, |
| tolerance: fragmentTolerance |
| } |
| ]; |
| for (var ss = 0; ss < shaderInfos.length; ++ss) { |
| var shaderInfo = shaderInfos[ss]; |
| var tests = params.tests; |
| // var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); |
| // Test vertex shaders |
| for (var ii = 0; ii < tests.length; ++ii) { |
| var test = tests[ii]; |
| debug(""); |
| var passMsg = "Testing: " + test.name + " in " + shaderInfo.type + " shader"; |
| debug(passMsg); |
| |
| function genShader(shaderInfo, template, shader, subs) { |
| shader = replaceParams(shader, subs, { |
| input: shaderInfo.input, |
| output: shaderInfo.output |
| }); |
| shader = replaceParams(template, subs, { |
| test: shader, |
| emu: "", |
| extra: "" |
| }); |
| return shader; |
| } |
| |
| var referenceVertexShaderSource = genShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| test.reference.shader, |
| test.reference.subs); |
| var referenceFragmentShaderSource = genShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| test.reference.shader, |
| test.reference.subs); |
| var testVertexShaderSource = genShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| test.test.shader, |
| test.test.subs); |
| var testFragmentShaderSource = genShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| test.test.shader, |
| test.test.subs); |
| |
| debug(""); |
| var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'reference'); |
| var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'reference'); |
| var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true, 'test'); |
| var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true, 'test'); |
| debug(""); |
| |
| if (parseInt(wtu.getUrlOptions().dumpShaders)) { |
| var vRefInfo = { |
| shader: referenceVertexShader, |
| shaderSuccess: true, |
| label: "reference vertex shader", |
| source: referenceVertexShaderSource |
| }; |
| var fRefInfo = { |
| shader: referenceFragmentShader, |
| shaderSuccess: true, |
| label: "reference fragment shader", |
| source: referenceFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); |
| |
| var vTestInfo = { |
| shader: testVertexShader, |
| shaderSuccess: true, |
| label: "test vertex shader", |
| source: testVertexShaderSource |
| }; |
| var fTestInfo = { |
| shader: testFragmentShader, |
| shaderSuccess: true, |
| label: "test fragment shader", |
| source: testFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); |
| } |
| |
| var refData = draw(referenceVertexShader, referenceFragmentShader); |
| var refImg = wtu.makeImageFromCanvas(canvas); |
| if (ss == 0) { |
| var testData = draw(testVertexShader, referenceFragmentShader); |
| } else { |
| var testData = draw(referenceVertexShader, testFragmentShader); |
| } |
| var testImg = wtu.makeImageFromCanvas(canvas); |
| |
| _reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance, |
| width, height, ctx, imgData, wtu, canvas2d, consoleDiv); |
| } |
| } |
| |
| finishTest(); |
| |
| function draw(vertexShader, fragmentShader) { |
| var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); |
| |
| var posLoc = gl.getAttribLocation(program, "aPosition"); |
| wtu.setupIndexedQuad(gl, gridRes, posLoc); |
| |
| gl.useProgram(program); |
| wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); |
| |
| var img = new Uint8Array(width * height * 4); |
| gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); |
| return img; |
| } |
| |
| }; |
| |
| var runReferenceImageTest = function(params) { |
| var wtu = WebGLTestUtils; |
| var gridRes = params.gridRes; |
| var vertexTolerance = params.tolerance || 0; |
| var fragmentTolerance = vertexTolerance; |
| if ('fragmentTolerance' in params) |
| fragmentTolerance = params.fragmentTolerance || 0; |
| |
| description("Testing GLSL feature: " + params.feature); |
| |
| var width = 32; |
| var height = 32; |
| |
| var consoleDiv = document.getElementById("console"); |
| var canvas = document.createElement('canvas'); |
| canvas.width = width; |
| canvas.height = height; |
| var gl = wtu.create3DContext(canvas, { antialias: false, premultipliedAlpha: false }); |
| if (!gl) { |
| testFailed("context does not exist"); |
| finishTest(); |
| return; |
| } |
| |
| var canvas2d = document.createElement('canvas'); |
| canvas2d.width = width; |
| canvas2d.height = height; |
| var ctx = canvas2d.getContext("2d"); |
| var imgData = ctx.getImageData(0, 0, width, height); |
| |
| // State for reference images for vertex shader tests. |
| // These are drawn with the same tessellated grid as the test vertex |
| // shader so that the interpolation is identical. The grid is reused |
| // from test to test; the colors are changed. |
| |
| var indexedQuadForReferenceVertexShader = |
| wtu.setupIndexedQuad(gl, gridRes, 0); |
| var referenceVertexShaderProgram = |
| wtu.setupProgram(gl, [ baseVertexShaderWithColor, baseFragmentShader ], |
| ["aPosition", "aColor"]); |
| var referenceVertexShaderColorBuffer = gl.createBuffer(); |
| |
| var shaderInfos = [ |
| { type: "vertex", |
| input: "color", |
| output: "vColor", |
| vertexShaderTemplate: vertexShaderTemplate, |
| fragmentShaderTemplate: baseFragmentShader, |
| tolerance: vertexTolerance |
| }, |
| { type: "fragment", |
| input: "vColor", |
| output: "gl_FragColor", |
| vertexShaderTemplate: baseVertexShader, |
| fragmentShaderTemplate: fragmentShaderTemplate, |
| tolerance: fragmentTolerance |
| } |
| ]; |
| for (var ss = 0; ss < shaderInfos.length; ++ss) { |
| var shaderInfo = shaderInfos[ss]; |
| var tests = params.tests; |
| var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); |
| // Test vertex shaders |
| for (var ii = 0; ii < tests.length; ++ii) { |
| var type = testTypes[ii]; |
| var isVertex = (ss == 0); |
| debug(""); |
| var str = replaceParams(params.testFunc, { |
| func: params.feature, |
| type: type.type, |
| arg0: type.type |
| }); |
| var passMsg = "Testing: " + str + " in " + shaderInfo.type + " shader"; |
| debug(passMsg); |
| |
| var referenceVertexShaderSource = generateReferenceShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| params, |
| type, |
| tests[ii].source); |
| var referenceFragmentShaderSource = generateReferenceShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| params, |
| type, |
| tests[ii].source); |
| var testVertexShaderSource = generateTestShader( |
| shaderInfo, |
| shaderInfo.vertexShaderTemplate, |
| params, |
| tests[ii].source); |
| var testFragmentShaderSource = generateTestShader( |
| shaderInfo, |
| shaderInfo.fragmentShaderTemplate, |
| params, |
| tests[ii].source); |
| var referenceTextureOrArray = generateReferenceImage( |
| gl, |
| tests[ii].generator, |
| isVertex ? gridRes : width, |
| isVertex ? gridRes : height, |
| isVertex); |
| |
| debug(""); |
| var testVertexShader = wtu.loadShader(gl, testVertexShaderSource, gl.VERTEX_SHADER, testFailed, true); |
| var testFragmentShader = wtu.loadShader(gl, testFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed, true); |
| debug(""); |
| |
| |
| if (parseInt(wtu.getUrlOptions().dumpShaders)) { |
| var vRefInfo = { |
| shader: referenceVertexShader, |
| shaderSuccess: true, |
| label: "reference vertex shader", |
| source: referenceVertexShaderSource |
| }; |
| var fRefInfo = { |
| shader: referenceFragmentShader, |
| shaderSuccess: true, |
| label: "reference fragment shader", |
| source: referenceFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vRefInfo, fRefInfo); |
| |
| var vTestInfo = { |
| shader: testVertexShader, |
| shaderSuccess: true, |
| label: "test vertex shader", |
| source: testVertexShaderSource |
| }; |
| var fTestInfo = { |
| shader: testFragmentShader, |
| shaderSuccess: true, |
| label: "test fragment shader", |
| source: testFragmentShaderSource |
| }; |
| wtu.dumpShadersInfo(gl, window.location.pathname, passMsg, vTestInfo, fTestInfo); |
| } |
| |
| var refData; |
| if (isVertex) { |
| refData = drawVertexReferenceImage(referenceTextureOrArray); |
| } else { |
| refData = drawFragmentReferenceImage(referenceTextureOrArray); |
| } |
| var refImg = wtu.makeImageFromCanvas(canvas); |
| var testData; |
| if (isVertex) { |
| var referenceFragmentShader = wtu.loadShader(gl, referenceFragmentShaderSource, gl.FRAGMENT_SHADER, testFailed); |
| testData = draw( |
| testVertexShader, referenceFragmentShader); |
| } else { |
| var referenceVertexShader = wtu.loadShader(gl, referenceVertexShaderSource, gl.VERTEX_SHADER, testFailed); |
| testData = draw( |
| referenceVertexShader, testFragmentShader); |
| } |
| var testImg = wtu.makeImageFromCanvas(canvas); |
| var testTolerance = shaderInfo.tolerance; |
| // Provide per-test tolerance so that we can increase it only for those desired. |
| if ('tolerance' in tests[ii]) |
| testTolerance = tests[ii].tolerance || 0; |
| _reportResults(refData, refImg, testData, testImg, testTolerance, |
| width, height, ctx, imgData, wtu, canvas2d, consoleDiv); |
| } |
| } |
| |
| finishTest(); |
| |
| function draw(vertexShader, fragmentShader) { |
| var program = wtu.createProgram(gl, vertexShader, fragmentShader, testFailed); |
| |
| var posLoc = gl.getAttribLocation(program, "aPosition"); |
| wtu.setupIndexedQuad(gl, gridRes, posLoc); |
| |
| gl.useProgram(program); |
| wtu.clearAndDrawIndexedQuad(gl, gridRes, [0, 0, 255, 255]); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); |
| |
| var img = new Uint8Array(width * height * 4); |
| gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); |
| return img; |
| } |
| |
| function drawVertexReferenceImage(colors) { |
| gl.bindBuffer(gl.ARRAY_BUFFER, indexedQuadForReferenceVertexShader[0]); |
| gl.enableVertexAttribArray(0); |
| gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); |
| gl.bindBuffer(gl.ARRAY_BUFFER, referenceVertexShaderColorBuffer); |
| gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW); |
| gl.enableVertexAttribArray(1); |
| gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, true, 0, 0); |
| gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexedQuadForReferenceVertexShader[1]); |
| gl.useProgram(referenceVertexShaderProgram); |
| wtu.clearAndDrawIndexedQuad(gl, gridRes); |
| gl.disableVertexAttribArray(0); |
| gl.disableVertexAttribArray(1); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); |
| |
| var img = new Uint8Array(width * height * 4); |
| gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); |
| return img; |
| } |
| |
| function drawFragmentReferenceImage(texture) { |
| var program = wtu.setupTexturedQuad(gl); |
| |
| gl.activeTexture(gl.TEXTURE0); |
| gl.bindTexture(gl.TEXTURE_2D, texture); |
| var texLoc = gl.getUniformLocation(program, "tex"); |
| gl.uniform1i(texLoc, 0); |
| wtu.clearAndDrawUnitQuad(gl); |
| wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); |
| |
| var img = new Uint8Array(width * height * 4); |
| gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); |
| return img; |
| } |
| |
| /** |
| * Creates and returns either a Uint8Array (for vertex shaders) or |
| * WebGLTexture (for fragment shaders) containing the reference |
| * image for the function being tested. Exactly how the function is |
| * evaluated, and the size of the returned texture or array, depends on |
| * whether we are testing a vertex or fragment shader. If a fragment |
| * shader, the function is evaluated at the pixel centers. If a |
| * vertex shader, the function is evaluated at the triangle's |
| * vertices. |
| * |
| * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use to generate texture objects. |
| * @param {!function(number,number,number,number): !Array.<number>} generator The reference image generator function. |
| * @param {number} width The width of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. |
| * @param {number} height The height of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. |
| * @param {boolean} isVertex True if generating a reference image for a vertex shader; false if for a fragment shader. |
| * @return {!WebGLTexture|!Uint8Array} The texture object or array that was generated. |
| */ |
| function generateReferenceImage( |
| gl, |
| generator, |
| width, |
| height, |
| isVertex) { |
| |
| // Note: the math in this function must match that in the vertex and |
| // fragment shader templates above. |
| function computeTexCoord(x) { |
| return x * 0.5 + 0.5; |
| } |
| |
| function computeVertexColor(texCoordX, texCoordY) { |
| return [ texCoordX, |
| texCoordY, |
| texCoordX * texCoordY, |
| (1.0 - texCoordX) * texCoordY * 0.5 + 0.5 ]; |
| } |
| |
| /** |
| * Computes fragment color according to the algorithm used for interpolation |
| * in OpenGL (GLES 2.0 spec 3.5.1, OpenGL 4.3 spec 14.6.1). |
| */ |
| function computeInterpolatedColor(texCoordX, texCoordY) { |
| // Calculate grid line indexes below and to the left from texCoord. |
| var gridBottom = Math.floor(texCoordY * gridRes); |
| if (gridBottom == gridRes) { |
| --gridBottom; |
| } |
| var gridLeft = Math.floor(texCoordX * gridRes); |
| if (gridLeft == gridRes) { |
| --gridLeft; |
| } |
| |
| // Calculate coordinates relative to the grid cell. |
| var cellX = texCoordX * gridRes - gridLeft; |
| var cellY = texCoordY * gridRes - gridBottom; |
| |
| // Barycentric coordinates inside either triangle ACD or ABC |
| // are used as weights for the vertex colors in the corners: |
| // A--B |
| // |\ | |
| // | \| |
| // D--C |
| |
| var aColor = computeVertexColor(gridLeft / gridRes, (gridBottom + 1) / gridRes); |
| var bColor = computeVertexColor((gridLeft + 1) / gridRes, (gridBottom + 1) / gridRes); |
| var cColor = computeVertexColor((gridLeft + 1) / gridRes, gridBottom / gridRes); |
| var dColor = computeVertexColor(gridLeft / gridRes, gridBottom / gridRes); |
| |
| // Calculate weights. |
| var a, b, c, d; |
| |
| if (cellX + cellY < 1) { |
| // In bottom triangle ACD. |
| a = cellY; // area of triangle C-D-(cellX, cellY) relative to ACD |
| c = cellX; // area of triangle D-A-(cellX, cellY) relative to ACD |
| d = 1 - a - c; |
| b = 0; |
| } else { |
| // In top triangle ABC. |
| a = 1 - cellX; // area of the triangle B-C-(cellX, cellY) relative to ABC |
| c = 1 - cellY; // area of the triangle A-B-(cellX, cellY) relative to ABC |
| b = 1 - a - c; |
| d = 0; |
| } |
| |
| var interpolated = []; |
| for (var ii = 0; ii < aColor.length; ++ii) { |
| interpolated.push(a * aColor[ii] + b * bColor[ii] + c * cColor[ii] + d * dColor[ii]); |
| } |
| return interpolated; |
| } |
| |
| function clamp(value, minVal, maxVal) { |
| return Math.max(minVal, Math.min(value, maxVal)); |
| } |
| |
| // Evaluates the function at clip coordinates (px,py), storing the |
| // result in the array "pixel". Each channel's result is clamped |
| // between 0 and 255. |
| function evaluateAtClipCoords(px, py, pixel, colorFunc) { |
| var tcx = computeTexCoord(px); |
| var tcy = computeTexCoord(py); |
| |
| var color = colorFunc(tcx, tcy); |
| |
| var output = generator(color[0], color[1], color[2], color[3]); |
| |
| // Multiply by 256 to get even distribution for all values between 0 and 1. |
| // Use rounding rather than truncation to more closely match the GPU's behavior. |
| pixel[0] = clamp(Math.round(256 * output[0]), 0, 255); |
| pixel[1] = clamp(Math.round(256 * output[1]), 0, 255); |
| pixel[2] = clamp(Math.round(256 * output[2]), 0, 255); |
| pixel[3] = clamp(Math.round(256 * output[3]), 0, 255); |
| } |
| |
| function generateFragmentReference() { |
| var data = new Uint8Array(4 * width * height); |
| |
| var horizTexel = 1.0 / width; |
| var vertTexel = 1.0 / height; |
| var halfHorizTexel = 0.5 * horizTexel; |
| var halfVertTexel = 0.5 * vertTexel; |
| |
| var pixel = new Array(4); |
| |
| for (var yi = 0; yi < height; ++yi) { |
| for (var xi = 0; xi < width; ++xi) { |
| // The function must be evaluated at pixel centers. |
| |
| // Compute desired position in clip space |
| var px = -1.0 + 2.0 * (halfHorizTexel + xi * horizTexel); |
| var py = -1.0 + 2.0 * (halfVertTexel + yi * vertTexel); |
| |
| evaluateAtClipCoords(px, py, pixel, computeInterpolatedColor); |
| var index = 4 * (width * yi + xi); |
| data[index + 0] = pixel[0]; |
| data[index + 1] = pixel[1]; |
| data[index + 2] = pixel[2]; |
| data[index + 3] = pixel[3]; |
| } |
| } |
| |
| var texture = gl.createTexture(); |
| gl.bindTexture(gl.TEXTURE_2D, texture); |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); |
| gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); |
| gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, |
| gl.RGBA, gl.UNSIGNED_BYTE, data); |
| return texture; |
| } |
| |
| function generateVertexReference() { |
| // We generate a Uint8Array which contains the evaluation of the |
| // function at the vertices of the triangle mesh. It is expected |
| // that the width and the height are identical, and equivalent |
| // to the grid resolution. |
| if (width != height) { |
| throw "width and height must be equal"; |
| } |
| |
| var texSize = 1 + width; |
| var data = new Uint8Array(4 * texSize * texSize); |
| |
| var step = 2.0 / width; |
| |
| var pixel = new Array(4); |
| |
| for (var yi = 0; yi < texSize; ++yi) { |
| for (var xi = 0; xi < texSize; ++xi) { |
| // The function is evaluated at the triangles' vertices. |
| |
| // Compute desired position in clip space |
| var px = -1.0 + (xi * step); |
| var py = -1.0 + (yi * step); |
| |
| evaluateAtClipCoords(px, py, pixel, computeVertexColor); |
| var index = 4 * (texSize * yi + xi); |
| data[index + 0] = pixel[0]; |
| data[index + 1] = pixel[1]; |
| data[index + 2] = pixel[2]; |
| data[index + 3] = pixel[3]; |
| } |
| } |
| |
| return data; |
| } |
| |
| //---------------------------------------------------------------------- |
| // Body of generateReferenceImage |
| // |
| |
| if (isVertex) { |
| return generateVertexReference(); |
| } else { |
| return generateFragmentReference(); |
| } |
| } |
| }; |
| |
| return { |
| /** |
| * runs a bunch of GLSL tests using the passed in parameters |
| * The parameters are: |
| * |
| * feature: |
| * the name of the function being tested (eg, sin, dot, |
| * normalize) |
| * |
| * testFunc: |
| * The prototype of function to be tested not including the |
| * return type. |
| * |
| * emuFunc: |
| * A base function that can be used to generate emulation |
| * functions. Example for 'ceil' |
| * |
| * float $(func)_base(float value) { |
| * float m = mod(value, 1.0); |
| * return m != 0.0 ? (value + 1.0 - m) : value; |
| * } |
| * |
| * args: |
| * The arguments to the function |
| * |
| * baseArgs: (optional) |
| * The arguments when a base function is used to create an |
| * emulation function. For example 'float sign_base(float v)' |
| * is used to implemenent vec2 sign_emu(vec2 v). |
| * |
| * simpleEmu: |
| * if supplied, the code that can be used to generate all |
| * functions for all types. |
| * |
| * Example for 'normalize': |
| * |
| * $(type) $(func)_emu($(args)) { |
| * return value / length(value); |
| * } |
| * |
| * gridRes: (optional) |
| * The resolution of the mesh to generate. The default is a |
| * 1x1 grid but many vertex shaders need a higher resolution |
| * otherwise the only values passed in are the 4 corners |
| * which often have the same value. |
| * |
| * tests: |
| * The code for each test. It is assumed the tests are for |
| * float, vec2, vec3, vec4 in that order. |
| * |
| * tolerance: (optional) |
| * Allow some tolerance in the comparisons. The tolerance is applied to |
| * both vertex and fragment shaders. The default tolerance is 0, meaning |
| * the values have to be identical. |
| * |
| * fragmentTolerance: (optional) |
| * Specify a tolerance which only applies to fragment shaders. The |
| * fragment-only tolerance will override the shared tolerance for |
| * fragment shaders if both are specified. Fragment shaders usually |
| * use mediump float precision so they sometimes require higher tolerance |
| * than vertex shaders which use highp by default. |
| */ |
| runFeatureTest: runFeatureTest, |
| |
| /* |
| * Runs a bunch of GLSL tests using the passed in parameters |
| * |
| * The parameters are: |
| * |
| * tests: |
| * Array of tests. For each test the following parameters are expected |
| * |
| * name: |
| * some description of the test |
| * reference: |
| * parameters for the reference shader (see below) |
| * test: |
| * parameters for the test shader (see below) |
| * |
| * The parameter for the reference and test shaders are |
| * |
| * shader: the GLSL for the shader |
| * subs: any substitutions you wish to define for the shader. |
| * |
| * Each shader is created from a basic template that |
| * defines an input and an output. You can see the |
| * templates at the top of this file. The input and output |
| * change depending on whether or not we are generating |
| * a vertex or fragment shader. |
| * |
| * All this code function does is a bunch of string substitutions. |
| * A substitution is defined by $(name). If name is found in |
| * the 'subs' parameter it is replaced. 4 special names exist. |
| * |
| * 'input' the input to your GLSL. Always a vec4. All change |
| * from 0 to 1 over the quad to be drawn. |
| * |
| * 'output' the output color. Also a vec4 |
| * |
| * 'emu' a place to insert extra stuff |
| * 'extra' a place to insert extra stuff. |
| * |
| * You can think of the templates like this |
| * |
| * $(extra) |
| * $(emu) |
| * |
| * void main() { |
| * // do math to calculate input |
| * ... |
| * |
| * $(shader) |
| * } |
| * |
| * Your shader first has any subs you provided applied as well |
| * as 'input' and 'output' |
| * |
| * It is then inserted into the template which is also provided |
| * with your subs. |
| * |
| * gridRes: (optional) |
| * The resolution of the mesh to generate. The default is a |
| * 1x1 grid but many vertex shaders need a higher resolution |
| * otherwise the only values passed in are the 4 corners |
| * which often have the same value. |
| * |
| * tolerance: (optional) |
| * Allow some tolerance in the comparisons. The tolerance is applied to |
| * both vertex and fragment shaders. The default tolerance is 0, meaning |
| * the values have to be identical. |
| * |
| * fragmentTolerance: (optional) |
| * Specify a tolerance which only applies to fragment shaders. The |
| * fragment-only tolerance will override the shared tolerance for |
| * fragment shaders if both are specified. Fragment shaders usually |
| * use mediump float precision so they sometimes require higher tolerance |
| * than vertex shaders which use highp. |
| */ |
| runBasicTest: runBasicTest, |
| |
| /** |
| * Runs a bunch of GLSL tests using the passed in parameters. The |
| * expected results are computed as a reference image in JavaScript |
| * instead of on the GPU. The parameters are: |
| * |
| * feature: |
| * the name of the function being tested (eg, sin, dot, |
| * normalize) |
| * |
| * testFunc: |
| * The prototype of function to be tested not including the |
| * return type. |
| * |
| * args: |
| * The arguments to the function |
| * |
| * gridRes: (optional) |
| * The resolution of the mesh to generate. The default is a |
| * 1x1 grid but many vertex shaders need a higher resolution |
| * otherwise the only values passed in are the 4 corners |
| * which often have the same value. |
| * |
| * tests: |
| * Array of tests. It is assumed the tests are for float, vec2, |
| * vec3, vec4 in that order. For each test the following |
| * parameters are expected: |
| * |
| * source: the GLSL source code for the tests |
| * |
| * generator: a JavaScript function taking four parameters |
| * which evaluates the same function as the GLSL source, |
| * returning its result as a newly allocated array. |
| * |
| * tolerance: (optional) a per-test tolerance. |
| * |
| * extra: (optional) |
| * Extra GLSL code inserted at the top of each test's shader. |
| * |
| * tolerance: (optional) |
| * Allow some tolerance in the comparisons. The tolerance is applied to |
| * both vertex and fragment shaders. The default tolerance is 0, meaning |
| * the values have to be identical. |
| * |
| * fragmentTolerance: (optional) |
| * Specify a tolerance which only applies to fragment shaders. The |
| * fragment-only tolerance will override the shared tolerance for |
| * fragment shaders if both are specified. Fragment shaders usually |
| * use mediump float precision so they sometimes require higher tolerance |
| * than vertex shaders which use highp. |
| */ |
| runReferenceImageTest: runReferenceImageTest, |
| |
| none: false |
| }; |
| |
| }()); |
| |