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.
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>
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>
24 description("Checks that ReadPixels works as expected.");
26 var wtu
= WebGLTestUtils
;
29 debug("<h1>antialias = false</h1>")
30 runTest(document
.getElementById("example"), false);
31 debug("<h1>antialias = true</h1>")
32 runTest(document
.getElementById("example2"), true);
38 function runTest(canvas
, antialias
) {
39 gl
= wtu
.create3DContext(canvas
, {antialias
: antialias
});
40 var contextVersion
= wtu
.getDefault3DContextVersion();
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");
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");
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");
81 var continueTestFunc
= continueTestPart1
;
83 gl
.clearColor(1, 1, 1, 1);
84 gl
.clear(gl
.COLOR_BUFFER_BIT
);
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);
91 canvas
.height
= height
;
92 if (gl
.getError() != gl
.CONTEXT_LOST_WEBGL
) {
96 function continueTestAfterContextRestored() {
97 window
.gl
= wtu
.create3DContext(canvas
);
98 var func
= continueTestFunc
;
99 window
.continueTestFunc = function() { testFailed("should not be here"); };
103 function continueTestPart1() {
104 gl
.clearColor(0.2, 0.6, 0.4, 1);
105 gl
.clear(gl
.COLOR_BUFFER_BIT
);
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]
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}
127 for (var tt
= 0; tt
< tests
.length
; ++tt
) {
128 var test
= tests
[tt
];
130 debug("checking: " + test
.msg
);
131 checkBuffer(test
.checkColor
, test
.x
, test
.y
,
132 test
.oneColor
, test
.oneX
, test
.oneY
);
135 wtu
.glErrorShouldBe(gl
, gl
.NO_ERROR
, "there should be no GL errors");
137 function checkBuffer(checkColor
, x
, y
, oneColor
, oneX
, oneY
) {
138 var buf
= new Uint8Array(width
* height
* 4);
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];
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
);
162 "color pixel at " + xx
+ ", " + yy
+ " should be about " + expectedColors
+
163 ", was = " + [buf
[offset
], buf
[offset
+ 1], buf
[offset
+ 2], buf
[offset
+ 3]]);
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
]);
179 let neverValidTypeInfo
= [
180 {type
: desktopGL
.UNSIGNED_INT_24_8
, dest
: new Uint32Array(4)}
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)}
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
));
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
));
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
));
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
));
223 const combinations
= [
226 type
: gl
.UNSIGNED_SHORT_5_6_5
,
227 dest
: new Uint8Array(3),
231 type
: gl
.UNSIGNED_SHORT_5_5_5_1
,
232 dest
: new Uint16Array(1),
236 type
: gl
.UNSIGNED_SHORT_4_4_4_4
,
237 dest
: new Uint16Array(1),
249 format
: gl
.LUMINANCE_ALPHA
,
255 format
: gl
.LUMINANCE
,
259 if (contextVersion
>= 2) {
268 format
: gl
.RGBA_INTEGER
,
271 format
: gl
.RGB_INTEGER
,
274 format
: gl
.RG_INTEGER
,
277 format
: gl
.RED_INTEGER
,
287 type
: gl
.UNSIGNED_BYTE
,
293 type
: gl
.UNSIGNED_SHORT
,
299 type
: gl
.UNSIGNED_INT
,
310 if (contextVersion
>= 2) {
319 const ext
= gl
.getExtension('OES_texture_half_float');
323 type
: ext
.HALF_FLOAT_OES
,
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
);
340 debug("check invalid combinations of format/type");
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
));
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
)) {
359 "Should be able to read as " + wtu
.glEnumToString(gl
, format
) +
360 " / " + wtu
.glEnumToString(gl
, type
));
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
));
370 debug("check reading with lots of drawing");
371 continueTestFunc
= continueTestPart3
;
374 canvas
.width
= width
;
375 canvas
.height
= height
;
376 if (gl
.getError() != gl
.CONTEXT_LOST_WEBGL
) {
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]];
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
]);
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);
401 var buf
= new Uint8Array(4);
402 gl
.readPixels(512, 512, 1, 1, gl
.RGBA
, gl
.UNSIGNED_BYTE
, buf
);
404 for (var kk
= 0; kk
< 99; ++kk
) {
405 gl
.uniform1i(loc
, (jj
+ kk
) % 3);
406 gl
.drawArrays(gl
.TRIANGLES
, 0, 6);
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");
416 wtu
.glErrorShouldBe(gl
, gl
.NO_ERROR
, "there should be no GL errors");
419 debug("check readback into Uint8ClampedArray");
420 continueTestFunc
= continueTestPart4
;
422 canvas
.width
= kSize
;
423 canvas
.height
= kSize
;
424 if (gl
.getError() != gl
.CONTEXT_LOST_WEBGL
) {
429 function continueTestPart4() {
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
);
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");
442 "color pixel at 0, 0 should be [0, 255, 0, 255], was " +
443 [buf
[0], buf
[1], buf
[2], buf
[3]]);
448 `new Uint8Array(new ArrayBuffer(4))`,
449 `new Uint8ClampedArray(4)`,
450 `new Uint8ClampedArray(new ArrayBuffer(4))`,
452 if (window
.SharedArrayBuffer
) {
454 `new Uint8Array(new SharedArrayBuffer(4))`,
455 `new Uint8ClampedArray(new SharedArrayBuffer(4))`
458 for (const x
of validDatas
) {
459 shouldNotThrow(`gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, ${x});`);