summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/conformance2/extensions
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/conformance2/extensions')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt19
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-float.html508
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-half-float.html27
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html316
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-filter-anisotropic.html26
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-norm16.html253
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-draw-buffers-indexed.html573
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2.html524
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_depth.html138
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_draw_buffers.html158
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_flat_varying.html93
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_instanced_draw.html105
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_non_multiview_shaders.html93
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_single_view_operations.html253
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_timer_query.html142
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_transform_feedback.html125
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions-in-shaders.html115
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions.html64
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/required-extensions.html58
-rw-r--r--dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html1021
20 files changed, 4611 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt
new file mode 100644
index 0000000000..559071ff06
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/00_test_list.txt
@@ -0,0 +1,19 @@
+ext-color-buffer-float.html
+--min-version 2.0.1 ext-color-buffer-half-float.html
+ext-disjoint-timer-query-webgl2.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 oes-draw-buffers-indexed.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
+--min-version 2.0.1 ovr_multiview2_flat_varying.html
+--min-version 2.0.1 ovr_multiview2_instanced_draw.html
+--min-version 2.0.1 ovr_multiview2_non_multiview_shaders.html
+--min-version 2.0.1 ovr_multiview2_single_view_operations.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-multi-draw-instanced-base-vertex-base-instance.html
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-float.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-float.html
new file mode 100644
index 0000000000..b57a6ca10d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-float.html
@@ -0,0 +1,508 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL EXT_color_buffer_float 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>
+<!-- Shaders for testing floating-point textures -->
+<script id="testFragmentShader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D tex;
+uniform vec4 subtractor;
+varying vec2 texCoord;
+void main()
+{
+ vec4 color = texture2D(tex, texCoord);
+ if (abs(color.r - subtractor.r) +
+ abs(color.g - subtractor.g) +
+ abs(color.b - subtractor.b) +
+ abs(color.a - subtractor.a) < 16.0) {
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+ } else {
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+ }
+}
+</script>
+<!-- Shaders for testing floating-point render targets -->
+<script id="floatingPointFragmentShader" type="x-shader/x-fragment">
+void main()
+{
+ gl_FragColor = vec4(1000.0, 1000.0, 1000.0, 1000.0);
+}
+</script>
+<script>
+"use strict";
+
+function allocateTexture()
+{
+ var texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture parameter setup should succeed");
+ return texture;
+}
+
+function checkRenderingResults()
+{
+ wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+function arrayToString(arr, size) {
+ var mySize;
+ if (!size)
+ mySize = arr.length;
+ else
+ mySize = size;
+ var out = "[";
+ for (var ii = 0; ii < mySize; ++ii) {
+ if (ii > 0) {
+ out += ", ";
+ }
+ out += arr[ii];
+ }
+ return out + "]";
+}
+
+function runReadbackTest(testProgram, subtractor)
+{
+ // Verify floating point readback
+ debug("Checking readback of floating-point values");
+ var buf = new Float32Array(4);
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT , buf);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "readPixels from floating-point framebuffer should succeed");
+ var ok = true;
+ var tolerance = 8.0; // TODO: factor this out from both this test and the subtractor shader above.
+ for (var ii = 0; ii < buf.length; ++ii) {
+ if (Math.abs(buf[ii] - subtractor[ii]) > tolerance) {
+ ok = false;
+ break;
+ }
+ }
+ if (ok) {
+ testPassed("readPixels of float-type data from floating-point framebuffer succeeded");
+ } else {
+ testFailed("readPixels of float-type data from floating-point framebuffer failed: expected "
+ + arrayToString(subtractor, 4) + ", got " + arrayToString(buf));
+ }
+}
+
+function runFloatTextureRenderTargetTest(enabled, internalFormat, format, testProgram, numberOfChannels, subtractor, texSubImageCover)
+{
+ var formatString = wtu.glEnumToString(gl, internalFormat);
+ debug("");
+ debug("testing floating-point " + formatString + " texture render target" + (texSubImageCover > 0 ? " after calling texSubImage" : ""));
+
+ var texture = allocateTexture();
+ var width = 2;
+ var height = 2;
+ gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, gl.FLOAT, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point texture allocation should succeed");
+
+ // Try to use this texture as a render target.
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+
+ var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (!enabled) {
+ if (completeStatus == gl.FRAMEBUFFER_COMPLETE && !enabled)
+ testFailed("floating-point " + formatString + " render target should not be supported without enabling EXT_color_buffer_float");
+ else
+ testPassed("floating-point " + formatString + " render target should not be supported without enabling EXT_color_buffer_float");
+ return;
+ }
+
+ if (completeStatus != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("floating-point " + formatString + " render target not supported");
+ return;
+ }
+
+ if (texSubImageCover > 0) {
+ // Ensure that replacing the whole texture or a part of it with texSubImage2D doesn't affect renderability
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ var data = new Float32Array(width * height * numberOfChannels * texSubImageCover);
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height * texSubImageCover, format, gl.FLOAT, data);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage2D should succeed if EXT_color_buffer_float is enabled");
+ gl.bindTexture(gl.TEXTURE_2D, null);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("render target support changed after calling texSubImage2D");
+ return;
+ }
+ }
+
+ var renderProgram =
+ wtu.setupProgram(gl,
+ [wtu.simpleVertexShader, "floatingPointFragmentShader"],
+ ['vPosition'],
+ [0]);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rendering to floating-point texture should succeed");
+
+ // Now sample from the floating-point texture and verify we got the correct values.
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.useProgram(testProgram);
+ gl.uniform1i(gl.getUniformLocation(testProgram, "tex"), 0);
+ gl.uniform4fv(gl.getUniformLocation(testProgram, "subtractor"), subtractor);
+ wtu.clearAndDrawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rendering from floating-point texture should succeed");
+ checkRenderingResults();
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ runReadbackTest(testProgram, subtractor);
+}
+
+function runFloatRenderbufferRenderTargetTest(enabled, internalFormat, testProgram, numberOfChannels, subtractor)
+{
+ var formatString = wtu.glEnumToString(gl, internalFormat);
+ var samples = [0];
+ if (enabled) {
+ samples = Array.prototype.slice.call(gl.getInternalformatParameter(gl.RENDERBUFFER, internalFormat, gl.SAMPLES));
+ samples.push(0);
+ }
+ for (var ndx = 0; ndx < samples.length; ++ndx) {
+ debug("");
+ debug("testing floating-point " + formatString + " renderbuffer render target with number of samples " + samples[ndx]);
+
+ var colorbuffer = gl.createRenderbuffer();
+ var width = 2;
+ var height = 2;
+ gl.bindRenderbuffer(gl.RENDERBUFFER, colorbuffer);
+ if (samples[ndx] == 0)
+ gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height);
+ else
+ gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples[ndx], internalFormat, width, height);
+ if (!enabled) {
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "floating-point renderbuffer allocation should fail if EXT_color_buffer_float is not enabled");
+ return;
+ } else {
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "floating-point renderbuffer allocation should succeed if EXT_color_buffer_float is enabled");
+ }
+
+ // Try to use this renderbuffer as a render target.
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorbuffer);
+
+ var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (completeStatus != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("floating-point " + formatString + " render target not supported");
+ return;
+ }
+ var resolveColorRbo = null;
+ var resolveFbo = null;
+ if (samples[ndx] > 0) {
+ resolveColorRbo = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, resolveColorRbo);
+ gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height);
+ resolveFbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, resolveFbo);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, resolveColorRbo);
+ completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (completeStatus != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("Failed to create resolve framebuffer");
+ return;
+ }
+ }
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.clearColor(1000.0, 1000.0, 1000.0, 1000.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ if (samples[ndx] > 0) {
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, resolveFbo);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, resolveFbo);
+ }
+ runReadbackTest(testProgram, subtractor);
+ }
+}
+
+function runRGB16FNegativeTest()
+{
+ debug("");
+ debug("testing RGB16F isn't color renderable");
+
+ var texture = allocateTexture();
+ var width = 2;
+ var height = 2;
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB16F, width, height, 0, gl.RGB, gl.FLOAT, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "RGB16F texture allocation should succeed");
+
+ // Try to use this texture as a render target.
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+
+ var completeStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (completeStatus == gl.FRAMEBUFFER_COMPLETE)
+ testFailed("RGB16F render target should not be supported with or without enabling EXT_color_buffer_float");
+ else
+ testPassed("RGB16F render target should not be supported with or without enabling EXT_color_buffer_float");
+ gl.deleteTexture(texture);
+
+ var colorbuffer = gl.createRenderbuffer();
+ gl.bindRenderbuffer(gl.RENDERBUFFER, colorbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGB16F, width, height);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "RGB16F renderbuffer allocation should fail with or without enabling EXT_color_buffer_float");
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+ gl.deleteRenderbuffer(colorbuffer);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fbo);
+}
+
+function runUniqueObjectTest()
+{
+ debug("");
+ debug("Testing that getExtension() returns the same object each time");
+ gl.getExtension("EXT_color_buffer_float").myProperty = 2;
+ webglHarnessCollectGarbage();
+ shouldBe('gl.getExtension("EXT_color_buffer_float").myProperty', '2');
+}
+
+function runInternalFormatQueryTest()
+{
+ debug("");
+ debug("testing the internal format query");
+
+ var maxSamples = gl.getParameter(gl.MAX_SAMPLES);
+ const formats = [gl.RGBA16F, gl.R32F, gl.RG32F, gl.RGBA32F, gl.R16F, gl.RG16F, gl.R11F_G11F_B10F];
+ var firstMultiOnlyFormat = 4;
+ for (var fmt = 0; fmt < formats.length; ++fmt) {
+ var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, formats[fmt], gl.SAMPLES);
+ if (fmt >= firstMultiOnlyFormat && (samples.length == 0 || samples[0] < maxSamples)) {
+ testFailed("the maximum value in SAMPLES should be at least " + maxSamples);
+ return;
+ }
+
+ var prevSampleCount = 0;
+ var sampleCount;
+ for (var ndx = 0; ndx < samples.length; ++ndx, prevSampleCount = sampleCount) {
+ sampleCount = samples[ndx];
+ // sample count must be > 0
+ if (sampleCount <= 0) {
+ testFailed("Expected sample count to be at least one; got " + sampleCount);
+ return;
+ }
+
+ // samples must be ordered descending
+ if (ndx > 0 && sampleCount >= prevSampleCount) {
+ testFailed("Expected sample count to be ordered in descending order; got " + prevSampleCount + " at index " + (ndx - 1) + ", and " + sampleCount + " at index " + ndx);
+ return;
+ }
+ }
+ }
+ testPassed("Internal format query succeeded");
+}
+
+function runCopyTexImageTest(enabled)
+{
+ var width = 16;
+ var height = 16;
+ var level = 0;
+ var cases = [
+ {
+ internalformat: "RGBA16F",
+ format: "RGBA",
+ type: "HALF_FLOAT",
+ destInternalformat: "R16F",
+ data: new Uint16Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA32F",
+ format: "RGBA",
+ type: "FLOAT",
+ destInternalformat: "R32F",
+ data: new Float32Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA16F",
+ format: "RGBA",
+ type: "HALF_FLOAT",
+ destInternalformat: "RG16F",
+ data: new Uint16Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA32F",
+ format: "RGBA",
+ type: "FLOAT",
+ destInternalformat: "RG32F",
+ data: new Float32Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA16F",
+ format: "RGBA",
+ type: "HALF_FLOAT",
+ destInternalformat: "RGB16F",
+ data: new Uint16Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA32F",
+ format: "RGBA",
+ type: "FLOAT",
+ destInternalformat: "RGB32F",
+ data: new Float32Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA16F",
+ format: "RGBA",
+ type: "HALF_FLOAT",
+ destInternalformat: "RGBA16F",
+ data: new Uint16Array(width * height * 4)
+ },
+ {
+ internalformat: "RGBA32F",
+ format: "RGBA",
+ type: "FLOAT",
+ destInternalformat: "RGBA32F",
+ data: new Float32Array(width * height * 4)
+ },
+ {
+ internalformat: "R11F_G11F_B10F",
+ format: "RGB",
+ type: "FLOAT",
+ destInternalformat: "R11F_G11F_B10F",
+ data: new Float32Array(width * height * 3)
+ }
+ ];
+ cases.forEach(function(testcase) {
+ debug("");
+ debug("Testing CopyTexImage2D for internalformat: " + testcase.destInternalformat);
+
+ var internalformat = gl[testcase.internalformat];
+ var format = gl[testcase.format];
+ var type = gl[testcase.type];
+ var destInternalformat = gl[testcase.destInternalformat];
+ var texSrc = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texSrc);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ var data = testcase.data;
+ gl.texImage2D(gl.TEXTURE_2D, level, internalformat, width, height, 0, format, type, data);
+ var fbo = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texSrc, level);
+ var texDest = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texDest);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Setup framebuffer with texture should succeed.");
+ if (enabled) {
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_COMPLETE");
+ gl.copyTexImage2D(gl.TEXTURE_2D, level, destInternalformat, 0, 0, width, height, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "CopyTexImage2D should succeed.");
+ } else {
+ shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
+ gl.copyTexImage2D(gl.TEXTURE_2D, level, destInternalformat, 0, 0, width, height, 0);
+ wtu.glErrorShouldBe(gl, [gl.INVALID_ENUM, gl.INVALID_FRAMEBUFFER_OPERATION], "CopyTexImage2D should fail.");
+ }
+
+ gl.deleteTexture(texDest);
+ gl.deleteTexture(texSrc);
+ gl.deleteFramebuffer(fbo);
+ });
+}
+
+description("This test verifies the functionality of the EXT_color_buffer_float extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ var texturedShaders = [
+ wtu.simpleTextureVertexShader,
+ "testFragmentShader"
+ ];
+ var testProgram =
+ wtu.setupProgram(gl,
+ texturedShaders,
+ ['vPosition', 'texCoord0'],
+ [0, 1]);
+ var quadParameters = wtu.setupUnitQuad(gl, 0, 1);
+
+ // Ensure these formats can't be used for rendering if the extension is disabled
+ runFloatTextureRenderTargetTest(false, gl.R16F, gl.RED);
+ runFloatTextureRenderTargetTest(false, gl.RG16F, gl.RG);
+ runFloatTextureRenderTargetTest(false, gl.RGBA16F, gl.RGBA);
+ runFloatTextureRenderTargetTest(false, gl.R32F, gl.RED);
+ runFloatTextureRenderTargetTest(false, gl.RG32F, gl.RG);
+ runFloatTextureRenderTargetTest(false, gl.RGBA32F, gl.RGBA);
+ runFloatTextureRenderTargetTest(false, gl.R11F_G11F_B10F, gl.RGB);
+
+ runFloatRenderbufferRenderTargetTest(false, gl.R16F);
+ runFloatRenderbufferRenderTargetTest(false, gl.RG16F);
+ runFloatRenderbufferRenderTargetTest(false, gl.RGBA16F);
+ runFloatRenderbufferRenderTargetTest(false, gl.R32F);
+ runFloatRenderbufferRenderTargetTest(false, gl.RG32F);
+ runFloatRenderbufferRenderTargetTest(false, gl.RGBA32F);
+ runFloatRenderbufferRenderTargetTest(false, gl.R11F_G11F_B10F);
+
+ // Ensure RGB16F can't be used for rendering.
+ runRGB16FNegativeTest();
+
+ runCopyTexImageTest(false);
+
+ if (!gl.getExtension("EXT_color_buffer_float")) {
+ testPassed("No EXT_color_buffer_float support -- this is legal");
+ } else {
+ testPassed("Successfully enabled EXT_color_buffer_float extension");
+
+ runInternalFormatQueryTest();
+
+ runFloatTextureRenderTargetTest(true, gl.R16F, gl.RED, testProgram, 1, [1000, 1, 1, 1], 0);
+ runFloatTextureRenderTargetTest(true, gl.RG16F, gl.RG, testProgram, 2, [1000, 1000, 1, 1], 0);
+ runFloatTextureRenderTargetTest(true, gl.RGBA16F, gl.RGBA, testProgram, 4, [1000, 1000, 1000, 1000], 0);
+ runFloatTextureRenderTargetTest(true, gl.R32F, gl.RED, testProgram, 1, [1000, 1, 1, 1], 0);
+ runFloatTextureRenderTargetTest(true, gl.RG32F, gl.RG, testProgram, 2, [1000, 1000, 1, 1], 0);
+ runFloatTextureRenderTargetTest(true, gl.RGBA32F, gl.RGBA, testProgram, 4, [1000, 1000, 1000, 1000], 0);
+ runFloatTextureRenderTargetTest(true, gl.R11F_G11F_B10F, gl.RGB, testProgram, 3, [1000, 1000, 1000, 1], 0);
+ runFloatTextureRenderTargetTest(true, gl.RGBA32F, gl.RGBA, testProgram, 4, [1000, 1000, 1000, 1000], 1);
+ runFloatTextureRenderTargetTest(true, gl.RGBA32F, gl.RGBA, testProgram, 4, [1000, 1000, 1000, 1000], 0.5);
+
+ runFloatRenderbufferRenderTargetTest(true, gl.R16F, testProgram, 1, [1000, 1, 1, 1]);
+ runFloatRenderbufferRenderTargetTest(true, gl.RG16F, testProgram, 2, [1000, 1000, 1, 1]);
+ runFloatRenderbufferRenderTargetTest(true, gl.RGBA16F, testProgram, 4, [1000, 1000, 1000, 1000]);
+ runFloatRenderbufferRenderTargetTest(true, gl.R32F, testProgram, 1, [1000, 1, 1, 1]);
+ runFloatRenderbufferRenderTargetTest(true, gl.RG32F, testProgram, 2, [1000, 1000, 1, 1]);
+ runFloatRenderbufferRenderTargetTest(true, gl.RGBA32F, testProgram, 4, [1000, 1000, 1000, 1000]);
+ runFloatRenderbufferRenderTargetTest(true, gl.R11F_G11F_B10F, testProgram, 3, [1000, 1000, 1000, 1]);
+
+ // Ensure EXT_color_buffer_float does not enable RGB16F as color renderable.
+ runRGB16FNegativeTest();
+
+ runCopyTexImageTest(true);
+
+ runUniqueObjectTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-half-float.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-half-float.html
new file mode 100644
index 0000000000..4ced76e0fd
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-color-buffer-half-float.html
@@ -0,0 +1,27 @@
+<!--
+Copyright (c) 2020 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 EXT_color_buffer_half_float 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>
+var version = 2;
+</script>
+<script src="../../js/tests/ext-color-buffer-half-float.js"></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
new file mode 100644
index 0000000000..c051fa36a3
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-disjoint-timer-query-webgl2.html
@@ -0,0 +1,316 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2 EXT_disjoint_timer_query_webgl2 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 the EXT_disjoint_timer_query_webgl2 extension, if it is available.");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+var gl = wtu.create3DContext(canvas, null, 2);
+var ext = null;
+var query = null;
+var query2 = null;
+var elapsed_query = null;
+var timestamp_query1 = null;
+var timestamp_query2 = null;
+var availability_retry = 500;
+var timestamp_counter_bits = 0;
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+ finishTest();
+} else {
+ testPassed("WebGL context exists");
+
+ // Query the extension and store globally so shouldBe can access it
+ ext = wtu.getExtensionWithKnownPrefixes(gl, "EXT_disjoint_timer_query_webgl2");
+ if (!ext) {
+ testPassed("No EXT_disjoint_timer_query_webgl2 support -- this is legal");
+ finishTest();
+ } else {
+ runSanityTests();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ // Clear disjoint value.
+ gl.getParameter(ext.GPU_DISJOINT_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ runElapsedTimeTest();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ timestamp_counter_bits = gl.getQuery(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT);
+ if (timestamp_counter_bits > 0) {
+ runTimeStampTest();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ }
+ verifyQueryResultsNotAvailable();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ window.requestAnimationFrame(checkQueryResults);
+ }
+}
+
+function runSanityTests() {
+ debug("");
+ debug("Testing other query types");
+ query = gl.createQuery();
+ gl.beginQuery(gl.ANY_SAMPLES_PASSED, query);
+ shouldBeTrue("gl.getQuery(gl.ANY_SAMPLES_PASSED, gl.CURRENT_QUERY) !== null");
+ gl.endQuery(gl.ANY_SAMPLES_PASSED);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "enabling EXT_disjoint_timer_query_webgl2 should not break other queries");
+
+ debug("");
+ debug("Testing timer query expectations");
+
+ shouldBe("ext.QUERY_COUNTER_BITS_EXT", "0x8864");
+ shouldBe("ext.TIME_ELAPSED_EXT", "0x88BF");
+ shouldBe("ext.TIMESTAMP_EXT", "0x8E28");
+ shouldBe("ext.GPU_DISJOINT_EXT", "0x8FBB");
+
+ shouldBe("gl.isQuery(null)", "false");
+
+ shouldBeTrue("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY) === null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ shouldBeTrue("gl.getQuery(ext.TIME_ELAPSED_EXT, ext.QUERY_COUNTER_BITS_EXT) >= 30");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ shouldBeTrue("gl.getQuery(ext.TIMESTAMP_EXT, gl.CURRENT_QUERY) === null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ // Certain drivers set timestamp counter bits to 0 as they don't support timestamps
+ shouldBeTrue("gl.getQuery(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT) >= 30 || " +
+ "gl.getQuery(ext.TIMESTAMP_EXT, ext.QUERY_COUNTER_BITS_EXT) === 0");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing time elapsed query lifecycle");
+ query = gl.createQuery();
+ shouldBe("gl.isQuery(query)", "false");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Query creation must succeed.");
+ gl.beginQuery(ext.TIMESTAMP_EXT, query);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Beginning a timestamp query should fail.");
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
+ shouldBe("gl.isQuery(query)", "true");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Beginning an inactive time elapsed query should succeed.");
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Attempting to begin an active query should fail.");
+ gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Fetching query result availability of an active query should fail.");
+ gl.getQueryParameter(query, gl.QUERY_RESULT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Fetching query result of an active query should fail.");
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "query");
+ gl.endQuery(ext.TIME_ELAPSED_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Ending an active time elapsed query should succeed.");
+ gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Fetching query result availability after query end should succeed.");
+ gl.endQuery(ext.TIME_ELAPSED_EXT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Attempting to end an inactive query should fail.");
+ ext.queryCounterEXT(query, ext.TIMESTAMP_EXT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Should not be able to use time elapsed query to store a timestamp.");
+ gl.deleteQuery(query);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Query deletion must succeed.");
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Beginning a deleted query must fail.");
+ gl.getQueryParameter(query, gl.QUERY_RESULT_AVAILABLE);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Fetching query result availability after query deletion should fail.");
+ shouldBe("gl.isQuery(query)", "false");
+
+ debug("");
+ debug("Testing timestamp counter");
+ query = gl.createQuery();
+ shouldThrow("ext.queryCounterEXT(null, ext.TIMESTAMP_EXT)");
+ ext.queryCounterEXT(query, ext.TIMESTAMP_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Timestamp counter queries should work.");
+ gl.deleteQuery(query);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Performing parameter sanity checks");
+ gl.getParameter(ext.TIMESTAMP_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "getParameter timestamp calls should work.");
+ gl.getParameter(ext.GPU_DISJOINT_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "getParameter disjoint calls should work.");
+
+ debug("");
+ debug("Testing current query conditions");
+ query = gl.createQuery();
+ query2 = gl.createQuery();
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "null");
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query);
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "query");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing failed begin query should not change the current query.");
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Beginning an elapsed query without ending should fail.");
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "query");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing beginning a timestamp query is invalid and should not change the elapsed query.");
+ gl.beginQuery(ext.TIMESTAMP_EXT, query2)
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM);
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "query");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing timestamp queries end immediately so are never current.");
+ ext.queryCounterEXT(query2, ext.TIMESTAMP_EXT);
+ shouldBe("gl.getQuery(ext.TIMESTAMP_EXT, gl.CURRENT_QUERY)", "null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing ending the query should clear the current query.");
+ gl.endQuery(ext.TIME_ELAPSED_EXT);
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ debug("");
+ debug("Testing beginning a elapsed query using a timestamp query should fail and not affect current query.")
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, query2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Switching query targets should fail.");
+ shouldBe("gl.getQuery(ext.TIME_ELAPSED_EXT, gl.CURRENT_QUERY)", "null");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+
+ gl.deleteQuery(query);
+ gl.deleteQuery(query2);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors at end of sanity tests");
+}
+
+function runElapsedTimeTest() {
+ debug("");
+ debug("Testing elapsed time query");
+
+ elapsed_query = gl.createQuery();
+ gl.beginQuery(ext.TIME_ELAPSED_EXT, elapsed_query);
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.endQuery(ext.TIME_ELAPSED_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Time elapsed query should have no errors");
+}
+
+function runTimeStampTest() {
+ debug("");
+ debug("Testing timestamp query");
+
+ timestamp_query1 = gl.createQuery();
+ timestamp_query2 = gl.createQuery();
+ ext.queryCounterEXT(timestamp_query1, ext.TIMESTAMP_EXT);
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ ext.queryCounterEXT(timestamp_query2, ext.TIMESTAMP_EXT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Timestamp queries should have no errors");
+}
+
+function verifyQueryResultsNotAvailable() {
+ debug("");
+ debug("Verifying queries' results don't become available too early");
+
+ // Verify as best as possible that the implementation doesn't
+ // allow a query's result to become available the same frame, by
+ // spin-looping for some time and ensuring that none of the
+ // queries' results become available.
+ var startTime = Date.now();
+ while (Date.now() - startTime < 2000) {
+ gl.finish();
+ if (gl.getQueryParameter(elapsed_query, gl.QUERY_RESULT_AVAILABLE)) {
+ testFailed("One of the queries' results became available too early");
+ return;
+ }
+ if (timestamp_counter_bits > 0) {
+ if (gl.getQueryParameter(timestamp_query1, gl.QUERY_RESULT_AVAILABLE) ||
+ gl.getQueryParameter(timestamp_query2, gl.QUERY_RESULT_AVAILABLE)) {
+ testFailed("One of the queries' results became available too early");
+ return;
+ }
+ }
+ }
+
+ testPassed("Queries' results didn't become available in a spin loop");
+}
+
+function checkQueryResults() {
+ if (availability_retry > 0) {
+ // Make a reasonable attempt to wait for the queries' results to become available.
+ if (!gl.getQueryParameter(elapsed_query, gl.QUERY_RESULT_AVAILABLE) ||
+ (timestamp_counter_bits > 0 && !gl.getQueryParameter(timestamp_query2, gl.QUERY_RESULT_AVAILABLE))) {
+ var error = gl.getError();
+ if (error != gl.NO_ERROR) {
+ testFailed("getQueryParameter should have no errors: " + wtu.glEnumToString(gl, error));
+ debug("");
+ finishTest();
+ return;
+ }
+ availability_retry--;
+ window.requestAnimationFrame(checkQueryResults);
+ return;
+ }
+ }
+
+ debug("");
+ debug("Testing query results");
+
+ // Make sure queries are available.
+ shouldBe("gl.getQueryParameter(elapsed_query, gl.QUERY_RESULT_AVAILABLE)", "true");
+ if (timestamp_counter_bits > 0) {
+ shouldBe("gl.getQueryParameter(timestamp_query1, gl.QUERY_RESULT_AVAILABLE)", "true");
+ shouldBe("gl.getQueryParameter(timestamp_query2, gl.QUERY_RESULT_AVAILABLE)", "true");
+ }
+
+ var disjoint_value = gl.getParameter(ext.GPU_DISJOINT_EXT);
+ if (disjoint_value) {
+ // Cannot validate results make sense, but this is okay.
+ testPassed("Disjoint triggered.");
+ } else {
+ var elapsed_result = gl.getQueryParameter(elapsed_query, gl.QUERY_RESULT);
+ if (timestamp_counter_bits > 0) {
+ var timestamp_result1 = gl.getQueryParameter(timestamp_query1, gl.QUERY_RESULT);
+ var timestamp_result2 = gl.getQueryParameter(timestamp_query2, gl.QUERY_RESULT);
+ }
+ // Do some basic validity checking of the elapsed time query. There's no way it should
+ // take more than about half a second for a no-op query.
+ var halfSecondInNanos = 0.5 * 1000 * 1000 * 1000;
+ if (elapsed_result < 0 || elapsed_result > halfSecondInNanos) {
+ testFailed("Time elapsed query returned invalid data: " + elapsed_result);
+ } else {
+ testPassed("Time elapsed query results were valid.");
+ }
+
+ if (timestamp_counter_bits > 0) {
+ if (timestamp_result1 <= 0 ||
+ timestamp_result2 <= 0 ||
+ timestamp_result2 <= timestamp_result1) {
+ testFailed("Timestamp queries returned invalid data: timestamp_result1 = " +
+ timestamp_result1 + ", timestamp_result2 = " + timestamp_result2);
+ } else {
+ testPassed("Timestamp query results were valid.");
+ }
+ }
+ }
+
+ debug("");
+ finishTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-filter-anisotropic.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-filter-anisotropic.html
new file mode 100644
index 0000000000..c75a8e0cae
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-filter-anisotropic.html
@@ -0,0 +1,26 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL 2.0 EXT_texture_filter_anisotropic 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>
+var contextVersion = 2;
+</script>
+<script src="../../js/tests/ext-texture-filter-anisotropic.js"></script>
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-norm16.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-norm16.html
new file mode 100644
index 0000000000..add9fc038d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ext-texture-norm16.html
@@ -0,0 +1,253 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL EXT_texture_norm16 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_texture_norm16 extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext(null, null, 2);
+var ext;
+
+var formats = null;
+var textures;
+var fbos;
+var renderbuffer;
+var readbackBuf = new Uint16Array(4);
+
+function generateFormatColor(format, value, alpha) {
+ alpha = alpha !== undefined ? alpha : 255;
+ switch(format) {
+ case gl.RED:
+ return [value, 0, 0, alpha];
+ case gl.RG:
+ return [value, value, 0, alpha];
+ case gl.RGB:
+ return [value, value, value, alpha];
+ case gl.RGBA:
+ return [value, value, value, value];
+ default:
+ wtu.error("Unreachable: Unknown format.");
+ return null;
+ }
+}
+
+function testNorm16Texture(internalFormat, format, type, error="NO_ERROR") {
+ debug(`\ntestNorm16Texture(${[].slice.call(arguments).join(", ")})`);
+ let pixelValue;
+ let expectedValue;
+ let imageData;
+
+ switch(gl[type]) {
+ case gl.SHORT:
+ pixelValue = 0x7fff;
+ expectedValue = 0xff;
+ imageData = new Int16Array(4).fill(pixelValue);
+ break;
+ case gl.UNSIGNED_SHORT:
+ pixelValue = 0x6a35;
+ expectedValue = pixelValue >> 8;
+ imageData = new Uint16Array(4).fill(pixelValue);
+ break;
+ default:
+ wtu.error("Unreachable: Unknown texture type.");
+ break;
+ }
+
+ // Texture sampled from
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, textures[0]);
+ gl.texImage2D(gl.TEXTURE_2D, 0, ext[internalFormat] || gl[internalFormat],
+ 1, 1, 0, gl[format], gl[type], imageData);
+
+ wtu.glErrorShouldBe(gl, gl[error], `texImage should generate error:${error}`);
+ if (gl[error]) return;
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ // Read back as gl.UNSIGNED_BYTE
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, generateFormatColor(gl[format], expectedValue));
+}
+
+function testNorm16Render(interalFormat, format, type, tolerance) {
+ // Only UNSIGNED_SHORT are renderable
+ let pixelValue = 0x6a35;
+ let expectedValue = pixelValue;
+ let imageData = new Uint16Array(4).fill(pixelValue);
+
+ // Render to fbo texture attachment test
+ gl.bindTexture(gl.TEXTURE_2D, textures[1]);
+ gl.texImage2D(gl.TEXTURE_2D, 0, interalFormat, 1, 1, 0, format, type, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "rtt bindings succeed");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbos[0]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textures[1], 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "fbo bindings succeed");
+
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, textures[0]);
+ gl.texImage2D(gl.TEXTURE_2D, 0, interalFormat, 1, 1, 0, format, type, imageData);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture bindings succeed");
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, generateFormatColor(format, expectedValue, 0xffff), undefined, tolerance, readbackBuf, type);
+
+ // Renderbuffer test
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbos[1]);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
+ gl.renderbufferStorage(gl.RENDERBUFFER, interalFormat, 1, 1);
+ gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER,
+ renderbuffer);
+ gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "renderbuffer bindings succeed");
+
+ gl.clearColor(1, 1, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, generateFormatColor(format, 0xffff, 0xffff), undefined, tolerance, readbackBuf, type);
+
+ // Copy from renderbuffer to textures[1] test
+ gl.bindTexture(gl.TEXTURE_2D, textures[1]);
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "copy succeed");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbos[0]);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, generateFormatColor(format, 0xffff, 0xffff), undefined, tolerance, readbackBuf, type);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.bindTexture(gl.TEXTURE_2D, null);
+}
+
+function testExtFormatUnrenderable(internalFormatName, format, type) {
+ gl.bindTexture(gl.TEXTURE_2D, textures[1]);
+ gl.texImage2D(gl.TEXTURE_2D, 0, ext[internalFormatName], 1, 1, 0, format, type, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture definition succeeded");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fbos[0]);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, textures[1], 0);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "fbo binding succeeded");
+
+ wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, [ gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, gl.FRAMEBUFFER_UNSUPPORTED ],
+ `framebuffer should not be complete with ${internalFormatName} texture attached`);
+}
+
+function runInternalFormatQueryTest()
+{
+ debug("");
+ debug("testing the internal format query");
+
+ var maxSamples = gl.getParameter(gl.MAX_SAMPLES);
+ const formats = [ext.R16_EXT, ext.RG16_EXT, ext.RGBA16_EXT];
+ for (const format of formats) {
+ var samples = gl.getInternalformatParameter(gl.RENDERBUFFER, format, gl.SAMPLES);
+ if (samples == null || samples.length == 0 || samples[0] < maxSamples) {
+ testFailed("the maximum value in SAMPLES should be at least " + maxSamples);
+ return;
+ }
+ }
+ testPassed("Internal format query succeeded");
+}
+
+function runTestExtension() {
+ textures = [gl.createTexture(), gl.createTexture()];
+ fbos = [gl.createFramebuffer(), gl.createFramebuffer()];
+ renderbuffer = gl.createRenderbuffer();
+
+ for (let i = 0; i < 2; i++) {
+ gl.bindTexture(gl.TEXTURE_2D, textures[i]);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ }
+
+ gl.bindTexture(gl.TEXTURE_2D, null);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texture and framebuffer setup succeed");
+
+ let program300 = wtu.setupSimpleTextureProgramESSL300(gl);
+ let program100 = wtu.setupTexturedQuad(gl, 0, 1, wtu.simpleHighPrecisionTextureFragmentShader);
+
+ debug("");
+ debug("Texture creation");
+ testNorm16Texture("R16_EXT", "RED", "UNSIGNED_SHORT");
+ testNorm16Texture("RG16_EXT", "RG", "UNSIGNED_SHORT");
+ testNorm16Texture("RGB16_EXT", "RGB", "UNSIGNED_SHORT");
+ testNorm16Texture("RGBA16_EXT", "RGBA", "UNSIGNED_SHORT");
+ testNorm16Texture("R16_SNORM_EXT", "RED", "SHORT");
+ testNorm16Texture("RG16_SNORM_EXT", "RG", "SHORT");
+ testNorm16Texture("RGB16_SNORM_EXT", "RGB", "SHORT");
+ testNorm16Texture("RGBA16_SNORM_EXT", "RGBA", "SHORT");
+
+ testNorm16Texture("RGBA", "RGBA", "UNSIGNED_SHORT", "INVALID_OPERATION");
+ testNorm16Texture("RGBA", "RGBA", "SHORT", "INVALID_OPERATION");
+
+ debug("");
+ debug("Texture renderability");
+
+ testNorm16Render(ext.R16_EXT, gl.RED, gl.UNSIGNED_SHORT, 8);
+ testNorm16Render(ext.RG16_EXT, gl.RG, gl.UNSIGNED_SHORT, 8);
+ testNorm16Render(ext.RGBA16_EXT, gl.RGBA, gl.UNSIGNED_SHORT, 8);
+
+ gl.useProgram(program300);
+
+ testNorm16Render(ext.R16_EXT, gl.RED, gl.UNSIGNED_SHORT, 0);
+ testNorm16Render(ext.RG16_EXT, gl.RG, gl.UNSIGNED_SHORT, 0);
+ testExtFormatUnrenderable("RGB16_EXT", gl.RGB, gl.UNSIGNED_SHORT);
+ testNorm16Render(ext.RGBA16_EXT, gl.RGBA, gl.UNSIGNED_SHORT, 0);
+
+ testExtFormatUnrenderable("R16_SNORM_EXT", gl.RED, gl.SHORT);
+ testExtFormatUnrenderable("RG16_SNORM_EXT", gl.RG, gl.SHORT);
+ testExtFormatUnrenderable("RGB16_SNORM_EXT", gl.RGB, gl.SHORT);
+ testExtFormatUnrenderable("RGBA16_SNORM_EXT", gl.RGBA, gl.SHORT);
+};
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ } else {
+ testPassed("context exists");
+
+ ext = gl.getExtension("EXT_texture_norm16");
+
+ wtu.runExtensionSupportedTest(gl, "EXT_texture_norm16", ext !== null);
+
+ if (ext !== null) {
+ runInternalFormatQueryTest();
+ runTestExtension();
+ } else {
+ testPassed("No EXT_texture_norm16 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/oes-draw-buffers-indexed.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-draw-buffers-indexed.html
new file mode 100644
index 0000000000..700bb053c1
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/oes-draw-buffers-indexed.html
@@ -0,0 +1,573 @@
+<!--
+Copyright (c) 2020 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OES_draw_buffers_indexed 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="20" height="20" style="border: 1px solid blue;" id="c"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("This test verifies the functionality of the OES_draw_buffers_indexed extension, if it is available.");
+
+debug("");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("c", null, 2);
+var ext;
+
+const vs = `#version 300 es
+layout(location=0) in vec4 vPosition;
+void main()
+{
+ gl_Position = vPosition;
+}
+`;
+
+const fs = `#version 300 es
+precision lowp float;
+layout(location = 0) out vec4 o_color0;
+layout(location = 1) out vec4 o_color1;
+void main()
+{
+ o_color0 = vec4(1, 0, 0, 0);
+ o_color1 = vec4(1, 0, 0, 0);
+}
+`;
+
+function setup() {
+ const program = wtu.setupProgram(gl, [vs, fs]);
+ gl.useProgram(program);
+ wtu.setupUnitQuad(gl, 0);
+ wtu.glErrorShouldBe(gl, 0, 'No errors from program');
+
+ const tex1 = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex1);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ wtu.glErrorShouldBe(gl, 0, 'Create texture 1 successfully');
+
+ const tex2 = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex2);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ wtu.glErrorShouldBe(gl, 0, 'Create texture 2 successfully');
+
+ const attachments = [gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1];
+
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[0], gl.TEXTURE_2D, tex1, 0);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, attachments[1], gl.TEXTURE_2D, tex2, 0);
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.drawBuffers(attachments);
+ wtu.glErrorShouldBe(gl, 0, 'Set draw buffers without errors');
+}
+
+function enableDisableTest() {
+ debug("Testing enableiOES/disableiOES");
+
+ // Invalid input
+ ext.enableiOES(gl.DEPTH_TEST, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'target could only be gl.BLEND');
+
+ ext.disableiOES(gl.DEPTH_TEST, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'target could only be gl.BLEND');
+
+ gl.disable(gl.BLEND);
+
+ // Valid input
+ ext.enableiOES(gl.BLEND, 0);
+ shouldBe('gl.isEnabled(gl.BLEND)', 'true');
+ ext.disableiOES(gl.BLEND, 0);
+ ext.enableiOES(gl.BLEND, 1);
+ shouldBe('gl.isEnabled(gl.BLEND)', 'false');
+ gl.enable(gl.BLEND);
+ shouldBe('gl.isEnabled(gl.BLEND)', 'true');
+ wtu.glErrorShouldBe(gl, 0, 'No errors from enable and disable draw buffers blend state');
+}
+
+function constantAlphaBlendColorValidationTest() {
+ debug("Testing CONSTANT_COLOR/ALPHA blend functions limit validation");
+ function isConstantColorAndAlphaBlendFunctions(first, second)
+ {
+ return (first == gl.CONSTANT_COLOR || first == gl.ONE_MINUS_CONSTANT_COLOR) &&
+ (second == gl.CONSTANT_ALPHA || second == gl.ONE_MINUS_CONSTANT_ALPHA);
+ }
+
+ function checkBlendFunctions(src, dst)
+ {
+ if (isConstantColorAndAlphaBlendFunctions(src, dst) ||
+ isConstantColorAndAlphaBlendFunctions(dst, src))
+ {
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, 'invalid combinations');
+ return false;
+ }
+ else
+ {
+ wtu.glErrorShouldBe(gl, 0, 'No error');
+ return true;
+ }
+ }
+
+ const srcFunc = [
+ gl.ZERO,
+ gl.ONE,
+ gl.SRC_COLOR,
+ gl.ONE_MINUS_SRC_COLOR,
+ gl.DST_COLOR,
+ gl.ONE_MINUS_DST_COLOR,
+ gl.SRC_ALPHA,
+ gl.ONE_MINUS_SRC_ALPHA,
+ gl.DST_ALPHA,
+ gl.ONE_MINUS_DST_ALPHA,
+ gl.CONSTANT_COLOR,
+ gl.ONE_MINUS_CONSTANT_COLOR,
+ gl.CONSTANT_ALPHA,
+ gl.ONE_MINUS_CONSTANT_ALPHA,
+ gl.SRC_ALPHA_SATURATE,
+ ];
+
+ const dstFunc = [
+ gl.ZERO, gl.ONE,
+ gl.SRC_COLOR, gl.ONE_MINUS_SRC_COLOR,
+ gl.DST_COLOR, gl.ONE_MINUS_DST_COLOR,
+ gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA,
+ gl.DST_ALPHA, gl.ONE_MINUS_DST_ALPHA,
+ gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_COLOR,
+ gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA,
+ ];
+
+ let src, dst;
+
+ // CONSTANT_COLOR/ALPHA invalid combination check
+ for (let i = 0, leni = srcFunc.length; i < leni; i++)
+ {
+ src = srcFunc[i];
+ for (let j = 0, lenj = dstFunc.length; j < lenj; j++)
+ {
+ dst = dstFunc[j];
+ ext.blendFunciOES(0, src, dst);
+ checkBlendFunctions(src, dst);
+ ext.blendFuncSeparateiOES(0, src, dst, gl.ONE, gl.ONE);
+ checkBlendFunctions(src, dst);
+ }
+ }
+}
+
+function indexedBlendColorTest() {
+ debug('');
+ debug("Testing blendEquationiOES and blendFunciOES");
+ wtu.glErrorShouldBe(gl, 0, 'top of indexedBlendColorTest');
+
+ gl.clearColor(0, 0, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ ext.blendEquationiOES(0, gl.FUNC_ADD);
+ ext.blendFunciOES(0, gl.ONE, gl.ONE);
+ ext.blendEquationiOES(1, gl.FUNC_ADD);
+ ext.blendFunciOES(1, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, 0, 'Draw quad without errors');
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [255, 0, 255, 255]);
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 255, 255]);
+
+ debug("Testing blendEquationSeparateiOES and blendFuncSeparateiOES");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ ext.blendEquationSeparateiOES(0, gl.FUNC_ADD, gl.FUNC_SUBTRACT);
+ ext.blendFuncSeparateiOES(0, gl.ONE, gl.ONE, gl.ZERO, gl.ZERO);
+ ext.blendEquationSeparateiOES(1, gl.FUNC_ADD, gl.FUNC_SUBTRACT);
+ ext.blendFuncSeparateiOES(1, gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ZERO);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, 0, 'Draw quad without errors');
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [255, 0, 255, 0]);
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 255, 0]);
+
+ debug("Testing colorMaskiOES");
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ ext.colorMaskiOES(0, false, false, false, false);
+ ext.colorMaskiOES(1, true, true, true, true);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ wtu.glErrorShouldBe(gl, 0, 'Draw quad without errors');
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT0);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 255, 255]);
+
+ gl.readBuffer(gl.COLOR_ATTACHMENT1);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 255, 0]);
+
+ debug('');
+ debug(`Testing that new tokens aren't on the extension.`);
+ shouldBe('ext.BLEND_EQUATION_RGB', 'undefined');
+ shouldBe('ext.BLEND_EQUATION_ALPHA', 'undefined');
+ shouldBe('ext.BLEND_SRC_RGB', 'undefined');
+ shouldBe('ext.BLEND_SRC_ALPHA', 'undefined');
+ shouldBe('ext.BLEND_DST_RGB', 'undefined');
+ shouldBe('ext.BLEND_DST_ALPHA', 'undefined');
+ shouldBe('ext.COLOR_WRITEMASK', 'undefined');
+
+ debug("Testing new tokens for getIndexedParameterTest");
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 0)', 'gl.FUNC_ADD');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_ALPHA, 0)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_RGB, 0)', 'gl.ONE');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_RGB, 0)', 'gl.ONE');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 0)', 'gl.ZERO');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 0)', 'gl.ZERO');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 1)', 'gl.FUNC_ADD');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_ALPHA, 1)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_RGB, 1)', 'gl.SRC_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_RGB, 1)', 'gl.ONE_MINUS_SRC_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 1)', 'gl.ZERO');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 1)', 'gl.ZERO');
+
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 0)', '[false, false, false, false]');
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 1)', '[true, true, true, true]');
+
+ debug("Testing non-indexed getParamter get state from draw buffer 0");
+ shouldBe('gl.getParameter(gl.BLEND_SRC_RGB)', 'gl.ONE');
+ shouldBe('gl.getParameter(gl.BLEND_DST_RGB)', 'gl.ONE');
+ shouldBe('gl.getParameter(gl.BLEND_SRC_ALPHA)', 'gl.ZERO');
+ shouldBe('gl.getParameter(gl.BLEND_DST_ALPHA)', 'gl.ZERO');
+ shouldBe('gl.getParameter(gl.BLEND_EQUATION_RGB)', 'gl.FUNC_ADD');
+ shouldBe('gl.getParameter(gl.BLEND_EQUATION_ALPHA)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getParameter(gl.COLOR_WRITEMASK)', '[false, false, false, false]');
+
+ debug("Testing non-indexed calls modify all draw buffers state");
+ gl.blendEquationSeparate(gl.FUNC_SUBTRACT, gl.FUNC_ADD);
+ gl.blendFuncSeparate(gl.ONE_MINUS_DST_ALPHA, gl.DST_ALPHA, gl.ONE, gl.ONE);
+ gl.colorMask(true, false, true, false);
+ wtu.glErrorShouldBe(gl, 0, 'Non-indexed state set without errors');
+
+ shouldBe('gl.getParameter(gl.BLEND_EQUATION_RGB)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getParameter(gl.BLEND_EQUATION_ALPHA)', 'gl.FUNC_ADD');
+ shouldBe('gl.getParameter(gl.BLEND_SRC_RGB)', 'gl.ONE_MINUS_DST_ALPHA');
+ shouldBe('gl.getParameter(gl.BLEND_DST_RGB)', 'gl.DST_ALPHA');
+ shouldBe('gl.getParameter(gl.BLEND_SRC_ALPHA)', 'gl.ONE');
+ shouldBe('gl.getParameter(gl.BLEND_DST_ALPHA)', 'gl.ONE');
+ shouldBe('gl.getParameter(gl.COLOR_WRITEMASK)', '[true, false, true, false]');
+
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 0)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_ALPHA, 0)', 'gl.FUNC_ADD');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_RGB, 0)', 'gl.ONE_MINUS_DST_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_RGB, 0)', 'gl.DST_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 0)', 'gl.ONE');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 0)', 'gl.ONE');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_RGB, 1)', 'gl.FUNC_SUBTRACT');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_EQUATION_ALPHA, 1)', 'gl.FUNC_ADD');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_RGB, 1)', 'gl.ONE_MINUS_DST_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_RGB, 1)', 'gl.DST_ALPHA');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_SRC_ALPHA, 1)', 'gl.ONE');
+ shouldBe('gl.getIndexedParameter(gl.BLEND_DST_ALPHA, 1)', 'gl.ONE');
+
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 0)', '[true, false, true, false]');
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 1)', '[true, false, true, false]');
+}
+
+function runTestExtension() {
+ setup();
+
+ testInvalidValues();
+
+ enableDisableTest();
+
+ // blending should be enabled for drawBuffers 0 and 1 at this point
+
+ constantAlphaBlendColorValidationTest();
+
+ indexedBlendColorTest();
+
+ testColorMaskDrawNoOp();
+
+ testColorMaskAfterComposite();
+}
+
+function runInvalidEnumsTest() {
+ debug("Testing new enums for getIndexedParameterTest being invalid before requesting the extension");
+ shouldBeNull("gl.getIndexedParameter(0x8009, 0)"); // BLEND_EQUATION_RGB
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_EQUATION_RGB');
+ shouldBeNull("gl.getIndexedParameter(0x883D, 0)"); // BLEND_EQUATION_ALPHA
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_EQUATION_ALPHA');
+ shouldBeNull("gl.getIndexedParameter(0x80C9, 0)"); // BLEND_SRC_RGB
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_SRC_RGB');
+ shouldBeNull("gl.getIndexedParameter(0x80CB, 0)"); // BLEND_SRC_ALPHA
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_SRC_ALPHA');
+ shouldBeNull("gl.getIndexedParameter(0x80C8, 0)"); // BLEND_DST_RGB
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_DST_RGB');
+ shouldBeNull("gl.getIndexedParameter(0x80CA, 0)"); // BLEND_DST_ALPHA
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, 'BLEND_DST_ALPHA');
+ shouldBeNull("gl.getIndexedParameter(0x0C23, 0)"); // COLOR_WRITEMASK
+ wtu.glErrorShouldBe(gl, [gl.INVALID_OPERATION, gl.INVALID_ENUM], 'invalid operations or invalid enums for COLOR_WRITEMASK');
+}
+
+function testInvalidValues() {
+ const numDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS);
+ if (!numDrawBuffers) throw new Error('!numDrawBuffers');
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.enableiOES(gl.BLEND, -1)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.enableiOES(gl.BLEND, ${numDrawBuffers})`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.enableiOES(gl.BLEND, ${numDrawBuffers-1})`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.disableiOES(gl.BLEND, -1)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.disableiOES(gl.BLEND, ${numDrawBuffers})`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.disableiOES(gl.BLEND, ${numDrawBuffers-1})`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendEquationiOES(-1, gl.FUNC_ADD)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendEquationiOES(${numDrawBuffers}, gl.FUNC_ADD)`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.blendEquationiOES(${numDrawBuffers-1}, gl.FUNC_ADD)`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendEquationSeparateiOES(-1, gl.FUNC_ADD, gl.FUNC_ADD)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendEquationSeparateiOES(${numDrawBuffers}, gl.FUNC_ADD, gl.FUNC_ADD)`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.blendEquationSeparateiOES(${numDrawBuffers-1}, gl.FUNC_ADD, gl.FUNC_ADD)`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendFunciOES(-1, gl.ONE, gl.ONE)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendFunciOES(${numDrawBuffers}, gl.ONE, gl.ONE)`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.blendFunciOES(${numDrawBuffers-1}, gl.ONE, gl.ONE)`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendFuncSeparateiOES(-1, gl.ONE, gl.ZERO, gl.ONE, gl.ZERO)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.blendFuncSeparateiOES(${numDrawBuffers}, gl.ONE, gl.ZERO, gl.ONE, gl.ZERO)`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.blendFuncSeparateiOES(${numDrawBuffers-1}, gl.ONE, gl.ZERO, gl.ONE, gl.ZERO)`);
+
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.colorMaskiOES(-1, 1,1,1,1)`);
+ wtu.shouldGenerateGLError(gl, gl.INVALID_VALUE, `ext.colorMaskiOES(${numDrawBuffers}, 1,1,1,1)`);
+ wtu.shouldGenerateGLError(gl, 0, `ext.colorMaskiOES(${numDrawBuffers-1}, 1,1,1,1)`);
+}
+
+function* range(n) {
+ for (let i = 0; i < n; i++) {
+ yield i;
+ }
+}
+
+function testColorMaskDrawNoOp() {
+ debug('');
+ debug('testColorMaskDrawNoOp')
+ // > If any draw buffer with an attachment does not have a defined
+ // fragment shader output, draws generate INVALID_OPERATION,
+ // unless all 4 channels of colorMask are set to false.
+ const NUM_OUTPUTS = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
+ shouldBeTrue(`${NUM_OUTPUTS} > 1`);
+
+ const fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.viewport(0,0,1,1);
+
+ const DRAW_BUFFERS = [];
+ for (const i of range(NUM_OUTPUTS)) {
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1);
+ const ca = gl.COLOR_ATTACHMENT0+i;
+ DRAW_BUFFERS.push(ca)
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, ca,
+ gl.TEXTURE_2D, tex, 0);
+ }
+ shouldBe('gl.checkFramebufferStatus(gl.FRAMEBUFFER)', 'gl.FRAMEBUFFER_COMPLETE');
+
+ gl.drawBuffers(DRAW_BUFFERS);
+ gl.colorMask(1, 1, 1, 1);
+ gl.disable(gl.BLEND);
+
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ for (const i of range(NUM_OUTPUTS)) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT0+i);
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, [0, 0, 0, 0], `COLOR_ATTACHMENT${i} initially black`);
+ }
+
+ for (const validOutput of range(NUM_OUTPUTS)) {
+ const invalidOutput = validOutput ^ 0b11;
+ debug(`validOutput: ${validOutput}, invalidOutput: ${invalidOutput}`);
+ const prog = wtu.setupProgram(gl, [
+ `\
+#version 300 es
+void main() {
+ gl_Position = vec4(0,0,0,1);
+ gl_PointSize = 1.0f;
+}
+`,
+ `\
+#version 300 es
+precision mediump float;
+layout(location=${validOutput}) out vec4 o_color;
+void main() {
+ o_color = vec4(1,1,1,1);
+}
+`
+ ]);
+ gl.useProgram(prog);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'After init.');
+
+ gl.colorMask(1,1,1,1);
+ gl.drawBuffers(DRAW_BUFFERS);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION,
+ 'Drawing with unmasked undefined color outputs.');
+
+ gl.colorMask(0,0,0,0);
+ gl.drawBuffers(DRAW_BUFFERS);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'Drawing with colorMask-masked-out undefined color outputs.');
+
+ gl.colorMask(1,1,1,1);
+ gl.drawBuffers(DRAW_BUFFERS.map((x,i) => (i == invalidOutput) ? x : 0));
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION,
+ 'Drawing with wrong-id drawBuffer-masked-out undefined color outputs.');
+
+ gl.drawBuffers(DRAW_BUFFERS.map((x,i) => (i == validOutput) ? x : 0));
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'Drawing with drawBuffer-masked-out undefined color outputs.');
+
+ gl.colorMask(0,0,0,0);
+ gl.drawBuffers([]);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'Drawing with colorMask+drawBuffer-masked-out undefined color outputs.');
+
+ const testMask = (r,g,b,a) => {
+ debug(`testMask(${[r,g,b,a]})`);
+ gl.drawBuffers(DRAW_BUFFERS);
+
+ gl.colorMask(1,1,1,1);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.colorMask(0,0,0,0);
+ ext.colorMaskiOES(validOutput, r,g,b,a);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ `Drawing with sole defined color${validOutput} output writemask: ${[r,g,b,a]}.`);
+
+ for (const i of range(NUM_OUTPUTS)) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT0+i);
+ let expect = [0,0,0,0];
+ if (i == validOutput) {
+ expect = [r,g,b,a].map(x => 0xff*x);
+ }
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, expect, `COLOR_ATTACHMENT${i}: [${expect}]`);
+ }
+
+ gl.colorMask(1,1,1,1);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.colorMask(r,g,b,a);
+ for (const i of range(NUM_OUTPUTS)) {
+ if (i == validOutput) continue;
+ ext.colorMaskiOES(i, 0,0,0,0);
+ }
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ `Drawing with sole remaining defined color${validOutput} output writemask: ${[r,g,b,a]}.`);
+
+ for (const i of range(NUM_OUTPUTS)) {
+ gl.readBuffer(gl.COLOR_ATTACHMENT0+i);
+ let expect = [0,0,0,0];
+ if (i == validOutput) {
+ expect = [r,g,b,a].map(x => 0xff*x);
+ }
+ wtu.checkCanvasRect(gl, 0, 0, 1, 1, expect, `COLOR_ATTACHMENT${i}: [${expect}]`);
+ }
+
+ if (r || g || b || a) {
+ gl.colorMask(0,0,0,0);
+ ext.colorMaskiOES(invalidOutput, r,g,b,a);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION,
+ `Drawing with wrong-id undefined color output color masked: ${[r,g,b,a]}.`);
+
+ gl.drawBuffers([]);
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'Drawing with wrong-id colorMask, but all-off drawBuffers.');
+
+ gl.drawBuffers(DRAW_BUFFERS.map((x,i) => (i == validOutput) ? x : 0));
+ gl.drawArrays(gl.POINTS, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR,
+ 'Drawing with wrong-id colorMask, but right-id drawBuffers masked.');
+ }
+ };
+
+ testMask(0,0,0,0);
+ testMask(1,0,0,0);
+ testMask(0,1,0,0);
+ testMask(0,0,1,0);
+ testMask(0,0,0,1);
+ testMask(1,1,1,1);
+ }
+}
+
+function testColorMaskAfterComposite() {
+ debug('');
+ debug('testColorMaskAfterComposite')
+
+ const NUM_OUTPUTS = gl.getParameter(gl.MAX_COLOR_ATTACHMENTS);
+ shouldBeTrue(`${NUM_OUTPUTS} > 2`);
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.colorMask(0, 0, 1, 0);
+ ext.colorMaskiOES(0, 1, 0, 0, 0);
+ ext.colorMaskiOES(1, 0, 1, 0, 0);
+
+ function check() {
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 0)', '[true, false, false, false]');
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 1)', '[false, true, false, false]');
+ shouldBe('gl.getIndexedParameter(gl.COLOR_WRITEMASK, 2)', '[false, false, true, false]');
+ finishTest();
+ }
+
+ wtu.waitForComposite(check);
+}
+
+function runTest() {
+ if (!gl) {
+ testFailed("context does not exist");
+ } else {
+ testPassed("context exists");
+
+ runInvalidEnumsTest();
+
+ ext = gl.getExtension("OES_draw_buffers_indexed");
+
+ wtu.runExtensionSupportedTest(gl, "OES_draw_buffers_indexed", ext !== null);
+
+ if (ext !== null) {
+ runTestExtension();
+ } else {
+ testPassed("No OES_draw_buffers_indexed support -- this is legal");
+ }
+ }
+}
+
+runTest();
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2.html
new file mode 100644
index 0000000000..ba74d1398b
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2.html
@@ -0,0 +1,524 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+<script id="requireDefine_GL_OVR_multiview2" type="x-shader/x-fragment">#version 300 es
+#ifndef GL_OVR_multiview2
+ #error no GL_OVR_multiview2
+#endif
+precision highp float;
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+<script id="forbidDefine_GL_OVR_multiview" type="x-shader/x-fragment">#version 300 es
+#ifdef GL_OVR_multiview
+ #error legacy GL_OVR_multiview support must be forbidden
+#endif
+precision highp float;
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+<script id="legacyMultiview1Shader" type="x-shader/x-fragment">#version 300 es
+#extension GL_OVR_multiview: require
+precision highp float;
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runExtensionDisabledTest()
+{
+ debug("");
+ debug("Testing queries with extension disabled");
+
+ let maxViews = gl.getParameter(0x9631);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Can't query MAX_VIEWS_OVR without enabling OVR_multiview2");
+
+ let baseViewIndex = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.BACK, 0x9630);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Can't query FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR without enabling OVR_multiview2");
+ let numViews = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.BACK, 0x9632);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "Can't query FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR without enabling OVR_multiview2");
+}
+
+function runQueryTest()
+{
+ debug("");
+ debug("Testing querying MAX_VIEWS_OVR");
+
+ let maxViews = gl.getParameter(ext.MAX_VIEWS_OVR);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from querying MAX_VIEWS_OVR");
+ if (typeof maxViews != 'number') {
+ testFailed("Type of the value of MAX_VIEWS_OVR should be number, was " + (typeof maxViews));
+ }
+ if (maxViews < 2) {
+ testFailed("Value of MAX_VIEWS_OVR should be at least two, was: " + maxViews);
+ }
+}
+
+function runDefaultFramebufferQueryTest()
+{
+ debug("");
+ debug("Testing querying base view index and num views on the default framebuffer");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ // Same behavior as FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER:
+ gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.BACK, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR is INVALID_ENUM for default framebuffer");
+ gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.BACK, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR is INVALID_ENUM for default framebuffer");
+}
+
+function runInvalidTextureTypeTest()
+{
+ debug("");
+ debug("Testing invalid texture types");
+ let tex2D = createTextureWithNearestFiltering(gl.TEXTURE_2D);
+ gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 128, 128);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex2D, 0, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be possible to create a multiview framebuffer against a 2D texture");
+
+ let texCube = createTextureWithNearestFiltering(gl.TEXTURE_CUBE_MAP);
+ gl.texStorage2D(gl.TEXTURE_CUBE_MAP, 1, gl.RGBA8, 128, 128);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, texCube, 0, 0, 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be possible to create a multiview framebuffer against a cube map texture");
+
+ let tex3D = createTextureWithNearestFiltering(gl.TEXTURE_3D);
+ gl.texStorage3D(gl.TEXTURE_3D, 1, gl.RGBA8, 128, 128, 2);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, tex3D, 0, 0, 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "should not be possible to create a multiview framebuffer against a 3D texture");
+}
+
+/**
+ * If allocateStorage is true, the test will allocate texture storage. If it is false, attachments are done without allocations.
+ */
+function runFramebufferQueryTest(allocateStorage)
+{
+ debug("");
+ debug("Testing querying attachment object type, baseViewIndex, numViews and framebuffer status. Texture storage is " + (allocateStorage ? "allocated" : "not allocated") + ".");
+
+ let checkQueryResult = function(actual, expected, name) {
+ if (actual != expected) {
+ testFailed('Unexpected ' + name + ': ' + actual + ' when it was set to ' + expected);
+ } else {
+ testPassed(name + ' was ' + actual + ' when queried from the framebuffer');
+ }
+ }
+
+ let setupAndQuery = function(colorTex, levelSet, baseViewIndexSet, numViewsSet) {
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, levelSet, baseViewIndexSet, numViewsSet);
+ let objectType = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
+ if (objectType != gl.TEXTURE) {
+ testFailed('Unexpected FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ' + wtu.glEnumToString(gl, objectType) + ', should be TEXTURE');
+ } else {
+ testPassed('FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE was TEXTURE');
+ }
+
+ let level = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL);
+ checkQueryResult(level, levelSet, "level");
+
+ let textureName = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME);
+ checkQueryResult(textureName, colorTex, "texture object");
+
+ let baseViewIndex = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR);
+ checkQueryResult(baseViewIndex, baseViewIndexSet, "baseViewIndex");
+
+ let numViews = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR);
+ checkQueryResult(numViews, numViewsSet, "numViews");
+
+ let layer = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER);
+ checkQueryResult(layer, baseViewIndexSet, "texture layer (should match baseViewIndex)");
+ }
+
+ let setupSecondAttachmentAndQueryStatus = function(colorTex2, baseViewIndex, numViews, expectedStatus, msg) {
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, colorTex2, 0, baseViewIndex, numViews);
+ let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (status != expectedStatus) {
+ testFailed('Framebuffer status: ' + wtu.glEnumToString(gl, status) + ' did not match with the expected value: ' + wtu.glEnumToString(gl, expectedStatus) + ' - ' + msg);
+ } else {
+ testPassed('Framebuffer status: ' + wtu.glEnumToString(gl, status) + ' matched with the expected value - ' + msg);
+ }
+ }
+
+ let maxViews = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let baseViewIndex = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Querying baseViewIndex from a nonexistent attachment");
+ if (baseViewIndex != null) {
+ testFailed('Unexpected baseViewIndex ' + baseViewIndex + ' on a framebuffer without attachments');
+ } else {
+ testPassed('Querying baseViewIndex returned null on a framebuffer without attachments');
+ }
+ let numViews = gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, ext.FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "Querying numViews from a nonexistent attachment");
+ if (numViews != null) {
+ testFailed('Unexpected numViews ' + numViews + ' on a framebuffer without attachments');
+ } else {
+ testPassed('Querying numViews returned null on a framebuffer without attachments');
+ }
+
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ if (allocateStorage) {
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 2, gl.RGBA8, 128, 128, maxViews);
+ }
+ setupAndQuery(colorTex, 0, 0, maxViews);
+ setupAndQuery(colorTex, 1, 0, 2);
+ setupAndQuery(colorTex, 0, 1, maxViews - 1);
+
+ // Test matching and mismatching attachments for framebuffer status.
+ let colorTex2 = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ if (allocateStorage) {
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, 128, 128, maxViews);
+ }
+ setupSecondAttachmentAndQueryStatus(colorTex2, 1, maxViews - 1, allocateStorage ? gl.FRAMEBUFFER_COMPLETE : gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT, 'matching baseViewIndex and numViews on different attachments');
+ if (allocateStorage) {
+ setupSecondAttachmentAndQueryStatus(colorTex2, 0, maxViews - 1, ext.FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR, 'baseViewIndex mismatch');
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, maxViews);
+ setupSecondAttachmentAndQueryStatus(colorTex2, 0, maxViews - 1, ext.FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR, 'numViews mismatch');
+ }
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from framebuffer queries");
+}
+
+function runInvalidViewsTest()
+{
+ debug("");
+ debug("Testing invalid out-of-range values for baseViewIndex and numViews");
+
+ let maxViews = gl.getParameter(ext.MAX_VIEWS_OVR);
+ let maxLayers = gl.getParameter(gl.MAX_ARRAY_TEXTURE_LAYERS);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ // Don't allocate storage since it's not needed for the validation.
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, maxViews + 1);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Specified too many views");
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Specified zero views");
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, -1, 2);
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Specified negative baseViewIndex");
+
+ let colorTex2 = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex2, 0, maxLayers - maxViews + 1, maxViews);
+ // baseViewIndex + numViews = (maxLayers - maxViews + 1) + maxViews = maxLayers + 1
+ wtu.glErrorShouldBe(gl, gl.INVALID_VALUE, "Specified so many views that baseViewIndex + numViews is greater than MAX_ARRAY_TEXTURE_LAYERS");
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex2, 0, maxLayers - maxViews, maxViews);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "baseViewIndex + numViews is exactly MAX_ARRAY_TEXTURE_LAYERS");
+}
+
+function runDetachTest()
+{
+ debug("");
+ debug("Testing detaching multiview attachments");
+
+ let maxViews = gl.getParameter(ext.MAX_VIEWS_OVR);
+ let maxLayers = gl.getParameter(gl.MAX_ARRAY_TEXTURE_LAYERS);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, maxViews);
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, null, 0, maxLayers + 1, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "baseViewIndex and numViews are not validated when detaching");
+ let objectType = gl.getFramebufferAttachmentParameter(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
+ if (objectType != gl.NONE) {
+ testFailed('Unexpected FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ' + wtu.glEnumToString(gl, objectType) + ' after detach, should be NONE');
+ } else {
+ testPassed('FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE was NONE after detach');
+ }
+
+ ext.framebufferTextureMultiviewOVR(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, maxViews);
+ gl.framebufferTexture2D(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Can detach with framebufferTexture2D as well.");
+ objectType = gl.getFramebufferAttachmentParameter(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE);
+ if (objectType != gl.NONE) {
+ testFailed('Unexpected FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE ' + wtu.glEnumToString(gl, objectType) + ' after detach, should be NONE');
+ } else {
+ testPassed('FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE was NONE after detach');
+ }
+}
+
+function runShaderCompileTest(extensionEnabled)
+{
+ debug("");
+ debug("Testing shader compiles with extension " + (extensionEnabled ? "enabled" : "disabled"));
+
+ let prog = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, "requireDefine_GL_OVR_multiview2"], undefined, undefined, true);
+ expectTrue(!extensionEnabled == !prog,
+ "GL_OVR_multiview2 must be defined by the preprocessor iff OVR_multiview2 is enabled by getExtension.");
+ if (extensionEnabled) {
+ prog = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, "forbidDefine_GL_OVR_multiview"], undefined, undefined, true);
+ expectTrue(prog, "GL_OVR_multiview must never be defined by the preprocessor.");
+
+ prog = wtu.setupProgram(gl, [wtu.simpleVertexShaderESSL300, "legacyMultiview1Shader"], undefined, undefined, true);
+ expectTrue(!prog, "#extension GL_OVR_multiview must be forbidden.");
+ }
+
+ if (!extensionEnabled) {
+ let multiviewShaders = [
+ getMultiviewPassthroughVertexShader(2),
+ getMultiviewColorFragmentShader()
+ ];
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (testProgram) {
+ testFailed("Compilation of shaders using extension functionality succeeded when the extension is disabled, should fail.");
+ } else {
+ testPassed("Compilation of shaders using extension functionality should fail when the extension is disabled.");
+ }
+ }
+}
+
+function runClearTest()
+{
+ debug("");
+ debug("Testing that calling clear() clears all views");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+
+ gl.clearColor(0, 1, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = [0, 255, 255, 255];
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be cyan');
+ }
+}
+
+function runFragmentShaderRenderTest()
+{
+ debug("");
+ debug("Testing rendering with different colors in fragment shader");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewPassthroughVertexShader(views),
+ getMultiviewColorFragmentShader()
+ ];
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+}
+
+function runVertexShaderRenderTest()
+{
+ debug("");
+ debug("Testing rendering with different colors in fragment shader, different offsets in vertex shader");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewOffsetVertexShader(views),
+ getMultiviewColorFragmentShader()
+ ];
+
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+
+ checkVerticalStrip(width, height, views, viewIndex, expectedColor, 'view ' + viewIndex);
+ }
+}
+
+function runRealisticUseCaseRenderTest()
+{
+ debug("");
+ debug("Testing rendering with a different transformation matrix chosen from a uniform array according to ViewID");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewRealisticUseCaseVertexShader(views),
+ getMultiviewColorFragmentShader()
+ ];
+
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+
+ let transformLocation = gl.getUniformLocation(testProgram, 'transform');
+ let transformData = new Float32Array (views * 16);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ let scaleX = 1.0 / views;
+ // offsetX is the position of the left edge of the quad we want to get in normalized device coordinates
+ let offsetX = viewIndex / views * 2.0 - 1.0;
+
+ setupTranslateAndScaleXMatrix(transformData, viewIndex * 16, scaleX, offsetX);
+ }
+ gl.uniformMatrix4fv(transformLocation, false, transformData);
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+
+ checkVerticalStrip(width, height, views, viewIndex, expectedColor, 'view ' + viewIndex);
+ }
+}
+
+function runUniqueObjectTest()
+{
+ debug("");
+ debug("Testing that getExtension() returns the same object each time");
+ gl.getExtension("OVR_multiview2").myProperty = 2;
+ webglHarnessCollectGarbage();
+ shouldBe('gl.getExtension("OVR_multiview2").myProperty', '2');
+}
+
+description("This test verifies the functionality of the OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ runExtensionDisabledTest();
+
+ runShaderCompileTest(false);
+
+ debug("");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ runShaderCompileTest(true);
+
+ runQueryTest();
+
+ runDefaultFramebufferQueryTest();
+
+ runInvalidTextureTypeTest();
+
+ runFramebufferQueryTest(true);
+ runFramebufferQueryTest(false);
+
+ runInvalidViewsTest();
+
+ runDetachTest();
+
+ runClearTest();
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runFragmentShaderRenderTest();
+ runVertexShaderRenderTest();
+ runRealisticUseCaseRenderTest();
+ runUniqueObjectTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_depth.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_depth.html
new file mode 100644
index 0000000000..9ea071f25c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_depth.html
@@ -0,0 +1,138 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+<script id="macroFragmentShader" type="x-shader/x-fragment">#version 300 es
+precision highp float;
+out vec4 my_FragColor;
+void main() {
+#ifdef GL_OVR_multiview2
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+#else
+ // Error expected
+ #error no GL_OVR_multiview2;
+#endif
+}
+</script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runDepthRenderTest()
+{
+ debug("");
+ debug("Testing rendering with a depth texture array and depth test on");
+
+ let width = 64;
+ let height = 64;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewPassthroughVertexShader(views),
+ getMultiviewColorFragmentShader()
+ ];
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ let depthTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.DEPTH32F_STENCIL8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, depthTex, 0, 0, views);
+
+ let expectedStatus = gl.FRAMEBUFFER_COMPLETE;
+ let status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (status != expectedStatus) {
+ testFailed('Framebuffer status: ' + wtu.glEnumToString(gl, status) + ' did not match with the expected value: ' + wtu.glEnumToString(gl, expectedStatus));
+ } else {
+ testPassed('Framebuffer status: ' + wtu.glEnumToString(gl, status) + ' matched with the expected value');
+ }
+
+ // Draw so that the depth test succeeds for all pixels.
+ gl.viewport(0, 0, width, height);
+ gl.enable(gl.DEPTH_TEST);
+ gl.clearDepth(1.0);
+ gl.clear(gl.DEPTH_BUFFER_BIT);
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw when depth test succeeds");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+
+ // Draw so that the depth test fails for all pixels.
+ gl.clearDepth(0.0);
+ gl.clearColor(0.0, 0.0, 0.0, 0.0);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw when depth test fails");
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = [0, 0, 0, 0];
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+}
+
+description("This test verifies drawing to depth buffers with the OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ debug("");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runDepthRenderTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_draw_buffers.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_draw_buffers.html
new file mode 100644
index 0000000000..5da29d80b4
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_draw_buffers.html
@@ -0,0 +1,158 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runDrawBuffersClearTest()
+{
+ debug("");
+ debug("Testing that calling clear() clears all views in all draw buffers");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = [null, null, null];
+ let drawBuffers = [0, 0, 0];
+ for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) {
+ colorTex[texIndex] = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + texIndex, colorTex[texIndex], 0, 0, views);
+ drawBuffers[texIndex] = gl.COLOR_ATTACHMENT0 + texIndex;
+ }
+
+ gl.viewport(0, 0, width, height);
+ gl.drawBuffers(drawBuffers);
+
+ gl.clearColor(0, 1, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) {
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex[texIndex], 0, viewIndex);
+ let expectedColor = [0, 255, 255, 255];
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' of color attachment ' + texIndex + ' should be cyan');
+ }
+ debug("");
+ }
+}
+
+function runDrawBuffersRenderTest()
+{
+ debug("");
+ debug("Testing rendering into multiple draw buffers with a different transformation matrix chosen from a uniform array according to ViewID");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = [null, null, null];
+ let drawBuffers = [0, 0, 0];
+ for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) {
+ colorTex[texIndex] = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + texIndex, colorTex[texIndex], 0, 0, views);
+ drawBuffers[texIndex] = gl.COLOR_ATTACHMENT0 + texIndex;
+ }
+
+ let multiviewShaders = [
+ getMultiviewRealisticUseCaseVertexShader(views),
+ getMultiviewColorFragmentShaderForDrawBuffers(colorTex.length)
+ ];
+
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ gl.viewport(0, 0, width, height);
+ gl.drawBuffers(drawBuffers);
+
+ let transformLocation = gl.getUniformLocation(testProgram, 'transform');
+ let transformData = new Float32Array (views * 16);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ let scaleX = 1.0 / views;
+ // offsetX is the position of the left edge of the quad we want to get in normalized device coordinates
+ let offsetX = viewIndex / views * 2.0 - 1.0;
+
+ setupTranslateAndScaleXMatrix(transformData, viewIndex * 16, scaleX, offsetX);
+ }
+ gl.uniformMatrix4fv(transformLocation, false, transformData);
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) {
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex[texIndex], 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex + texIndex);
+
+ checkVerticalStrip(width, height, views, viewIndex, expectedColor, 'view ' + viewIndex + ' of color attachment ' + texIndex);
+ }
+ debug("");
+ }
+}
+
+description("This test verifies the functionality of the OVR_multiview2 extension when used with multiple draw buffers, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ runDrawBuffersClearTest();
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runDrawBuffersRenderTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_flat_varying.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_flat_varying.html
new file mode 100644
index 0000000000..9a99de7e84
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_flat_varying.html
@@ -0,0 +1,93 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runFlatVaryingTest()
+{
+ debug("");
+ debug("Testing rendering with different colors in fragment shader");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewFlatVaryingVertexShader(views),
+ getMultiviewFlatVaryingFragmentShader()
+ ];
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+}
+
+description("This test verifies that flat varyings work in multiview shaders using OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runFlatVaryingTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_instanced_draw.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_instanced_draw.html
new file mode 100644
index 0000000000..b55d0cfc03
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_instanced_draw.html
@@ -0,0 +1,105 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runInstancedDrawTest()
+{
+ debug("");
+ debug("Testing instanced rendering");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewInstancedVertexShader(views),
+ getInstanceColorFragmentShader()
+ ];
+
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 2);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let colorRegionLeftEdge = (width / (views * 2)) * viewIndex;
+ let colorRegionRightEdge = (width / (views * 2)) * (viewIndex + 2);
+ let stripWidth = (colorRegionRightEdge - colorRegionLeftEdge) / 2;
+ if (colorRegionLeftEdge > 0) {
+ wtu.checkCanvasRect(gl, 0, 0, Math.floor(colorRegionLeftEdge) - 1, height, [0, 0, 0, 0], 'the left edge of view ' + viewIndex + ' should be untouched');
+ }
+ if (colorRegionRightEdge < width) {
+ wtu.checkCanvasRect(gl, colorRegionRightEdge + 1, 0, width - colorRegionRightEdge - 1, height, [0, 0, 0, 0], 'the right edge of view ' + viewIndex + ' should be untouched');
+ }
+ let expectedColor = getExpectedColor(0);
+ wtu.checkCanvasRect(gl, colorRegionLeftEdge + 1, 0, stripWidth - 2, height, expectedColor, 'a thin strip in view ' + viewIndex + ' drawn by instance 0 should be colored ' + expectedColor);
+ expectedColor = getExpectedColor(1);
+ wtu.checkCanvasRect(gl, colorRegionLeftEdge + stripWidth + 1, 0, stripWidth - 2, height, expectedColor, 'a thin strip in view ' + viewIndex + ' drawn by instance 1 should be colored ' + expectedColor);
+ }
+}
+
+description("This test verifies instanced draws together with the OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runInstancedDrawTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_non_multiview_shaders.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_non_multiview_shaders.html
new file mode 100644
index 0000000000..44e2ea02ab
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_non_multiview_shaders.html
@@ -0,0 +1,93 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runNonMultiviewShaderTest()
+{
+ debug("");
+ debug("Testing rendering with shaders that don't declare num_views");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let testProgram = wtu.setupSimpleColorProgram(gl);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let colorUniformLocation = gl.getUniformLocation(testProgram, 'u_color');
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+
+ gl.uniform4f(colorUniformLocation, 0.0, 1.0, 0.0, 1.0);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw when using a non-multiview shader as long as the number of views is 1");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 0, 255], 'view 0 should be green');
+
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "draw should generate an INVALID_OPERATION error when using a non-multiview shader and the number of views is > 1");
+}
+
+description("This test verifies that non-multiview shaders work correctly with OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runNonMultiviewShaderTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_single_view_operations.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_single_view_operations.html
new file mode 100644
index 0000000000..0e01a3e3b2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_single_view_operations.html
@@ -0,0 +1,253 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+
+function runSingleViewReadTest()
+{
+ debug("");
+ debug("Testing reading from a multiview framebuffer with num_views = 1");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+ gl.clearColor(0.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let buf = new Uint8Array(width * height * 4);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from readPixels");
+
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 255, 255], 'view 0 should be cyan');
+ gl.getError();
+
+ // Also test for the error case with two views
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to read from a framebuffer with two views");
+}
+
+function runSingleViewBlitTest()
+{
+ debug("");
+ debug("Testing blitting from a multiview framebuffer with num_views = 1");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+ gl.clearColor(0.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ gl.canvas.width = width;
+ gl.canvas.height = height;
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.clearColor(0.0, 0.0, 0.0, 0.0)
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from blitFramebuffer");
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 255, 255], 'view 0 blitted to the default framebuffer should be cyan');
+
+ // Also test for the error case with multiview blit target
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to blit to a multiview framebuffer even if it has just one view");
+
+ // Also test for the error case with two views
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.blitFramebuffer(0, 0, width, height, 0, 0, width, height, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to blit from a framebuffer with two views");
+}
+
+function runSingleViewCopyTexImage2DTest()
+{
+ debug("");
+ debug("Testing copyTexImage2D from a multiview framebuffer with num_views = 1");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+ gl.clearColor(0.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let copyTargetTex = createTextureWithNearestFiltering(gl.TEXTURE_2D);
+ gl.bindTexture(gl.TEXTURE_2D, copyTargetTex);
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 0, 0, width, height, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from copyTexImage2D");
+
+ let copyFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, copyFb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, copyTargetTex, 0);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 255, 255], 'copy of view 0 in the 2D texture should be cyan');
+
+ // Also test for the error case with two views
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ gl.bindTexture(gl.TEXTURE_2D, copyTargetTex);
+ gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 0, 0, width, height, 0);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to copy from a framebuffer with two views");
+}
+
+function runSingleViewCopyTexSubImage2DTest()
+{
+ debug("");
+ debug("Testing copyTexSubImage2D from a multiview framebuffer with num_views = 1");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+ gl.clearColor(0.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let copyTargetTex = createTextureWithNearestFiltering(gl.TEXTURE_2D);
+ gl.bindTexture(gl.TEXTURE_2D, copyTargetTex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from copyTexImage2D");
+
+ let copyFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, copyFb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, copyTargetTex, 0);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 255, 255], 'copy of view 0 in the 2D texture should be cyan');
+
+ // Also test for the error case with two views
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ gl.bindTexture(gl.TEXTURE_2D, copyTargetTex);
+ gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, width, height);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to copy from a framebuffer with two views");
+}
+
+function runSingleViewCopyTexSubImage3DTest()
+{
+ debug("");
+ debug("Testing copyTexSubImage3D from a multiview framebuffer with num_views = 1");
+
+ let width = 256;
+ let height = 256;
+ let depth = 2; // always supported so no need to query MAX_VIEWS_OVR.
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, depth);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 1);
+
+ gl.viewport(0, 0, width, height);
+ gl.clearColor(0.0, 1.0, 1.0, 1.0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear");
+
+ let copyTargetTex = createTextureWithNearestFiltering(gl.TEXTURE_3D);
+ gl.bindTexture(gl.TEXTURE_3D, copyTargetTex);
+ gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA8, width, height, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
+ gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, 0, 0, width, height);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from copyTexSubImage3D");
+
+ let copyFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, copyFb);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, copyTargetTex, 0, 0);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, [0, 255, 255, 255], 'copy of view 0 in the 3D texture should be cyan');
+
+ // Also test for the error case with two views
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, 2);
+ gl.bindTexture(gl.TEXTURE_3D, copyTargetTex);
+ gl.copyTexSubImage3D(gl.TEXTURE_3D, 0, 0, 0, 0, 0, 0, width, height);
+ wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "should be an error to copy from a framebuffer with two views");
+}
+
+description("This test verifies that framebuffers with only one view can be read from with OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ runSingleViewReadTest();
+
+ runSingleViewBlitTest();
+
+ runSingleViewCopyTexImage2DTest();
+
+ runSingleViewCopyTexSubImage2DTest();
+
+ runSingleViewCopyTexSubImage3DTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_timer_query.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_timer_query.html
new file mode 100644
index 0000000000..9f76fadf98
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_timer_query.html
@@ -0,0 +1,142 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+let queryExt = null;
+
+function runClearTest()
+{
+ debug("");
+ debug("Testing clear");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+
+ gl.clearColor(1, 0, 0, 1);
+
+ let query = gl.createQuery();
+ gl.beginQuery(queryExt.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from setup");
+
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "multiview clear should generate invalid operation when a timer query is active");
+
+ gl.endQuery(queryExt.TIME_ELAPSED_EXT);
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = [0, 0, 0, 0];
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be untouched');
+ }
+}
+
+function runFragmentShaderRenderTest()
+{
+ debug("");
+ debug("Testing rendering with different colors in fragment shader");
+
+ let width = 256;
+ let height = 256;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewPassthroughVertexShader(views),
+ getMultiviewColorFragmentShader()
+ ];
+ let testProgram = wtu.setupProgram(gl, multiviewShaders, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ gl.viewport(0, 0, width, height);
+
+ let query = gl.createQuery();
+ gl.beginQuery(queryExt.TIME_ELAPSED_EXT, query);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from setup");
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "multiview draw should generate invalid operation when a timer query is active");
+
+ gl.endQuery(queryExt.TIME_ELAPSED_EXT);
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = [0, 0, 0, 0];
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be untouched');
+ }
+}
+
+description("This test verifies the functionality of the OVR_multiview2 extension and its interaction with EXT_disjoint_timer_query_webgl2, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2") || !gl.getExtension("EXT_disjoint_timer_query_webgl2")) {
+ testPassed("No OVR_multiview2 support or no EXT_disjoint_timer_query_webgl2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 and EXT_disjoint_timer_query_webgl2 extensions");
+ ext = gl.getExtension('OVR_multiview2');
+ queryExt = gl.getExtension('EXT_disjoint_timer_query_webgl2');
+
+ runClearTest();
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runFragmentShaderRenderTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_transform_feedback.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_transform_feedback.html
new file mode 100644
index 0000000000..297dd43f1f
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/ovr_multiview2_transform_feedback.html
@@ -0,0 +1,125 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL OVR_multiview2 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/ovr_multiview2_util.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+let wtu = WebGLTestUtils;
+let gl = wtu.create3DContext(null, null, 2);
+let ext = null;
+let queryExt = null;
+
+function runTransformFeedbackTest()
+{
+ debug("");
+ debug("Testing transform feedback combined with multiview rendering");
+
+ let width = 64;
+ let height = 64;
+
+ let views = gl.getParameter(ext.MAX_VIEWS_OVR);
+
+ let multiviewShaders = [
+ getMultiviewVaryingVertexShader(views),
+ getMultiviewVaryingFragmentShader()
+ ];
+ let testProgram = wtu.setupTransformFeedbackProgram(gl, multiviewShaders, ['testVarying'], gl.SEPARATE_ATTRIBS, ['a_position'], [0], true);
+ if (!testProgram) {
+ testFailed("Compilation with extension enabled failed.");
+ return;
+ }
+
+ let fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ let colorTex = createTextureWithNearestFiltering(gl.TEXTURE_2D_ARRAY);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, views);
+ ext.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, 0, views);
+
+ let xfb = gl.createTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, xfb);
+
+ let xfbBuffer = gl.createBuffer();
+ gl.bindBufferBase(gl.TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
+ gl.bufferData(gl.TRANSFORM_FEEDBACK_BUFFER, 128, gl.DYNAMIC_DRAW);
+
+ gl.viewport(0, 0, width, height);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from setup");
+
+ gl.beginTransformFeedback(gl.TRIANGLES);
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "rendering to a multiview framebuffer with more than one view when there's an active transform feedback should result in invalid operation");
+
+ gl.pauseTransformFeedback();
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw when rendering to a multiview framebuffer with more than one view when there's an active paused transform feedback");
+
+ let readFb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+
+ wtu.drawUnitQuad(gl);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from draw when transform feedback is unbound");
+
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb);
+ for (let viewIndex = 0; viewIndex < views; ++viewIndex) {
+ gl.framebufferTextureLayer(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, colorTex, 0, viewIndex);
+ let expectedColor = getExpectedColor(viewIndex);
+ wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'view ' + viewIndex + ' should be colored ' + expectedColor);
+ }
+
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, xfb);
+ gl.endTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from ending transform feedback");
+}
+
+description("This test verifies interaction between transform feedback and the OVR_multiview2 extension, if it is available.");
+
+debug("");
+
+if (!gl) {
+ testFailed("WebGL context does not exist");
+} else {
+ testPassed("WebGL context exists");
+
+ if (!gl.getExtension("OVR_multiview2")) {
+ testPassed("No OVR_multiview2 support -- this is legal");
+ } else {
+ testPassed("Successfully enabled OVR_multiview2 extension");
+ ext = gl.getExtension('OVR_multiview2');
+
+ wtu.setupUnitQuad(gl, 0, 1);
+
+ runTransformFeedbackTest();
+ }
+}
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions-in-shaders.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions-in-shaders.html
new file mode 100644
index 0000000000..b2b4d1257f
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions-in-shaders.html
@@ -0,0 +1,115 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Extensions promoted to core should not be possible to use in shaders</title>
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<link rel="stylesheet" href="../../resources/glsl-feature-tests.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 id="fragShaderRequire" type="x-shader/x-fragment">
+#extension $(ext) : require
+precision mediump float;
+void main() {
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+<script id="fragShaderIfdef" type="x-shader/x-fragment">
+precision mediump float;
+void main() {
+#ifdef $(ext)
+ gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+#else
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+#endif
+}
+</script>
+<script id="fragShader300Require" type="x-shader/x-fragment">#version 300 es
+#extension $(ext) : require
+precision mediump float;
+out vec4 my_FragColor;
+void main() {
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+<script id="fragShader300Ifdef" type="x-shader/x-fragment">#version 300 es
+precision mediump float;
+out vec4 my_FragColor;
+void main() {
+#ifdef $(ext)
+ my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
+#else
+ my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+#endif
+}
+</script>
+<script type="application/javascript">
+"use strict";
+description();
+
+var wtu = WebGLTestUtils;
+
+var shaderTemplateRequire = wtu.getScript('fragShaderRequire');
+var shaderTemplate300Require = wtu.getScript('fragShader300Require');
+var shaderTemplateIfdef = wtu.getScript('fragShaderIfdef');
+var shaderTemplate300Ifdef = wtu.getScript('fragShader300Ifdef');
+
+var extensions = [
+ 'GL_EXT_draw_buffers',
+ 'GL_EXT_frag_depth',
+ 'GL_EXT_shader_texture_lod',
+ 'GL_OES_standard_derivatives'
+];
+
+var tests = [];
+
+for (var i = 0; i < extensions.length; ++i) {
+ var shaderSrcRequire = wtu.replaceParams(shaderTemplateRequire, {'ext': extensions[i]});
+ tests.push({
+ fShaderSource: shaderSrcRequire,
+ fShaderSuccess: false,
+ linkSuccess: false,
+ passMsg: "ESSL 1.00 Fragment shader that requires " + extensions[i] + " should not compile."
+ });
+ var shaderSrc300Require = wtu.replaceParams(shaderTemplate300Require, {'ext': extensions[i]});
+ tests.push({
+ fShaderSource: shaderSrc300Require,
+ fShaderSuccess: false,
+ linkSuccess: false,
+ passMsg: "ESSL 3.00 Fragment shader that requires " + extensions[i] + " should not compile."
+ });
+
+ var shaderSrcIfdef = wtu.replaceParams(shaderTemplateIfdef, {'ext': extensions[i]});
+ tests.push({
+ fShaderSource: shaderSrcIfdef,
+ fShaderSuccess: true,
+ linkSuccess: true,
+ render: true,
+ passMsg: extensions[i] + " should not be defined in ESSL 1.00 fragment shader."
+ });
+ var shaderSrc300Ifdef = wtu.replaceParams(shaderTemplate300Ifdef, {'ext': extensions[i]});
+ tests.push({
+ fShaderSource: shaderSrc300Ifdef,
+ fShaderSuccess: true,
+ linkSuccess: true,
+ render: true,
+ passMsg: extensions[i] + " should not be defined in ESSL 3.00 fragment shader."
+ });
+}
+
+GLSLConformanceTester.runTests(tests, 2);
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions.html
new file mode 100644
index 0000000000..7e22942c4d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/promoted-extensions.html
@@ -0,0 +1,64 @@
+<!--
+Copyright (c) 2019 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<link rel="stylesheet" href="../../resources/js-test-style.css"/>
+<script src="../../js/js-test-pre.js"></script>
+<script src="../../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var gl;
+
+function checkExtensionNotAvailable(extension, extensions) {
+ if (extensions.indexOf(extension) >= 0) {
+ testFailed(extension + " was exposed in the WebGL 2.0 context but should not have been");
+ } else {
+ testPassed(extension + " was not exposed in the WebGL 2.0 context");
+ }
+}
+
+description("Promoted extensions from WebGL 1.0 should not be exposed in WebGL 2.0");
+
+shouldBeNonNull("gl = wtu.create3DContext(undefined, undefined, 2)");
+
+var exts = gl.getSupportedExtensions();
+
+var promotedExtensions = [
+ "ANGLE_instanced_arrays",
+ "EXT_blend_minmax",
+ "EXT_frag_depth",
+ "EXT_shader_texture_lod",
+ "EXT_sRGB",
+ "OES_element_index_uint",
+ "OES_standard_derivatives",
+ "OES_texture_float",
+ "OES_texture_half_float",
+ "OES_texture_half_float_linear",
+ "OES_vertex_array_object",
+ "WEBGL_depth_texture",
+ "WEBGL_draw_buffers",
+]
+
+for (var i = 0; i < promotedExtensions.length; ++i) {
+ checkExtensionNotAvailable(promotedExtensions[i], exts);
+}
+
+debug("")
+var successfullyParsed = true;
+</script>
+
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/required-extensions.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/required-extensions.html
new file mode 100644
index 0000000000..c3de2a5677
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/required-extensions.html
@@ -0,0 +1,58 @@
+<!--
+Copyright (c) 2021 The Khronos Group Inc.
+Use of this source code is governed by an MIT-style license that can be
+found in the LICENSE.txt file.
+-->
+
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<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";
+const wtu = WebGLTestUtils;
+let gl;
+
+description("Ensure that required extensions are supported");
+
+shouldBeNonNull("gl = wtu.create3DContext(undefined, undefined, 2)");
+
+const supportedExts = gl.getSupportedExtensions();
+
+function hasExt(name) {
+ return supportedExts.indexOf(name) >= 0;
+}
+
+const has_s3tc = hasExt('WEBGL_compressed_texture_s3tc');
+const has_s3tc_srgb = hasExt('WEBGL_compressed_texture_s3tc_srgb');
+const has_rgtc = hasExt('EXT_texture_compression_rgtc');
+const has_etc = hasExt('WEBGL_compressed_texture_etc');
+
+debug(`has_s3tc: ${has_s3tc}`);
+debug(`has_s3tc_srgb: ${has_s3tc_srgb}`);
+debug(`has_rgtc: ${has_rgtc}`);
+debug(`has_etc: ${has_etc}`);
+
+shouldBeTrue("((has_s3tc && has_s3tc_srgb && has_rgtc) || has_etc)");
+
+// ETC1 extension must not be exposed on WebGL 2.0 contexts without ETC2.
+debug("");
+const has_etc1 = hasExt('WEBGL_compressed_texture_etc1');
+debug(`has_etc1: ${has_etc1}`);
+shouldBeTrue("has_etc1 == has_etc");
+
+debug("");
+var successfullyParsed = true;
+</script>
+
+<script src="../../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html
new file mode 100644
index 0000000000..37f3e23762
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/extensions/webgl-multi-draw-instanced-base-vertex-base-instance.html
@@ -0,0 +1,1021 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>WebGL ANGLE_base_vertex_base_instance 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>
+<script id="vshaderBaseInstanceWithoutExt" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, gl_BaseInstance, 1);
+}
+</script>
+<!-- Check gl_InstanceID starts at 0 regardless of gl_BaseInstance -->
+<script id="vshaderInstanceIDCheck" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ if (gl_InstanceID == 0) {
+ color = vec4(0, 1, 0, 1);
+ } else {
+ color = vec4(1, 0, 0, 1);
+ }
+ gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
+}
+</script>
+<script id="vshaderBaseVertexWithoutExt" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, gl_BaseVertex, 1);
+}
+</script>
+<script id="vshaderWithExt" type="x-shader/x-vertex">#version 300 es
+#extension GL_ANGLE_base_vertex_base_instance : require
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ color = vec4(0, 1, 0, 1);
+ gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
+}
+</script>
+<!-- Check gl_VertexID starts at gl_BaseVertex -->
+<script id="vshaderVertexIDCheck" type="x-shader/x-vertex">#version 300 es
+layout(location = 0) in vec2 vPosition;
+out vec4 color;
+void main()
+{
+ if (gl_VertexID >= 3) {
+ color = vec4(0, 1, 0, 1);
+ } else {
+ color = vec4(1, 0, 0, 1);
+ }
+ gl_Position = vec4(vPosition * 2.0 - 1.0, 0, 1);
+}
+</script>
+<script id="vshaderSimple" type="x-shader/x-vertex">#version 300 es
+ layout(location = 0) in vec2 vPosition;
+ layout(location = 1) in float vInstance;
+ out vec4 color;
+ void main()
+ {
+ if (vInstance <= 0.0) {
+ color = vec4(1.0, 0.0, 0.0, 1.0);
+ } else if (vInstance <= 1.0) {
+ color = vec4(0.0, 1.0, 0.0, 1.0);
+ } else if (vInstance <= 2.0) {
+ color = vec4(0.0, 0.0, 1.0, 1.0);
+ } else {
+ color = vec4(0.0, 0.0, 0.0, 1.0);
+ }
+
+ gl_Position = vec4(vec3(vPosition, 1.0) * 2.0 - 1.0, 1);
+ }
+</script>
+<script id="fshader" type="x-shader/x-fragment">#version 300 es
+ precision mediump float;
+ in vec4 color;
+ out vec4 oColor;
+ void main() {
+ oColor = color;
+ }
+</script>
+<div id="description"></div>
+<canvas id="canvas" width="128" height="128"> </canvas>
+<div id="console"></div>
+
+<script>
+"use strict";
+description("This test verifies the functionality of the WEBGL_[multi]_draw_basevertex_base_instance extension, if it is available.");
+
+const wtu = WebGLTestUtils;
+const canvas = document.getElementById("canvas");
+canvas.style.backgroundColor = '#000';
+canvas.style.imageRendering = 'pixelated'; // Because Chrome doesn't support crisp-edges.
+canvas.style.imageRendering = 'crisp-edges';
+const attribs = {
+ antialias: false,
+};
+const gl = wtu.create3DContext(canvas, attribs, 2);
+
+const width = gl.canvas.width;
+const height = gl.canvas.height;
+const x_count = 8;
+const y_count = 8;
+const quad_count = x_count * y_count;
+const tri_count = quad_count * 2;
+const tileSize = [ 1/x_count, 1/y_count ];
+const tilePixelSize = [ Math.floor(width / x_count), Math.floor(height / y_count) ];
+const quadRadius = [ 0.25 * tileSize[0], 0.25 * tileSize[1] ];
+const pixelCheckSize = [ Math.floor(quadRadius[0] * width), Math.floor(quadRadius[1] * height) ];
+const bufferUsageSet = [ gl.STATIC_DRAW, gl.DYNAMIC_DRAW ];
+
+function getTileCenter(x, y) {
+ return [ tileSize[0] * (0.5 + x), tileSize[1] * (0.5 + y) ];
+}
+
+function getQuadVertices(x, y) {
+ const center = getTileCenter(x, y);
+ return [
+ [center[0] - quadRadius[0], center[1] - quadRadius[1], 0],
+ [center[0] + quadRadius[0], center[1] - quadRadius[1], 0],
+ [center[0] + quadRadius[0], center[1] + quadRadius[1], 0],
+ [center[0] - quadRadius[0], center[1] + quadRadius[1], 0],
+ ];
+}
+
+const indicesData = [];
+let verticesData = [];
+let nonIndexedVerticesData = [];
+const instanceIDsData = Array.from(Array(x_count).keys());
+const is = new Uint16Array([0, 1, 2, 0, 2, 3]);
+// Rects in the same column are within a vertex array, testing gl_VertexID, gl_BaseVertex
+// Rects in the same row are drawn by instancing, testing gl_InstanceID, gl_BaseInstance
+for (let y = 0; y < y_count; ++y) {
+ // v3 ---- v2
+ // | |
+ // | |
+ // v0 ---- v1
+
+ // Get only one column of quad vertices as our geometry
+ // Rely on BaseInstance to duplicate on x axis
+ const vs = getQuadVertices(0, y);
+
+ for (let i = 0; i < vs.length; ++i) {
+ verticesData = verticesData.concat(vs[i]);
+ }
+
+ for (let i = 0; i < is.length; ++i) {
+ nonIndexedVerticesData = nonIndexedVerticesData.concat(vs[is[i]]);
+ }
+}
+
+// Build the indicesData used by drawElements*
+for (let i = 0; i < y_count; ++i) {
+ let oi = 6 * i;
+ let ov = 4 * i;
+ for (let j = 0; j < is.length; ++j) {
+ indicesData[oi + j] = is[j] + ov;
+ }
+}
+
+const indices = new Uint16Array(indicesData);
+const vertices = new Float32Array(verticesData);
+const nonIndexedVertices = new Float32Array(nonIndexedVerticesData);
+const instanceIDs = new Float32Array(instanceIDsData);
+
+const indexBuffer = gl.createBuffer();
+const vertexBuffer = gl.createBuffer();
+const nonIndexedVertexBuffer = gl.createBuffer();
+const instanceIDBuffer = gl.createBuffer();
+
+const drawArraysDrawCount = x_count / 2;
+let drawArraysParams = {
+ drawCount: drawArraysDrawCount,
+ firsts: new Uint32Array(drawArraysDrawCount).fill(0),
+ counts: new Uint32Array(drawArraysDrawCount).fill(y_count * 6),
+ instances: new Uint32Array(drawArraysDrawCount).fill(2),
+ baseInstances: new Uint32Array(drawArraysDrawCount)
+};
+
+for (let i = 0; i < x_count / 2; ++i) {
+ drawArraysParams.baseInstances[i] = i * 2;
+}
+
+const drawElementsDrawCount = x_count * y_count / 2;
+let drawElementsParams = {
+ drawCount: drawElementsDrawCount,
+ offsets: new Uint32Array(drawElementsDrawCount).fill(0),
+ counts: new Uint32Array(drawElementsDrawCount).fill(6),
+ instances: new Uint32Array(drawElementsDrawCount).fill(2),
+ baseVertices: new Uint32Array(drawElementsDrawCount),
+ baseInstances: new Uint32Array(drawElementsDrawCount)
+};
+
+let b = 0;
+for (let v = 0; v < y_count; ++v) {
+ for (let i = 0; i < x_count; i+=2) {
+ drawElementsParams.baseVertices[b] = v * 4;
+ drawElementsParams.baseInstances[b] = i;
+ ++b;
+ }
+}
+
+function setupGeneralBuffers(bufferUsage) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, vertices, bufferUsage);
+
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, bufferUsage);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, nonIndexedVertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, nonIndexedVertices, bufferUsage);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceIDBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, instanceIDs, bufferUsage);
+}
+
+// Check if the extension is either both enabled and supported or
+// not enabled and not supported.
+function runSupportedTest(extensionName, extensionEnabled) {
+ const supported = gl.getSupportedExtensions();
+ if (supported.indexOf(extensionName) >= 0) {
+ if (extensionEnabled) {
+ testPassed(extensionName + ' listed as supported and getExtension succeeded');
+ return true;
+ } else {
+ testFailed(extensionName + ' listed as supported but getExtension failed');
+ }
+ } else {
+ if (extensionEnabled) {
+ testFailed(extensionName + ' not listed as supported but getExtension succeeded');
+ } else {
+ testPassed(extensionName + ' not listed as supported and getExtension failed -- this is legal');
+ }
+ }
+ return false;
+}
+
+function runTest() {
+ if (!gl) {
+ return function() {
+ testFailed('WebGL context does not exist');
+ }
+ }
+
+ doTest('WEBGL_draw_instanced_base_vertex_base_instance', false);
+ doTest('WEBGL_multi_draw_instanced_base_vertex_base_instance', true);
+
+ testGlslBuiltins();
+}
+
+// -
+
+function* range(n) {
+ for (let i = 0; i < n; i++) {
+ yield i;
+ }
+}
+
+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;
+}
+
+// -
+
+const PASSTHROUGH_FRAG_SRC = `\
+#version 300 es
+precision mediump float;
+in vec4 v_color;
+out vec4 o_color;
+
+void main() {
+ o_color = v_color;
+}
+`;
+
+function testGlslBuiltins() {
+ const EXT = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+
+ const vertid_prog = (() => {
+ const vert_src = `\
+#version 300 es
+#line 405
+layout(location = 0) in int a_vertex_id; // Same as gl_VertexID
+out vec4 v_color;
+
+void main() {
+ gl_Position = vec4(0,0,0,1);
+ gl_PointSize = 1.0;
+ v_color = vec4(float(gl_VertexID), float(a_vertex_id),0,0);
+ v_color /= 255.0;
+}
+`;
+ const prog = wtu.setupProgram(gl, [vert_src, PASSTHROUGH_FRAG_SRC],
+ undefined, undefined, /*logShaders*/ true);
+ expectTrue(!!prog, `make_vertid_prog failed`);
+ return prog;
+ })();
+
+ const instid_prog = (() => {
+ const vert_src = `\
+#version 300 es
+#line 425
+layout(location = 0) in int a_vertex_id; // Same as gl_VertexID
+layout(location = 1) in int a_instance_div1; // Same as base_instance+gl_InstanceID
+layout(location = 2) in int a_instance_div2; // Same as base_instance+floor(gl_InstanceID/2)
+layout(location = 3) in int a_instance_div3; // Same as base_instance+floor(gl_InstanceID/3)
+out vec4 v_color;
+
+void main() {
+ gl_Position = vec4(0,0,0,1);
+ gl_PointSize = 1.0;
+ v_color = vec4(float(gl_InstanceID), float(a_instance_div1),
+ float(a_instance_div2), float(a_instance_div3));
+ v_color /= 255.0;
+}
+`;
+ const prog = wtu.setupProgram(gl, [vert_src, PASSTHROUGH_FRAG_SRC],
+ undefined, undefined, /*logShaders*/ true);
+ expectTrue(!!prog, `make_instid_prog failed`);
+ return prog;
+ })();
+
+ const COUNT_UP_DATA = new Int32Array(1000);
+ for (const i in COUNT_UP_DATA) {
+ COUNT_UP_DATA[i] = i;
+ }
+
+ const vertex_id_buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertex_id_buf);
+ gl.bufferData(gl.ARRAY_BUFFER, COUNT_UP_DATA, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribIPointer(0, 1, gl.INT, 0, 0);
+
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribIPointer(1, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+
+ gl.enableVertexAttribArray(2);
+ gl.vertexAttribIPointer(2, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(2, 2);
+
+ gl.enableVertexAttribArray(3);
+ gl.vertexAttribIPointer(3, 1, gl.INT, 0, 0);
+ gl.vertexAttribDivisor(3, 3);
+
+ const index_buf = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, index_buf);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, COUNT_UP_DATA, gl.STATIC_DRAW);
+
+ gl.canvas.width = gl.canvas.height = 1;
+ gl.canvas.style.width = gl.canvas.style.height = '1em';
+ gl.viewport(0, 0, 1, 1);
+
+ const expect_pixel = (() => {
+ const was = new Uint8Array(4);
+ return (desc, subtest, expected) => {
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, was);
+ if (!areArraysEqual(was, expected)) {
+ testFailed(`${subtest}: Expected [${expected}], was [${was}]. desc: ${JSON.stringify(desc)}`);
+ } else {
+ debug(`${subtest}: Was [${was}] as expected.`);
+ }
+ };
+ })();
+
+ // Common setup complete
+ // -
+ // Create testcases
+
+ const DRAW_FUNC_COMBINER = [{
+ name: 'drawArraysInstanced',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (desc.base_inst) return false;
+ gl.drawArraysInstanced(gl[desc.mode], desc.first_vert,
+ desc.vert_count, desc.inst_count);
+ return true;
+ },
+ }, {
+ name: 'drawElementsInstanced',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (desc.base_inst) return false;
+ gl.drawElementsInstanced(gl[desc.mode], desc.vert_count,
+ gl.UNSIGNED_INT, 4*desc.first_vert, desc.inst_count);
+ return true;
+ },
+ }, {
+ name: 'drawArraysInstancedBaseInstanceWEBGL',
+ draw: desc => {
+ if (desc.base_vert) return false;
+ if (!EXT) return false;
+ EXT.drawArraysInstancedBaseInstanceWEBGL(gl[desc.mode],
+ desc.first_vert, desc.vert_count, desc.inst_count,
+ desc.base_inst);
+ return true;
+ },
+ }, {
+ name: 'drawElementsInstancedBaseVertexBaseInstanceWEBGL',
+ draw: desc => {
+ if (!EXT) return false;
+ EXT.drawElementsInstancedBaseVertexBaseInstanceWEBGL(
+ gl[desc.mode], desc.vert_count, gl.UNSIGNED_INT, 4*desc.first_vert,
+ desc.inst_count, desc.base_vert, desc.base_inst);
+ return true;
+ },
+ }];
+
+ // -
+
+ function make_key_combiner(key, vals) {
+ const ret = [];
+ for (const v of vals) {
+ const cur = {};
+ cur[key] = v;
+ ret.push(cur);
+ }
+ return ret;
+ }
+
+ const TEST_DESCS = crossCombine(
+ DRAW_FUNC_COMBINER,
+ make_key_combiner('base_vert', [0,1,2]),
+ make_key_combiner('vert_count', [0,1,2]),
+ make_key_combiner('base_inst', [0,1,2]),
+ make_key_combiner('inst_count', range(10)),
+ make_key_combiner('first_vert', [0,1,2]),
+ );
+ console.log('TEST_DESCS', TEST_DESCS);
+
+ // -
+ // Run testcases
+
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.STENCIL_TEST);
+ gl.disable(gl.BLEND);
+
+ for (const desc of TEST_DESCS) {
+ gl.disable(gl.SCISSOR_TEST);
+ gl.clearBufferfv(gl.COLOR, 0, [1,0,0,1]);
+
+ // From OpenGL ES 3.2 spec section 10.5
+ // https://www.khronos.org/registry/OpenGL/specs/es/3.2/es_spec_3.2.pdf
+ // 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.
+ const last_gl_vert_id = desc.base_vert + desc.first_vert + desc.vert_count - 1;
+ const last_vert_id = last_gl_vert_id;
+ const last_inst_id = desc.inst_count - 1;
+ const last_inst_div1 = desc.base_inst + last_inst_id;
+ const last_inst_div2 = desc.base_inst + Math.floor(last_inst_id / 2);
+ const last_inst_div3 = desc.base_inst + Math.floor(last_inst_id / 3);
+
+ gl.useProgram(vertid_prog);
+ if (!desc.draw(desc)) continue;
+ debug('\ndesc: ' + JSON.stringify(desc));
+
+ wtu.glErrorAssert(gl, 0);
+ if (!desc.vert_count || !desc.inst_count) {
+ expect_pixel(desc, 'vertid_prog', [255, 0, 0, 255]);
+ continue;
+ }
+
+ expect_pixel(desc, 'vertid_prog', [last_gl_vert_id, last_vert_id, 0, 0]);
+
+ gl.useProgram(instid_prog);
+ desc.draw(desc);
+ expect_pixel(desc, 'instid_prog', [last_inst_id, last_inst_div1, last_inst_div2, last_inst_div3]);
+ }
+}
+
+// -
+
+function doTest(extensionName, multiDraw) {
+ const ext = gl.getExtension(extensionName);
+ if (!runSupportedTest(extensionName, ext)) {
+ return;
+ }
+
+ function getShaderSource(countX, countY, config) {
+ const vs = [
+ '#version 300 es',
+ config.isMultiDraw ? '#extension GL_ANGLE_multi_draw : require' : '',
+ '#define kCountX ' + countX.toString(),
+ '#define kCountY ' + countY.toString(),
+ 'layout(location = 0) in vec2 vPosition;',
+ 'layout(location = 1) in float vInstanceID;',
+ 'out vec4 color;',
+ 'void main()',
+ '{',
+ ' const float xStep = 1.0 / float(kCountX);',
+ ' const float yStep = 1.0 / float(kCountY);',
+ ' float xID = vInstanceID;',
+ ' float xColor = 1.0 - xStep * xID;',
+ ' float yID = floor(float(gl_VertexID) / ' + (config.isDrawArrays ? '6.0' : '4.0') + ' + 0.01);',
+ ' color = vec4(xColor, 1.0 - yStep * yID, 1.0',
+ ' , 1.0);',
+ ' mat3 transform = mat3(1.0);',
+ ' transform[2][0] = xID * xStep;',
+ ' gl_Position = vec4(transform * vec3(vPosition, 1.0) * 2.0 - 1.0, 1.0);',
+ '}'
+ ].join('\n');
+
+ const fs = document.getElementById('fshader').text.trim();
+
+ return [vs, fs];
+ }
+
+ function runValidationTests(bufferUsage) {
+ const vertexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0.2,0.2, 0.8,0.2, 0.5,0.8 ]), bufferUsage);
+
+ const indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([ 0, 1, 2 ]), bufferUsage);
+
+ const instanceBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 1, 2 ]), bufferUsage);
+
+ const program = wtu.setupProgram(gl, ['vshaderSimple', 'fshader'], ['vPosition, vInstanceID'], [0, 1], true);
+ expectTrue(program != null, "can compile simple program");
+
+ function setupInstanced() {
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceBuffer);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+ }
+
+ setupInstanced();
+
+ function setupDrawArrays() {
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+ }
+
+ function setupDrawElements() {
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+ }
+
+ function makeDrawValidationCheck(drawFunc, setup) {
+ if (!drawFunc) {
+ return function() {};
+ }
+ return function(f_args, expect, msg) {
+ setup();
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawFunc.apply(ext, f_args);
+ wtu.glErrorShouldBe(gl, expect, drawFunc.name + " " + msg);
+ gl.disableVertexAttribArray(0);
+ }
+ }
+
+ if (!multiDraw) {
+ const checkDrawArraysInstancedBaseInstance = makeDrawValidationCheck(
+ ext.drawArraysInstancedBaseInstanceWEBGL, setupDrawArrays);
+ const checkDrawElementsInstancedBaseVertexBaseInstance = makeDrawValidationCheck(
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL, setupDrawElements);
+ checkDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, 0, 3, 1, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 0, 0],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+
+ checkDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, 0, 3, 1, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseInstance leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 2, 0],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseVertex leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 0, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with baseInstance leading to out of bounds"
+ );
+ checkDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, 3, gl.UNSIGNED_BYTE, 0, 1, 2, 3],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with both baseVertex and baseInstance leading to out of bounds"
+ );
+ } else {
+ const checkMultiDrawArraysInstancedBaseInstance = makeDrawValidationCheck(
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL, setupDrawArrays);
+ const checkMultiDrawElementsInstancedBaseVertexBaseInstance = makeDrawValidationCheck(
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL, setupDrawElements);
+
+ // Check that drawing a single triangle works
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 1],
+ gl.NO_ERROR, "with gl.TRIANGLES"
+ );
+
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseInstance leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [2], 0, [0], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseVertex leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION], "with baseInstance leads to out of bounds"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [2], 0, [3], 0, 1],
+ [gl.NO_ERROR, gl.INVALID_OPERATION],
+ "with both baseVertex and baseInstance lead to out of bounds"
+ );
+
+ // Zero drawcount permitted
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, 0],
+ gl.NO_ERROR, "with drawcount == 0"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 0],
+ gl.NO_ERROR, "with drawcount == 0"
+ );
+
+ // Check negative drawcount
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 0, -1],
+ gl.INVALID_VALUE, "with drawcount < 0"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, -1],
+ gl.INVALID_VALUE, "with drawcount < 0"
+ );
+
+ // Check offsets greater than array length
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 1, [3], 0, [1], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with firstsStart >= firstsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 1, [1], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with countsStart >= countsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 1, [0], 0, 1],
+ gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length"
+ );
+ checkMultiDrawArraysInstancedBaseInstance(
+ [gl.TRIANGLES, [0], 0, [3], 0, [1], 0, [0], 1, 1],
+ gl.INVALID_OPERATION, "with baseInstancesStart >= baseInstancesList.length"
+ );
+
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 1, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with countsStart >= countsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 1, [1], 0, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with offsetsStart >= offsetsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 1, [0], 0, [0], 0, 1],
+ gl.INVALID_OPERATION, "with instanceCountsStart >= instanceCountsList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 1, [0], 0, 1],
+ gl.INVALID_OPERATION, "with baseVerticesStart >= baseVerticesList.length"
+ );
+ checkMultiDrawElementsInstancedBaseVertexBaseInstance(
+ [gl.TRIANGLES, [3], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [0], 0, [0], 1, 1],
+ gl.INVALID_OPERATION, "with baseInstancesStart >= baseInstancesList.length"
+ );
+ }
+ }
+
+ function runShaderTests(bufferUsage) {
+ let badProgram;
+
+ badProgram = wtu.setupProgram(gl, ["vshaderBaseInstanceWithoutExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with gl_BaseInstance but no extension directive");
+ badProgram = wtu.setupProgram(gl, ["vshaderBaseVertexWithoutExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with gl_BaseVertex but no extension directive");
+
+ badProgram = wtu.setupProgram(gl, ["vshaderWithExt", "fshader"]);
+ expectTrue(!badProgram, "cannot compile program with #extension GL_ANGLE_base_vertex_base_instance");
+
+ const x = Math.floor(width * 0.4);
+ const y = Math.floor(height * 0.4);
+ const xSize = Math.floor(width * 0.2);
+ const ySize = Math.floor(height * 0.2);
+
+ // gl_InstanceID
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0.5,1, 0,1, 0.5,0, 1,1 ]), bufferUsage);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ const instanceIDProgram = wtu.setupProgram(gl, ["vshaderInstanceIDCheck", "fshader"], ["vPosition"], [0]);
+ expectTrue(instanceIDProgram !== null, "can compile program with gl_InstanceID");
+ gl.useProgram(instanceIDProgram);
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ if (!multiDraw) {
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, 6, 1, 5);
+ } else {
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, [0], 0, [6], 0, [1], 0, [5], 0, 1);
+ }
+
+ wtu.checkCanvasRect(gl, x, y, xSize, ySize, [0, 255, 0, 255], "gl_InstanceID should always starts from 0");
+
+ // gl_VertexID
+ gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0.5,1, 0,1, 0.5,0, 1,1, 0,0, 1,0, 0.5,1, 0,1 ]), bufferUsage);
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, gl.createBuffer());
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array([0, 1, 2, 3, 4, 5]), bufferUsage);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ const vertexIDProgram = wtu.setupProgram(gl, ["vshaderVertexIDCheck", "fshader"], ["vPosition"], [0]);
+ expectTrue(vertexIDProgram !== null, "can compile program with gl_VertexID");
+ gl.useProgram(vertexIDProgram);
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ if (!multiDraw) {
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1, 3, 0);
+ } else {
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, [6], 0, gl.UNSIGNED_BYTE, [0], 0, [1], 0, [3], 0, [0], 0, 1);
+ }
+
+ wtu.checkCanvasRect(gl, x, y, xSize, ySize, [0, 255, 0, 255], "gl_VertexID should always starts from 0");
+ }
+
+ function runPixelTests() {
+
+ function checkResult(config) {
+ const rects = [];
+ const expected = [
+ [255, 0, 0, 255],
+ [0, 255, 0, 255],
+ [0, 0, 255, 255],
+ ];
+ const msg = config.drawFunc.name + (
+ config.useBaseVertexBuiltin ? ' gl_BaseVertex' : ''
+ ) + (
+ config.useBaseInstanceBuiltin ? ' gl_BaseInstance' : ' InstanceIDArray'
+ );
+ for (let y = 0; y < y_count; ++y) {
+ for (let x = 0; x < x_count; ++x) {
+ const center_x = x * tilePixelSize[0] + Math.floor(tilePixelSize[0] / 2);
+ const center_y = y * tilePixelSize[1] + Math.floor(tilePixelSize[1] / 2);
+
+ rects.push(wtu.makeCheckRect(
+ center_x - Math.floor(pixelCheckSize[0] / 2),
+ center_y - Math.floor(pixelCheckSize[1] / 2),
+ pixelCheckSize[0],
+ pixelCheckSize[1],
+ [
+ 256.0 * (1.0 - x / x_count),
+ 256.0 * (1.0 - y / y_count),
+ (!config.isDrawArrays && config.useBaseVertexBuiltin) ? 256.0 * (1.0 - y / y_count) : 255.0,
+ 255.0
+ ],
+ msg + ' (' + x + ',' + y + ')', 1.0
+ ));
+ }
+ }
+ wtu.checkCanvasRects(gl, rects);
+ }
+
+ // Draw functions variations
+
+ function drawArraysInstancedBaseInstance() {
+ const countPerDraw = y_count * 6;
+ for (let x = 0; x < x_count; x += 2) {
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, countPerDraw, 2, x);
+ }
+ }
+
+ function multiDrawArraysInstancedBaseInstance() {
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, drawArraysParams.firsts, 0, drawArraysParams.counts, 0, drawArraysParams.instances, 0, drawArraysParams.baseInstances, 0, drawArraysParams.drawCount);
+ }
+
+ function drawElementsInstancedBaseVertexBaseInstance() {
+ const countPerDraw = 6;
+ for (let v = 0; v < y_count; ++v) {
+ for (let x = 0; x < x_count; x += 2) {
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, countPerDraw, gl.UNSIGNED_SHORT, 0, 2, v * 4, x);
+ }
+ }
+ }
+
+ function multiDrawElementsInstancedBaseVertexBaseInstance() {
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, drawElementsParams.counts, 0, gl.UNSIGNED_SHORT, drawElementsParams.offsets, 0, drawElementsParams.instances, 0, drawElementsParams.baseVertices, 0, drawElementsParams.baseInstances, 0, drawElementsParams.drawCount);
+ }
+
+ function checkDraw(config) {
+ const program = wtu.setupProgram(
+ gl,
+ getShaderSource(x_count, y_count, config),
+ !config.useBaseInstanceBuiltin ? ['vPosition'] : ['vPosition', 'vInstanceID']
+ );
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ if (config.isDrawArrays) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, nonIndexedVertexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+ } else {
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+ }
+
+ if (!config.useBaseInstanceBuiltin) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, instanceIDBuffer);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 1, gl.FLOAT, false, 0, 0);
+ gl.vertexAttribDivisor(1, 1);
+ }
+
+ config.drawFunc();
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "there should be no errors");
+
+ checkResult(config);
+ }
+
+ checkDraw({
+ drawFunc: multiDraw ? multiDrawArraysInstancedBaseInstance : drawArraysInstancedBaseInstance,
+ isDrawArrays: true,
+ isMultiDraw: multiDraw,
+ useBaseVertexBuiltin: false,
+ useBaseInstanceBuiltin: false
+ });
+
+ checkDraw({
+ drawFunc: multiDraw ? multiDrawElementsInstancedBaseVertexBaseInstance : drawElementsInstancedBaseVertexBaseInstance,
+ isDrawArrays: false,
+ isMultiDraw: multiDraw,
+ useBaseVertexBuiltin: false,
+ useBaseInstanceBuiltin: false
+ });
+ }
+
+ for (let i = 0; i < bufferUsageSet.length; i++) {
+ let bufferUsage = bufferUsageSet[i];
+ debug("Testing with BufferUsage = " + bufferUsage);
+ setupGeneralBuffers(bufferUsage);
+ runValidationTests(bufferUsage);
+ runShaderTests(bufferUsage);
+ runPixelTests();
+ }
+}
+
+
+async function runDrawTests(testFn) {
+ function drawArrays(gl) {
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ }
+
+ function drawElements(gl) {
+ gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0);
+ }
+
+ function drawArraysInstanced(gl) {
+ gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, 1);
+ }
+
+ function drawElementsInstanced(gl) {
+ gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1);
+ }
+
+ function drawArraysInstancedBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_draw_instanced_base_vertex_base_instance';
+ }
+
+ ext.drawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, 0, 6, 1, 0);
+ }
+
+ function drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_draw_instanced_base_vertex_base_instance';
+ }
+
+ ext.drawElementsInstancedBaseVertexBaseInstanceWEBGL(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 1, 0, 0);
+ }
+
+ function multiDrawArraysInstancedBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_multi_draw_instanced_base_vertex_base_instance';
+ }
+ ext.multiDrawArraysInstancedBaseInstanceWEBGL(gl.TRIANGLES, [0], 0, [6], 0, [1], 0, [0], 0, 1);
+ }
+
+ function multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(gl) {
+ const ext = gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance');
+ if (!ext) {
+ throw 'Should not have run this test without WEBGL_multi_draw_instanced_base_vertex_base_instance';
+ }
+ ext.multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL(
+ gl.TRIANGLES,
+ [6], 0, // counts
+ gl.UNSIGNED_BYTE,
+ [0], 0, // offsets
+ [1], 0, // instances
+ [0], 0, // baseVerts
+ [0], 0, // baseInstances
+ 1, // drawCount
+ );
+ }
+
+ await testFn(drawArrays); // sanity check
+ await testFn(drawElements); // sanity check
+ await testFn(drawArraysInstanced); // sanity check
+ await testFn(drawElementsInstanced); // sanity check
+
+ // It's only legal to call testFn if the extension is supported,
+ // since the invalid vertex attrib tests, in particular, expect the
+ // draw function to have an effect.
+ if (gl.getExtension('WEBGL_draw_instanced_base_vertex_base_instance')) {
+ await testFn(drawArraysInstancedBaseInstanceWEBGL);
+ await testFn(drawElementsInstancedBaseVertexBaseInstanceWEBGL);
+ }
+ if (gl.getExtension('WEBGL_multi_draw_instanced_base_vertex_base_instance')) {
+ await testFn(multiDrawArraysInstancedBaseInstanceWEBGL);
+ await testFn(multiDrawElementsInstancedBaseVertexBaseInstanceWEBGL);
+ }
+}
+
+async function runCompositingTests() {
+ const compositingTestFn = createCompositingTestFn({
+ webglVersion: 2,
+ shadersFn(gl) {
+ const vs = `\
+ #version 300 es
+ layout(location = 0) in vec4 position;
+ void main() {
+ gl_Position = position;
+ }
+ `;
+ const fs = `\
+ #version 300 es
+ precision highp float;
+ out vec4 fragColor;
+ void main() {
+ fragColor = vec4(1, 0, 0, 1);
+ }
+ `;
+ return [vs, fs];
+ },
+ });
+ await runDrawTests(compositingTestFn);
+}
+
+async function runInvalidAttribTests(gl) {
+ const invalidAttribTestFn = createInvalidAttribTestFn(gl);
+ await runDrawTests(invalidAttribTestFn);
+}
+
+async function main() {
+ runTest();
+ await runInvalidAttribTests(gl);
+ await runCompositingTests();
+ finishTest();
+}
+main();
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>