summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance2
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-15 03:35:49 +0000
commitd8bbc7858622b6d9c278469aab701ca0b609cddf (patch)
treeeff41dc61d9f714852212739e6b3738b82a2af87 /dom/canvas/test/webgl-conf/checkout/conformance2
parentReleasing progress-linux version 125.0.3-1~progress7.99u1. (diff)
downloadfirefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.tar.xz
firefox-d8bbc7858622b6d9c278469aab701ca0b609cddf.zip
Merging upstream version 126.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/conformance2')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/00_test_list.txt1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/canvas/00_test_list.txt1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/canvas/drawingbuffer-storage-test.html27
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt11
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-conservative-depth.html145
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html58
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-render-snorm.html201
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/nv-shader-noperspective-interpolation.html251
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-sample-variables.html474
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-shader-multisample-interpolation.html313
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-blend-func-extended.html26
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-clip-cull-distance.html475
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-provoking-vertex.html165
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-render-shared-exponent.html251
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-shader-pixel-local-storage.html445
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-stencil-texturing.html279
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/00_test_list.txt1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/texture-bias.html146
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/query/00_test_list.txt1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/query/occlusion-query-scissor.html117
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-object-attachment.html112
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-texture-layer.html31
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/rendering/00_test_list.txt1
-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/textures/misc/00_test_list.txt1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-10bpc.html66
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html1
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-new-formats.html30
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/simultaneous_binding.html11
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/transform_feedback.html66
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/00_test_list.txt12
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-16gb-wasm-memory.html51
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-4gb-wasm-memory.html51
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-16gb-wasm-memory.html52
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-4gb-wasm-memory.html52
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-16gb-wasm-memory.html48
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-4gb-wasm-memory.html48
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-16gb-wasm-memory.html51
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-4gb-wasm-memory.html50
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-16gb-wasm-memory.html85
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-4gb-wasm-memory.html85
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-16gb-wasm-memory.html86
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-4gb-wasm-memory.html86
43 files changed, 4443 insertions, 429 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/00_test_list.txt
index e251dc9758..fabc72c1da 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/00_test_list.txt
@@ -18,3 +18,4 @@ textures/00_test_list.txt
transform_feedback/00_test_list.txt
uniforms/00_test_list.txt
vertex_arrays/00_test_list.txt
+--min-version 2.0.1 wasm/00_test_list.txt
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/00_test_list.txt
index 35e011f3bf..878a2797c4 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/00_test_list.txt
@@ -1 +1,2 @@
+--min-version 2.0.1 drawingbuffer-storage-test.html
--min-version 2.0.1 to-data-url-with-pack-params.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/drawingbuffer-storage-test.html b/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/drawingbuffer-storage-test.html
new file mode 100644
index 0000000000..6f18d441c0
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/canvas/drawingbuffer-storage-test.html
@@ -0,0 +1,27 @@
+<!--
+Copyright (c) 2023 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 drawingBufferStorage 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/drawingbuffer-storage-test.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+runTest(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/extensions/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt
index 559071ff06..2cc4456ecb 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt
@@ -1,11 +1,16 @@
ext-color-buffer-float.html
--min-version 2.0.1 ext-color-buffer-half-float.html
+--min-version 2.0.1 ext-conservative-depth.html
ext-disjoint-timer-query-webgl2.html
+--min-version 2.0.1 ext-render-snorm.html
--min-version 2.0.1 ext-texture-filter-anisotropic.html
--min-version 2.0.1 ext-texture-norm16.html
promoted-extensions.html
promoted-extensions-in-shaders.html
+--min-version 2.0.1 nv-shader-noperspective-interpolation.html
--min-version 2.0.1 oes-draw-buffers-indexed.html
+--min-version 2.0.1 oes-sample-variables.html
+--min-version 2.0.1 oes-shader-multisample-interpolation.html
--min-version 2.0.1 ovr_multiview2.html
--min-version 2.0.1 ovr_multiview2_depth.html
--min-version 2.0.1 ovr_multiview2_draw_buffers.html
@@ -16,4 +21,10 @@ promoted-extensions-in-shaders.html
--min-version 2.0.1 ovr_multiview2_timer_query.html
--min-version 2.0.1 ovr_multiview2_transform_feedback.html
--min-version 2.0.1 required-extensions.html
+--min-version 2.0.1 webgl-blend-func-extended.html
+--min-version 2.0.1 webgl-clip-cull-distance.html
--min-version 2.0.1 webgl-multi-draw-instanced-base-vertex-base-instance.html
+--min-version 2.0.1 webgl-provoking-vertex.html
+--min-version 2.0.1 webgl-render-shared-exponent.html
+--min-version 2.0.1 webgl-shader-pixel-local-storage.html
+--min-version 2.0.1 webgl-stencil-texturing.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-conservative-depth.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-conservative-depth.html
new file mode 100644
index 0000000000..c9c9f85bdb
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-conservative-depth.html
@@ -0,0 +1,145 @@
+<!--
+Copyright (c) 2023 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 EXT_conservative_depth 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>
+<canvas width="32" height="32" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the EXT_conservative_depth extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", null, 2);
+var ext;
+
+function runShaderTests(extensionEnabled) {
+ debug("");
+ debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+ const macro = `#version 300 es
+ precision mediump float;
+ out vec4 my_FragColor;
+ void main() {
+ #ifdef GL_EXT_conservative_depth
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ #else
+ #error no GL_EXT_conservative_depth;
+ #endif
+ }`;
+
+ const missingExtension = `#version 300 es
+ precision mediump float;
+ out vec4 my_FragColor;
+ layout (depth_any) out float gl_FragDepth;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_FragDepth = 1.0;
+ }`;
+
+ const valid = `#version 300 es
+ #extension GL_EXT_conservative_depth : enable
+ precision mediump float;
+ out vec4 my_FragColor;
+ layout (depth_any) out float gl_FragDepth;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_FragDepth = 1.0;
+ }`;
+
+ const invalid = `#version 300 es
+ #extension GL_EXT_conservative_depth : enable
+ precision mediump float;
+ out vec4 my_FragColor;
+ layout (depth_unchanged) out float gl_FragDepth;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_FragDepth = 1.0;
+ }`;
+
+ // Always expect the shader missing the #extension pragma to fail (whether enabled or not)
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, missingExtension])) {
+ testFailed("Depth layout qualifier allowed without #extension pragma");
+ } else {
+ testPassed("Depth layout qualifier disallowed without #extension pragma");
+ }
+
+ // Expect the macro shader to succeed ONLY if enabled
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, macro])) {
+ if (extensionEnabled) {
+ testPassed("Macro defined in shaders when extension is enabled");
+ } else {
+ testFailed("Macro defined in shaders when extension is disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Macro not defined in shaders when extension is enabled");
+ } else {
+ testPassed("Macro not defined in shaders when extension is disabled");
+ }
+ }
+
+ // Try to compile a shader using a layout qualifier that should only succeed if enabled
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, valid])) {
+ if (extensionEnabled) {
+ testPassed("Depth layout qualifier compiled successfully when extension enabled");
+ } else {
+ testFailed("Depth layout qualifier compiled successfully when extension disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Depth layout qualifier failed to compile when extension enabled");
+ } else {
+ testPassed("Depth layout qualifier failed to compile when extension disabled");
+ }
+ }
+
+ // Try to compile a shader using a disallowed layout qualifier
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, invalid])) {
+ testFailed("Unsupported depth layout qualifier compiled successfully");
+ } else {
+ testPassed("Unsupported depth layout qualifier failed to compile");
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ runShaderTests(false);
+
+ ext = gl.getExtension("EXT_conservative_depth");
+ wtu.runExtensionSupportedTest(gl, "EXT_conservative_depth", ext !== null);
+
+ if (!ext) {
+ testPassed("No EXT_conservative_depth support -- this is legal");
+ } else {
+ testPassed("Successfully enabled EXT_conservative_depth extension");
+ runShaderTests(true);
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html
index c051fa36a3..f1e9a82d8a 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html
@@ -25,7 +25,9 @@ description("This test verifies the functionality of the EXT_disjoint_timer_quer
var wtu = WebGLTestUtils;
var canvas = document.getElementById("canvas");
var gl = wtu.create3DContext(canvas, null, 2);
+var gl2 = null;
var ext = null;
+var ext2 = null;
var query = null;
var query2 = null;
var elapsed_query = null;
@@ -62,6 +64,8 @@ if (!gl) {
wtu.glErrorShouldBe(gl, gl.NO_ERROR);
}
verifyQueryResultsNotAvailable();
+ verifyDeleteQueryBehavior();
+ verifyDeleteQueryErrorBehavior();
wtu.glErrorShouldBe(gl, gl.NO_ERROR);
window.requestAnimationFrame(checkQueryResults);
@@ -249,6 +253,60 @@ function verifyQueryResultsNotAvailable() {
testPassed("Queries' results didn't become available in a spin loop");
}
+function verifyDeleteQueryBehavior() {
+ debug("");
+ debug("Testing deleting an active query should end it.");
+
+ // Use a new context for this test
+ gl2 = wtu.create3DContext(null, null, 2);
+ if (!gl2) return;
+ ext2 = gl2.getExtension("EXT_disjoint_timer_query_webgl2");
+ if (!ext2) return;
+
+ query = gl2.createQuery();
+ gl2.beginQuery(ext.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl2, gl2.NONE, "The query began successfully");
+ gl2.deleteQuery(query);
+ wtu.glErrorShouldBe(gl2, gl2.NONE, "Deletion of the active query succeeds");
+ shouldBeNull("gl2.getQuery(ext2.TIME_ELAPSED_EXT, gl2.CURRENT_QUERY)");
+ shouldBeFalse("gl2.isQuery(query)");
+ query = gl2.createQuery();
+ gl2.beginQuery(ext2.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl, gl2.NONE, "Beginning a new query succeeds");
+ gl2.endQuery(gl2.TIME_ELAPSED_EXT);
+ gl2.deleteQuery(query);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ query = null;
+ ext2 = null;
+ gl2 = null;
+}
+
+function verifyDeleteQueryErrorBehavior() {
+ debug("");
+ debug("Testing deleting a query created by another context.");
+
+ // Use new contexts for this test
+ gl2 = wtu.create3DContext(null, null, 2);
+ var gl3 = wtu.create3DContext(null, null, 2);
+ if (!gl2 || !gl3) return;
+ ext2 = gl2.getExtension("EXT_disjoint_timer_query_webgl2");
+ if (!ext2) return;
+
+ query = gl2.createQuery();
+ gl2.beginQuery(ext2.TIME_ELAPSED_EXT, query);
+ gl3.deleteQuery(query);
+ wtu.glErrorShouldBe(gl3, gl3.INVALID_OPERATION);
+ shouldBeTrue("gl2.isQuery(query)");
+ shouldBe("gl2.getQuery(ext2.TIME_ELAPSED_EXT, gl2.CURRENT_QUERY)", "query");
+ gl2.endQuery(ext2.TIME_ELAPSED_EXT);
+ gl2.deleteQuery(query);
+ wtu.glErrorShouldBe(gl2, gl2.NONE);
+ query = null;
+ ext2 = null;
+ gl2 = null;
+ gl3 = null;
+}
+
function checkQueryResults() {
if (availability_retry > 0) {
// Make a reasonable attempt to wait for the queries' results to become available.
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-render-snorm.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-render-snorm.html
new file mode 100644
index 0000000000..723e762773
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-render-snorm.html
@@ -0,0 +1,201 @@
+<!--
+Copyright (c) 2023 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 EXT_render_snorm 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("This test verifies the functionality of the EXT_render_snorm extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext(null, null, 2);
+var ext;
+
+function createTypedArray(type) {
+ switch (type) {
+ case gl.BYTE:
+ return new Int8Array(4);
+ case gl.UNSIGNED_BYTE:
+ return new Uint8Array(4);
+ case gl.SHORT:
+ return new Int16Array(4);
+ case gl.UNSIGNED_SHORT:
+ return new Uint16Array(4);
+ default:
+ return null;
+ }
+}
+
+function drawTest(config) {
+ wtu.drawUnitQuad(gl);
+
+ const implementationType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
+ const implementationFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
+
+ // Support for reading signed data with unsigned read type is not required
+ // but implementations may allow such conversions. Do not expect the error
+ // when the type matches the buffer type or when it's explicitly supported.
+ for (const type of [gl.BYTE, gl.UNSIGNED_BYTE, gl.SHORT, gl.UNSIGNED_SHORT]) {
+ if (type == config.type) continue;
+ if (implementationFormat != gl.RGBA || implementationType != type) {
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, type, createTypedArray(type));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "reading with unsupported type fails");
+ }
+ }
+
+ const defaultPixel = createTypedArray(config.type);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, config.color,
+ "reading with the RGBA format and matching type", 1,
+ defaultPixel,
+ config.type, gl.RGBA);
+
+ if (implementationFormat == config.format && implementationType == config.type) {
+ const implementationPixel = createTypedArray(implementationType);
+ const color = [config.color[0]];
+ if (config.format != gl.RED) color.push(config.color[1]);
+ if (config.format == gl.RGBA) color.push(config.color[2], config.color[3]);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, color,
+ "reading with the exact format/type", 1,
+ implementationPixel,
+ implementationType, implementationFormat);
+ }
+}
+
+function renderbufferTest(config, isSupported) {
+ debug("");
+ debug(`${config.name} renderbuffer: ` +
+ `${!isSupported || !config.color ? "NOT " : ""}supported`);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorage(gl.RENDERBUFFER, config.internalFormat, 1, 1);
+ if (!isSupported || !config.color) {
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "renderbuffer allocation failed");
+ return;
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "renderbuffer allocation succeeded");
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ drawTest(config);
+}
+
+function textureTest(config, isRenderable, isTexturable) {
+ debug("");
+ debug(`${config.name} texture: ` +
+ `${!isRenderable || !config.color ? "NOT " : ""}renderable, ` +
+ `${!isTexturable ? "NOT " : ""}texturable`);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, config.internalFormat, 1, 1, 0, config.format, config.type, null);
+ if (!isTexturable) {
+ wtu.glErrorShouldBe(gl,
+ [gl.INVALID_ENUM, gl.INVALID_VALUE, gl.INVALID_OPERATION],
+ "texture allocation failed");
+ return;
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture allocation succeeded");
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ if (!isRenderable || !config.color) {
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+ return;
+ }
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ drawTest(config);
+}
+
+function formatTest(isSnormEnabled, isNorm16Enabled) {
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShader,
+ wtu.simpleColorFragmentShader]);
+ gl.useProgram(program);
+ gl.uniform4f(gl.getUniformLocation(program, "u_color"), -0.0625, -0.125, -0.25, -0.5);
+
+ wtu.setupUnitQuad(gl);
+
+ const configs8 = [
+ {name: "R8_SNORM", format: gl.RED, type: gl.BYTE, internalFormat: gl.R8_SNORM, color: [-8, 0, 0, 127]},
+ {name: "RG8_SNORM", format: gl.RG, type: gl.BYTE, internalFormat: gl.RG8_SNORM, color: [-8, -16, 0, 127]},
+ {name: "RGB8_SNORM", format: gl.RGB, type: gl.BYTE, internalFormat: gl.RGB8_SNORM, color: null},
+ {name: "RGBA8_SNORM", format: gl.RGBA, type: gl.BYTE, internalFormat: gl.RGBA8_SNORM, color: [-8, -16, -32, -64]}
+ ];
+
+ const configs16 = [
+ {name: "R16_SNORM", format: gl.RED, type: gl.SHORT, internalFormat: 0x8F98 /* R16_SNORM_EXT */, color: [-2048, 0, 0, 32767]},
+ {name: "RG16_SNORM", format: gl.RG, type: gl.SHORT, internalFormat: 0x8F99 /* RG16_SNORM_EXT */, color: [-2048, -4096, 0, 32767]},
+ {name: "RGB16_SNORM", format: gl.RGB, type: gl.SHORT, internalFormat: 0x8F9A /* RGB16_SNORM_EXT */, color: null},
+ {name: "RGBA16_SNORM", format: gl.RGBA, type: gl.SHORT, internalFormat: 0x8F9B /* RGBA16_SNORM_EXT */, color: [-2048, -4096, -8192, -16384]}
+ ];
+
+ for (const config of configs8) {
+ renderbufferTest(config, isSnormEnabled);
+ textureTest(config, isSnormEnabled, true);
+ }
+
+ for (const config of configs16) {
+ renderbufferTest(config, isSnormEnabled && isNorm16Enabled);
+ textureTest(config, isSnormEnabled && isNorm16Enabled, isNorm16Enabled);
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ return;
+ }
+
+ testPassed("context exists");
+
+ debug("");
+ debug("Testing signed normalized formats with EXT_render_snorm disabled");
+ formatTest(false, false);
+
+ ext = gl.getExtension("EXT_render_snorm");
+ wtu.runExtensionSupportedTest(gl, "EXT_render_snorm", ext !== null);
+
+ if (ext !== null) {
+ debug("");
+ debug("Testing signed normalized formats with only EXT_render_snorm enabled");
+ formatTest(true, false);
+
+ if (gl.getExtension("EXT_texture_norm16")) {
+ debug("");
+ debug("Testing signed normalized formats with EXT_render_snorm and EXT_texture_norm16 enabled");
+ formatTest(true, true);
+ }
+ } else {
+ testPassed("No EXT_render_snorm support -- this is legal");
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/nv-shader-noperspective-interpolation.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/nv-shader-noperspective-interpolation.html
new file mode 100644
index 0000000000..2198c17b1a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/nv-shader-noperspective-interpolation.html
@@ -0,0 +1,251 @@
+<!--
+Copyright (c) 2023 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 NV_shader_noperspective_interpolation 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>
+<canvas width="128" height="128" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the NV_shader_noperspective_interpolation extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", null, 2);
+var ext;
+
+function runShaderTests(extensionEnabled) {
+ debug("");
+ debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+ const macroVertex = `#version 300 es
+ in vec4 vPosition;
+ void main() {
+ #ifdef GL_NV_shader_noperspective_interpolation
+ gl_Position = vPosition;
+ #else
+ #error no GL_NV_shader_noperspective_interpolation;
+ #endif
+ }`;
+
+ const macroFragment = `#version 300 es
+ precision highp float;
+ out vec4 my_FragColor;
+ void main() {
+ #ifdef GL_NV_shader_noperspective_interpolation
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ #else
+ #error no GL_NV_shader_noperspective_interpolation;
+ #endif
+ }`;
+
+ for (const shaders of [[wtu.simpleVertexShaderESSL300, macroFragment],
+ [macroVertex, wtu.simpleColorFragmentShaderESSL300]]) {
+ // Expect the macro shader to succeed ONLY if enabled
+ if (wtu.setupProgram(gl, shaders)) {
+ if (extensionEnabled) {
+ testPassed("Macro defined in shaders when extension is enabled");
+ } else {
+ testFailed("Macro defined in shaders when extension is disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Macro not defined in shaders when extension is enabled");
+ } else {
+ testPassed("Macro not defined in shaders when extension is disabled");
+ }
+ }
+ }
+
+ const missingVertex = `#version 300 es
+ noperspective out float interpolant;
+ in vec4 vPosition;
+ void main() {
+ gl_Position = vPosition;
+ }`;
+
+ const missingFragment = `#version 300 es
+ precision highp float;
+ noperspective in float interpolant;
+ out vec4 my_FragColor;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ }`;
+
+ // Always expect the shader missing the #extension pragma to fail (whether enabled or not)
+ for (const shaders of [[missingVertex, wtu.simpleColorFragmentShaderESSL300],
+ [wtu.simpleVertexShaderESSL300, missingFragment],
+ [missingVertex, missingFragment]]) {
+ if (wtu.setupProgram(gl, shaders)) {
+ testFailed("Noperspective interpolation qualifier allowed without #extension pragma");
+ } else {
+ testPassed("Noperspective interpolation qualifier disallowed without #extension pragma");
+ }
+ }
+
+ const validVertex = `#version 300 es
+ #extension GL_NV_shader_noperspective_interpolation : enable
+ noperspective out float interpolant;
+ in vec4 vPosition;
+ void main() {
+ gl_Position = vPosition;
+ }`;
+
+ const validFragment = `#version 300 es
+ #extension GL_NV_shader_noperspective_interpolation : enable
+ precision highp float;
+ noperspective in float interpolant;
+ out vec4 my_FragColor;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ }`;
+
+ // Try to compile a shader using a noperspective qualifier that should only succeed if enabled
+ if (wtu.setupProgram(gl, [validVertex, validFragment])) {
+ if (extensionEnabled) {
+ testPassed("Noperspective interpolation qualifier compiled successfully when extension enabled");
+ } else {
+ testFailed("Noperspective interpolation qualifier compiled successfully when extension disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Noperspective interpolation qualifier failed to compile when extension enabled");
+ } else {
+ testPassed("Noperspective interpolation qualifier failed to compile when extension disabled");
+ }
+ }
+
+ debug("");
+}
+
+function runInterpolationTest() {
+ function draw(program, skew) {
+ gl.useProgram(program);
+
+ const posLoc = gl.getAttribLocation(program, "position");
+ const colLoc = gl.getAttribLocation(program, "color");
+
+ const buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+ gl.bufferData(
+ gl.ARRAY_BUFFER,
+ new Float32Array([
+ -1.0, -1.0, 0.0, 1.0,
+ +1.0, -1.0, 0.0, 1.0,
+ 0.0, +1.0 * skew, 0.0, skew,
+
+ 1.0, 0.0, 0.0, 1.0,
+ 0.0, 1.0, 0.0, 1.0,
+ 0.0, 0.0, 1.0, 1.0]),
+ gl.STATIC_DRAW);
+
+ gl.vertexAttribPointer(posLoc, 4, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribPointer(colLoc, 4, gl.FLOAT, false, 0, 48);
+ gl.enableVertexAttribArray(posLoc);
+ gl.enableVertexAttribArray(colLoc);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ }
+
+ const vertexSmooth = `#version 300 es
+ in vec4 position;
+ in vec4 color;
+ smooth out vec4 interp_color;
+ void main() {
+ gl_Position = position;
+ interp_color = color;
+ }`;
+
+ const fragmentSmooth = `#version 300 es
+ precision highp float;
+ smooth in vec4 interp_color;
+ out vec4 fragColor;
+ void main() {
+ fragColor = interp_color;
+ }`;
+ const programSmooth = wtu.setupProgram(gl, [vertexSmooth, fragmentSmooth]);
+
+ debug("Get non-skewed value with smooth interpolation");
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ draw(programSmooth, 1.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const smoothColor = new Uint8Array(4);
+ gl.readPixels(64, 64, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, smoothColor);
+
+ const vertexNoperspective = `#version 300 es
+ #extension GL_NV_shader_noperspective_interpolation : require
+ in vec4 position;
+ in vec4 color;
+ noperspective out vec4 interp_color;
+ void main() {
+ gl_Position = position;
+ interp_color = color;
+ }`;
+
+ const fragmentNoperspective = `#version 300 es
+ #extension GL_NV_shader_noperspective_interpolation : require
+ precision highp float;
+ noperspective in vec4 interp_color;
+ out vec4 fragColor;
+ void main() {
+ fragColor = interp_color;
+ }`;
+ const programNoperspective = wtu.setupProgram(gl, [vertexNoperspective, fragmentNoperspective]);
+
+ debug("");
+ debug("Check non-skewed value with noperspective interpolation");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ draw(programNoperspective, 1.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ wtu.checkCanvasRect(gl, 64, 64, 1, 1, smoothColor, "Non-skewed noperspective should match smooth");
+
+ debug("");
+ debug("Check skewed value with noperspective interpolation");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ draw(programNoperspective, 2.0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ wtu.checkCanvasRect(gl, 64, 64, 1, 1, smoothColor, "Skewed noperspective should match smooth");
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ runShaderTests(false);
+
+ ext = gl.getExtension("NV_shader_noperspective_interpolation");
+ wtu.runExtensionSupportedTest(gl, "NV_shader_noperspective_interpolation", ext !== null);
+
+ if (!ext) {
+ testPassed("No NV_shader_noperspective_interpolation support -- this is legal");
+ } else {
+ testPassed("Successfully enabled NV_shader_noperspective_interpolation extension");
+ runShaderTests(true);
+ runInterpolationTest();
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-sample-variables.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-sample-variables.html
new file mode 100644
index 0000000000..41fc8f8242
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-sample-variables.html
@@ -0,0 +1,474 @@
+<!--
+Copyright (c) 2023 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 OES_sample_variables 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>
+<canvas width="32" height="32" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_sample_variables extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", { antialias: false }, 2);
+var ext;
+
+function runShaderTests(extensionEnabled) {
+ debug("");
+ debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+ const macro = `#version 300 es
+ precision highp float;
+ out vec4 my_FragColor;
+ void main() {
+ #ifdef GL_OES_sample_variables
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ #else
+ #error no GL_OES_sample_variables;
+ #endif
+ }`;
+
+ // Expect the macro shader to succeed ONLY if enabled
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, macro])) {
+ if (extensionEnabled) {
+ testPassed("Macro defined in shaders when extension is enabled");
+ } else {
+ testFailed("Macro defined in shaders when extension is disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Macro not defined in shaders when extension is enabled");
+ } else {
+ testPassed("Macro not defined in shaders when extension is disabled");
+ }
+ }
+
+ const missing = `#version 300 es
+ precision highp float;
+ out vec4 my_FragColor;
+ void main() {
+ gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
+ my_FragColor = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples));
+ }`;
+
+ // Always expect the shader missing the #extension pragma to fail (whether enabled or not)
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, missing])) {
+ testFailed("Sample variables allowed without #extension pragma");
+ } else {
+ testPassed("Sample variables disallowed without #extension pragma");
+ }
+
+ const valid = `#version 300 es
+ #extension GL_OES_sample_variables : enable
+ precision highp float;
+ out vec4 my_FragColor;
+ void main() {
+ gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
+ my_FragColor = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples));
+ }`;
+
+ // Try to compile a shader using sample variables that should only succeed if enabled
+ if (wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, valid])) {
+ if (extensionEnabled) {
+ testPassed("Sample variables compiled successfully when extension enabled");
+ } else {
+ testFailed("Sample variables compiled successfully when extension disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Sample variables failed to compile when extension enabled");
+ } else {
+ testPassed("Sample variables failed to compile when extension disabled");
+ }
+ }
+
+ debug("");
+}
+
+function runMaxSamplesTest() {
+ debug("");
+ debug("Testing gl_MaxSamples");
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ out vec4 color;
+ void main() {
+ color = vec4(float(gl_MaxSamples * 4) / 255.0, 0.0, 0.0, 1.0);
+ }`;
+ gl.useProgram(wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]));
+
+ wtu.setupUnitQuad(gl);
+ wtu.drawUnitQuad(gl);
+
+ wtu.checkCanvas(gl, [gl.getParameter(gl.MAX_SAMPLES) * 4, 0, 0, 255], "should match MAX_SAMPLES", 1);
+}
+
+function runNumSamplesTest() {
+ debug("");
+ debug("Testing gl_NumSamples");
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 32, 32);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ out vec4 color;
+ void main() {
+ if (gl_NumSamples == 4) {
+ color = vec4(0.0, 1.0, 0.0, 1.0);
+ } else {
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ }
+ }`;
+ gl.useProgram(wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]));
+
+ wtu.setupUnitQuad(gl);
+ wtu.drawUnitQuad(gl);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+function runSampleIDTest() {
+ debug("");
+ debug("Testing gl_SampleID");
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ out vec4 color;
+ uniform int id;
+ void main() {
+ // Special value when the selected sample is processed, 0.0 otherwise
+ float r = float(gl_SampleID == id ? (1 << gl_SampleID) : 0) * 32.0 / 255.0;
+ // Must always be 0.0
+ float g = float(gl_SampleID < 0 || gl_SampleID >= gl_NumSamples);
+ color = vec4(r, g, 0.0, 1.0);
+ }`;
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]);
+ gl.useProgram(program);
+
+ wtu.setupUnitQuad(gl);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 32, 32);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ for (let sample = 0; sample < 4; sample++) {
+ debug(`Sample ${sample} is selected`);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo);
+ gl.uniform1i(gl.getUniformLocation(program, "id"), sample);
+ wtu.drawUnitQuad(gl);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [(1 << sample) * 8, 0, 0, 255], undefined, 1);
+ }
+}
+
+function runSampleMaskInTest() {
+ debug("");
+ debug("Testing gl_SampleMaskIn");
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ out vec4 color;
+ uint popcount(uint v) {
+ uint c = 0u;
+ for (; v != 0u; v >>= 1) c += v & 1u;
+ return c;
+ }
+ void main() {
+ float r = float(popcount(uint(gl_SampleMaskIn[0])));
+ color = vec4(r * 4.0 / 255.0, 0, 0, 1);
+ }`;
+
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]);
+ gl.useProgram(program);
+
+ // Use a triangle instead of the WTU's quad
+ // to avoid artifacts along the diagonal
+ const vertices = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertices);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1.0, 1.0,
+ 1.0, -1.0,
+ -1.0, -1.0]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ function test(sampleCount, sampleCoverageEnabled, coverage) {
+ if (sampleCoverageEnabled) {
+ gl.enable(gl.SAMPLE_COVERAGE);
+ } else {
+ gl.disable(gl.SAMPLE_COVERAGE);
+ }
+
+ gl.sampleCoverage(coverage, false);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ // Shader scales up the number of input samples to increase precision in unorm8 space.
+ let expected = Math.max(sampleCount, 1) * 4;
+
+ // Sample coverage must not affect single sampled buffers
+ if (sampleCoverageEnabled && sampleCount > 0) {
+ // The number of samples in gl_SampleMaskIn must be affected by the sample
+ // coverage GL state and then the resolved value must be scaled down again.
+ expected *= coverage * coverage;
+ }
+
+ // Check only the red channel
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ const pixel = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
+ const message = `Expected: ${expected}, Actual: ${pixel[0]}, ` +
+ `Samples: ${sampleCount}, Sample Coverage: ${sampleCoverageEnabled}, Coverage: ${coverage}`;
+ if (Math.abs(pixel[0] - expected) > 2) {
+ testFailed(message);
+ } else {
+ testPassed(message);
+ }
+ }
+
+ // Include all exposed sample counts and additionally test single-sampled rendering
+ const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0];
+
+ for (const sampleCount of sampleCounts) {
+ if (sampleCount > 32) {
+ // This test will not work with more than 32 samples.
+ continue;
+ }
+
+ for (const sampleCoverageEnabled of [false, true]) {
+ for (const coverage of [0.0, 0.5, 1.0]) {
+ if (sampleCount == 1 && coverage != 0.0 && coverage != 1.0) {
+ continue;
+ }
+ test(sampleCount, sampleCoverageEnabled, coverage);
+ }
+ }
+ }
+}
+
+function runSampleMaskInPerSampleTest() {
+ debug("");
+ debug("Testing gl_SampleMaskIn with per-sample shading");
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ out vec4 color;
+ void main() {
+ float r = float(gl_SampleMaskIn[0] == (1 << gl_SampleID));
+ color = vec4(r, 0, 0, 1);
+ }`;
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]);
+ gl.useProgram(program);
+
+ wtu.setupUnitQuad(gl);
+
+ // Include all exposed sample counts and additionally test single-sampled rendering
+ const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0];
+ for (const sampleCount of sampleCounts) {
+ if (sampleCount > 32) {
+ // This test will not work with more than 32 samples.
+ continue;
+ }
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo);
+ wtu.drawUnitQuad(gl);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ wtu.checkCanvas(gl, [255, 0, 0, 255], `Samples: ${sampleCount}`, 1);
+ }
+}
+
+function runSampleMaskTest() {
+ debug("");
+ debug("Testing gl_SampleMask");
+
+ const frag = `#version 300 es
+ #extension GL_OES_sample_variables : require
+ precision highp float;
+ uniform highp int sampleMask;
+ out vec4 color;
+ void main() {
+ gl_SampleMask[0] = sampleMask;
+ color = vec4(1, 0, 0, 1);
+ }`;
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, frag]);
+ gl.useProgram(program);
+
+ // Use a triangle instead of the WTU's quad
+ // to avoid artifacts along the diagonal
+ const vertices = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertices);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1.0, 1.0,
+ 1.0, -1.0,
+ -1.0, -1.0]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ function test(sampleCount, sampleMask) {
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, sampleCount, gl.RGBA8, 32, 32);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fbo);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.uniform1i(gl.getUniformLocation(program, "sampleMask"), sampleMask);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 32, 32, 0, 0, 32, 32, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+ let expected = 1.0;
+ if (sampleCount > 0) {
+ let mask = sampleMask & ((1 << Math.max(sampleCount, 1)) - 1);
+ let bits = 0;
+ for (; mask != 0; mask >>= 1) bits += mask & 1;
+ expected = bits / Math.max(sampleCount, 1);
+ }
+ expected *= 255;
+
+ // Check only the red channel
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ const pixel = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
+ const message = `Samples: ${sampleCount}, `
+ + `gl_SampleMask[0]: 0x${sampleMask.toString(16).padStart(8, "0").toUpperCase()}, `
+ + `Actual: ${pixel[0]}, Expected: ${expected}`;
+ if (Math.abs(pixel[0] - expected) > 2) {
+ testFailed(message);
+ } else {
+ testPassed(message);
+ }
+ }
+
+ // Include all exposed sample counts and additionally test single-sampled rendering
+ const sampleCounts = [...gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES), 0];
+
+ for (const sampleCount of sampleCounts) {
+ if (sampleCount > 31) {
+ // This test will not work with more than 31 samples.
+ continue;
+ }
+
+ for (const sampleMask of [0xFFFFFFFF, 0x55555555, 0xAAAAAAAA, 0x00000000]) {
+ test(sampleCount, sampleMask);
+ }
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ runShaderTests(false);
+
+ ext = gl.getExtension("OES_sample_variables");
+ wtu.runExtensionSupportedTest(gl, "OES_sample_variables", ext !== null);
+
+ if (!ext) {
+ testPassed("No OES_sample_variables support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OES_sample_variables extension");
+ runShaderTests(true);
+
+ debug("Testing sample variables");
+ runMaxSamplesTest();
+ runNumSamplesTest();
+ runSampleIDTest();
+ runSampleMaskInTest();
+ runSampleMaskInPerSampleTest();
+ runSampleMaskTest();
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-shader-multisample-interpolation.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-shader-multisample-interpolation.html
new file mode 100644
index 0000000000..dcb272c2f0
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-shader-multisample-interpolation.html
@@ -0,0 +1,313 @@
+<!--
+Copyright (c) 2023 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 OES_shader_multisample_interpolation 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>
+<canvas width="32" height="32" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_shader_multisample_interpolation extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", { antialias: false }, 2);
+var ext;
+
+function runShaderTests(extensionEnabled) {
+ debug("");
+ debug("Testing various shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+ const macroVertex = `#version 300 es
+ in vec4 vPosition;
+ void main() {
+ #ifdef GL_OES_shader_multisample_interpolation
+ gl_Position = vPosition;
+ #else
+ #error no GL_OES_shader_multisample_interpolation;
+ #endif
+ }`;
+
+ const macroFragment = `#version 300 es
+ precision highp float;
+ out vec4 my_FragColor;
+ void main() {
+ #ifdef GL_OES_shader_multisample_interpolation
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ #else
+ #error no GL_OES_shader_multisample_interpolation;
+ #endif
+ }`;
+
+ for (const shaders of [[wtu.simpleVertexShaderESSL300, macroFragment],
+ [macroVertex, wtu.simpleColorFragmentShaderESSL300]]) {
+ // Expect the macro shader to succeed ONLY if enabled
+ if (wtu.setupProgram(gl, shaders)) {
+ if (extensionEnabled) {
+ testPassed("Macro defined in shaders when extension is enabled");
+ } else {
+ testFailed("Macro defined in shaders when extension is disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Macro not defined in shaders when extension is enabled");
+ } else {
+ testPassed("Macro not defined in shaders when extension is disabled");
+ }
+ }
+ }
+
+ const missingVertex = `#version 300 es
+ sample out float interpolant;
+ in vec4 vPosition;
+ void main() {
+ gl_Position = vPosition;
+ }`;
+
+ const missingFragment = `#version 300 es
+ precision highp float;
+ sample in float interpolant;
+ out vec4 my_FragColor;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ }`;
+
+ // Always expect the shader missing the #extension pragma to fail (whether enabled or not)
+ for (const shaders of [[missingVertex, wtu.simpleColorFragmentShaderESSL300],
+ [wtu.simpleVertexShaderESSL300, missingFragment],
+ [missingVertex, missingFragment]]) {
+ if (wtu.setupProgram(gl, shaders)) {
+ testFailed("Sample interpolation qualifier allowed without #extension pragma");
+ } else {
+ testPassed("Sample interpolation qualifier disallowed without #extension pragma");
+ }
+ }
+
+ const validVertex = `#version 300 es
+ #extension GL_OES_shader_multisample_interpolation : enable
+ sample out float interpolant;
+ in vec4 vPosition;
+ void main() {
+ gl_Position = vPosition;
+ }`;
+
+ const validFragment = `#version 300 es
+ #extension GL_OES_shader_multisample_interpolation : enable
+ precision highp float;
+ sample in float interpolant;
+ out vec4 my_FragColor;
+ void main() {
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ }`;
+
+ // Try to compile a shader using a sample qualifier that should only succeed if enabled
+ if (wtu.setupProgram(gl, [validVertex, validFragment])) {
+ if (extensionEnabled) {
+ testPassed("Sample interpolation qualifier compiled successfully when extension enabled");
+ } else {
+ testFailed("Sample interpolation qualifier compiled successfully when extension disabled");
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed("Sample interpolation qualifier failed to compile when extension enabled");
+ } else {
+ testPassed("Sample interpolation qualifier failed to compile when extension disabled");
+ }
+ }
+}
+
+function runQueryTests(extensionEnabled) {
+ debug("");
+ debug("Testing parameters with extension " + (extensionEnabled ? "enabled" : "disabled"));
+ if (extensionEnabled) {
+ shouldBeGreaterThanOrEqual("gl.getParameter(ext.FRAGMENT_INTERPOLATION_OFFSET_BITS_OES)", "4");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const limit = 0.5 - Math.pow(2, -gl.getParameter(ext.FRAGMENT_INTERPOLATION_OFFSET_BITS_OES));
+ shouldBeLessThanOrEqual("gl.getParameter(ext.MIN_FRAGMENT_INTERPOLATION_OFFSET_OES)", `-${limit}`);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBeGreaterThanOrEqual("gl.getParameter(ext.MAX_FRAGMENT_INTERPOLATION_OFFSET_OES)", `${limit}`);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ } else {
+ shouldBeNull("gl.getParameter(0x8E5B /* MIN_FRAGMENT_INTERPOLATION_OFFSET_OES */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBeNull("gl.getParameter(0x8E5C /* MAX_FRAGMENT_INTERPOLATION_OFFSET_OES */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBeNull("gl.getParameter(0x8E5D /* FRAGMENT_INTERPOLATION_OFFSET_BITS_OES */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ }
+}
+
+function checkEnums() {
+ debug("");
+ debug("Check enums");
+ shouldBe("ext.MIN_FRAGMENT_INTERPOLATION_OFFSET_OES", "0x8E5B");
+ shouldBe("ext.MAX_FRAGMENT_INTERPOLATION_OFFSET_OES", "0x8E5C");
+ shouldBe("ext.FRAGMENT_INTERPOLATION_OFFSET_BITS_OES", "0x8E5D");
+}
+
+/*
+ * This test renders a triangle using MSAAx4 and 1x1 viewport
+ * with the following vertex colors.
+ *
+ * | Position | Color |
+ * |==========|===========|
+ * | (-1, -1) | (0, 0, 0) |
+ * | (-1, +1) | (0, 1, 0) |
+ * | (+1, -1) | (1, 0, 0) |
+ *
+ * This triangle cannot cover all four samples.
+ *
+ * When default interpolation is used, the vertex color is interpolated
+ * once, most likely in the pixel center.
+ *
+ * When per-sample interpolation is used, the vertex color is interpolated
+ * several times, producing a distinct value for each covered sample.
+ * Due to the asymmetry of sample positions, the resolved pixel color must
+ * not match the color produced by default interpolation.
+ *
+ * OpenGL specs do not guarantee specific sample positions, so the test
+ * checks only that the resolved colors are different.
+ */
+function runInterpolationTest() {
+ debug("");
+ debug("Testing multisample interpolation");
+
+ function draw(program) {
+ gl.viewport(0, 0, 1, 1);
+ gl.useProgram(program);
+
+ const posLoc = gl.getAttribLocation(program, "position");
+ const buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+ gl.bufferData(
+ gl.ARRAY_BUFFER,
+ new Float32Array([
+ -1.0, -1.0,
+ -1.0, +1.0,
+ +1.0, -1.0]),
+ gl.STATIC_DRAW);
+
+ gl.vertexAttribPointer(posLoc, 2, gl.FLOAT, false, 0, 0);
+ gl.enableVertexAttribArray(posLoc);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, 4, gl.RGBA8, 1, 1);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, fbo);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, null);
+ }
+
+ const vertexCenter = `#version 300 es
+ in vec4 position;
+ out vec4 interp_color;
+ void main() {
+ gl_Position = position;
+ interp_color = vec4(position.xy * 0.5 + 0.5, 0.0, 1.0);
+ }`;
+
+ const fragmentCenter = `#version 300 es
+ precision highp float;
+ in vec4 interp_color;
+ out vec4 fragColor;
+ void main() {
+ fragColor = interp_color;
+ }`;
+ const programCenter = wtu.setupProgram(gl, [vertexCenter, fragmentCenter]);
+
+ draw(programCenter);
+ const centerColor = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, centerColor);
+
+ const vertexSample = `#version 300 es
+ #extension GL_OES_shader_multisample_interpolation : require
+ in vec4 position;
+ sample out vec4 interp_color;
+ void main() {
+ gl_Position = position;
+ interp_color = vec4(position.xy * 0.5 + 0.5, 0.0, 1.0);
+ }`;
+
+ const fragmentSample = `#version 300 es
+ #extension GL_OES_shader_multisample_interpolation : require
+ precision highp float;
+ sample in vec4 interp_color;
+ out vec4 fragColor;
+ void main() {
+ fragColor = interp_color;
+ }`;
+ const programSample = wtu.setupProgram(gl, [vertexSample, fragmentSample]);
+
+ draw(programSample);
+ const sampleColor = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, sampleColor);
+
+ const message = `Pixel-center value: ${centerColor}, sample-average value: ${sampleColor}`;
+ if (centerColor[0] == sampleColor[0] && centerColor[1] == sampleColor[1]) {
+ testFailed(message);
+ } else {
+ testPassed(message);
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("WebGL context does not exist");
+ return;
+ }
+ testPassed("WebGL context exists");
+
+ runQueryTests(false);
+ runShaderTests(false);
+
+ debug("");
+ ext = gl.getExtension("OES_shader_multisample_interpolation");
+ wtu.runExtensionSupportedTest(gl, "OES_shader_multisample_interpolation", ext !== null);
+
+ if (!ext) {
+ testPassed("No OES_shader_multisample_interpolation support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OES_shader_multisample_interpolation extension");
+ runQueryTests(true);
+ runShaderTests(true);
+ runInterpolationTest();
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-blend-func-extended.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-blend-func-extended.html
new file mode 100644
index 0000000000..792e9aafa7
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-blend-func-extended.html
@@ -0,0 +1,26 @@
+<!--
+Copyright (c) 2023 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.0 WEBGL_blend_func_extended 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>
+<canvas width="32" height="32" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+var contextVersion = 2;
+</script>
+<script src="../../js/tests/webgl-blend-func-extended.js"></script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-clip-cull-distance.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-clip-cull-distance.html
new file mode 100644
index 0000000000..cb2253c326
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-clip-cull-distance.html
@@ -0,0 +1,475 @@
+<!--
+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 WEBGL_clip_cull_distance 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>
+<canvas width="32" height="32" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_clip_cull_distance extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", null, 2);
+var ext;
+const w = gl.drawingBufferWidth;
+const h = gl.drawingBufferHeight;
+
+function runTestNoExtension() {
+ debug("");
+ debug("Check parameters and capabilities without the extension");
+
+ shouldBeNull("gl.getParameter(0x0D32 /* MAX_CLIP_DISTANCES_WEBGL */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ shouldBeNull("gl.getParameter(0x82F9 /* MAX_CULL_DISTANCES_WEBGL */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ shouldBeNull("gl.getParameter(0x82FA /* MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const assertState = (i) => {
+ shouldBeFalse(`gl.isEnabled(${0x3000 + i} /* CLIP_DISTANCE${i}_WEBGL */)`);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+
+ shouldBeNull(`gl.getParameter(${0x3000 + i} /* CLIP_DISTANCE${i}_WEBGL */)`);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ };
+
+ for (let i = 0; i < 8; i++) {
+ assertState(i);
+
+ gl.enable(0x3000 + i /* CLIP_DISTANCEi_WEBGL */);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "capability unknown without enabling the extension");
+
+ assertState(i);
+
+ gl.disable(0x3000 + i /* CLIP_DISTANCEi_WEBGL */);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "capability unknown without enabling the extension");
+
+ assertState(i);
+ }
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ debug("");
+}
+
+function checkEnums() {
+ debug("");
+ debug("Check enums");
+ shouldBe("ext.MAX_CLIP_DISTANCES_WEBGL", "0x0D32");
+ shouldBe("ext.MAX_CULL_DISTANCES_WEBGL", "0x82F9");
+ shouldBe("ext.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL", "0x82FA");
+ shouldBe("ext.CLIP_DISTANCE0_WEBGL", "0x3000");
+ shouldBe("ext.CLIP_DISTANCE1_WEBGL", "0x3001");
+ shouldBe("ext.CLIP_DISTANCE2_WEBGL", "0x3002");
+ shouldBe("ext.CLIP_DISTANCE3_WEBGL", "0x3003");
+ shouldBe("ext.CLIP_DISTANCE4_WEBGL", "0x3004");
+ shouldBe("ext.CLIP_DISTANCE5_WEBGL", "0x3005");
+ shouldBe("ext.CLIP_DISTANCE6_WEBGL", "0x3006");
+ shouldBe("ext.CLIP_DISTANCE7_WEBGL", "0x3007");
+}
+
+function checkQueries() {
+ debug("");
+ debug("Check parameters");
+ shouldBeGreaterThanOrEqual('gl.getParameter(ext.MAX_CLIP_DISTANCES_WEBGL)', '8');
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const maxCullDistances = gl.getParameter(ext.MAX_CULL_DISTANCES_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ if (maxCullDistances == 0) {
+ testPassed("No cull distance support");
+ shouldBe("gl.getParameter(ext.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL)", "0");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ } else if (maxCullDistances >= 8) {
+ testPassed("Optional cull distance support");
+ shouldBeGreaterThanOrEqual("gl.getParameter(ext.MAX_COMBINED_CLIP_AND_CULL_DISTANCES_WEBGL)", "8");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ } else {
+ testFailed("Invalid number of supported cull distances");
+ }
+
+ debug("");
+ debug("Check clip distance capabilities");
+
+ const assertState = (i, s) => {
+ shouldBe(`gl.isEnabled(${0x3000 + i} /* CLIP_DISTANCE${i}_WEBGL */)`, s ? "true" : "false");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ shouldBe(`gl.getParameter(${0x3000 + i} /* CLIP_DISTANCE${i}_WEBGL */)`, s ? "true" : "false");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ };
+
+ for (let i = 0; i < 8; i++) {
+ assertState(i, false);
+
+ gl.enable(ext.CLIP_DISTANCE0_WEBGL + i);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ assertState(i, true);
+
+ gl.disable(ext.CLIP_DISTANCE0_WEBGL + i);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ assertState(i, false);
+ }
+}
+
+function checkClipDistance() {
+ debug("");
+ debug("Check clip distance operation");
+
+ const vs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+
+uniform vec4 u_plane;
+in vec2 a_position;
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+ gl_ClipDistance[0] = dot(gl_Position, u_plane);
+}`;
+
+ const program = wtu.setupProgram(gl, [vs, wtu.simpleColorFragmentShaderESSL300]);
+ gl.useProgram(program);
+ gl.uniform4fv(gl.getUniformLocation(program, 'u_color'), [1.0, 0.0, 0.0, 1.0]);
+
+ gl.enable(ext.CLIP_DISTANCE0_WEBGL);
+
+ // Clear to blue
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.setupUnitQuad(gl);
+
+ // Draw full screen quad with color red
+ gl.uniform4f(gl.getUniformLocation(program, "u_plane"), 1, 0, 0, 0.5);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ // All pixels on the left of the plane x = -0.5 must be blue
+ let x = 0;
+ let y = 0;
+ let width = w / 4 - 1;
+ let height = h;
+ wtu.checkCanvasRect(gl, x, y, width, height,
+ [0, 0, 255, 255], "should be blue");
+
+ // All pixels on the right of the plane x = -0.5 must be red
+ x = w / 4 + 2;
+ y = 0;
+ width = w - x;
+ height = h;
+ wtu.checkCanvasRect(gl, x, y, width, height,
+ [255, 0, 0, 255], "should be red");
+
+ // Clear to green
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Draw full screen quad with color red
+ gl.uniform4f(gl.getUniformLocation(program, "u_plane"), -1, 0, 0, -0.5);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ // All pixels on the left of the plane x = -0.5 must be red
+ x = 0;
+ y = 0;
+ width = w / 4 - 1;
+ height = h;
+ wtu.checkCanvasRect(gl, x, y, width, height,
+ [255, 0, 0, 255], "should be red");
+
+ // All pixels on the right of the plane x = -0.5 must be green
+ x = w / 4 + 2;
+ y = 0;
+ width = w - x;
+ height = h;
+ wtu.checkCanvasRect(gl, x, y, width, height,
+ [0, 255, 0, 255], "should be green");
+
+ // Disable CLIP_DISTANCE0 and draw again
+ gl.disable(ext.CLIP_DISTANCE0_WEBGL);
+ wtu.drawUnitQuad(gl);
+
+ // All pixels must be red
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "should be red");
+}
+
+function checkClipDistanceInterpolation() {
+ debug("");
+ debug("Check clip distance interpolation");
+
+ const vs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+in vec2 a_position;
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+ gl_ClipDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 0.5));
+ gl_ClipDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 0.5));
+ gl_ClipDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 0.5));
+ gl_ClipDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 0.5));
+ gl_ClipDistance[4] = gl_ClipDistance[0];
+ gl_ClipDistance[5] = gl_ClipDistance[1];
+ gl_ClipDistance[6] = gl_ClipDistance[2];
+ gl_ClipDistance[7] = gl_ClipDistance[3];
+}`;
+
+ const fs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+precision highp float;
+out vec4 my_FragColor;
+void main()
+{
+ float r = gl_ClipDistance[0] + gl_ClipDistance[1];
+ float g = gl_ClipDistance[2] + gl_ClipDistance[3];
+ float b = gl_ClipDistance[4] + gl_ClipDistance[5];
+ float a = gl_ClipDistance[6] + gl_ClipDistance[7];
+ my_FragColor = vec4(r, g, b, a) * 0.5;
+}`;
+
+ const program = wtu.setupProgram(gl, [vs, fs]);
+ gl.useProgram(program);
+
+ gl.enable(ext.CLIP_DISTANCE0_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE1_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE2_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE3_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE4_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE5_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE6_WEBGL);
+ gl.enable(ext.CLIP_DISTANCE7_WEBGL);
+
+ wtu.setupUnitQuad(gl);
+
+ // Clear to blue
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Draw full screen quad with color gray
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const data = new Uint8Array(w * h * 4);
+ gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ let passed = true;
+ for (let x = 0; x < w; x++) {
+ for (let y = 0; y < h; y++) {
+ const currentPosition = (y * h + x) * 4;
+ const inside = (x >= w / 4 && x < w * 3 / 4 && y >= h / 4 && y < h * 3 / 4);
+ const expected = inside ? [127, 127, 127, 127] : [0, 0, 255, 255];
+ const actual = data.slice(currentPosition, currentPosition + 4);
+ if (Math.abs(actual[0] - expected[0]) > 1 ||
+ Math.abs(actual[1] - expected[1]) > 1 ||
+ Math.abs(actual[2] - expected[2]) > 1 ||
+ Math.abs(actual[3] - expected[3]) > 1) {
+ passed = false;
+ }
+ }
+ }
+ if (passed) {
+ testPassed("Correct clip distance interpolation");
+ } else {
+ testFailed("Incorrect clip distance interpolation");
+ }
+}
+
+function checkCullDistance() {
+ debug("");
+ debug("Check cull distance operation");
+
+ if (gl.getParameter(ext.MAX_CULL_DISTANCES_WEBGL) == 0) {
+ testPassed("No cull distance support");
+ return;
+ }
+
+ const vs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+
+uniform vec4 u_plane;
+in vec2 a_position;
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+ gl_CullDistance[0] = dot(gl_Position, u_plane);
+}`;
+
+ const program = wtu.setupProgram(gl, [vs, wtu.simpleColorFragmentShaderESSL300]);
+ gl.useProgram(program);
+ gl.uniform4fv(gl.getUniformLocation(program, 'u_color'), [1.0, 0.0, 0.0, 1.0]);
+
+ // Clear to blue
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.setupUnitQuad(gl);
+
+ // Draw full screen quad with color red
+ gl.uniform4f(gl.getUniformLocation(program, "u_plane"), 1, 0, 0, 0.5);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ // All pixels must be red
+ wtu.checkCanvas(gl, [255, 0, 0, 255], "should be red");
+
+ // Clear to green
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Draw full screen quad with color red
+ gl.uniform4f(gl.getUniformLocation(program, "u_plane"), -1, 1, 0, -0.5);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ // All pixels above the y > x line must be red
+ const data = new Uint8Array(w * h * 4);
+ gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ let passed = true;
+ for (let x = 0; x < w; ++x) {
+ for (let y = 0; y < h; ++y) {
+ if (y <= x + 2 && y >= x - 2) continue; // skip the edge
+ const currentPosition = (y * h + x) * 4;
+ const actual = data.slice(currentPosition, currentPosition + 2);
+ const expected = (y > x) ? [255, 0] : [0, 255];
+ if (actual[0] != expected[0] || actual[1] != expected[1]) {
+ passed = false;
+ }
+ }
+ }
+ if (passed) {
+ testPassed("Correct cull distance operation");
+ } else {
+ testFailed("Incorrect cull distance operation");
+ }
+}
+
+function checkCullDistanceInterpolation() {
+ debug("");
+ debug("Check cull distance interpolation");
+
+ if (gl.getParameter(ext.MAX_CULL_DISTANCES_WEBGL) == 0) {
+ testPassed("No cull distance support");
+ return;
+ }
+
+ const vs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+in vec2 a_position;
+void main()
+{
+ gl_Position = vec4(a_position, 0.0, 1.0);
+ gl_CullDistance[0] = dot(gl_Position, vec4( 1, 0, 0, 1));
+ gl_CullDistance[1] = dot(gl_Position, vec4(-1, 0, 0, 1));
+ gl_CullDistance[2] = dot(gl_Position, vec4( 0, 1, 0, 1));
+ gl_CullDistance[3] = dot(gl_Position, vec4( 0, -1, 0, 1));
+ gl_CullDistance[4] = gl_CullDistance[0];
+ gl_CullDistance[5] = gl_CullDistance[1];
+ gl_CullDistance[6] = gl_CullDistance[2];
+ gl_CullDistance[7] = gl_CullDistance[3];
+}`;
+
+ const fs = `#version 300 es
+#extension GL_ANGLE_clip_cull_distance : require
+precision highp float;
+out vec4 my_FragColor;
+void main()
+{
+ float r = gl_CullDistance[0] + gl_CullDistance[1];
+ float g = gl_CullDistance[2] + gl_CullDistance[3];
+ float b = gl_CullDistance[4] + gl_CullDistance[5];
+ float a = gl_CullDistance[6] + gl_CullDistance[7];
+ my_FragColor = vec4(r, g, b, a) * 0.25;
+}`;
+
+ const program = wtu.setupProgram(gl, [vs, fs]);
+ gl.useProgram(program);
+
+ // Clear to blue
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.setupQuad(gl, {scale: 0.5});
+
+ // Draw a small quad with color gray
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ const data = new Uint8Array(w * h * 4);
+ gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
+ let passed = true;
+ for (let x = 0; x < w; x++) {
+ for (let y = 0; y < h; y++) {
+ const currentPosition = (y * h + x) * 4;
+ const inside = (x >= w / 4 && x < w * 3 / 4 && y >= h / 4 && y < h * 3 / 4);
+ const expected = inside ? [127, 127, 127, 127] : [0, 0, 255, 255];
+ const actual = data.slice(currentPosition, currentPosition + 4);
+ if (Math.abs(actual[0] - expected[0]) > 1 ||
+ Math.abs(actual[1] - expected[1]) > 1 ||
+ Math.abs(actual[2] - expected[2]) > 1 ||
+ Math.abs(actual[3] - expected[3]) > 1) {
+ passed = false;
+ }
+ }
+ }
+ if (passed) {
+ testPassed("Correct cull distance interpolation");
+ } else {
+ testFailed("Incorrect cull distance interpolation");
+ }
+}
+
+function runTestExtension() {
+ checkEnums();
+ checkQueries();
+
+ checkClipDistance();
+ checkClipDistanceInterpolation();
+
+ checkCullDistance();
+ checkCullDistanceInterpolation();
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ } else {
+ testPassed("context exists");
+
+ runTestNoExtension();
+
+ ext = gl.getExtension("WEBGL_clip_cull_distance");
+
+ wtu.runExtensionSupportedTest(gl, "WEBGL_clip_cull_distance", ext !== null);
+
+ if (ext !== null) {
+ runTestExtension();
+ } else {
+ testPassed("No WEBGL_clip_cull_distance support -- this is legal");
+ }
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-provoking-vertex.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-provoking-vertex.html
new file mode 100644
index 0000000000..3737409b3a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-provoking-vertex.html
@@ -0,0 +1,165 @@
+<!--
+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 WEBGL_provoking_vertex 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>
+<canvas width="16" height="16" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_provoking_vertex extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", null, 2);
+var ext;
+
+function runTestNoExtension() {
+ debug("");
+ debug("Check getParameter without the extension");
+ shouldBeNull("gl.getParameter(0x8E4F /* PROVOKING_VERTEX_WEBGL */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ debug("");
+}
+
+function runTestExtension() {
+ debug("");
+ debug("Check enums");
+ shouldBe("ext.FIRST_VERTEX_CONVENTION_WEBGL", "0x8E4D");
+ shouldBe("ext.LAST_VERTEX_CONVENTION_WEBGL", "0x8E4E");
+ shouldBe("ext.PROVOKING_VERTEX_WEBGL", "0x8E4F");
+
+ debug("");
+ debug("Check default state");
+ shouldBe("gl.getParameter(ext.PROVOKING_VERTEX_WEBGL)", "ext.LAST_VERTEX_CONVENTION_WEBGL");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "parameter known with the extension enabled");
+
+ debug("");
+ debug("Check state updates");
+ ext.provokingVertexWEBGL(ext.FIRST_VERTEX_CONVENTION_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "provokingVertexWEBGL(ext.FIRST_VERTEX_CONVENTION_WEBGL) generates no errors");
+ shouldBe("gl.getParameter(ext.PROVOKING_VERTEX_WEBGL)", "ext.FIRST_VERTEX_CONVENTION_WEBGL");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ ext.provokingVertexWEBGL(ext.LAST_VERTEX_CONVENTION_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "provokingVertexWEBGL(ext.LAST_VERTEX_CONVENTION_WEBGL) generates no errors");
+ shouldBe("gl.getParameter(ext.PROVOKING_VERTEX_WEBGL)", "ext.LAST_VERTEX_CONVENTION_WEBGL");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Check invalid provoking vertex mode");
+ ext.provokingVertexWEBGL(ext.FIRST_VERTEX_CONVENTION_WEBGL);
+ ext.provokingVertexWEBGL(ext.PROVOKING_VERTEX_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "invalid provoking mode generates an error");
+ shouldBe("gl.getParameter(ext.PROVOKING_VERTEX_WEBGL)", "ext.FIRST_VERTEX_CONVENTION_WEBGL");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Check provoking vertex operation");
+
+ const vs = `#version 300 es
+ in int intAttrib;
+ in vec2 position;
+ flat out int attrib;
+ void main() {
+ gl_Position = vec4(position, 0, 1);
+ attrib = intAttrib;
+ }`;
+
+ const fs = `#version 300 es
+ flat in int attrib;
+ out int fragColor;
+ void main() {
+ fragColor = attrib;
+ }`;
+
+ const program = wtu.setupProgram(gl, [vs, fs]);
+ gl.useProgram(program);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R32I, 16, 16);
+
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ const vb = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vb);
+ const buf = new ArrayBuffer(36);
+ new Float32Array(buf, 0, 6).set([-1.0, -1.0, 3.0, -1.0, -1.0, 3.0]);
+ new Int32Array(buf, 24, 3).set([1, 2, 3]);
+ gl.bufferData(gl.ARRAY_BUFFER, buf, gl.STATIC_DRAW);
+
+ const positionLocation = gl.getAttribLocation(program, "position");
+ gl.enableVertexAttribArray(positionLocation);
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
+
+ const intAttribLocation = gl.getAttribLocation(program, "intAttrib");
+ gl.enableVertexAttribArray(intAttribLocation);
+ gl.vertexAttribIPointer(intAttribLocation, 1, gl.INT, 0, 24);
+
+ const pixel = new Int32Array(4);
+
+ ext.provokingVertexWEBGL(ext.LAST_VERTEX_CONVENTION_WEBGL);
+ gl.clearBufferiv(gl.COLOR, 0, new Int32Array(4));
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA_INTEGER, gl.INT, pixel);
+
+ if (pixel[0] == 3) {
+ testPassed("Correct last provoking vertex");
+ } else {
+ testFailed("Incorrect last provoking vertex");
+ }
+
+ ext.provokingVertexWEBGL(ext.FIRST_VERTEX_CONVENTION_WEBGL);
+ gl.clearBufferiv(gl.COLOR, 0, new Int32Array(4));
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA_INTEGER, gl.INT, pixel);
+
+ if (pixel[0] == 1) {
+ testPassed("Correct first provoking vertex");
+ } else {
+ testFailed("Incorrect first provoking vertex");
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ } else {
+ testPassed("context exists");
+
+ runTestNoExtension();
+
+ ext = gl.getExtension("WEBGL_provoking_vertex");
+
+ wtu.runExtensionSupportedTest(gl, "WEBGL_provoking_vertex", ext !== null);
+
+ if (ext !== null) {
+ runTestExtension();
+ } else {
+ testPassed("No WEBGL_provoking_vertex support -- this is legal");
+ }
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-render-shared-exponent.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-render-shared-exponent.html
new file mode 100644
index 0000000000..11d505fcc6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-render-shared-exponent.html
@@ -0,0 +1,251 @@
+<!--
+Copyright (c) 2023 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 WEBGL_render_shared_exponent 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("This test verifies the functionality of the WEBGL_render_shared_exponent extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext(null, null, 2);
+var ext;
+const color = [64.0, 32.0, 16.0, 1.0];
+
+function drawTest() {
+ wtu.clearAndDrawUnitQuad(gl);
+
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, color,
+ "reading with the RGBA format and FLOAT type", 1,
+ new Float32Array(4), gl.FLOAT, gl.RGBA);
+
+ const implementationType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
+ const implementationFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
+ if (implementationFormat == gl.RGB && implementationType == gl.UNSIGNED_INT_5_9_9_9_REV) {
+ // Shared exponent value may be implementation
+ // specific, so compare decoded values.
+ const value = new Uint32Array(1);
+ gl.readPixels(0, 0, 1, 1, gl.RGB, gl.UNSIGNED_INT_5_9_9_9_REV, value);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ let r = (value >> 0) & 0x1FF;
+ let g = (value >> 9) & 0x1FF;
+ let b = (value >> 18) & 0x1FF;
+ let e = (value >> 27) & 0x01F;
+ debug(`Raw value: 0x${value[0].toString(16).toUpperCase()}, ` +
+ `Raw components: R = ${r}, G = ${g}, B = ${b}, E = ${e}`);
+
+ e = Math.pow(2, e - 24);
+ r *= e;
+ g *= e;
+ b *= e;
+ debug(`Decoded color: (${r}, ${g}, ${b})`);
+
+ if (r == color[0] && g == color[1] && b == color[2]) {
+ testPassed("reading with the exact format/type");
+ } else {
+ testFailed("reading with the exact format/type");
+ }
+ }
+}
+
+function renderbufferTest(isSupported) {
+ debug("");
+ debug(`RGB9_E5 renderbuffer: ` +
+ `${!isSupported ? "NOT " : ""}supported`);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB9_E5, 1, 1);
+ if (!isSupported) {
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "renderbuffer allocation failed");
+ return;
+ }
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "renderbuffer allocation succeeded");
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ drawTest();
+}
+
+function textureTest(isRenderable) {
+ debug("");
+ debug(`RGB9_E5 texture: ` +
+ `${!isRenderable ? "NOT " : ""}renderable`);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB9_E5, 1, 1, 0, gl.RGB, gl.UNSIGNED_INT_5_9_9_9_REV, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture allocation succeeded");
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ if (!isRenderable) {
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT);
+ return;
+ }
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ drawTest();
+}
+
+function formatTest(isEnabled) {
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShader,
+ wtu.simpleColorFragmentShader]);
+ gl.useProgram(program);
+ gl.uniform4fv(gl.getUniformLocation(program, "u_color"), color);
+
+ wtu.setupUnitQuad(gl);
+
+ renderbufferTest(isEnabled);
+ textureTest(isEnabled);
+}
+
+function colorMaskTest() {
+ debug("");
+ debug("Test color write masks with shared exponent color buffers");
+
+ const fs = `#version 300 es
+ precision highp float;
+ layout(location = 0) out vec4 color0;
+ layout(location = 1) out vec4 color1;
+ void main() {
+ color0 = vec4(1.0, 0.0, 0.0, 1.0);
+ color1 = vec4(0.0, 1.0, 0.0, 1.0);
+ }`;
+ const program = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, fs]);
+ gl.useProgram(program);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+
+ const rb0 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb0);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB9_E5, 4, 4);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rb0);
+
+ const rb1 = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rb1);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 4, 4);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.RENDERBUFFER, rb1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ const clearValue = new Float32Array(4);
+ const dbiExt = gl.getExtension("OES_draw_buffers_indexed");
+
+ function expectError(enabled, effectiveMask, operation) {
+ if (!enabled ||
+ effectiveMask == 0x0 /* 0000 */ ||
+ effectiveMask == 0x8 /* 000A */ ||
+ effectiveMask == 0x7 /* RGB0 */ ||
+ effectiveMask == 0xF /* RGBA */ ) {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, operation);
+ } else {
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, operation);
+ }
+ }
+
+ function runOps(enabled, mask0) {
+ wtu.drawUnitQuad(gl);
+ expectError(enabled, mask0, "draw");
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ expectError(enabled, mask0, "clear");
+
+ gl.clearBufferfv(gl.COLOR, 0, clearValue);
+ expectError(enabled, mask0, "clearBufferfv(RGB9_E5)");
+ gl.clearBufferfv(gl.COLOR, 1, clearValue);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "clearBufferfv(RGBA8)");
+ }
+
+ for (let mask = 0; mask < 16; mask++) {
+ for (const enabled of [false, true]) {
+ debug("");
+ debug(`Setting common color mask ` +
+ `${mask & 1 ? "R" : "0"}` +
+ `${mask & 2 ? "G" : "0"}` +
+ `${mask & 4 ? "B" : "0"}` +
+ `${mask & 8 ? "A" : "0"}` +
+ " with RGB9_E5 attachment " +
+ (enabled ? "enabled" : "disabled"));
+ gl.colorMask(mask & 1, mask & 2, mask & 4, mask & 8);
+ gl.drawBuffers([enabled ? gl.COLOR_ATTACHMENT0 : gl.NONE,
+ gl.COLOR_ATTACHMENT1]);
+
+ runOps(enabled, mask);
+
+ if (dbiExt) {
+ debug("Setting incompatible color mask on unused draw buffer")
+ dbiExt.colorMaskiOES(2, true, false, false, false);
+ runOps(enabled, mask); // common mask remains on draw buffer 0
+
+ debug("Setting incompatible color mask on RGBA8 draw buffer")
+ dbiExt.colorMaskiOES(1, true, false, false, false);
+ runOps(enabled, mask); // common mask remains on draw buffer 0
+
+ debug("Setting incompatible color mask on RGB9_E5 draw buffer")
+ dbiExt.colorMaskiOES(0, true, false, false, false);
+ runOps(enabled, 1); // overridden
+
+ debug("Setting compatible color mask on RGB9_E5 draw buffer")
+ dbiExt.colorMaskiOES(0, true, true, true, false);
+ runOps(enabled, 7); // overridden
+ }
+ }
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ return;
+ }
+ testPassed("context exists");
+
+ debug("");
+ debug("Testing shared exponent rendering with extension disabled");
+ formatTest(false);
+
+ ext = gl.getExtension("WEBGL_render_shared_exponent");
+ wtu.runExtensionSupportedTest(gl, "WEBGL_render_shared_exponent", ext !== null);
+
+ if (ext !== null) {
+ debug("");
+ debug("Testing shared exponent rendering with extension enabled");
+ formatTest(true);
+ colorMaskTest();
+ } else {
+ testPassed("No WEBGL_render_shared_exponent support -- this is legal");
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-shader-pixel-local-storage.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-shader-pixel-local-storage.html
new file mode 100644
index 0000000000..e548eea46c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-shader-pixel-local-storage.html
@@ -0,0 +1,445 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL WEBGL_shader_pixel_local_storage 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>
+<script src="../../js/tests/compositing-test.js"></script>
+<script src="../../js/tests/invalid-vertex-attrib-test.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<canvas id="canvas" width="128" height="128" style="background-color:#080"> </canvas>
+<canvas id="canvas_no_alpha" width="128" height="128"> </canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_shader_pixel_local_storage " +
+ "extension, if it is available.");
+
+const wtu = WebGLTestUtils;
+const canvas = document.getElementById("canvas");
+const gl = wtu.create3DContext(canvas, {alpha: true}, 2);
+const gl_no_alpha = wtu.create3DContext("canvas_no_alpha", {alpha: false}, 2);
+let pls = null;
+
+// Outputs a fullscreen quad from a 4-vertex triangle strip.
+const fullscreenQuadVertexShader = `#version 300 es
+void main() {
+ gl_Position.x = (gl_VertexID & 1) == 0 ? -1. : 1.;
+ gl_Position.y = (gl_VertexID & 2) == 0 ? -1. : 1.;
+ gl_Position.zw = vec2(0, 1);
+}`;
+
+function arraysEqual(a, b) {
+ if (typeof a !== typeof b)
+ return false;
+ if (a.length != b.length)
+ return false;
+ for (let i = 0; i < a.length; ++i) {
+ if (a[i] !== b[i])
+ return false;
+ }
+ return true;
+}
+
+async function runTest() {
+ if (!gl) {
+ testFailed("WebGL2 context does not exist");
+ finishTest();
+ return;
+ }
+
+ debug("\nCheck the behavior surrounding WEBGL_shader_pixel_local_storage being enabled.");
+ checkExtensionNotSupportedWhenDisabled();
+ checkDependencyExtensionsEnabled(false);
+ debug("Enable WEBGL_shader_pixel_local_storage.");
+ pls = gl.getExtension("WEBGL_shader_pixel_local_storage");
+ wtu.runExtensionSupportedTest(gl, "WEBGL_shader_pixel_local_storage", pls != null);
+ if (!pls) {
+ finishTest();
+ return;
+ }
+ checkDependencyExtensionsEnabled(true);
+
+ checkImplementationDependentLimits();
+ checkInitialValues();
+ checkWebGLNonNormativeBehavior();
+
+ await checkRendering(gl);
+ await checkRendering(gl_no_alpha);
+
+ finishTest();
+}
+
+function checkExtensionNotSupportedWhenDisabled() {
+ debug("\nCheck that a context does not support WEBGL_shader_pixel_local_storage before it is " +
+ "enabled");
+ shouldBeNull("gl.getParameter(0x96E0 /*MAX_PIXEL_LOCAL_STORAGE_PLANES_WEBGL*/)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ shouldBeNull(
+ "gl.getParameter(0x96E1 /*MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE_WEBGL*/)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ shouldBeNull(
+ "gl.getParameter(0x96E2 /*MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_WEBGL*/)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ shouldBeNull(
+ "gl.getParameter(0x96E3 /*PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL*/)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NONE);
+}
+
+function checkDependencyExtensionsEnabled(enabled) {
+ debug("\nCheck that dependency extensions of WEBGL_shader_pixel_local_storage are " +
+ (enabled ? "enabled" : "disabled"));
+ if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "OES_draw_buffers_indexed") !== undefined) {
+ gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 1);
+ wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
+ "OES_draw_buffers_indexed not enabled or disabled as expected");
+ }
+ if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "EXT_color_buffer_float") !== undefined) {
+ gl.bindRenderbuffer(gl.RENDERBUFFER, gl.createRenderbuffer());
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.R32F, 1, 1);
+ wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
+ "EXT_color_buffer_float not enabled or disabled as expected");
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ }
+ if (wtu.getSupportedExtensionWithKnownPrefixes(gl, "EXT_color_buffer_half_float") !== undefined) {
+ gl.bindRenderbuffer(gl.RENDERBUFFER, gl.createRenderbuffer());
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RG16F, 1, 1);
+ wtu.glErrorShouldBe(gl, enabled ? gl.NONE : gl.INVALID_ENUM,
+ "EXT_color_buffer_half_float not enabled or disabled as expected");
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ }
+}
+
+function checkImplementationDependentLimits() {
+ debug("\nVerify conformant implementation-dependent PLS limits.");
+ window.MAX_PIXEL_LOCAL_STORAGE_PLANES =
+ gl.getParameter(pls.MAX_PIXEL_LOCAL_STORAGE_PLANES_WEBGL);
+ window.MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE =
+ gl.getParameter(pls.MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE_WEBGL);
+ window.MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES =
+ gl.getParameter(pls.MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NONE, "Pixel local storage queries should be supported.");
+
+ window.MAX_COLOR_ATTACHMENTS = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
+ window.MAX_DRAW_BUFFERS = gl.getParameter(gl.MAX_DRAW_BUFFERS);
+
+ // Table 6.X: Impementation Dependent Pixel Local Storage Limits.
+ shouldBeTrue("MAX_PIXEL_LOCAL_STORAGE_PLANES >= 4");
+ shouldBeTrue("MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE >= 0");
+ shouldBeTrue("MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >= 4");
+
+ // Logical deductions based on 6.X.
+ shouldBeTrue(`MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
+ MAX_PIXEL_LOCAL_STORAGE_PLANES`);
+ shouldBeTrue(`MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES >=
+ MAX_COLOR_ATTACHMENTS_WITH_ACTIVE_PIXEL_LOCAL_STORAGE`);
+ shouldBeTrue(`MAX_COLOR_ATTACHMENTS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
+ MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES`);
+ shouldBeTrue(`MAX_DRAW_BUFFERS + MAX_PIXEL_LOCAL_STORAGE_PLANES >=
+ MAX_COMBINED_DRAW_BUFFERS_AND_PIXEL_LOCAL_STORAGE_PLANES`);
+}
+
+function checkInitialValues() {
+ debug("\nCheck that PLS state has the correct initial values.");
+ shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
+ wtu.glErrorShouldBe(
+ gl, gl.NONE,
+ "It's valid to query GL_PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL even when fbo 0 is bound.");
+
+ // Table 6.Y: Pixel Local Storage State
+ gl.bindFramebuffer(gl.FRAMEBUFFER, gl.createFramebuffer());
+ shouldBeTrue("gl.getParameter(pls.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
+ debug("Check the initial clear values for each plane.");
+ const MAX_PIXEL_LOCAL_STORAGE_PLANES =
+ gl.getParameter(pls.MAX_PIXEL_LOCAL_STORAGE_PLANES_WEBGL);
+ for (let i = 0; i < MAX_PIXEL_LOCAL_STORAGE_PLANES; ++i)
+ {
+ expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_FORMAT_WEBGL) == gl.NONE);
+ expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_TEXTURE_NAME_WEBGL) == null);
+ expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_TEXTURE_LEVEL_WEBGL) == 0);
+ expectTrue(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_TEXTURE_LAYER_WEBGL) == 0);
+ expectTrue(arraysEqual(
+ pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
+ new Float32Array([0, 0, 0, 0])));
+ expectTrue(arraysEqual(
+ pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
+ new Int32Array([0, 0, 0, 0])));
+ expectTrue(arraysEqual(
+ pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ i, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
+ new Uint32Array([0, 0, 0, 0])));
+ }
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+}
+
+function checkWebGLNonNormativeBehavior() {
+ debug("\nCheck the WebGL-specific behavior not found in the " +
+ "ANGLE_shader_pixel_local_storage specification.");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, gl.createFramebuffer());
+
+ debug("If 'texture' has been deleted, generates an INVALID_OPERATION error.");
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ gl.deleteTexture(tex);
+ pls.framebufferTexturePixelLocalStorageWEBGL(0, tex, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+
+ debug("\nIf 'texture' was generated by a different WebGL2RenderingContext than this one, " +
+ "generates an INVALID_OPERATION error.");
+ const gl2 = wtu.create3DContext(null, null, 2);
+ const tex2 = gl2.createTexture();
+ gl2.bindTexture(gl2.TEXTURE_2D, tex2);
+ gl2.texStorage2D(gl2.TEXTURE_2D, 1, gl2.RGBA8, 1, 1);
+ pls.framebufferTexturePixelLocalStorageWEBGL(0, tex2, 0, 0);
+ wtu.glErrorShouldBe(gl2, gl2.NONE);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+
+ debug("\nIf value has less than srcOffset + 4 elements, generates an INVALID_VALUE error.");
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ pls.framebufferPixelLocalClearValuefvWEBGL(0, new Float32Array(3));
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValuefvWEBGL(1, [0, 0, 0]);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueivWEBGL(2, new Int32Array(3));
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueivWEBGL(3, [0, 0, 0]);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueuivWEBGL(4, new Uint32Array(3));
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueuivWEBGL(3, [0, 0, 0]);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValuefvWEBGL(2, new Float32Array(5), 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValuefvWEBGL(1, [0, 0, 0, 0, 0], 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueivWEBGL(0, new Int32Array(5), 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueivWEBGL(1, [0, 0, 0, 0, 0], 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueuivWEBGL(2, new Uint32Array(5), 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+ pls.framebufferPixelLocalClearValueuivWEBGL(3, [0, 0, 0, 0, 0], 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE);
+
+ debug("\nCheck that srcOffset works properly.");
+ const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
+ pls.framebufferPixelLocalClearValuefvWEBGL(0, new Float32Array(arr), 1);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 0, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
+ new Float32Array([1, 2, 3, 4]))`);
+ pls.framebufferPixelLocalClearValuefvWEBGL(1, arr, 2);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 1, pls.PIXEL_LOCAL_CLEAR_VALUE_FLOAT_WEBGL),
+ [2, 3, 4, 5])`);
+ pls.framebufferPixelLocalClearValueivWEBGL(2, new Int32Array(arr), 3);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 2, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
+ new Float32Array([3, 4, 5, 6]))`);
+ pls.framebufferPixelLocalClearValueivWEBGL(3, arr, 4);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 3, pls.PIXEL_LOCAL_CLEAR_VALUE_INT_WEBGL),
+ [4, 5, 6, 7])`);
+ pls.framebufferPixelLocalClearValueuivWEBGL(2, new Uint32Array(arr), 5);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 2, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
+ new Uint32Array([5, 6, 7, 8]))`);
+ pls.framebufferPixelLocalClearValueuivWEBGL(1, arr, 6);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ shouldBeTrue(`arraysEqual(pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 1, pls.PIXEL_LOCAL_CLEAR_VALUE_UNSIGNED_INT_WEBGL),
+ [6, 7, 8, 9])`);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+
+ debug("\nCheck that PIXEL_LOCAL_TEXTURE_NAME_WEBGL returns a WebGLTexture.");
+ shouldBeTrue(`pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 0, pls.PIXEL_LOCAL_TEXTURE_NAME_WEBGL) === null`);
+ window.validTex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, validTex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ pls.framebufferTexturePixelLocalStorageWEBGL(0, validTex, 0, 0);
+ shouldBeTrue(`pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 0, pls.PIXEL_LOCAL_TEXTURE_NAME_WEBGL) === validTex`);
+ pls.framebufferTexturePixelLocalStorageWEBGL(0, null, 0, 0);
+ shouldBeTrue(`pls.getFramebufferPixelLocalStorageParameterWEBGL(
+ 0, pls.PIXEL_LOCAL_TEXTURE_NAME_WEBGL) === null`);
+
+ wtu.glErrorShouldBe(gl, gl.NONE);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+}
+
+async function checkRendering(localGL) {
+ const localCanvas = localGL.canvas;
+ const alpha = localGL.getContextAttributes().alpha;
+ debug("\nCheck very simple rendering with {alpha: " + alpha + "}");
+
+ const localPLS = localGL.getExtension("WEBGL_shader_pixel_local_storage");
+ if (!localPLS) {
+ testFailed("localGL doesn't support pixel local storage.");
+ return;
+ }
+
+ const tex = localGL.createTexture();
+ localGL.bindTexture(localGL.TEXTURE_2D, tex);
+ localGL.texStorage2D(localGL.TEXTURE_2D, 1, localGL.RGBA8, localCanvas.width, localCanvas.height);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+
+ const plsFBO = localGL.createFramebuffer();
+ localGL.bindFramebuffer(localGL.FRAMEBUFFER, plsFBO);
+ localPLS.framebufferTexturePixelLocalStorageWEBGL(0, tex, 0, 0);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+
+ localGL.viewport(0, 0, localCanvas.width, localCanvas.height);
+
+ // Adds a uniform color into the existing color in pixel local storage.
+ const fs = `#version 300 es
+ #extension GL_ANGLE_shader_pixel_local_storage : require
+ precision lowp float;
+ uniform vec4 color;
+ layout(binding=0, rgba8) uniform lowp pixelLocalANGLE pls;
+ void main() {
+ vec4 newColor = color + pixelLocalLoadANGLE(pls);
+ pixelLocalStoreANGLE(pls, newColor);
+ }`;
+
+ const program = wtu.setupProgram(localGL, [fullscreenQuadVertexShader, fs]);
+ if (!program) {
+ testFailed("Failed to compile program.");
+ return;
+ }
+
+ localGL.useProgram(program);
+ const colorUniLocation = localGL.getUniformLocation(program, "color");
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+
+ // Disable color mask to ensure PLS and canvas manage their own color masks properly.
+ localGL.colorMask(false, true, false, true);
+
+ // Set global variables for shouldBeTrue().
+ window.localGL = localGL;
+ window.localPLS = localPLS;
+
+ debug("\nCheck that pixel local storage works properly");
+ localGL.disable(localGL.DITHER);
+ localPLS.beginPixelLocalStorageWEBGL([localPLS.LOAD_OP_ZERO_WEBGL]);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ shouldBeTrue("localGL.getParameter(localPLS.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 1");
+
+ localGL.uniform4f(colorUniLocation, 0, 1, 0, 0);
+ localGL.drawArrays(localGL.TRIANGLE_STRIP, 0, 4);
+
+ localPLS.pixelLocalStorageBarrierWEBGL();
+
+ localGL.uniform4f(colorUniLocation, 1, 0, 0, 0);
+ localGL.drawArrays(localGL.TRIANGLE_STRIP, 0, 4);
+
+ localPLS.endPixelLocalStorageWEBGL([localPLS.STORE_OP_STORE_WEBGL]);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ shouldBeTrue("localGL.getParameter(localPLS.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
+
+ const readFBO = localGL.createFramebuffer();
+ localGL.bindFramebuffer(localGL.READ_FRAMEBUFFER, readFBO);
+ localGL.framebufferTexture2D(localGL.READ_FRAMEBUFFER, localGL.COLOR_ATTACHMENT0,
+ localGL.TEXTURE_2D, tex, 0);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ wtu.checkCanvas(localGL, [255, 255, 0, 0]);
+
+ debug("\nCheck that alpha is properly handled in the main canvas.");
+ localGL.bindFramebuffer(localGL.DRAW_FRAMEBUFFER, null);
+ localGL.blitFramebuffer(0, 0, localCanvas.width, localCanvas.height, 0, 0, localCanvas.width,
+ localCanvas.height, localGL.COLOR_BUFFER_BIT, localGL.NEAREST);
+ localGL.bindFramebuffer(localGL.FRAMEBUFFER, null);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ wtu.checkCanvas(localGL, [255, 255, 0, alpha ? 0 : 255]);
+
+ localGL.bindFramebuffer(localGL.FRAMEBUFFER, plsFBO);
+ localPLS.beginPixelLocalStorageWEBGL([localPLS.LOAD_OP_LOAD_WEBGL]);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ shouldBeTrue("localGL.getParameter(localPLS.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 1");
+
+ debug("\nGoing down from composite.");
+
+ // The canvas should get cleared after compositing, even if PLS is active and color mask is
+ // disabled.
+ await new Promise(resolve => wtu.waitForComposite(resolve));
+
+ // Reset global variables for shouldBeTrue() after await.
+ window.localGL = localGL;
+ window.localPLS = localPLS;
+
+ debug("\nBack from composite!");
+ debug("\nPLS should still be active on plsFBO even after being interrupted for compositing.");
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ shouldBeTrue("localGL.getParameter(localPLS.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 1");
+
+ localGL.uniform4f(colorUniLocation, 0, 0, 1, 0);
+ localGL.drawArrays(localGL.TRIANGLE_STRIP, 0, 4);
+
+ localPLS.endPixelLocalStorageWEBGL([localPLS.STORE_OP_STORE_WEBGL]);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+ shouldBeTrue("localGL.getParameter(localPLS.PIXEL_LOCAL_STORAGE_ACTIVE_PLANES_WEBGL) == 0");
+
+ debug("\nThe canvas should have gotten cleared while PLS was active.");
+ localGL.bindFramebuffer(localGL.FRAMEBUFFER, null);
+ wtu.checkCanvas(localGL, [0, 0, 0, alpha ? 0 : 255]);
+
+ debug("\nThe additional PLS draw to plsFBO should have still worked after being interrupted " +
+ "for compositing.");
+ localGL.bindFramebuffer(localGL.READ_FRAMEBUFFER, readFBO);
+ wtu.checkCanvas(localGL, [255, 255, 255, 0]);
+ wtu.glErrorShouldBe(localGL, localGL.NONE);
+
+ // Draws 'tex' to the canvas.
+ const fs2 = `#version 300 es
+ uniform lowp sampler2D tex;
+ out lowp vec4 fragColor;
+ void main() {
+ ivec2 pixelCoord = ivec2(floor(gl_FragCoord.xy));
+ fragColor = texelFetch(tex, pixelCoord, 0);
+ }`;
+
+ const program2 = wtu.setupProgram(localGL, [fullscreenQuadVertexShader, fs2]);
+ if (!program2) {
+ testFailed("Failed to compile program2.");
+ return;
+ }
+
+ debug("\nBlue should still be disabled in the color mask. Alpha is not disabled but should be " +
+ "implicitly disabled since the canvas doesn't have alpha.");
+ localGL.useProgram(program2);
+ localGL.uniform1i(localGL.getUniformLocation(program2, "tex"), 0);
+ localGL.bindFramebuffer(localGL.FRAMEBUFFER, null);
+ localGL.drawArrays(localGL.TRIANGLE_STRIP, 0, 4);
+ wtu.checkCanvas(localGL, [0, 255, 0, alpha ? 0 : 255]);
+
+ debug("\nThe client's color mask should have been preserved.");
+ shouldBeTrue(`arraysEqual(localGL.getParameter(localGL.COLOR_WRITEMASK),
+ [false, true, false, true])`);
+}
+
+runTest();
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-stencil-texturing.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-stencil-texturing.html
new file mode 100644
index 0000000000..729a5bcf8a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-stencil-texturing.html
@@ -0,0 +1,279 @@
+<!--
+Copyright (c) 2023 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 WEBGL_stencil_texturing 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("This test verifies the functionality of the WEBGL_stencil_texturing extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext(null, null, 2);
+var ext;
+
+function runTestNoExtension() {
+ debug("");
+ debug("Check the texture parameter without the extension");
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+
+ shouldBeNull("gl.getTexParameter(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */)");
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+
+ gl.texParameteri(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for texParameteri without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+
+ gl.texParameterf(gl.TEXTURE_2D, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for texParameterf without enabling the extension");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+
+ const sampler = gl.createSampler();
+ gl.samplerParameteri(sampler, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameteri");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+ gl.samplerParameterf(sampler, 0x90EA /* DEPTH_STENCIL_TEXTURE_MODE_WEBGL */, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameterf");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+}
+
+function checkEnums() {
+ debug("");
+ debug("Check enums");
+ shouldBe("ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL", "0x90EA");
+ shouldBe("ext.STENCIL_INDEX_WEBGL", "0x1901");
+}
+
+function checkQueries() {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+
+ debug("");
+ debug("Check default texture state");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
+ debug("");
+ debug("Check texture state updates using texParameteri");
+ gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, ext.STENCIL_INDEX_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'ext.STENCIL_INDEX_WEBGL');
+ gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
+ gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "invalid depth stencil mode value rejected by texParameteri");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
+ debug("");
+ debug("Check texture state updates using texParameterf");
+ gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, ext.STENCIL_INDEX_WEBGL);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'ext.STENCIL_INDEX_WEBGL');
+ gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
+ gl.texParameterf(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "invalid depth stencil mode value rejected by texParameterf");
+ shouldBe('gl.getTexParameter(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL)', 'gl.DEPTH_COMPONENT');
+
+ debug("");
+ debug("Check that depth stencil texture mode is not accepted as a sampler state");
+ const sampler = gl.createSampler();
+ gl.samplerParameteri(sampler, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameteri");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+ gl.samplerParameterf(sampler, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, gl.DEPTH_COMPONENT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "parameter unknown for samplerParameterf");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no more errors");
+}
+
+function checkSampling() {
+ const formats = [
+ {name: "DEPTH_COMPONENT16", internalFormat: gl.DEPTH_COMPONENT16,
+ format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_SHORT},
+ {name: "DEPTH_COMPONENT24", internalFormat: gl.DEPTH_COMPONENT24,
+ format: gl.DEPTH_COMPONENT, type: gl.UNSIGNED_INT},
+ {name: "DEPTH_COMPONENT32F", internalFormat: gl.DEPTH_COMPONENT32F,
+ format: gl.DEPTH_COMPONENT, type: gl.FLOAT},
+ {name: "DEPTH24_STENCIL8", internalFormat: gl.DEPTH24_STENCIL8,
+ format: gl.DEPTH_STENCIL, type: gl.UNSIGNED_INT_24_8},
+ {name: "DEPTH32F_STENCIL8", internalFormat: gl.DEPTH32F_STENCIL8,
+ format: gl.DEPTH_STENCIL, type: gl.FLOAT_32_UNSIGNED_INT_24_8_REV}
+ ];
+
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.STENCIL_TEST);
+ gl.stencilFunc(gl.ALWAYS, 170, 0xFF);
+ gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
+
+ wtu.setupUnitQuad(gl);
+
+ const drawProgram = wtu.setupProgram(gl, [wtu.simpleVertexShader,
+ wtu.simpleColorFragmentShader]);
+
+ const readDepthProgram = wtu.setupProgram(gl, [wtu.simpleTextureVertexShaderESSL300,
+ wtu.simpleTextureFragmentShaderESSL300]);
+
+ const readStencilShader = `#version 300 es
+ precision highp float;
+ uniform highp usampler2D tex;
+ in vec2 texCoord;
+ out vec4 out_color;
+ void main() {
+ out_color = vec4(texture(tex, texCoord)) / 255.0;
+ }`;
+ const readStencilProgram = wtu.setupProgram(gl, [wtu.simpleTextureVertexShaderESSL300,
+ readStencilShader]);
+
+ for (const format of formats) {
+ debug("");
+ debug(`Testing depth stencil texture modes with ${format.name}`);
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+
+ const rbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 1, 1);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, rbo);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, format.internalFormat, 1, 1, 0, format.format, format.type, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture created");
+
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, tex, 0);
+ if (format.format == gl.DEPTH_STENCIL) {
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.TEXTURE_2D, tex, 0);
+ }
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ gl.clear(gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
+ gl.useProgram(drawProgram);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "no errors after drawing to the depth or depth stencil texture");
+
+ // Detach the depth or depth stencil texture to avoid feedback loop
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null);
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE);
+
+ const magFilters = ['NEAREST', 'LINEAR'];
+
+ const minFilters = [
+ 'NEAREST',
+ 'LINEAR',
+ 'NEAREST_MIPMAP_NEAREST',
+ 'LINEAR_MIPMAP_NEAREST',
+ 'NEAREST_MIPMAP_LINEAR',
+ 'LINEAR_MIPMAP_LINEAR'
+ ];
+
+ const modes = [
+ [gl.DEPTH_COMPONENT, 'DEPTH_COMPONENT'],
+ [ext.STENCIL_INDEX_WEBGL, 'STENCIL_INDEX_WEBGL']
+ ];
+
+ const programs = [
+ [readDepthProgram, 'depth'],
+ [readStencilProgram, 'stencil']
+ ];
+
+ function validFilters(magFilter, minFilter) {
+ return magFilter == gl.NEAREST &&
+ (minFilter == gl.NEAREST || minFilter == gl.NEAREST_MIPMAP_NEAREST);
+ }
+
+ for (const program of programs) {
+ gl.useProgram(program[0]);
+ for (const mode of modes) {
+ gl.texParameteri(gl.TEXTURE_2D, ext.DEPTH_STENCIL_TEXTURE_MODE_WEBGL, mode[0]);
+ for (const magFilter of magFilters) {
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl[magFilter]);
+ for (const minFilter of minFilters) {
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl[minFilter]);
+ debug(`Program: ${program[1]}, mode: ${mode[1]}, mag: ${magFilter}, min: ${minFilter}`);
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.drawUnitQuad(gl);
+
+ if (format.format == gl.DEPTH_COMPONENT || mode[0] == gl.DEPTH_COMPONENT) {
+ if (program[1] == 'depth') {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ if (validFilters(gl[magFilter], gl[minFilter])) {
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [128, 0, 0, 255], "sampling depth from complete texture", 1);
+ } else {
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 0, 255], "sampling depth from incomplete texture", 1);
+ }
+ } else {
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "sampling depth using incompatible program");
+ }
+ } else {
+ if (program[1] == 'stencil') {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+ if (validFilters(gl[magFilter], gl[minFilter])) {
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [170, 0, 0, 1], "sampling stencil from complete texture", 1);
+ } else {
+ // Incomplete textures may produce [0, 0, 0, 1] or [0, 0, 0, 255].
+ const value = new Uint8Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, value);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ const msg = "sampling stencil from incomplete texture";
+ if (value[0] == 0 && value[1] == 0 && value[2] == 0 && (value[3] == 1 || value[3] == 255)) {
+ testPassed(msg);
+ } else {
+ testFailed(`${msg}: ${value}`);
+ }
+ }
+ } else {
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "sampling stencil using incompatible program");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ return;
+ }
+ testPassed("context exists");
+
+ runTestNoExtension();
+
+ ext = gl.getExtension("WEBGL_stencil_texturing");
+ wtu.runExtensionSupportedTest(gl, "WEBGL_stencil_texturing", ext !== null);
+
+ if (ext !== null) {
+ checkEnums();
+ checkQueries();
+ checkSampling();
+ } else {
+ testPassed("No WEBGL_stencil_texturing support -- this is legal");
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/00_test_list.txt
index 5a47d470f9..9e8bc87a62 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/00_test_list.txt
@@ -41,6 +41,7 @@ shader-with-invalid-characters.html
shader-with-mis-matching-uniform-block.html
short-circuiting-in-loop-condition.html
--min-version 2.0.1 switch-case.html
+--min-version 2.0.1 texture-bias.html
--min-version 2.0.1 texture-offset-non-constant-offset.html
texture-offset-out-of-range.html
--min-version 2.0.1 texture-offset-uniform-texture-coordinate.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/texture-bias.html b/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/texture-bias.html
new file mode 100644
index 0000000000..0c30eb7129
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/glsl3/texture-bias.html
@@ -0,0 +1,146 @@
+<!--
+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>GLSL texture bias 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>
+<script src="../../js/glsl-conformance-test.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Texture bias should both function and respect limits.");
+
+function runTest(gl) {
+ // no if idea any drivers have a giant limit like 2^32 so just in case.
+ const kMaxMaxTextureSize = 256 * 1024 * 1024;
+ const maxTextureSize = Math.min(kMaxMaxTextureSize, gl.getParameter(gl.MAX_TEXTURE_SIZE));
+ const maxLODs = (Math.log2(maxTextureSize) | 0) + 1;
+ const maxTextureLODBias = gl.getParameter(gl.MAX_TEXTURE_LOD_BIAS);
+
+ debug(`maxTextureSize: ${maxTextureSize}`);
+ debug(`maxLODs: ${maxLODs}`);
+ debug(`maxTextureLODBias: ${maxTextureLODBias}`);
+
+ const vs = `#version 300 es
+ uniform float uvMult;
+ out vec2 v_uv;
+ void main() {
+ vec2 xy = vec2(
+ gl_VertexID % 2,
+ (gl_VertexID / 2 + gl_VertexID / 3) % 2);
+
+ gl_Position = vec4(xy * 2. - 1.0, 0, 1);
+ v_uv = xy * uvMult;
+ }
+ `;
+ const fs = `#version 300 es
+ precision highp float;
+ uniform sampler2D tex;
+ uniform float biasMult;
+ in vec2 v_uv;
+ out vec4 fragColor;
+ void main() {
+ vec4 texColor = texture(tex, v_uv, (gl_FragCoord.x - 0.5) * biasMult); // the color we care about
+ vec4 texelColor = texelFetch(tex, ivec2(0), int(gl_FragCoord)); // just a sanity check
+ vec4 coordColor = vec4((100.0 + gl_FragCoord.x - 0.5) / 255.0); // another sanity check
+ fragColor = mix(texColor, coordColor, step(1.0, gl_FragCoord.y)); // line < 1 = texColor, line >= 1 = coordColor
+ fragColor = mix(fragColor, texelColor, step(2.0, gl_FragCoord.y)); // line < 2 = fragColor, line >= 2 = texelColor
+ }
+ `;
+ const program = wtu.setupProgram(gl, [vs, fs]);
+ const uvMultLoc = gl.getUniformLocation(program, 'uvMult');
+ const biasLoc = gl.getUniformLocation(program, 'biasMult');
+
+ gl.canvas.width = maxLODs;
+ gl.canvas.height = 3;
+ gl.viewport(0, 0, maxLODs, 3);
+
+ // create a texture where each mip is a different color (1, 2, 3, ...)
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);
+ gl.texStorage2D(gl.TEXTURE_2D, maxLODs, gl.RGBA8, maxTextureSize, 1);
+ {
+ let level = 0;
+ for (let width = maxTextureSize; width > 0; width = width / 2 | 0) {
+ const pixels = new Uint8Array(width * 1 * 4);
+ pixels.fill(level + 1);
+ debug(`fill mip level: ${level}, width: ${width}`);
+ gl.texSubImage2D(gl.TEXTURE_2D, level, 0, 0, width, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ ++level;
+ }
+ }
+
+ // Draw each mip. Result should be [mip0, mip1, mip2, ...]
+ debug("");
+ debug("check positive bias");
+ // set the UVs so we'd get mip level 0 for every pixel
+ gl.uniform1f(uvMultLoc, maxLODs / maxTextureSize);
+ gl.uniform1f(biasLoc, 1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ const clampPlusMinus = (v, limit) => Math.min(limit, Math.max(-limit, v));
+
+ const checkResults = (gl, biasMult) => {
+ const base = biasMult > 0 ? 1 : maxLODs;
+ for (let i = 0; i < maxLODs; ++i) {
+ {
+ const expected = new Array(4).fill(clampPlusMinus(i * biasMult, maxTextureLODBias) + base);
+ wtu.checkCanvasRect(gl, i, 0, 1, 1, expected, `should be: ${expected}`);
+ }
+ {
+ const expected = new Array(4).fill(100 + i);
+ wtu.checkCanvasRect(gl, i, 1, 1, 1, expected, `should be: ${expected}`);
+ }
+ {
+ const expected = new Array(4).fill(i + 1);
+ wtu.checkCanvasRect(gl, i, 2, 1, 1, expected, `should be: ${expected}`);
+ }
+ }
+ }
+
+ checkResults(gl, 1);
+
+ // Draw each mip. Result should be [mipMax, mipMax - 1, mipMax - 2, ...]
+ debug("");
+ debug("check negative bias");
+ // set the UVs so we'd get highest mip level (the 1x1 level mip) for every pixel
+ gl.uniform1f(uvMultLoc, maxLODs);
+ gl.uniform1f(biasLoc, -1);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ checkResults(gl, -1);
+
+ finishTest();
+}
+
+const wtu = WebGLTestUtils;
+
+const gl = wtu.create3DContext(undefined, undefined, 2);
+
+var successfullyParsed = true;
+
+if (!gl) {
+ testFailed("Unable to initialize WebGL 2.0 context.");
+} else {
+ runTest(gl);
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/query/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/query/00_test_list.txt
index c40921bf88..a2e21b5ad7 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/query/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/query/00_test_list.txt
@@ -1,2 +1,3 @@
occlusion-query.html
+--min-version 2.0.1 occlusion-query-scissor.html
query.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/query/occlusion-query-scissor.html b/dom/canvas/test/webgl-conf/checkout/conformance2/query/occlusion-query-scissor.html
new file mode 100644
index 0000000000..dec88e56d3
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/query/occlusion-query-scissor.html
@@ -0,0 +1,117 @@
+<!--
+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 Occlusion Query w/Scissor 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" style="width: 50px; height: 50px;"> </canvas>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of occlusion query objects with the scissor test.");
+debug('Regression test for <a href="http://anglebug.com/7157">http://anglebug.com/7157</a>');
+
+debug("");
+
+const wtu = WebGLTestUtils;
+
+const wait = () => new Promise(resolve => setTimeout(resolve));
+
+async function runOcclusionQueryTest(gl) {
+ const kSize = 4;
+ const colors = [
+ [0, 0, 0, 255],
+ [255, 0, 0, 255],
+ [0, 255, 0, 255],
+ [255, 255, 0, 255],
+ ];
+ const framebuffers = colors.map((color, index) => {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, kSize, kSize, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ gl.clearColor(index & 1, (index >> 1) & 1, (index >> 2) & 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.checkCanvasRect(gl, 0, 0, kSize, kSize, color);
+
+ return fb;
+ });
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup should succeed");
+
+ gl.viewport(0, 0, kSize, kSize);
+
+ const program = wtu.setupSimpleColorProgram(gl, 0);
+ gl.uniform4f(gl.getUniformLocation(program, "u_color"), 0, 1, 0, 1);
+ wtu.setupUnitQuad(gl, 0);
+
+ const query = gl.createQuery();;
+
+ for (let i = 0; i < 256; ++i)
+ {
+ gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
+ let drawn = false;
+
+ framebuffers.forEach((fb, index) => {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ const scissor = (i >> 4 >> index) & 1;
+ if (i & (1 << index))
+ {
+ if (scissor)
+ {
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(0, 0, 0, 0);
+ }
+ wtu.drawUnitQuad(gl);
+ drawn ||= !scissor;
+ if (scissor)
+ {
+ gl.disable(gl.SCISSOR_TEST);
+ gl.scissor(0, 0, kSize, kSize);
+ }
+ }
+ });
+
+ gl.endQuery(gl.ANY_SAMPLES_PASSED);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should draw");
+
+ do {
+ await wait();
+ } while (!gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE));
+
+ const result = gl.getQueryParameter(query, gl.QUERY_RESULT);
+
+ const expected = drawn ? 1 : 0;
+ assertMsg(result === expected, `pass ${i}, result: ${result} === expected: ${expected}`);
+ }
+ finishTest();
+}
+
+const canvas = document.getElementById("canvas");
+const gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+ runOcclusionQueryTest(gl);
+}
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-object-attachment.html b/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-object-attachment.html
index 754ff2cc73..bc4d623446 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-object-attachment.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-object-attachment.html
@@ -111,9 +111,10 @@ function testFramebufferWebGL1RequiredCombinations() {
gl.deleteFramebuffer(fbo);
}
-function testDepthStencilAttachmentBehaviors() {
+function testDepthStencilAttachmentBehaviors(testOrphanedRenderbuffers) {
+ let suffix = testOrphanedRenderbuffers ? " with deleted renderbuffer" : "";
debug("");
- debug("Checking ES3 DEPTH_STENCIL_ATTACHMENT behaviors are implemented for WebGL 2");
+ debug("Checking ES3 DEPTH_STENCIL_ATTACHMENT behaviors are implemented for WebGL 2" + suffix);
// DEPTH_STENCIL_ATTACHMENT is treated as an independent attachment point in WebGL 1;
// however, in WebGL 2, it is treated as an alias for DEPTH_ATTACHMENT + STENCIL_ATTACHMENT.
var size = 16;
@@ -127,24 +128,45 @@ function testDepthStencilAttachmentBehaviors() {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, size, size);
checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
- var depthBuffer = gl.createRenderbuffer();
- gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+ function createDepthBuffer() {
+ let buffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ return buffer;
+ }
+
+ function createStencilBuffer() {
+ let buffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, size, size);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ return buffer;
+ }
- var stencilBuffer = gl.createRenderbuffer();
- gl.bindRenderbuffer(gl.RENDERBUFFER, stencilBuffer);
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, size, size);
+ function createDepthStencilBuffer() {
+ let buffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, buffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ return buffer;
+ }
- var depthStencilBuffer = gl.createRenderbuffer();
- gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer);
- gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size);
+ function orphan(renderbuffer) {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteRenderbuffer(renderbuffer);
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ }
wtu.glErrorShouldBe(gl, gl.NO_ERROR);
debug("");
- debug("color + depth");
+ debug("color + depth" + suffix);
+ var depthBuffer = createDepthBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
@@ -153,9 +175,12 @@ function testDepthStencilAttachmentBehaviors() {
checkBufferBits(gl.DEPTH_ATTACHMENT);
debug("");
- debug("color + depth + stencil: depth != stencil");
+ debug("color + depth + stencil: depth != stencil" + suffix);
+ var stencilBuffer = createStencilBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(stencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", stencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
@@ -170,9 +195,12 @@ function testDepthStencilAttachmentBehaviors() {
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
debug("");
- debug("color + depth: DEPTH_STENCIL for DEPTH_ATTACHMENT");
+ debug("color + depth: DEPTH_STENCIL for DEPTH_ATTACHMENT" + suffix);
+ var depthStencilBuffer = createDepthStencilBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
@@ -181,9 +209,16 @@ function testDepthStencilAttachmentBehaviors() {
checkBufferBits(gl.DEPTH_ATTACHMENT);
debug("");
- debug("color + depth + stencil: DEPTH_STENCIL for DEPTH_ATTACHMENT and STENCIL_ATTACHMENT");
+ debug("color + depth + stencil: DEPTH_STENCIL for DEPTH_ATTACHMENT and STENCIL_ATTACHMENT" + suffix);
+ if (testOrphanedRenderbuffers) {
+ depthStencilBuffer = createDepthStencilBuffer();
+ gl.framebufferRenderbuffer(
+ gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ }
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
@@ -192,7 +227,7 @@ function testDepthStencilAttachmentBehaviors() {
checkBufferBits(gl.DEPTH_STENCIL_ATTACHMENT);
debug("");
- debug("color + depth_stencil");
+ debug("color + depth_stencil" + suffix);
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH24_STENCIL8, size, size, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null);
@@ -209,8 +244,12 @@ function testDepthStencilAttachmentBehaviors() {
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ if (testOrphanedRenderbuffers)
+ depthStencilBuffer = createDepthStencilBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
@@ -219,16 +258,24 @@ function testDepthStencilAttachmentBehaviors() {
checkBufferBits(gl.DEPTH_STENCIL_ATTACHMENT);
debug("");
- debug("DEPTH_STENCIL_ATTACHMENT overwrites DEPTH_ATTACHMENT/STENCIL_ATTACHMENT")
+ debug("DEPTH_STENCIL_ATTACHMENT overwrites DEPTH_ATTACHMENT/STENCIL_ATTACHMENT" + suffix);
+ if (testOrphanedRenderbuffers)
+ depthBuffer = createDepthBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+ if (testOrphanedRenderbuffers)
+ depthStencilBuffer = createDepthStencilBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
@@ -244,9 +291,13 @@ function testDepthStencilAttachmentBehaviors() {
checkBufferBits();
debug("");
- debug("STENCIL_ATTACHMENT overwrites stencil set by DEPTH_STENCIL_ATTACHMENT")
+ debug("STENCIL_ATTACHMENT overwrites stencil set by DEPTH_STENCIL_ATTACHMENT" + suffix);
+ if (testOrphanedRenderbuffers)
+ depthStencilBuffer = createDepthStencilBuffer();
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
@@ -260,6 +311,28 @@ function testDepthStencilAttachmentBehaviors() {
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
checkBufferBits(gl.DEPTH_ATTACHMENT);
+
+ debug("");
+ debug("DEPTH_ATTACHMENT overwrites depth set by DEPTH_STENCIL_ATTACHMENT" + suffix);
+ if (testOrphanedRenderbuffers)
+ depthStencilBuffer = createDepthStencilBuffer();
+ gl.framebufferRenderbuffer(
+ gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer);
+ if (testOrphanedRenderbuffers)
+ orphan(depthStencilBuffer);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ gl.framebufferRenderbuffer(
+ gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", depthStencilBuffer);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", null);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ checkBufferBits(gl.STENCIL_ATTACHMENT);
}
function testFramebufferIncompleteAttachment() {
@@ -469,7 +542,8 @@ description("Test framebuffer object attachment behaviors");
shouldBeNonNull("gl = wtu.create3DContext(undefined, undefined, 2)");
testFramebufferWebGL1RequiredCombinations();
-testDepthStencilAttachmentBehaviors();
+testDepthStencilAttachmentBehaviors(false);
+testDepthStencilAttachmentBehaviors(true);
testFramebufferIncompleteAttachment();
testFramebufferIncompleteMissingAttachment();
testFramebufferWithImagesOfDifferentSizes();
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-texture-layer.html b/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-texture-layer.html
index 0e435d6a2e..13e5d790e8 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-texture-layer.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/renderbuffers/framebuffer-texture-layer.html
@@ -121,6 +121,37 @@ function testFramebufferTextureLayer() {
"attaching a depth_stencil texture to a framebuffer should succeed.");
checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ var texDepthStencil = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texDepthStencil);
+
+ var texDepthStencilMany = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, texDepthStencilMany);
+ var fbDepthStencilMany = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbDepthStencilMany);
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY,
+ 0, // level
+ gl.DEPTH24_STENCIL8, // internalFormat
+ 1, // width
+ 1, // height
+ 2, // depth
+ 0, // border
+ gl.DEPTH_STENCIL, // format
+ gl.UNSIGNED_INT_24_8, // type
+ new Uint32Array([0, 1])); // data
+
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, texDepthStencilMany, 0, 0);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, texDepthStencilMany, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ "attaching a depth_stencil 2d array texture level 0 to a framebuffer should succeed.");
+ checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", texDepthStencilMany);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, texDepthStencilMany, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ "attaching a 2d array texture level 0 to depth and layer 1 to stencil attachment of a framebuffer should succeed.");
+ // "Depth and stencil attachments, if present, are the same image." If not, then "FRAMEBUFFER_UNSUPPORTED".
+ checkFramebuffer([gl.FRAMEBUFFER_UNSUPPORTED]);
+ shouldEvaluateTo("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)", texDepthStencilMany);
+
// Clean up
gl.deleteTexture(tex3d);
gl.deleteTexture(texDepthStencil);
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
index 92ce232ee2..c4a1bbe9e8 100644
--- 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
@@ -11,7 +11,6 @@ blitframebuffer-size-overflow.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
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
deleted file mode 100644
index cc64c9034b..0000000000
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/rendering/builtin-vert-attribs.html
+++ /dev/null
@@ -1,408 +0,0 @@
-<!--
-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/textures/misc/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/00_test_list.txt
index 17f8312e58..3639c10547 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/00_test_list.txt
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/00_test_list.txt
@@ -20,6 +20,7 @@ mipmap-fbo.html
--min-version 2.0.1 origin-clean-conformance-offscreencanvas.html
tex-3d-size-limit.html
--min-version 2.0.1 tex-base-level-bug.html
+--min-version 2.0.1 tex-image-10bpc.html
tex-image-and-sub-image-with-array-buffer-view-sub-source.html
tex-image-with-bad-args.html
tex-image-with-bad-args-from-dom-elements.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-10bpc.html b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-10bpc.html
new file mode 100644
index 0000000000..973a0ec0f5
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-10bpc.html
@@ -0,0 +1,66 @@
+<!--
+Copyright (c) 2023 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>Ensure 10bpc image is not crushed to 8bpc in texImage2D</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="24" height="24"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("example", undefined, 2);
+var uniquePixels;
+
+// This is an 8x1, 10-bit-per-channel PNG (encoded as 16bpc).
+// The first pixel is black, and each next pixel is one brighter; approximately:
+// (0/1023,0,0), (1/1023,0,0), (2/1023,0,0), ..., (7/1023,0,0)
+const imgW = 8, imgH = 1;
+const imgURL = "../../../resources/red-gradient-8x1-10bit-untagged.png";
+
+const img = document.createElement("img");
+img.onload = () => {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ const level = 0;
+ const internalformat = gl.RGB10_A2;
+ const border = 0;
+ const format = gl.RGBA;
+ const type = gl.UNSIGNED_INT_2_10_10_10_REV;
+ gl.texImage2D(gl.TEXTURE_2D, level, internalformat, imgW, imgH, border, format, type, img);
+
+ const fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+
+ const pixels = new Uint32Array(imgW * imgH);
+ gl.readPixels(0, 0, imgW, imgH, format, type, pixels);
+ uniquePixels = new Set(pixels);
+ // If the image was crushed to 8bpc, there will be 2-3 distinct values:
+ // (0/255,0,0), (1/255,0,0), and maybe (2/255,0,0) (depending on truncation vs rounding).
+ // If it wasn't, there should be 7-8.
+ // At time of writing, on Mac M1, Chrome gets 2 if it's crushed, and 7 if it's not.
+ shouldBeGreaterThanOrEqual("uniquePixels.size", "7", "there should be at least 7 distinct color values");
+
+ finishTest();
+};
+img.src = imgURL;
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html
index f1616e81d7..34ece05699 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-image-with-bad-args-from-dom-elements.html
@@ -106,7 +106,6 @@ var tests = [
{ type: "video", src: "../../../resources/red-green.mp4", videoType: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', run: testVideo },
{ type: "video", src: "../../../resources/red-green.bt601.vp9.webm", videoType: 'video/webm; codecs="vp9"', run: testVideo },
{ type: "video", src: "../../../resources/red-green.webmvp8.webm", videoType: 'video/webm; codecs="vp8, vorbis"', run: testVideo },
- { type: "video", src: "../../../resources/red-green.theora.ogv", videoType: 'video/ogg; codecs="theora, vorbis"', run: testVideo },
];
var testIndex = 0;
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-new-formats.html b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-new-formats.html
index df10edb4d3..760bc6bd19 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-new-formats.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-new-formats.html
@@ -37,6 +37,7 @@ if (!gl) {
testPassed("WebGL context exists");
runTexFormatsTest();
+ runDepthStencilFormatTest();
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
}
@@ -556,6 +557,35 @@ function runTexFormatsTest()
});
}
+function runDepthStencilFormatTest() {
+ debug("");
+ debug("Testing FLOAT_32_UNSIGNED_INT_24_8_REV with data");
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+
+ const tex2D = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex2D);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH32F_STENCIL8, 1, 1, 0, gl.DEPTH_STENCIL, gl.FLOAT_32_UNSIGNED_INT_24_8_REV, new Uint8Array(8));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, tex2D, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("2D texture with invalid type was created");
+ } else {
+ testPassed("2D texture with invalid type was not created")
+ }
+
+ const tex3D = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, tex3D);
+ gl.texImage3D(gl.TEXTURE_2D_ARRAY, 0, gl.DEPTH32F_STENCIL8, 1, 1, 1, 0, gl.DEPTH_STENCIL, gl.FLOAT_32_UNSIGNED_INT_24_8_REV, new Uint8Array(8));
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, tex3D, 0, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("2D array texture with invalid type was created");
+ } else {
+ testPassed("2D array texture with invalid type was not created")
+ }
+}
+
debug("");
var successfullyParsed = true;
</script>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/simultaneous_binding.html b/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/simultaneous_binding.html
index 228b4ab5cf..11d1eaa829 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/simultaneous_binding.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/simultaneous_binding.html
@@ -304,6 +304,17 @@ for (let genericBindPointValue of genericBindPointValues) {
gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 1);
wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "copyBufferSubData with double bound buffer");
+ debug("<hr/>Test that rejected operations do not change the bound buffer size");
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, tfBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, 8, gl.STATIC_DRAW);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "bufferData with double bound buffer");
+
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Uint8Array(16));
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "bufferSubData should succeed");
+ gl.bindBuffer(gl.ARRAY_BUFFER, null);
+
debug("<hr/>Test bufferData family with tf object unbound");
gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/transform_feedback.html b/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/transform_feedback.html
index 20256c6ace..16855453f0 100644
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/transform_feedback.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/transform_feedback/transform_feedback.html
@@ -64,6 +64,8 @@ if (!gl) {
runUnboundDeleteTest();
runBoundDeleteTest();
runOneOutputFeedbackTest();
+ runUnchangedBufferBindingsTest();
+ runNoOutputsTest();
// Must be the last test, since it's asynchronous and calls finishTest().
runTwoOutputFeedbackTest();
}
@@ -638,6 +640,70 @@ function runContextLostOneOutputFeedbackTest() {
finishTest();
}
+function runUnchangedBufferBindingsTest() {
+ debug("");
+ debug("Testing that buffer bindings cannot be changed while transform feedback is active");
+
+ program = wtu.setupTransformFeedbackProgram(
+ gl, [wtu.simpleVertexShader, wtu.simpleColorFragmentShader], ['gl_Position'], gl.INTERLEAVED_ATTRIBS);
+
+ tf = gl.createTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
+
+ buf = gl.createBuffer();
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+
+ gl.beginTransformFeedback(gl.POINTS);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Transform feedback is active");
+
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, gl.createBuffer());
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Cannot change the bound buffer while transform feedback is active");
+ shouldBe("gl.getIndexedParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, 0)", "buf");
+
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Cannot rebind the same buffer while transform feedback is active");
+
+ gl.bindBufferRange(gl.TRANSFORM_FEEDBACK_BUFFER, 0, gl.createBuffer(), 0, 64);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Cannot change the bound buffer while transform feedback is active");
+ shouldBe("gl.getIndexedParameter(gl.TRANSFORM_FEEDBACK_BUFFER_BINDING, 0)", "buf");
+
+ gl.bindBufferRange(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buf, 0, 64);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Cannot rebind the same buffer while transform feedback is active");
+
+ gl.endTransformFeedback();
+ gl.deleteTransformFeedback(tf);
+ gl.deleteBuffer(buf);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No extra errors after the test");
+}
+
+function runNoOutputsTest() {
+ debug("");
+ debug("Testing transform feedback with no varyings to record");
+
+ tf = gl.createTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, tf);
+
+ buf = gl.createBuffer();
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, buf);
+
+ for (const mode of ['SEPARATE_ATTRIBS', 'INTERLEAVED_ATTRIBS']) {
+ program = wtu.setupTransformFeedbackProgram(
+ gl, [wtu.simpleVertexShader, wtu.simpleColorFragmentShader], [], gl[mode]);
+
+ debug(`Testing with ${mode}`);
+ gl.beginTransformFeedback(gl.POINTS);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "beginTransformFeedback: No varyings to record");
+ gl.useProgram(null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "useProgram: Transform feedback is not active");
+ gl.endTransformFeedback();
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "endTransformFeedback: Transform feedback is not active");
+ }
+
+ gl.deleteTransformFeedback(tf);
+ gl.deleteBuffer(buf);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "No extra errors after the test");
+}
+
debug("");
</script>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/00_test_list.txt
new file mode 100644
index 0000000000..d188fc30a6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/00_test_list.txt
@@ -0,0 +1,12 @@
+--min-version 2.0.1 readpixels-16gb-wasm-memory.html
+--min-version 2.0.1 readpixels-4gb-wasm-memory.html
+--min-version 2.0.1 teximage2d-16gb-wasm-memory.html
+--min-version 2.0.1 teximage2d-4gb-wasm-memory.html
+--min-version 2.0.1 texsubimage2d-16gb-wasm-memory.html
+--min-version 2.0.1 texsubimage2d-4gb-wasm-memory.html
+--min-version 2.0.1 bufferdata-16gb-wasm-memory.html
+--min-version 2.0.1 bufferdata-4gb-wasm-memory.html
+--min-version 2.0.1 buffersubdata-16gb-wasm-memory.html
+--min-version 2.0.1 buffersubdata-4gb-wasm-memory.html
+--min-version 2.0.1 getbuffersubdata-16gb-wasm-memory.html
+--min-version 2.0.1 getbuffersubdata-4gb-wasm-memory.html \ No newline at end of file
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-16gb-wasm-memory.html
new file mode 100644
index 0000000000..0dd21bf64f
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-16gb-wasm-memory.html
@@ -0,0 +1,51 @@
+<!--
+Copyright (c) 2024 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>bufferData test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that bufferData can be called on WebAssembly Memory of 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16 * 1024 * 1024 * 1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2, 3, 4]);
+const length = expectedData.length;
+const offset = SIZE - length;
+view.set(expectedData, offset);
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW, offset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+let actualData = new Uint8Array(length);
+gl.getBufferSubData(gl.ARRAY_BUFFER, 0, actualData);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+for (let i = 0; i < length; i++) {
+ shouldBe(`actualData[${i}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-4gb-wasm-memory.html
new file mode 100644
index 0000000000..2296c3a5ea
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/bufferdata-4gb-wasm-memory.html
@@ -0,0 +1,51 @@
+<!--
+Copyright (c) 2024 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>bufferData test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that bufferData can be called on WebAssembly Memory of 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4 * 1024 * 1024 * 1024 - PAGE;
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2, 3, 4]);
+const length = expectedData.length;
+const offset = SIZE - length;
+view.set(expectedData, offset);
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, view, gl.STATIC_DRAW, offset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+let actualData = new Uint8Array(length);
+gl.getBufferSubData(gl.ARRAY_BUFFER, 0, actualData);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+for (let i = 0; i < length; i++) {
+ shouldBe(`actualData[${i}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-16gb-wasm-memory.html
new file mode 100644
index 0000000000..08d6d1df50
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-16gb-wasm-memory.html
@@ -0,0 +1,52 @@
+<!--
+Copyright (c) 2024 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>bufferSubData test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that bufferSubData can be called on WebAssembly Memory of 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16 * 1024 * 1024 * 1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2]);
+const length = expectedData.length;
+let srcOffset = SIZE - length;
+view.set(expectedData, srcOffset);
+const dstByteOffset = 4;
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, 8, gl.STATIC_DRAW);
+gl.bufferSubData(gl.ARRAY_BUFFER, dstByteOffset, view, srcOffset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+let actualData = new Uint8Array(length);
+gl.getBufferSubData(gl.ARRAY_BUFFER, dstByteOffset, actualData);
+for (let i = 0; i < length; i++) {
+ shouldBe(`actualData[${i}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-4gb-wasm-memory.html
new file mode 100644
index 0000000000..2834a6901b
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/buffersubdata-4gb-wasm-memory.html
@@ -0,0 +1,52 @@
+<!--
+Copyright (c) 2024 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>bufferSubData test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that bufferSubData can be called on WebAssembly Memory of 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4 * 1024 * 1024 * 1024 - PAGE;
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2]);
+const length = expectedData.length;
+let srcOffset = SIZE - length;
+view.set(expectedData, srcOffset);
+const dstByteOffset = 4;
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, 8, gl.STATIC_DRAW);
+gl.bufferSubData(gl.ARRAY_BUFFER, dstByteOffset, view, srcOffset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+let actualData = new Uint8Array(length);
+gl.getBufferSubData(gl.ARRAY_BUFFER, dstByteOffset, actualData);
+for (let i = 0; i < length; i++) {
+ shouldBe(`actualData[${i}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-16gb-wasm-memory.html
new file mode 100644
index 0000000000..8390957b02
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-16gb-wasm-memory.html
@@ -0,0 +1,48 @@
+<!--
+Copyright (c) 2024 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>getBufferSubData test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that getBufferSubData can be called on WebAssembly Memory of 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16 * 1024 * 1024 * 1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2, 3, 4]);
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, expectedData, gl.STATIC_DRAW);
+
+const length = expectedData.length;
+const offset = SIZE - length;
+gl.getBufferSubData(gl.ARRAY_BUFFER, 0, view, offset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+for (let i = 0; i < length; i++) {
+ shouldBe(`view[${i + offset}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-4gb-wasm-memory.html
new file mode 100644
index 0000000000..09a336b753
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/getbuffersubdata-4gb-wasm-memory.html
@@ -0,0 +1,48 @@
+<!--
+Copyright (c) 2024 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>getBufferSubData test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that getBufferSubData can be called on WebAssembly Memory of 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4 * 1024 * 1024 * 1024 - PAGE;
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE / PAGE }).buffer);
+let expectedData = new Uint8Array([1, 2, 3, 4]);
+
+let buf = gl.createBuffer();
+gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+gl.bufferData(gl.ARRAY_BUFFER, expectedData, gl.STATIC_DRAW);
+
+const length = expectedData.length;
+const offset = SIZE - length;
+gl.getBufferSubData(gl.ARRAY_BUFFER, 0, view, offset, length);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+for (let i = 0; i < length; i++) {
+ shouldBe(`view[${i + offset}]`, `expectedData[${i}]`);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-16gb-wasm-memory.html
new file mode 100644
index 0000000000..af74678dda
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-16gb-wasm-memory.html
@@ -0,0 +1,51 @@
+<!--
+Copyright (c) 2023 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>gl.readPixels() test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("");
+debug("Tests that gl.readPixels() can be called on WebAssembly Memory of 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16*1024*1024*1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE/PAGE }).buffer);
+
+// Clear the canvas to a specific color
+const expectedColor = [42, 84, 128, 255];
+gl.clearColor(expectedColor[0]/255.0, expectedColor[1]/255.0, expectedColor[2]/255.0, expectedColor[3]/255.0);
+gl.clear(gl.COLOR_BUFFER_BIT);
+
+// Test that gl.readPixels() can be called with a high offset to Memory
+const offset = SIZE - 4;
+view.set([0,0,0,0], offset); // For good measure, clear data at offset before reading
+gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+let obtainedColor = view.subarray(offset, offset+4);
+shouldBe('obtainedColor[0]', 'expectedColor[0]');
+shouldBe('obtainedColor[1]', 'expectedColor[1]');
+shouldBe('obtainedColor[2]', 'expectedColor[2]');
+shouldBe('obtainedColor[3]', 'expectedColor[3]');
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-4gb-wasm-memory.html
new file mode 100644
index 0000000000..f97a3ccba0
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/readpixels-4gb-wasm-memory.html
@@ -0,0 +1,50 @@
+<!--
+Copyright (c) 2023 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>gl.readPixels() test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that gl.readPixels() can be called on WebAssembly Memory of 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4*1024*1024*1024 - PAGE; // when uint32_t size is max, we can only reach one page short of full 4GB
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE/PAGE }).buffer);
+
+// Clear the canvas to a specific color
+const expectedColor = [42, 84, 128, 255];
+gl.clearColor(expectedColor[0]/255.0, expectedColor[1]/255.0, expectedColor[2]/255.0, expectedColor[3]/255.0);
+gl.clear(gl.COLOR_BUFFER_BIT);
+
+// Test that gl.readPixels() can be called with a high offset to Memory
+const offset = SIZE - 4;
+view.set([0,0,0,0], offset); // For good measure, clear data at offset before reading
+gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+let obtainedColor = view.subarray(offset, offset+4);
+shouldBe('obtainedColor[0]', 'expectedColor[0]');
+shouldBe('obtainedColor[1]', 'expectedColor[1]');
+shouldBe('obtainedColor[2]', 'expectedColor[2]');
+shouldBe('obtainedColor[3]', 'expectedColor[3]');
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-16gb-wasm-memory.html
new file mode 100644
index 0000000000..8ce5111953
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-16gb-wasm-memory.html
@@ -0,0 +1,85 @@
+<!--
+Copyright (c) 2023 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>gl.texImage2D() test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that gl.texImage2D() can be called on WebAssembly Memory 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16*1024*1024*1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE/PAGE }).buffer);
+
+function compileShader(type, src) {
+ let shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ let log = gl.getShaderInfoLog(shader);
+ if (log) debug(log);
+ return shader;
+}
+
+function createProgram(vs, fs) {
+ let program = gl.createProgram();
+ gl.attachShader(program, vs);
+ gl.attachShader(program, fs);
+ gl.bindAttribLocation(program, 0, 'pos');
+ gl.linkProgram(program);
+ gl.useProgram(program);
+ return program;
+}
+
+let program = createProgram(
+ compileShader(gl.VERTEX_SHADER, `
+ varying vec2 uv;
+ attribute vec2 pos;
+ void main() { uv = pos; gl_Position = vec4(pos*2.0-vec2(1.0,1.0),0,1); }`),
+ compileShader(gl.FRAGMENT_SHADER, `
+ precision lowp float;
+ uniform sampler2D tex;
+ varying vec2 uv;
+ void main() { gl_FragColor = texture2D(tex,uv); }`));
+
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);
+gl.vertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 0, 0);
+gl.enableVertexAttribArray(0);
+
+let texture = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, texture);
+
+// Test uploading an image
+const expectedColor = [42, 84, 128, 255];
+const offset = SIZE - 4;
+view.set(expectedColor, offset);
+gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+// Test rendering with that image
+gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+// Verify that we rendered what we expected
+wtu.checkCanvasRect(gl, 0, 0, 1, 1, expectedColor, "texImage2D produced expected color");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-4gb-wasm-memory.html
new file mode 100644
index 0000000000..5d6897347d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/teximage2d-4gb-wasm-memory.html
@@ -0,0 +1,85 @@
+<!--
+Copyright (c) 2023 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>gl.texImage2D() test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that gl.texImage2D() can be called on WebAssembly Memory 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4*1024*1024*1024 - PAGE; // when uint32_t size is max, we can only reach one page short of full 4GB
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE/PAGE }).buffer);
+
+function compileShader(type, src) {
+ let shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ let log = gl.getShaderInfoLog(shader);
+ if (log) debug(log);
+ return shader;
+}
+
+function createProgram(vs, fs) {
+ let program = gl.createProgram();
+ gl.attachShader(program, vs);
+ gl.attachShader(program, fs);
+ gl.bindAttribLocation(program, 0, 'pos');
+ gl.linkProgram(program);
+ gl.useProgram(program);
+ return program;
+}
+
+let program = createProgram(
+ compileShader(gl.VERTEX_SHADER, `
+ varying vec2 uv;
+ attribute vec2 pos;
+ void main() { uv = pos; gl_Position = vec4(pos*2.0-vec2(1.0,1.0),0,1); }`),
+ compileShader(gl.FRAGMENT_SHADER, `
+ precision lowp float;
+ uniform sampler2D tex;
+ varying vec2 uv;
+ void main() { gl_FragColor = texture2D(tex,uv); }`));
+
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);
+gl.vertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 0, 0);
+gl.enableVertexAttribArray(0);
+
+let texture = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, texture);
+
+// Test uploading an image
+const expectedColor = [42, 84, 128, 255];
+const offset = SIZE - 4;
+view.set(expectedColor, offset);
+gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+// Test rendering with that image
+gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+// Verify that we rendered what we expected
+wtu.checkCanvasRect(gl, 0, 0, 1, 1, expectedColor, "texImage2D produced expected color");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-16gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-16gb-wasm-memory.html
new file mode 100644
index 0000000000..328d42ec49
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-16gb-wasm-memory.html
@@ -0,0 +1,86 @@
+<!--
+Copyright (c) 2023 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>gl.texSubImage2D() test to Wasm Memory 16GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that gl.texSubImage2D() can be called on WebAssembly Memory 16GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 16*1024*1024*1024;
+let view = new Uint8Array(new WebAssembly.Memory({ index: 'i64', initial: SIZE/PAGE }).buffer);
+
+function compileShader(type, src) {
+ let shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ let log = gl.getShaderInfoLog(shader);
+ if (log) debug(log);
+ return shader;
+}
+
+function createProgram(vs, fs) {
+ let program = gl.createProgram();
+ gl.attachShader(program, vs);
+ gl.attachShader(program, fs);
+ gl.bindAttribLocation(program, 0, 'pos');
+ gl.linkProgram(program);
+ gl.useProgram(program);
+ return program;
+}
+
+let program = createProgram(
+ compileShader(gl.VERTEX_SHADER, `
+ varying vec2 uv;
+ attribute vec2 pos;
+ void main() { uv = pos; gl_Position = vec4(pos*2.0-vec2(1.0,1.0),0,1); }`),
+ compileShader(gl.FRAGMENT_SHADER, `
+ precision lowp float;
+ uniform sampler2D tex;
+ varying vec2 uv;
+ void main() { gl_FragColor = texture2D(tex,uv); }`));
+
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);
+gl.vertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 0, 0);
+gl.enableVertexAttribArray(0);
+
+let texture = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, texture);
+
+// Test uploading an image
+const expectedColor = [42, 84, 128, 255];
+const offset = SIZE - 4;
+view.set(expectedColor, offset);
+gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
+gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+// Test rendering with that image
+gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+// Verify that we rendered what we expected
+wtu.checkCanvasRect(gl, 0, 0, 1, 1, expectedColor, "texSubImage2D produced expected color");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-4gb-wasm-memory.html b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-4gb-wasm-memory.html
new file mode 100644
index 0000000000..d7756629a0
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/wasm/texsubimage2d-4gb-wasm-memory.html
@@ -0,0 +1,86 @@
+<!--
+Copyright (c) 2023 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>gl.texSubImage2D() test to Wasm Memory 4GB in size.</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="2" height="2" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description(document.title);
+debug("Tests that gl.texSubImage2D() can be called on WebAssembly Memory 4GB in size.");
+debug("");
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext("canvas", undefined, 2);
+
+const PAGE = 65536;
+const SIZE = 4*1024*1024*1024 - PAGE; // when uint32_t size is max, we can only reach one page short of full 4GB
+let view = new Uint8Array(new WebAssembly.Memory({ initial: SIZE/PAGE }).buffer);
+
+function compileShader(type, src) {
+ let shader = gl.createShader(type);
+ gl.shaderSource(shader, src);
+ gl.compileShader(shader);
+ let log = gl.getShaderInfoLog(shader);
+ if (log) debug(log);
+ return shader;
+}
+
+function createProgram(vs, fs) {
+ let program = gl.createProgram();
+ gl.attachShader(program, vs);
+ gl.attachShader(program, fs);
+ gl.bindAttribLocation(program, 0, 'pos');
+ gl.linkProgram(program);
+ gl.useProgram(program);
+ return program;
+}
+
+let program = createProgram(
+ compileShader(gl.VERTEX_SHADER, `
+ varying vec2 uv;
+ attribute vec2 pos;
+ void main() { uv = pos; gl_Position = vec4(pos*2.0-vec2(1.0,1.0),0,1); }`),
+ compileShader(gl.FRAGMENT_SHADER, `
+ precision lowp float;
+ uniform sampler2D tex;
+ varying vec2 uv;
+ void main() { gl_FragColor = texture2D(tex,uv); }`));
+
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]), gl.STATIC_DRAW);
+gl.vertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, 0, 0);
+gl.enableVertexAttribArray(0);
+
+let texture = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, texture);
+
+// Test uploading an image
+const expectedColor = [42, 84, 128, 255];
+const offset = SIZE - 4;
+view.set(expectedColor, offset);
+gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
+gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, view, offset);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+// Test rendering with that image
+gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+// Verify that we rendered what we expected
+wtu.checkCanvasRect(gl, 0, 0, 1, 1, expectedColor, "texSubImage2D produced expected color");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>