no-over-optimizations-on-uniform-array.js (8974B)
1 /* 2 Copyright (c) 2019 The Khronos Group Inc. 3 Use of this source code is governed by an MIT-style license that can be 4 found in the LICENSE.txt file. 5 */ 6 NoOverOptimizeOnUniformArrayTester = (function(){ 7 8 var vshader = [ 9 "attribute vec4 a_position;", 10 "void main()", 11 "{", 12 " gl_Position = a_position;", 13 "}" 14 ].join('\n'); 15 16 var fshader_max = [ 17 "precision mediump float;", 18 "uniform vec4 colora[$(maxUniformVectors)];", 19 "void main()", 20 "{", 21 " gl_FragColor = vec4(colora[$(usedUniformVector)]);", 22 "}" 23 ].join('\n'); 24 25 var fshader_max_ab_ab = [ 26 "precision mediump float;", 27 "uniform vec4 $(decl1);", 28 "uniform vec4 $(decl2);", 29 "void main()", 30 "{", 31 "gl_FragColor = vec4($(usage1) + $(usage2));", 32 "}" 33 ].join('\n'); 34 35 // MaxInt32 is 2^32-1. We need +1 of that to test overflow conditions 36 var MaxInt32PlusOne = 4294967296; 37 38 function setupTests(gl) { 39 var tests = []; 40 var maxUniformVectors = gl.getParameter(gl.MAX_FRAGMENT_UNIFORM_VECTORS); 41 42 // This test is to test drivers the have bugs related to optimizing 43 // an array of uniforms when only 1 of those uniforms is used. 44 tests.push({ 45 desc: "using last element", 46 maxUniformVectors: maxUniformVectors, 47 usedUniformVector: maxUniformVectors - 1, 48 shader: "fshader-max", 49 color: [0, 1, 0, 1], 50 arrayName: "colora", 51 extraName: "colorb", 52 }); 53 tests.push({ 54 desc: "using first element", 55 maxUniformVectors: maxUniformVectors, 56 usedUniformVector: 0, 57 shader: "fshader-max", 58 color: [0, 1, 0, 1], 59 arrayName: "colora", 60 extraName: "colorb", 61 }); 62 63 // Generate test shaders. We're trying to force the driver to 64 // overflow from 1 array into the next if it optimizes. So for example if it was C 65 // 66 // int big[4]; 67 // int little[1]; 68 // big[5] = 124; 69 // 70 // Would end up setting little[0] instead of big. Some drivers optimize 71 // where if you only use say 'big[3]' it will actually only allocate just 1 element 72 // for big. 73 // 74 // But, some drivers have a bug where the fact that they optimized big to 1 element 75 // does not get passed down to glUniform so when setting the uniform 'big[3]' they 76 // overwrite memory. 77 // 78 // If the driver crashes, yea. We found a bug. We can block the driver. 79 // Otherwise we try various combinations so that setting 'little[0]' first 80 // and then setting all elements of 'big' we hope it will overwrite 'little[0]' 81 // which will show the bug and again we can block the driver. 82 // 83 // We don't know how the driver will order, in memory, the various uniforms 84 // or for that matter we don't even know if they will be contiguous in memory 85 // but to hopefully expose any bugs we try various combinations. 86 // 87 // It could be the compiler orders uniforms alphabetically. 88 // It could be it orders them in order of declaration. 89 // It could be it orders them in order of usage. 90 // 91 // We also test using only first element of big or just the last element of big. 92 // 93 for (var nameOrder = 0; nameOrder < 2; ++nameOrder) { 94 var name1 = nameOrder ? "colora" : "colorb"; 95 var name2 = nameOrder ? "colorb" : "colora"; 96 for (var last = 0; last < 2; ++last) { 97 var usedUniformVector = last ? maxUniformVectors - 2 : 0; 98 for (var declOrder = 0; declOrder < 2; ++declOrder) { 99 var bigName = declOrder ? name1 : name2; 100 var littleName = declOrder ? name2 : name1; 101 var decl1 = bigName + "[" + (maxUniformVectors - 1) + "]"; 102 var decl2 = littleName + "[1]"; 103 if (declOrder) { 104 var t = decl1; 105 decl1 = decl2; 106 decl2 = t; 107 } 108 for (var usageOrder = 0; usageOrder < 2; ++usageOrder) { 109 var usage1 = bigName + "[" + usedUniformVector + "]"; 110 var usage2 = littleName + "[0]"; 111 if (usageOrder) { 112 var t = usage1; 113 usage1 = usage2; 114 usage2 = t; 115 } 116 var fSrc = wtu.replaceParams(fshader_max_ab_ab, { 117 decl1: decl1, 118 decl2: decl2, 119 usage1: usage1, 120 usage2: usage2, 121 }); 122 var desc = "testing: " + name1 + ":" + name2 + " using " + (last ? "last" : "first") + 123 " creating uniforms " + decl1 + " " + decl2 + " and accessing " + usage1 + " " + usage2; 124 tests.push({ 125 desc: desc, 126 maxUniformVectors: maxUniformVectors - 1, 127 usedUniformVector: usedUniformVector, 128 source: fSrc, 129 color: [0, 0, 0, 1], 130 arrayName: bigName, 131 extraName: littleName, 132 }); 133 } 134 } 135 } 136 } 137 return tests; 138 }; 139 140 function testUniformOptimizationIssues(test) { 141 debug(""); 142 debug(test.desc); 143 var fshader = test.source; 144 if (!fshader) { 145 fshader = wtu.replaceParams(fshader_max, test); 146 } 147 148 var consoleElem = document.getElementById("console"); 149 wtu.addShaderSource( 150 consoleElem, "vertex shader", vshader); 151 wtu.addShaderSource( 152 consoleElem, "fragment shader", fshader); 153 154 var program = wtu.loadProgram(gl, vshader, fshader); 155 gl.useProgram(program); 156 157 var colorbLocation = gl.getUniformLocation(program, test.extraName + "[0]"); 158 if (colorbLocation) { 159 gl.uniform4fv(colorbLocation, [0, 1, 0, 0]); 160 } 161 162 // Ensure that requesting an array uniform past MaxInt32PlusOne returns no uniform 163 var nameMaxInt32PlusOne = test.arrayName + "[" + (test.usedUniformVector + MaxInt32PlusOne) + "]"; 164 assertMsg(gl.getUniformLocation(program, nameMaxInt32PlusOne) === null, 165 "Requesting " + nameMaxInt32PlusOne + " uniform should return a null uniform location"); 166 167 // Set just the used uniform 168 var name = test.arrayName + "[" + test.usedUniformVector + "]"; 169 var uniformLocation = gl.getUniformLocation(program, name); 170 gl.uniform4fv(uniformLocation, test.color); 171 wtu.setupIndexedQuad(gl, 1); 172 wtu.clearAndDrawIndexedQuad(gl, 1); 173 wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); 174 175 // Set all the unused uniforms 176 var locations = []; 177 var allRequiredUniformLocationsQueryable = true; 178 for (var ii = 0; ii < test.maxUniformVectors; ++ii) { 179 var name = test.arrayName + "[" + ii + "]"; 180 var uniformLocation = gl.getUniformLocation(program, name); 181 locations.push(uniformLocation); 182 if (ii == test.usedUniformVector) { 183 continue; 184 } 185 // Locations > usedUnformVector may not exist. 186 // Locations <= usedUniformVector MUST exist. 187 if (ii <= test.usedUniformVector && (uniformLocation === undefined || uniformLocation === null)) { 188 allRequiredUniformLocationsQueryable = false; 189 } 190 gl.uniform4fv(uniformLocation, [1, 0, 0, 1]); 191 } 192 if (allRequiredUniformLocationsQueryable) { 193 testPassed("allRequiredUniformLocationsQueryable is true."); 194 } 195 else { 196 testFailed("allRequiredUniformLocationsQueryable should be true. Was false."); 197 } 198 var positionLoc = gl.getAttribLocation(program, "a_position"); 199 wtu.setupIndexedQuad(gl, 1, positionLoc); 200 wtu.clearAndDrawIndexedQuad(gl, 1); 201 wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green"); 202 203 // Check we can read & write each uniform. 204 // Note: uniforms past test.usedUniformVector might not exist. 205 for (var ii = 0; ii < test.maxUniformVectors; ++ii) { 206 gl.uniform4fv(locations[ii], [ii + 4, ii + 2, ii + 3, ii + 1]); 207 } 208 209 var kEpsilon = 0.01; 210 var isSame = function(v1, v2) { 211 return Math.abs(v1 - v2) < kEpsilon; 212 }; 213 214 for (var ii = 0; ii < test.maxUniformVectors; ++ii) { 215 var location = locations[ii]; 216 if (location) { 217 var value = gl.getUniform(program, locations[ii]); 218 if (!isSame(value[0], ii + 4) || 219 !isSame(value[1], ii + 2) || 220 !isSame(value[2], ii + 3) || 221 !isSame(value[3], ii + 1)) { 222 testFailed("location: " + ii + " was not correct value"); 223 break; 224 } 225 } 226 } 227 } 228 229 function runOneTest(gl, test) { 230 testUniformOptimizationIssues(test); 231 }; 232 233 function runTests(gl, tests) { 234 debug(""); 235 debug("Test drivers don't over optimize unused array elements"); 236 237 for (var ii = 0; ii < tests.length; ++ii) { 238 runOneTest(gl, tests[ii]); 239 } 240 }; 241 242 return { 243 setupTests : setupTests, 244 runTests : runTests 245 }; 246 247 }());