Source code

Revision control

Copy as Markdown

Other Tools

/*
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
};
}());