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