michael@0: GLSLGenerator = (function() { michael@0: michael@0: var vertexShaderTemplate = [ michael@0: "attribute vec4 aPosition;", michael@0: "", michael@0: "varying vec4 vColor;", michael@0: "", michael@0: "$(extra)", michael@0: "$(emu)", michael@0: "", michael@0: "void main()", michael@0: "{", michael@0: " gl_Position = aPosition;", michael@0: " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", michael@0: " vec4 color = vec4(", michael@0: " texcoord,", michael@0: " texcoord.x * texcoord.y,", michael@0: " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", michael@0: " $(test)", michael@0: "}" michael@0: ].join("\n"); michael@0: michael@0: var fragmentShaderTemplate = [ michael@0: "#if defined(GL_ES)", michael@0: "precision mediump float;", michael@0: "#endif", michael@0: "", michael@0: "varying vec4 vColor;", michael@0: "", michael@0: "$(extra)", michael@0: "$(emu)", michael@0: "", michael@0: "void main()", michael@0: "{", michael@0: " $(test)", michael@0: "}" michael@0: ].join("\n"); michael@0: michael@0: var baseVertexShader = [ michael@0: "attribute vec4 aPosition;", michael@0: "", michael@0: "varying vec4 vColor;", michael@0: "", michael@0: "void main()", michael@0: "{", michael@0: " gl_Position = aPosition;", michael@0: " vec2 texcoord = vec2(aPosition.xy * 0.5 + vec2(0.5, 0.5));", michael@0: " vColor = vec4(", michael@0: " texcoord,", michael@0: " texcoord.x * texcoord.y,", michael@0: " (1.0 - texcoord.x) * texcoord.y * 0.5 + 0.5);", michael@0: "}" michael@0: ].join("\n"); michael@0: michael@0: var baseFragmentShader = [ michael@0: "#if defined(GL_ES)", michael@0: "precision mediump float;", michael@0: "#endif", michael@0: "varying vec4 vColor;", michael@0: "", michael@0: "void main()", michael@0: "{", michael@0: " gl_FragColor = vColor;", michael@0: "}" michael@0: ].join("\n"); michael@0: michael@0: var types = [ michael@0: { type: "float", michael@0: code: [ michael@0: "float $(func)_emu($(args)) {", michael@0: " return $(func)_base($(baseArgs));", michael@0: "}"].join("\n") michael@0: }, michael@0: { type: "vec2", michael@0: code: [ michael@0: "vec2 $(func)_emu($(args)) {", michael@0: " return vec2(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)));", michael@0: "}"].join("\n") michael@0: }, michael@0: { type: "vec3", michael@0: code: [ michael@0: "vec3 $(func)_emu($(args)) {", michael@0: " return vec3(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)),", michael@0: " $(func)_base($(baseArgsZ)));", michael@0: "}"].join("\n") michael@0: }, michael@0: { type: "vec4", michael@0: code: [ michael@0: "vec4 $(func)_emu($(args)) {", michael@0: " return vec4(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)),", michael@0: " $(func)_base($(baseArgsZ)),", michael@0: " $(func)_base($(baseArgsW)));", michael@0: "}"].join("\n") michael@0: } michael@0: ]; michael@0: michael@0: var bvecTypes = [ michael@0: { type: "bvec2", michael@0: code: [ michael@0: "bvec2 $(func)_emu($(args)) {", michael@0: " return bvec2(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)));", michael@0: "}"].join("\n") michael@0: }, michael@0: { type: "bvec3", michael@0: code: [ michael@0: "bvec3 $(func)_emu($(args)) {", michael@0: " return bvec3(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)),", michael@0: " $(func)_base($(baseArgsZ)));", michael@0: "}"].join("\n") michael@0: }, michael@0: { type: "bvec4", michael@0: code: [ michael@0: "vec4 $(func)_emu($(args)) {", michael@0: " return bvec4(", michael@0: " $(func)_base($(baseArgsX)),", michael@0: " $(func)_base($(baseArgsY)),", michael@0: " $(func)_base($(baseArgsZ)),", michael@0: " $(func)_base($(baseArgsW)));", michael@0: "}"].join("\n") michael@0: } michael@0: ]; michael@0: michael@0: var replaceRE = /\$\((\w+)\)/g; michael@0: michael@0: var replaceParams = function(str) { michael@0: var args = arguments; michael@0: return str.replace(replaceRE, function(str, p1, offset, s) { michael@0: for (var ii = 1; ii < args.length; ++ii) { michael@0: if (args[ii][p1] !== undefined) { michael@0: return args[ii][p1]; michael@0: } michael@0: } michael@0: throw "unknown string param '" + p1 + "'"; michael@0: }); michael@0: }; michael@0: michael@0: var generateReferenceShader = function( michael@0: shaderInfo, template, params, typeInfo, test) { michael@0: var input = shaderInfo.input; michael@0: var output = shaderInfo.output; michael@0: var feature = params.feature; michael@0: var testFunc = params.testFunc; michael@0: var emuFunc = params.emuFunc || ""; michael@0: var extra = params.extra || ''; michael@0: var args = params.args || "$(type) value"; michael@0: var type = typeInfo.type; michael@0: var typeCode = typeInfo.code; michael@0: michael@0: var baseArgs = params.baseArgs || "value$(field)"; michael@0: var baseArgsX = replaceParams(baseArgs, {field: ".x"}); michael@0: var baseArgsY = replaceParams(baseArgs, {field: ".y"}); michael@0: var baseArgsZ = replaceParams(baseArgs, {field: ".z"}); michael@0: var baseArgsW = replaceParams(baseArgs, {field: ".w"}); michael@0: var baseArgs = replaceParams(baseArgs, {field: ""}); michael@0: michael@0: test = replaceParams(test, { michael@0: input: input, michael@0: output: output, michael@0: func: feature + "_emu" michael@0: }); michael@0: emuFunc = replaceParams(emuFunc, { michael@0: func: feature michael@0: }); michael@0: args = replaceParams(args, { michael@0: type: type michael@0: }); michael@0: typeCode = replaceParams(typeCode, { michael@0: func: feature, michael@0: type: type, michael@0: args: args, michael@0: baseArgs: baseArgs, michael@0: baseArgsX: baseArgsX, michael@0: baseArgsY: baseArgsY, michael@0: baseArgsZ: baseArgsZ, michael@0: baseArgsW: baseArgsW michael@0: }); michael@0: var shader = replaceParams(template, { michael@0: extra: extra, michael@0: emu: emuFunc + "\n\n" + typeCode, michael@0: test: test michael@0: }); michael@0: return shader; michael@0: }; michael@0: michael@0: var generateTestShader = function( michael@0: shaderInfo, template, params, test) { michael@0: var input = shaderInfo.input; michael@0: var output = shaderInfo.output; michael@0: var feature = params.feature; michael@0: var testFunc = params.testFunc; michael@0: var extra = params.extra || ''; michael@0: michael@0: test = replaceParams(test, { michael@0: input: input, michael@0: output: output, michael@0: func: feature michael@0: }); michael@0: var shader = replaceParams(template, { michael@0: extra: extra, michael@0: emu: '', michael@0: test: test michael@0: }); michael@0: return shader; michael@0: }; michael@0: michael@0: var runFeatureTest = function(params) { michael@0: if (window.initNonKhronosFramework) { michael@0: window.initNonKhronosFramework(false); michael@0: } michael@0: michael@0: var wtu = WebGLTestUtils; michael@0: var gridRes = params.gridRes; michael@0: var vertexTolerance = params.tolerance || 0; michael@0: var fragmentTolerance = vertexTolerance; michael@0: if ('fragmentTolerance' in params) michael@0: fragmentTolerance = params.fragmentTolerance || 0; michael@0: michael@0: description("Testing GLSL feature: " + params.feature); michael@0: michael@0: var width = 32; michael@0: var height = 32; michael@0: michael@0: var console = document.getElementById("console"); michael@0: var canvas = document.createElement('canvas'); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: var gl = wtu.create3DContext(canvas); michael@0: if (!gl) { michael@0: testFailed("context does not exist"); michael@0: finishTest(); michael@0: return; michael@0: } michael@0: michael@0: var canvas2d = document.createElement('canvas'); michael@0: canvas2d.width = width; michael@0: canvas2d.height = height; michael@0: var ctx = canvas2d.getContext("2d"); michael@0: var imgData = ctx.getImageData(0, 0, width, height); michael@0: michael@0: var shaderInfos = [ michael@0: { type: "vertex", michael@0: input: "color", michael@0: output: "vColor", michael@0: vertexShaderTemplate: vertexShaderTemplate, michael@0: fragmentShaderTemplate: baseFragmentShader, michael@0: tolerance: vertexTolerance michael@0: }, michael@0: { type: "fragment", michael@0: input: "vColor", michael@0: output: "gl_FragColor", michael@0: vertexShaderTemplate: baseVertexShader, michael@0: fragmentShaderTemplate: fragmentShaderTemplate, michael@0: tolerance: fragmentTolerance michael@0: } michael@0: ]; michael@0: for (var ss = 0; ss < shaderInfos.length; ++ss) { michael@0: var shaderInfo = shaderInfos[ss]; michael@0: var tests = params.tests; michael@0: var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); michael@0: // Test vertex shaders michael@0: for (var ii = 0; ii < tests.length; ++ii) { michael@0: var type = testTypes[ii]; michael@0: if (params.simpleEmu) { michael@0: type = { michael@0: type: type.type, michael@0: code: params.simpleEmu michael@0: }; michael@0: } michael@0: debug(""); michael@0: var str = replaceParams(params.testFunc, { michael@0: func: params.feature, michael@0: type: type.type, michael@0: arg0: type.type michael@0: }); michael@0: debug("Testing: " + str + " in " + shaderInfo.type + " shader"); michael@0: michael@0: var referenceVertexShaderSource = generateReferenceShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: params, michael@0: type, michael@0: tests[ii]); michael@0: var referenceFragmentShaderSource = generateReferenceShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: params, michael@0: type, michael@0: tests[ii]); michael@0: var testVertexShaderSource = generateTestShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: params, michael@0: tests[ii]); michael@0: var testFragmentShaderSource = generateTestShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: params, michael@0: tests[ii]); michael@0: michael@0: debug(""); michael@0: wtu.addShaderSource( michael@0: console, "reference vertex shader", referenceVertexShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "reference fragment shader", referenceFragmentShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "test vertex shader", testVertexShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "test fragment shader", testFragmentShaderSource); michael@0: debug(""); michael@0: michael@0: var refData = draw( michael@0: canvas, referenceVertexShaderSource, referenceFragmentShaderSource); michael@0: var refImg = wtu.makeImage(canvas); michael@0: if (ss == 0) { michael@0: var testData = draw( michael@0: canvas, testVertexShaderSource, referenceFragmentShaderSource); michael@0: } else { michael@0: var testData = draw( michael@0: canvas, referenceVertexShaderSource, testFragmentShaderSource); michael@0: } michael@0: var testImg = wtu.makeImage(canvas); michael@0: michael@0: reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance); michael@0: } michael@0: } michael@0: michael@0: finishTest(); michael@0: michael@0: function reportResults(refData, refImage, testData, testImage, tolerance) { michael@0: var same = true; michael@0: for (var yy = 0; yy < height; ++yy) { michael@0: for (var xx = 0; xx < width; ++xx) { michael@0: var offset = (yy * width + xx) * 4; michael@0: var imgOffset = ((height - yy - 1) * width + xx) * 4; michael@0: imgData.data[imgOffset + 0] = 0; michael@0: imgData.data[imgOffset + 1] = 0; michael@0: imgData.data[imgOffset + 2] = 0; michael@0: imgData.data[imgOffset + 3] = 255; michael@0: if (Math.abs(refData[offset + 0] - testData[offset + 0]) > tolerance || michael@0: Math.abs(refData[offset + 1] - testData[offset + 1]) > tolerance || michael@0: Math.abs(refData[offset + 2] - testData[offset + 2]) > tolerance || michael@0: Math.abs(refData[offset + 3] - testData[offset + 3]) > tolerance) { michael@0: imgData.data[imgOffset] = 255; michael@0: same = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: var diffImg = null; michael@0: if (!same) { michael@0: ctx.putImageData(imgData, 0, 0); michael@0: diffImg = wtu.makeImage(canvas2d); michael@0: } michael@0: michael@0: var div = document.createElement("div"); michael@0: div.className = "testimages"; michael@0: wtu.insertImage(div, "ref", refImg); michael@0: wtu.insertImage(div, "test", testImg); michael@0: if (diffImg) { michael@0: wtu.insertImage(div, "diff", diffImg); michael@0: } michael@0: div.appendChild(document.createElement('br')); michael@0: michael@0: michael@0: console.appendChild(div); michael@0: michael@0: if (!same) { michael@0: testFailed("images are different"); michael@0: } else { michael@0: testPassed("images are the same"); michael@0: } michael@0: michael@0: console.appendChild(document.createElement('hr')); michael@0: } michael@0: michael@0: function draw(canvas, vsSource, fsSource) { michael@0: var program = wtu.loadProgram(gl, vsSource, fsSource, testFailed); michael@0: michael@0: var posLoc = gl.getAttribLocation(program, "aPosition"); michael@0: WebGLTestUtils.setupQuad(gl, gridRes, posLoc); michael@0: michael@0: gl.useProgram(program); michael@0: gl.clearColor(0, 0, 1, 1); michael@0: gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); michael@0: gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0); michael@0: wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); michael@0: michael@0: var img = new Uint8Array(width * height * 4); michael@0: gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); michael@0: return img; michael@0: } michael@0: michael@0: }; michael@0: michael@0: var runBasicTest = function(params) { michael@0: if (window.initNonKhronosFramework) { michael@0: window.initNonKhronosFramework(false); michael@0: } michael@0: michael@0: var wtu = WebGLTestUtils; michael@0: var gridRes = params.gridRes; michael@0: var vertexTolerance = params.tolerance || 0; michael@0: var fragmentTolerance = vertexTolerance; michael@0: if ('fragmentTolerance' in params) michael@0: fragmentTolerance = params.fragmentTolerance || 0; michael@0: michael@0: description("Testing : " + document.getElementsByTagName("title")[0].innerText); michael@0: michael@0: var width = 32; michael@0: var height = 32; michael@0: michael@0: var console = document.getElementById("console"); michael@0: var canvas = document.createElement('canvas'); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: var gl = wtu.create3DContext(canvas); michael@0: if (!gl) { michael@0: testFailed("context does not exist"); michael@0: finishTest(); michael@0: return; michael@0: } michael@0: michael@0: var canvas2d = document.createElement('canvas'); michael@0: canvas2d.width = width; michael@0: canvas2d.height = height; michael@0: var ctx = canvas2d.getContext("2d"); michael@0: var imgData = ctx.getImageData(0, 0, width, height); michael@0: michael@0: var shaderInfos = [ michael@0: { type: "vertex", michael@0: input: "color", michael@0: output: "vColor", michael@0: vertexShaderTemplate: vertexShaderTemplate, michael@0: fragmentShaderTemplate: baseFragmentShader, michael@0: tolerance: vertexTolerance michael@0: }, michael@0: { type: "fragment", michael@0: input: "vColor", michael@0: output: "gl_FragColor", michael@0: vertexShaderTemplate: baseVertexShader, michael@0: fragmentShaderTemplate: fragmentShaderTemplate, michael@0: tolerance: fragmentTolerance michael@0: } michael@0: ]; michael@0: for (var ss = 0; ss < shaderInfos.length; ++ss) { michael@0: var shaderInfo = shaderInfos[ss]; michael@0: var tests = params.tests; michael@0: // var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); michael@0: // Test vertex shaders michael@0: for (var ii = 0; ii < tests.length; ++ii) { michael@0: var test = tests[ii]; michael@0: debug(""); michael@0: debug("Testing: " + test.name + " in " + shaderInfo.type + " shader"); michael@0: michael@0: function genShader(shaderInfo, template, shader, subs) { michael@0: shader = replaceParams(shader, subs, { michael@0: input: shaderInfo.input, michael@0: output: shaderInfo.output michael@0: }); michael@0: shader = replaceParams(template, subs, { michael@0: test: shader, michael@0: emu: "", michael@0: extra: "" michael@0: }); michael@0: return shader; michael@0: } michael@0: michael@0: var referenceVertexShaderSource = genShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: test.reference.shader, michael@0: test.reference.subs); michael@0: var referenceFragmentShaderSource = genShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: test.reference.shader, michael@0: test.reference.subs); michael@0: var testVertexShaderSource = genShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: test.test.shader, michael@0: test.test.subs); michael@0: var testFragmentShaderSource = genShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: test.test.shader, michael@0: test.test.subs); michael@0: michael@0: debug(""); michael@0: wtu.addShaderSource( michael@0: console, "reference vertex shader", referenceVertexShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "reference fragment shader", referenceFragmentShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "test vertex shader", testVertexShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "test fragment shader", testFragmentShaderSource); michael@0: debug(""); michael@0: michael@0: var refData = draw( michael@0: canvas, referenceVertexShaderSource, referenceFragmentShaderSource); michael@0: var refImg = wtu.makeImage(canvas); michael@0: if (ss == 0) { michael@0: var testData = draw( michael@0: canvas, testVertexShaderSource, referenceFragmentShaderSource); michael@0: } else { michael@0: var testData = draw( michael@0: canvas, referenceVertexShaderSource, testFragmentShaderSource); michael@0: } michael@0: var testImg = wtu.makeImage(canvas); michael@0: michael@0: reportResults(refData, refImg, testData, testImg, shaderInfo.tolerance); michael@0: } michael@0: } michael@0: michael@0: finishTest(); michael@0: michael@0: function reportResults(refData, refImage, testData, testImage, tolerance) { michael@0: var same = true; michael@0: for (var yy = 0; yy < height; ++yy) { michael@0: for (var xx = 0; xx < width; ++xx) { michael@0: var offset = (yy * width + xx) * 4; michael@0: var imgOffset = ((height - yy - 1) * width + xx) * 4; michael@0: imgData.data[imgOffset + 0] = 0; michael@0: imgData.data[imgOffset + 1] = 0; michael@0: imgData.data[imgOffset + 2] = 0; michael@0: imgData.data[imgOffset + 3] = 255; michael@0: if (Math.abs(refData[offset + 0] - testData[offset + 0]) > tolerance || michael@0: Math.abs(refData[offset + 1] - testData[offset + 1]) > tolerance || michael@0: Math.abs(refData[offset + 2] - testData[offset + 2]) > tolerance || michael@0: Math.abs(refData[offset + 3] - testData[offset + 3]) > tolerance) { michael@0: imgData.data[imgOffset] = 255; michael@0: same = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: var diffImg = null; michael@0: if (!same) { michael@0: ctx.putImageData(imgData, 0, 0); michael@0: diffImg = wtu.makeImage(canvas2d); michael@0: } michael@0: michael@0: var div = document.createElement("div"); michael@0: div.className = "testimages"; michael@0: wtu.insertImage(div, "ref", refImg); michael@0: wtu.insertImage(div, "test", testImg); michael@0: if (diffImg) { michael@0: wtu.insertImage(div, "diff", diffImg); michael@0: } michael@0: div.appendChild(document.createElement('br')); michael@0: michael@0: console.appendChild(div); michael@0: michael@0: if (!same) { michael@0: testFailed("images are different"); michael@0: } else { michael@0: testPassed("images are the same"); michael@0: } michael@0: michael@0: console.appendChild(document.createElement('hr')); michael@0: } michael@0: michael@0: function draw(canvas, vsSource, fsSource) { michael@0: var program = wtu.loadProgram(gl, vsSource, fsSource, testFailed); michael@0: michael@0: var posLoc = gl.getAttribLocation(program, "aPosition"); michael@0: WebGLTestUtils.setupQuad(gl, gridRes, posLoc); michael@0: michael@0: gl.useProgram(program); michael@0: gl.clearColor(0, 0, 1, 1); michael@0: gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); michael@0: gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0); michael@0: wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); michael@0: michael@0: var img = new Uint8Array(width * height * 4); michael@0: gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); michael@0: return img; michael@0: } michael@0: michael@0: }; michael@0: michael@0: var runReferenceImageTest = function(params) { michael@0: if (window.initNonKhronosFramework) { michael@0: window.initNonKhronosFramework(false); michael@0: } michael@0: michael@0: var wtu = WebGLTestUtils; michael@0: var gridRes = params.gridRes; michael@0: var vertexTolerance = params.tolerance || 0; michael@0: var fragmentTolerance = vertexTolerance; michael@0: if ('fragmentTolerance' in params) michael@0: fragmentTolerance = params.fragmentTolerance || 0; michael@0: michael@0: description("Testing GLSL feature: " + params.feature); michael@0: michael@0: var width = 32; michael@0: var height = 32; michael@0: michael@0: var console = document.getElementById("console"); michael@0: var canvas = document.createElement('canvas'); michael@0: canvas.width = width; michael@0: canvas.height = height; michael@0: var gl = wtu.create3DContext(canvas, { antialias: false }); michael@0: if (!gl) { michael@0: testFailed("context does not exist"); michael@0: finishTest(); michael@0: return; michael@0: } michael@0: michael@0: var canvas2d = document.createElement('canvas'); michael@0: canvas2d.width = width; michael@0: canvas2d.height = height; michael@0: var ctx = canvas2d.getContext("2d"); michael@0: var imgData = ctx.getImageData(0, 0, width, height); michael@0: michael@0: var shaderInfos = [ michael@0: { type: "vertex", michael@0: input: "color", michael@0: output: "vColor", michael@0: vertexShaderTemplate: vertexShaderTemplate, michael@0: fragmentShaderTemplate: baseFragmentShader, michael@0: tolerance: vertexTolerance michael@0: }, michael@0: { type: "fragment", michael@0: input: "vColor", michael@0: output: "gl_FragColor", michael@0: vertexShaderTemplate: baseVertexShader, michael@0: fragmentShaderTemplate: fragmentShaderTemplate, michael@0: tolerance: fragmentTolerance michael@0: } michael@0: ]; michael@0: for (var ss = 0; ss < shaderInfos.length; ++ss) { michael@0: var shaderInfo = shaderInfos[ss]; michael@0: var tests = params.tests; michael@0: var testTypes = params.emuFuncs || (params.bvecTest ? bvecTypes : types); michael@0: // Test vertex shaders michael@0: for (var ii = 0; ii < tests.length; ++ii) { michael@0: var type = testTypes[ii]; michael@0: var isVertex = (ss == 0); michael@0: debug(""); michael@0: var str = replaceParams(params.testFunc, { michael@0: func: params.feature, michael@0: type: type.type, michael@0: arg0: type.type michael@0: }); michael@0: debug("Testing: " + str + " in " + shaderInfo.type + " shader"); michael@0: michael@0: var referenceVertexShaderSource = generateReferenceShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: params, michael@0: type, michael@0: tests[ii].source); michael@0: var referenceFragmentShaderSource = generateReferenceShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: params, michael@0: type, michael@0: tests[ii].source); michael@0: var testVertexShaderSource = generateTestShader( michael@0: shaderInfo, michael@0: shaderInfo.vertexShaderTemplate, michael@0: params, michael@0: tests[ii].source); michael@0: var testFragmentShaderSource = generateTestShader( michael@0: shaderInfo, michael@0: shaderInfo.fragmentShaderTemplate, michael@0: params, michael@0: tests[ii].source); michael@0: var referenceTexture = generateReferenceTexture( michael@0: gl, michael@0: tests[ii].generator, michael@0: isVertex ? gridRes : width, michael@0: isVertex ? gridRes : height, michael@0: isVertex); michael@0: michael@0: debug(""); michael@0: wtu.addShaderSource( michael@0: console, "test vertex shader", testVertexShaderSource); michael@0: wtu.addShaderSource( michael@0: console, "test fragment shader", testFragmentShaderSource); michael@0: debug(""); michael@0: var refData = drawReferenceImage(canvas, referenceTexture, isVertex); michael@0: var refImg = wtu.makeImage(canvas); michael@0: if (isVertex) { michael@0: var testData = draw( michael@0: canvas, testVertexShaderSource, referenceFragmentShaderSource); michael@0: } else { michael@0: var testData = draw( michael@0: canvas, referenceVertexShaderSource, testFragmentShaderSource); michael@0: } michael@0: var testImg = wtu.makeImage(canvas); michael@0: var testTolerance = shaderInfo.tolerance; michael@0: // Provide per-test tolerance so that we can increase it only for those desired. michael@0: if ('tolerance' in tests[ii]) michael@0: testTolerance = tests[ii].tolerance || 0; michael@0: reportResults(refData, refImg, testData, testImg, testTolerance); michael@0: } michael@0: } michael@0: michael@0: finishTest(); michael@0: michael@0: function reportResults(refData, refImage, testData, testImage, tolerance) { michael@0: var same = true; michael@0: for (var yy = 0; yy < height; ++yy) { michael@0: for (var xx = 0; xx < width; ++xx) { michael@0: var offset = (yy * width + xx) * 4; michael@0: var imgOffset = ((height - yy - 1) * width + xx) * 4; michael@0: imgData.data[imgOffset + 0] = 0; michael@0: imgData.data[imgOffset + 1] = 0; michael@0: imgData.data[imgOffset + 2] = 0; michael@0: imgData.data[imgOffset + 3] = 255; michael@0: if (Math.abs(refData[offset + 0] - testData[offset + 0]) > tolerance || michael@0: Math.abs(refData[offset + 1] - testData[offset + 1]) > tolerance || michael@0: Math.abs(refData[offset + 2] - testData[offset + 2]) > tolerance || michael@0: Math.abs(refData[offset + 3] - testData[offset + 3]) > tolerance) { michael@0: console.appendChild(document.createTextNode('at (' + xx + ',' + yy + '): ref=(' + michael@0: refData[offset + 0] + ',' + michael@0: refData[offset + 1] + ',' + michael@0: refData[offset + 2] + ',' + michael@0: refData[offset + 3] + ') test=(' + michael@0: testData[offset + 0] + ',' + michael@0: testData[offset + 1] + ',' + michael@0: testData[offset + 2] + ',' + michael@0: testData[offset + 3] + ')')); michael@0: console.appendChild(document.createElement('br')); michael@0: michael@0: michael@0: michael@0: imgData.data[imgOffset] = 255; michael@0: same = false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: var diffImg = null; michael@0: if (!same) { michael@0: ctx.putImageData(imgData, 0, 0); michael@0: diffImg = wtu.makeImage(canvas2d); michael@0: } michael@0: michael@0: var div = document.createElement("div"); michael@0: div.className = "testimages"; michael@0: wtu.insertImage(div, "ref", refImg); michael@0: wtu.insertImage(div, "test", testImg); michael@0: if (diffImg) { michael@0: wtu.insertImage(div, "diff", diffImg); michael@0: } michael@0: div.appendChild(document.createElement('br')); michael@0: michael@0: console.appendChild(div); michael@0: michael@0: if (!same) { michael@0: testFailed("images are different"); michael@0: } else { michael@0: testPassed("images are the same"); michael@0: } michael@0: michael@0: console.appendChild(document.createElement('hr')); michael@0: } michael@0: michael@0: function draw(canvas, vsSource, fsSource) { michael@0: var program = wtu.loadProgram(gl, vsSource, fsSource, testFailed); michael@0: michael@0: var posLoc = gl.getAttribLocation(program, "aPosition"); michael@0: WebGLTestUtils.setupQuad(gl, gridRes, posLoc); michael@0: michael@0: gl.useProgram(program); michael@0: gl.clearColor(0, 0, 1, 1); michael@0: gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); michael@0: gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0); michael@0: wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); michael@0: michael@0: var img = new Uint8Array(width * height * 4); michael@0: gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); michael@0: return img; michael@0: } michael@0: michael@0: function drawReferenceImage(canvas, texture, isVertex) { michael@0: var program; michael@0: if (isVertex) { michael@0: var halfTexel = 0.5 / (1.0 + gridRes); michael@0: program = WebGLTestUtils.setupTexturedQuadWithTexCoords( michael@0: gl, [halfTexel, halfTexel], [1.0 - halfTexel, 1.0 - halfTexel]); michael@0: } else { michael@0: program = WebGLTestUtils.setupTexturedQuad(gl); michael@0: } michael@0: michael@0: gl.activeTexture(gl.TEXTURE0); michael@0: gl.bindTexture(gl.TEXTURE_2D, texture); michael@0: var texLoc = gl.getUniformLocation(program, "tex"); michael@0: gl.uniform1i(texLoc, 0); michael@0: wtu.drawQuad(gl); michael@0: wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors from draw"); michael@0: michael@0: var img = new Uint8Array(width * height * 4); michael@0: gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, img); michael@0: return img; michael@0: } michael@0: michael@0: /** michael@0: * Creates and returns a texture containing the reference image for michael@0: * the function being tested. Exactly how the function is evaluated, michael@0: * and the size of the returned texture, depends on whether we are michael@0: * testing a vertex or fragment shader. If a fragment shader, the michael@0: * function is evaluated at the pixel centers. If a vertex shader, michael@0: * the function is evaluated at the triangle's vertices, and the michael@0: * resulting texture must be offset by half a texel during michael@0: * rendering. michael@0: * michael@0: * @param {!WebGLRenderingContext} gl The WebGLRenderingContext to use to generate texture objects. michael@0: * @param {!function(number,number,number,number): !Array.} generator The reference image generator function. michael@0: * @param {number} width The width of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. michael@0: * @param {number} height The height of the texture to generate if testing a fragment shader; the grid resolution if testing a vertex shader. michael@0: * @param {boolean} isVertex True if generating a reference image for a vertex shader; false if for a fragment shader. michael@0: * @return {!WebGLTexture} The texture object that was generated. michael@0: */ michael@0: function generateReferenceTexture( michael@0: gl, michael@0: generator, michael@0: width, michael@0: height, michael@0: isVertex) { michael@0: michael@0: // Note: the math in this function must match that in the vertex and michael@0: // fragment shader templates above. michael@0: function computeTexCoord(x) { michael@0: return x * 0.5 + 0.5; michael@0: } michael@0: michael@0: function computeColor(texCoordX, texCoordY) { michael@0: return [ texCoordX, michael@0: texCoordY, michael@0: texCoordX * texCoordY, michael@0: (1.0 - texCoordX) * texCoordY * 0.5 + 0.5 ]; michael@0: } michael@0: michael@0: function clamp(value, minVal, maxVal) { michael@0: return Math.max(minVal, Math.min(value, maxVal)); michael@0: } michael@0: michael@0: // Evaluates the function at clip coordinates (px,py), storing the michael@0: // result in the array "pixel". Each channel's result is clamped michael@0: // between 0 and 255. michael@0: function evaluateAtClipCoords(px, py, pixel) { michael@0: var tcx = computeTexCoord(px); michael@0: var tcy = computeTexCoord(py); michael@0: michael@0: var color = computeColor(tcx, tcy); michael@0: michael@0: var output = generator(color[0], color[1], color[2], color[3]); michael@0: michael@0: // Multiply by 256 to get even distribution for all values between 0 and 1. michael@0: // Use rounding rather than truncation to more closely match the GPU's behavior. michael@0: pixel[0] = clamp(Math.round(256 * output[0]), 0, 255); michael@0: pixel[1] = clamp(Math.round(256 * output[1]), 0, 255); michael@0: pixel[2] = clamp(Math.round(256 * output[2]), 0, 255); michael@0: pixel[3] = clamp(Math.round(256 * output[3]), 0, 255); michael@0: } michael@0: michael@0: function fillFragmentReference() { michael@0: var data = new Uint8Array(4 * width * height); michael@0: michael@0: var horizTexel = 1.0 / width; michael@0: var vertTexel = 1.0 / height; michael@0: var halfHorizTexel = 0.5 * horizTexel; michael@0: var halfVertTexel = 0.5 * vertTexel; michael@0: michael@0: var pixel = new Array(4); michael@0: michael@0: for (var yi = 0; yi < height; ++yi) { michael@0: for (var xi = 0; xi < width; ++xi) { michael@0: // The function must be evaluated at pixel centers. michael@0: michael@0: // Compute desired position in clip space michael@0: var px = -1.0 + 2.0 * (halfHorizTexel + xi * horizTexel); michael@0: var py = -1.0 + 2.0 * (halfVertTexel + yi * vertTexel); michael@0: michael@0: evaluateAtClipCoords(px, py, pixel); michael@0: var index = 4 * (width * yi + xi); michael@0: data[index + 0] = pixel[0]; michael@0: data[index + 1] = pixel[1]; michael@0: data[index + 2] = pixel[2]; michael@0: data[index + 3] = pixel[3]; michael@0: } michael@0: } michael@0: michael@0: gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, michael@0: gl.RGBA, gl.UNSIGNED_BYTE, data); michael@0: } michael@0: michael@0: function fillVertexReference() { michael@0: // We generate a texture which contains the evaluation of the michael@0: // function at the vertices of the triangle mesh. It is expected michael@0: // that the width and the height are identical, and equivalent michael@0: // to the grid resolution. michael@0: if (width != height) { michael@0: throw "width and height must be equal"; michael@0: } michael@0: michael@0: var texSize = 1 + width; michael@0: var data = new Uint8Array(4 * texSize * texSize); michael@0: michael@0: var step = 2.0 / width; michael@0: michael@0: var pixel = new Array(4); michael@0: michael@0: for (var yi = 0; yi < texSize; ++yi) { michael@0: for (var xi = 0; xi < texSize; ++xi) { michael@0: // The function is evaluated at the triangles' vertices. michael@0: michael@0: // Compute desired position in clip space michael@0: var px = -1.0 + (xi * step); michael@0: var py = -1.0 + (yi * step); michael@0: michael@0: evaluateAtClipCoords(px, py, pixel); michael@0: var index = 4 * (texSize * yi + xi); michael@0: data[index + 0] = pixel[0]; michael@0: data[index + 1] = pixel[1]; michael@0: data[index + 2] = pixel[2]; michael@0: data[index + 3] = pixel[3]; michael@0: } michael@0: } michael@0: michael@0: gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, texSize, texSize, 0, michael@0: gl.RGBA, gl.UNSIGNED_BYTE, data); michael@0: } michael@0: michael@0: //---------------------------------------------------------------------- michael@0: // Body of generateReferenceTexture michael@0: // michael@0: michael@0: var texture = gl.createTexture(); michael@0: gl.bindTexture(gl.TEXTURE_2D, texture); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); michael@0: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); michael@0: michael@0: if (isVertex) { michael@0: fillVertexReference(); michael@0: } else { michael@0: fillFragmentReference(); michael@0: } michael@0: michael@0: return texture; michael@0: } michael@0: }; michael@0: michael@0: return { michael@0: /** michael@0: * runs a bunch of GLSL tests using the passed in parameters michael@0: * The parameters are: michael@0: * michael@0: * feature: michael@0: * the name of the function being tested (eg, sin, dot, michael@0: * normalize) michael@0: * michael@0: * testFunc: michael@0: * The prototype of function to be tested not including the michael@0: * return type. michael@0: * michael@0: * emuFunc: michael@0: * A base function that can be used to generate emulation michael@0: * functions. Example for 'ceil' michael@0: * michael@0: * float $(func)_base(float value) { michael@0: * float m = mod(value, 1.0); michael@0: * return m != 0.0 ? (value + 1.0 - m) : value; michael@0: * } michael@0: * michael@0: * args: michael@0: * The arguments to the function michael@0: * michael@0: * baseArgs: (optional) michael@0: * The arguments when a base function is used to create an michael@0: * emulation function. For example 'float sign_base(float v)' michael@0: * is used to implemenent vec2 sign_emu(vec2 v). michael@0: * michael@0: * simpleEmu: michael@0: * if supplied, the code that can be used to generate all michael@0: * functions for all types. michael@0: * michael@0: * Example for 'normalize': michael@0: * michael@0: * $(type) $(func)_emu($(args)) { michael@0: * return value / length(value); michael@0: * } michael@0: * michael@0: * gridRes: (optional) michael@0: * The resolution of the mesh to generate. The default is a michael@0: * 1x1 grid but many vertex shaders need a higher resolution michael@0: * otherwise the only values passed in are the 4 corners michael@0: * which often have the same value. michael@0: * michael@0: * tests: michael@0: * The code for each test. It is assumed the tests are for michael@0: * float, vec2, vec3, vec4 in that order. michael@0: * michael@0: * tolerance: (optional) michael@0: * Allow some tolerance in the comparisons. The tolerance is applied to michael@0: * both vertex and fragment shaders. The default tolerance is 0, meaning michael@0: * the values have to be identical. michael@0: * michael@0: * fragmentTolerance: (optional) michael@0: * Specify a tolerance which only applies to fragment shaders. The michael@0: * fragment-only tolerance will override the shared tolerance for michael@0: * fragment shaders if both are specified. Fragment shaders usually michael@0: * use mediump float precision so they sometimes require higher tolerance michael@0: * than vertex shaders which use highp by default. michael@0: */ michael@0: runFeatureTest: runFeatureTest, michael@0: michael@0: /* michael@0: * Runs a bunch of GLSL tests using the passed in parameters michael@0: * michael@0: * The parameters are: michael@0: * michael@0: * tests: michael@0: * Array of tests. For each test the following parameters are expected michael@0: * michael@0: * name: michael@0: * some description of the test michael@0: * reference: michael@0: * parameters for the reference shader (see below) michael@0: * test: michael@0: * parameters for the test shader (see below) michael@0: * michael@0: * The parameter for the reference and test shaders are michael@0: * michael@0: * shader: the GLSL for the shader michael@0: * subs: any substitutions you wish to define for the shader. michael@0: * michael@0: * Each shader is created from a basic template that michael@0: * defines an input and an output. You can see the michael@0: * templates at the top of this file. The input and output michael@0: * change depending on whether or not we are generating michael@0: * a vertex or fragment shader. michael@0: * michael@0: * All this code function does is a bunch of string substitutions. michael@0: * A substitution is defined by $(name). If name is found in michael@0: * the 'subs' parameter it is replaced. 4 special names exist. michael@0: * michael@0: * 'input' the input to your GLSL. Always a vec4. All change michael@0: * from 0 to 1 over the quad to be drawn. michael@0: * michael@0: * 'output' the output color. Also a vec4 michael@0: * michael@0: * 'emu' a place to insert extra stuff michael@0: * 'extra' a place to insert extra stuff. michael@0: * michael@0: * You can think of the templates like this michael@0: * michael@0: * $(extra) michael@0: * $(emu) michael@0: * michael@0: * void main() { michael@0: * // do math to calculate input michael@0: * ... michael@0: * michael@0: * $(shader) michael@0: * } michael@0: * michael@0: * Your shader first has any subs you provided applied as well michael@0: * as 'input' and 'output' michael@0: * michael@0: * It is then inserted into the template which is also provided michael@0: * with your subs. michael@0: * michael@0: * gridRes: (optional) michael@0: * The resolution of the mesh to generate. The default is a michael@0: * 1x1 grid but many vertex shaders need a higher resolution michael@0: * otherwise the only values passed in are the 4 corners michael@0: * which often have the same value. michael@0: * michael@0: * tolerance: (optional) michael@0: * Allow some tolerance in the comparisons. The tolerance is applied to michael@0: * both vertex and fragment shaders. The default tolerance is 0, meaning michael@0: * the values have to be identical. michael@0: * michael@0: * fragmentTolerance: (optional) michael@0: * Specify a tolerance which only applies to fragment shaders. The michael@0: * fragment-only tolerance will override the shared tolerance for michael@0: * fragment shaders if both are specified. Fragment shaders usually michael@0: * use mediump float precision so they sometimes require higher tolerance michael@0: * than vertex shaders which use highp. michael@0: */ michael@0: runBasicTest: runBasicTest, michael@0: michael@0: /** michael@0: * Runs a bunch of GLSL tests using the passed in parameters. The michael@0: * expected results are computed as a reference image in JavaScript michael@0: * instead of on the GPU. The parameters are: michael@0: * michael@0: * feature: michael@0: * the name of the function being tested (eg, sin, dot, michael@0: * normalize) michael@0: * michael@0: * testFunc: michael@0: * The prototype of function to be tested not including the michael@0: * return type. michael@0: * michael@0: * args: michael@0: * The arguments to the function michael@0: * michael@0: * gridRes: (optional) michael@0: * The resolution of the mesh to generate. The default is a michael@0: * 1x1 grid but many vertex shaders need a higher resolution michael@0: * otherwise the only values passed in are the 4 corners michael@0: * which often have the same value. michael@0: * michael@0: * tests: michael@0: * Array of tests. It is assumed the tests are for float, vec2, michael@0: * vec3, vec4 in that order. For each test the following michael@0: * parameters are expected: michael@0: * michael@0: * source: the GLSL source code for the tests michael@0: * michael@0: * generator: a JavaScript function taking four parameters michael@0: * which evaluates the same function as the GLSL source, michael@0: * returning its result as a newly allocated array. michael@0: * michael@0: * tolerance: (optional) a per-test tolerance. michael@0: * michael@0: * extra: (optional) michael@0: * Extra GLSL code inserted at the top of each test's shader. michael@0: * michael@0: * tolerance: (optional) michael@0: * Allow some tolerance in the comparisons. The tolerance is applied to michael@0: * both vertex and fragment shaders. The default tolerance is 0, meaning michael@0: * the values have to be identical. michael@0: * michael@0: * fragmentTolerance: (optional) michael@0: * Specify a tolerance which only applies to fragment shaders. The michael@0: * fragment-only tolerance will override the shared tolerance for michael@0: * fragment shaders if both are specified. Fragment shaders usually michael@0: * use mediump float precision so they sometimes require higher tolerance michael@0: * than vertex shaders which use highp. michael@0: */ michael@0: runReferenceImageTest: runReferenceImageTest, michael@0: michael@0: none: false michael@0: }; michael@0: michael@0: }()); michael@0: