blob: 1202b7868cddece634cd61da6963593605bfd48d [file] [log] [blame]
/*
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.
*/
NoOverOptimizeOnUniformArrayTester = (function(){
var vshader = [
"attribute vec4 a_position;",
"void main()",
"{",
" gl_Position = a_position;",
"}"
].join('\n');
var fshader_max = [
"precision mediump float;",
"uniform vec4 colora[$(maxUniformVectors)];",
"void main()",
"{",
" gl_FragColor = vec4(colora[$(usedUniformVector)]);",
"}"
].join('\n');
var fshader_max_ab_ab = [
"precision mediump float;",
"uniform vec4 $(decl1);",
"uniform vec4 $(decl2);",
"void main()",
"{",
"gl_FragColor = vec4($(usage1) + $(usage2));",
"}"
].join('\n');
// MaxInt32 is 2^32-1. We need +1 of that to test overflow conditions
var MaxInt32PlusOne = 4294967296;
function setupTests(gl) {
var tests = [];
var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS);
// This test is to test drivers the have bugs related to optimizing
// an array of uniforms when only 1 of those uniforms is used.
tests.push({
desc: "using last element",
maxUniformVectors: maxUniformVectors,
usedUniformVector: maxUniformVectors - 1,
shader: "fshader-max",
color: [0, 1, 0, 1],
arrayName: "colora",
extraName: "colorb",
});
tests.push({
desc: "using first element",
maxUniformVectors: maxUniformVectors,
usedUniformVector: 0,
shader: "fshader-max",
color: [0, 1, 0, 1],
arrayName: "colora",
extraName: "colorb",
});
// Generate test shaders. We're trying to force the driver to
// overflow from 1 array into the next if it optimizes. So for example if it was C
//
// int big[4];
// int little[1];
// big[5] = 124;
//
// Would end up setting little[0] instead of big. Some drivers optimize
// where if you only use say 'big[3]' it will actually only allocate just 1 element
// for big.
//
// But, some drivers have a bug where the fact that they optimized big to 1 element
// does not get passed down to glUniform so when setting the uniform 'big[3]' they
// overwrite memory.
//
// If the driver crashes, yea. We found a bug. We can block the driver.
// Otherwise we try various combinations so that setting 'little[0]' first
// and then setting all elements of 'big' we hope it will overwrite 'little[0]'
// which will show the bug and again we can block the driver.
//
// We don't know how the driver will order, in memory, the various uniforms
// or for that matter we don't even know if they will be contiguous in memory
// but to hopefully expose any bugs we try various combinations.
//
// It could be the compiler orders uniforms alphabetically.
// It could be it orders them in order of declaration.
// It could be it orders them in order of usage.
//
// We also test using only first element of big or just the last element of big.
//
for (var nameOrder = 0; nameOrder < 2; ++nameOrder) {
var name1 = nameOrder ? "colora" : "colorb";
var name2 = nameOrder ? "colorb" : "colora";
for (var last = 0; last < 2; ++last) {
var usedUniformVector = last ? maxUniformVectors - 2 : 0;
for (var declOrder = 0; declOrder < 2; ++declOrder) {
var bigName = declOrder ? name1 : name2;
var littleName = declOrder ? name2 : name1;
var decl1 = bigName + "[" + (maxUniformVectors - 1) + "]";
var decl2 = littleName + "[1]";
if (declOrder) {
var t = decl1;
decl1 = decl2;
decl2 = t;
}
for (var usageOrder = 0; usageOrder < 2; ++usageOrder) {
var usage1 = bigName + "[" + usedUniformVector + "]";
var usage2 = littleName + "[0]";
if (usageOrder) {
var t = usage1;
usage1 = usage2;
usage2 = t;
}
var fSrc = wtu.replaceParams(fshader_max_ab_ab, {
decl1: decl1,
decl2: decl2,
usage1: usage1,
usage2: usage2,
});
var desc = "testing: " + name1 + ":" + name2 + " using " + (last ? "last" : "first") +
" creating uniforms " + decl1 + " " + decl2 + " and accessing " + usage1 + " " + usage2;
tests.push({
desc: desc,
maxUniformVectors: maxUniformVectors - 1,
usedUniformVector: usedUniformVector,
source: fSrc,
color: [0, 0, 0, 1],
arrayName: bigName,
extraName: littleName,
});
}
}
}
}
return tests;
};
function testUniformOptimizationIssues(test) {
debug("");
debug(test.desc);
var fshader = test.source;
if (!fshader) {
fshader = wtu.replaceParams(fshader_max, test);
}
var consoleElem = document.getElementById("console");
wtu.addShaderSource(
consoleElem, "vertex shader", vshader);
wtu.addShaderSource(
consoleElem, "fragment shader", fshader);
var program = wtu.loadProgram(gl, vshader, fshader);
gl.useProgram(program);
var colorbLocation = gl.getUniformLocation(program, test.extraName + "[0]");
if (colorbLocation) {
gl.uniform4fv(colorbLocation, [0, 1, 0, 0]);
}
// Ensure that requesting an array uniform past MaxInt32PlusOne returns no uniform
var nameMaxInt32PlusOne = test.arrayName + "[" + (test.usedUniformVector + MaxInt32PlusOne) + "]";
assertMsg(gl.getUniformLocation(program, nameMaxInt32PlusOne) === null,
"Requesting " + nameMaxInt32PlusOne + " uniform should return a null uniform location");
// Set just the used uniform
var name = test.arrayName + "[" + test.usedUniformVector + "]";
var uniformLocation = gl.getUniformLocation(program, name);
gl.uniform4fv(uniformLocation, test.color);
wtu.setupIndexedQuad(gl, 1);
wtu.clearAndDrawIndexedQuad(gl, 1);
wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
// Set all the unused uniforms
var locations = [];
var allRequiredUniformLocationsQueryable = true;
for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
var name = test.arrayName + "[" + ii + "]";
var uniformLocation = gl.getUniformLocation(program, name);
locations.push(uniformLocation);
if (ii == test.usedUniformVector) {
continue;
}
// Locations > usedUnformVector may not exist.
// Locations <= usedUniformVector MUST exist.
if (ii <= test.usedUniformVector && (uniformLocation === undefined || uniformLocation === null)) {
allRequiredUniformLocationsQueryable = false;
}
gl.uniform4fv(uniformLocation, [1, 0, 0, 1]);
}
if (allRequiredUniformLocationsQueryable) {
testPassed("allRequiredUniformLocationsQueryable is true.");
}
else {
testFailed("allRequiredUniformLocationsQueryable should be true. Was false.");
}
var positionLoc = gl.getAttribLocation(program, "a_position");
wtu.setupIndexedQuad(gl, 1, positionLoc);
wtu.clearAndDrawIndexedQuad(gl, 1);
wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
// Check we can read & write each uniform.
// Note: uniforms past test.usedUniformVector might not exist.
for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
gl.uniform4fv(locations[ii], [ii + 4, ii + 2, ii + 3, ii + 1]);
}
var kEpsilon = 0.01;
var isSame = function(v1, v2) {
return Math.abs(v1 - v2) < kEpsilon;
};
for (var ii = 0; ii < test.maxUniformVectors; ++ii) {
var location = locations[ii];
if (location) {
var value = gl.getUniform(program, locations[ii]);
if (!isSame(value[0], ii + 4) ||
!isSame(value[1], ii + 2) ||
!isSame(value[2], ii + 3) ||
!isSame(value[3], ii + 1)) {
testFailed("location: " + ii + " was not correct value");
break;
}
}
}
}
function runOneTest(gl, test) {
testUniformOptimizationIssues(test);
};
function runTests(gl, tests) {
debug("");
debug("Test drivers don't over optimize unused array elements");
for (var ii = 0; ii < tests.length; ++ii) {
runOneTest(gl, tests[ii]);
}
};
return {
setupTests : setupTests,
runTests : runTests
};
}());