diff options
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/conformance2/extensions')
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> |