summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance2/extensions
diff options
context:
space:
mode:
Diffstat (limited to '')
-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
13 files changed, 3094 insertions, 0 deletions
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>