read-pixels-test.html (16111B)
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 7 <!DOCTYPE html> 8 <html> 9 <head> 10 <meta charset="utf-8"> 11 <title>WebGL ReadPixels conformance test.</title> 12 <link rel="stylesheet" href="../../resources/js-test-style.css"/> 13 <script src="../../js/desktop-gl-constants.js"></script> 14 <script src="../../js/js-test-pre.js"></script> 15 <script src="../../js/webgl-test-utils.js"> </script> 16 </head> 17 <body> 18 <canvas id="example" width="200" height="200" style="width: 20px; height: 20px"></canvas> 19 <canvas id="example2" width="200" height="200" style="width: 20px; height: 20px"></canvas> 20 <div id="description"></div> 21 <div id="console"></div> 22 <script> 23 "use strict"; 24 description("Checks that ReadPixels works as expected."); 25 26 var wtu = WebGLTestUtils; 27 let gl; 28 29 debug("<h1>antialias = false</h1>") 30 runTest(document.getElementById("example"), false); 31 debug("<h1>antialias = true</h1>") 32 runTest(document.getElementById("example2"), true); 33 finishTest(); 34 35 var actual; 36 var expected; 37 38 function runTest(canvas, antialias) { 39 gl = wtu.create3DContext(canvas, {antialias: antialias}); 40 var contextVersion = wtu.getDefault3DContextVersion(); 41 42 debug(""); 43 debug("Test null pixels"); 44 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, null); 45 wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "null pixels"); 46 47 debug(""); 48 debug("Test pixels size"); 49 gl.readPixels(0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(0)); 50 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "empty pixels array with 0x0 read data"); 51 gl.readPixels(0, 0, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(0)); 52 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "empty pixels array with 1x0 read data"); 53 gl.readPixels(0, 0, 0, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(0)); 54 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "empty pixels array with 0x1 read data"); 55 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(3)); 56 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "small pixels array for 1x1 read data"); 57 if (contextVersion >= 2) { 58 gl.readPixels(0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(0), 1); 59 wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "offset is greater than array size"); 60 gl.readPixels(0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(1), 1); 61 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no space left in pixels array with 0x0 read data"); 62 gl.readPixels(0, 0, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(1), 1); 63 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no space left in pixels array with 1x0 read data"); 64 gl.readPixels(0, 0, 0, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(1), 1); 65 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no space left in pixels array with 0x1 read data"); 66 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4), 1); 67 wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "no space left in pixels array with 1x1 read data"); 68 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(5), 1); 69 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "read 1x1 data fits into pixels with offset"); 70 } 71 72 debug(""); 73 debug("Test combined depth-stencil type"); 74 // The combined type is undefined in WebGL 1.0 and never allowed as a read type in WebGL 2.0 75 gl.readPixels(0, 0, 1, 1, gl.RGBA, 0x8DAD /* FLOAT_32_UNSIGNED_INT_24_8_REV */, new Uint8Array(32)); 76 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "FLOAT_32_UNSIGNED_INT_24_8_REV is rejected"); 77 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no extra error generated"); 78 79 var width = 2; 80 var height = 2; 81 var continueTestFunc = continueTestPart1; 82 83 gl.clearColor(1, 1, 1, 1); 84 gl.clear(gl.COLOR_BUFFER_BIT); 85 86 // Resize the canvas to 2x2. This is an attempt to get stuff in the backbuffer. 87 // that shouldn't be there. 88 canvas.addEventListener("webglcontextlost", function(e) { e.preventDefault(); }, false); 89 canvas.addEventListener("webglcontextrestored", continueTestAfterContextRestored, false); 90 canvas.width = width; 91 canvas.height = height; 92 if (gl.getError() != gl.CONTEXT_LOST_WEBGL) { 93 continueTestPart1(); 94 } 95 96 function continueTestAfterContextRestored() { 97 window.gl = wtu.create3DContext(canvas); 98 var func = continueTestFunc; 99 window.continueTestFunc = function() { testFailed("should not be here"); }; 100 func(); 101 } 102 103 function continueTestPart1() { 104 gl.clearColor(0.2, 0.6, 0.4, 1); 105 gl.clear(gl.COLOR_BUFFER_BIT); 106 107 var innerColor = [51, 153, 102, 255]; // (0.2, 0.6, 0.4, 1) 108 var outerColor = [19, 72, 0, 198]; // Random color other than [0, 0, 0, 0] 109 110 var tests = [ 111 { msg: 'in range', checkColor: innerColor, x: 0, y: 0, 112 oneColor: innerColor, oneX: 0, oneY: 0}, 113 { msg: 'off top left', checkColor: outerColor, x: -1, y: -1, 114 oneColor: innerColor, oneX: 1, oneY: 1}, 115 { msg: 'off bottom right', checkColor: outerColor, x: 1, y: 1, 116 oneColor: innerColor, oneX: 0, oneY: 0}, 117 { msg: 'completely off top ', checkColor: outerColor, x: 0, y: -2, 118 oneColor: outerColor, oneX: 0, oneY: 0}, 119 { msg: 'completely off bottom', checkColor: outerColor, x: 0, y: 2, 120 oneColor: outerColor, oneX: 0, oneY: 0}, 121 { msg: 'completely off left', checkColor: outerColor, x: -2, y: 0, 122 oneColor: outerColor, oneX: 0, oneY: 0}, 123 { msg: 'completeley off right', checkColor: outerColor, x: 2, y: 0, 124 oneColor: outerColor, oneX: 0, oneY: 0} 125 ]; 126 127 for (var tt = 0; tt < tests.length; ++tt) { 128 var test = tests[tt]; 129 debug(""); 130 debug("checking: " + test.msg); 131 checkBuffer(test.checkColor, test.x, test.y, 132 test.oneColor, test.oneX, test.oneY); 133 } 134 135 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no GL errors"); 136 137 function checkBuffer(checkColor, x, y, oneColor, oneX, oneY) { 138 var buf = new Uint8Array(width * height * 4); 139 // Initialize buf. 140 for (var ii = 0; ii < width * height; ++ii) { 141 buf[ii * 4] = outerColor[0]; 142 buf[ii * 4 + 1] = outerColor[1]; 143 buf[ii * 4 + 2] = outerColor[2]; 144 buf[ii * 4 + 3] = outerColor[3]; 145 } 146 gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf); 147 for (var yy = 0; yy < height; ++yy) { 148 for (var xx = 0; xx < width; ++xx) { 149 var offset = (yy * width + xx) * 4; 150 var expectedColors = (oneX == xx && oneY == yy) ? oneColor : checkColor; 151 var mismatch = false; 152 for (var cc = 0; cc < 4; ++cc) { 153 var expectedColor = expectedColors[cc]; 154 var color = buf[offset + cc]; 155 var diff = Math.abs(expectedColor - color); 156 if (diff >= 3) { 157 mismatch = true; 158 break; 159 } 160 } 161 assertMsg(!mismatch, 162 "color pixel at " + xx + ", " + yy + " should be about " + expectedColors + 163 ", was = " + [buf[offset], buf[offset + 1], buf[offset + 2], buf[offset + 3]]); 164 } 165 } 166 } 167 168 continueTestPart2(); 169 } 170 171 function continueTestPart2() { 172 let neverValidFormats = [gl.DEPTH_COMPONENT, gl.DEPTH_STENCIL, desktopGL.R8, gl.RGBA4]; 173 let maybeValidFormats = [gl.LUMINANCE, gl.LUMINANCE_ALPHA]; 174 if (contextVersion < 2) { 175 // They are valid in WebGL 2 or higher 176 maybeValidFormats = maybeValidFormats.concat([desktopGL.RED, desktopGL.RG_INTEGER, desktopGL.RGBA_INTEGER]); 177 } 178 179 let neverValidTypeInfo = [ 180 {type: desktopGL.UNSIGNED_INT_24_8, dest: new Uint32Array(4)} 181 ]; 182 let maybeValidTypeInfo = []; 183 if (contextVersion < 2) { 184 // They are valid in WebGL 2 or Higher 185 maybeValidTypeInfo = maybeValidTypeInfo.concat([ 186 {type: gl.UNSIGNED_SHORT, dest: new Uint16Array(4)}, 187 {type: gl.SHORT, dest: new Int16Array(4)}, 188 {type: gl.BYTE, dest: new Int8Array(4)}, 189 {type: gl.UNSIGNED_INT, dest: new Uint32Array(4)}, 190 {type: desktopGL.UNSIGNED_INT_2_10_10_10_REV, dest: new Uint32Array(4)} 191 ]); 192 } 193 194 debug(""); 195 debug("check non-default format or type"); 196 for (let format of neverValidFormats) { 197 var buf = new Uint8Array(4); 198 gl.readPixels(0, 0, 1, 1, format, gl.UNSIGNED_BYTE, buf); 199 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Should not be able to read as " + wtu.glEnumToString(gl, format)); 200 } 201 for (let format of maybeValidFormats) { 202 var buf = new Uint8Array(4); 203 gl.readPixels(0, 0, 1, 1, format, gl.UNSIGNED_BYTE, buf); 204 wtu.glErrorShouldBe(gl, [gl.INVALID_ENUM, gl.INVALID_OPERATION], "Should not be able to read as " + wtu.glEnumToString(gl, format)); 205 } 206 207 for (let info of neverValidTypeInfo) { 208 var type = info.type; 209 var dest = info.dest; 210 gl.readPixels(0, 0, 1, 1, gl.RGBA, type, dest); 211 wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Should not be able to read as " + wtu.glEnumToString(gl, type)); 212 } 213 for (let info of maybeValidTypeInfo) { 214 var type = info.type; 215 var dest = info.dest; 216 gl.readPixels(0, 0, 1, 1, gl.RGBA, type, dest); 217 wtu.glErrorShouldBe(gl, [gl.INVALID_ENUM, gl.INVALID_OPERATION], "Should not be able to read as " + wtu.glEnumToString(gl, type)); 218 } 219 220 221 // - 222 223 const combinations = [ 224 { 225 format: gl.RGB, 226 type: gl.UNSIGNED_SHORT_5_6_5, 227 dest: new Uint8Array(3), 228 }, 229 { 230 format: gl.RGBA, 231 type: gl.UNSIGNED_SHORT_5_5_5_1, 232 dest: new Uint16Array(1), 233 }, 234 { 235 format: gl.RGBA, 236 type: gl.UNSIGNED_SHORT_4_4_4_4, 237 dest: new Uint16Array(1), 238 }, 239 ]; 240 241 const FORMATS = [ 242 { 243 format: gl.RGBA, 244 channels: 4, 245 }, { 246 format: gl.RGB, 247 channels: 3, 248 }, { 249 format: gl.LUMINANCE_ALPHA, 250 channels: 2, 251 }, { 252 format: gl.ALPHA, 253 channels: 1, 254 }, { 255 format: gl.LUMINANCE, 256 channels: 3, 257 }, 258 ]; 259 if (contextVersion >= 2) { 260 FORMATS.push( 261 { 262 format: gl.RED, 263 channels: 1, 264 }, { 265 format: gl.RG, 266 channels: 1, 267 }, { 268 format: gl.RGBA_INTEGER, 269 channels: 4, 270 }, { 271 format: gl.RGB_INTEGER, 272 channels: 3, 273 }, { 274 format: gl.RG_INTEGER, 275 channels: 2, 276 }, { 277 format: gl.RED_INTEGER, 278 channels: 1, 279 } 280 ); 281 } 282 283 // - 284 285 const TYPES = [ 286 { 287 type: gl.UNSIGNED_BYTE, 288 ctor: Uint8Array, 289 }, { 290 type: gl.BYTE, 291 ctor: Int8Array, 292 }, { 293 type: gl.UNSIGNED_SHORT, 294 ctor: Uint16Array, 295 }, { 296 type: gl.SHORT, 297 ctor: Int16Array, 298 }, { 299 type: gl.UNSIGNED_INT, 300 ctor: Uint32Array, 301 }, { 302 type: gl.INT, 303 ctor: Int32Array, 304 }, { 305 type: gl.FLOAT, 306 ctor: Float32Array, 307 } 308 ]; 309 310 if (contextVersion >= 2) { 311 TYPES.push( 312 { 313 type: gl.HALF_FLOAT, 314 ctor: Uint16Array, 315 } 316 ); 317 } 318 319 const ext = gl.getExtension('OES_texture_half_float'); 320 if (ext) { 321 TYPES.push( 322 { 323 type: ext.HALF_FLOAT_OES, 324 ctor: Uint16Array, 325 } 326 ); 327 } 328 329 for (const t of TYPES) { 330 for (const f of FORMATS) { 331 const desc = Object.assign({}, f, t); 332 desc.dest = new desc.ctor(desc.channels); 333 combinations.push(desc); 334 } 335 } 336 337 // - 338 339 debug(""); 340 debug("check invalid combinations of format/type"); 341 342 var implFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT); 343 var implType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE); 344 debug("IMPLEMENTATION_COLOR_READ_FORMAT: " + wtu.glEnumToString(gl, implFormat)); 345 debug("IMPLEMENTATION_COLOR_READ_TYPE: " + wtu.glEnumToString(gl, implType)); 346 347 for (var tt = 0; tt < combinations.length; ++ tt) { 348 var info = combinations[tt]; 349 var format = info.format; 350 var type = info.type; 351 var dest = info.dest; 352 gl.readPixels(0, 0, 1, 1, format, type, dest); 353 // Only two format/type parameter pairs are accepted. GL_RGBA/GL_UNSIGNED_BYTE is always 354 // accepted on default readbuffer. The other acceptable pair can be discovered by querying 355 // GL_IMPLEMENTATION_COLOR_READ_FORMAT and GL_IMPLEMENTATION_COLOR_READ_TYPE. 356 if ((format == gl.RGBA && type == gl.UNSIGNED_BYTE) || (format == implFormat && type == implType)) { 357 wtu.glErrorShouldBe( 358 gl, gl.NO_ERROR, 359 "Should be able to read as " + wtu.glEnumToString(gl, format) + 360 " / " + wtu.glEnumToString(gl, type)); 361 } else { 362 wtu.glErrorShouldBe( 363 gl, [gl.INVALID_OPERATION, gl.INVALID_ENUM], 364 "Should not be able to read as " + wtu.glEnumToString(gl, format) + 365 " / " + wtu.glEnumToString(gl, type)); 366 } 367 } 368 369 debug(""); 370 debug("check reading with lots of drawing"); 371 continueTestFunc = continueTestPart3; 372 width = 1024; 373 height = 1024; 374 canvas.width = width; 375 canvas.height = height; 376 if (gl.getError() != gl.CONTEXT_LOST_WEBGL) { 377 continueTestPart3(); 378 } 379 } 380 381 function continueTestPart3() { 382 gl.viewport(0, 0, 1024, 1024); 383 var program = wtu.setupTexturedQuad(gl); 384 var loc = gl.getUniformLocation(program, "tex"); 385 gl.disable(gl.BLEND); 386 gl.disable(gl.DEPTH_TEST); 387 var colors = [[255, 0, 0, 255], [0, 255, 0, 255], [0, 0, 255, 255]]; 388 var textures = []; 389 var results = []; 390 for (var ii = 0; ii < colors.length; ++ii) { 391 gl.activeTexture(gl.TEXTURE0 + ii); 392 var tex = gl.createTexture(); 393 wtu.fillTexture(gl, tex, 1, 1, colors[ii]); 394 textures.push(tex); 395 } 396 for (var ii = 0; ii < colors.length; ++ii) { 397 for (var jj = 0; jj < 300 + ii + 1; ++jj) { 398 gl.uniform1i(loc, jj % 3); 399 gl.drawArrays(gl.TRIANGLES, 0, 6); 400 } 401 var buf = new Uint8Array(4); 402 gl.readPixels(512, 512, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf); 403 results.push(buf); 404 for (var kk = 0; kk < 99; ++kk) { 405 gl.uniform1i(loc, (jj + kk) % 3); 406 gl.drawArrays(gl.TRIANGLES, 0, 6); 407 } 408 } 409 for (var ii = 0; ii < colors.length; ++ii) { 410 var buf = results[ii]; 411 var color = colors[ii]; 412 actual = [buf[0], buf[1], buf[2], buf[3]]; 413 expected = [color[0], color[1], color[2], color[3]]; 414 shouldBe("actual", "expected"); 415 } 416 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no GL errors"); 417 418 debug(""); 419 debug("check readback into Uint8ClampedArray"); 420 continueTestFunc = continueTestPart4; 421 const kSize = 32; 422 canvas.width = kSize; 423 canvas.height = kSize; 424 if (gl.getError() != gl.CONTEXT_LOST_WEBGL) { 425 continueTestPart4(); 426 } 427 } 428 429 function continueTestPart4() { 430 const kSize = 32; 431 gl.viewport(0, 0, kSize, kSize); 432 gl.clearColor(0.0, 1.0, 0.0, 1.0); 433 gl.clear(gl.COLOR_BUFFER_BIT); 434 435 var buf = new Uint8ClampedArray(4); 436 gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf); 437 wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no GL errors reading back into a Uint8ClampedArray"); 438 if (buf[0] == 0 && buf[1] == 255 && buf[2] == 0 && buf[3] == 255) { 439 testPassed("Readback into Uint8ClampedArray worked successfully"); 440 } else { 441 assertMsg(false, 442 "color pixel at 0, 0 should be [0, 255, 0, 255], was " + 443 [buf[0], buf[1], buf[2], buf[3]]); 444 } 445 446 const validDatas = [ 447 `new Uint8Array(4)`, 448 `new Uint8Array(new ArrayBuffer(4))`, 449 `new Uint8ClampedArray(4)`, 450 `new Uint8ClampedArray(new ArrayBuffer(4))`, 451 ]; 452 if (window.SharedArrayBuffer) { 453 validDatas.push( 454 `new Uint8Array(new SharedArrayBuffer(4))`, 455 `new Uint8ClampedArray(new SharedArrayBuffer(4))` 456 ); 457 } 458 for (const x of validDatas) { 459 shouldNotThrow(`gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, ${x});`); 460 } 461 } 462 } 463 </script> 464 </body> 465 </html>