summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance2/rendering
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/conformance2/rendering')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/00_test_list.txt51
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/attrib-type-match.html561
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-outofbounds.html172
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-srgb.html161
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-multisampled-readbuffer.html112
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-outside-readbuffer.html267
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-r11f-g11f-b10f.html113
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-resolve-to-back-buffer.html245
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-scissor-enabled.html160
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-size-overflow.html98
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-srgb-and-linear-drawbuffers.html207
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-stencil-only.html170
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-test.html361
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-unaffected-by-colormask.html102
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/builtin-vert-attribs.html408
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/canvas-resizing-with-pbo-bound.html111
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-func-buffer-type-match.html145
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-srgb-color-buffer.html89
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-and-draw.html216
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-sub-source.html110
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbufferfv-with-alpha-false.html80
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clipping-wide-points.html26
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/depth-stencil-feedback-loop.html165
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-dirty-state-bug.html111
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-driver-hang.html187
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-sparse-output-locations.html108
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers.html598
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-with-integer-texture-base-level.html65
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/element-index-uint.html432
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-draw-framebuffer.html74
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-unaffected.html89
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-mismatched-attachment-targets.html162
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer-angle-issue.html90
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer.html440
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-changing-base-level.html107
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-level1.html64
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-to-texture.html201
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-unsupported.html134
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html169
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-arrays.html271
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-bug.html254
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-large-divisor.html145
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/line-rendering-quality.html27
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-depth-resolve.html179
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-fragment-evaluation.html143
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/out-of-bounds-index-buffers-after-copying.html187
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rasterizer-discard-and-implicit-clear.html149
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/read-draw-when-missing-image.html288
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rgb-format-support.html111
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/texture-switch-performance.html101
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/uniform-block-buffer-size.html228
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id-large-count.html127
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id.html219
53 files changed, 9590 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/00_test_list.txt
new file mode 100644
index 0000000000..92ce232ee2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/00_test_list.txt
@@ -0,0 +1,51 @@
+attrib-type-match.html
+blitframebuffer-filter-outofbounds.html
+blitframebuffer-filter-srgb.html
+blitframebuffer-multisampled-readbuffer.html
+--min-version 2.0.1 blitframebuffer-outside-readbuffer.html
+--min-version 2.0.1 blitframebuffer-r11f-g11f-b10f.html
+--min-version 2.0.1 blitframebuffer-resolve-to-back-buffer.html
+blitframebuffer-scissor-enabled.html
+blitframebuffer-size-overflow.html
+--min-version 2.0.1 blitframebuffer-srgb-and-linear-drawbuffers.html
+--min-version 2.0.1 blitframebuffer-stencil-only.html
+blitframebuffer-test.html
+--min-version 2.0.1 blitframebuffer-unaffected-by-colormask.html
+--min-version 2.0.1 builtin-vert-attribs.html
+canvas-resizing-with-pbo-bound.html
+--min-version 2.0.1 clearbuffer-sub-source.html
+--min-version 2.0.1 clearbufferfv-with-alpha-false.html
+clear-func-buffer-type-match.html
+--min-version 2.0.1 clear-srgb-color-buffer.html
+--min-version 2.0.1 clipping-wide-points.html
+--min-version 2.0.1 depth-stencil-feedback-loop.html
+draw-buffers.html
+--min-version 2.0.1 draw-buffers-dirty-state-bug.html
+--min-version 2.0.1 draw-buffers-driver-hang.html
+--min-version 2.0.1 draw-buffers-sparse-output-locations.html
+--min-version 2.0.1 draw-with-integer-texture-base-level.html
+element-index-uint.html
+--min-version 2.0.1 framebuffer-completeness-draw-framebuffer.html
+framebuffer-completeness-unaffected.html
+--min-version 2.0.1 framebuffer-mismatched-attachment-targets.html
+--min-version 2.0.1 framebuffer-render-to-layer.html
+--min-version 2.0.1 framebuffer-render-to-layer-angle-issue.html
+--min-version 2.0.1 framebuffer-texture-changing-base-level.html
+--min-version 2.0.1 framebuffer-texture-level1.html
+--min-version 2.0.1 framebuffer-to-texture.html
+framebuffer-unsupported.html
+--min-version 2.0.1 fs-color-type-mismatch-color-buffer-type.html
+instanced-arrays.html
+--min-version 2.0.1 instanced-rendering-bug.html
+--min-version 2.0.1 instanced-rendering-large-divisor.html
+--min-version 2.0.1 line-rendering-quality.html
+--min-version 2.0.1 multisampling-depth-resolve.html
+--min-version 2.0.1 multisampling-fragment-evaluation.html
+out-of-bounds-index-buffers-after-copying.html
+--min-version 2.0.1 rasterizer-discard-and-implicit-clear.html
+--min-version 2.0.1 read-draw-when-missing-image.html
+rgb-format-support.html
+uniform-block-buffer-size.html
+--min-version 2.0.1 texture-switch-performance.html
+--min-version 2.0.1 vertex-id.html
+--min-version 2.0.1 vertex-id-large-count.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/attrib-type-match.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/attrib-type-match.html
new file mode 100644
index 0000000000..5b1b2884aa
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/attrib-type-match.html
@@ -0,0 +1,561 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Conformance Tests: Vertex Attribute Type Match</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing instanced draws -->
+<script id="outputVertexShader" type="x-shader/x-vertex">#version 300 es
+in vec4 aPosition;
+in ivec2 aOffsetI;
+in uvec2 aOffsetU;
+in vec4 aColor;
+out vec4 vColor;
+void main() {
+ vColor = aColor;
+ vec2 offset = vec2(float(aOffsetI.x) + float(aOffsetU.x),
+ float(aOffsetI.y) + float(aOffsetU.y));
+ gl_Position = aPosition + vec4(offset, 0.0, 0.0);
+}
+</script>
+
+<script id="outputFragmentShader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+in vec4 vColor;
+out vec4 fragColor;
+void main() {
+ fragColor = vColor;
+}
+</script>
+
+<script id='vshader_inactive_attrib' type='x-shader/x-vertex'>#version 300 es
+in ivec4 p;
+in ivec4 a;
+void main()
+{
+ gl_Position = vec4(p);
+}
+</script>
+<script id='vshader_active_attrib_int' type='x-shader/x-vertex'>#version 300 es
+in ivec4 p;
+in ivec4 a;
+in uvec4 b;
+void main()
+{
+ gl_Position = vec4(p) + vec4(a) + vec4(b);
+}
+</script>
+<script id='vshader_active_attrib_float' type='x-shader/x-vertex'>#version 300 es
+in vec4 p;
+in vec4 a;
+in vec4 c;
+void main()
+{
+ gl_Position = vec4(p) + vec4(a) + vec4(c);
+}
+</script>
+<script id='fshader' type='x-shader/x-fragment'>#version 300 es
+precision mediump float;
+layout(location=0) out vec4 oColor;
+void main()
+{
+ oColor = vec4(1.0, 0.0, 0.0, 1.0);
+}
+</script>
+
+
+<script>
+"use strict";
+description("This test verifies an active vertex attribute's base type has to match the verexAttrib function type.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ testGenericAttribs();
+ runTests();
+}
+
+function testGenericAttribs() {
+ debug("");
+ debug("Test Generic Vertex Attributes for some corner cases");
+
+ var pIndex = 2;
+ var aIndex = 3;
+ var bIndex = 4;
+ var cIndex = 5;
+ var program0 = wtu.setupProgram(gl, ["vshader_inactive_attrib", "fshader"],
+ ['p', 'a'], [pIndex, aIndex]);
+ var program1 = wtu.setupProgram(gl, ["vshader_active_attrib_int", "fshader"],
+ ['p', 'a', 'b'], [pIndex, aIndex, bIndex]);
+ var program2 = wtu.setupProgram(gl, ["vshader_active_attrib_float", "fshader"],
+ ['p', 'a', 'c'], [pIndex, aIndex, cIndex]);
+ if (!program0 || !program1 || !program2) {
+ testFailed("Set up program failed");
+ return;
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No GL error from set up");
+
+ wtu.setupUnitQuad(gl, 0);
+
+ debug("Inactive input in vertex shader");
+ gl.useProgram(program0);
+ gl.vertexAttribI4i(pIndex, 1, 0, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds: type in shader mismatch default vertex type is valid for inactive attrib");
+
+ gl.vertexAttrib4f(aIndex, 0.0, 1.0, 0.0, 0.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds: type in shader mismatch vertexAttrib type is valid for inactive attrib");
+
+ debug("active int/uint inputs in vertex shader");
+ gl.useProgram(program1);
+ gl.vertexAttribI4i(pIndex, 1, 0, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Type mismatch: type in shader mismatch the default type for a vertex attrib");
+ gl.vertexAttribI4i(aIndex, 0, 1, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Type mismatch: type in shader mismatch the default type for a vertex attrib");
+ gl.vertexAttribI4ui(bIndex, 0, 0, 1, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds");
+
+ debug("active float input in vertex shader");
+ gl.useProgram(program2);
+ gl.vertexAttrib4f(pIndex, 1.0, 0.0, 0.0, 0.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Type mismatch: generic attrib is valid per context. 'a' is set to int type by previous test case");
+ gl.vertexAttrib4f(aIndex, 0.0, 1.0, 0.0, 0.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds: default type of generic attrib is float");
+}
+
+function setupAttribValues(offsetILoc, offsetULoc, colorLoc) {
+ gl.vertexAttribI4i(offsetILoc, -1, -2, 0, 0);
+ gl.vertexAttribI4ui(offsetULoc, 1, 2, 0, 0);
+ gl.vertexAttrib4f(colorLoc, 1.0, 0, 0, 1.0);
+}
+
+function setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetIBuffer);
+ gl.vertexAttribIPointer(offsetILoc, 2, gl.INT, 0, 0);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetUBuffer);
+ gl.vertexAttribIPointer(offsetULoc, 2, gl.UNSIGNED_INT, 0, 0);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+ gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
+}
+
+function runTests() {
+ debug("");
+ debug("Test vertexAttrib with drawArrays and drawArraysInstanced");
+
+ var instanceCount = 4;
+
+ var positionLoc = 0;
+ var offsetILoc = 2;
+ var offsetULoc = 3;
+ var colorLoc = 4;
+ var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"],
+ ['aPosition', 'aOffsetI', 'aOffsetU','aColor'],
+ [positionLoc, offsetILoc, offsetULoc, colorLoc]);
+ if (!program) {
+ testFailed("Set up program failed");
+ return;
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No GL error from set up");
+
+ wtu.setupUnitQuad(gl, 0);
+
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+
+ debug("int type function on uint type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4i(offsetULoc, 1, 2, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("float type function on uint type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttrib4f(offsetULoc, 1.0, 2.0, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("uint type function on int type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4ui(offsetILoc, 1, 2, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("float type function on int type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttrib4f(offsetILoc, 1.0, 2.0, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("int type function on float type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4i(colorLoc, 1, 0, 0, 1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("uint type function on float type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4ui(colorLoc, 1, 0, 0, 1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("");
+ debug("Test vertexAttrib with drawElements, drawRangeElements, and drawElementsInstanced");
+ wtu.setupIndexedQuad(gl, 1, 0);
+
+ debug("Correct setup");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElements succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawRangeElements succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [255, 0, 0, 255]);
+
+ debug("int type function on uint type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4i(offsetULoc, 1, 2, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("float type function on uint type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttrib4f(offsetULoc, 1.0, 2.0, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("uint type function on int type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4ui(offsetILoc, 1, 2, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("float type function on int type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttrib4f(offsetILoc, 1.0, 2.0, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("int type function on float type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4i(colorLoc, 1, 0, 0, 1);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("uint type function on float type attrib");
+ setupAttribValues(offsetILoc, offsetULoc, colorLoc);
+ gl.vertexAttribI4ui(colorLoc, 1, 0, 0, 1);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+
+ var offsetIBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetIBuffer);
+ var offsetI = new Int32Array([-1, -2,
+ -1, -2,
+ -1, -2,
+ -1, -2,
+ -1, -2,
+ -1, -2]);
+ gl.bufferData(gl.ARRAY_BUFFER, offsetI, gl.STATIC_DRAW);
+
+ var offsetUBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetUBuffer);
+ var offsetU = new Uint32Array([1, 2,
+ 1, 2,
+ 1, 2,
+ 1, 2,
+ 1, 2,
+ 1, 2]);
+ gl.bufferData(gl.ARRAY_BUFFER, offsetU, gl.STATIC_DRAW);
+
+ var offsetFBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetFBuffer);
+ var offsetF = new Float32Array([1.0, 2.0,
+ 1.0, 2.0,
+ 1.0, 2.0,
+ 1.0, 2.0,
+ 1.0, 2.0,
+ 1.0, 2.0]);
+ gl.bufferData(gl.ARRAY_BUFFER, offsetF, gl.STATIC_DRAW);
+
+ var colorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+ var colors = new Float32Array([0.0, 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0]);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+
+ var colorUBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorUBuffer);
+ var colorsU = new Uint32Array([0, 1, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 0, 1,
+ 0, 1, 0, 1]);
+ gl.bufferData(gl.ARRAY_BUFFER, colorsU, gl.STATIC_DRAW);
+
+ gl.enableVertexAttribArray(offsetILoc);
+ gl.enableVertexAttribArray(offsetULoc);
+ gl.enableVertexAttribArray(colorLoc);
+
+ debug("");
+ debug("Test vertexAttrib{I}Pointer with drawArrays and drawArraysInstanced");
+ wtu.setupUnitQuad(gl, 0);
+
+ debug("Correct setup");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [0, 255, 0, 255]);
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [0, 255, 0, 255]);
+
+ debug("vertexAttribIPointer with int type on uint type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetIBuffer);
+ gl.vertexAttribIPointer(offsetULoc, 2, gl.INT, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribPointer on uint type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetFBuffer);
+ gl.vertexAttribPointer(offsetULoc, 2, gl.FLOAT, false, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("VertexAttribIPointer with uint type on int type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetUBuffer);
+ gl.vertexAttribIPointer(offsetILoc, 2, gl.UNSIGNED_INT, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribPointer on int type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetFBuffer);
+ gl.vertexAttribPointer(offsetILoc, 2, gl.FLOAT, false, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribIPointer with uint type on float type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorUBuffer);
+ gl.vertexAttribIPointer(colorLoc, 4, gl.UNSIGNED_INT, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribIPointer with int type on float type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorUBuffer);
+ gl.vertexAttribIPointer(colorLoc, 4, gl.INT, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("");
+ debug("Test vertexAttrib{I}Pointer with drawElements, drawRangeElements, and drawElementsInstanced");
+ wtu.setupIndexedQuad(gl, 1, 0);
+
+ debug("Correct setup");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up succeeds");
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElements succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [0, 255, 0, 255]);
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawRangeElements succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [0, 255, 0, 255]);
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced succeeds");
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width, canvas.height, [0, 255, 0, 255]);
+
+ debug("vertexAttribIPointer with int type on uint type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetIBuffer);
+ gl.vertexAttribIPointer(offsetULoc, 2, gl.INT, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribPointer on uint type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetFBuffer);
+ gl.vertexAttribPointer(offsetULoc, 2, gl.FLOAT, false, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("VertexAttribIPointer with uint type on int type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetUBuffer);
+ gl.vertexAttribIPointer(offsetILoc, 2, gl.UNSIGNED_INT, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribPointer on int type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetFBuffer);
+ gl.vertexAttribPointer(offsetILoc, 2, gl.FLOAT, false, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribIPointer with uint type on float type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorUBuffer);
+ gl.vertexAttribIPointer(colorLoc, 4, gl.UNSIGNED_INT, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+
+ debug("vertexAttribIPointer with int type on float type attrib");
+ setupAttribPointers(offsetILoc, offsetULoc, colorLoc,
+ offsetIBuffer, offsetUBuffer, colorBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorUBuffer);
+ gl.vertexAttribIPointer(colorLoc, 4, gl.INT, 0, 0);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "type mismatch");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-outofbounds.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-outofbounds.html
new file mode 100644
index 0000000000..f1fda162d5
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-outofbounds.html
@@ -0,0 +1,172 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer when src/dst region are out-of-bounds.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+function checkPixel(color, expectedColor) {
+ var tolerance = 3;
+ return (Math.abs(color[0] - expectedColor[0]) <= tolerance &&
+ Math.abs(color[1] - expectedColor[1]) <= tolerance &&
+ Math.abs(color[2] - expectedColor[2]) <= tolerance &&
+ Math.abs(color[3] - expectedColor[3]) <= tolerance);
+}
+
+function blitframebuffer_filter_outofbounds(readbufferFormat, drawbufferFormat, filter) {
+ debug("");
+ debug("blitting pixels out-of-bounds, read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat) + ", draw buffer format is: " + wtu.glEnumToString(gl, drawbufferFormat) + ", filter is: " + wtu.glEnumToString(gl, filter));
+
+ // Initiate data to read framebuffer
+ var size = 8;
+ var uint_read = new Uint8Array(size * size * 4);
+ var color = 0x20;
+ for (var ii = 0; ii < size * size * 4; ii += 4) {
+ for (var jj = 0; jj < 3; ++jj) {
+ uint_read[ii + jj] = color;
+ }
+ uint_read[ii + 3] = 0xff;
+ }
+
+ // Create read framebuffer and feed data to read buffer
+ // Read buffer may have srgb image
+ var tex_read = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ gl.texImage2D(gl.TEXTURE_2D, 0, readbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint_read);
+
+ var fbo_read = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+
+ // Create draw framebuffer. Color in draw buffer is initialized to 0.
+ // Draw buffer may have srgb image
+ var tex_draw = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ var fbo_draw = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_draw, 0);
+
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+ // Blit read framebuffer to the image in draw framebuffer.
+ var tests = [
+ // [srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1]
+
+ { args: [-2, -2, 4, 4, 1, 1, 4, 4], changedDstRect: [2, 2, 4, 4], desc: 'only src region is out-of-bounds, dst region has different width/height as src region.'},
+ { args: [-2, -2, 4, 4, 1, 1, 7, 7], changedDstRect: [3, 3, 7, 7], desc: 'only src region is out-of-bounds, dst region has the same width/height as src region.'},
+ { args: [0, 0, 6, 6, 7, 7, 10, 10], changedDstRect: [7, 7, 8, 8], desc: 'only dst region is out-of-bounds, dst region has different width/height as src region after dst region is clipped to the bounds of draw buffer.'},
+ { args: [0, 0, 6, 6, 4, 4, 10, 10], changedDstRect: [4, 4, 8, 8], desc: 'only dst region is out-of-bounds, dst region has the same width/height as src region after dst region is clipped to the bounds of draw buffer.'},
+ { args: [-2, -2, 4, 4, 7, 7, 10, 10], changedDstRect: [8, 8, 8, 8], desc: 'both src and dst region are out-of-bounds, dst region has different width/height as src region after dst region is clipped to the bounds of draw buffer.'},
+ { args: [-2, -2, 4, 4, 4, 4, 10, 10], changedDstRect: [6, 6, 8, 8], desc: 'both src and dst region are out-of-bounds, dst region has the same width/height as src region after dst region is clipped to the bounds of draw buffer.'},
+ { args: [-2, -2, 4, 4, 2, 2, 10, 10], changedDstRect: [2 + 2/6*8, 2 + 2/6*8, 8, 8], desc: 'both src and dst region are out-of-bounds. There are some dst pixels (x and y are within [4, 8] , and x or y equals to 4) whose corresponding src pixels are partially inside and partially outside the real sampling area of the src region (the real sampling area is [0, 0, 4, 4]). But the centers of such src pixels are lying outside the real sampling area.'},
+ { args: [-2, -2, 4, 4, 3, 3, 10, 10], changedDstRect: [5, 5, 8, 8], desc: 'both src and dst region are out-of-bounds. There are some dst pixels (x and y are within [4, 7] , and x or y equals to 5) whose corresponding src pixels are partially inside and partially outside the real sampling area of the src region (the real sampling area is [0, 0, 4, 4]). But the centers of such src pixels are lying inside the real sampling area.'},
+ { args: [-2, -2, 4, 4, 10, 10, 2, 2], changedDstRect: [2, 2, 7, 7], desc: 'both src and dst region are out-of-bounds, and the dst coordinates are reversed. There are some dst pixels (x and y are within [2, 7] , and x or y equals to 7) whose corresponding src pixels are partially inside and partially outside the real sampling area of the src region (the real sampling area is [0, 0, 4, 4]). But the centers of such src pixels are lying outside the real sampling area.'},
+ { args: [-2, -2, 4, 4, 10, 10, 3, 3], changedDstRect: [3, 3, 8, 8], desc: 'both src and dst region are out-of-bounds, and the dst coordinates are reversed. There are some dst pixels (x and y are within [3, 7] , and x or y equals to 7) whose corresponding src pixels are partially inside and partially outside the read sampling area of the src region (the real sampling area is [0, 0, 4, 4]). But the centers of such src pixels are lying inside the real sampling area.'},
+ ];
+
+ var readbufferHasSRGBImage = (readbufferFormat == gl.SRGB8_ALPHA8);
+ var drawbufferHasSRGBImage = (drawbufferFormat == gl.SRGB8_ALPHA8);
+
+ for (const test of tests) {
+ const args = test.args;
+ const changedDstRect = test.changedDstRect;
+ debug("");
+ debug("Both the read framebuffer and draw framebuffer bounds are [0, 0, 8, 8]");
+ debug(`Blitting from src region [${args.slice(0, 4)}] to dst region [${args.slice(4, 8)}]`);
+ debug(`Expects changed dst region of [${changedDstRect}]`);
+ debug(`Explaination: ${test.desc}`);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.blitFramebuffer(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], gl.COLOR_BUFFER_BIT, filter);
+
+ // Read pixels and check the correctness.
+ var pixels = new Uint8Array(size * size * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+
+
+ for (var ii = 0; ii < size; ++ii) {
+ for (var jj = 0; jj < size; ++jj) {
+ var loc = ii * size + jj;
+ var color = [pixels[loc * 4], pixels[loc * 4 + 1], pixels[loc * 4 + 2], pixels[loc * 4 + 3]];
+
+ var expectedColor = [0, 0, 0, 0];
+ if (ii >= changedDstRect[0] && ii < changedDstRect[2] && jj >= changedDstRect[1] && jj < changedDstRect[3]) {
+ expectedColor = [0x20, 0x20, 0x20, 0xff];
+
+ // We may need to covert the color space for pixels in blit region
+ if (readbufferHasSRGBImage ^ drawbufferHasSRGBImage) {
+ if (drawbufferHasSRGBImage) {
+ expectedColor = wtu.linearToSRGB(expectedColor);
+ } else {
+ expectedColor = wtu.sRGBToLinear(expectedColor);
+ }
+ }
+ }
+
+ if (checkPixel(color, expectedColor) == true) {
+ testPassed("pixel at [" + jj + ", " + ii + "] is (" + color + "). It is correct!");
+ } else {
+ testFailed("pixel at [" + jj + ", " + ii + "] should be (" + expectedColor + "), but the actual color is (" + color + ")");
+ }
+ }
+ }
+ }
+ } else {
+ testFailed("framebuffer not complete");
+ }
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fbo_read);
+ gl.deleteFramebuffer(fbo_draw);
+ gl.deleteTexture(tex_read);
+ gl.deleteTexture(tex_draw);
+};
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ var filters = [gl.LINEAR, gl.NEAREST];
+ for (var ii = 0; ii < filters.length; ++ii) {
+ blitframebuffer_filter_outofbounds(gl.RGBA8, gl.RGBA8, filters[ii]);
+ break;
+ blitframebuffer_filter_outofbounds(gl.RGBA8, gl.SRGB8_ALPHA8, filters[ii]);
+ blitframebuffer_filter_outofbounds(gl.SRGB8_ALPHA8, gl.RGBA8, filters[ii]);
+ blitframebuffer_filter_outofbounds(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8, filters[ii]);
+ }
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-srgb.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-srgb.html
new file mode 100644
index 0000000000..4054a0af25
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-filter-srgb.html
@@ -0,0 +1,161 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer with sRGB framebuffers.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+function checkPixel(color, expectedColor) {
+ var tolerance = 7;
+ return (Math.abs(color[0] - expectedColor[0]) <= tolerance &&
+ Math.abs(color[1] - expectedColor[1]) <= tolerance &&
+ Math.abs(color[2] - expectedColor[2]) <= tolerance &&
+ Math.abs(color[3] - expectedColor[3]) <= tolerance);
+}
+
+var tex_read = gl.createTexture();
+var tex_draw = gl.createTexture();
+var fbo_read = gl.createFramebuffer();
+var fbo_draw = gl.createFramebuffer();
+var size_read = 4;
+var size_draw = 0;
+
+function blitframebuffer_helper(readbufferFormat, drawbufferFormat, filter, data) {
+ // Create read framebuffer and feed data to read buffer
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ gl.texImage2D(gl.TEXTURE_2D, 0, readbufferFormat, size_read, size_read, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+
+ // Create draw framebuffer and feed 0 to draw buffer
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size_draw, size_draw, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_draw, 0);
+
+ gl.blitFramebuffer(0, 0, size_read, size_read, 0, 0, size_draw, size_draw, gl.COLOR_BUFFER_BIT, filter);
+
+ // Read pixels for comparision
+ var pixels = new Uint8Array(size_draw * size_draw * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ gl.readPixels(0, 0, size_draw, size_draw, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ return pixels;
+}
+
+function blitframebuffer_filter_srgb(readbufferFormat, drawbufferFormat, filter, minified) {
+ debug("");
+ debug("Test srgb filtering for blitFramebuffer, the current filter is: " + wtu.glEnumToString(gl, filter));
+ var min_mag = minified ? "minified to half the size." : "magnified to double the size.";
+ debug("read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat) + ", draw buffer format is: " + wtu.glEnumToString(gl, drawbufferFormat) + ", minify/magnify: " + min_mag);
+
+ // Initiate data to read framebuffer
+ var src_buffer = new Uint8Array(size_read * size_read * 4);
+ var start = 0;
+ for (var ii = 0; ii < size_read * size_read * 4; ii += 4) {
+ for (var jj = 0; jj < 3; ++jj) {
+ src_buffer[ii + jj] = start;
+ }
+ src_buffer[ii + 3] = 0xff;
+ start += 0x10;
+ }
+
+ // We may need to decode srgb to linear for reference data
+ var ref_buffer = new Uint8Array(size_read * size_read * 4);
+ for (var ii = 0; ii < size_read * size_read * 4; ii += 4) {
+ var color = [src_buffer[ii], src_buffer[ii + 1], src_buffer[ii + 2], src_buffer[ii + 3]];
+ var ref_color;
+ if (readbufferFormat == gl.SRGB8_ALPHA8) {
+ ref_color = wtu.sRGBToLinear(color);
+ } else {
+ ref_color = color;
+ }
+ for (var jj = 0; jj < 4; ++jj) {
+ ref_buffer[ii + jj] = ref_color[jj];
+ }
+ }
+
+ // Blit framebuffer to filter srgb image, but the reference data is always retrieved by blitFramebuffer against linear image
+ size_draw = minified ? size_read / 2 : size_read * 2;
+ var pixels = blitframebuffer_helper(readbufferFormat, drawbufferFormat, filter, src_buffer);
+ var temp = blitframebuffer_helper(gl.RGBA, gl.RGBA, filter, ref_buffer);
+
+ // We may need to encode linear to srgb for reference data
+ var ref_pixels = new Uint8Array(size_draw * size_draw * 4);
+ for (var ii = 0; ii < size_draw * size_draw * 4; ii += 4) {
+ var color = [temp[ii], temp[ii + 1], temp[ii + 2], temp[ii + 3]];
+ var ref_color;
+ if (drawbufferFormat == gl.SRGB8_ALPHA8) {
+ ref_color = wtu.linearToSRGB(color);
+ } else {
+ ref_color = color;
+ }
+ for (var jj = 0; jj < 4; ++jj) {
+ ref_pixels[ii + jj] = ref_color[jj];
+ }
+ }
+
+ // Compare
+ for (var ii = 0; ii < size_draw; ++ii) {
+ for (var jj = 0; jj < size_draw; ++jj) {
+ var index = ii * size_draw * 4 + jj;
+ var color = [pixels[index], pixels[index + 1], pixels[index + 2], pixels[index + 3]];
+ var expectedColor = [ref_pixels[index], ref_pixels[index + 1], ref_pixels[index + 2], ref_pixels[index + 3]];
+ if (checkPixel(color, expectedColor) == true) {
+ testPassed("pixel at [" + jj + ", " + ii + "] is (" + color + "). It is correct!");
+ } else {
+ testFailed("pixel at [" + jj + ", " + ii + "] should be (" + expectedColor + "), but the actual color is (" + color + ")");
+ }
+ }
+ }
+}
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ var filters = [gl.LINEAR, gl.NEAREST];
+ for (var ii = 0; ii < filters.length; ++ii) {
+ blitframebuffer_filter_srgb(gl.RGBA8, gl.SRGB8_ALPHA8, filters[ii], true);
+ blitframebuffer_filter_srgb(gl.RGBA8, gl.SRGB8_ALPHA8, filters[ii], false);
+ blitframebuffer_filter_srgb(gl.SRGB8_ALPHA8, gl.RGBA8, filters[ii], true);
+ blitframebuffer_filter_srgb(gl.SRGB8_ALPHA8, gl.RGBA8, filters[ii], false);
+ blitframebuffer_filter_srgb(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8, filters[ii], true);
+ blitframebuffer_filter_srgb(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8, filters[ii], false);
+ }
+}
+
+gl.bindTexture(gl.TEXTURE_2D, null);
+gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+gl.deleteFramebuffer(fbo_read);
+gl.deleteFramebuffer(fbo_draw);
+gl.deleteTexture(tex_read);
+gl.deleteTexture(tex_draw);
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-multisampled-readbuffer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-multisampled-readbuffer.html
new file mode 100644
index 0000000000..73f8e8b735
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-multisampled-readbuffer.html
@@ -0,0 +1,112 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer with multisampled sRGB color buffer.");
+
+var gl = wtu.create3DContext("canvas", undefined, 2);
+
+var tex_blit = gl.createTexture();
+var fb0 = gl.createFramebuffer();
+var rb0 = gl.createRenderbuffer();
+var fbo_blit = gl.createFramebuffer();
+var size = 32;
+var program;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ init();
+
+ var filters = [gl.LINEAR, gl.NEAREST];
+ for (var ii = 0; ii < filters.length; ++ii) {
+ blitframebuffer_multisampled_readbuffer(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8, filters[ii]);
+ }
+}
+
+function init() {
+ program = wtu.setupColorQuad(gl);
+ gl.viewport(0, 0, size, size);
+}
+
+function blitframebuffer_helper(readbufferFormat, drawbufferFormat, filter) {
+ // Create draw framebuffer and feed 0 to draw buffer
+ gl.bindTexture(gl.TEXTURE_2D, tex_blit);
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_blit);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_blit, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup draw framebuffer should succeed");
+
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, filter);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitframebuffer should succeed");
+}
+
+function blitframebuffer_multisampled_readbuffer(readbufferFormat, drawbufferFormat, filter) {
+ debug("");
+ debug("Test blitFramebuffer when the read buffer is a multisampled srgb image. The filter is: " + wtu.glEnumToString(gl, filter));
+ debug("read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat) + ", draw buffer format is: " + wtu.glEnumToString(gl, drawbufferFormat));
+
+ // Draw to a multi-sampled srgb image, and blit to a srgb image.
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb0);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, readbufferFormat, size, size);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb0);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ var color = [252, 122, 15, 255];
+ var expectedColor = wtu.linearToSRGB(color);
+ for (var i = 0; i < 4; ++i) {
+ color[i] = color[i] / 255;
+ }
+ // Draw a rectangle. Fill it with solid color.
+ // Note that the draw buffer is a multisampled srgb image. So during drawing, the color will be converted into srgb color space.
+ gl.useProgram(program);
+ wtu.drawFloatColorQuad(gl, color);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ blitframebuffer_helper(readbufferFormat, drawbufferFormat, filter);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Blit from a multi-sampled srgb image to a srgb image should succeed");
+
+ // Compare
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_blit);
+ wtu.checkCanvasRect(gl, 0, 0, size, size, expectedColor);
+}
+
+gl.bindTexture(gl.TEXTURE_2D, null);
+gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+gl.deleteRenderbuffer(rb0);
+gl.deleteTexture(tex_blit);
+gl.deleteFramebuffer(fb0);
+gl.deleteFramebuffer(fbo_blit);
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-outside-readbuffer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-outside-readbuffer.html
new file mode 100644
index 0000000000..a2e87034eb
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-outside-readbuffer.html
@@ -0,0 +1,267 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+function checkPixel(color, expectedColor) {
+ var tolerance = 3;
+ return (Math.abs(color[0] - expectedColor[0]) <= tolerance &&
+ Math.abs(color[1] - expectedColor[1]) <= tolerance &&
+ Math.abs(color[2] - expectedColor[2]) <= tolerance &&
+ Math.abs(color[3] - expectedColor[3]) <= tolerance);
+}
+
+function blitframebuffer_outside_readbuffer(readbufferFormat, drawbufferFormat) {
+ debug("");
+ debug("blitting outside of read framebuffer, read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat) + ", draw buffer format is: " + wtu.glEnumToString(gl, drawbufferFormat));
+
+ // Initiate data to read framebuffer
+ var size_read = 3;
+ var uint_read = new Uint8Array(size_read * size_read * 4);
+ var start = 0x20;
+ for (var ii = 0; ii < size_read * size_read * 4; ii += 4) {
+ for (var jj = 0; jj < 3; ++jj) {
+ uint_read[ii + jj] = start;
+ }
+ uint_read[ii + 3] = 0xff;
+ start += 0x10;
+ }
+
+ // Create read framebuffer and feed data to read buffer
+ // Read buffer may has srgb image
+ var tex_read = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ gl.texImage2D(gl.TEXTURE_2D, 0, readbufferFormat, size_read, size_read, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint_read);
+
+ var fbo_read = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+
+ // Initiate data to draw framebuffer
+ var size_draw = 7;
+ var uint_draw = new Uint8Array(size_draw * size_draw * 4);
+ for (var ii = 0; ii < size_draw * size_draw * 4; ii += 4) {
+ for (var jj = 0; jj < 3; ++jj) {
+ uint_draw[ii + jj] = 0x10;
+ }
+ uint_draw[ii + 3] = 0xff;
+ }
+
+ // Create draw framebuffer and feed data to draw buffer
+ // Draw buffer may has srgb image
+ var tex_draw = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size_draw, size_draw, 0, gl.RGBA, gl.UNSIGNED_BYTE, uint_draw);
+
+ var fbo_draw = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_draw, 0);
+
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+ var ref = [
+ // The reference pixels of the 1st line: (0, 0) ~ (6, 0)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 2nd line: (0, 1) ~ (6, 1)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 3rd line: (0, 2) ~ (6, 2)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x20, 0x20, 0x20, 0xff], [0x30, 0x30, 0x30, 0xff],
+ [0x40, 0x40, 0x40, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 4th line: (0, 3) ~ (6, 3)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x50, 0x50, 0x50, 0xff], [0x60, 0x60, 0x60, 0xff],
+ [0x70, 0x70, 0x70, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 5th line: (0, 4) ~ (6, 4)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x80, 0x80, 0x80, 0xff], [0x90, 0x90, 0x90, 0xff],
+ [0xa0, 0xa0, 0xa0, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 6th line: (0, 5) ~ (6, 5)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+
+ // The reference pixels of the 7th line: (0, 6) ~ (6, 6)
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+ [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff], [0x10, 0x10, 0x10, 0xff],
+ ];
+
+ // The 1st round test: blit read framebuffer to the image in draw framebuffer
+ // All directions of the read region have pixels outside of the read buffer
+ // The src region and/or dst region may be reversed during blitting.
+ var test1 = [
+ [-1, 4, 1, 6], // reverse neither src nor dst
+ [4, -1, 1, 6], // reverse src only
+ [-1, 4, 6, 1], // reverse dst only
+ [4, -1, 6, 1] // reverse both src and dst
+ ];
+
+ var readbufferHasSRGBImage = (readbufferFormat == gl.SRGB8_ALPHA8);
+ var drawbufferHasSRGBImage = (drawbufferFormat == gl.SRGB8_ALPHA8);
+
+ for (var i = 0; i < 4; ++i) {
+ debug("");
+ switch (i) {
+ case 0: debug("reverse neither src region nor dst region"); break;
+ case 1: debug("reverse src region only"); break;
+ case 2: debug("reverse dst region only"); break;
+ case 3: debug("reverse both src region and dst region"); break;
+ }
+ var srcStart = test1[i][0];
+ var srcEnd = test1[i][1];
+ var dstStart = test1[i][2];
+ var dstEnd = test1[i][3];
+ var realBlittedDstStart = 2;
+ var realBlittedDstEnd = 5;
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.blitFramebuffer(srcStart, srcStart, srcEnd, srcEnd, dstStart, dstStart, dstEnd, dstEnd, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+
+ // Read pixels and check the correctness.
+ var pixels = new Uint8Array(size_draw * size_draw * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ gl.readPixels(0, 0, size_draw, size_draw, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+
+ for (var ii = 0; ii < size_draw; ++ii) {
+ for (var jj = 0; jj < size_draw; ++jj) {
+ var loc = ii * size_draw + jj;
+ var color = [pixels[loc * 4], pixels[loc * 4 + 1], pixels[loc * 4 + 2], pixels[loc * 4 + 3]];
+
+ // We may need to reverse the reference loc if necessary
+ var ref_loc = loc;
+ var reverse_src = (srcStart < srcEnd);
+ var reverse_dst = (dstStart < dstEnd);
+ var reversed = reverse_src ^ reverse_dst;
+ if (reversed) {
+ ref_loc = (size_draw - ii - 1) * size_draw + (size_draw - jj -1);
+ }
+ var expectedColor = ref[ref_loc];
+
+ // We may need to covert the color space for pixels in blit region
+ if ((readbufferHasSRGBImage ^ drawbufferHasSRGBImage) &&
+ (ii >= realBlittedDstStart && ii < realBlittedDstEnd && jj >= realBlittedDstStart && jj < realBlittedDstEnd)) {
+ if (drawbufferHasSRGBImage) {
+ expectedColor = wtu.linearToSRGB(expectedColor);
+ } else {
+ expectedColor = wtu.sRGBToLinear(expectedColor);
+ }
+ }
+ if (checkPixel(color, expectedColor) == true) {
+ testPassed("pixel at [" + jj + ", " + ii + "] is (" + color + "). It is correct!");
+ } else {
+ testFailed("pixel at [" + jj + ", " + ii + "] should be (" + expectedColor + "), but the actual color is (" + color + ")");
+ }
+ }
+ }
+ }
+
+ // The 2nd round test: blit read framebuffer to the image in draw framebuffer
+ // Only one direction of the read region have pixels outside of the read buffer
+ var tests = [
+ [-1, 0], // pixels are outside the left edge of the read buffer
+ [0, -1], // pixels are outside the bottom edge of the read buffer
+ [1, 0], // pixels are outside the right edge of the read buffer
+ [0, 1] // pixels are outside the top edge of the read buffer
+ ];
+ for (var i = 0; i < 4; ++i) {
+ debug("");
+ switch (i) {
+ case 0: debug("verify that pixels lying outside the left edge of the read buffer should remain untouched"); break;
+ case 1: debug("verify that pixels lying outside the bottom edge of the read buffer should remain untouched"); break;
+ case 2: debug("verify that pixels lying outside the right edge of the read buffer should remain untouched"); break;
+ case 3: debug("verify that pixels lying outside the top edge of the read buffer should remain untouched"); break;
+ }
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ var srcX = tests[i][0];
+ var srcY = tests[i][1];
+ var offset = dstStart - srcStart;
+ gl.blitFramebuffer(srcX, srcY, srcX + size_read, srcY + size_read,
+ srcX + offset, srcY + offset, srcX + offset + size_read, srcY + offset + size_read,
+ gl.COLOR_BUFFER_BIT, gl.LINEAR);
+
+ // Read pixels and check the correctness.
+ var pixels = new Uint8Array(size_draw * size_draw * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ gl.readPixels(0, 0, size_draw, size_draw, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ for (var ii = srcY + offset; ii < srcY + offset + size_read; ++ii) {
+ for (var jj = srcX + offset; jj < srcX + offset + size_read; ++jj) {
+ var loc = ii * size_draw + jj;
+ var color = [pixels[loc * 4], pixels[loc * 4 + 1], pixels[loc * 4 + 2], pixels[loc * 4 + 3]];
+ var expectedColor = ref[loc];
+ // We may need to covert the color space for pixels in blit region
+ if ((readbufferHasSRGBImage ^ drawbufferHasSRGBImage) &&
+ (ii >= realBlittedDstStart && ii < realBlittedDstEnd && jj >= realBlittedDstStart && jj < realBlittedDstEnd)) {
+ if (drawbufferHasSRGBImage) {
+ expectedColor = wtu.linearToSRGB(expectedColor);
+ } else {
+ expectedColor = wtu.sRGBToLinear(expectedColor);
+ }
+ }
+ if (checkPixel(color, expectedColor) == true) {
+ testPassed("pixel at [" + jj + ", " + ii + "] is (" + color + "). It is correct!");
+ } else {
+ testFailed("pixel at [" + jj + ", " + ii + "] should be (" + expectedColor + "), but the actual color is (" + color + ")");
+ }
+ }
+ }
+ }
+ } else {
+ testFailed("framebuffer not complete");
+ }
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fbo_read);
+ gl.deleteFramebuffer(fbo_draw);
+ gl.deleteTexture(tex_read);
+ gl.deleteTexture(tex_draw);
+};
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ blitframebuffer_outside_readbuffer(gl.RGBA8, gl.RGBA8);
+ blitframebuffer_outside_readbuffer(gl.RGBA8, gl.SRGB8_ALPHA8);
+ blitframebuffer_outside_readbuffer(gl.SRGB8_ALPHA8, gl.RGBA8);
+ blitframebuffer_outside_readbuffer(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-r11f-g11f-b10f.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-r11f-g11f-b10f.html
new file mode 100644
index 0000000000..636e76ac29
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-r11f-g11f-b10f.html
@@ -0,0 +1,113 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL R11F_G11F_B10F BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This tests multisample blitting with the R11F_G11F_B10F format.");
+
+var width = 8;
+var height = 8;
+
+function runWithContextCreationArguments(args) {
+ debug('');
+ debug('Running test with arguments: ' + JSON.stringify(args));
+
+ var canvas = document.createElement('canvas');
+ canvas.width = width;
+ canvas.height = height;
+
+ var gl = wtu.create3DContext(canvas, args, 2);
+ if (!gl) {
+ testFailed("WebGL 2.0 context does not exist");
+ return;
+ }
+
+ var ext = gl.getExtension("EXT_color_buffer_float");
+ if (!ext) {
+ testPassed("EXT_color_buffer_float extension not supported");
+ return;
+ }
+
+ var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.R11F_G11F_B10F, gl.SAMPLES);
+
+ // Set up source framebuffer.
+ var rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[0], gl.R11F_G11F_B10F, width, height);
+ var readfb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, readfb);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after multisampled R11F_G11F_B10F FBO setup");
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Source framebuffer incomplete.");
+ return;
+ }
+
+ // Draw something to that framebuffer.
+ gl.clearColor(0.0, 1.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after clearing R11F_G11F_B10F framebuffer");
+
+ // Set up destination framebuffer for resolving MSAA.
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.R11F_G11F_B10F, width, height, 0, gl.RGB, gl.UNSIGNED_INT_10F_11F_11F_REV, null);
+ var drawfb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, drawfb);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after destination R11F_G11F_B10F FBO setup");
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ // Attempt a blit.
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after blitFramebuffer for multisample resolve");
+
+ // Try a readback.
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, drawfb);
+ var readBackBuf = new Float32Array(width * height * 4);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0.0, 1.0, 0.0], "should be green", undefined, readBackBuf, gl.FLOAT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after floating-point canvas readback");
+
+ // If default backbuffer is RGB and non-antialiased, test blitting to it too.
+ if (args && !args['alpha'] && !args['antialias']) {
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, drawfb);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after blit to default back buffer");
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [ 0, 255, 0, 255 ], "default back buffer should be green");
+ }
+}
+
+// The format of the back buffer should have no effect on the behavior of this test.
+runWithContextCreationArguments(undefined);
+runWithContextCreationArguments({ alpha: true, antialias: true });
+runWithContextCreationArguments({ alpha: true, antialias: false });
+runWithContextCreationArguments({ alpha: false, antialias: true });
+runWithContextCreationArguments({ alpha: false, antialias: false });
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-resolve-to-back-buffer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-resolve-to-back-buffer.html
new file mode 100644
index 0000000000..addbdc4e9d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-resolve-to-back-buffer.html
@@ -0,0 +1,245 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Resolve to Back Buffer</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="canvasHeader"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the behavior of blitFramebuffer when resolving directly to the back buffer.");
+
+debug("Regression test for <a href='http://crbug.com/699566'>http://crbug.com/699566</a>");
+
+function runTest(testParams) {
+ const sz = 64;
+
+ if (testParams.multisampled === undefined) {
+ testParams.multisampled = true;
+ }
+
+ debug('');
+ debug('Testing with alpha = ' + testParams.attribs.alpha +
+ ', antialias = ' + testParams.attribs.antialias +
+ ', internalformat = ' + testParams.internalformat +
+ ', multisampled = ' + testParams.multisampled);
+
+ var canvas = document.createElement('canvas');
+ canvas.width = sz;
+ canvas.height = sz;
+ document.getElementById('canvasHeader').appendChild(canvas);
+ var gl = wtu.create3DContext(canvas, testParams.attribs, 2);
+
+ // Find the supported samples for a multisampled renderbuffer of the appropriate internal format.
+ let samples = [0];
+ if (testParams.multisampled) {
+ samples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl[testParams.internalformat], gl.SAMPLES);
+ if (!samples || !samples.length) {
+ testFailed("At least one multisampled format is required to be supported");
+ return;
+ }
+ }
+
+ // Create a framebuffer with a multisampled renderbuffer.
+ let rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[0], gl[testParams.internalformat], sz, sz);
+
+ // Create a framebuffer.
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+
+ // Check for completeness.
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Rendering to a multisampled renderbuffer of format " + testParams.internalformat + " is required by the spec");
+ return;
+ }
+
+ // Clear to specified color.
+ gl.clearColor.apply(gl, testParams.clearColor);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Unbind draw framebuffer. Read framebuffer is now user framebuffer;
+ // draw framebuffer is default framebuffer.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors before blit");
+
+ // Blit from user framebuffer to default framebuffer.
+ gl.blitFramebuffer(0, 0, sz, sz, 0, 0, sz, sz, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ if (testParams.shouldSucceed) {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be legal to blit/resolve to default back buffer");
+ } else {
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "incompatible src/dest blitFramebuffer combination must fail");
+ }
+
+ // Unbind user framebuffer completely.
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+
+ if (testParams.shouldSucceed) {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error before readback");
+ wtu.checkCanvasRect(gl, 0, 0, 8, 8, testParams.resultColor);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ }
+}
+
+var tests = [
+ // No-alpha, no-antialias, RGB8 source
+ {
+ attribs: {
+ alpha: false,
+ antialias: false,
+ },
+ internalformat: 'RGB8',
+ clearColor: [ 0.0, 1.0, 0.0, 0.5 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: true,
+ },
+ // No-alpha, no-antialias, RGBA8 source
+ {
+ attribs: {
+ alpha: false,
+ antialias: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 0.5 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // No-alpha, no-antialias, RGBA8 source, single-sampled blit
+ {
+ attribs: {
+ alpha: false,
+ antialias: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 0.5 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: true,
+ multisampled: false,
+ },
+ // Alpha, no-antialias, RGB8 source
+ {
+ attribs: {
+ alpha: true,
+ antialias: false,
+ },
+ internalformat: 'RGB8',
+ clearColor: [ 0.0, 1.0, 0.0, 1.0 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // No-alpha, no-antialias, RGBA8 source
+ // premultiplyAlpha:false just to avoid semantically incorrect
+ // colors (should only affect rendering, not contents of WebGL
+ // back buffer)
+ {
+ attribs: {
+ alpha: false,
+ antialias: false,
+ premultiplyAlpha: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 1.0 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // Alpha, no-antialias, RGBA8 source
+ // premultiplyAlpha:false just to avoid semantically incorrect
+ // colors (should only affect rendering, not contents of WebGL
+ // back buffer)
+ {
+ attribs: {
+ alpha: true,
+ antialias: false,
+ premultiplyAlpha: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 0.0 ],
+ resultColor: [ 0, 255, 0, 0 ],
+ shouldSucceed: true,
+ },
+
+ // All attempts to blit to an antialiased back buffer should fail.
+
+ // No-alpha, antialias, RGB8 source
+ {
+ attribs: {
+ alpha: false,
+ antialias: true,
+ },
+ internalformat: 'RGB8',
+ clearColor: [ 0.0, 1.0, 0.0, 1.0 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // Alpha, antialias, RGB8 source
+ {
+ attribs: {
+ alpha: true,
+ antialias: true,
+ },
+ internalformat: 'RGB8',
+ clearColor: [ 0.0, 1.0, 0.0, 1.0 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // No-alpha, antialias, RGBA8 source
+ // premultiplyAlpha:false just to avoid semantically incorrect
+ // colors (should only affect rendering, not contents of WebGL
+ // back buffer)
+ {
+ attribs: {
+ alpha: false,
+ antialias: true,
+ premultiplyAlpha: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 1.0 ],
+ resultColor: [ 0, 255, 0, 255 ],
+ shouldSucceed: false,
+ },
+ // Alpha, antialias, RGBA8 source
+ // premultiplyAlpha:false just to avoid semantically incorrect
+ // colors (should only affect rendering, not contents of WebGL
+ // back buffer)
+ {
+ attribs: {
+ alpha: true,
+ antialias: true,
+ premultiplyAlpha: false,
+ },
+ internalformat: 'RGBA8',
+ clearColor: [ 0.0, 1.0, 0.0, 0.0 ],
+ resultColor: [ 0, 255, 0, 0 ],
+ shouldSucceed: false,
+ },
+];
+
+for (var ii in tests) {
+ runTest(tests[ii]);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-scissor-enabled.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-scissor-enabled.html
new file mode 100644
index 0000000000..d0e2dfaefa
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-scissor-enabled.html
@@ -0,0 +1,160 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer when scissor test is enabled.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+// Define the src region and dst region for blitFramebuffer
+var blit_src = [0, 0, 4, 4];
+var blit_dst = [2, 2, 6, 6];
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ var bounds = [
+ [0, 0, 4, 4], // Partially intersects with blitFramebuffer's dst region
+ [0, 0, 2, 2], // No intersection with blitFramebuffer's dst region
+ ];
+
+ // We can compute the real drawing area by intersecting the scissor bound with dst region of blitting.
+ var intersections = [
+ [2, 2, 4, 4],
+ [0, 0, 0, 0],
+ ];
+
+ for (var ii = 0; ii < bounds.length; ++ii) {
+ blitframebuffer_scissor(gl.RGBA8, gl.RGBA8, bounds[ii], intersections[ii]);
+ blitframebuffer_scissor(gl.RGBA8, gl.SRGB8_ALPHA8, bounds[ii], intersections[ii]);
+ blitframebuffer_scissor(gl.SRGB8_ALPHA8, gl.RGBA8, bounds[ii], intersections[ii]);
+ blitframebuffer_scissor(gl.SRGB8_ALPHA8, gl.SRGB8_ALPHA8, bounds[ii], intersections[ii]);
+ }
+}
+
+function checkPixel(color, expectedColor) {
+ var tolerance = 3;
+ return (Math.abs(color[0] - expectedColor[0]) <= tolerance &&
+ Math.abs(color[1] - expectedColor[1]) <= tolerance &&
+ Math.abs(color[2] - expectedColor[2]) <= tolerance &&
+ Math.abs(color[3] - expectedColor[3]) <= tolerance);
+}
+
+function blitframebuffer_scissor(readbufferFormat, drawbufferFormat, bound, intersection) {
+ debug("");
+ debug("read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat) + ", draw buffer format is: " + wtu.glEnumToString(gl, drawbufferFormat));
+
+
+ // Initiate data to read framebuffer
+ var size = 8;
+ var data = new Uint8Array(size * size * 4);
+ var color = [250, 100, 15, 255];
+ for (var ii = 0; ii < size * size * 4; ii += 4) {
+ for (var jj = 0; jj < 4; ++jj) {
+ data[ii + jj] = color[jj];
+ }
+ }
+
+ // Feed data to read buffer. Feed 0 to draw buffer.
+ var tex_read = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ gl.texImage2D(gl.TEXTURE_2D, 0, readbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
+
+ var fbo_read = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+
+ var tex_draw = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ gl.texImage2D(gl.TEXTURE_2D, 0, drawbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ var fbo_draw = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_draw, 0);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE || gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ // Enable scissor test. Then blit framebuffer.
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(bound[0], bound[1], bound[2], bound[3]);
+ gl.blitFramebuffer(blit_src[0], blit_src[1], blit_src[2], blit_src[3], blit_dst[0], blit_dst[1], blit_dst[2], blit_dst[3], gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitframebuffer should succeed");
+
+ // Read pixels and Comparison
+ var pixels = new Uint8Array(size * size * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels should succeed");
+
+ var blitColor;
+ var expectedColor;
+ var clearColor = [0, 0, 0, 0];
+
+ if (readbufferFormat == drawbufferFormat) {
+ blitColor = color;
+ } else if (readbufferFormat == gl.SRGB8_ALPHA8) {
+ blitColor = wtu.sRGBToLinear(color);
+ } else {
+ blitColor = wtu.linearToSRGB(color);
+ }
+
+ var failed = false;
+ for (var ii = 0; ii < size; ++ii) {
+ for (var jj = 0; jj < size; ++jj) {
+ if (ii >= intersection[0] && jj >= intersection[1] && ii < intersection[2] && jj < intersection[3]) {
+ expectedColor = blitColor;
+ } else {
+ expectedColor = clearColor;
+ }
+ var index = (ii * size + jj) * 4;
+ var pixelColor = [pixels[index], pixels[index + 1], pixels[index + 2], pixels[index + 3]];
+ if (checkPixel(pixelColor, expectedColor) == false) {
+ failed = true;
+ debug("Pixels comparison failed. Pixel at [" + jj + ", " + ii + "] should be (" + expectedColor + "), but the actual color is (" + pixelColor + ")");
+ }
+ }
+ }
+ if (failed == false) {
+ testPassed("All pixels comparision passed!");
+ }
+
+ // Deinit
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fbo_read);
+ gl.deleteFramebuffer(fbo_draw);
+ gl.deleteTexture(tex_read);
+ gl.deleteTexture(tex_draw);
+};
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-size-overflow.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-size-overflow.html
new file mode 100644
index 0000000000..512946cb82
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-size-overflow.html
@@ -0,0 +1,98 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies blitFramebuffer won't cause a crash when the computed sizes might overflow.");
+
+var width = 8;
+var height = 8;
+
+var gl = wtu.create3DContext("example", undefined, 2);
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ blit_region_test();
+}
+
+function blit_region_test() {
+
+ debug("");
+ debug("Begin to run blitFramebuffer. The computed width/height of src and/or dst region might overflow during blitting.");
+ var tex0 = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex0);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ var fb0 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex0, 0);
+
+ var tex1 = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ var fb1 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex1, 0);
+ if ((gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) ||
+ (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ var max = 0x7fffffff;
+ gl.blitFramebuffer(0, 0, max, max, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, max, max, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.blitFramebuffer(0, 0, max, max, 0, 0, max, max, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Using max 32-bit integer as blitFramebuffer parameter should succeed.");
+
+ gl.blitFramebuffer(-1, -1, max - 1, max - 1, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.blitFramebuffer(0, 0, width, height, -1, -1, max - 1, max - 1, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.blitFramebuffer(-1, -1, max - 1, max - 1, -1, -1, max - 1, max - 1, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Using blitFramebuffer parameters where calculated width/height matches max 32-bit integer should succeed.");
+
+ gl.blitFramebuffer(-1, -1, max, max, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using source width/height greater than max 32-bit integer should fail.");
+ gl.blitFramebuffer(max, max, -1, -1, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using source width/height greater than max 32-bit integer should fail.");
+ gl.blitFramebuffer(0, 0, width, height, -1, -1, max, max, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using destination width/height greater than max 32-bit integer should fail.");
+ gl.blitFramebuffer(0, 0, width, height, max, max, -1, -1, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using destination width/height greater than max 32-bit integer should fail.");
+ gl.blitFramebuffer(-1, -1, max, max, -1, -1, max, max, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using both source and destination width/height greater than max 32-bit integer should fail.");
+ gl.blitFramebuffer(-max - 1, -max - 1, max, max, -max - 1, -max - 1, max, max, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Using minimum and maximum integers for all boundaries should fail.");
+
+ gl.bindTexture(gl.TEXTURE_2D, null)
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteTexture(tex0);
+ gl.deleteTexture(tex1);
+ gl.deleteFramebuffer(fb0);
+ gl.deleteFramebuffer(fb1);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-srgb-and-linear-drawbuffers.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-srgb-and-linear-drawbuffers.html
new file mode 100644
index 0000000000..35b8c3ddab
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-srgb-and-linear-drawbuffers.html
@@ -0,0 +1,207 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer with multiple draw buffers (srgb image and linear image).");
+
+var gl = wtu.create3DContext("canvas", undefined, 2);
+var linearMask = 1;
+var srgbMask = 2;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ var filters = [gl.LINEAR, gl.NEAREST];
+ var drawbuffersFormats = [linearMask, srgbMask, linearMask | srgbMask];
+ for (var ii = 0; ii < filters.length; ++ii) {
+ for (var jj = 0; jj < drawbuffersFormats.length; ++jj) {
+ blitframebuffer_srgb_and_linear_drawbuffers(gl.SRGB8_ALPHA8, drawbuffersFormats[jj], filters[ii]);
+ blitframebuffer_srgb_and_linear_drawbuffers(gl.RGBA8, drawbuffersFormats[jj], filters[ii]);
+ }
+ }
+}
+
+function blitframebuffer_srgb_and_linear_drawbuffers(readbufferFormat, drawbuffersFormatMask, filter) {
+ debug("");
+ debug("The filter is: " + wtu.glEnumToString(gl, filter));
+ debug("Read buffer format is: " + wtu.glEnumToString(gl, readbufferFormat));
+ var drawbuffersFormat = "\0";
+ if (drawbuffersFormatMask & linearMask) {
+ drawbuffersFormat += " linear ";
+ }
+ if (drawbuffersFormatMask & srgbMask) {
+ drawbuffersFormat += " srgb ";
+ }
+ debug("The test have multiple draw buffers, the images are: " + drawbuffersFormat);
+
+ var tex_srgb0 = gl.createTexture();
+ var tex_srgb1 = gl.createTexture();
+ var tex_linear0 = gl.createTexture();
+ var tex_linear1 = gl.createTexture();
+ var tex_read = gl.createTexture();
+ var fbo_read = gl.createFramebuffer();
+ var fbo_draw = gl.createFramebuffer();
+
+ // Create read buffer and feed data to the read buffer
+ var size = 8;
+ var data = new Uint8Array(size * size * 4);
+ var color = [250, 100, 15, 255];
+ for (var ii = 0; ii < size * size * 4; ii += 4) {
+ for (var jj = 0; jj < 4; ++jj) {
+ data[ii + jj] = color[jj];
+ }
+ }
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ gl.texImage2D(gl.TEXTURE_2D, 0, readbufferFormat, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup read framebuffer should succeed");
+
+ // Create multiple textures. Attach them as fbo's draw buffers.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+
+ var drawbuffers = [gl.NONE, gl.NONE, gl.NONE, gl.NONE];
+ if (drawbuffersFormatMask & srgbMask) {
+ gl.bindTexture(gl.TEXTURE_2D, tex_srgb0);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_srgb0, 0);
+ gl.bindTexture(gl.TEXTURE_2D, tex_srgb1);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, tex_srgb1, 0);
+ drawbuffers[0] = gl.COLOR_ATTACHMENT0;
+ drawbuffers[2] = gl.COLOR_ATTACHMENT2;
+ }
+
+ if (drawbuffersFormatMask & linearMask) {
+ gl.bindTexture(gl.TEXTURE_2D, tex_linear0);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, tex_linear0, 0);
+ gl.bindTexture(gl.TEXTURE_2D, tex_linear1);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT3, gl.TEXTURE_2D, tex_linear1, 0);
+ drawbuffers[1] = gl.COLOR_ATTACHMENT1;
+ drawbuffers[3] = gl.COLOR_ATTACHMENT3;
+ }
+
+ gl.drawBuffers(drawbuffers);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup draw framebuffer should succeed");
+
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete when setup draw framebuffer.");
+ return;
+ }
+
+ // Blit to multiple draw buffers with srgb images and linear images
+ var dstSize = size - 1;
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, dstSize, dstSize, gl.COLOR_BUFFER_BIT, filter);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitframebuffer should succeed");
+
+ // Read pixels from srgb images and linear images
+ var srgbPixels0 = new Uint8Array(dstSize * dstSize * 4);
+ var srgbPixels1 = new Uint8Array(dstSize * dstSize * 4);
+ var linearPixels0 = new Uint8Array(dstSize * dstSize * 4);
+ var linearPixels1 = new Uint8Array(dstSize * dstSize * 4);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_draw);
+ if (drawbuffersFormatMask & srgbMask) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.readPixels(0, 0, dstSize, dstSize, gl.RGBA, gl.UNSIGNED_BYTE, srgbPixels0);
+ gl.readBuffer(gl.COLOR_ATTACHMENT2);
+ gl.readPixels(0, 0, dstSize, dstSize, gl.RGBA, gl.UNSIGNED_BYTE, srgbPixels1);
+ }
+
+ if (drawbuffersFormatMask & linearMask) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ gl.readPixels(0, 0, dstSize, dstSize, gl.RGBA, gl.UNSIGNED_BYTE, linearPixels0);
+ gl.readBuffer(gl.COLOR_ATTACHMENT3);
+ gl.readPixels(0, 0, dstSize, dstSize, gl.RGBA, gl.UNSIGNED_BYTE, linearPixels1);
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readpixels should succeed");
+
+ // Compare
+ var expectedSRGBColor = (readbufferFormat == gl.SRGB8_ALPHA8) ? color : wtu.linearToSRGB(color);
+ var expectedLinearColor = (readbufferFormat == gl.SRGB8_ALPHA8) ? wtu.sRGBToLinear(color) : color;
+ var failed = false;
+ for (var ii = 0; ii < dstSize; ++ii) {
+ for (var jj = 0; jj < dstSize; ++jj) {
+ var index = (ii * dstSize + jj) * 4;
+ if (drawbuffersFormatMask & srgbMask) {
+ var srgbColor0 = [srgbPixels0[index], srgbPixels0[index + 1], srgbPixels0[index + 2], srgbPixels0[index + 3]];
+ if (checkPixel(srgbColor0, expectedSRGBColor) == false) {
+ failed = true;
+ debug("Pixels comparison failed for the 1st sRGB image. Pixel at [" + jj + ", " + ii + "] should be (" + expectedSRGBColor + "), but the actual color is (" + srgbColor0 + ")");
+ }
+ var srgbColor1 = [srgbPixels1[index], srgbPixels1[index + 1], srgbPixels1[index + 2], srgbPixels1[index + 3]];
+ if (checkPixel(srgbColor1, expectedSRGBColor) == false) {
+ failed = true;
+ debug("Pixels comparison failed for the 2nd sRGB image. Pixel at [" + jj + ", " + ii + "] should be (" + expectedSRGBColor + "), but the actual color is (" + srgbColor1 + ")");
+ }
+ }
+
+ if (drawbuffersFormatMask & linearMask) {
+ var linearColor0 = [linearPixels0[index], linearPixels0[index + 1], linearPixels0[index + 2], linearPixels0[index + 3]];
+ if (checkPixel(linearColor0, expectedLinearColor) == false) {
+ failed = true;
+ debug("Pixel comparison failed for the 1st linear image. Pixel at [" + jj + ", " + ii + "] should be (" + color + "), but the actual color is (" + linearColor0 + ")");
+ }
+ var linearColor1 = [linearPixels1[index], linearPixels1[index + 1], linearPixels1[index + 2], linearPixels1[index + 3]];
+ if (checkPixel(linearColor1, expectedLinearColor) == false) {
+ failed = true;
+ debug("Pixel comparison failed for the 2nd linear image. Pixel at [" + jj + ", " + ii + "] should be (" + color + "), but the actual color is (" + linearColor1 + ")");
+ }
+ }
+ }
+ }
+ if (failed == false) {
+ testPassed("All pixels comparision passed!");
+ }
+
+ // deinit
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteTexture(tex_srgb0);
+ gl.deleteTexture(tex_linear0);
+ gl.deleteTexture(tex_srgb1);
+ gl.deleteTexture(tex_linear1);
+ gl.deleteTexture(tex_read);
+ gl.deleteFramebuffer(fbo_read);
+ gl.deleteFramebuffer(fbo_draw);
+}
+
+function checkPixel(color, expectedColor) {
+ var tolerance = 3;
+ return (Math.abs(color[0] - expectedColor[0]) <= tolerance &&
+ Math.abs(color[1] - expectedColor[1]) <= tolerance &&
+ Math.abs(color[2] - expectedColor[2]) <= tolerance &&
+ Math.abs(color[3] - expectedColor[3]) <= tolerance);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-stencil-only.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-stencil-only.html
new file mode 100644
index 0000000000..b14acf3456
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-stencil-only.html
@@ -0,0 +1,170 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Stencil-only Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+
+<script id="vs" type="x-shader/x-vertex">#version 300 es
+in vec4 position;
+void main() {
+ gl_Position = position;
+}
+</script>
+<script id="fs" type="x-shader/x-fragment">#version 300 es
+out mediump vec4 colorOut;
+uniform mediump vec3 color;
+void main() {
+ colorOut = vec4(color, 1.0);
+}
+</script>
+
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test covers some edge cases of blitFramebuffer with stencil.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+var program, colorLoc;
+
+function init_buffer(format) {
+ var buf = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, buf)
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 16, 16);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ var rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorage(gl.RENDERBUFFER, format, 16, 16);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER,
+ gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, rbo);
+
+ gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 1.0, 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after buffer init");
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ return { fbo: buf, color: tex, depthStencil: rbo };
+}
+
+var quadVB;
+
+function drawQuad(depth) {
+ if (!quadVB) {
+ quadVB = gl.createBuffer()
+ }
+
+ var quadVerts = new Float32Array(3 * 6);
+ quadVerts[0] = -1.0; quadVerts[1] = 1.0; quadVerts[2] = depth;
+ quadVerts[3] = -1.0; quadVerts[4] = -1.0; quadVerts[5] = depth;
+ quadVerts[6] = 1.0; quadVerts[7] = -1.0; quadVerts[8] = depth;
+ quadVerts[9] = -1.0; quadVerts[10] = 1.0; quadVerts[11] = depth;
+ quadVerts[12] = 1.0; quadVerts[13] = -1.0; quadVerts[14] = depth;
+ quadVerts[15] = 1.0; quadVerts[16] = 1.0; quadVerts[17] = depth;
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, quadVB);
+ gl.bufferData(gl.ARRAY_BUFFER, quadVerts, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, gl.FALSE, 0, 0);
+ gl.enableVertexAttribArray(0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after drawQuad");
+}
+
+// Test based on dEQP-GLES3.functional.blit.depth_stencil.depth_24_stencil8_stencil_only
+function test_stencil_only_blit(format) {
+ debug("testing format: " + wtu.glEnumToString(gl, format))
+
+ var src = init_buffer(format);
+ var dest = init_buffer(format);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, src.fbo);
+ gl.viewport(0, 0, 16, 16);
+
+ // Fill source with red, depth = 0.5, stencil = 7
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.STENCIL_TEST);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE);
+ gl.stencilFunc(gl.ALWAYS, 7, 0xFF);
+ gl.uniform3f(colorLoc, 1.0, 0.0, 0.0);
+ drawQuad(0.5);
+
+ // Fill dest with yellow, depth = 0.0, stencil = 1
+ gl.bindFramebuffer(gl.FRAMEBUFFER, dest.fbo);
+ gl.stencilFunc(gl.ALWAYS, 1, 0xff);
+ gl.uniform3f(colorLoc, 1.0, 1.0, 0.0);
+ drawQuad(0.0);
+
+ // Perform copy.
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, src.fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, dest.fbo);
+ gl.blitFramebuffer(0, 0, 16, 16, 0, 0, 16, 16, gl.STENCIL_BUFFER_BIT, gl.NEAREST);
+
+ // Render blue where depth < 0, decrement on depth failure.
+ gl.bindFramebuffer(gl.FRAMEBUFFER, dest.fbo);
+ gl.stencilOp(gl.KEEP, gl.DECR, gl.KEEP);
+ gl.stencilFunc(gl.ALWAYS, 0, 0xff);
+
+ gl.uniform3f(colorLoc, 0.0, 0.0, 1.0);
+ drawQuad(0.0);
+
+ // Render green where stencil == 6.
+ gl.disable(gl.DEPTH_TEST);
+ gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
+ gl.stencilFunc(gl.EQUAL, 6, 0xff);
+
+ gl.uniform3f(colorLoc, 0.0, 1.0, 0.0);
+ drawQuad(0.0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after test");
+ wtu.checkCanvasRect(gl, 0, 0, 16, 16, [0, 255, 0, 255],
+ "stencil test should be green");
+
+ gl.deleteFramebuffer(src.fbo);
+ gl.deleteFramebuffer(dest.fbo);
+ gl.deleteTexture(src.color);
+ gl.deleteTexture(dest.color);
+ gl.deleteRenderbuffer(src.depthStencil);
+ gl.deleteRenderbuffer(dest.depthStencil);
+}
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ program = wtu.setupProgram(gl, ["vs", "fs"], ["position"]);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after program initialization");
+ shouldBe('gl.getProgramParameter(program, gl.LINK_STATUS)', 'true');
+
+ colorLoc = gl.getUniformLocation(program, "color")
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "query uniform location");
+ shouldBeNonNull('colorLoc')
+
+ test_stencil_only_blit(gl.DEPTH24_STENCIL8);
+ test_stencil_only_blit(gl.DEPTH32F_STENCIL8);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-test.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-test.html
new file mode 100644
index 0000000000..c470b02e4e
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-test.html
@@ -0,0 +1,361 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL BlitFramebuffer Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of blitFramebuffer for some corner cases.");
+
+var width = 8;
+var height = 8;
+
+var gl = wtu.create3DContext("example", undefined, 2);
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ blit_framebuffer_repeated();
+ blit_framebuffer_feedback_loop();
+ blit_framebuffer_multisampling_srgb();
+}
+
+function blit_framebuffer_repeated() {
+ debug("");
+ debug("This test verifies repeated calls to blitFramebuffer.");
+
+ // Create offscreen fbo and its color attachment.
+ var tex_2d = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_2d);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height);
+ 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.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_2d, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ var prog = wtu.setupColorQuad(gl, 0);
+ wtu.setFloatDrawColor(gl, [ 1.0, 0.0, 0.0, 1.0 ]);
+ wtu.drawUnitQuad(gl);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [ 255, 0, 0, 255 ], "should be red at first");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ wtu.setFloatDrawColor(gl, [ 0.0, 1.0, 0.0, 1.0 ]);
+ wtu.drawUnitQuad(gl);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [ 0, 255, 0, 255 ], "should be green");
+}
+
+function blit_framebuffer_feedback_loop() {
+
+ debug("");
+ debug("This test checks whether the src resource and dst resource have identical images.");
+ // Create read fbo and its color attachment.
+ var tex_2d = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex_2d);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.generateMipmap(gl.TEXTURE_2D);
+
+ var fb0 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_2d, 0);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ // Create draw fbo and its color attachment.
+ var rb0 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb0);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, width, height);
+
+ var fb1 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ // Blit framebuffer, all conditions are OK.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed.");
+
+ // Blit framebuffer, the src buffer and the dst buffer should not be identical.
+ // Exactly the same read/draw fbo
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb0);
+ gl.blitFramebuffer(0, 0, 2, 2, 4, 4, 6, 6, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw buffer are identical.");
+
+ // Exactly the same read/draw framebuffer: default framebuffer
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 2, 2, 4, 4, 6, 6, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw buffer are identical.");
+
+ // The same image with the same level bound to read/draw buffer.
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_2d, 0);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_2d, 0);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 4, 4, 6, 6, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw color buffer are identical.");
+
+ // The same image in read/draw buffer, but different levels are bound to read/draw buffer respectively.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_2d, 1);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed if read/draw buffer has the same image with different levels.");
+
+ // The same cube_map image in read/draw buffer, but different faces are bound to read/draw buffer respectively.
+ var tex_cube_map = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex_cube_map);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X, tex_cube_map, 0);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, tex_cube_map, 0);
+ if ((gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) ||
+ (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed if read/draw buffer has the same CUBE_MAP image with different faces.");
+
+ // The same 3D/2D_ARRAY image in read/draw buffer, but different layers are bound to read/draw buffer respectively.
+ var tex_2d_array = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex_2d_array);
+ var depth = 2;
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.RGBA8, width, height, depth, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ var level = 0, layer = 0;
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex_2d_array, level, layer);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ layer = 1;
+ gl.framebufferTextureLayer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex_2d_array, level, layer);
+ if ((gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) ||
+ (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE)) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed if read/draw buffer has the same 3D/2D_ARRAY image with different layers.");
+
+ // The same image are bound as depth buffer in both read framebuffer and draw framebuffer
+ var rb1 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X, tex_cube_map, 0);
+ gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb1);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, tex_cube_map, 0);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ // But the mask doesn't have depth buffer bit.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed.");
+
+ // The mask has depth buffer bit.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw framebuffer have identical depth buffer attachment.");
+
+ // The same image are bound as stencil buffer in both read framebuffer and draw framebuffer
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rb1);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ // But the mask doesn't have stencil buffer bit.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed.");
+
+ // The mask has stencil buffer bit.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw framebuffer have identical stencil buffer attachment.");
+
+ // The same image are bound as color buffer in both read framebuffer and draw framebuffer
+ var rb2 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb2);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, width, height);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X, tex_cube_map, 0);
+ gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
+ gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb2);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_NEGATIVE_X, tex_cube_map, 0);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_CUBE_MAP_POSITIVE_X, tex_cube_map, 0);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ // But the mask doesn't have color buffer bit.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed.");
+
+ // The mask has color buffer bit, but the same image is not specified as draw buffer.
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer should succeed.");
+
+ // The mask has color buffer bit, the same image is specified as both read buffer and draw buffer.
+ gl.drawBuffers([gl.COLOR_ATTACHENT0, gl.COLOR_ATTACHMENT1]);
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer should generate INVALID_OPERATION if read/draw buffers have identical color buffer attachment.");
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteTexture(tex_2d);
+ gl.deleteTexture(tex_cube_map);
+ gl.deleteTexture(tex_2d_array);
+ gl.deleteRenderbuffer(rb0);
+ gl.deleteRenderbuffer(rb1);
+ gl.deleteRenderbuffer(rb2);
+ gl.deleteFramebuffer(fb0);
+ gl.deleteFramebuffer(fb1);
+};
+
+function blit_framebuffer_multisampling_srgb() {
+
+ debug("");
+ debug("This test vefify the functionality of blitframebuffer from or to a multisampled srgb image.");
+
+ // Read buffer can have multisampled srgb image, but draw buffers can not.
+ var rb0 = gl.createRenderbuffer();
+ var fb0 = gl.createFramebuffer();
+ var rb1 = gl.createRenderbuffer();
+ var fb1 = gl.createFramebuffer();
+ var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.SRGB8_ALPHA8, gl.SAMPLES);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb0);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[0], gl.SRGB8_ALPHA8, width, height);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fb0);
+ gl.framebufferRenderbuffer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0);
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.SRGB8_ALPHA8, width, height);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE ||
+ gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "blitFramebuffer from multisampled srgb image should succeed.");
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[0], gl.SRGB8_ALPHA8, width, height);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer to a multisampled srgb image should generate INVALID_OPERATION.");
+
+ // BlitFramebuffer from a multisampled srgb image, the src region and the dst region must be exactly the same.
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.SRGB8_ALPHA8, width, height);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb1);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 2, 2, 4, 4, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer from a multisampled srgb image, the src region and the dst region must be exactly the same.");
+
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 4, 4, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer from a multisampled srgb image, the src region and the dst region must be exactly the same.");
+
+ // BlitFramebuffer from a multisampled srgb image, the format/type must be exactly the same. So blit from a multisampled srgb image to a linear image is invalid.
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb1);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.blitFramebuffer(0, 0, 2, 2, 0, 0, 2, 2, gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "blitFramebuffer from a multisampled srgb image, the format/type must be exactly the same.");
+
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.deleteRenderbuffer(rb0);
+ gl.deleteRenderbuffer(rb1);
+ gl.deleteTexture(tex);
+ gl.deleteFramebuffer(fb0);
+ gl.deleteFramebuffer(fb1);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-unaffected-by-colormask.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-unaffected-by-colormask.html
new file mode 100644
index 0000000000..3d2d7f54bc
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/blitframebuffer-unaffected-by-colormask.html
@@ -0,0 +1,102 @@
+<!--
+Copyright (c) 2021 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>BlitFramebuffer Should Be Unaffected by ColorMask</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+const wtu = WebGLTestUtils;
+description("This test verifies that the blitFramebuffer is unaffected by the colorMask state.");
+
+debug('Regression test for <a href="https://crbug.com/1257769">https://crbug.com/1257769</a> and <a href="https://bugs.webkit.org/show_bug.cgi?id=220129">https://bugs.webkit.org/show_bug.cgi?id=220129</a>');
+
+function allocateTexture(gl, size) {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ return tex;
+}
+
+function allocateFBO(gl, tex) {
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ return fbo;
+}
+
+function run() {
+ const gl = wtu.create3DContext("canvas", { antialias: false }, 2);
+
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ finishTest();
+ return;
+ }
+
+ const size = 8;
+
+ testPassed("WebGL context exists");
+
+ // Allocate source and destination textures and framebuffer objects.
+ const sourceTex = allocateTexture(gl, size);
+ const sourceFBO = allocateFBO(gl, sourceTex);
+
+ const destTex = allocateTexture(gl, size);
+ const destFBO = allocateFBO(gl, destTex);
+
+ const program = wtu.setupColorQuad(gl);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, sourceFBO);
+
+ // Clear the source framebuffer to red.
+ gl.clearColor(1, 0, 0, 1);
+ gl.colorMask(true, true, true, true);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Draw a transparent green quad.
+ gl.useProgram(program);
+ wtu.drawFloatColorQuad(gl, [ 0, 255, 0, 0 ]);
+
+ // Clear the alpha channel.
+ gl.colorMask(false, false, false, true);
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // At this point, even setting the colorMask to all-true won't
+ // work around the bug, since that state is latched inside ANGLE
+ // only during draws / clears.
+
+ // Blit source to dest.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, destFBO);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, destFBO);
+
+ // Note that the on-screen canvas is always black - we don't blit the result to it.
+ wtu.checkCanvas(gl, [ 0, 255, 0, 255 ], "should be green", 1);
+ finishTest();
+}
+
+var successfullyParsed = true;
+
+requestAnimationFrame(run);
+
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/builtin-vert-attribs.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/builtin-vert-attribs.html
new file mode 100644
index 0000000000..cc64c9034b
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/builtin-vert-attribs.html
@@ -0,0 +1,408 @@
+<!--
+Copyright (c) 2022 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset=utf-8>
+<link rel=stylesheet href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id=e_canvas width=1 height=1 style="width: 100px; height: 100px;"></canvas>
+<div id=description></div>
+<div id=console></div>
+<script>
+"use strict";
+description('gl_VertexID and gl_InstanceID should behave as per spec.');
+
+//
+
+/*
+So what are gl_VertexID and gl_InstanceID supposed to do?
+In ES 3.0 and GL 4.1 (Core), this is all we get:
+
+# ES 3.0
+
+> (p78) gl_VertexID holds the integer index i implicitly passed by DrawArrays or
+> one of the other drawing commands defined in section 2.9.3. The value of
+> gl_VertexID is defined if and only if all enabled vertex arrays have non-zero buffer
+> object bindings.
+> gl_InstanceID holds the integer instance number of the current primitive in
+> an instanced draw call (see section 2.9.3).
+
+
+# GL 4.1 (Core)
+
+> (p102) gl_VertexID holds the integer index i implicitly passed by DrawArrays or
+> one of the other drawing commands defined in section 2.8.3.
+> gl_InstanceID holds the integer index of the current primitive in an
+> instanced draw call (see section 2.8.3).
+
+
+# ES 3.1
+
+ES 3.1 retains the wording from ES 3.0, but adds the following clarifications:
+
+gl_VertexID:
+> (p252) The index of any element transferred to the GL by DrawArraysOneInstance
+> is referred to as its vertex ID, and may be read by a vertex shader as gl_VertexID.
+> The vertex ID of the ith element transferred is first + i.
+
+> (p254) The index of any element transferred to the GL by
+> DrawElementsOneInstance is referred to as its vertex ID, and may be read by a vertex shader as
+> gl_VertexID. If no element array buffer is bound, the vertex ID of the ith element
+> transferred is indices[i] + basevertex. Otherwise, the vertex ID of the ith
+> element transferred is the sum of basevertex and the value stored in the currently
+> bound element array buffer at offset indices + i.
+
+gl_InstanceID
+> (p255) If an enabled vertex attribute array is instanced (it has a non-zero divisor as
+> specified by VertexAttribDivisor), the element index that is transferred to the GL,
+> for all vertices, is given by
+> `floor(instance / divisor) + baseinstance`
+
+
+# Errata
+
+Drivers generally do implement the ES 3.1 behavior.
+A notable exception is Mac's legacy GL (4.1) driver which has two bugs here.
+(Both ANGLE-on-Metal and the system M1+ GL driver seem correct though)
+
+## gl_InstanceID random for DrawArrays calls
+Use ERRATA.IGNORE_GL_INSTANCE_ID to cause these tests to pass.
+
+## Adds `first` to user-attrib instanced fetch ids in DrawArrays calls.
+Use ERRATA.FIRST_ADDS_TO_INSTANCE to cause these tests to pass.
+*/
+
+const wtu = WebGLTestUtils;
+const gl = wtu.create3DContext('e_canvas');
+
+const ERRATA = {};
+//ERRATA.IGNORE_GL_INSTANCE_ID = true; // Chrome on ANGLE-on-Mac-GL needs this.
+//ERRATA.FIRST_ADDS_TO_INSTANCE = true; // Firefox with MOZ_WEBGL_WORKAROUND_FIRST_AFFECTS_INSTANCE_ID=0 would need this.
+
+debug(`ERRATA: ${JSON.stringify(ERRATA)}`);
+
+function make_vs_point(vid, iid) {
+ return `\
+ #version 300 es
+
+ ${vid.name == 'gl_VertexID' ? '// ' : ''}layout(location=${vid.loc}) in highp int ${vid.name};
+ ${iid.name == 'gl_InstanceID' ? '// ' :''}layout(location=${iid.loc}) in highp int ${iid.name};
+ out vec4 v_color;
+
+ void main() {
+ gl_PointSize = 1.0;
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+ v_color = vec4(1.0, float(${vid.name}) / 255.0, float(${iid.name}) / 255.0, 1.0);
+#if ${(iid.name == 'gl_InstanceID' && ERRATA.IGNORE_GL_INSTANCE_ID)|0}
+ v_color.b = 0.0;
+#endif
+ }`;
+}
+
+function make_vs_tri(vid, iid) {
+ return `\
+ #version 300 es
+
+ ${vid.name == 'gl_VertexID' ? '// ' : ''}layout(location=${vid.loc}) in highp int ${vid.name};
+ ${iid.name == 'gl_InstanceID' ? '// ' :''}layout(location=${iid.loc}) in highp int ${iid.name};
+ out vec4 v_color;
+
+ void main() {
+ int prim_vert_id = ${vid.name} % 3;
+ int flat_vert_id = ${vid.name} - prim_vert_id + 2;
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+ gl_Position.x = (prim_vert_id == 1) ? 2.0 : -1.0;
+ gl_Position.y = (prim_vert_id == 2) ? 2.0 : -1.0;
+ v_color = vec4(1.0, float(flat_vert_id) / 255.0, float(${iid.name}) / 255.0, 1.0);
+#if ${(iid.name == 'gl_InstanceID' && ERRATA.IGNORE_GL_INSTANCE_ID)|0}
+ v_color.b = 0.0;
+#endif
+ }`;
+}
+
+const FS = `\
+ #version 300 es
+ precision mediump float;
+
+ in vec4 v_color;
+ out vec4 o_color;
+
+ void main() {
+ o_color = v_color;
+ }
+`;
+
+
+function crossCombine(...args) {
+ function crossCombine2(listA, listB) {
+ const listC = [];
+ for (const a of listA) {
+ for (const b of listB) {
+ const c = Object.assign({}, a, b);
+ listC.push(c);
+ }
+ }
+ return listC;
+ }
+
+ let res = [{}];
+ while (args.length) {
+ const next = args.shift();
+ next[0].defined;
+ res = crossCombine2(res, next);
+ }
+ return res;
+}
+
+/// makeCombiner('foo', [5, 3]) -> [{foo: 5}, {foo: 3}]
+function makeCombiner(key, vals) {
+ const ret = [];
+ for (const val of vals) {
+ const cur = {};
+ cur[key] = val;
+ ret.push(cur);
+ }
+ return ret;
+}
+
+debug('Draw a point with a shader that takes no attributes and verify it fills the whole canvas.');
+
+
+let TESTS = [
+ makeCombiner('vid', [
+ {name: 'a_VertexID', loc:0},
+ {name: 'a_VertexID', loc:2}, // Test 2, so that we're not only testing 0.
+ {name: 'gl_VertexID', loc:-1},
+ {name: 'gl_VertexID', loc:0}, // Enable a vertex array, despite not using it.
+ {name: 'gl_VertexID', loc:2}, // Enable a vertex array, despite not using it.
+ ]),
+ makeCombiner('iid', [
+ {name: 'a_InstanceID', loc:1},
+ {name: 'gl_InstanceID', loc:-1},
+ {name: 'gl_InstanceID', loc:1}, // Enable a vertex array, despite not using it.
+ ]),
+ makeCombiner('separate_vbufs', [true, false]),
+];
+//console.log('a', {TESTS});
+TESTS = crossCombine(...TESTS);
+//console.log('b', {TESTS});
+
+
+let vdata = new Int32Array(1000);
+vdata = vdata.map((v,i) => i);
+const vbuf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
+gl.bufferData(gl.ARRAY_BUFFER, vdata, gl.STATIC_DRAW);
+
+
+const vbuf2 = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, vbuf2);
+gl.bufferData(gl.ARRAY_BUFFER, vdata, gl.STATIC_DRAW);
+
+
+let index_data = new Uint32Array(1000);
+index_data = index_data.map((x,i) => 10+i);
+const index_buffer = gl.createBuffer();
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buffer);
+gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, index_data, gl.STATIC_DRAW);
+
+
+gl.disable(gl.DEPTH_TEST);
+
+(async () => {
+ for (const desc of TESTS) {
+ await wtu.dispatchPromise(); // Yield, for responsiveness.
+ debug('');
+ debug('---------------------');
+ debug(`desc: ${JSON.stringify(desc)}`);
+
+ let fn = (vs) => {
+ //console.log({vs});
+ const prog = wtu.setupProgram(gl, [vs, FS]);
+
+ {
+ const WEBGL_debug_shaders = gl.getExtension('WEBGL_debug_shaders');
+ let i = -1;
+ for (const s of gl.getAttachedShaders(prog)) {
+ i += 1;
+ debug('');
+ debug(`shader[${i}] getShaderSource() -> `);
+ debug(gl.getShaderSource(s));
+ if (WEBGL_debug_shaders) {
+ debug(`shader[${i}] getTranslatedShaderSource() -> `);
+ debug(WEBGL_debug_shaders.getTranslatedShaderSource(s));
+ }
+ }
+ }
+ return prog;
+ };
+ const point_prog = fn(make_vs_point(desc.vid, desc.iid));
+ const tri_prog = fn(make_vs_tri(desc.vid, desc.iid));
+
+ // -
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+ for (let i = 0; i <= 2; i++) {
+ gl.disableVertexAttribArray(i);
+ gl.vertexAttribPointer(i, 4, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(i, 0);
+ }
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
+ let loc = desc.vid.loc;
+ if (loc != -1) {
+ gl.enableVertexAttribArray(loc);
+ gl.vertexAttribIPointer(loc, 1, gl.INT, 0, 0);
+ };
+
+ if (desc.separate_vbufs) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbuf2);
+ }
+ loc = desc.iid.loc;
+ if (loc != -1) {
+ gl.enableVertexAttribArray(loc);
+ gl.vertexAttribIPointer(loc, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(loc, 1);
+ };
+
+ {
+ const err = gl.getError();
+ if (err) throw err; // Broken init.
+ }
+
+ // -
+
+ fn = (eval_str, expected_arr) => {
+ if (ERRATA.IGNORE_GL_INSTANCE_ID) {
+ if (desc.iid.name == 'gl_InstanceID') {
+ expected_arr = expected_arr.map(x => x);
+ expected_arr[2] = 0;
+ }
+ }
+
+ debug('');
+ //debug(`${eval_str} -> [${expected_arr.join(', ')}]`);
+ eval(eval_str);
+
+ const err = gl.getError();
+ if (err) throw err; // Broken subtest.
+
+ wtu.checkCanvas(gl, expected_arr, eval_str);
+ }
+
+ gl.useProgram(point_prog);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArrays(gl.POINTS, 0, 0)`, [0, 0, 0, 0]);
+ fn(`gl.drawArrays(gl.POINTS, 0, 1)`, [255, 0, 0, 255]);
+ fn(`gl.drawArrays(gl.POINTS, 0, 2)`, [255, 1, 0, 255]);
+ if (ERRATA.FIRST_ADDS_TO_INSTANCE) {
+ fn(`gl.drawArrays(gl.POINTS, 100, 2)`, [255, 100+2-1, 100, 255]);
+ } else {
+ fn(`gl.drawArrays(gl.POINTS, 100, 2)`, [255, 100+2-1, 0, 255]);
+ }
+ fn(`gl.drawArrays(gl.POINTS, 0, 255)`, [255, 254, 0, 255]);
+ fn(`gl.drawArrays(gl.POINTS, 0, 256)`, [255, 255, 0, 255]);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 0, 1)`, [0, 0, 0, 0]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 1, 0)`, [0, 0, 0, 0]);
+
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 1, 1)`, [255, 0, 0, 255]);
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 2, 1)`, [255, 1, 0, 255]);
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 1, 2)`, [255, 0, 1, 255]);
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 2, 2)`, [255, 1, 1, 255]);
+ if (ERRATA.FIRST_ADDS_TO_INSTANCE) {
+ fn(`gl.drawArraysInstanced(gl.POINTS, 100, 2, 2)`, [255, 100+2-1, 101, 255]);
+ } else {
+ fn(`gl.drawArraysInstanced(gl.POINTS, 100, 2, 2)`, [255, 100+2-1, 1, 255]);
+ }
+ fn(`gl.drawArraysInstanced(gl.POINTS, 0, 255, 255)`, [255, 254, 254, 255]);
+
+ // -
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElements(gl.POINTS, 0, gl.UNSIGNED_INT, 4*0)`, [0, 0, 0, 0]);
+ fn(`gl.drawElements(gl.POINTS, 1, gl.UNSIGNED_INT, 4*0)`, [255, 10+0, 0, 255]);
+ fn(`gl.drawElements(gl.POINTS, 2, gl.UNSIGNED_INT, 4*0)`, [255, 10+1, 0, 255]);
+ fn(`gl.drawElements(gl.POINTS, 2, gl.UNSIGNED_INT, 4*100)`, [255, 100+10+1, 0, 255]);
+ fn(`gl.drawElements(gl.POINTS, 245, gl.UNSIGNED_INT, 4*0)`, [255, 10+244, 0, 255]);
+ fn(`gl.drawElements(gl.POINTS, 246, gl.UNSIGNED_INT, 4*0)`, [255, 10+245, 0, 255]);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 0, gl.UNSIGNED_INT, 4*0, 1)`, [0, 0, 0, 0]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 1, gl.UNSIGNED_INT, 4*0, 0)`, [0, 0, 0, 0]);
+
+ fn(`gl.drawElementsInstanced(gl.POINTS, 1, gl.UNSIGNED_INT, 4*0, 1)`, [255, 10+0, 0, 255]);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 2, gl.UNSIGNED_INT, 4*0, 1)`, [255, 10+1, 0, 255]);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 1, gl.UNSIGNED_INT, 4*0, 2)`, [255, 10+0, 1, 255]);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 2, gl.UNSIGNED_INT, 4*0, 2)`, [255, 10+1, 1, 255]);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 2, gl.UNSIGNED_INT, 4*100, 2)`, [255, 100+10+1, 1, 255]);
+ fn(`gl.drawElementsInstanced(gl.POINTS, 245, gl.UNSIGNED_INT, 4*0, 255)`, [255, 10+244, 254, 255]);
+
+ // -
+
+ gl.useProgram(tri_prog);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArrays(gl.TRIANGLES, 0, 0*3)`, [0, 0, 0, 0]);
+ fn(`gl.drawArrays(gl.TRIANGLES, 0, 1*3)`, [255, 1*3-1, 0, 255]);
+ fn(`gl.drawArrays(gl.TRIANGLES, 0, 2*3)`, [255, 2*3-1, 0, 255]);
+ if (ERRATA.FIRST_ADDS_TO_INSTANCE) {
+ fn(`gl.drawArrays(gl.TRIANGLES, 90, 2*3)`, [255, 90+2*3-1, 90, 255]);
+ } else {
+ fn(`gl.drawArrays(gl.TRIANGLES, 90, 2*3)`, [255, 90+2*3-1, 0, 255]);
+ }
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 0, 1)`, [0, 0, 0, 0]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 1*3, 0)`, [0, 0, 0, 0]);
+
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 1*3, 1)`, [255, 1*3-1, 0, 255]);
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 2*3, 1)`, [255, 2*3-1, 0, 255]);
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 1*3, 2)`, [255, 1*3-1, 1, 255]);
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 0, 2*3, 2)`, [255, 2*3-1, 1, 255]);
+ if (ERRATA.FIRST_ADDS_TO_INSTANCE) {
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 90, 2*3, 2)`, [255, 90+2*3-1, 91, 255]);
+ } else {
+ fn(`gl.drawArraysInstanced(gl.TRIANGLES, 90, 2*3, 2)`, [255, 90+2*3-1, 1, 255]);
+ }
+
+ // -
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElements(gl.TRIANGLES, 0*3, gl.UNSIGNED_INT, 4*0)`, [0, 0, 0, 0]);
+ fn(`gl.drawElements(gl.TRIANGLES, 1*3, gl.UNSIGNED_INT, 4*0)`, [255, 10+1*3-1, 0, 255]);
+ fn(`gl.drawElements(gl.TRIANGLES, 2*3, gl.UNSIGNED_INT, 4*0)`, [255, 10+2*3-1, 0, 255]);
+ fn(`gl.drawElements(gl.TRIANGLES, 2*3, gl.UNSIGNED_INT, 4*100)`, [255, 100+10+2*3-1, 0, 255]);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 0*3, gl.UNSIGNED_INT, 4*0, 1)`, [0, 0, 0, 0]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 1*3, gl.UNSIGNED_INT, 4*0, 0)`, [0, 0, 0, 0]);
+
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 1*3, gl.UNSIGNED_INT, 4*0, 1)`, [255, 10+1*3-1, 0, 255]);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 2*3, gl.UNSIGNED_INT, 4*0, 1)`, [255, 10+2*3-1, 0, 255]);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 1*3, gl.UNSIGNED_INT, 4*0, 2)`, [255, 10+1*3-1, 1, 255]);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 2*3, gl.UNSIGNED_INT, 4*0, 2)`, [255, 10+2*3-1, 1, 255]);
+ fn(`gl.drawElementsInstanced(gl.TRIANGLES, 2*3, gl.UNSIGNED_INT, 4*100, 2)`, [255, 100+10+2*3-1, 1, 255]);
+ }
+
+ finishTest();
+})();
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/canvas-resizing-with-pbo-bound.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/canvas-resizing-with-pbo-bound.html
new file mode 100644
index 0000000000..713c88f515
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/canvas-resizing-with-pbo-bound.html
@@ -0,0 +1,111 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 Resizing With PBO Bound Test</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas1" style="width: 256px; height: 256px;"> </canvas>
+<canvas id="canvas2" style="width: 256px; height: 256px;"> </canvas>
+<div id="console"></div>
+<script>
+"use strict";
+
+description("Verifies that resizing the canvas (recreating the backing framebuffer) works correctly while a PBO is bound.");
+
+debug("");
+debug("Regression test for Chromium <a href='https://bugs.chromium.org/p/chromium/issues/detail?id=644572'>Issue 644572</a>");
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas;
+var largeSize = 256;
+var smallSize = 128;
+var currentSize;
+var gl;
+var numFrames = 0;
+var testNumber = 0;
+var pbo;
+
+function nextTest() {
+ ++testNumber;
+ numFrames = 0;
+ currentSize = largeSize;
+ if (testNumber > 2) {
+ finishTest();
+ return;
+ }
+
+ canvas = document.getElementById("canvas" + testNumber);
+ canvas.width = currentSize;
+ canvas.height = currentSize;
+ var usePreserveDrawingBuffer = (testNumber == 1) ? true : false;
+ debug("Testing preserveDrawingBuffer = " + usePreserveDrawingBuffer);
+ gl = wtu.create3DContext(canvas, { preserveDrawingBuffer: usePreserveDrawingBuffer }, 2);
+
+ if (!gl) {
+ testFailed("context does not exist");
+
+ wtu.requestAnimFrame(nextTest);
+ } else {
+ testPassed("context exists");
+
+ gl.clearColor(0, 1, 0, 1);
+
+ pbo = gl.createBuffer();
+ gl.bindBuffer(gl.PIXEL_UNPACK_BUFFER, pbo);
+
+ wtu.requestAnimFrame(render);
+ }
+}
+
+function render() {
+ if (++numFrames < 4) {
+ if (currentSize == largeSize) {
+ canvas.height = smallSize;
+ currentSize = smallSize;
+ } else {
+ canvas.height = largeSize;
+ currentSize = largeSize;
+ }
+ }
+
+ gl.viewport(0, 0, largeSize, currentSize);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Check the four corners
+ var green = [ 0, 255, 0, 255 ];
+ var inset = 3;
+ wtu.checkCanvasRect(gl, inset, inset, 1, 1, green, "lower left should be green", 1);
+ wtu.checkCanvasRect(gl, largeSize - inset, inset, 1, 1, green, "lower right should be green", 1);
+ wtu.checkCanvasRect(gl, inset, currentSize - inset, 1, 1, green, "upper left should be green", 1);
+ wtu.checkCanvasRect(gl, largeSize - inset, currentSize - inset, 1, 1, green, "upper right should be green", 1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No GL error");
+ if (gl.getParameter(gl.PIXEL_UNPACK_BUFFER_BINDING) != pbo) {
+ testFailed("Pixel unpack buffer binding was lost");
+ }
+
+ if (numFrames < 4) {
+ wtu.requestAnimFrame(render);
+ } else {
+ wtu.requestAnimFrame(nextTest);
+ }
+}
+
+wtu.requestAnimFrame(nextTest);
+
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-func-buffer-type-match.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-func-buffer-type-match.html
new file mode 100644
index 0000000000..8054d74df2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-func-buffer-type-match.html
@@ -0,0 +1,145 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test clear and clearBuffer functions have to match fbo's buffer format</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="20" height="20"> </canvas>
+<script>
+"use strict";
+description("This tests the WebGL2 specific constraint that clear or clearBuffer* functions have to be compatible with fbo's buffer format");
+
+var setupRenderbuffer = function(attachment, format) {
+ var renderbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, format, canvas.width, canvas.height);
+ return renderbuffer;
+}
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, undefined, 2);
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ debug("");
+ debug("Signed integer buffer");
+
+ var colorbuffer = setupRenderbuffer(gl.COLOR_ATTACHMENT0, gl.RGBA8);
+ var colorbuffer1 = setupRenderbuffer(gl.COLOR_ATTACHMENT1, gl.RGBA32I);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clear and INT buffer");
+
+ gl.clearBufferfv(gl.COLOR, 1, new Float32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferfv and INT buffer");
+
+ gl.clearBufferuiv(gl.COLOR, 1, new Uint32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferuiv and INT buffer");
+
+ debug("Set up draw buffer so INT buffer is set to NONE");
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clear and INT buffer is NONE");
+
+ gl.clearBufferfv(gl.COLOR, 1, new Float32Array(4));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBufferfv and INT buffer is NONE");
+
+ gl.clearBufferuiv(gl.COLOR, 0, new Uint32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferuiv and float buffer");
+
+ debug("");
+ debug("Unsigned integer buffer");
+
+ colorbuffer = setupRenderbuffer(gl.COLOR_ATTACHMENT0, gl.RGBA32UI);
+ colorbuffer1 = setupRenderbuffer(gl.COLOR_ATTACHMENT1, gl.RGBA8);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clear and UINT buffer");
+
+ gl.clearBufferfv(gl.COLOR, 0, new Float32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferfv and UINT buffer");
+
+ gl.clearBufferiv(gl.COLOR, 0, new Int32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferiv and UINT buffer");
+
+ debug("Set up draw buffer so INT buffer is set to NONE");
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clear and UINT buffer is NONE");
+
+ gl.clearBufferfv(gl.COLOR, 0, new Float32Array(4));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBufferfv and UINT buffer is NONE");
+
+ gl.clearBufferiv(gl.COLOR, 1, new Int32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferiv and float buffer");
+
+ debug("");
+ debug("Float buffer");
+
+ colorbuffer = setupRenderbuffer(gl.COLOR_ATTACHMENT0, gl.RGBA8);
+ var numAttachments = 1;
+ var ext = gl.getExtension("EXT_color_buffer_float");
+ var bufferType = "float buffer";
+ if (ext) {
+ debug("EXT_color_buffer_float is available: testing RGBA8 + RGBA32F");
+ colorbuffer1 = setupRenderbuffer(gl.COLOR_ATTACHMENT1, gl.RGBA32F);
+ ++numAttachments;
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ } else {
+ debug("EXT_color_buffer_float is unavailable: testing RGBA8");
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ bufferType = "RGBA8 buffer";
+ }
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clear and " + bufferType);
+
+ for (var ii = 0; ii < numAttachments; ++ii) {
+ gl.clearBufferfv(gl.COLOR, ii, new Float32Array(4));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBufferfv and " + bufferType);
+
+ gl.clearBufferiv(gl.COLOR, ii, new Int32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferiv and " + bufferType);
+
+ gl.clearBufferuiv(gl.COLOR, ii, new Uint32Array(4));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "clearBufferuiv and " + bufferType);
+ }
+
+ gl.deleteFramebuffer(fb);
+ gl.deleteRenderbuffer(colorbuffer);
+ gl.deleteRenderbuffer(colorbuffer1);
+}
+
+debug("");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-srgb-color-buffer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-srgb-color-buffer.html
new file mode 100644
index 0000000000..b88518635e
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clear-srgb-color-buffer.html
@@ -0,0 +1,89 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Clear sRGB Color Buffer</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of clearing srgb color buffer.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+var tex = gl.createTexture();
+var fbo = gl.createFramebuffer();
+var size = 8;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ // Create a srgb color buffer
+ init();
+
+ clear_srgb_color_buffer(0);
+ clear_srgb_color_buffer(1);
+}
+
+function init() {
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ } else {
+ testPassed("framebuffer complete!");
+ }
+}
+
+function clear_srgb_color_buffer(iter) {
+ debug("");
+ debug("Clear sRGB color buffer through glClear or glClearBufferfv");
+
+ var color = [0x33, 0x88, 0xbb, 0xff];
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+
+ if (iter == 0) {
+ gl.clearColor(color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ } else {
+ var data = new Float32Array([color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255]);
+ gl.clearBufferfv(gl.COLOR, 0, data);
+ }
+
+ var color_ref = wtu.linearToSRGB(color);
+ var tolerance = 3;
+ var msg = "";
+ wtu.checkCanvasRect(gl, 0, 0, size, size, color_ref, msg, tolerance);
+}
+
+gl.bindTexture(gl.TEXTURE_2D, null);
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+gl.deleteTexture(tex);
+gl.deleteFramebuffer(fbo);
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-and-draw.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-and-draw.html
new file mode 100644
index 0000000000..051066c8ea
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-and-draw.html
@@ -0,0 +1,216 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test clearBuffer with drawing</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body onload="runTest()">
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="64" height="64" style="position:fixed;left:0;top:0"> </canvas>
+<script>
+"use strict";
+description("This tests the operation of clearBuffer followed by a draw call.");
+
+debug("Verifies that these combined with preserveDrawingBuffer's implicit clears work properly together.");
+debug("Regression test for <a href='http://crbug.com/828262'>Chromium bug 828262</a>.");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl;
+var testIndex = 0;
+var iterations = 0;
+var maxIterations = 10;
+var prog;
+var tests;
+var stage = 0;
+var blackUint8 = [0, 0, 0, 255];
+var redFloat = [1.0, 0.0, 0.0, 0.0];
+var redUint8 = [255, 0, 0, 255];
+var greenFloat = [0.0, 1.0, 0.0, 0.0];
+var greenUint8 = [0, 255, 0, 255];
+
+function verifyOnePixel(kind, x, y, readFormat, readType, arrayType, expectedColor) {
+ var buffer = new arrayType(4);
+ gl.readPixels(Math.floor(x), Math.floor(y), 1, 1, readFormat, readType, buffer);
+ if (buffer[0] == expectedColor[0] &&
+ buffer[1] == expectedColor[1] &&
+ buffer[2] == expectedColor[2] &&
+ buffer[3] == expectedColor[3]) {
+ testPassed(kind + " succeeded");
+ } else {
+ testFailed(kind + " failed. Expected: " + expectedColor + ", got: " + buffer);
+ }
+}
+
+function testClearBufferAndDraw(test) {
+ gl.stencilFunc(gl.EQUAL, 0, 0xFF);
+ test['clear']();
+ wtu.setFloatDrawColor(gl, greenFloat);
+ wtu.drawUnitQuad(gl);
+ // Back buffer has no alpha channel.
+ let readFormat = gl.RGBA;
+ let readType = gl.UNSIGNED_BYTE;
+ if (stage == 2) {
+ verifyOnePixel("Clearing outside scissor",63, 63, readFormat, readType, Uint8Array, blackUint8);
+ verifyOnePixel("Drawing outside scissor", 40, 40, readFormat, readType, Uint8Array, blackUint8);
+ }
+ verifyOnePixel("Clearing", 0, 0, readFormat, readType, Uint8Array, test['bgColor']);
+ verifyOnePixel("Drawing", 32, 32, readFormat, readType, Uint8Array, test['drawColor']);
+}
+
+function runNextTest() {
+ if (testIndex >= tests.length) {
+ // Restore after the last clearBufferiv test
+ gl.enable(gl.DEPTH_TEST);
+ if (stage == 0) {
+ debug('');
+ debug('Enabling full-canvas scissor');
+ gl.enable(gl.SCISSOR_TEST);
+ } else if (stage == 1) {
+ debug('');
+ debug('Limiting scissor rect');
+ gl.scissor(0, 0, 33, 33);
+ } else if (stage == 2) {
+ finishTest();
+ return;
+ }
+ testIndex = 0;
+ stage++;
+ }
+
+
+ let test = tests[testIndex];
+ if (iterations == 0) {
+ debug('');
+ debug('Testing: ' + test['desc'])
+ }
+ testClearBufferAndDraw(test);
+
+ if (++iterations == maxIterations) {
+ iterations = 0;
+ ++testIndex;
+
+ // Clear to yellow between the tests to ensure that
+ // subsequent tests do not rely on past results.
+ gl.clearColor(1.0, 1.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ }
+
+ wtu.waitForComposite(runNextTest);
+}
+
+function runTest() {
+ gl = wtu.create3DContext(canvas, { alpha: false, stencil: true }, 2);
+
+ if (!gl) {
+ testFailed("context does not exist");
+ return;
+ } else {
+ testPassed("context exists");
+ }
+
+ prog = wtu.setupColorQuad(gl, 0, { scale: 0.5 });
+
+ tests = [
+ {
+ desc: 'Implicit clear',
+ clear: function() {},
+ bgColor: blackUint8,
+ // The implicit clear clears depth to 1.0, and since the quad is
+ // drawn at a depth of 0.0, it's always discarded.
+ drawColor: blackUint8,
+ },
+ {
+ desc: 'clearBufferfi only',
+ clear: function() {
+ gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 0.0, 1);
+ gl.stencilFunc(gl.EQUAL, 1, 0xFF);
+ },
+ bgColor: blackUint8,
+ drawColor: greenUint8,
+ },
+ {
+ desc: 'clearBufferfv only',
+ clear: function() {
+ gl.clearBufferfv(gl.DEPTH, 0, [0.0]);
+ },
+ bgColor: blackUint8,
+ drawColor: greenUint8,
+ },
+ {
+ desc: 'clearBufferfv and clear',
+ clear: function() {
+ gl.clearBufferfv(gl.COLOR, 0, redFloat);
+ gl.clearDepth(0.0);
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+ },
+ bgColor: redUint8,
+ drawColor: greenUint8,
+ },
+ {
+ desc: 'clearBufferfv (no-op) and clear',
+ clear: function() {
+ gl.clearBufferfv(gl.COLOR, 1, greenFloat);
+ gl.clearDepth(0.0);
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+ },
+ bgColor: blackUint8,
+ drawColor: greenUint8,
+ },
+ {
+ desc: 'clearBuffer{fv} and {fi}',
+ clear: function() {
+ gl.clearBufferfv(gl.COLOR, 0, redFloat);
+ gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 0.0, 2);
+ gl.stencilFunc(gl.EQUAL, 2, 0xFF);
+ },
+ bgColor: redUint8,
+ drawColor: greenUint8,
+ },
+ {
+ desc: 'clearBufferiv only',
+ clear: function() {
+ gl.disable(gl.DEPTH_TEST);
+ gl.clearBufferiv(gl.STENCIL, 0, [3]);
+ gl.stencilFunc(gl.EQUAL, 3, 0xFF);
+ },
+ bgColor: blackUint8,
+ drawColor: greenUint8,
+ },
+ ];
+
+ // Clear canvas to something other than black to start.
+ gl.clearColor(0.0, 0.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.enable(gl.DEPTH_TEST);
+ // Unreal Engine's depth test is reversed from the
+ // default. Including the clear of the depth buffer in this test
+ // case highlights the rendering error more clearly, since neither
+ // the background nor any rendered object show up.
+ gl.depthFunc(gl.GEQUAL);
+
+ gl.enable(gl.STENCIL_TEST);
+
+ // Must run in a requestAnimationFrame loop to provoke implicit
+ // clears of the canvas.
+ wtu.waitForComposite(runNextTest);
+}
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-sub-source.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-sub-source.html
new file mode 100644
index 0000000000..616d2bda4c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbuffer-sub-source.html
@@ -0,0 +1,110 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test clearBuffer functions with optional srcOffset argument</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="20" height="20"> </canvas>
+<script>
+"use strict";
+description("This tests clearBuffer* functions with optional srcOffset argument");
+
+function verifyOnePixel(readFormat, readType, arrayType, expectedColor) {
+ var buffer = new arrayType(4);
+ gl.readPixels(0, 0, 1, 1, readFormat, readType, buffer);
+ if (buffer[0] == expectedColor[0] &&
+ buffer[1] == expectedColor[1] &&
+ buffer[2] == expectedColor[2] &&
+ buffer[3] == expectedColor[3]) {
+ testPassed("clearBuffer sets the renderbuffer with the correct data");
+ } else {
+ testFailed("clearBuffer fails to work. Expected: " + expectedColor + ", got: " + buffer);
+ }
+}
+
+function testClearBuffer(func, format, arrayType, readFormat, readType) {
+ debug("");
+ debug("Testing " + func);
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ var renderbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, format, canvas.width, canvas.height);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ var srcData = new arrayType([1, 2, 3, 4, 5, 6]);
+ gl[func](gl.COLOR, 0, srcData);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBuffer with no srcOffset should succeed");
+ verifyOnePixel(readFormat, readType, arrayType, [1,2,3,4]);
+
+ gl[func](gl.COLOR, 0, srcData, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBuffer with srcOffset = 0 should succeed");
+ verifyOnePixel(readFormat, readType, arrayType, [1,2,3,4]);
+
+ gl[func](gl.COLOR, 0, srcData, 2);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBuffer with srcOffset = 2 should succeed");
+ verifyOnePixel(readFormat, readType, arrayType, [3,4, 5, 6]);
+
+ gl[func](gl.COLOR, 0, srcData, 4);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "clearBuffer with srcOffset = 4 should fail: out of bounds");
+
+ gl.deleteFramebuffer(fb);
+ gl.deleteRenderbuffer(renderbuffer);
+}
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, undefined, 2);
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ var testCases = [
+ {
+ func: "clearBufferiv", format: gl.RGBA32I, arrayType: Int32Array,
+ readFormat: gl.RGBA_INTEGER, readType: gl.INT,
+ },
+ {
+ func: "clearBufferuiv", format: gl.RGBA32UI, arrayType: Uint32Array,
+ readFormat: gl.RGBA_INTEGER, readType: gl.UNSIGNED_INT,
+ },
+ {
+ func: "clearBufferfv", format: gl.RGBA32F, arrayType: Float32Array,
+ readFormat: gl.RGBA, readType: gl.FLOAT,
+ extension: "EXT_color_buffer_float",
+ },
+ ];
+
+ for (var tt = 0; tt < testCases.length; ++tt) {
+ var test = testCases[tt];
+ if (test.extension && !gl.getExtension(test.extension))
+ continue;
+ testClearBuffer(test.func, test.format, test.arrayType, test.readFormat, test.readType);
+ }
+}
+
+debug("");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbufferfv-with-alpha-false.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbufferfv-with-alpha-false.html
new file mode 100644
index 0000000000..a50b6f8e2f
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clearbufferfv-with-alpha-false.html
@@ -0,0 +1,80 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test clearBufferfv with alpha:false canvas</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="20" height="20"> </canvas>
+<script>
+"use strict";
+description("This tests the operation of clearBufferfv with the back buffer of an alpha:false canvas.");
+
+function verifyOnePixel(readFormat, readType, arrayType, expectedColor) {
+ var buffer = new arrayType(4);
+ gl.readPixels(0, 0, 1, 1, readFormat, readType, buffer);
+ if (buffer[0] == expectedColor[0] &&
+ buffer[1] == expectedColor[1] &&
+ buffer[2] == expectedColor[2] &&
+ buffer[3] == expectedColor[3]) {
+ testPassed("clearBufferfv set the color buffer to the correct value");
+ } else {
+ testFailed("clearBufferfv failed to work. Expected: " + expectedColor + ", got: " + buffer);
+ }
+}
+
+function testClearBuffer(func, format, arrayType, readFormat, readType, readArrayType) {
+ debug("");
+ debug("Testing " + func);
+
+ var srcData = new arrayType([0, 1, 0, 0]);
+ gl[func](gl.COLOR, 0, srcData);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBuffer with no srcOffset should succeed");
+ // Back buffer has no alpha channel
+ verifyOnePixel(readFormat, readType, Uint8Array, [0, 255, 0, 255]);
+}
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, { alpha:false }, 2);
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ var testCases = [
+ {
+ func: "clearBufferfv", format: gl.RGBA, arrayType: Float32Array,
+ readFormat: gl.RGBA, readType: gl.UNSIGNED_BYTE, readArrayType: Uint8Array,
+ },
+ ];
+
+ for (var tt = 0; tt < testCases.length; ++tt) {
+ var test = testCases[tt];
+ if (test.extension && !gl.getExtension(test.extension))
+ continue;
+ testClearBuffer(test.func, test.format, test.arrayType,
+ test.readFormat, test.readType, test.readArrayType);
+ }
+}
+
+debug("");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clipping-wide-points.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clipping-wide-points.html
new file mode 100644
index 0000000000..ab2457a6a6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/clipping-wide-points.html
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Clipping wide points test</title>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="testbed" width="1" height="1"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+var contextVersion = 2;
+</script>
+<script src="../../js/tests/clipping-wide-points.js"></script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/depth-stencil-feedback-loop.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/depth-stencil-feedback-loop.html
new file mode 100644
index 0000000000..e7678017ee
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/depth-stencil-feedback-loop.html
@@ -0,0 +1,165 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Rendering and Sampling Feedback Loop Tests for Depth/Stencil Buffer</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+in highp vec4 aPosition;
+in vec2 aTexCoord;
+out vec2 texCoord;
+void main() {
+ gl_Position = aPosition;
+ texCoord = aTexCoord;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+uniform sampler2D tex;
+in vec2 texCoord;
+out vec4 oColor;
+void main() {
+ oColor = texture(tex, texCoord);
+}
+</script>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of rendering to the same texture where it samples from.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+var width = 8;
+var height = 8;
+var tex0;
+var tex1;
+var tex2;
+var fbo;
+var program;
+var positionLoc;
+var texCoordLoc;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ init();
+ detect_depth_stencil_feedback_loop();
+ deinit();
+}
+
+function init() {
+ // Setup program
+ program = wtu.setupProgram(gl, ['vshader', 'fshader'], ['aPosition', 'aTexCoord'], [0, 1]);
+ positionLoc = gl.getAttribLocation(program, "aPosition");
+ texCoordLoc = gl.getAttribLocation(program, "aTexCoord");
+ if (!program || positionLoc < 0 || texCoordLoc < 0) {
+ testFailed("Set up program failed");
+ return;
+ }
+ testPassed("Set up program succeeded");
+
+ wtu.setupUnitQuad(gl, 0, 1);
+ gl.viewport(0, 0, width, height);
+
+ var texLoc = gl.getUniformLocation(program, "tex");
+ gl.uniform1i(texLoc, 0);
+
+ // Create textures and allocate storage
+ tex0 = gl.createTexture();
+ tex1 = gl.createTexture();
+ tex2 = gl.createTexture();
+ wtu.fillTexture(gl, tex0, width, height, [0x0, 0xff, 0x0, 0xff], 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.RGBA);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ wtu.fillTexture(gl, tex1, width, height, [0x80], 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, gl.DEPTH_COMPONENT16);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
+ wtu.fillTexture(gl, tex2, width, height, [0x40], 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, gl.DEPTH24_STENCIL8);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_COMPARE_MODE, gl.COMPARE_REF_TO_TEXTURE);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Succeed to create textures.");
+
+ fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex0, 0);
+}
+
+function detect_depth_stencil_feedback_loop() {
+ // Test rendering and sampling feedback loop for depth buffer
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, tex1, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+ gl.enable(gl.DEPTH_TEST);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test samples from a image. The same image is used as depth buffer during rendering.");
+
+ gl.depthMask(gl.FALSE);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test samples from a image. The same image is used as depth buffer. A feedback loop is formed regardless of the status of depth mask.");
+
+ gl.depthMask(gl.TRUE);
+ gl.disable(gl.DEPTH_TEST);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test samples from a image. The same image is used as depth buffer. A feedback loop is formed regardless of whether the depth test is enabled.");
+
+ // Test rendering and sampling feedback loop for stencil buffer
+ gl.bindTexture(gl.TEXTURE_2D, tex2);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, null, 0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.TEXTURE_2D, tex2, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ gl.enable(gl.STENCIL_TEST);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test samples from a image. The same image is used as stencil buffer during rendering.");
+
+ gl.stencilMask(0x0);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test sampls from a image. The same image is used as stencil buffer. A feedback loop is formed regardless of the status of stencil mask.");
+
+ gl.stencilMask(0xffff);
+ gl.disable(gl.STENCIL_TEST);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "The test samples from a image. The same image is used as stencil buffer. A feedback loop is formed regardless of whether the stencil test is enabled.");
+}
+
+function deinit() {
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteTexture(tex0);
+ gl.deleteTexture(tex1);
+ gl.deleteTexture(tex2);
+ gl.deleteFramebuffer(fbo);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-dirty-state-bug.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-dirty-state-bug.html
new file mode 100644
index 0000000000..2b54d4c255
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-dirty-state-bug.html
@@ -0,0 +1,111 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Draw Buffers Dirty State Bug Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="64" height="64"> </canvas>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+in vec4 a_position;
+void main() {
+ gl_Position = a_position;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(1, 0, 0, 1);
+}
+</script>
+<script>
+"use strict";
+description("This test verifies a bug in draw buffers dirty state management in Chrome (crbug.com/678153).");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runTest();
+
+ debug("");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function runTest() {
+ debug("");
+
+ var program = wtu.setupProgram(gl, ["vshader", "fshader"], ["a_position"]);
+ wtu.setupUnitQuad(gl);
+
+ var width = 2, height = 2;
+
+ var colorTex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, colorTex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ var depthTex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, depthTex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT24, width, height, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setup should cause no GL errors");
+
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Clear and draw should cause no GL errors");
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [255, 0, 0, 255], "should be red");
+
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTex, 0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);
+
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Clear and draw should cause no GL errors");
+
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, null, 0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0);
+
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [255, 0, 0, 255], "should be red");
+
+ gl.clearColor(0.0, 1.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ // Previously in Chrome, switching around the attachments on a framebuffer
+ // affected a DrawBuffers cache that was maintained properly during draw
+ // calls, but not clears.
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 0, 255], "should be green");
+
+ gl.deleteProgram(program);
+ gl.deleteFramebuffer(fb);
+ gl.deleteTexture(colorTex);
+ gl.deleteTexture(depthTex);
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-driver-hang.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-driver-hang.html
new file mode 100644
index 0000000000..70a8360d6e
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-driver-hang.html
@@ -0,0 +1,187 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Draw Buffers Driver Hang Conformance Test</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="64" height="64"> </canvas>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+in vec4 a_position;
+void main() {
+ gl_Position = a_position;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(1, 0, 0, 1);
+}
+</script>
+<script>
+"use strict";
+description("This is a regression test for a driver bug causing a hang in the driver and thereby the browser (crbug.com/696187).")
+
+debug("Thanks to Andre Weissflog (@FlohOfWoe / @floooh) for this test.");
+debug("If the bug exists, this test doesn't fail or time out per the harness; the browser basically hangs.");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, { depth: true, stencil: true, alpha: false }, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runTest();
+}
+
+function runTest() {
+ // create a global VAO
+ let vao = gl.createVertexArray();
+ gl.bindVertexArray(vao);
+
+ // create a 3 MSAA 'offscreen render targets', each consisting of:
+ // - 1 color texture which will hold the MSAA resolve result
+ // - 1 MSAA color renderbuffer
+ // plus one depth-stencil renderbuffer
+ let tex0 = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex0);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
+ 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.RGBA8, 200, 200, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ let c_rb0 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, c_rb0);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 200, 200);
+ let ds_rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, ds_rb);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.DEPTH24_STENCIL8, 200, 200);
+
+ // 2nd offscreen render target
+ let tex1 = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
+ 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.RGBA8, 200, 200, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ let c_rb1 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, c_rb1);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 200, 200);
+
+ // 3rd offscreen render target
+ let tex2 = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex2);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAX_LEVEL, 0);
+ 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.RGBA8, 200, 200, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ let c_rb2 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, c_rb2);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 200, 200);
+
+ // an MRT framebuffer with the 3 MSAA renderbuffers and MSAA depth/stencil attachments
+ let mrt_fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, mrt_fb);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, c_rb0);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.RENDERBUFFER, c_rb1);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.RENDERBUFFER, c_rb2);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, ds_rb);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, ds_rb);
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+
+ // 3 'MSAA resolve framebuffers' which are the target for the MSAA-resolve-blit,
+ // with the 3 color textures as color attachments
+ let res_fb0 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, res_fb0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex0, 0);
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ let res_fb1 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, res_fb1);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, tex1, 0);
+ gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ let res_fb2 = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, res_fb2);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, tex2, 0);
+ let frameNumber = 0;
+
+ function draw() {
+ // draw one frame
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, 1024, 768);
+
+ //--- BEGIN: comment out the block begin BEGIN/END to make the demo run
+ // clear the 3 MSAA offscreen color renderbuffers and depth/stencil renderbuffer
+ gl.bindFramebuffer(gl.FRAMEBUFFER, mrt_fb);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2]);
+ gl.viewport(0, 0, 200, 200);
+ gl.depthMask(true);
+ gl.clearBufferfv(gl.COLOR, 0, [0.25,0.0,0.0,1.0]);
+ gl.clearBufferfv(gl.COLOR, 1, [0.0,0.25,0.0,1.0]);
+ gl.clearBufferfv(gl.COLOR, 2, [0.0,0.0,0.25,1.0]);
+ gl.clearBufferfi(gl.DEPTH_STENCIL, 0, 1.0, 0);
+
+ // the MSAA resolve operation
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, mrt_fb);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, res_fb0);
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ gl.blitFramebuffer(0, 0, 200, 200, 0, 0, 200, 200, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, res_fb1);
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ gl.blitFramebuffer(0, 0, 200, 200, 0, 0, 200, 200, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, res_fb2);
+ gl.readBuffer(gl.COLOR_ATTACHMENT2);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ gl.blitFramebuffer(0, 0, 200, 200, 0, 0, 200, 200, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+//--- END
+
+ // bind and clear the default framebuffer
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, 1024, 768);
+ gl.clearColor(0.5, 0.5, 0.5, 1.0);
+ gl.clearDepth(1.0);
+ gl.clearStencil(0);
+ gl.clear(gl.COLOR_BUFFER_BIT|gl.STENCIL_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
+
+ if (++frameNumber < 10) {
+ requestAnimationFrame(draw);
+ } else {
+ finishTest();
+ }
+ }
+
+ // Start the rendering loop
+ draw();
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-sparse-output-locations.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-sparse-output-locations.html
new file mode 100644
index 0000000000..4c679df6a2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers-sparse-output-locations.html
@@ -0,0 +1,108 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Conformance Tests: Verify drawBuffers sparse output locations</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="1" height="1" style="width: 4px; height: 4px;"> </canvas>
+<div id="console"></div>
+
+<script id="vs" type="x-shader/x-vertex">#version 300 es
+void main() {
+ gl_PointSize = 100.0;
+ gl_Position = vec4(0, 0, 0, 1);
+}
+</script>
+
+<script id="fs" type="x-shader/x-fragment">#version 300 es
+// fragment shader only outputs to attachments 1 and 3
+precision highp float;
+layout(location = 1) out vec4 output1;
+layout(location = 3) out vec4 output2;
+void main()
+{
+ output1 = vec4(0.0, 1.0, 0.0, 1.0);
+ output2 = vec4(0.0, 0.0, 1.0, 1.0);
+}
+
+</script>
+<script>
+"use strict";
+description("This test verifies sparse output locations of fragment shaders render correctly");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ runTests();
+}
+
+function testAttachment(attachment, expected) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT0 + attachment);
+ wtu.checkCanvas(gl, expected, `check COLOR_ATTACHMENT${attachment}`, 1);
+}
+
+function runTests() {
+ var program = wtu.setupProgram(gl, ["vs", "fs"]);
+ if (!program) {
+ testFailed("Set up program failed");
+ return;
+ }
+ gl.useProgram(program);
+
+ // create a framebuffer with 4 1x1 pixel color attachments
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ for (let i = 0; i < 4; ++i) {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, 0);
+ }
+
+ // draw only to the 1st and 3rd attachments
+ gl.drawBuffers([
+ gl.NONE,
+ gl.COLOR_ATTACHMENT1,
+ gl.NONE,
+ gl.COLOR_ATTACHMENT3,
+ ]);
+
+ // draw
+ gl.drawArrays(gl.POINTS, 0, 1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No GL error from set up");
+
+ // check we got the correct values
+ testAttachment(0, [0, 0, 0, 0]);
+ testAttachment(1, [0, 255, 0, 255]);
+ testAttachment(2, [0, 0, 0, 0]);
+ testAttachment(3, [0, 0, 255, 255]);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No GL error from testing");
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers.html
new file mode 100644
index 0000000000..0e2ecb8f47
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-buffers.html
@@ -0,0 +1,598 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Draw Buffers Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script src="../../js/tests/webgl-draw-buffers-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="64" height="64"> </canvas>
+<div id="console"></div>
+<script id="vshaderESSL3" type="x-shader/x-vertex">#version 300 es
+in vec4 a_position;
+void main() {
+ gl_Position = a_position;
+}
+</script>
+<script id="vshaderESSL1" type="x-shader/x-vertex">
+attribute vec4 a_position;
+void main() {
+ gl_Position = a_position;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+uniform vec4 u_colors[$(numDrawingBuffers)];
+
+// Only one out variable - does not need explicit output layout (ESSL 3 section 4.3.8.2)
+out vec4 my_FragData[$(numDrawingBuffers)];
+void main() {
+$(assignUColorsToFragData)
+}
+</script>
+<script id="fshaderDiscard" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+uniform vec4 u_colors[$(numDrawingBuffers)];
+uniform float u_zero;
+
+// Only one out variable - does not need explicit output layout (ESSL 3 section 4.3.8.2)
+out vec4 my_FragData[$(numDrawingBuffers)];
+void main() {
+$(assignUColorsToFragData)
+ if (u_zero < 1.0) {
+ discard;
+ }
+}
+</script>
+<script id="fshaderRed" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(1, 0, 0, 1);
+}
+</script>
+<script id="fshaderBlueESSL1" type="x-shader/x-fragment">
+precision mediump float;
+
+void main() {
+ gl_FragColor = vec4(0, 0, 1, 1);
+}
+</script>
+<script id="fshaderBuiltInConstEnabled" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = (gl_MaxDrawBuffers == $(numDrawingBuffers)) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
+}
+</script>
+<script>
+"use strict";
+description("This test verifies the functionality of Multiple Render Targets.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+var drawBuffersUtils;
+let fb;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ drawBuffersUtils = WebGLDrawBuffersUtils(gl);
+
+ if (testParameters()) {
+ runShadersTest();
+ runAttachmentTest();
+ runDrawTests();
+ }
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function createDrawBuffersProgram(scriptId, sub) {
+ var fsource = wtu.getScript(scriptId);
+ fsource = wtu.replaceParams(fsource, sub);
+ return wtu.setupProgram(gl, ["vshaderESSL3", fsource], ["a_position"], undefined, true);
+}
+
+function runShadersTest() {
+ debug("");
+ debug("test shaders");
+
+ var sub = {numDrawingBuffers: gl.getParameter(gl.MAX_DRAW_BUFFERS)};
+ var program = createDrawBuffersProgram("fshaderBuiltInConstEnabled", sub);
+ wtu.setupUnitQuad(gl);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+ gl.deleteProgram(program);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function makeArray(size, value) {
+ var array = []
+ for (var ii = 0; ii < size; ++ii) {
+ array.push(value);
+ }
+ return array;
+}
+
+function runAttachmentTest() {
+ debug("");
+ debug("test attachment enabled");
+
+ var maxDrawingBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS);
+ var maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
+
+ var tex = gl.createTexture();
+ fb = gl.createFramebuffer();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments, gl.TEXTURE_2D, tex, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "should not be able to attach pass the max attachment point: gl.COLOR_ATTACHMENT0 + " + maxColorAttachments);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + maxColorAttachments - 1, gl.TEXTURE_2D, tex, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to attach to the max attachment point: gl.COLOR_ATTACHMENT0 + " + (maxColorAttachments - 1));
+ gl.drawBuffers(makeArray(maxDrawingBuffers, gl.NONE));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffers with array NONE of size " + maxColorAttachments);
+ var bufs = drawBuffersUtils.makeColorAttachmentArray(maxDrawingBuffers);
+ gl.drawBuffers(bufs);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffers with array attachments of size " + maxColorAttachments);
+ bufs[0] = gl.NONE;
+ gl.drawBuffers(bufs);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffers with mixed array attachments of size " + maxColorAttachments);
+ if (maxDrawingBuffers > 1) {
+ bufs[0] = gl.COLOR_ATTACHMENT1;
+ bufs[1] = gl.COLOR_ATTACHMENT0;
+ gl.drawBuffers(bufs);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be able to call drawBuffers with out of order attachments of size " + maxColorAttachments);
+ var bufs = drawBuffersUtils.makeColorAttachmentArray(Math.floor(maxDrawingBuffers / 2));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be able to call drawBuffers with short array of attachments of size " + bufs.length);
+ }
+
+ gl.deleteFramebuffer(fb);
+ gl.deleteTexture(tex);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+
+ debug("Testing drawBuffers and getParameter with bindFramebuffer, without drawing.");
+ fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.COLOR_ATTACHMENT0");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0+1)", "gl.NONE");
+ wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawBuffers([gl.NONE])");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.BACK");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.NONE");
+
+ wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawBuffers([gl.NONE,gl.COLOR_ATTACHMENT0+1])");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.NONE");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0+1)", "gl.COLOR_ATTACHMENT0+1");
+
+ wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.drawBuffers([gl.COLOR_ATTACHMENT0,gl.COLOR_ATTACHMENT0+1])");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.COLOR_ATTACHMENT0");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0+1)", "gl.COLOR_ATTACHMENT0+1");
+
+ wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.deleteFramebuffer(fb)");
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0)", "gl.BACK");
+}
+
+function makeColorByIndex(index) {
+ var low = (index - 1) % 15 + 1;
+ var high = (index - 1) / 15;
+
+ var zeroOrOne = function(v) {
+ return v ? 1 : 0;
+ };
+
+ var oneOrTwo = function(v) {
+ return v ? 2 : 1;
+ }
+
+ var makeComponent = function(b0, b1, b2) {
+ return Math.floor(255 * zeroOrOne(b0) / oneOrTwo(b1) / oneOrTwo(b2));
+ };
+ return [
+ makeComponent(low & (1 << 0), high & (1 << 0), high & (1 << 4)),
+ makeComponent(low & (1 << 1), high & (1 << 1), high & (1 << 5)),
+ makeComponent(low & (1 << 2), high & (1 << 2), high & (1 << 6)),
+ makeComponent(low & (1 << 3), high & (1 << 3), high & (1 << 7)),
+ ];
+}
+
+function runDrawTests() {
+ debug("");
+ debug("--------- draw tests -----------");
+ var fb = gl.createFramebuffer();
+ var fb2 = gl.createFramebuffer();
+ var halfFB1 = gl.createFramebuffer();
+ var halfFB2 = gl.createFramebuffer();
+ var endsFB = gl.createFramebuffer();
+ var middleFB = gl.createFramebuffer();
+
+ var maxDrawingBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS);
+ var maxUsable = drawBuffersUtils.getMaxUsableColorAttachments();
+ var half = Math.floor(maxUsable / 2);
+ var bufs = drawBuffersUtils.makeColorAttachmentArray(maxUsable);
+ var nones = makeArray(maxUsable, gl.NONE);
+
+ [fb, fb2, halfFB1, halfFB2, endsFB, middleFB].forEach(function(fbo) {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.drawBuffers(bufs);
+ });
+
+ var checkProgram = wtu.setupTexturedQuad(gl);
+ var redProgram = wtu.setupProgram(gl, ["vshaderESSL3", "fshaderRed"], ["a_position"]);
+ var blueProgramESSL1 = wtu.setupProgram(gl, ["vshaderESSL1", "fshaderBlueESSL1"], ["a_position"]);
+
+ var assignCode = [];
+ for (var i = 0; i < maxDrawingBuffers; ++i) {
+ assignCode.push(" my_FragData[" + i + "] = u_colors[" + i + "];");
+ }
+
+ var drawProgram = createDrawBuffersProgram("fshader",
+ {numDrawingBuffers: maxDrawingBuffers, assignUColorsToFragData: assignCode.join("\n")});
+ var width = 64;
+ var height = 64;
+ var attachments = [];
+ // Makes 6 framebuffers.
+ // fb and fb2 have all the attachments.
+ // halfFB1 has the first half of the attachments
+ // halfFB2 has the second half of the attachments
+ // endsFB has the first and last attachments
+ // middleFB has all but the first and last attachments
+ for (var ii = 0; ii < maxUsable; ++ii) {
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ 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.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, ii < half ? halfFB1 : halfFB2);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, (ii == 0 || ii == (maxUsable - 1)) ? endsFB : middleFB);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + ii, gl.TEXTURE_2D, tex, 0);
+ var location = gl.getUniformLocation(drawProgram, "u_colors[" + ii + "]");
+ var color = makeColorByIndex(ii + 1);
+ var floatColor = [color[0] / 255, color[1] / 255, color[2] / 255, color[3] / 255];
+ gl.uniform4fv(location, floatColor);
+ attachments.push({
+ texture: tex,
+ color: color
+ });
+ }
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+
+ var drawAndCheckAttachments = function(testFB, msg, testFn) {
+ debug("test clearing " + msg);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+
+ attachments.forEach(function(attachment, index) {
+ debug("attachment: " + index + " = " + wtu.glEnumToString(gl, gl.getParameter(gl.DRAW_BUFFER0 + index)) +
+ ", " + wtu.glEnumToString(gl, gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + index, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)));
+ });
+
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ debug("framebuffer not complete");
+ debug("");
+ return;
+ }
+
+ // Clear all the attachments
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ //drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ // return [0, 0, 0, 0];
+ //});
+ //debug("--");
+
+ // Clear some attachments using testFB
+ gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return testFn(attachment, index) ? [0, 255, 0, 255] : [0, 0, 0, 0];
+ });
+
+ debug("test drawing to " + msg);
+
+ // Draw to some attachments using testFB
+ gl.useProgram(drawProgram);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, testFB);
+ wtu.drawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return testFn(attachment, index) ? attachment.color : [0, 0, 0, 0];
+ });
+ };
+
+ gl.useProgram(drawProgram);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ gl.drawBuffers(bufs);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+
+ wtu.drawUnitQuad(gl);
+
+ debug("test that each texture got the correct color.");
+
+ drawBuffersUtils.checkAttachmentsForColor(attachments);
+
+ debug("test clearing clears all the textures");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [0, 255, 0, 255]);
+
+ debug("test that NONE draws nothing");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(nones);
+ gl.useProgram(redProgram);
+ wtu.clearAndDrawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [0, 255, 0, 255]);
+
+ // GLES3 spec section 3.9.2 Shader Outputs
+ debug("test that gl_FragColor only writes to color number zero");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.useProgram(blueProgramESSL1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Active draw buffers with missing frag outputs.");
+ gl.enable(gl.RASTERIZER_DISCARD);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors when RASTERIZER_DISCARD is enabled.");
+ gl.disable(gl.RASTERIZER_DISCARD);
+ gl.colorMask(false, false, false, false);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors when all 4 channels of color mask are disabled.");
+ gl.colorMask(false, true, false, false);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "partially diabled color mask shall have no impact.");
+ gl.colorMask(true, true, true, true);
+
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ wtu.drawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return (index == 0) ? [0, 0, 255, 255] : [0, 255, 0, 255];
+ });
+
+ // If there is only a single output, the location defaults to zero if not specified.
+ // See GLSL ES Spec 3.00.4, Section 4.3.8.2, Output Layout Qualifiers.
+ debug("test that an OpenGL ES Shading Language 3.00 shader with a single output color defaults to color number zero");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.useProgram(redProgram);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Active draw buffers with missing frag outputs.");
+
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ wtu.drawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return (index == 0) ? [255, 0, 0, 255] : [0, 255, 0, 255];
+ });
+
+ if (maxUsable > 1) {
+ // Prepare for following tests by clearing all attachments to red.
+ debug("prepare by clearing all attachments to red");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+ var bufs1 = drawBuffersUtils.makeColorAttachmentArray(maxUsable);
+ var bufs2 = drawBuffersUtils.makeColorAttachmentArray(maxUsable);
+ for (var ii = 0; ii < maxUsable; ++ii) {
+ if (ii < half) {
+ bufs1[ii] = gl.NONE;
+ } else {
+ bufs2[ii] = gl.NONE;
+ }
+ }
+
+ debug("test setting first half to NONE and clearing");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs1);
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return index < half ? [255, 0, 0, 255] : [0, 255, 0, 255];
+ });
+
+ debug("test setting first half to NONE and drawing");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.useProgram(drawProgram);
+ wtu.drawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return index < half ? [255, 0, 0, 255] : attachment.color;
+ });
+
+ debug("test setting second half to NONE and clearing");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawBuffers(bufs2);
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return index < half ? [0, 0, 255, 255] : [255, 0, 0, 255];
+ });
+
+ debug("test setting second half to NONE and drawing");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.useProgram(drawProgram);
+ wtu.drawUnitQuad(gl);
+
+ drawBuffersUtils.checkAttachmentsForColorFn(attachments, function(attachment, index) {
+ return index < half ? attachment.color : [255, 0, 0, 255];
+ });
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB1);
+ gl.drawBuffers(bufs);
+ drawAndCheckAttachments(
+ halfFB1, "framebuffer that only has first half of attachments",
+ function(attachment, index) {
+ return index < half;
+ });
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, halfFB2);
+ gl.drawBuffers(bufs);
+ drawAndCheckAttachments(
+ halfFB2, "framebuffer that only has second half of attachments",
+ function(attachment, index) {
+ return index >= half;
+ });
+
+ if (maxUsable > 2) {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, endsFB);
+ gl.drawBuffers(bufs);
+ drawAndCheckAttachments(
+ endsFB, "framebuffer that only has first and last attachments",
+ function(attachment, index) {
+ return index == 0 || index == (maxUsable - 1);
+ });
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, middleFB);
+ gl.drawBuffers(bufs);
+ drawAndCheckAttachments(
+ middleFB,
+ "framebuffer that has all but the first and last attachments",
+ function(attachment, index) {
+ return index != 0 && index != (maxUsable - 1);
+ });
+ }
+ }
+
+ debug("test switching between fbos keeps drawbuffer state");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ gl.drawBuffers(nones);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ gl.useProgram(drawProgram);
+ wtu.drawUnitQuad(gl);
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [255, 0, 0, 255]);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.useProgram(drawProgram);
+ wtu.drawUnitQuad(gl);
+ drawBuffersUtils.checkAttachmentsForColor(attachments);
+
+ debug("test that none of the attachments are written in case the fragment shader discards");
+ var discardProgram = createDrawBuffersProgram("fshaderDiscard",
+ {numDrawingBuffers: maxDrawingBuffers, assignUColorsToFragData: assignCode.join("\n")});
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.drawBuffers(bufs);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.useProgram(discardProgram);
+ wtu.drawUnitQuad(gl);
+ drawBuffersUtils.checkAttachmentsForColor(attachments, [0, 0, 0, 0]);
+
+ debug("test queries");
+ debug("check framebuffer with all attachments on");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ for (var ii = 0; ii < maxUsable; ++ii) {
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0 + " + ii + ")", "gl.COLOR_ATTACHMENT0 + " + ii);
+ }
+
+ debug("check framebuffer with all attachments off");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ for (var ii = 0; ii < maxUsable; ++ii) {
+ shouldBe("gl.getParameter(gl.DRAW_BUFFER0 + " + ii + ")", "gl.NONE");
+ }
+
+ // WebGL generates FRAMEBUFFER_INCOMPLETE_DIMENSIONS when attached images have different sizes.
+ // This behavior differs from GLES 3.
+ debug("");
+ debug("test attachment size mis-match");
+ gl.bindTexture(gl.TEXTURE_2D, attachments[0].texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width * 2, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb2);
+ shouldBeTrue("gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS");
+
+ // TODO: Rendering when framebuffer attachments have mismatched size should be tested, maybe in a separate test.
+
+ gl.deleteFramebuffer(fb);
+ gl.deleteFramebuffer(fb2);
+ gl.deleteFramebuffer(halfFB1);
+ gl.deleteFramebuffer(halfFB2);
+ attachments.forEach(function(attachment) {
+ gl.deleteTexture(attachment.texture);
+ });
+ gl.deleteProgram(checkProgram);
+ gl.deleteProgram(redProgram);
+ gl.deleteProgram(drawProgram);
+}
+
+function testParameters() {
+ debug("");
+ debug("check that MAX_DRAW_BUFFERS and MAX_COLOR_ATTACHMENTS are valid");
+ var maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS);
+ var maxColorAttachments = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
+ debug("MAX_DRAW_BUFFERS = " + maxDrawBuffers);
+ debug("MAX_COLOR_ATTACHMENTS = " + maxColorAttachments);
+ if (maxDrawBuffers != maxColorAttachments) {
+ testFailed("MAX_DRAW_BUFFERS and MAX_COLOR_ATTACHMENTS should be the same");
+ return false;
+ }
+ if (maxDrawBuffers < 4) {
+ testFailed("MAX_DRAW_BUFFERS should be at least 4");
+ return false;
+ }
+ return true;
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-with-integer-texture-base-level.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-with-integer-texture-base-level.html
new file mode 100644
index 0000000000..e2f9fd13c2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/draw-with-integer-texture-base-level.html
@@ -0,0 +1,65 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Draw With Integer Texture Base Level Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script src="../../js/tests/tex-image-and-sub-image-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="24" height="24"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+var canvas;
+var wtu = WebGLTestUtils;
+canvas = document.getElementById("example");
+var gl = wtu.create3DContext(canvas, undefined, 2);
+var tiu = TexImageUtils;
+
+// Both Chrome and Firefox fail on this test on NVIDIA Windows, see crbug.com/679639.
+function testDrawIntegerTextureBaseLevel()
+{
+ description("This test verifies the functionality of rendering with integer texture non-zero base level.");
+
+ var green = [0, 255, 0, 255];
+
+ var width = 16;
+ var height = 16;
+ canvas.width = width;
+ canvas.height = height;
+ gl.viewport(0, 0, width, height);
+
+ var texture = gl.createTexture();
+ var level = 1;
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, level);
+ wtu.fillTexture(gl, texture, width, height, green, level, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, gl.RGBA8UI);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ var program = tiu.setupTexturedQuad(gl, "RGBA8UI");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ wtu.checkCanvas(gl, green);
+}
+
+testDrawIntegerTextureBaseLevel();
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/element-index-uint.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/element-index-uint.html
new file mode 100644
index 0000000000..123254f4cd
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/element-index-uint.html
@@ -0,0 +1,432 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Uint element indices Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+
+<script id="vs" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec4 vColor;
+varying vec4 color;
+void main() {
+ gl_Position = vPosition;
+ color = vColor;
+}
+</script>
+<script id="fs" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 color;
+void main() {
+ gl_FragColor = color;
+}
+</script>
+<script id="vsCheckOutOfBounds" type="x-shader/x-vertex">
+ precision mediump float;
+ attribute vec2 position;
+ attribute vec4 vecRandom;
+ varying vec4 v_color;
+
+ // Per the spec, each component can either contain existing contents
+ // of the buffer or 0.
+ bool testFloatComponent(float component) {
+ return (component == 0.2 || component == 0.0);
+ }
+ // The last component is additionally allowed to be 1.0.
+ bool testLastFloatComponent(float component) {
+ return testFloatComponent(component) || component == 1.0;
+ }
+
+ void main() {
+ if (testFloatComponent(vecRandom.x) &&
+ testFloatComponent(vecRandom.y) &&
+ testFloatComponent(vecRandom.z) &&
+ testLastFloatComponent(vecRandom.w)) {
+ v_color = vec4(0.0, 1.0, 0.0, 1.0); // green -- Out of range
+ } else {
+ v_color = vec4(1.0, 0.0, 0.0, 1.0); // red -- Unexpected value
+ }
+ gl_Position = vec4(position, 0.0, 1.0);
+ }
+</script>
+
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the Uint element indices.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = null;
+var canvas = null;
+
+// Test both STATIC_DRAW and DYNAMIC_DRAW as a regression test
+// for a bug in ANGLE which has since been fixed.
+for (var ii = 0; ii < 2; ++ii) {
+ canvas = document.createElement("canvas");
+ canvas.width = 50;
+ canvas.height = 50;
+
+ gl = wtu.create3DContext(canvas, null, 2);
+
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ } else {
+ testPassed("WebGL context exists");
+
+ var drawType = (ii == 0) ? gl.STATIC_DRAW : gl.DYNAMIC_DRAW;
+ debug("Testing " + ((ii == 0) ? "STATIC_DRAW" : "DYNAMIC_DRAW"));
+
+ runDrawTests(drawType);
+
+ // These tests are tweaked duplicates of the buffers/index-validation* tests
+ // using unsigned int indices to ensure that behavior remains consistent
+ runIndexValidationTests(drawType);
+ runIndexOutOfRangeTests(drawType);
+ runResizedBufferTests(drawType);
+ runCrashWithBufferSubDataTests(drawType);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+ }
+}
+
+function runDrawTests(drawType) {
+ debug("Test that draws with unsigned integer indices produce the expected results");
+
+ canvas.width = 50; canvas.height = 50;
+ gl.viewport(0, 0, canvas.width, canvas.height);
+
+ var program = wtu.setupNoTexCoordTextureProgram(gl);
+
+ function setupDraw(s) {
+ // Create a vertex buffer that cannot be fully indexed via shorts
+ var quadArrayLen = 65537 * 3;
+ var quadArray = new Float32Array(quadArrayLen);
+
+ // Leave all but the last 4 values zero-ed out
+ var idx = quadArrayLen - 12;
+
+ // Initialized the last 4 values to a quad
+ quadArray[idx++] = 1.0 * s;
+ quadArray[idx++] = 1.0 * s;
+ quadArray[idx++] = 0.0;
+
+ quadArray[idx++] = -1.0 * s;
+ quadArray[idx++] = 1.0 * s;
+ quadArray[idx++] = 0.0;
+
+ quadArray[idx++] = -1.0 * s;
+ quadArray[idx++] = -1.0 * s;
+ quadArray[idx++] = 0.0;
+
+ quadArray[idx++] = 1.0 * s;
+ quadArray[idx++] = -1.0 * s;
+ quadArray[idx++] = 0.0;
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, quadArray, drawType);
+
+ // Create an unsigned int index buffer that indexes the last 4 vertices
+ var baseIndex = (quadArrayLen / 3) - 4;
+
+ var indexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array([
+ baseIndex + 0,
+ baseIndex + 1,
+ baseIndex + 2,
+ baseIndex + 2,
+ baseIndex + 3,
+ baseIndex + 0]), drawType);
+
+ var opt_positionLocation = 0;
+ gl.enableVertexAttribArray(opt_positionLocation);
+ gl.vertexAttribPointer(opt_positionLocation, 3, gl.FLOAT, false, 0, 0);
+ };
+ function readLocation(x, y) {
+ var pixels = new Uint8Array(1 * 1 * 4);
+ gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ return pixels;
+ };
+ function testPixel(blockList, allowList) {
+ function testList(list, expected) {
+ for (var n = 0; n < list.length; n++) {
+ var l = list[n];
+ var x = -Math.floor(l * canvas.width / 2) + canvas.width / 2;
+ var y = -Math.floor(l * canvas.height / 2) + canvas.height / 2;
+ var source = readLocation(x, y);
+ if (Math.abs(source[0] - expected) > 2) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return testList(blockList, 0) && testList(allowList, 255);
+ };
+ function verifyDraw(drawNumber, s) {
+ gl.clearColor(1.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
+
+ var blockList = [];
+ var allowList = [];
+ var points = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0];
+ for (var n = 0; n < points.length; n++) {
+ if (points[n] <= s) {
+ blockList.push(points[n]);
+ } else {
+ allowList.push(points[n]);
+ }
+ }
+ if (testPixel(blockList, allowList)) {
+ testPassed("Draw " + drawNumber + " passed pixel test");
+ } else {
+ testFailed("Draw " + drawNumber + " failed pixel test");
+ }
+ };
+
+ setupDraw(0.5);
+ verifyDraw(0, 0.5);
+}
+
+function runIndexValidationTests(drawType) {
+ description("Tests that index validation verifies the correct number of indices");
+
+ function sizeInBytes(type) {
+ switch (type) {
+ case gl.BYTE:
+ case gl.UNSIGNED_BYTE:
+ return 1;
+ case gl.SHORT:
+ case gl.UNSIGNED_SHORT:
+ return 2;
+ case gl.INT:
+ case gl.UNSIGNED_INT:
+ case gl.FLOAT:
+ return 4;
+ default:
+ throw "unknown type";
+ }
+ }
+
+ var program = wtu.loadStandardProgram(gl);
+
+ // 3 vertices => 1 triangle, interleaved data
+ var dataComplete = new Float32Array([0, 0, 0, 1,
+ 0, 0, 1,
+ 1, 0, 0, 1,
+ 0, 0, 1,
+ 1, 1, 1, 1,
+ 0, 0, 1]);
+ var dataIncomplete = new Float32Array([0, 0, 0, 1,
+ 0, 0, 1,
+ 1, 0, 0, 1,
+ 0, 0, 1,
+ 1, 1, 1, 1]);
+ var indices = new Uint32Array([0, 1, 2]);
+
+ debug("Testing with valid indices");
+
+ var bufferComplete = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferComplete);
+ gl.bufferData(gl.ARRAY_BUFFER, dataComplete, drawType);
+ var elements = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+ gl.useProgram(program);
+ var vertexLoc = gl.getAttribLocation(program, "a_vertex");
+ var normalLoc = gl.getAttribLocation(program, "a_normal");
+ gl.vertexAttribPointer(vertexLoc, 4, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 0);
+ gl.enableVertexAttribArray(vertexLoc);
+ gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 4 * sizeInBytes(gl.FLOAT));
+ gl.enableVertexAttribArray(normalLoc);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ var bufferIncomplete = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferIncomplete);
+ gl.bufferData(gl.ARRAY_BUFFER, dataIncomplete, drawType);
+ gl.vertexAttribPointer(vertexLoc, 4, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 0);
+ gl.enableVertexAttribArray(vertexLoc);
+ gl.disableVertexAttribArray(normalLoc);
+ debug("Enable vertices, valid");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("Test with enabled attribute that does not belong to current program");
+
+ gl.disableVertexAttribArray(normalLoc);
+ var extraLoc = Math.max(vertexLoc, normalLoc) + 1;
+ gl.enableVertexAttribArray(extraLoc);
+ debug("Enable an extra attribute with null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+ debug("Enable an extra attribute with insufficient data buffer");
+ gl.vertexAttribPointer(extraLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), 4 * sizeInBytes(gl.FLOAT));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+ debug("Pass large negative index to vertexAttribPointer");
+ gl.vertexAttribPointer(normalLoc, 3, gl.FLOAT, false, 7 * sizeInBytes(gl.FLOAT), -2000000000 * sizeInBytes(gl.FLOAT));
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ shouldBeUndefined('gl.drawElements(gl.TRIANGLES, 3, gl.UNSIGNED_INT, 0)');
+}
+
+function runIndexOutOfRangeTests(drawType) {
+ debug("Testing with out-of-range indices");
+
+ var bufferPos = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferPos);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 1.0, 1.0,
+ -1.0, 1.0,
+ -1.0, -1.0,
+ 1.0, -1.0]), drawType);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ var bufferIncomplete = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, bufferIncomplete);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]), drawType);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
+
+ var glProgram = wtu.setupProgram(gl, ["vsCheckOutOfBounds", wtu.simpleVertexColorFragmentShader], ["position", "vecRandom"]);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Shader and buffer setup successfully");
+
+ var indices = new Uint32Array([0, 1, 2, 0, 2, 3]);
+ var elements = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elements);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+
+ gl.clearColor(0.0, 0.0, 1.0, 1.0); // Start with blue to indicate no pixels touched.
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+
+
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
+ var error = gl.getError();
+ if (error === gl.INVALID_OPERATION) {
+ testPassed("drawElements flagged INVALID_OPERATION, which is valid so long as all canvas pixels were not touched.");
+ wtu.checkCanvas(gl, [0, 0, 255, 255]);
+ } else if (error === gl.NO_ERROR) {
+ testPassed("drawElements flagged NO_ERROR, which is valid so long as all canvas pixels are green.");
+ wtu.checkCanvas(gl, [0, 255, 0, 255]);
+ } else {
+ testFailed("Invalid error flagged by drawElements. Should be INVALID_OPERATION or NO_ERROR");
+ }
+
+ debug("Test that client data is always copied during bufferData and bufferSubData calls");
+
+ indices[5] = 1;
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_INT, 0);
+ var error = gl.getError();
+ if (error === gl.INVALID_OPERATION) {
+ testPassed("drawElements flagged INVALID_OPERATION, which is valid so long as all canvas pixels were not touched.");
+ wtu.checkCanvas(gl, [0, 0, 255, 255]);
+ } else if (error === gl.NO_ERROR) {
+ testPassed("drawElements flagged NO_ERROR, which is valid so long as all canvas pixels are green.");
+ wtu.checkCanvas(gl, [0, 255, 0, 255]);
+ } else {
+ testFailed("Invalid error flagged by drawElements. Should be INVALID_OPERATION or NO_ERROR");
+ }
+}
+
+function runResizedBufferTests(drawType) {
+ debug("Test that updating the size of a vertex buffer is properly noticed by the WebGL implementation.");
+
+ var program = wtu.setupProgram(gl, ["vs", "fs"], ["vPosition", "vColor"]);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after initialization");
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
+ [-1,1,0, 1,1,0, -1,-1,0,
+ -1,-1,0, 1,1,0, 1,-1,0]), drawType);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after vertex setup");
+
+ var texCoordObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(
+ [0,0, 1,0, 0,1,
+ 0,1, 1,0, 1,1]), drawType);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after texture coord setup");
+
+ // Now resize these buffers because we want to change what we're drawing.
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1,1,0, 1,1,0, -1,-1,0, 1,-1,0,
+ -1,1,0, 1,1,0, -1,-1,0, 1,-1,0]), drawType);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after vertex redefinition");
+ gl.bindBuffer(gl.ARRAY_BUFFER, texCoordObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([
+ 255, 0, 0, 255,
+ 255, 0, 0, 255,
+ 255, 0, 0, 255,
+ 255, 0, 0, 255,
+ 0, 255, 0, 255,
+ 0, 255, 0, 255,
+ 0, 255, 0, 255,
+ 0, 255, 0, 255]), drawType);
+ gl.vertexAttribPointer(1, 4, gl.UNSIGNED_BYTE, false, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after texture coordinate / color redefinition");
+
+ var numQuads = 2;
+ var indices = new Uint32Array(numQuads * 6);
+ for (var ii = 0; ii < numQuads; ++ii) {
+ var offset = ii * 6;
+ var quad = (ii == (numQuads - 1)) ? 4 : 0;
+ indices[offset + 0] = quad + 0;
+ indices[offset + 1] = quad + 1;
+ indices[offset + 2] = quad + 2;
+ indices[offset + 3] = quad + 2;
+ indices[offset + 4] = quad + 1;
+ indices[offset + 5] = quad + 3;
+ }
+ var indexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexObject);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, drawType);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after setting up indices");
+ gl.drawElements(gl.TRIANGLES, numQuads * 6, gl.UNSIGNED_INT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after drawing");
+}
+
+function runCrashWithBufferSubDataTests(drawType) {
+ debug('Verifies that the index validation code which is within bufferSubData does not crash.')
+
+ var elementBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementBuffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, 256, drawType);
+ var data = new Uint32Array(127);
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 64, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "after attempting to update a buffer outside of the allocated bounds");
+ testPassed("bufferSubData, when buffer object was initialized with null, did not crash");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-draw-framebuffer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-draw-framebuffer.html
new file mode 100644
index 0000000000..1c0709e0b8
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-draw-framebuffer.html
@@ -0,0 +1,74 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test draw framebuffer completeness when an incomplete framebuffer is bound to read framebuffer</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+// This exposes a bug in Chrome 67: If the read framebuffer is incomplete, then draw can fail even if the draw framebuffer is complete.
+// http://anglebug.com/2737
+
+"use strict";
+description();
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext(undefined, undefined, 2);
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ var incompleteFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, incompleteFb);
+ var incompleteTex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, incompleteTex);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, incompleteTex, 0);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ var testProgram = wtu.setupSimpleColorProgram(gl, 0);
+
+ // If this is changed to gl.FRAMEBUFFER, the rendering succeeds on Chrome 67.
+ var drawFbTarget = gl.DRAW_FRAMEBUFFER;
+
+ var completeFb = gl.createFramebuffer();
+ gl.bindFramebuffer(drawFbTarget, completeFb);
+ var completeTex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, completeTex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 128, 128);
+ gl.framebufferTexture2D(drawFbTarget, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, completeTex, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error after setup");
+
+ shouldBe('gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER)', 'gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT');
+ shouldBe('gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.viewport(0, 0, 128, 128);
+ gl.uniform4f(gl.getUniformLocation(testProgram, 'u_color'), 0, 1, 0, 1);
+ wtu.drawUnitQuad(gl);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error after draw");
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, completeFb);
+ wtu.checkCanvasRect(gl, 0, 0, 128, 128, [0, 255, 0, 255], 'should be green', 2);
+}
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-unaffected.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-unaffected.html
new file mode 100644
index 0000000000..74b6106419
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-completeness-unaffected.html
@@ -0,0 +1,89 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Test drawBuffers, readBuffer, and fbo completeness</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="20" height="20"> </canvas>
+<script>
+// In MacOSX, if drawBuffers() and readBuffer() both select an attachment with no image attached,
+// fbo becomes incomplete. However, drawBuffers() and readBuffer() should not affect fbo completeness.
+
+"use strict";
+description("This tests drawBuffers, readBuffer, and fbo completeness");
+
+var setupRenderbuffer = function(attachment) {
+ var renderbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, canvas.width, canvas.height);
+ return renderbuffer;
+}
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, undefined, 2);
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ debug("fbo with two color images attached should be complete");
+ var colorbuffer = setupRenderbuffer(gl.COLOR_ATTACHMENT0);
+ var colorbuffer1 = setupRenderbuffer(gl.COLOR_ATTACHMENT1);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("drawBuffers selects ATTACHMENT1, fbo should be complete");
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("remove image attached to ATTACHMENT1, fbo should be complete");
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.RENDERBUFFER, null);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("set read buffer to ATTACHMENT1, fbo should be complete");
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("drawBuffers selects ATTACHMENT0, fbo should be complete");
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("drawBuffers selects ATTACHMENT1, fbo should be complete");
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ debug("set read buffer to ATTACHMENT0, fbo should be complete");
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.deleteFramebuffer(fb);
+ gl.deleteRenderbuffer(colorbuffer);
+ gl.deleteRenderbuffer(colorbuffer1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no error after setup and clear render buffer");
+}
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-mismatched-attachment-targets.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-mismatched-attachment-targets.html
new file mode 100644
index 0000000000..a8a28d6444
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-mismatched-attachment-targets.html
@@ -0,0 +1,162 @@
+<!--
+Copyright (c) 2020 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL2 can render to framebuffer attachments with different targets</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+void main(void) {
+ gl_Position = vec4(-0.5, -0.5, 0, 1);
+ gl_PointSize = 1.0;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 outColor;
+void main() {
+ outColor = vec4(0, 1, 0, 1);
+}
+</script>
+</head>
+<body>
+<canvas id="example" width="1", height="1"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+debug("");
+
+description("Test framebuffer attachments with different targets");
+
+const wtu = WebGLTestUtils;
+const gl = wtu.create3DContext("example", undefined, 2);
+
+if (!gl) {
+ testFailed("WebGL context creation failed");
+} else {
+ testPassed("WebGL context creation succeeded");
+ runTest();
+}
+
+function newResource(target, mipLevels, format, size) {
+ let ret;
+ switch (target) {
+ case gl.RENDERBUFFER: {
+ ret = gl.createRenderbuffer();
+ ret.mips = [ ret ];
+ for (let i = 1; i < mipLevels; i++) {
+ ret.mips.push(gl.createRenderbuffer());
+ }
+ for (const i in ret.mips) {
+ const rb = ret.mips[i];
+ gl.bindRenderbuffer(target, rb);
+ gl.renderbufferStorage(target, format, size>>i, size>>i);
+ }
+ ret.attach = (attachEnum, mipLevel) => {
+ const rb = ret.mips[mipLevel];
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachEnum, gl.RENDERBUFFER, rb);
+ };
+ break;
+ }
+ case gl.TEXTURE_2D:
+ case gl.TEXTURE_CUBE_MAP: {
+ ret = gl.createTexture();
+ gl.bindTexture(target, ret);
+ gl.texStorage2D(target, mipLevels, format, size, size);
+ let imageTarget = target;
+ if (imageTarget == gl.TEXTURE_CUBE_MAP) {
+ imageTarget = gl.TEXTURE_CUBE_MAP_POSITIVE_X+2; // Deliberately don't choose the first image.
+ }
+ ret.attach = (attachEnum, mipLevel) => {
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, attachEnum, imageTarget, ret, mipLevel);
+ };
+ break;
+ }
+ case gl.TEXTURE_3D:
+ case gl.TEXTURE_2D_ARRAY: {
+ ret = gl.createTexture();
+ gl.bindTexture(target, ret);
+ gl.texStorage3D(target, mipLevels, format, size, size, 1);
+ ret.attach = (attachEnum, mipLevel) => {
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, attachEnum, ret, mipLevel, 0);
+ };
+ break;
+ }
+ default:
+ throw new Error();
+ }
+ ret.target = wtu.glEnumToString(gl, target);
+ ret.format = wtu.glEnumToString(gl, format);
+ return ret;
+}
+
+function runTest() {
+ const MIP_LEVELS = 2;
+ const SIZE = 2;
+
+ gl.clearColor(1, 0, 0, 1);
+
+ const program = wtu.setupProgram(gl, ['vshader','fshader'], [], console.log.bind(console));
+ gl.useProgram(program);
+
+ const colorResList = [
+ newResource(gl.RENDERBUFFER, MIP_LEVELS, gl.RGBA8, SIZE),
+ newResource(gl.TEXTURE_2D, MIP_LEVELS, gl.RGBA8, SIZE),
+ newResource(gl.TEXTURE_CUBE_MAP, MIP_LEVELS, gl.RGBA8, SIZE),
+ newResource(gl.TEXTURE_3D, MIP_LEVELS, gl.RGBA8, SIZE),
+ newResource(gl.TEXTURE_2D_ARRAY, MIP_LEVELS, gl.RGBA8, SIZE),
+ ];
+
+ const depthResList = [
+ newResource(gl.RENDERBUFFER, MIP_LEVELS, gl.DEPTH_COMPONENT16, SIZE),
+ newResource(gl.TEXTURE_2D, MIP_LEVELS, gl.DEPTH_COMPONENT16, SIZE),
+ newResource(gl.TEXTURE_CUBE_MAP, MIP_LEVELS, gl.DEPTH_COMPONENT16, SIZE),
+ //newResource(gl.TEXTURE_3D, MIP_LEVELS, gl.DEPTH_COMPONENT16, SIZE), // Depth formats forbidden for TEXTURE_3D.
+ newResource(gl.TEXTURE_2D_ARRAY, MIP_LEVELS, gl.DEPTH_COMPONENT16, SIZE),
+ ];
+
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ for (const color of colorResList) {
+ for (const depth of depthResList) {
+ debug(`\ncolor: ${color.target}; depth: ${depth.target}`);
+ for (let mipLevel = 0; mipLevel < MIP_LEVELS; mipLevel++) {
+ debug(`mipLevel: ${mipLevel}`);
+ color.attach(gl.COLOR_ATTACHMENT0, mipLevel);
+ depth.attach(gl.DEPTH_ATTACHMENT, mipLevel);
+ const maybeStatus = wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, [gl.FRAMEBUFFER_COMPLETE, gl.FRAMEBUFFER_UNSUPPORTED]);
+ if (!maybeStatus || maybeStatus[0] != gl.FRAMEBUFFER_COMPLETE) {
+ continue;
+ }
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.checkCanvas(gl, [255, 0, 0, 255], `framebuffer layer ${mipLevel} should be cleared red`);
+
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.checkCanvas(gl, [0, 255, 0, 255], `framebuffer layer ${mipLevel} should be drawn green`);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors`);
+ }
+ }
+ }
+
+ // make sure we were not rendering to the canvas.
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null)
+ wtu.checkCanvas(gl, [0, 0, 0, 0], "canvas should be zero");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer-angle-issue.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer-angle-issue.html
new file mode 100644
index 0000000000..1fbdb6bbeb
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer-angle-issue.html
@@ -0,0 +1,90 @@
+<!--
+Copyright (c) 2020 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL2 can render to layers in 3D texture angle issue check</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+void main(void) {
+ gl_Position = vec4(0, 0, 0, 1);
+ gl_PointSize = 1.0;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 outColor;
+void main() {
+ outColor = vec4(0, 1, 0, 1);
+}
+</script>
+</head>
+<body>
+<canvas id="example" width="1", height="1"></canvas>
+<div id="description"></div>
+<a href='https://bugs.chromium.org/p/angleproject/issues/detail?id=4417'>ANGLE issue #4417</a>
+<div id="console"></div>
+<script>
+"use strict";
+debug("");
+
+description("Test that WebGL2 can render to layers in 3D textures");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example", undefined, 2);
+
+if (!gl) {
+ testFailed("WebGL context creation failed");
+} else {
+ testPassed("WebGL context creation succeeded");
+ runTest();
+}
+
+function runTest() {
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_3D, tex);
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
+ gl.texParameteri(gl.TEXTURE_3D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA8, gl.canvas.width, gl.canvas.height, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ for (let i = 0; i < 2; i++) {
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex, 0, i);
+
+ const rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, gl.canvas.width, gl.canvas.height);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ const program = wtu.setupProgram(gl, ['vshader','fshader'], [], console.log.bind(console));
+ gl.useProgram(program);
+
+ gl.drawArrays(gl.POINTS, 0, 1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors`);
+ wtu.checkCanvas(gl, [0, 255, 0, 255], `framebuffer layer ${i} should be green`);
+ }
+
+ // make sure we were not rendering to the canvas.
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null)
+ wtu.checkCanvas(gl, [0, 0, 0, 0], "canvas should be zero");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer.html
new file mode 100644
index 0000000000..c34f2a4143
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-render-to-layer.html
@@ -0,0 +1,440 @@
+<!--
+Copyright (c) 2020 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL2 can render to layers in 3D and 2D_ARRAY textures</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+void main(void) {
+ gl_Position = vec4(0, 0, 0, 1);
+ gl_PointSize = 1.0;
+}
+</script>
+</head>
+<body>
+<canvas id="example" width="100", height="100"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+debug("");
+
+description("Test that WebGL2 can render to layers in 3D and 2D_ARRAY textures");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example", undefined, 2);
+
+if (!gl) {
+ testFailed("WebGL context creation failed");
+} else {
+ testPassed("WebGL context creation succeeded");
+ runTest();
+}
+
+function runTest() {
+ const texWidth = 1;
+ const texHeight = 1;
+ const texDepth = 2;
+
+ function makeFragmentShader(typeInfo) {
+ const src = `#version 300 es
+ precision mediump float;
+ out ${typeInfo.outType} color;
+ void main() {
+ color = ${typeInfo.outValue};
+ }
+ `;
+ return src;
+ }
+
+ const textureInternalFormatInfo = {};
+ {
+ const t = textureInternalFormatInfo;
+ // unsized formats
+ // If understand correctly these 3 unsized formats are not required to be color renderable
+ t[gl.ALPHA] = { textureFormat: gl.ALPHA, colorRenderable: false, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [gl.UNSIGNED_BYTE, gl.HALF_FLOAT, gl.HALF_FLOAT_OES, gl.FLOAT], };
+ t[gl.LUMINANCE] = { textureFormat: gl.LUMINANCE, colorRenderable: false, textureFilterable: true, bytesPerElement: [1, 2, 2, 4], type: [gl.UNSIGNED_BYTE, gl.HALF_FLOAT, gl.HALF_FLOAT_OES, gl.FLOAT], };
+ t[gl.LUMINANCE_ALPHA] = { textureFormat: gl.LUMINANCE_ALPHA, colorRenderable: false, textureFilterable: true, bytesPerElement: [2, 4, 4, 8], type: [gl.UNSIGNED_BYTE, gl.HALF_FLOAT, gl.HALF_FLOAT_OES, gl.FLOAT], };
+
+ t[gl.RGB] = { textureFormat: gl.RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 6, 6, 12, 2], type: [gl.UNSIGNED_BYTE, gl.HALF_FLOAT, gl.HALF_FLOAT_OES, gl.FLOAT, gl.UNSIGNED_SHORT_5_6_5], };
+ t[gl.RGBA] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 8, 8, 16, 2, 2], type: [gl.UNSIGNED_BYTE, gl.HALF_FLOAT, gl.HALF_FLOAT_OES, gl.FLOAT, gl.UNSIGNED_SHORT_4_4_4_4, gl.UNSIGNED_SHORT_5_5_5_1], };
+
+ // sized formats
+ t[gl.R8] = { textureFormat: gl.RED, colorRenderable: true, textureFilterable: true, bytesPerElement: [1], type: [gl.UNSIGNED_BYTE], };
+ t[gl.R8_SNORM] = { textureFormat: gl.RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [1], type: [gl.BYTE], };
+ t[gl.R16F] = { textureFormat: gl.RED, colorRenderable: false, textureFilterable: true, bytesPerElement: [4, 2], type: [gl.FLOAT, gl.HALF_FLOAT], };
+ t[gl.R32F] = { textureFormat: gl.RED, colorRenderable: false, textureFilterable: false, bytesPerElement: [4], type: [gl.FLOAT], };
+ t[gl.R8UI] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [gl.UNSIGNED_BYTE], };
+ t[gl.R8I] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [1], type: [gl.BYTE], };
+ t[gl.R16UI] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [gl.UNSIGNED_SHORT], };
+ t[gl.R16I] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [gl.SHORT], };
+ t[gl.R32UI] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_INT], };
+ t[gl.R32I] = { textureFormat: gl.RED_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.INT], };
+ t[gl.RG8] = { textureFormat: gl.RG, colorRenderable: true, textureFilterable: true, bytesPerElement: [2], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RG8_SNORM] = { textureFormat: gl.RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [2], type: [gl.BYTE], };
+ t[gl.RG16F] = { textureFormat: gl.RG, colorRenderable: false, textureFilterable: true, bytesPerElement: [8, 4], type: [gl.FLOAT, gl.HALF_FLOAT], };
+ t[gl.RG32F] = { textureFormat: gl.RG, colorRenderable: false, textureFilterable: false, bytesPerElement: [8], type: [gl.FLOAT], };
+ t[gl.RG8UI] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RG8I] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [2], type: [gl.BYTE], };
+ t[gl.RG16UI] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_SHORT], };
+ t[gl.RG16I] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.SHORT], };
+ t[gl.RG32UI] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [gl.UNSIGNED_INT], };
+ t[gl.RG32I] = { textureFormat: gl.RG_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [gl.INT], };
+ t[gl.RGB8] = { textureFormat: gl.RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3], type: [gl.UNSIGNED_BYTE], };
+ t[gl.SRGB8] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RGB565] = { textureFormat: gl.RGB, colorRenderable: true, textureFilterable: true, bytesPerElement: [3, 2], type: [gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT_5_6_5], };
+ t[gl.RGB8_SNORM] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [3], type: [gl.BYTE], };
+ t[gl.R11F_G11F_B10F] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [gl.FLOAT, gl.HALF_FLOAT, gl.UNSIGNED_INT_10F_11F_11F_REV], };
+ t[gl.RGB9_E5] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6, 4], type: [gl.FLOAT, gl.HALF_FLOAT, gl.UNSIGNED_INT_5_9_9_9_REV], };
+ t[gl.RGB16F] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: true, bytesPerElement: [12, 6], type: [gl.FLOAT, gl.HALF_FLOAT], };
+ t[gl.RGB32F] = { textureFormat: gl.RGB, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [gl.FLOAT], };
+ t[gl.RGB8UI] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RGB8I] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [3], type: [gl.BYTE], };
+ t[gl.RGB16UI] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [gl.UNSIGNED_SHORT], };
+ t[gl.RGB16I] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [6], type: [gl.SHORT], };
+ t[gl.RGB32UI] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [gl.UNSIGNED_INT], };
+ t[gl.RGB32I] = { textureFormat: gl.RGB_INTEGER, colorRenderable: false, textureFilterable: false, bytesPerElement: [12], type: [gl.INT], };
+ t[gl.RGBA8] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [gl.UNSIGNED_BYTE], };
+ t[gl.SRGB8_ALPHA8] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RGBA8_SNORM] = { textureFormat: gl.RGBA, colorRenderable: false, textureFilterable: true, bytesPerElement: [4], type: [gl.BYTE], };
+ t[gl.RGB5_A1] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2, 4], type: [gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT_5_5_5_1, gl.UNSIGNED_INT_2_10_10_10_REV], };
+ t[gl.RGBA4] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4, 2], type: [gl.UNSIGNED_BYTE, gl.UNSIGNED_SHORT_4_4_4_4], };
+ t[gl.RGB10_A2] = { textureFormat: gl.RGBA, colorRenderable: true, textureFilterable: true, bytesPerElement: [4], type: [gl.UNSIGNED_INT_2_10_10_10_REV], };
+ t[gl.RGBA16F] = { textureFormat: gl.RGBA, colorRenderable: false, textureFilterable: true, bytesPerElement: [16, 8], type: [gl.FLOAT, gl.HALF_FLOAT], };
+ t[gl.RGBA32F] = { textureFormat: gl.RGBA, colorRenderable: false, textureFilterable: false, bytesPerElement: [16], type: [gl.FLOAT], };
+ t[gl.RGBA8UI] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_BYTE], };
+ t[gl.RGBA8I] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.BYTE], };
+ t[gl.RGB10_A2UI] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_INT_2_10_10_10_REV], };
+ t[gl.RGBA16UI] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [gl.UNSIGNED_SHORT], };
+ t[gl.RGBA16I] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [8], type: [gl.SHORT], };
+ t[gl.RGBA32I] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [gl.INT], };
+ t[gl.RGBA32UI] = { textureFormat: gl.RGBA_INTEGER, colorRenderable: true, textureFilterable: false, bytesPerElement: [16], type: [gl.UNSIGNED_INT], };
+
+ // Sized Internal
+ t[gl.DEPTH_COMPONENT16] = { textureFormat: gl.DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [2, 4], type: [gl.UNSIGNED_SHORT, gl.UNSIGNED_INT], };
+ t[gl.DEPTH_COMPONENT24] = { textureFormat: gl.DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_INT], };
+ t[gl.DEPTH_COMPONENT32F] = { textureFormat: gl.DEPTH_COMPONENT, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.FLOAT], };
+ t[gl.DEPTH24_STENCIL8] = { textureFormat: gl.DEPTH_STENCIL, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.UNSIGNED_INT_24_8], };
+ t[gl.DEPTH32F_STENCIL8] = { textureFormat: gl.DEPTH_STENCIL, colorRenderable: true, textureFilterable: false, bytesPerElement: [4], type: [gl.FLOAT_32_UNSIGNED_INT_24_8_REV], };
+
+ Object.keys(t).forEach(function(internalFormat) {
+ const info = t[internalFormat];
+ info.bytesPerElementMap = {};
+ info.bytesPerElement.forEach(function(bytesPerElement, ndx) {
+ const type = info.type[ndx];
+ info.bytesPerElementMap[type] = bytesPerElement;
+ });
+ });
+ }
+
+ const validChannelsByTextureFormat = {};
+ {
+ const v = validChannelsByTextureFormat;
+ v[gl.RED] = [1, 0, 0, 0];
+ v[gl.RG] = [1, 1, 0, 0];
+ v[gl.RGB] = [1, 1, 1, 0];
+ v[gl.RGBA] = [1, 1, 1, 1];
+ v[gl.RED_INTEGER] = [1, 0, 0, 0];
+ v[gl.RG_INTEGER] = [1, 1, 0, 0];
+ v[gl.RGB_INTEGER] = [1, 1, 1, 0];
+ v[gl.RGBA_INTEGER] = [1, 1, 1, 1];
+ }
+
+ const depthTextureFormats = [
+ gl.DEPTH_COMPONENT16,
+ gl.DEPTH_COMPONENT24,
+ gl.DEPTH_COMPONENT32F,
+ gl.DEPTH24_STENCIL8,
+ gl.DEPTH32F_STENCIL8,
+ ];
+
+ const intTextureFormats = [
+ gl.R8I,
+ gl.R16I,
+ gl.R32I,
+ gl.RG8I,
+ gl.RG16I,
+ gl.RG32I,
+ gl.RGB8I,
+ gl.RGB16I,
+ gl.RGB32I,
+ gl.RGBA8I,
+ gl.RGBA16I,
+ gl.RGBA32I,
+ ];
+
+ const unsignedIntTextureFormats = [
+ gl.R8UI,
+ gl.R16UI,
+ gl.R32UI,
+ gl.RG8UI,
+ gl.RG16UI,
+ gl.RG32UI,
+ gl.RGB8UI,
+ gl.RGB16UI,
+ gl.RGB32UI,
+ gl.RGBA8UI,
+ gl.RGB10_A2UI,
+ gl.RGBA16UI,
+ gl.RGBA32UI,
+ ];
+
+ const floatTextureFormats = Object.keys(textureInternalFormatInfo).map(function(v) {
+ return parseInt(v);
+ }).filter(function(format) {
+ return intTextureFormats.indexOf(format) < 0 &&
+ unsignedIntTextureFormats.indexOf(format) < 0 &&
+ depthTextureFormats.indexOf(format);
+ });
+
+ const expectedColorByInternalFormat = {};
+ expectedColorByInternalFormat[gl.SRGB8_ALPHA8] = [225, 188, 137, 255];
+
+ function clearFloat(gl) {
+ gl.clearBufferfv(gl.COLOR, 0, [0, 0, 0, 0]);
+ }
+
+ function clearInt(gl) {
+ gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
+ }
+
+ function clearUint(gl) {
+ gl.clearBufferuiv(gl.COLOR, 0, [0, 0, 0, 0]);
+ }
+
+
+ function checkData(data, expected, internalFormat, tolerance) {
+ const internalFormatInfo = textureInternalFormatInfo[internalFormat];
+ const validChannels = validChannelsByTextureFormat[internalFormatInfo.textureFormat];
+ if (!validChannels) {
+ testFailed('oops');
+ return;
+ }
+ for (let y = 0; y < texHeight; ++y) {
+ for (let x = 0; x < texWidth; ++x) {
+ for (let c = 0; c < validChannels.length; ++c) {
+ if (validChannels[c]) {
+ const offset = (y * texWidth + x) * 4 + c;
+ const pixel = data[offset];
+ const diff = Math.abs(pixel - expected[c]);
+ if (diff > tolerance) {
+ testFailed(`pixel ${x},${y} channel ${c} was ${pixel} expected ${expected[c]} +/- ${tolerance}`);
+ return;
+ }
+ }
+ }
+ }
+ }
+ testPassed(`data was ${expected.join(',')}`);
+ }
+
+ function checkFloat(gl, textureInfo, expected) {
+ const data = new Uint8Array(texWidth * texHeight * 4);
+ gl.readPixels(0, 0, texWidth, texHeight, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ const internalFormat = textureInfo.internalFormat;
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from readPixels with ${wtu.glEnumToString(gl, internalFormat)}`);
+ checkData(data, expected, internalFormat, 9);
+ }
+
+ function checkInt(gl, textureInfo, expected) {
+ const data = new Int32Array(texWidth * texHeight * 4);
+ gl.readPixels(0, 0, texWidth, texHeight, gl.RGBA_INTEGER, gl.INT, data);
+ const internalFormat = textureInfo.internalFormat;
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from readPixels with ${wtu.glEnumToString(gl, internalFormat)}`);
+ checkData(data, expected, internalFormat, 0);
+ }
+
+ function checkUint(gl, textureInfo, expected) {
+ const data = new Uint32Array(texWidth * texHeight * 4);
+ gl.readPixels(0, 0, texWidth, texHeight, gl.RGBA_INTEGER, gl.UNSIGNED_INT, data);
+ const internalFormat = textureInfo.internalFormat;
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from readPixels with ${wtu.glEnumToString(gl, internalFormat)}`);
+ checkData(data, expected, internalFormat, 0);
+ }
+
+ const expectedFloatColor = [.75 * 255 | 0, .5 * 255 | 0, .25 * 255 | 0, 1 * 255 | 0];
+ const floatTypes = [
+ { outType: 'vec4', outValue: 'vec4(.75, .5, .25, 1)', expected: expectedFloatColor, clear: clearFloat, check: checkFloat, target: gl.TEXTURE_2D, },
+ { outType: 'vec4', outValue: 'vec4(.75, .5, .25, 1)', expected: expectedFloatColor, clear: clearFloat, check: checkFloat, target: gl.TEXTURE_3D, },
+ { outType: 'vec4', outValue: 'vec4(.75, .5, .25, 1)', expected: expectedFloatColor, clear: clearFloat, check: checkFloat, target: gl.TEXTURE_2D_ARRAY, },
+ ];
+
+ const expectedIntColor = [1, 2, 4, 3];
+ const signedIntTypes = [
+ { outType: 'ivec4', outValue: 'ivec4(1, 2, 4, 3)', expected: expectedIntColor, clear: clearInt, check: checkInt, target: gl.TEXTURE_2D, },
+ { outType: 'ivec4', outValue: 'ivec4(1, 2, 4, 3)', expected: expectedIntColor, clear: clearInt, check: checkInt, target: gl.TEXTURE_3D, },
+ { outType: 'ivec4', outValue: 'ivec4(1, 2, 4, 3)', expected: expectedIntColor, clear: clearInt, check: checkInt, target: gl.TEXTURE_2D_ARRAY, },
+ ];
+
+ const expectedUintColor = [1, 2, 4, 3];
+ const unsignedIntTypes = [
+ { outType: 'uvec4', outValue: 'uvec4(1, 2, 4, 3)', expected: expectedUintColor, clear: clearUint, check: checkUint, target: gl.TEXTURE_2D, },
+ { outType: 'uvec4', outValue: 'uvec4(1, 2, 4, 3)', expected: expectedUintColor, clear: clearUint, check: checkUint, target: gl.TEXTURE_3D, },
+ { outType: 'uvec4', outValue: 'uvec4(1, 2, 4, 3)', expected: expectedUintColor, clear: clearUint, check: checkUint, target: gl.TEXTURE_2D_ARRAY, },
+ ];
+
+ /**
+ * Gets the number of bytes per element for a given internalFormat / type
+ * @param {number} internalFormat The internalFormat parameter from texImage2D etc..
+ * @param {number} type The type parameter for texImage2D etc..
+ * @return {number} the number of bytes per element for the given internalFormat, type combo
+ * @memberOf module:twgl/textures
+ */
+ function getBytesPerElementForInternalFormat(internalFormat, type) {
+ const info = textureInternalFormatInfo[internalFormat];
+ if (!info) {
+ throw "unknown internal format";
+ }
+ const bytesPerElement = info.bytesPerElementMap[type];
+ if (bytesPerElement === undefined) {
+ throw "unknown internal format";
+ }
+ return bytesPerElement;
+ }
+
+ function make2DTexture(gl, target, internalFormat, format, type) {
+ gl.texImage2D(target, 0, internalFormat, texWidth, texHeight, 0, format, type, null);
+ }
+
+ function make3DTexture(gl, target, internalFormat, format, type) {
+ gl.texImage3D(target, 0, internalFormat, texWidth, texHeight, texDepth, 0, format, type, null);
+ }
+
+ function attach2DTexture(gl, target, texture) {
+ const level = 0;
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, target, texture, level);
+ }
+
+ function attach3DTexture(gl, target, texture) {
+ const level = 0;
+ const slice = texDepth - 1;
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texture, level, slice);
+ }
+
+ const targets = {};
+ targets[gl.TEXTURE_2D] = { make: make2DTexture, attach: attach2DTexture, },
+ targets[gl.TEXTURE_3D] = { make: make3DTexture, attach: attach3DTexture, },
+ targets[gl.TEXTURE_2D_ARRAY] = { make: make3DTexture, attach: attach3DTexture, },
+
+ debug("create textures");
+ Object.keys(targets).forEach(function(target) {
+ debug("");
+ target = parseInt(target);
+ debug(wtu.glEnumToString(gl, target))
+ const targetInfo = targets[target];
+ targetInfo.textures = [];
+ Object.keys(textureInternalFormatInfo).forEach(function(internalFormat) {
+ internalFormat = parseInt(internalFormat);
+ const isDepthFormat = depthTextureFormats.indexOf(internalFormat) >= 0;
+ if (isDepthFormat) {
+ return;
+ }
+ const info = textureInternalFormatInfo[internalFormat];
+ if (!info.colorRenderable) {
+ return;
+ }
+ const texture = gl.createTexture();
+ gl.bindTexture(target, texture);
+ targetInfo.make(gl, target, internalFormat, info.textureFormat, info.type[0]);
+ targetInfo.textures.push({
+ internalFormat: internalFormat,
+ texture: texture,
+ });
+ gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from setup for ${wtu.glEnumToString(gl, target)} ${wtu.glEnumToString(gl, internalFormat)}`);
+ });
+ });
+
+ // set the canvas to a known color
+ const half = 127 / 255;
+ gl.clearColor(half, half, half, half);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.clearColor(0, 0, 0, 0);
+
+ const framebuffer = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
+ gl.viewport(0, 0, texWidth, texHeight);
+
+ const rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, texWidth, texHeight);
+
+ testTypes('float', floatTypes, floatTextureFormats);
+ testTypes('int', signedIntTypes, intTextureFormats);
+ testTypes('unsigned', unsignedIntTypes, unsignedIntTextureFormats);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [127, 127, 127, 127], "canvas should be [127, 127, 127, 127]");
+
+ function testTypes(label, types, compatibleFormats) {
+ debug('');
+ types.forEach(function(typeInfo) {
+ debug(`\nchecking ${wtu.glEnumToString(gl, typeInfo.target)} with ${label} texture formats`);
+ const program = wtu.setupProgram(gl, ['vshader', makeFragmentShader(typeInfo)], [], console.log.bind(console));
+ if (!program) {
+ testFailed("Loading program failed");
+ return;
+ }
+ testPassed("Loading program succeeded");
+
+ const target = typeInfo.target;
+ const targetInfo = targets[target];
+ targetInfo.textures.filter(function(textureInfo) {
+ return compatibleFormats.indexOf(textureInfo.internalFormat) >= 0;
+ }).forEach(function(textureInfo) {
+ const internalFormat = textureInfo.internalFormat;
+ const desc = `${wtu.glEnumToString(gl, target)} ${wtu.glEnumToString(gl, internalFormat)}`;
+ const expected = expectedColorByInternalFormat[internalFormat] || typeInfo.expected;
+
+ debug('');
+ debug(desc);
+
+ targetInfo.attach(gl, target, textureInfo.texture);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, `for ${desc}`);
+ typeInfo.clear(gl);
+ if (wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from clear to ${desc}`)) {
+ return;
+ }
+ typeInfo.check(gl, textureInfo, [0, 0, 0, 0]);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ if (wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from render to ${desc}`)) {
+ return;
+ }
+ typeInfo.check(gl, textureInfo, expected);
+
+ typeInfo.clear(gl);
+ if (wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from clear to ${desc}`)) {
+ return;
+ }
+ typeInfo.check(gl, textureInfo, [0, 0, 0, 0]);
+
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, `for ${desc} with depth renderbuffer`);
+ gl.clearBufferfv(gl.DEPTH, 0, [1]);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, `No errors from render to ${desc}`);
+
+ typeInfo.check(gl, textureInfo, expected);
+ });
+ });
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-changing-base-level.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-changing-base-level.html
new file mode 100644
index 0000000000..de462d6015
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-changing-base-level.html
@@ -0,0 +1,107 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL framebuffer using a non-square texture with a changing base level</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="16" height="16"> </canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+// http://anglebug.com/2291
+
+var wtu = WebGLTestUtils;
+var gl;
+
+function testNonSquareFramebufferTextureWithChangingBaseLevel() {
+ var program = wtu.setupSimpleTextureProgram(gl);
+ wtu.setupUnitQuad(gl);
+
+ var width = 8;
+ var height = 4;
+
+ debug("");
+ debug("Text texture width " + width + " x height " + height);
+
+ var texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ 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);
+
+ // Create all mipmap levels for the texture from level 0 to the 1x1 pixel level.
+ var level = 0;
+ var levelW = width;
+ var levelH = height;
+ gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, levelW, levelH, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ while (levelW > 1 || levelH > 1)
+ {
+ ++level;
+ levelW = Math.max(1, Math.floor(width / Math.pow(2, level)));
+ levelH = Math.max(1, Math.floor(height / Math.pow(2, level)));
+ gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, levelW, levelH, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ }
+
+ // Clear each level of the texture using an FBO. Change the base level to match the level used for the FBO on each iteration.
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ level = 0;
+ levelW = width;
+ levelH = height;
+ while (levelW > 1 || levelH > 1)
+ {
+ var levelW = Math.floor(width / Math.pow(2, level));
+ var levelH = Math.floor(height / Math.pow(2, level));
+
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, level);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, level);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setup framebuffer with texture should succeed.");
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Clearing the texture level " + level + " to green should succeed.");
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 255, 0, 255], "should be green");
+ ++level;
+ }
+
+ debug("");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, 0);
+ wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Drawing the texture to default framebuffer with base level 0 should succeed.");
+ wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+
+ gl.deleteTexture(texture);
+ gl.deleteFramebuffer(fbo);
+}
+
+description();
+
+var canvas = document.getElementById("canvas");
+shouldBeNonNull("gl = wtu.create3DContext(canvas, undefined, 2)");
+
+testNonSquareFramebufferTextureWithChangingBaseLevel();
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-level1.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-level1.html
new file mode 100644
index 0000000000..d5ac8cb1e7
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-texture-level1.html
@@ -0,0 +1,64 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL framebuffer using texture level 1</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var gl;
+
+function testFramebufferTextureWithNonZeroBaseLevel(level) {
+ if (level < 1) {
+ throw "This test is incorrect if level < 1";
+ }
+ var width = 32;
+ var height = 16;
+ var texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ 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, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, level);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_BASE_LEVEL, level);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setup framebuffer with texture should succeed.");
+
+ gl.deleteTexture(texture);
+ gl.deleteFramebuffer(fbo);
+}
+
+description("Test fbo completeness when using non level 0 texture images");
+
+var canvas = document.getElementById("canvas");
+shouldBeNonNull("gl = wtu.create3DContext(canvas, undefined, 2)");
+
+testFramebufferTextureWithNonZeroBaseLevel(1);
+
+debug("");
+var successfullyParsed = true;
+
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-to-texture.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-to-texture.html
new file mode 100644
index 0000000000..926e14beab
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-to-texture.html
@@ -0,0 +1,201 @@
+<!--
+Copyright (c) 2022 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL framebuffer to texture conformance test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Test resolving and copying the framebuffer to a texture, and drawing the result.");
+debug('Reduced test case for <a href="http://anglebug.com/6972">http://anglebug.com/6972</a>');
+
+// Reproduces two behaviors:
+//
+// 1) The initial draw disappearing entirely from the default back
+// buffer. The current test case does not show this behavior
+// independently from the other, but a previous iteration, with the
+// textured quad scaled to half size and translated (-0.5, -0.5), did.
+//
+// 2) With Metal debug layers and load/store validation turned on on
+// Intel Macs, the transparent area of the texture prior to the bug
+// fix was magenta = undefined. Similar behavior would presumably
+// reproduce on M1 hardware without debug layers or validation.
+
+const size = 64;
+const halfSize = size / 2;
+const green = [ 0, 255, 0, 255 ];
+const transparent = [ 0, 0, 0, 0 ];
+
+let wtu = WebGLTestUtils;
+let canvas = document.getElementById("canvas");
+canvas.width = size;
+canvas.height = size;
+
+let gl = wtu.create3DContext("canvas", {
+ // Antialiasing is crucial for reproducing the bug.
+ antialias: true,
+ // Depth testing is not.
+ depth: false,
+}, 2);
+
+function allocateTexture(sz) {
+ let texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, sz, sz);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ 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.bindTexture(gl.TEXTURE_2D, null);
+ return texture;
+}
+
+// Allocate destination texture
+let destTexture = allocateTexture(halfSize);
+
+// Set up half-size solid color quad in center
+let colorQuadVAO = gl.createVertexArray();
+gl.bindVertexArray(colorQuadVAO);
+let colorQuadProgram = wtu.setupColorQuad(gl, 0, { scale: 0.5 });
+
+// Setup textured quad covering the entire renderable area
+let quadVAO = gl.createVertexArray();
+gl.bindVertexArray(quadVAO);
+let quadProgram = wtu.setupTexturedQuad(gl, 0, 1);
+gl.useProgram(quadProgram);
+let quadTexLoc = gl.getUniformLocation(quadProgram, "tex");
+gl.uniform1i(quadTexLoc, 0);
+
+gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
+gl.activeTexture(gl.TEXTURE0); // To match quadTexLoc=0
+
+function runTest() {
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.disable(gl.BLEND);
+ gl.bindVertexArray(colorQuadVAO);
+ gl.useProgram(colorQuadProgram);
+ wtu.drawUByteColorQuad(gl, [ 0, 255, 0, 255 ]);
+
+ gl.bindTexture(gl.TEXTURE_2D, destTexture);
+ // Copy the upper right corner of the framebuffer to the texture.
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, halfSize, halfSize, halfSize, halfSize);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.useProgram(quadProgram);
+ gl.enable(gl.BLEND);
+ gl.bindVertexArray(quadVAO);
+ gl.bindTexture(gl.TEXTURE_2D, destTexture);
+ // Magnify and blend this texture over the current framebuffer.
+ wtu.drawUnitQuad(gl);
+}
+
+function runUserDefinedFBOTest() {
+ let fbo1 = gl.createFramebuffer();
+ let fbo2 = gl.createFramebuffer();
+ let rb = gl.createRenderbuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo1);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, size, size);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, [ gl.FRAMEBUFFER_COMPLETE ]);
+
+ let tex = allocateTexture(size, size);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, [ gl.FRAMEBUFFER_COMPLETE ]);
+
+ // Same rendering steps as in the default-framebuffer test, with appropriate framebuffer blits interspersed.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo1);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.disable(gl.BLEND);
+ gl.bindVertexArray(colorQuadVAO);
+ gl.useProgram(colorQuadProgram);
+ wtu.drawUByteColorQuad(gl, [ 0, 255, 0, 255 ]);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo1);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo2);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
+
+ gl.bindTexture(gl.TEXTURE_2D, destTexture);
+ // Copy the upper right corner of the framebuffer to the texture.
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, halfSize, halfSize, halfSize, halfSize);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo1);
+
+ gl.useProgram(quadProgram);
+ gl.enable(gl.BLEND);
+ gl.bindVertexArray(quadVAO);
+ gl.bindTexture(gl.TEXTURE_2D, destTexture);
+ // Magnify and blend this texture over the current framebuffer.
+ wtu.drawUnitQuad(gl);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo1);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo2);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
+
+ // No longer easy to put these results on the canvas, because it's
+ // antialiased and we can't blitFramebuffer to it. Let's assume
+ // that if failures occur, they'll be straightforward to debug.
+}
+
+function checkRenderingResults(prefix) {
+ // Center quad should be rendered correctly.
+ wtu.checkCanvasRect(gl,
+ halfSize / 2 + 1, halfSize / 2 + 1,
+ halfSize - 2, halfSize - 2,
+ green,
+ prefix + ": center quad should be green");
+
+ // Overlapping lower-left quad should be green as well.
+ wtu.checkCanvasRect(gl,
+ 1, 1,
+ halfSize - 2, halfSize - 2,
+ green,
+ prefix + ": lower left quad should be green");
+
+ // Leftmost area above the lower-left quad should be transparent.
+ wtu.checkCanvasRect(gl,
+ 1, halfSize + 1,
+ halfSize / 2 - 2, halfSize / 2 - 2,
+ transparent,
+ prefix + ": leftmost area above lower left quad should be transparent");
+
+ // Bottommost area to the right of the lower-left quad should be transparent.
+ wtu.checkCanvasRect(gl,
+ halfSize + 1, 1,
+ halfSize / 2 - 2, halfSize / 2 - 2,
+ transparent,
+ prefix + ": bottommost area to the right of lower left quad should be transparent");
+}
+
+runTest();
+checkRenderingResults("default back buffer");
+
+runUserDefinedFBOTest();
+checkRenderingResults("user-defined framebuffer");
+
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors at the end of the test.");
+
+finishTest();
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-unsupported.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-unsupported.html
new file mode 100644
index 0000000000..1780650ed6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/framebuffer-unsupported.html
@@ -0,0 +1,134 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL FRAMEBUFFER_UNSUPPORTED Test</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var gl;
+var canvas = document.getElementById("canvas");
+
+function checkFramebuffer(expected) {
+ var actual = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (expected.indexOf(actual) < 0) {
+ var msg = "checkFramebufferStatus expects [";
+ for (var index = 0; index < expected.length; ++index) {
+ msg += wtu.glEnumToString(gl, expected[index]);
+ if (index + 1 < expected.length)
+ msg += ", ";
+ }
+ msg += "], was " + wtu.glEnumToString(gl, actual);
+ testFailed(msg);
+ } else {
+ var msg = "checkFramebufferStatus got " + wtu.glEnumToString(gl, actual) +
+ " as expected";
+ testPassed(msg);
+ }
+}
+
+function testImageAttachedTwoPoints() {
+ debug("");
+ debug("Checking an image is attached to more than one color attachment in a framebuffer.");
+
+ var tex1 = gl.createTexture();
+ var tex2 = gl.createTexture();
+ var fb = gl.createFramebuffer();
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ 1, // width
+ 1, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ new Uint8Array(4)); // data
+ gl.bindTexture(gl.TEXTURE_2D, tex2);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Texture creation should succeed.");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex1, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, tex2, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_2D, tex1, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_UNSUPPORTED]);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fb);
+ fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ var texCube = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, texCube);
+ for (var target = gl.TEXTURE_CUBE_MAP_POSITIVE_X; target < gl.TEXTURE_CUBE_MAP_POSITIVE_X + 6; target++) {
+ gl.texImage2D(target, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
+ }
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, texCube, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, gl.TEXTURE_CUBE_MAP_POSITIVE_X, texCube, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_UNSUPPORTED]);
+
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fb);
+ fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ var tex3d = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_3D, tex3d);
+ gl.texImage3D(gl.TEXTURE_3D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ 2, // width
+ 2, // height
+ 2, // depth
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ new Uint8Array(4 * 2 * 2 * 2)); // data
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex3d, 0, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, tex3d, 0, 1);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT2, tex3d, 0, 0);
+ checkFramebuffer([gl.FRAMEBUFFER_UNSUPPORTED]);
+
+ // Clean up
+ gl.deleteTexture(tex1);
+ gl.deleteTexture(tex2);
+ gl.deleteTexture(texCube);
+ gl.deleteTexture(tex3d);
+ gl.deleteFramebuffer(fb);
+}
+
+description("This tests FRAMEBUFFER_UNSUPPORTED.");
+
+shouldBeNonNull("gl = wtu.create3DContext(undefined, undefined, 2)");
+
+testImageAttachedTwoPoints();
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html
new file mode 100644
index 0000000000..36f5f50a72
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/fs-color-type-mismatch-color-buffer-type.html
@@ -0,0 +1,169 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>The Color Types of Fragment Shader's Outputs Should Match The Data Types of Color Buffers</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+in highp vec4 aPosition;
+void main() {
+ gl_Position = aPosition;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 oColor;
+void main() {
+ oColor = vec4(1.0, 0.0, 0.0, 0.0);
+}
+</script>
+
+<script id="fshaderMRT" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 oColor[2];
+void main() {
+ oColor[0] = vec4(1.0, 0.0, 0.0, 0.0);
+}
+</script>
+
+<script id="fshaderRealMRT" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 oColor[2];
+void main() {
+ oColor[0] = vec4(1.0, 0.0, 0.0, 0.0);
+ oColor[1] = vec4(0.0, 1.0, 0.0, 0.0);
+}
+</script>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies that the color types of fragment shader's outputs should match color buffers' types.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+var width = 8;
+var height = 8;
+var tex0;
+var tex1;
+var rb0;
+var rb1;
+var fbo = gl.createFramebuffer();
+var program0;
+var program1;
+var program2;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ init();
+
+ // COLOR_ATTACHMENT0 is fixed-point data, which can be converted to float.
+ // COLOR_ATTACHMENT1 is integer data. The fragment outputs are all float.
+ allocate_textures();
+ check_type_match();
+ allocate_renderbuffers();
+ check_type_match();
+}
+
+function check_type_match() {
+ gl.useProgram(program0);
+ rendering([gl.COLOR_ATTACHMENT0, gl.NONE], gl.NO_ERROR);
+ rendering([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1], gl.INVALID_OPERATION);
+
+ gl.useProgram(program1);
+ rendering([gl.COLOR_ATTACHMENT0, gl.NONE], gl.NO_ERROR);
+ rendering([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1], gl.INVALID_OPERATION);
+
+ gl.useProgram(program2);
+ rendering([gl.COLOR_ATTACHMENT0, gl.NONE], gl.NO_ERROR);
+ rendering([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1], gl.INVALID_OPERATION);
+}
+
+function init() {
+ program0 = wtu.setupProgram(gl, ['vshader', 'fshader'], ['aPosition'], [0]);
+ program1 = wtu.setupProgram(gl, ['vshader', 'fshaderMRT'], ['aPosition'], [0]);
+ program2 = wtu.setupProgram(gl, ['vshader', 'fshaderRealMRT'], ['aPosition'], [0]);
+ if (!program0 || !program1 || !program2) {
+ testFailed("Failed to set up program");
+ return;
+ }
+ testPassed("Succeed to set up program");
+
+ wtu.setupUnitQuad(gl, 0, 1);
+ gl.viewport(0, 0, width, height);
+}
+
+function allocate_textures() {
+ tex0 = gl.createTexture();
+ tex1 = gl.createTexture();
+ wtu.fillTexture(gl, tex0, width, height, [0xff, 0x0, 0x0, 0xff], 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.RGBA);
+ wtu.fillTexture(gl, tex1, width, height, [0x0, 0xff, 0x0, 0xff], 0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, gl.RGBA8UI);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex0, 0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, tex1, 0);
+}
+
+function allocate_renderbuffers() {
+ rb0 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb0);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, width, height);
+ rb1 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8UI, width, height);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.RENDERBUFFER, rb1);
+}
+
+function rendering(draw_buffers, error) {
+ gl.drawBuffers(draw_buffers);
+
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ }
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, error, "If color buffers' type mismatch the type of fragment shader's outputs, geneate INVALID_OPERATION. Otherwise, it should be NO_ERROR");
+}
+
+gl.bindTexture(gl.TEXTURE_2D, null);
+gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+gl.useProgram(null);
+gl.deleteTexture(tex0);
+gl.deleteTexture(tex1);
+gl.deleteRenderbuffer(rb0);
+gl.deleteRenderbuffer(rb1);
+gl.deleteFramebuffer(fbo);
+gl.deleteProgram(program0);
+gl.deleteProgram(program1);
+gl.deleteProgram(program2);
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-arrays.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-arrays.html
new file mode 100644
index 0000000000..65009ea5b3
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-arrays.html
@@ -0,0 +1,271 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Instanced Arrays Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<!-- Shaders for testing instanced draws -->
+<script id="outputVertexShader" type="x-shader/x-vertex">
+attribute vec4 aPosition;
+attribute vec2 aOffset;
+attribute vec4 aColor;
+varying vec4 vColor;
+void main() {
+ vColor = aColor;
+ gl_Position = aPosition + vec4(aOffset, 0.0, 0.0);
+}
+</script>
+
+<script id="outputFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 vColor;
+void main() {
+ gl_FragColor = vColor;
+}
+</script>
+
+<script>
+"use strict";
+description("This test verifies the functionality of Instanced Arrays.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runDivisorTest();
+ runOutputTests();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+}
+
+function runDivisorTest() {
+ debug("Testing VERTEX_ATTRIB_ARRAY_DIVISOR");
+
+ shouldBe("gl.VERTEX_ATTRIB_ARRAY_DIVISOR", "0x88FE");
+
+ var max_vertex_attribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
+
+ for (var i = 0; i < max_vertex_attribs; ++i) {
+ var queried_value = gl.getVertexAttrib(i, gl.VERTEX_ATTRIB_ARRAY_DIVISOR);
+ if(queried_value == 0){
+ testPassed("Vertex attribute " + i + " must has a default divisor of 0");
+ }
+ else{
+ testFailed("Default divisor of vertex attribute " + i + " should be: 0, returned value was: " + queried_value);
+ }
+ }
+
+ gl.vertexAttribDivisor(max_vertex_attribs, 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "vertexAttribDivisor index set greater than or equal to MAX_VERTEX_ATTRIBS should be an invalid value");
+
+ gl.vertexAttribDivisor(max_vertex_attribs-1, 2);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "vertexAttribDivisor index set less than MAX_VERTEX_ATTRIBS should succeed");
+
+ var queried_value = gl.getVertexAttrib(max_vertex_attribs-1, gl.VERTEX_ATTRIB_ARRAY_DIVISOR);
+ if(queried_value == 2){
+ testPassed("Set value of VERTEX_ATTRIB_ARRAY_DIVISOR matches expecation");
+ }
+ else{
+ testFailed("Set value of VERTEX_ATTRIB_ARRAY_DIVISOR should be: 2, returned value was: " + queried_value);
+ }
+}
+
+function runOutputTests() {
+ var e = 2; // Amount of variance to allow in result pixels - may need to be tweaked higher
+ var instanceCount = 4;
+
+ debug("Testing various draws for valid built-in function behavior");
+
+ canvas.width = 50; canvas.height = 50;
+ gl.viewport(0, 0, canvas.width, canvas.height);
+ gl.clearColor(0, 0, 0, 0);
+
+ var positionLoc = 0;
+ var offsetLoc = 2;
+ var colorLoc = 3;
+ var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"], ['aPosition', 'aOffset', 'aColor'], [positionLoc, offsetLoc, colorLoc]);
+
+ var offsets = new Float32Array([
+ -1.0, 1.0,
+ 1.0, 1.0,
+ -1.0, -1.0,
+ 1.0, -1.0,
+ ]);
+ var offsetBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(offsetLoc);
+ gl.vertexAttribPointer(offsetLoc, 2, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(offsetLoc, 1);
+
+ var colors = new Float32Array([
+ 1.0, 0.0, 0.0, 1.0, // Red
+ 0.0, 1.0, 0.0, 1.0, // Green
+ 0.0, 0.0, 1.0, 1.0, // Blue
+ 1.0, 1.0, 0.0, 1.0, // Yellow
+ // extra data when colorLoc divisor is set back to 0
+ 1.0, 1.0, 0.0, 1.0, // Yellow
+ 1.0, 1.0, 0.0, 1.0, // Yellow
+ ]);
+ var colorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(colorLoc);
+ gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(colorLoc, 1);
+
+ // Draw 1: Draw Non-indexed instances
+ debug("Testing drawArraysInstanced");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.setupUnitQuad(gl, 0);
+
+ // Test drawArraysInstanced error conditions
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.checkCanvasRect(gl, 0, canvas.height/2, canvas.width/2, canvas.height/2, [255, 0, 0, 255]);
+ wtu.checkCanvasRect(gl, canvas.width/2, canvas.height/2, canvas.width/2, canvas.height/2, [0, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width/2, canvas.height/2, [0, 0, 255, 255]);
+ wtu.checkCanvasRect(gl, canvas.width/2, 0, canvas.width/2, canvas.height/2, [255, 255, 0, 255]);
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, -1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "drawArraysInstanced cannot have a primcount less than 0");
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, -1, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "drawArraysInstanced cannot have a count less than 0");
+
+ gl.vertexAttribDivisor(positionLoc, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "It's allowed for all vertex attributes to have non-zero divisors when calling drawArraysInstanced");
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "It's allowed for all vertex attributes to have non-zero divisors when calling drawArrays");
+ wtu.checkCanvas(gl, [0, 0, 0, 0], "Nothing should be drawn on the framebuffer when all attributes have non-zero divisors (not enough vertices per instance to form a triangle)");
+ gl.vertexAttribDivisor(positionLoc, 0);
+
+ gl.drawArraysInstanced(gl.POINTS, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced with POINTS should succeed");
+ gl.drawArraysInstanced(gl.LINES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced with LINES should succeed");
+ gl.drawArraysInstanced(gl.LINE_LIST, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced with LINE_LIST should return succeed");
+ gl.drawArraysInstanced(gl.TRI_LIST, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced with TRI_LIST should succeed");
+
+ gl.drawArraysInstanced(desktopGL['QUAD_STRIP'], 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstanced with QUAD_STRIP should return INVALID_ENUM");
+ gl.drawArraysInstanced(desktopGL['QUADS'], 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstanced with QUADS should return INVALID_ENUM");
+ gl.drawArraysInstanced(desktopGL['POLYGON'], 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawArraysInstanced with POLYGON should return INVALID_ENUM");
+
+ debug("Testing drawArraysInstanced with param 'first' > 0");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.setupQuad(gl, {
+ positionLocation: 0,
+ scale: 0.5
+ });
+ var offsetsHalf = new Float32Array([
+ -0.5, 0.5,
+ 0.5, 0.5,
+ -0.5, -0.5,
+ 0.5, -0.5
+ ]);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, offsetsHalf, gl.STATIC_DRAW);
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 3, 3, instanceCount);
+ var w = Math.floor(0.25*canvas.width),
+ h = Math.floor(0.25*canvas.height);
+ wtu.checkCanvasRect(gl, Math.ceil(0.25*canvas.width), 0.5*canvas.height, w, h, [255, 0, 0, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.75*canvas.width), 0.5*canvas.height, w, h, [0, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.25*canvas.width), 0, w, h, [0, 0, 255, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.75*canvas.width), 0, w, h, [255, 255, 0, 255]);
+
+ debug("Testing drawArraysInstanced with attributes 'divisor' reset to 0");
+ debug("Correct rendering output: 4 yellow triangles");
+ debug("Possible incorrect rendering output: missing triangles, or triangles with different color at each vertex");
+ gl.vertexAttribDivisor(colorLoc, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArraysInstanced(gl.TRIANGLES, 3, 3, instanceCount);
+ wtu.checkCanvasRect(gl, Math.ceil(0.25*canvas.width), 0.5*canvas.height, w, h, [255, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.75*canvas.width), 0.5*canvas.height, w, h, [255, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.25*canvas.width), 0, w, h, [255, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, Math.ceil(0.75*canvas.width), 0, w, h, [255, 255, 0, 255]);
+ gl.vertexAttribDivisor(colorLoc, 1);
+
+ wtu.setupUnitQuad(gl, 0);
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
+
+ // Draw 2: Draw indexed instances
+ debug("Testing drawElementsInstanced");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.setupIndexedQuad(gl, 1, 0);
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.checkCanvasRect(gl, 0, canvas.height/2, canvas.width/2, canvas.height/2, [255, 0, 0, 255]);
+ wtu.checkCanvasRect(gl, canvas.width/2, canvas.height/2, canvas.width/2, canvas.height/2, [0, 255, 0, 255]);
+ wtu.checkCanvasRect(gl, 0, 0, canvas.width/2, canvas.height/2, [0, 0, 255, 255]);
+ wtu.checkCanvasRect(gl, canvas.width/2, 0, canvas.width/2, canvas.height/2, [255, 255, 0, 255]);
+
+ // Test drawElementsInstanced error conditions
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, -1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "drawElementsInstanced cannot have a primcount less than 0");
+
+ gl.drawElementsInstanced(gl.TRIANGLES, -1, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "drawElementsInstanced cannot have a count less than 0");
+
+ gl.vertexAttribDivisor(positionLoc, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "It's allowed for all vertex attributes to have non-zero divisors when calling drawElementsInstanced");
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "It's allowed for all vertex attributes to have non-zero divisors when calling drawElements");
+ wtu.checkCanvas(gl, [0, 0, 0, 0], "Nothing should be drawn on the framebuffer when all attributes have non-zero divisors (not enough vertices per instance to form a triangle)");
+ gl.vertexAttribDivisor(positionLoc, 0);
+
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced with UNSIGNED_BYTE should succeed");
+
+ gl.drawElementsInstanced(gl.POINTS, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced with POINTS should succeed");
+ gl.drawElementsInstanced(gl.LINES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced with LINES should succeed");
+ gl.drawElementsInstanced(gl.LINE_LIST, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced with LINE_LIST should return succeed");
+ gl.drawElementsInstanced(gl.TRI_LIST, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced with TRI_LIST should succeed");
+
+ gl.drawElementsInstanced(desktopGL['QUAD_STRIP'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstanced with QUAD_STRIP should return INVALID_ENUM");
+ gl.drawElementsInstanced(desktopGL['QUADS'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstanced with QUADS should return INVALID_ENUM");
+ gl.drawElementsInstanced(desktopGL['POLYGON'], 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "drawElementsInstanced with POLYGON should return INVALID_ENUM");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-bug.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-bug.html
new file mode 100644
index 0000000000..59f25096d5
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-bug.html
@@ -0,0 +1,254 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Instanced Arrays Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 128px; height: 128px;"> </canvas>
+<div id="console"></div>
+<script id="outputVertexShader" type="x-shader/x-vertex">#version 300 es
+in highp vec2 aPosition;
+in highp float aOffset;
+in highp float aColor;
+out mediump float vColor;
+void main() {
+ gl_Position = vec4(aPosition, 0.0, 1.0) + vec4(aOffset, 0.0, 0.0, 0.0);
+ vColor = aColor;
+}
+</script>
+
+<script id="outputFragmentShader" type="x-shader/x-fragment">#version 300 es
+layout(location = 0) out mediump vec4 oColor;
+in mediump float vColor;
+void main() {
+ oColor = vec4(vColor, 0.0, 0.0, 1.0);
+}
+</script>
+
+<script>
+"use strict";
+description("This test verifies a bug related with instanced rendering on Mac AMD.");
+debug("http://crbug.com/645298");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+// The second and fourth test cases fail - it seems if the divisor doesn't change,
+// the next instanced draw call behaves incorrectly.
+// Also note that if we don't perform a readPixels (in wtu.checkCanvasRect), the bug
+// isn't triggered.
+var testCases = [
+ { instanceCount: 8, divisor: 4 },
+ { instanceCount: 6, divisor: 4 },
+ { instanceCount: 6, divisor: 3 },
+ { instanceCount: 8, divisor: 3 },
+];
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ for (var ii = 0; ii < testCases.length; ++ii) {
+ runDrawArraysTest(testCases[ii].instanceCount, testCases[ii].divisor);
+ }
+
+ for (var ii = 0; ii < testCases.length; ++ii) {
+ runDrawElementsTest(testCases[ii].instanceCount, testCases[ii].divisor);
+ }
+}
+
+function runDrawArraysTest(instanceCount, divisor) {
+ debug("");
+ debug("Testing drawArraysInstanced: instanceCount = " + instanceCount + ", divisor = " + divisor);
+
+ gl.viewport(0, 0, canvas.width, canvas.height);
+
+ var vao = gl.createVertexArray();
+ gl.bindVertexArray(vao);
+
+ var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"]);
+ var positionLoc = gl.getAttribLocation(program, "aPosition");
+ var offsetLoc = gl.getAttribLocation(program, "aOffset");
+ var colorLoc = gl.getAttribLocation(program, "aColor");
+ if (!program || positionLoc < 0 || offsetLoc < 0 || colorLoc < 0) {
+ testFailed("Set up program failed");
+ return;
+ }
+ testPassed("Set up program succeeded");
+
+ var scale = 1.0 / instanceCount;
+
+ gl.enableVertexAttribArray(positionLoc);
+ gl.vertexAttribDivisor(positionLoc, 0);
+ var positions = new Float32Array([
+ 1.0 * scale, 1.0,
+ -1.0 * scale, 1.0,
+ -1.0 * scale, -1.0,
+ 1.0 * scale, 1.0,
+ -1.0 * scale, -1.0,
+ 1.0 * scale, -1.0,
+ ]);
+ var positionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
+
+ gl.enableVertexAttribArray(offsetLoc);
+ gl.vertexAttribDivisor(offsetLoc, 1);
+ var offsets = new Float32Array(instanceCount);
+ for (var ii = 0; ii < instanceCount; ++ii) {
+ offsets[ii] = scale * (1 - instanceCount + ii * 2);
+ }
+ var offsetBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(offsetLoc, 1, gl.FLOAT, false, 0, 0);
+
+ gl.enableVertexAttribArray(colorLoc);
+ gl.vertexAttribDivisor(colorLoc, divisor);
+ var colorCount = instanceCount / divisor;
+ if ((instanceCount % divisor) != 0)
+ colorCount++;
+ var colors = new Float32Array(colorCount);
+ for (var ii = 0; ii < colorCount; ++ii) {
+ colors[ii] = 1.0 / colorCount * (ii + 1);
+ }
+ var colorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(colorLoc, 1, gl.FLOAT, false, 0, 0);
+
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced should succeed");
+
+ var colorIndex = -1;
+ for (var ii = 0; ii < instanceCount; ++ii) {
+ if ((ii % divisor) == 0)
+ colorIndex++;
+ var refColor = [ Math.floor(colors[colorIndex] * 255), 0, 0, 255 ];
+ wtu.checkCanvasRect(gl, Math.floor(canvas.width / instanceCount * ii) + 1, 0, 1, canvas.height, refColor,
+ "instance " + ii + " should be " + refColor, 2);
+ }
+
+ gl.deleteBuffer(positionBuffer);
+ gl.deleteBuffer(offsetBuffer);
+ gl.deleteBuffer(colorBuffer);
+ gl.deleteProgram(program);
+ gl.deleteVertexArray(vao);
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+ gl.useProgram(null);
+ gl.bindVertexArray(null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clean up should succeed");
+}
+
+function runDrawElementsTest(instanceCount, divisor) {
+ debug("");
+ debug("Testing drawElementsInstanced: instanceCount = " + instanceCount + ", divisor = " + divisor);
+
+ gl.viewport(0, 0, canvas.width, canvas.height);
+
+ var vao = gl.createVertexArray();
+ gl.bindVertexArray(vao);
+
+ var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"]);
+ var positionLoc = gl.getAttribLocation(program, "aPosition");
+ var offsetLoc = gl.getAttribLocation(program, "aOffset");
+ var colorLoc = gl.getAttribLocation(program, "aColor");
+ if (!program || positionLoc < 0 || offsetLoc < 0 || colorLoc < 0) {
+ testFailed("Set up program failed");
+ return;
+ }
+ testPassed("Set up program succeeded");
+
+ var scale = 1.0 / instanceCount;
+
+ gl.enableVertexAttribArray(positionLoc);
+ gl.vertexAttribDivisor(positionLoc, 0);
+ var positions = new Float32Array([
+ 1.0 * scale, 1.0,
+ -1.0 * scale, 1.0,
+ -1.0 * scale, -1.0,
+ 1.0 * scale, -1.0,
+ ]);
+ var positionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
+
+ gl.enableVertexAttribArray(offsetLoc);
+ gl.vertexAttribDivisor(offsetLoc, 1);
+ var offsets = new Float32Array(instanceCount);
+ for (var ii = 0; ii < instanceCount; ++ii) {
+ offsets[ii] = scale * (1 - instanceCount + ii * 2);
+ }
+ var offsetBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, offsets, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(offsetLoc, 1, gl.FLOAT, false, 0, 0);
+
+ gl.enableVertexAttribArray(colorLoc);
+ gl.vertexAttribDivisor(colorLoc, divisor);
+ var colorCount = instanceCount / divisor;
+ if ((instanceCount % divisor) != 0)
+ colorCount++;
+ var colors = new Float32Array(colorCount);
+ for (var ii = 0; ii < colorCount; ++ii) {
+ colors[ii] = 1.0 / colorCount * (ii + 1);
+ }
+ var colorBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(colorLoc, 1, gl.FLOAT, false, 0, 0);
+
+ var indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ var indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced should succeed");
+
+ var colorIndex = -1;
+ for (var ii = 0; ii < instanceCount; ++ii) {
+ if ((ii % divisor) == 0)
+ colorIndex++;
+ var refColor = [ Math.floor(colors[colorIndex] * 255), 0, 0, 255 ];
+ wtu.checkCanvasRect(gl, Math.floor(canvas.width / instanceCount * ii) + 1, 0, 1, canvas.height, refColor,
+ "instance " + ii + " should be " + refColor, 2);
+ }
+
+ gl.deleteBuffer(positionBuffer);
+ gl.deleteBuffer(offsetBuffer);
+ gl.deleteBuffer(colorBuffer);
+ gl.deleteBuffer(indexBuffer);
+ gl.deleteProgram(program);
+ gl.deleteVertexArray(vao);
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
+ gl.useProgram(null);
+ gl.bindVertexArray(null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clean up should succeed");
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-large-divisor.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-large-divisor.html
new file mode 100644
index 0000000000..229649ee3c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/instanced-rendering-large-divisor.html
@@ -0,0 +1,145 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL Instanced Arrays Conformance Tests - large vertex attrib divisors</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas"> </canvas>
+<div id="console"></div>
+<script id="outputVertexShader" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 a_position;
+layout(location = 1) in float a_positionOffset;
+layout(location = 2) in vec4 a_color;
+out vec4 v_color;
+void main()
+{
+ gl_Position = vec4(a_position.x * 0.05 + a_positionOffset, a_position.y * 0.05, 0.0, 1.0);
+ v_color = a_color;
+}
+</script>
+
+<script id="outputFragmentShader" type="x-shader/x-fragment">#version 300 es
+precision highp float;
+in vec4 v_color;
+out vec4 my_FragColor;
+void main()
+{
+ my_FragColor = v_color;
+}
+</script>
+
+<script>
+"use strict";
+description("Test switching vertex attrib divisor of one attribute between different large values");
+// This is a regression test for http://anglebug.com/2832
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+canvas.width = 256;
+canvas.height = 256;
+var gl = wtu.create3DContext(canvas, null, 2);
+
+var colorDivisor = 65536 * 2;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runTest();
+}
+
+function runTest() {
+ var program = wtu.setupProgram(gl, ["outputVertexShader", "outputFragmentShader"]);
+
+ gl.clearColor(0.0, 0.0, 1.0, 1.0);
+
+ wtu.setupIndexedQuadWithOptions(gl, {positionLocation: 0});
+
+ var offsetBuf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuf);
+ // Note that only the first two offsets below should be used for rendering.
+ // We add some extra ones to the buffer since it can reveal if a too small divisor is used on the WebGL backend.
+ var offsetData = [];
+ for (var i = 0; i < 4; ++i) {
+ offsetData.push(0.0 + i * 0.25);
+ }
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(offsetData), gl.DYNAMIC_DRAW);
+
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up vertex buffer should succeed");
+
+ var colorBuf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, colorBuf);
+ // Red and green colors.
+ var colorData = new Float32Array([1.0, 0.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0]);
+ gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.DYNAMIC_DRAW);
+
+ gl.enableVertexAttribArray(2);
+ gl.vertexAttribPointer(2, 4, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(2, colorDivisor);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setting up color buffer should succeed");
+
+ var divisorsToTry = [
+ 256,
+ 65536,
+ 65536 * 2
+ ];
+
+ for (var i = 0; i < divisorsToTry.length; ++i) {
+ runDrawElementsTest(divisorsToTry[i]);
+ }
+}
+
+function runDrawElementsTest(divisor) {
+ debug("Using divisor " + divisor);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.vertexAttribDivisor(1, divisor);
+
+ var instanceCount = divisor + 1;
+ var quadsRendered = Math.floor((instanceCount - 1) / divisor) + 1;
+
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Instanced draw should succeed");
+
+ for (var quadIndex = 0; quadIndex < quadsRendered + 1; ++quadIndex) {
+ var quadX = Math.floor((quadIndex / 8) * 256 + 128);
+ var quadY = 128;
+ if (quadIndex < quadsRendered) {
+ var quadColorIndex = Math.floor((quadIndex * divisor) / colorDivisor);
+ if (quadColorIndex == 0) {
+ wtu.checkCanvasRect(gl, quadX, quadY, 1, 1, [255, 0, 0, 255]);
+ } else {
+ wtu.checkCanvasRect(gl, quadX, quadY, 1, 1, [0, 255, 0, 255]);
+ }
+ } else {
+ wtu.checkCanvasRect(gl, quadX, quadY, 1, 1, [0, 0, 255, 255]);
+ }
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/line-rendering-quality.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/line-rendering-quality.html
new file mode 100644
index 0000000000..24442ea3c6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/line-rendering-quality.html
@@ -0,0 +1,27 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<title>Line rendering quality test</title>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="testbed" width="256" height="256"></canvas>
+<canvas id="testbed2" width="256" height="256"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+var contextVersion = 2;
+</script>
+<script src="../../js/tests/line-rendering-quality.js"></script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-depth-resolve.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-depth-resolve.html
new file mode 100644
index 0000000000..14aeab4f87
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-depth-resolve.html
@@ -0,0 +1,179 @@
+<!--
+Copyright (c) 2022 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL framebuffer to texture conformance test.</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="canvas"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Test resolving multisample depth buffer");
+debug('Reduced test case for <a href="https://bugs.webkit.org/show_bug.cgi?id=238118">https://bugs.webkit.org/show_bug.cgi?id=238118</a>');
+
+// Reproduces an inconistent behavior where if:
+// 1) You render into a multisampling frame buffer
+// 2) Geometry is drawn with DEPTH_TEST disabled and then enabled
+// 3) More than one frame is rendered via requestAnimationFrame
+
+const size = 64;
+const halfSize = size / 2;
+
+let wtu = WebGLTestUtils;
+let canvas = document.getElementById("canvas");
+canvas.width = size;
+canvas.height = size;
+
+let gl = wtu.create3DContext("canvas", {}, 2);
+
+function createTexture(res, format, bytes) {
+ let texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, format, res, res);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ 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.bindTexture(gl.TEXTURE_2D, null);
+ return texture;
+}
+
+function createRenderBuffer(res, format, samples) {
+ let rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ if (samples > 1)
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, format, res, res);
+ else
+ gl.renderbufferStorage(gl.RENDERBUFFER, format, res, res);
+ return rb;
+}
+
+let yellowQuadVAO = gl.createVertexArray();
+gl.bindVertexArray(yellowQuadVAO);
+let yellowQuadProgram = wtu.setupColorQuad(gl, 0, { scale: 0.75 });
+
+let blueQuadVAO = gl.createVertexArray();
+gl.bindVertexArray(blueQuadVAO);
+let blueQuadProgram = wtu.setupColorQuad(gl, 0, { scale: 0.5 });
+
+let fsVAO = gl.createVertexArray();
+gl.bindVertexArray(fsVAO);
+let fsProgram = wtu.setupTexturedQuad(gl, 0, 1);
+gl.useProgram(fsProgram);
+let fsTexLoc = gl.getUniformLocation(fsProgram, "tex");
+gl.uniform1i(fsTexLoc, 0);
+
+// An incorrect render can occur if...
+
+// 1) You use renderbufferStorageMultisample.
+const msaaSamples = 4;
+const colorRB = createRenderBuffer(size, gl.RGBA8, msaaSamples);
+const depthRB = createRenderBuffer(size, gl.DEPTH_COMPONENT16, msaaSamples);
+const resolveTex = createTexture(size, gl.RGBA8);
+
+let renderFBO = gl.createFramebuffer();
+gl.bindFramebuffer(gl.FRAMEBUFFER, renderFBO);
+gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRB);
+gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRB);
+
+let resolveFBO = gl.createFramebuffer();
+gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFBO);
+gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resolveTex, 0);
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+gl.disable(gl.CULL_FACE);
+gl.disable(gl.BLEND);
+
+var frameCount = 0;
+function runTest() {
+ // 2) Render from requestAnimationFrame, only starting with the 2nd frame.
+ gl.bindFramebuffer(gl.FRAMEBUFFER, renderFBO);
+
+ // Clear background red
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT);
+
+ // 3) You disable gl.DEPTH_TEST
+ gl.disable(gl.DEPTH_TEST);
+ gl.depthMask(false);
+
+ gl.bindVertexArray(yellowQuadVAO);
+ gl.useProgram(yellowQuadProgram);
+ wtu.drawUByteColorQuad(gl, [ 255, 255, 0, 255 ]);
+
+ // 4) And re-enable gl.DEPTH_TEST
+ gl.enable(gl.DEPTH_TEST);
+ gl.depthMask(true);
+
+ gl.bindVertexArray(blueQuadVAO);
+ gl.useProgram(blueQuadProgram);
+ wtu.drawUByteColorQuad(gl, [ 0, 0, 255, 255 ]);
+
+ // Resolve the multisample framebuffer to a texture
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, renderFBO);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, resolveFBO);
+ gl.clearBufferfv(gl.COLOR, 0, [0.0, 0.0, 0.0, 0.0]);
+ gl.blitFramebuffer(0, 0, size, size,
+ 0, 0, size, size,
+ gl.COLOR_BUFFER_BIT, gl.LINEAR);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+
+ // Draw the resolved texture to the backbuffer
+ gl.bindTexture(gl.TEXTURE_2D, resolveTex);
+ gl.useProgram(fsProgram);
+ gl.bindVertexArray(fsVAO);
+ wtu.drawUnitQuad(gl);
+
+ // 5) The incorrect render can occur on the second rendered frame, called from
+ // requestAnimationFrame.
+ frameCount++;
+ if (frameCount == 2) {
+ checkRenderingResults("multisampling-depth-resolve");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors at the end of the test.");
+ finishTest();
+ } else {
+ requestAnimationFrame(runTest);
+ }
+}
+
+requestAnimationFrame(runTest);
+
+function checkRenderingResults(prefix) {
+ // Outer color should be red
+ wtu.checkCanvasRect(gl,
+ 1, 1,
+ 2, 2,
+ [255, 0, 0, 255],
+ prefix + ": outer pixels should be red");
+
+ // Outer quad should be rendered yellow.
+ wtu.checkCanvasRect(gl,
+ 10, 10,
+ 2, 2,
+ [255, 255, 0, 255],
+ prefix + ": outer quad should be yellow");
+
+ // Center quad should be rendered blue.
+ wtu.checkCanvasRect(gl,
+ halfSize / 2 + 1, halfSize / 2 + 1,
+ 2, 2,
+ [0, 0, 255, 255],
+ prefix + ": center quad should be blue");
+}
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-fragment-evaluation.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-fragment-evaluation.html
new file mode 100644
index 0000000000..df7bce9b09
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/multisampling-fragment-evaluation.html
@@ -0,0 +1,143 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL multisampling fragment shader evaluation</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+layout(location=0) in vec4 aPosition;
+out vec4 vPosition;
+void main()
+{
+ gl_Position = vec4(aPosition);
+ vPosition = aPosition;
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision highp float;
+in vec4 vPosition;
+layout(location=0) out vec4 oColor;
+void main()
+{
+ if (vPosition.x < 0.0) {
+ oColor = vec4(1, 0, 0, 1);
+ } else if (vPosition.y < 0.0) {
+ oColor = vec4(0, 1, 0, 1);
+ } else {
+ oColor = vec4(0, 0, 1, 1);
+ }
+}
+</script>
+
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("Verify that fragment shader is evaluated only once per framebuffer pixel when multisampling is used.");
+
+// GLES 3.0.5 section 3.6.3. Polygon Multisample Rasterization:
+// "Polygon rasterization produces a fragment for each framebuffer pixel with one or more sample points that satisfy
+// the point sampling criteria described in section 3.6.1."
+
+debug("Regression test for <a href='http://crbug.com/682815'>http://crbug.com/682815</a>");
+
+function runTest(testParams) {
+ let canvas = document.createElement('canvas');
+ canvas.width = 1;
+ canvas.height = 1;
+ let gl = wtu.create3DContext(canvas, {antialias: false}, 2);
+
+ // Find the supported samples for a multisampled renderbuffer of the appropriate internal format.
+ let samples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl[testParams.internalformat], gl.SAMPLES);
+ if (!samples || !samples.length) {
+ testFailed("Could not query supported sample counts for required multisampling format " + testParams.internalformat);
+ return;
+ }
+
+ // Note that supported sample counts are required to be reported in descending order.
+ debug('Testing with sample count ' + samples[0]);
+ // Create a framebuffer with a multisampled renderbuffer with the maximum supported number of samples.
+ let rb = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[0], gl[testParams.internalformat], 1, 1);
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Rendering to a multisampled renderbuffer of format " + testParams.internalformat + " is required.");
+ return;
+ }
+
+ // Create a program that will choose between one of different possible colors in the fragment shader.
+ // It should be evaluated only once per framebuffer pixel, so only one of the colors will end up in the framebuffer.
+ // However, if the multisampling mode is incorrectly implemented by supersampling, the samples may have different
+ // colors.
+ let program = wtu.setupProgram(gl, ["vshader", "fshader"], ["aPosition"]);
+
+ // Render one triangle using the program. The triangle needs to extend far outside the viewport on all sides, so
+ // that we can safely assume all samples fall inside the triangle. GLES 3.0.5:
+ // "The sample points associated with a pixel may be located inside or outside of the unit square that is considered to bound the pixel."
+ // Here we assume that sample points are less than 9999 pixels away from the pixel they are associated with.
+ let buffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 10000, 30000,
+ -30000, -10000,
+ 10000, -10000]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+
+ let readBuffer = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer);
+
+ // Check that the canvas is one of the colors that the fragment shader may generate, and not a blend of them.
+ let possibleColors = [
+ [255, 0, 0, 255],
+ [0, 255, 0, 255],
+ [0, 0, 255, 255]
+ ];
+ let anyColorMatched = false;
+ for (let i = 0; i < possibleColors.length; ++i) {
+ let colorMatched = true;
+ for (let j = 0; j < 4; ++j) {
+ if (Math.abs(readBuffer[j] - possibleColors[i][j]) > 2) {
+ colorMatched = false;
+ }
+ }
+ if (colorMatched) {
+ anyColorMatched = true;
+ }
+ }
+ if (!anyColorMatched) {
+ testFailed("Color in framebuffer was not one of the colors generated by the fragment shader: " + readBuffer);
+ } else {
+ testPassed("Color in framebuffer was one of the colors generated by the fragment shader: " + readBuffer);
+ }
+}
+
+runTest({internalformat: 'RGBA8'});
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/out-of-bounds-index-buffers-after-copying.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/out-of-bounds-index-buffers-after-copying.html
new file mode 100644
index 0000000000..39bbb5e348
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/out-of-bounds-index-buffers-after-copying.html
@@ -0,0 +1,187 @@
+<!--
+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.
+-->
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<title>WebGL Out-of-Bounds Index Buffer Caused by CopyBufferSubData Conformance Test</title>
+</head>
+<body>
+<canvas id="canvas" width="8" height="8" style="width: 100px; height: 100px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vsCheckOutOfBounds" type="x-shader/x-vertex">
+ #define TEST_CASE_IN_BOUND 1
+ #define TEST_CASE_OUT_OF_BOUND 2
+
+ precision mediump float;
+ attribute vec2 position;
+ attribute vec4 vecRandom;
+ varying vec4 v_color;
+ uniform int u_testCase;
+
+ bool testFloatComponentAccurate(float component) {
+ return component == 0.2;
+ }
+ // Per the spec, each component can either contain existing contents
+ // of the buffer or 0.
+ bool testFloatComponent(float component) {
+ return (component == 0.2 || component == 0.0);
+ }
+ // The last component is additionally allowed to be 1.0.
+ bool testLastFloatComponent(float component) {
+ return testFloatComponent(component) || component == 1.0;
+ }
+
+ bool testData(vec4 data) {
+ if (u_testCase == TEST_CASE_IN_BOUND) {
+ return (testFloatComponentAccurate(data.x) &&
+ testFloatComponentAccurate(data.y) &&
+ testFloatComponentAccurate(data.z) &&
+ testFloatComponentAccurate(data.w));
+ } else if (u_testCase == TEST_CASE_OUT_OF_BOUND) {
+ return (testFloatComponent(data.x) &&
+ testFloatComponent(data.y) &&
+ testFloatComponent(data.z) &&
+ testLastFloatComponent(data.w));
+ }
+ return false;
+ }
+
+ void main() {
+ if (testData(vecRandom)) {
+ v_color = vec4(0.0, 1.0, 0.0, 1.0); // green -- We're good
+ } else {
+ v_color = vec4(1.0, 0.0, 0.0, 1.0); // red -- Unexpected value
+ }
+ gl_Position = vec4(position, 0.0, 1.0);
+ }
+</script>
+<script>
+"use strict";
+description("This test verifies that out-of-bounds index buffers caused by CopyBufferSubData behave according to spec.");
+
+// Ensure that drawElements flags either no error or INVALID_OPERATION. In the case of INVALID_OPERATION,
+// no canvas pixels can be touched. In the case of NO_ERROR, all written values must either be the
+// zero vertex or a value in the vertex buffer. See vsCheckOutOfBounds shader.
+function verifyOutOfBoundsIndex(gl) {
+ var error = gl.getError();
+ if (error === gl.INVALID_OPERATION) {
+ testPassed("drawElements flagged INVALID_OPERATION, which is valid so long as all canvas pixels were not touched.");
+ wtu.checkCanvas(gl, [0, 0, 255, 255]);
+ } else if (error === gl.NO_ERROR) {
+ testPassed("drawElements flagged NO_ERROR, which is valid so long as all canvas pixels are green.");
+ wtu.checkCanvas(gl, [0, 255, 0, 255]);
+ } else {
+ testFailed("Invalid error flagged by drawElements. Should be INVALID_OPERATION or NO_ERROR");
+ }
+}
+
+// Create an element array buffer with a tri-strip that starts at startIndex and make
+// it the active element array buffer.
+function prepareElementArrayBuffer(gl, startIndex) {
+ var glElementArrayBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glElementArrayBuffer);
+ var quadIndices = new Uint16Array(4);
+ for (var i = 0; i < quadIndices.length; i++) {
+ quadIndices[i] = startIndex + i;
+ }
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, quadIndices, gl.STATIC_DRAW);
+ return glElementArrayBuffer;
+}
+
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, {antialias: false}, 2);
+
+var numberOfQuads = 200;
+
+// Create a vertex buffer with 200 properly formed tri-strip quads. These quads will cover the canvas texture
+// such that every single pixel is touched by the fragment shader.
+var quadBuffer = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, quadBuffer);
+var quadPositions = new Float32Array(numberOfQuads * /*ComponentsPerQuad*/2 * /*VerticesPerQuad*/4);
+for (var i = 0; i < quadPositions.length; i += /*ComponentsPerQuad*/2 * /*VerticesPerQuad*/4) {
+ quadPositions[i+0] = -1.0; // upper left
+ quadPositions[i+1] = 1.0;
+ quadPositions[i+2] = 1.0; // upper right
+ quadPositions[i+3] = 1.0;
+ quadPositions[i+4] = -1.0; // lower left
+ quadPositions[i+5] = -1.0;
+ quadPositions[i+6] = 1.0; // lower right
+ quadPositions[i+7] = -1.0;
+}
+gl.bufferData(gl.ARRAY_BUFFER, quadPositions, gl.STATIC_DRAW);
+gl.enableVertexAttribArray(0);
+gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+// Create a small vertex buffer with determined-ahead-of-time "random" values (0.2). This buffer will be
+// the one indexed off the end.
+var vertexBuffer = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+gl.bufferData(gl.ARRAY_BUFFER,
+ new Float32Array([0.2, 0.2, 0.2, 0.2,
+ 0.2, 0.2, 0.2, 0.2,
+ 0.2, 0.2, 0.2, 0.2,
+ 0.2, 0.2, 0.2, 0.2]),
+ gl.STATIC_DRAW);
+gl.enableVertexAttribArray(1);
+gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
+
+// Setup the verification program.
+var program = wtu.setupProgram(gl, ["vsCheckOutOfBounds", wtu.simpleVertexColorFragmentShader], ["position", "vecRandom"]);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Shader and buffer setup should generate no errors");
+var loc = gl.getUniformLocation(program, "u_testCase");
+shouldBeNonNull(loc);
+
+debug("");
+debug("Test -- Vertex indices are in bounds.");
+gl.uniform1i(loc, 1); // TEST_CASE_IN_BOUND == 1
+gl.clearColor(0.0, 0.0, 1.0, 1.0); // Start with blue to indicate no pixels touched.
+gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+var elementArrayBuffer = prepareElementArrayBuffer(gl, /*StartIndex*/0);
+gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_SHORT, /*offset*/0);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Draw call should generate no errors");
+wtu.checkCanvas(gl, [0, 255, 0, 255]);
+
+debug("");
+debug("Test -- Index off the end of the vertex buffer near the beginning of the out of bounds area.");
+gl.uniform1i(loc, 2); // TEST_CASE_OUT_OF_BOUND == 2
+gl.clearColor(0.0, 0.0, 1.0, 1.0); // Start with blue to indicate no pixels touched.
+gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+var outOfBoundsElementArrayBuffer = prepareElementArrayBuffer(gl, /*StartIndex*/4);
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, outOfBoundsElementArrayBuffer);
+gl.bindBuffer(gl.COPY_WRITE_BUFFER, elementArrayBuffer);
+gl.copyBufferSubData(gl.ELEMENT_ARRAY_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 4 * Uint16Array.BYTES_PER_ELEMENT);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "copyBufferSubData should generate no errors");
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementArrayBuffer);
+gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_SHORT, /*offset*/0);
+verifyOutOfBoundsIndex(gl);
+
+debug("");
+debug("Test -- Index off the end of the vertex buffer near the end of the out of bounds area.")
+gl.uniform1i(loc, 2); // TEST_CASE_OUT_OF_BOUND == 2
+gl.clearColor(0.0, 0.0, 1.0, 1.0); // Start with blue to indicate no pixels touched.
+gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+outOfBoundsElementArrayBuffer = prepareElementArrayBuffer(gl, /*StartIndex*/numberOfQuads - 4);
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementArrayBuffer);
+gl.bindBuffer(gl.COPY_READ_BUFFER, outOfBoundsElementArrayBuffer);
+gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.ELEMENT_ARRAY_BUFFER, 0, 0, 4 * Uint16Array.BYTES_PER_ELEMENT);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "copyBufferSubData should generate no errors");
+gl.drawElements(gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_SHORT, /*offset*/0);
+verifyOutOfBoundsIndex(gl);
+
+debug("");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Running tests should generate no errors");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rasterizer-discard-and-implicit-clear.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rasterizer-discard-and-implicit-clear.html
new file mode 100644
index 0000000000..f605a25f26
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rasterizer-discard-and-implicit-clear.html
@@ -0,0 +1,149 @@
+<!--
+Copyright (c) 2020 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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>RASTERIZER_DISCARD doesn't affect implicit clears</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script id="vshader" type="x-shader/x-vertex">#version 300 es
+layout(location=0) in vec2 vPosition;
+uniform float xTranslation;
+void main(void) {
+ gl_Position = vec4(vPosition[0] + xTranslation, vPosition[1], 0.0, 1.0);
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+uniform vec4 color;
+out vec4 outColor;
+void main() {
+ outColor = color;
+}
+</script>
+</head>
+<body>
+<canvas id="example"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+debug("");
+
+description("Enabling RASTERIZER_DISCARD should not affect implicit clears");
+
+const wtu = WebGLTestUtils;
+const canvas = document.getElementById("example");
+const sz = canvas.width = canvas.height = 256;
+const gl = wtu.create3DContext(canvas, undefined, 2);
+const NUM_FRAMES = 15;
+let framesToGo = NUM_FRAMES;
+let xTranslationLoc;
+let colorLoc;
+const positionLocation = 0;
+const red = [ 1.0, 0.0, 0.0, 1.0 ];
+const green = [ 0.0, 1.0, 0.0, 1.0 ];
+const transparentBlackRender = [ 0, 0, 0, 0 ];
+const greenRender = [ 0, 255, 0, 255 ];
+
+if (!gl) {
+ testFailed("WebGL context creation failed");
+ finishTest();
+} else {
+ testPassed("WebGL context creation succeeded");
+ runDrawTest();
+}
+
+function runDrawTest() {
+ debug("Verify that draws with rasterizer discard enabled do not interfere with implicit clears");
+ let prog = wtu.loadProgramFromScript(gl, "vshader", "fshader");
+ gl.useProgram(prog);
+ xTranslationLoc = gl.getUniformLocation(prog, "xTranslation");
+ colorLoc = gl.getUniformLocation(prog, "color");
+ let leftRectBuffer = gl.createBuffer();
+ gl.enableVertexAttribArray(positionLocation);
+ gl.bindBuffer(gl.ARRAY_BUFFER, leftRectBuffer);
+ // Create a rectangle covering the left half of the viewport, in
+ // normalized device coordinates.
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 0.0, 1.0,
+ -1.0, 1.0,
+ -1.0, -1.0,
+ 0.0, 1.0,
+ -1.0, -1.0,
+ 0.0, -1.0]), gl.STATIC_DRAW);
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
+ requestAnimationFrame(renderDrawTestFrame);
+}
+
+function renderDrawTestFrame() {
+ // Animation is required in order to expose this bug. When it
+ // occurs, the rectangle leaves trails behind it.
+ gl.uniform1f(xTranslationLoc, 0.0);
+ gl.enable(gl.RASTERIZER_DISCARD);
+ gl.uniform4fv(colorLoc, red);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ gl.disable(gl.RASTERIZER_DISCARD);
+
+ // Animate the rectangle from the left to the right half of the viewport.
+ gl.uniform1f(xTranslationLoc, (NUM_FRAMES - framesToGo) / NUM_FRAMES);
+ // Draw the last frame with green so any (incorrect) trails are visibly red.
+ if (framesToGo == 0)
+ gl.uniform4fv(colorLoc, green);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ if (framesToGo-- == 0) {
+ // The left half of the canvas should be transparent black,
+ // which comes from the implicit clear just before drawing the
+ // rectangle without rasterizer discard enabled.
+ wtu.checkCanvasRect(gl, 0, 0, sz / 2, sz, transparentBlackRender, "left half of canvas should be clear", 3);
+ // The right half of the canvas should be solid green, from
+ // the last render of the translated rectangle.
+ wtu.checkCanvasRect(gl, sz / 2, 0, sz / 2, sz, greenRender, "right half of canvas should be green", 3);
+ runReadPixelsTest();
+ } else {
+ requestAnimationFrame(renderDrawTestFrame);
+ }
+}
+
+function runReadPixelsTest() {
+ debug("Verify that readPixels with rasterizer discard enabled receives implicitly cleared data");
+ framesToGo = NUM_FRAMES; // Reset state.
+ // Clear to transparent black.
+ gl.clearColor(0.0, 0.0, 0.0, 0.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ // Start with rasterizer discard enabled.
+ gl.enable(gl.RASTERIZER_DISCARD);
+ requestAnimationFrame(renderReadPixelsTestFrame);
+}
+
+function renderReadPixelsTestFrame() {
+ // Rasterizer discard is enabled at the beginning of this test.
+
+ // The canvas should always contain transparent black at the beginning of the frame.
+ wtu.checkCanvasRect(gl, 0, 0, sz, sz, transparentBlackRender, undefined, 3);
+
+ gl.disable(gl.RASTERIZER_DISCARD);
+ // Clear to red.
+ gl.clearColor(1.0, 0.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ // Enable rasterizer discard again.
+ gl.enable(gl.RASTERIZER_DISCARD);
+
+ if (--framesToGo == 0) {
+ finishTest();
+ } else {
+ requestAnimationFrame(renderReadPixelsTestFrame);
+ }
+}
+
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/read-draw-when-missing-image.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/read-draw-when-missing-image.html
new file mode 100644
index 0000000000..a9d8c74b9d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/read-draw-when-missing-image.html
@@ -0,0 +1,288 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Read or Draw when Attachment(s) Miss Image</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="example" width="8" height="8"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+
+var wtu = WebGLTestUtils;
+description("This test verifies the functionality of reading/drawing when color attachment(s) miss image.");
+
+var gl = wtu.create3DContext("example", undefined, 2);
+
+var tex_read = gl.createTexture();
+var tex_draw = gl.createTexture();
+var tex_depth = gl.createTexture();
+var tex_stencil = gl.createTexture();
+var fbo_read = gl.createFramebuffer();
+var fbo_draw = gl.createFramebuffer();
+var size = 8;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ // READ_FRAMEBUFFER has image at COLOR_ATTACHMENT0. READ_FRAMEBUFFER is framebuffer complete.
+ // Read from COLOR_ATTACHMENT1, which has no image attached.
+ init_read_fbo();
+ read();
+
+ // DRAW_FRAMEBUFFER has image at COLOR_ATTACHMENT0. DRAW_FRAMEBUFFER is framebuffer complete.
+ // Clear and draw COLOR_ATTACHMENT1 or COLOR_ATTACHMENT0 + COLOR_ATTACHMENT1 attaching point(s).
+ init_draw_fbo();
+ clear();
+ draw();
+
+ blit();
+}
+
+function init_read_fbo() {
+ gl.bindTexture(gl.TEXTURE_2D, tex_read);
+ wtu.fillTexture(gl, tex_read, size, size, [0x0, 0xff, 0xff, 0xff], 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.RGBA8);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_read, 0);
+ if (gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ } else {
+ testPassed("framebuffer complete!");
+ }
+}
+
+function init_draw_fbo() {
+ gl.bindTexture(gl.TEXTURE_2D, tex_draw);
+ wtu.fillTexture(gl, tex_draw, size, size, [0x0, 0xff, 0xff, 0xff], 0, gl.RGBA, gl.UNSIGNED_BYTE, gl.RGBA8);
+ wtu.fillTexture(gl, tex_depth, size, size, [0x80], 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, gl.DEPTH_COMPONENT16);
+ wtu.fillTexture(gl, tex_stencil, size, size, [0x40], 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, gl.DEPTH24_STENCIL8);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex_draw, 0);
+ if (gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Framebuffer incomplete.");
+ return;
+ } else {
+ testPassed("framebuffer complete!");
+ }
+}
+
+function read() {
+ debug("");
+ debug("read from a color buffer which has no image attached");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+
+ var data = new Uint8Array(size * size * 4);
+ gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when reading from a color buffer without image.");
+
+ var copy_2d = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, copy_2d);
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 0, 0, size, size, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when reading from a color buffer without image.");
+
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, size / 2, size / 2, 0, 0, size / 2, size / 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when reading from a color buffer without image.");
+
+ var copy_3d = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_3D, copy_3d);
+ gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA8, size, size, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, size / 2, size / 2, 0, 0, 0, size / 2, size / 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when reading from a color buffer without image.");
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ gl.deleteTexture(copy_2d);
+ gl.bindTexture(gl.TEXTURE_3D, null);
+ gl.deleteTexture(copy_3d);
+}
+
+function checkTextureValue(fbo, buffer, value) {
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.readBuffer(buffer);
+ wtu.checkCanvas(gl, value);
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+}
+
+function clear() {
+ debug("");
+ debug("clear a color buffer which has no image attached");
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ var color = [0.0, 1.0, 0.0, 1.0];
+ gl.clearColor(color[0], color[1], color[2], color[3]);
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 255, 255]);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 0, 255]);
+
+ var data = new Float32Array(color);
+ gl.clearBufferfv(gl.COLOR, 1, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+}
+
+function draw() {
+ debug("");
+ debug("draw to a color buffer which has no image attached");
+
+ var program = wtu.setupSimpleColorProgram(gl, 0);
+ gl.uniform4f(gl.getUniformLocation(program, "u_color"), 1, 0, 0, 1);
+ wtu.setupUnitQuad(gl, 0);
+
+ // Call to drawArrays
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 0, 255]);
+
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [255, 0, 0, 255]);
+
+ // Call to drawElements
+ gl.uniform4f(gl.getUniformLocation(program, "u_color"), 1, 1, 0, 1);
+ wtu.setupIndexedQuad(gl, 1);
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ wtu.drawIndexedQuad(gl, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [255, 0, 0, 255]);
+
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ wtu.drawIndexedQuad(gl, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 should be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [255, 255, 0, 255]);
+}
+
+function blit() {
+ debug("");
+ debug("blit color buffer(s) which have no image attached");
+ // Some or all draw buffers have no image. Read buffer have image. It should be OK.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 in draw fbo should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [255, 255, 0, 255]);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 in draw fbo should be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 255, 255]);
+
+ // Draw buffer(s) have no image. Read buffer have no image. It should be OK.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ gl.drawBuffers([gl.NONE, gl.COLOR_ATTACHMENT1]);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // The image color at COLOR_ATTACHMENT0 in draw fbo should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 255, 255]);
+
+ // Read buffer have no image. Some or all draw buffers have image. It should generate INVALID_OPERATION.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when read buffer misses image.");
+ // The image color at COLOR_ATTACHMENT0 in draw fbo should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 255, 255]);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when read buffer misses image.");
+ // The image color at COLOR_ATTACHMENT0 in draw fbo should not be changed.
+ checkTextureValue(fbo_draw, gl.COLOR_ATTACHMENT0, [0, 255, 255, 255]);
+
+ // Depth buffer in read fbo has no image. It should generate INVALID_OPERATION if depth buffer in draw fbo has image.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, tex_depth, 0);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when depth buffer misses image.");
+
+ // Depth buffer in read fbo has no image. It should be OK if depth buffer in draw fbo has no image too.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, null, 0);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.DEPTH_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // Validate some other parameters as usual
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.DEPTH_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid filter");
+
+ // Stencil buffer in read fbo has no image. It should generate INVALID_OPERATION if stencil buffer in draw fbo has image.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.TEXTURE_2D, tex_stencil, 0);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.STENCIL_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should generate INVALID_OPERATION when stencil buffer misses image.");
+
+ // Stencil buffer in read fbo has no image. It should be OK if stencil buffer in draw fbo has no image too.
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo_draw);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo_read);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.TEXTURE_2D, null, 0);
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.STENCIL_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no error.");
+ // Validate some other parameters as usual
+ gl.blitFramebuffer(0, 0, size, size, 0, 0, size, size, gl.STENCIL_BUFFER_BIT, gl.LINEAR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "invalid filter");
+}
+
+gl.bindTexture(gl.TEXTURE_2D, null);
+gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+gl.deleteTexture(tex_read);
+gl.deleteTexture(tex_draw);
+gl.deleteTexture(tex_depth);
+gl.deleteTexture(tex_stencil);
+gl.deleteFramebuffer(fbo_read);
+gl.deleteFramebuffer(fbo_draw);
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rgb-format-support.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rgb-format-support.html
new file mode 100644
index 0000000000..46712ae68b
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/rgb-format-support.html
@@ -0,0 +1,111 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+description("Verify RGB/RGB8 textures and renderbuffers support");
+
+var gl = wtu.create3DContext(undefined, undefined, 2);
+
+function testRenderbuffer(width, height) {
+ var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGB8, gl.SAMPLES);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from getInternalformatParameter()");
+
+ if (!samples || samples.length == 0) {
+ testFailed("getInternalformatParameter on RGB8 fails to return valid samples");
+ return;
+ }
+
+ for (var idx = 0; idx < samples.length + 2; ++idx) {
+ debug("");
+ var renderbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+ var sampleCount = 0;
+ switch (idx) {
+ case samples.length:
+ sampleCount = 0;
+ break;
+ case samples.length + 1:
+ sampleCount = -1; // non multisampled
+ break;
+ default:
+ sampleCount = samples[idx];
+ }
+
+ if (sampleCount < 0) {
+ debug("test non-multisampled RGB8 renderbuffer");
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB8, 2, 2);
+ } else {
+ debug("test RGB8 renderbuffer with " + sampleCount + " samples");
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGB8, width, height);
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from renderbufferStorage{Multisample}");
+
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("framebuffer with renderbuffer is incomplete");
+ } else {
+ testPassed("framebuffer with renderbuffer is complete");
+ }
+
+ gl.clearColor(1, 0, 1, 1);
+ gl.clear(gl.COLOR_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear()");
+ }
+}
+
+function testTexture(width, height) {
+ var formats = [ "RGB", "RGB8" ];
+ for (var idx = 0; idx < formats.length; ++idx) {
+ debug("");
+ debug("test texture format " + formats[idx]);
+ var internalformat = gl[formats[idx]];
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalformat, width, height, 0, gl.RGB, gl.UNSIGNED_BYTE, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from texture setup");
+
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("framebuffer with texture is incomplete");
+ } else {
+ testPassed("framebuffer with texture is complete");
+ }
+
+ gl.clearColor(1, 0, 1, 1);
+ gl.clear(gl.COLOR_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear()");
+ }
+}
+
+if (!gl) {
+ testFailed('canvas.getContext() failed');
+} else {
+ testRenderbuffer(2, 2);
+ testTexture(2, 2);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/texture-switch-performance.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/texture-switch-performance.html
new file mode 100644
index 0000000000..da7a9e2f57
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/texture-switch-performance.html
@@ -0,0 +1,101 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 Texture Switch Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Ensures that switching the texture referenced by a sampler uniform performs reasonably well.");
+var wtu = WebGLTestUtils;
+var canvas = document.createElement('canvas');
+canvas.width = 32;
+canvas.height = 32;
+var gl = wtu.create3DContext(canvas, undefined, 2);
+if (!gl) {
+ testFailed("context does not exist");
+ finishTest();
+} else {
+ var program = wtu.setupTexturedQuad(gl);
+ var tex = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ var tex2 = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE1);
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ var loc = gl.getUniformLocation(program, "tex");
+
+ var RUNTIME = 2000;
+ var THRESHOLD = 0.8;
+ var baseStartTime = 0;
+ var baseFrameCount = 0;
+ var testStartTime = 0;
+ var testFrameCount = 0;
+
+ baseStartTime = Date.now();
+ function drawBaseline() {
+ for (var i = 0; i < 400; ++i) {
+ gl.uniform1i(loc, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ gl.uniform1i(loc, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ }
+
+ ++baseFrameCount;
+
+ if (Date.now() - baseStartTime > RUNTIME) {
+ testStartTime = Date.now();
+ requestAnimationFrame(drawTest);
+ } else {
+ requestAnimationFrame(drawBaseline);
+ }
+ }
+
+ function drawTest() {
+ for (var i = 0; i < 400; ++i) {
+ gl.uniform1i(loc, 0);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ gl.uniform1i(loc, 1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ }
+
+ ++testFrameCount;
+
+ if (Date.now() - testStartTime > RUNTIME) {
+ var perfString = " - achieved " + testFrameCount + " frames in " + ((Date.now() - testStartTime) / 1000.0) +
+ " seconds (" + (testFrameCount / baseFrameCount).toFixed(2) + " times baseline performance)";
+ if (testFrameCount > baseFrameCount * THRESHOLD) {
+ testPassed("Texture switching did not significantly hurt performance" + perfString);
+ } else {
+ testFailed("Texture switching significantly hurt performance" + perfString);
+ }
+ console.log(testFrameCount);
+ finishTest();
+ } else {
+ requestAnimationFrame(drawTest);
+ }
+ }
+
+ requestAnimationFrame(drawBaseline);
+}
+var successfullyParsed = true;
+</script>
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/uniform-block-buffer-size.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/uniform-block-buffer-size.html
new file mode 100644
index 0000000000..35cc5205d4
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/uniform-block-buffer-size.html
@@ -0,0 +1,228 @@
+<!--
+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.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL UniformBlock Buffer Size Conformance Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+<script id='vshader' type='x-shader/x-vertex'>#version 300 es
+layout(location=0) in vec3 p;
+void main()
+{
+ gl_Position = vec4(p.xyz, 1.0);
+}
+</script>
+<script id='fshader' type='x-shader/x-fragment'>#version 300 es
+precision mediump float;
+layout(location=0) out vec4 oColor;
+
+uniform UBOData {
+ float UBORed;
+ float UBOGreen;
+ float UBOBlue;
+};
+
+void main()
+{
+ oColor = vec4(UBORed, UBOGreen, UBOBlue, 1.0);
+}
+</script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies an active UniformBlock should be populated with a large enough buffer object");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runDrawArraysTests();
+ runDrawElementsTests();
+}
+
+function runDrawArraysTests() {
+ debug("");
+ debug("Testing drawArrays and drawArraysInstanced");
+
+ var instanceCount = 4;
+
+ var program = wtu.setupProgram(gl, ['vshader', 'fshader']);
+ if (!program) {
+ testFailed("Could not compile shader with uniform blocks without error");
+ return;
+ }
+
+ var blockIndex = gl.getUniformBlockIndex(program, "UBOData");
+ var blockSize = gl.getActiveUniformBlockParameter(program, blockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
+ var uniformIndices = gl.getUniformIndices(program, ["UBORed", "UBOGreen", "UBOBlue"]);
+ var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
+
+ if (uniformOffsets.length < 3) {
+ testFailed("Could not query uniform offsets");
+ return;
+ }
+
+ var uboArray = new ArrayBuffer(blockSize);
+ var uboFloatView = new Float32Array(uboArray);
+ uboFloatView[uniformOffsets[0] / Float32Array.BYTES_PER_ELEMENT] = 1.0; // UBORed
+ uboFloatView[uniformOffsets[1] / Float32Array.BYTES_PER_ELEMENT] = 0.0; // UBOGreen
+ uboFloatView[uniformOffsets[2] / Float32Array.BYTES_PER_ELEMENT] = 0.0; // UBOBlue
+
+ var binding = 1;
+ gl.uniformBlockBinding(program, blockIndex, binding);
+
+ wtu.setupUnitQuad(gl);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Set up succeeded");
+
+ var buffer = gl.createBuffer();
+ gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArrays: UniformBlock is not backed by a buffer");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArraysInstanced: UniformBlock is not backed by a buffer");
+
+ gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArrays: UniformBlock is backed by a buffer with no data store");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArraysInstanced: UniformBlock is backed by a buffer with no data store");
+
+ var arrayNotLargeEnough = new ArrayBuffer(blockSize - 1);
+ gl.bufferData(gl.UNIFORM_BUFFER, arrayNotLargeEnough, gl.DYNAMIC_DRAW);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArrays: UniformBlock not populated with a large enough buffer");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArraysInstanced: UniformBlock not populated with a large enough buffer");
+
+ gl.bufferData(gl.UNIFORM_BUFFER, uboFloatView, gl.DYNAMIC_DRAW);
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArrays: should be able to draw with sufficient data for UniformBlock");
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawArraysInstanced: should be able to draw with sufficient data for UniformBlock");
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
+
+ gl.bindBufferRange(gl.UNIFORM_BUFFER, binding, buffer, 0, blockSize -1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArrays: bindBufferRange set size too small for UniformBlock");
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawArraysInstanced: bindBufferRange set size too small for UniformBlock");
+}
+
+function runDrawElementsTests() {
+ debug("");
+ debug("Testing drawElements, drawRangeElements and drawElementsInstanced");
+
+ var instanceCount = 4;
+
+ var program = wtu.setupProgram(gl, ['vshader', 'fshader']);
+ if (!program) {
+ testFailed("Could not compile shader with uniform blocks without error");
+ return;
+ }
+
+ var blockIndex = gl.getUniformBlockIndex(program, "UBOData");
+ var blockSize = gl.getActiveUniformBlockParameter(program, blockIndex, gl.UNIFORM_BLOCK_DATA_SIZE);
+ var uniformIndices = gl.getUniformIndices(program, ["UBORed", "UBOGreen", "UBOBlue"]);
+ var uniformOffsets = gl.getActiveUniforms(program, uniformIndices, gl.UNIFORM_OFFSET);
+
+ if (uniformOffsets.length < 3) {
+ testFailed("Could not query uniform offsets");
+ return;
+ }
+
+ var uboArray = new ArrayBuffer(blockSize);
+ var uboFloatView = new Float32Array(uboArray);
+ uboFloatView[uniformOffsets[0] / Float32Array.BYTES_PER_ELEMENT] = 1.0; // UBORed
+ uboFloatView[uniformOffsets[1] / Float32Array.BYTES_PER_ELEMENT] = 0.0; // UBOGreen
+ uboFloatView[uniformOffsets[2] / Float32Array.BYTES_PER_ELEMENT] = 0.0; // UBOBlue
+
+ var binding = 1;
+ gl.uniformBlockBinding(program, blockIndex, binding);
+
+ wtu.setupIndexedQuad(gl, 1, 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Set up succeeded");
+
+ var buffer = gl.createBuffer();
+ gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
+
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElements: UniformBlock is not backed by a buffer");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawRangeElements: UniformBlock is not backed by a buffer");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElementsInstanced: UniformBlock is not backed a buffer");
+
+ gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElements: UniformBlock is populated with a buffer with no data store");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawRangeElements: UniformBlock is populated with a buffer with no data store");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElementsInstanced: UniformBlock is populated with a buffer with no data store");
+
+ var arrayNotLargeEnough = new ArrayBuffer(blockSize - 1);
+ gl.bufferData(gl.UNIFORM_BUFFER, arrayNotLargeEnough, gl.DYNAMIC_DRAW);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElements: UniformBlock not populated with a large enough buffer");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawRangeElements: UniformBlock not populated with a large enough buffer");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElementsInstanced: UniformBlock not populated with a large enough buffer");
+
+ gl.bufferData(gl.UNIFORM_BUFFER, uboFloatView, gl.DYNAMIC_DRAW);
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElements: should be able to draw with sufficient data for UniformBlock");
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawRangeElements: should be able to draw with sufficient data for UniformBlock");
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "drawElementsInstanced: should be able to draw with sufficient data for UniformBlock");
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "draw call should set canvas to red", 2);
+
+ gl.bindBufferRange(gl.UNIFORM_BUFFER, binding, buffer, 0, blockSize -1);
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElements: bindBufferRange set size too small for UniformBlock");
+ gl.drawRangeElements(gl.TRIANGLES, 0, 5, 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawRangeElements: bindBufferRange set size too small for UniformBlock");
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0, instanceCount);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "drawElementsInstanced: bindBufferRange set size too small for UniformBlock");
+}
+
+debug("");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id-large-count.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id-large-count.html
new file mode 100644
index 0000000000..a68f5d911c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id-large-count.html
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 gl_VertexID Large Count Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<!-- Shaders for testing instanced draws -->
+<script id="vs" type="text/plain">
+#version 300 es
+
+uniform ivec2 resolution;
+void main() {
+ ivec2 pixel = ivec2(
+ gl_VertexID % resolution.x,
+ gl_VertexID / resolution.x);
+ vec2 xy = (vec2(pixel) + 0.5) / vec2(resolution) * 2.0 - 1.0;
+
+ gl_Position = vec4(xy, 0, 1);
+ gl_PointSize = 1.0;
+}
+</script>
+<script id="fs" type="text/plain">
+#version 300 es
+precision mediump float;
+uniform vec4 color;
+out vec4 fragColor;
+void main() {
+ fragColor = color;
+}
+</script>
+
+<script>
+"use strict";
+description("Test gl_VertexID");
+
+debug("");
+
+const wtu = WebGLTestUtils;
+const canvas = document.createElement("canvas");
+const size = 2048;
+canvas.width = size;
+canvas.height = size;
+const gl = wtu.create3DContext(canvas, null, 2);
+
+// document.body.appendChild(gl.canvas);
+// gl.canvas.style.background = 'yellow';
+// gl.canvas.style.margin = '20px';
+// const ext = gl.getExtension('WEBGL_debug_renderer_info');
+// if (ext) {
+// debug(gl.getParameter(ext.UNMASKED_RENDERER_WEBGL));
+// }
+
+(function() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ const vs = document.getElementById("vs").innerHTML.trim();
+ const fs = document.getElementById("fs").innerHTML.trim();
+ const prog = wtu.loadProgram(gl, vs, fs);
+ gl.useProgram(prog);
+ const resolutionLoc = gl.getUniformLocation(prog, 'resolution');
+ const colorLoc = gl.getUniformLocation(prog, 'color');
+
+ // -
+
+ debug("");
+ debug("----------------");
+ debug("drawArrays");
+
+ const u8Color = c => c.map(v => v * 255 | 0);
+ const transparentBlack = [0, 0, 0, 0];
+ const red = [1, 0, 0, 1];
+ const green = [0, 1, 0, 1];
+ const blue = [0, 0, 1, 1];
+
+ const test = function(first, count, color) {
+ debug("");
+ debug(`drawArrays(first: ${first}, count: ${count})`);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.uniform4fv(colorLoc, color);
+ gl.uniform2i(resolutionLoc, size, size);
+ gl.drawArrays(gl.POINTS, first, count);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ const y = first / size;
+ const height = count / size;
+
+ // The shader draws 1 pixel points by looking at gl_VertexID
+ // as a 1D index into a 2D array. In other words
+ // row = gl_VertexID / width;
+ // col = gl_VertexID % width;
+ // Setting first to a value of rows * width (ie, y * size)
+ // lets us skip y rows when drawing so we then check that
+ // from y to height rows are the expected color and everything
+ // else is the cleared color.
+ wtu.checkCanvasRect(gl, 0, y, size, height, u8Color(color));
+ wtu.checkCanvasRect(gl, 0, 0, size, size - height, transparentBlack);
+ };
+
+ test(0, size * size, red);
+ test(size * size / 2, size * size / 2, green);
+ test(size * size / 4, size * size / 4 * 3, blue);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "There should be no remaining errors");
+})();
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
+
+<!--
+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.
+-->
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id.html b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id.html
new file mode 100644
index 0000000000..ec4bd05ad4
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/vertex-id.html
@@ -0,0 +1,219 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 gl_VertexID Tests</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/desktop-gl-constants.js"></script>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<!-- Shaders for testing instanced draws -->
+<script id="vs" type="text/plain">
+#version 300 es
+flat out highp int vVertexID;
+
+void main() {
+ vVertexID = gl_VertexID;
+ gl_PointSize = 1.0;
+ gl_Position = vec4(0,0,0,1);
+}
+</script>
+<script id="fs" type="text/plain">
+#version 300 es
+flat in highp int vVertexID;
+out highp int oVertexID;
+void main() {
+ oVertexID = vVertexID;
+}
+</script>
+
+<script>
+"use strict";
+description("Test gl_VertexID");
+
+debug("");
+
+const wtu = WebGLTestUtils;
+const canvas = document.createElement("canvas");
+canvas.width = 1;
+canvas.height = 1;
+const gl = wtu.create3DContext(canvas, null, 2);
+
+(function() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ const vs = document.getElementById("vs").innerHTML.trim();
+ const fs = document.getElementById("fs").innerHTML.trim();
+ const prog = wtu.loadProgram(gl, vs, fs);
+ gl.useProgram(prog);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32I, 1, 1);
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No errors after setup");
+
+ function shouldBeVal(prefix, expected, was) {
+ let text = prefix + "Should be " + expected;
+ let func = testPassed;
+ if (was != expected) {
+ text = text + ", was " + was;
+ func = testFailed;
+ }
+ func(text);
+ };
+
+ const readValData = new Int32Array(10000);
+ function ensureVal(prefix, expected) {
+ gl.readPixels(0, 0, 1, 1, gl.RGBA_INTEGER, gl.INT, readValData);
+ const was = readValData[0];
+ shouldBeVal(prefix, expected, was);
+ };
+
+ gl.clearBufferiv(gl.COLOR, 0, new Int32Array([42, 0, 0, 0]));
+ ensureVal("After clear", 42);
+
+ // -
+
+ debug("");
+ debug("----------------");
+ debug("drawArrays");
+
+ let test = function(first, count) {
+ debug("");
+ debug(`drawArrays(first: ${first}, count: ${count})`);
+ gl.drawArrays(gl.POINTS, first, count);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ ensureVal("", first+count-1);
+ };
+
+ test(0, 1);
+ test(1, 1);
+ test(10000, 1);
+ test(100000, 1);
+ test(1000000, 1);
+
+ test(0, 2);
+ test(1, 2);
+ test(10000, 2);
+ test(100000, 2);
+ test(1000000, 2);
+
+ const INT32_MAX = 0x7fffffff;
+
+ test = function(first, count) {
+ debug("");
+ debug(`drawArrays(first: ${first}, count: ${count})`);
+ gl.drawArrays(gl.POINTS, first, count);
+ if (!wtu.glErrorShouldBe(gl, [gl.NO_ERROR, gl.OUT_OF_MEMORY])) {
+ ensureVal("", first+count-1);
+ }
+ };
+
+ test(INT32_MAX-2, 1);
+ test(INT32_MAX-1, 1);
+ test(INT32_MAX, 1);
+
+ // -
+
+ debug("");
+ debug("----------------");
+ debug("drawElements");
+
+ const indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ const indexData = new Uint16Array([1, 2, 5, 3, 10000]);
+ debug("indexData: " + indexData);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);
+
+ test = function(first, count) {
+ debug("");
+ debug(`drawElements(first: ${first}, count: ${count})`);
+ gl.drawElements(gl.POINTS, count, gl.UNSIGNED_SHORT, first*2);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ ensureVal("", indexData[first+count-1]);
+ };
+
+ for (let f = 0; f < indexData.length; ++f) {
+ for (let c = 1; f + c <= indexData.length; ++c) {
+ test(f, c);
+ }
+ }
+
+ // -
+
+ debug("");
+ debug("----------------");
+ debug("Via transform feedback");
+
+ gl.transformFeedbackVaryings(prog, ["vVertexID"], gl.INTERLEAVED_ATTRIBS);
+ wtu.linkProgram(gl, prog);
+ gl.useProgram(prog);
+
+ const tfBuffer = gl.createBuffer();
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, tfBuffer);
+ gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, 4*10000, gl.DYNAMIC_READ);
+
+ test = function(offset, count) {
+ debug("");
+ debug("drawArrays(" + offset + ", " + count + ")");
+ gl.beginTransformFeedback(gl.POINTS);
+ gl.drawArrays(gl.POINTS, offset, count);
+ gl.endTransformFeedback();
+ gl.getBufferSubData(gl.TRANSFORM_FEEDBACK_BUFFER, 0, readValData);
+ let ok = true;
+ for (let i = 0; i < readValData.length; i++) {
+ if (i >= count)
+ break;
+ const expected = offset + i;
+ const was = readValData[i];
+ if (was != expected) {
+ testFailed("[" + i + "] expected " + expected + ", was " + was);
+ ok = false;
+ break;
+ }
+ }
+ if (ok) {
+ testPassed("ok");
+ }
+ };
+
+ test(0, 1);
+ test(1, 1);
+ test(10000, 1);
+
+ test(0, 2);
+ test(1, 2);
+ test(10000, 2);
+
+ test(10000, 10000);
+
+ // -
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "There should be no remaining errors");
+})();
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
+
+<!--
+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.
+-->