Index: conformance/resources/glsl-generator.js |
=================================================================== |
--- conformance/resources/glsl-generator.js (revision 0) |
+++ conformance/resources/glsl-generator.js (working copy) |
@@ -0,0 +1,1257 @@ |
+/* |
+** 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 = [ |
+ "#if defined(GL_ES)", |
+ "precision mediump float;", |
+ "#endif", |
+ "", |
+ "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 = [ |
+ "#if defined(GL_ES)", |
+ "precision mediump float;", |
+ "#endif", |
+ "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; |
+}; |
+ |
+var runFeatureTest = 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 console = 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 |
+ }); |
+ debug("Testing: " + str + " in " + shaderInfo.type + " shader"); |
+ |
+ 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(""); |
+ wtu.addShaderSource( |
+ console, "reference vertex shader", referenceVertexShaderSource); |
+ wtu.addShaderSource( |
+ console, "reference fragment shader", referenceFragmentShaderSource); |
+ wtu.addShaderSource( |
+ console, "test vertex shader", testVertexShaderSource); |
+ wtu.addShaderSource( |
+ console, "test fragment shader", testFragmentShaderSource); |
+ debug(""); |
+ |
+ var refData = draw( |
+ canvas, referenceVertexShaderSource, referenceFragmentShaderSource); |
+ var refImg = wtu.makeImage(canvas); |
+ if (ss == 0) { |
+ var testData = draw( |
+ canvas, testVertexShaderSource, referenceFragmentShaderSource); |
+ } else { |
+ var testData = draw( |
+ canvas, referenceVertexShaderSource, testFragmentShaderSource); |
+ } |
+ var testImg = wtu.makeImage(canvas); |
+ |
+ reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance); |
+ } |
+ } |
+ |
+ finishTest(); |
+ |
+ function reportResults(refData, refImage, testData, testImage, tolerance) { |
+ var same = true; |
+ 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) { |
+ imgData.data[imgOffset] = 255; |
+ same = false; |
+ } |
+ } |
+ } |
+ |
+ var diffImg = null; |
+ if (!same) { |
+ ctx.putImageData(imgData, 0, 0); |
+ diffImg = wtu.makeImage(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')); |
+ |
+ |
+ console.appendChild(div); |
+ |
+ if (!same) { |
+ testFailed("images are different"); |
+ } else { |
+ testPassed("images are the same"); |
+ } |
+ |
+ console.appendChild(document.createElement('hr')); |
+ } |
+ |
+ function draw(canvas, vsSource, fsSource) { |
+ var program = wtu.loadProgram(gl, vsSource, fsSource, 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 console = 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(""); |
+ debug("Testing: " + test.name + " in " + shaderInfo.type + " shader"); |
+ |
+ 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(""); |
+ wtu.addShaderSource( |
+ console, "reference vertex shader", referenceVertexShaderSource); |
+ wtu.addShaderSource( |
+ console, "reference fragment shader", referenceFragmentShaderSource); |
+ wtu.addShaderSource( |
+ console, "test vertex shader", testVertexShaderSource); |
+ wtu.addShaderSource( |
+ console, "test fragment shader", testFragmentShaderSource); |
+ debug(""); |
+ |
+ var refData = draw( |
+ canvas, referenceVertexShaderSource, referenceFragmentShaderSource); |
+ var refImg = wtu.makeImage(canvas); |
+ if (ss == 0) { |
+ var testData = draw( |
+ canvas, testVertexShaderSource, referenceFragmentShaderSource); |
+ } else { |
+ var testData = draw( |
+ canvas, referenceVertexShaderSource, testFragmentShaderSource); |
+ } |
+ var testImg = wtu.makeImage(canvas); |
+ |
+ reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance); |
+ } |
+ } |
+ |
+ finishTest(); |
+ |
+ function reportResults(refData, refImage, testData, testImage, tolerance) { |
+ var same = true; |
+ 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) { |
+ imgData.data[imgOffset] = 255; |
+ same = false; |
+ } |
+ } |
+ } |
+ |
+ var diffImg = null; |
+ if (!same) { |
+ ctx.putImageData(imgData, 0, 0); |
+ diffImg = wtu.makeImage(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')); |
+ |
+ console.appendChild(div); |
+ |
+ if (!same) { |
+ testFailed("images are different"); |
+ } else { |
+ testPassed("images are the same"); |
+ } |
+ |
+ console.appendChild(document.createElement('hr')); |
+ } |
+ |
+ function draw(canvas, vsSource, fsSource) { |
+ var program = wtu.loadProgram(gl, vsSource, fsSource, 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 console = 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 |
+ }); |
+ debug("Testing: " + str + " in " + shaderInfo.type + " shader"); |
+ |
+ 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(""); |
+ wtu.addShaderSource( |
+ console, "test vertex shader", testVertexShaderSource); |
+ wtu.addShaderSource( |
+ console, "test fragment shader", testFragmentShaderSource); |
+ debug(""); |
+ var refData; |
+ if (isVertex) { |
+ refData = drawVertexReferenceImage(canvas, referenceTextureOrArray); |
+ } else { |
+ refData = drawFragmentReferenceImage(canvas, referenceTextureOrArray); |
+ } |
+ var refImg = wtu.makeImage(canvas); |
+ var testData; |
+ if (isVertex) { |
+ testData = draw( |
+ canvas, testVertexShaderSource, referenceFragmentShaderSource); |
+ } else { |
+ testData = draw( |
+ canvas, referenceVertexShaderSource, testFragmentShaderSource); |
+ } |
+ var testImg = wtu.makeImage(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); |
+ } |
+ } |
+ |
+ finishTest(); |
+ |
+ function reportResults(refData, refImage, testData, testImage, tolerance) { |
+ var same = true; |
+ 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) { |
+ console.appendChild(document.createTextNode('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] + ')')); |
+ console.appendChild(document.createElement('br')); |
+ |
+ |
+ |
+ imgData.data[imgOffset] = 255; |
+ same = false; |
+ } |
+ } |
+ } |
+ |
+ var diffImg = null; |
+ if (!same) { |
+ ctx.putImageData(imgData, 0, 0); |
+ diffImg = wtu.makeImage(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')); |
+ |
+ console.appendChild(div); |
+ |
+ if (!same) { |
+ testFailed("images are different"); |
+ } else { |
+ testPassed("images are the same"); |
+ } |
+ |
+ console.appendChild(document.createElement('hr')); |
+ } |
+ |
+ function draw(canvas, vsSource, fsSource) { |
+ var program = wtu.loadProgram(gl, vsSource, fsSource, 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(canvas, 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(canvas, 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 |
+}; |
+ |
+}()); |
+ |
Property changes on: conformance/resources/glsl-generator.js |
___________________________________________________________________ |
Added: svn:eol-style |
## -0,0 +1 ## |
+LF |
\ No newline at end of property |