summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js1234
1 files changed, 1234 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js b/dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js
new file mode 100644
index 0000000000..d0b65bcb4b
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/resources/glsl-generator.js
@@ -0,0 +1,1234 @@
+/*
+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.
+*/
+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
+};
+
+}());
+