summaryrefslogtreecommitdiffstats
path: root/dom/canvas/test/webgl-conf/checkout/extra
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /dom/canvas/test/webgl-conf/checkout/extra
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/extra')
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/50x50pixel-black-with-red-triangle.pngbin0 -> 3032 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/big-fbos-example.html256
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/buffer-gc-stress.html155
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/buffer-sizes.html268
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.html88
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.pngbin0 -> 212531 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/constant-index-out-of-range.html219
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/context-creation-and-destruction-stress.html35
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/cube-map-uploads-out-of-order.html91
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/fbo-lost-context.html202
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/getbuffersubdata-nonblocking-benchmark.html223
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/logo-256x256.pngbin0 -> 4155 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-example.html89
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-shader-example.html155
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/multisample-corruption-stress.html37
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/offscreen-issue.html43
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/out-of-bounds-uniform-array-access.html105
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/out-of-memory.html84
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/out-of-resources.html120
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/out-of-vram.html114
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/point-no-attributes-stress.html61
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/program-test-1.html80
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/readpixels-after-alert.html65
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/sample-100.pngbin0 -> 960 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/sample-200.pngbin0 -> 2364 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/sample-400.pngbin0 -> 5181 bytes
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/sample.svg4
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/simulated-attrib-0-bug-test.html36
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/slow-shader-example.html112
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.html27
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.js158
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/texture-allocation-stress-test.html47
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/texture-from-camera-stress.html79
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/texture-sizing.html229
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/webgl-compressed-texture-size-limit-stress.html33
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/webgl-drawelements-validation.html142
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/webgl-info.html275
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/webgl-translate-shader.html153
-rw-r--r--dom/canvas/test/webgl-conf/checkout/extra/workload-simulator.html851
39 files changed, 4636 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/50x50pixel-black-with-red-triangle.png b/dom/canvas/test/webgl-conf/checkout/extra/50x50pixel-black-with-red-triangle.png
new file mode 100644
index 0000000000..4ec9751070
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/50x50pixel-black-with-red-triangle.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/big-fbos-example.html b/dom/canvas/test/webgl-conf/checkout/extra/big-fbos-example.html
new file mode 100644
index 0000000000..0e0a09bcef
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/big-fbos-example.html
@@ -0,0 +1,256 @@
+<!--
+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 Big FBO Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../../devtools/src/debug/webgl-debug.js"></script>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="256" height="256"> </canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec2 texCoord0;
+varying vec2 texCoord;
+void main()
+{
+ gl_Position = vec4(vPosition.xyz, 1.0);
+ texCoord = texCoord0;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D tex;
+varying vec2 texCoord;
+void main()
+{
+ gl_FragColor = texture2D(tex, texCoord);
+}
+</script>
+<script>
+"use strict";
+window.onload = init;
+
+var g_textures = [];
+
+debug("Tests the performance of using lots of large FBOs");
+
+function init() {
+ if (confirm(
+ "After clicking OK your machine may become unresponsive or crash.")) {
+ main();
+ } else {
+ debug("cancelled");
+ }
+}
+
+function checkFBOStatus(gl) {
+ var err = gl.getError();
+ if (err != gl.NO_ERROR) {
+ if (err != gl.OUT_OF_MEMORY)
+ testFailed("gl.getError returned " + err);
+ else
+ testPassed("OUT-OF-MEMORY");
+ return false;
+ }
+ var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (status != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("gl.checkFramebufferStatus() returned " + WebGLTestUtils.glEnumToString(gl, status));
+ return false;
+ }
+ return true;
+}
+
+function setupFBO(gl, size) {
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ 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);
+
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ size, // width
+ size, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ null); // data
+ if (!checkFBOStatus(gl))
+ return null;
+
+ return { fb: fb, tex: tex };
+}
+
+function checkPixels(gl) {
+ var width = 256;
+ var height = 256;
+
+ var thresh = 3;
+
+ var buf = new Uint8Array(width * height * 4);
+ gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buf);
+ for (var yy = 0; yy < height; ++yy) {
+ for (var xx = 0; xx < width; ++xx) {
+ var offset = (yy * width + xx) * 4;
+ if (Math.abs(buf[offset] - 255) > thresh ||
+ Math.abs(buf[offset + 1] - 0) > thresh ||
+ Math.abs(buf[offset + 2] - 0) > thresh) {
+ testFailed("drawing results incorrect");
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+function handleContextLost() {
+ debug("context lost");
+}
+
+function main() {
+ debug("");
+ debug("Checking for out of memory handling.");
+
+ var canvas = document.getElementById("canvas");
+ canvas.addEventListener('webglcontextlost', handleContextLost);
+ var wtu = WebGLTestUtils;
+ var gl = wtu.create3DContext("canvas");
+ var prog = wtu.setupProgram(gl, ["vshader", "fshader"], ["vPosition", "texCoord0"]);
+
+ WebGLDebugUtils.init(gl);
+
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.BLEND);
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1,1,0, 1,1,0, -1,-1,0,
+ -1,-1,0, 1,1,0, 1,-1,0
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0, 1,0, 0,1,
+ 0,1, 1,0, 1,1
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+
+ var texLoc = gl.getUniformLocation(prog, "tex");
+ gl.uniform1i(texLoc, 0);
+
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup should succeed");
+
+ var size = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
+ debug("max render buffer size: " + size +
+ ", size used: " + (size / 2));
+ size /= 2;
+
+ var maxFBOs = 200;
+ var numFBOs = 0;
+ allocateNextFBO();
+
+ function allocateNextFBO() {
+ if (numFBOs >= maxFBOs) {
+ phase2();
+ return;
+ }
+ if (!allocateFBO()) {
+ phase2();
+ return;
+ }
+ ++numFBOs;
+ setTimeout(allocateNextFBO, 100);
+ }
+
+ function allocateFBO() {
+ debug("");
+ debug("trying to create fbo #" + (numFBOs + 1));
+ var t = setupFBO(gl, 2);
+ if (!t) {
+ return false;
+ }
+
+ var tex = t.tex;
+ var fb = t.fb;
+
+ debug("allocating fbo color buffer of size " + size + " x " + size);
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ size, // width
+ size, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ null); // data
+ if (!checkFBOStatus(gl)) {
+ return false;
+ }
+ g_textures.push(tex);
+ debug("succeeded in creating fbo");
+
+ debug("clearing the fbo with red color");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.clearColor(1, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ debug("deleting fbo, but the now red texture should be untouched");
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.deleteFramebuffer(fb);
+
+ debug("drawing to the canvas using the red texture");
+ gl.clearColor(0, 0, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+ if (!checkPixels(gl)) {
+ return false;
+ }
+
+ debug("succeeded in drawing");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "each run with no error");
+ return true;
+ }
+
+ function phase2() {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+ debug("");
+ debug("fbos allocated:" + numFBOs);
+ if (!checkPixels(gl)) {
+ testFailed("final check of canvas drawing buffer pixels failed");
+ }
+ debug("");
+ finishTest();
+ }
+}
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/buffer-gc-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/buffer-gc-stress.html
new file mode 100644
index 0000000000..86056747e2
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/buffer-gc-stress.html
@@ -0,0 +1,155 @@
+<!--
+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>Float32Array garbage collection test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="canvas" width="40" height="40"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec2 inPosition;
+attribute vec4 inColor;
+
+varying vec4 color;
+
+void main()
+{
+ color = inColor;
+
+ gl_Position = vec4(inPosition, 0.0, 1.0);
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+
+varying vec4 color;
+
+void main()
+{
+ if (color == vec4(0.0))
+ discard;
+
+ gl_FragColor = color;
+}
+</script>
+
+<script>
+"use strict";
+
+description("Allocates a buffer object and then updates it repeatedly using throw-away Float32Array objects. " +
+ "Ideally, this should not result in a browser crash or instability, since GC should be able to collect all Float32Arrays.");
+var wtu = WebGLTestUtils;
+
+var vertices = [];
+var w = 0.25;
+for (var x = -1; x < 1; x += w) {
+ for (var y = -1; y < 1; y += w) {
+ vertices.push(x + w, y + w);
+ vertices.push(x, y + w);
+ vertices.push(x, y );
+
+ vertices.push(x + w, y + w);
+ vertices.push(x, y );
+ vertices.push(x + w, y );
+ }
+}
+var numVertices = (vertices.length / 2);
+
+var gl;
+var squareBuffer;
+var buffer;
+var updateBufferData;
+var drawIterationsPerTest = 100;
+
+function initGL() {
+ gl = wtu.create3DContext("canvas");
+ var attribs = ["inPosition", "inColor"];
+ wtu.setupProgram(gl, ["vshader", "fshader"], attribs);
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.BLEND);
+
+ squareBuffer = gl.createBuffer();
+ gl.enableVertexAttribArray(0);
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ buffer = gl.createBuffer();
+ gl.enableVertexAttribArray(1);
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.vertexAttribPointer(1, 4, gl.FLOAT, false, 0, 0);
+}
+
+var testIndex = -1;
+var drawIterations = 0;
+var size = 0;
+function runNextTest() {
+ ++testIndex;
+ var prevSize = size;
+ size = Math.pow(2, testIndex) * numVertices * 16;
+
+ if (size > 2 * 1024 * 1024 && prevSize <= 2 * 1024 * 1024) {
+ if (!confirm("The following tests can cause unresponsiveness or instability. Press OK to continue.")) {
+ testFailed("Tests aborted");
+ return;
+ }
+ }
+
+ if (size > 64 * 1024 * 1024) {
+ gl.deleteBuffer(buffer);
+ testPassed("Tests finished");
+ return;
+ }
+
+ debug('Initializing buffer with size: ' + size);
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, size, gl.DYNAMIC_DRAW);
+ updateBufferData = new Float32Array(size / 4);
+
+ debug("Drawing " + drawIterationsPerTest + " times, each time creating a new throw-away Float32Array of size " + size + " and using it to update the buffer");
+ drawIterations = 0;
+ doDraw();
+};
+
+var doDraw = function() {
+ gl.clearColor(0, 255, 0, 255);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // Update the array buffer with a throw-away Float32Array
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(updateBufferData));
+
+ gl.drawArrays(gl.TRIANGLES, 0, numVertices);
+ var error = gl.getError();
+ if (error !== gl.NO_ERROR) {
+ testFailed("drawArrays failed with error " + wtu.glEnumToString(gl, error));
+ return;
+ }
+ if (drawIterations < drawIterationsPerTest) {
+ ++drawIterations;
+ requestAnimationFrame(doDraw);
+ } else {
+ runNextTest();
+ }
+};
+
+initGL();
+runNextTest();
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/buffer-sizes.html b/dom/canvas/test/webgl-conf/checkout/extra/buffer-sizes.html
new file mode 100644
index 0000000000..736c135b7a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/buffer-sizes.html
@@ -0,0 +1,268 @@
+<!--
+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>Buffer allocation test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<div id="canvasParent"></div>
+<div id="description"></div>
+<div id="console"></div>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec2 inPosition;
+attribute vec4 inColor0;
+attribute vec4 inColor1;
+attribute vec4 inColor2;
+attribute vec4 inColor3;
+attribute vec4 inColor4;
+attribute vec4 inColor5;
+attribute vec4 inColor6;
+attribute vec4 inColor7;
+
+varying vec4 color;
+
+void main()
+{
+ color = abs(inColor0) + abs(inColor1) + abs(inColor2) + abs(inColor3) +
+ abs(inColor4) + abs(inColor5) + abs(inColor6) + abs(inColor7);
+
+ color = clamp(color, vec4(0.0), vec4(1.0));
+
+ gl_Position = vec4(inPosition, 0.0, 1.0);
+}
+</script>
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+
+varying vec4 color;
+
+void main()
+{
+ if (color == vec4(0.0))
+ discard;
+
+ gl_FragColor = color;
+}
+</script>
+
+<script>
+description("Allocates a number of different sized buffers and checks that the buffers are cleared " +
+ "OR that the allocation results in gl.OUT_OF_MEMORY or context loss.");
+var wtu = WebGLTestUtils;
+
+// The shader processes eight vec4 attributes at once to reduce the amount of
+// draw calls.
+var numColorAttrs = 8;
+
+// Process 64 squares at once to also reduce the amount of draw calls.
+var vertices = [];
+var w = 0.25;
+for (var x = -1; x < 1; x += w) {
+ for (var y = -1; y < 1; y += w) {
+ vertices.push(x + w, y + w);
+ vertices.push(x, y + w);
+ vertices.push(x, y );
+
+ vertices.push(x + w, y + w);
+ vertices.push(x, y );
+ vertices.push(x + w, y );
+ }
+}
+var numVertices = (vertices.length / 2);
+
+var gl;
+var squareBuffer;
+var error = 0;
+var expectContextLost = false;
+
+function initGLForBufferSizesTest() {
+ var canvas = document.createElement("canvas");
+ canvas.width = 40;
+ canvas.height = 40;
+ var parent = document.getElementById("canvasParent");
+ parent.innerHTML = '';
+ parent.appendChild(canvas);
+ gl = wtu.create3DContext(canvas);
+ var attribs = ["inPosition", "inColor0", "inColor1", "inColor2", "inColor3",
+ "inColor4", "inColor5", "inColor6", "inColor7"];
+ wtu.setupProgram(gl, ["vshader", "fshader"], attribs);
+ gl.enableVertexAttribArray(0);
+ for (var i = 0; i < numColorAttrs; i++) {
+ gl.enableVertexAttribArray(1 + i);
+ }
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.BLEND);
+
+ squareBuffer = gl.createBuffer();
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
+}
+
+function createBuffer(size, allowedToFail) {
+ var msg = "Calling bufferData with size=" + size;
+ var buffer = gl.createBuffer();
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, size, gl.STATIC_DRAW);
+
+ error = gl.getError();
+ if (error !== gl.NO_ERROR) {
+ gl.deleteBuffer(buffer);
+ if (allowedToFail) {
+ if (error === gl.OUT_OF_MEMORY) {
+ testPassed(msg + " failed with gl.OUT_OF_MEMORY (this is allowed)");
+ return null;
+ } else if (error === gl.CONTEXT_LOST_WEBGL) {
+ testPassed(msg + " failed with gl.CONTEXT_LOST_WEBGL (this is allowed)");
+ return null;
+ }
+ }
+ testFailed(msg + " failed with error " + wtu.glEnumToString(gl, error));
+ return null;
+ }
+
+ testPassed(msg + " did not result in any errors");
+ var reportedSize = gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE);
+ expectContextLost = false;
+ if (reportedSize === null) {
+ testPassed("Null size reported by gl, this should happen if the context is lost which is allowed.");
+ expectContextLost = true;
+ } else if (reportedSize !== size) {
+ if (size > Math.pow(2, 32)) {
+ testPassed("gl reported different size " + reportedSize + " for the buffer, but this is expected since " +
+ "the requested size was above what the return value of getBufferParameter can represent.");
+ } else {
+ testFailed("gl reported different size " + reportedSize + " for the buffer.");
+ }
+ } else {
+ testPassed("Size reported by gl was the same as the requested size.");
+ }
+
+ return buffer;
+}
+
+// Draw a square on the canvas using attributes from the clear buffer created with bufferData.
+function drawWithBuffer(buffer, allowedToFail) {
+ gl.clearColor(0, 1, 0, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ var size = gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE);
+ // Each vec4 is 16 bytes
+ var increment = numVertices * numColorAttrs * 16;
+ for (var offset = 0; offset + increment <= size; offset += increment) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, squareBuffer);
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
+
+ for (var i = 0; i < numColorAttrs; i++) {
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.vertexAttribPointer(1 + i, 4, gl.FLOAT, false, 0,
+ offset + increment * i / numColorAttrs);
+ }
+ gl.drawArrays(gl.TRIANGLES, 0, numVertices);
+ error = gl.getError();
+
+ if (error !== gl.NO_ERROR) {
+ if (allowedToFail) {
+ if (error === gl.OUT_OF_MEMORY) {
+ testPassed("drawArrays failed with gl.OUT_OF_MEMORY (this is allowed)");
+ return;
+ } else if (error === gl.CONTEXT_LOST_WEBGL) {
+ testPassed("drawArrays failed with gl.CONTEXT_LOST_WEBGL (this is allowed)");
+ return;
+ }
+ }
+ testFailed("drawArrays failed with error " + wtu.glEnumToString(gl, error));
+ return;
+ }
+ }
+ wtu.checkCanvas(gl, [0, 255, 0, 255], "should be green");
+}
+
+
+// To be able to confirm the whole buffer has been cleared, the size needs to
+// be divisible by the amount of vertices. Thus most sizes are multiples of 3.
+var tests = [
+ // Reasonable sized buffers.
+ { size: 3 * 1024, allowedToFail: false, tryDrawing: true },
+ { size: 3 * 1024 * 1024, allowedToFail: false, tryDrawing: true },
+ { size: 3 * 1024 * 1024 * 16, allowedToFail: false, tryDrawing: true },
+
+ // Huge buffers, which are meant to test out of memory handling.
+ // Allowed failures are gl.OUT_OF_MEMORY or context loss.
+ // Succeeding in the allocations is allowed as well for forward compatibility.
+
+ // 1.5 GB allocation for stressing lower-end 32-bit systems.
+ // Allocation is likely to succeed on higher-end hardware.
+ { size: 3 * 1024 * 1024 * 512, allowedToFail: true, tryDrawing: true },
+ // A buffer that no implementation will be able to allocate for some time
+ // to come. To do this, we use half of the lower 43-bit half of a 44-bit
+ // memory address space, so that the size is still valid on current common
+ // 64-bit implementations, and also below 52-bit limit for exact conversion
+ // from float to long long in WebIDL (though 2^n should be safe anyway).
+ // The 4 TB size is large enough that even extrapolating the historical
+ // exponential growth trend of memory sizes, hardware in 2020's should
+ // still have some trouble actually doing the allocation.
+ { size: (1 << 12) * (1 << 30), allowedToFail: true, tryDrawing: false }
+];
+
+function finishBufferSizesTest() {
+ gl.deleteBuffer(squareBuffer);
+ finishTest();
+}
+
+var testIndex = -1;
+function runNextTest() {
+ ++testIndex;
+ if (testIndex > 0 && tests[testIndex - 1].allowedToFail) {
+ if (gl.isContextLost() || error === gl.OUT_OF_MEMORY) {
+ initGLForBufferSizesTest();
+ } else if (expectContextLost) {
+ testFailed("Context was not lost after timeout even though gl.getBufferParameter returned null.");
+ }
+ }
+ var buffer = createBuffer(tests[testIndex].size, tests[testIndex].allowedToFail);
+ if (buffer) {
+ if (tests[testIndex].tryDrawing) {
+ drawWithBuffer(buffer, tests[testIndex].allowedToFail);
+ }
+ gl.deleteBuffer(buffer);
+ }
+
+ if (testIndex + 1 >= tests.length) {
+ finishBufferSizesTest();
+ } else {
+ if (tests[testIndex + 1].allowedToFail && !tests[testIndex].allowedToFail) {
+ if (!confirm("The following tests can cause unresponsiveness or instability. Press OK to continue.")) {
+ testFailed("Tests aborted");
+ return;
+ }
+ }
+ if (tests[testIndex].allowedToFail) {
+ // Give plenty of time for possible context loss
+ setTimeout(runNextTest(), 5000);
+ } else {
+ runNextTest();
+ }
+ }
+};
+
+initGLForBufferSizesTest();
+runNextTest();
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.html b/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.html
new file mode 100644
index 0000000000..6eab0020bc
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.html
@@ -0,0 +1,88 @@
+<!--
+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>Canvas Compositing Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+Below are 2 50x50 pixel canvas but using CSS to display them at 100x100 pixels. <br/>
+They are solid black with a red triangle<br/>
+They each have a 10px CSS solid black border around them.<br/>
+Depending on how the browser composites the canvas with the page they will get
+a white outline<hr/>
+<div>
+2d canvas<br/>
+<canvas id="example2" width="50" height="50" style="width: 100px; height: 100px; border: 10px solid black;"></canvas>
+</div>
+<hr/>
+3d canvas<br/>
+<div>
+<canvas id="example" width="50" height="50" style="width: 100px; height: 100px; border: 10px solid black;"></canvas>
+</div>
+<hr/>
+img tag<br/>
+<img src="50x50pixel-black-with-red-triangle.png" style="width: 100px; height: 100px; border: 10px solid black;"/>
+<div id="description"></div>
+<div id="console"></div>
+ <script id="vshader" type="x-shader/x-vertex">
+ attribute vec4 vPosition;
+ void main()
+ {
+ gl_Position = vPosition;
+ }
+ </script>
+
+ <script id="fshader" type="x-shader/x-fragment">
+ void main()
+ {
+ gl_FragColor = vec4(1.0,0.0,0.0,1.0);
+ }
+ </script>
+
+ <script>
+ "use strict";
+ function init()
+ {
+ var wtu = WebGLTestUtils;
+ var canvas2d = document.getElementById("example2");
+ var ctx2d = canvas2d.getContext("2d");
+ ctx2d.fillStyle = "rgba(0, 0, 0, 255)"
+ ctx2d.fillRect(0, 0, 50, 50);
+ ctx2d.fillStyle = "rgba(255, 0, 0, 255)"
+ ctx2d.beginPath();
+ ctx2d.moveTo(25, 12.5);
+ ctx2d.lineTo(12.5, 37.5);
+ ctx2d.lineTo(37.5, 37.5);
+ ctx2d.lineTo(25, 12.5);
+ ctx2d.fill();
+
+
+ var gl = wtu.create3DContext("example");
+ var program = wtu.setupProgram(gl, ["vshader", "fshader"], ["vPosition"]);
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0,0.5,0, -0.5,-0.5,0, 0.5,-0.5,0 ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
+ }
+
+ init();
+ var successfullyParsed = true;
+ </script>
+</body>
+<script src="../js/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.png b/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.png
new file mode 100644
index 0000000000..ba80dbe760
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/constant-index-out-of-range.html b/dom/canvas/test/webgl-conf/checkout/extra/constant-index-out-of-range.html
new file mode 100644
index 0000000000..f16ade96af
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/constant-index-out-of-range.html
@@ -0,0 +1,219 @@
+<!--
+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>Indexing with a constant expression should compile only if the index is in range</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>
+<script id="VertexArrayTemplate" type="x-shader/x-vertex">
+precision mediump float;
+
+uniform float ua[4];
+
+$(init)
+
+void main() {
+ float c = ua[$(constantExpression)];
+ gl_Position = vec4(c);
+}
+</script>
+<script id="FragmentArrayTemplate" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform float ua[4];
+
+$(init)
+
+void main() {
+ float c = ua[$(constantExpression)];
+ gl_FragColor = vec4(c);
+}
+</script>
+<script id="VertexVectorTemplate" type="x-shader/x-vertex">
+precision mediump float;
+
+uniform vec4 uv;
+
+$(init)
+
+void main() {
+ float c = uv[$(constantExpression)];
+ gl_Position = vec4(c);
+}
+</script>
+<script id="FragmentVectorTemplate" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform vec4 uv;
+
+$(init)
+
+void main() {
+ float c = uv[$(constantExpression)];
+ gl_FragColor = vec4(c);
+}
+</script>
+<script id="VertexMatrixTemplate" type="x-shader/x-vertex">
+precision mediump float;
+
+uniform mat4 um;
+
+$(init)
+
+void main() {
+ float c = um[$(constantExpression)].x;
+ gl_Position = vec4(c);
+}
+</script>
+<script id="FragmentMatrixTemplate" type="x-shader/x-fragment">
+precision mediump float;
+
+uniform mat4 um;
+
+$(init)
+
+void main() {
+ float c = um[$(constantExpression)].x;
+ gl_FragColor = vec4(c);
+}
+</script>
+</head>
+<body onload="runTest()">
+<div id="description"></div>
+<div id="console"></div>
+<script type="application/javascript">
+"use strict";
+description();
+
+var wtu = WebGLTestUtils;
+
+// ESSL 1.00 section 4.1.9 Arrays
+// It is illegal to index an array with an integral constant expression greater than or equal to its
+// declared size. It is also illegal to index an array with a negative constant expression.
+
+// ESSL 1.00 section 5.5 Vector Components
+// Reading from or writing to a vector using a constant integral expression with a value that is negative
+// or greater than or equal to the size of the vector is illegal.
+
+// ESSL 1.00 section 5.6 Matrix components
+// The behavior when accessing a component outside the bounds of a matrix are the same as those for
+// vectors and arrays. The compiler must generate an error if the index expression is a constant expression.
+
+// ESSL 1.00 spec section 5.10.
+// A constant expression is one of
+// * a literal value (e.g., 5 or true)
+// * a global or local variable qualified as const excluding function parameters
+// * an expression formed by an operator on operands that are constant expressions, including getting
+// an element of a constant vector or a constant matrix, or a field of a constant structure
+// * a constructor whose arguments are all constant expressions
+// * a built-in function call whose arguments are all constant expressions, with the exception of the
+// texture lookup functions.
+
+var runTest = function() {
+ var vsArrayTemplate = document.getElementById('VertexArrayTemplate').text;
+ var fsArrayTemplate = document.getElementById('FragmentArrayTemplate').text;
+ var vsVectorTemplate = document.getElementById('VertexVectorTemplate').text;
+ var fsVectorTemplate = document.getElementById('FragmentVectorTemplate').text;
+ var vsMatrixTemplate = document.getElementById('VertexMatrixTemplate').text;
+ var fsMatrixTemplate = document.getElementById('FragmentMatrixTemplate').text;
+
+ var tests = [];
+
+ var pushTest = function(constantExpression, expectSuccess, opt_init) {
+ if (opt_init === undefined) {
+ opt_init = '';
+ }
+ tests.push({
+ vShaderSource: wtu.replaceParams(vsArrayTemplate, {constantExpression: constantExpression, init: opt_init}),
+ vShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as an index for an array with size 4 in a vertex shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ tests.push({
+ fShaderSource: wtu.replaceParams(fsArrayTemplate, {constantExpression: constantExpression, init: opt_init}),
+ fShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as an index for an array with size 4 in a fragment shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ tests.push({
+ vShaderSource: wtu.replaceParams(vsVectorTemplate, {constantExpression: constantExpression, init: opt_init}),
+ vShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as a vec4 index in a vertex shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ tests.push({
+ fShaderSource: wtu.replaceParams(fsVectorTemplate, {constantExpression: constantExpression, init: opt_init}),
+ fShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as a vec4 index in a fragment shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ tests.push({
+ vShaderSource: wtu.replaceParams(vsMatrixTemplate, {constantExpression: constantExpression, init: opt_init}),
+ vShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as a mat4 index in a vertex shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ tests.push({
+ fShaderSource: wtu.replaceParams(fsMatrixTemplate, {constantExpression: constantExpression, init: opt_init}),
+ fShaderSuccess: expectSuccess,
+ linkSuccess: expectSuccess,
+ passMsg: "Using " + constantExpression + " as a mat4 index in a fragment shader should " + (expectSuccess ? "compile." : "fail compilation.")
+ });
+ }
+
+ pushTest('0', true);
+ pushTest('3', true);
+ pushTest('-1', false);
+ pushTest('4', false);
+ pushTest('2 + 2', false);
+ pushTest('6 - 2', false);
+ pushTest('2 * 2', false);
+ pushTest('8 / 2', false);
+ pushTest('int(true) * 4', false);
+ pushTest('ivec4(4).x', false);
+ pushTest('ivec4(4)[0]', false);
+ pushTest('int(vec4(5.0).x)', false);
+ pushTest('int(mat4(5.0)[0].x)', false);
+
+ pushTest('int(radians(360.0))', false);
+ pushTest('int(degrees(1.0))', false);
+ pushTest('int(5.0 + sin(0.0))', false);
+ pushTest('int(5.0 + asin(0.0))', false);
+ pushTest('int(pow(2.0, 3.0))', false);
+ pushTest('int(exp(3.0))', false);
+ pushTest('int(exp2(4.0))', false);
+ pushTest('int(floor(-0.5))', false); // floor(-0.5) = -1.0
+ pushTest('int(5.0 + fract(-3.5))', false);
+ pushTest('int(mod(2.0, -4.0))', false); // mod(2.0, -4.0) = 2.0 - (-4.0) * floor(2.0 / -4.0) = 2.0 + 4.0 * (-1.0) = -2.0
+ pushTest('int(mix(2.0, 8.0, 0.9))', false);
+ pushTest('int(length(vec4(3.0)))', false);
+ pushTest('int(lessThan(vec4(2.0), vec4(3.0)).x) * 4', false);
+
+ pushTest('true ? 5 : 0', false);
+ pushTest('int(false ? 0.0 : 5.0)', false);
+ pushTest('my_struct(5, 1).field', false, 'struct my_struct { int field; int field2; };');
+
+ pushTest('int(-0.9)', true); // conversion to int drops the fractional part
+
+ // Sequence operator returns the value of the right-most expression.
+ // Note that the sequence operator is allowed in constant expressions in ESSL 1.00,
+ // but not in ESSL 3.00, so with ESSL 3.00 failing compilation would not be required.
+ pushTest('5, 1', true);
+ pushTest('1, 5', false);
+
+ GLSLConformanceTester.runTests(tests);
+}
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/context-creation-and-destruction-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/context-creation-and-destruction-stress.html
new file mode 100644
index 0000000000..7f316da3a0
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/context-creation-and-destruction-stress.html
@@ -0,0 +1,35 @@
+<!--
+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>Test that contexts are freed and garbage collected reasonably</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/iterable-test.js"> </script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description();
+
+var wtu = WebGLTestUtils;
+
+var test = IterableTest.createContextCreationAndDestructionTest();
+var iterations = parseInt(wtu.getUrlOptions().iterations, 10) || 500;
+IterableTest.run(test, iterations);
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/cube-map-uploads-out-of-order.html b/dom/canvas/test/webgl-conf/checkout/extra/cube-map-uploads-out-of-order.html
new file mode 100644
index 0000000000..abf9d1dd7d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/cube-map-uploads-out-of-order.html
@@ -0,0 +1,91 @@
+<!--
+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 cube map out of order upload test.</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+<script src="../js/glsl-conformance-test.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="example" width="64" height="64">
+</canvas>
+<script>
+"use strict";
+description("Test out of order cube map uploads.");
+debug("Regression test for crbug.com/473739 / Apple Radar 20444072.");
+
+<!-- Thanks to Gregg Tavares for the original report and test case. -->
+
+var wtu = WebGLTestUtils;
+
+var canvas = document.getElementById("example");
+canvas.addEventListener('webglcontextlost', contextLost, false);
+
+var contextWasLost = false;
+
+function contextLost(e) {
+ e.preventDefault();
+ contextWasLost = true;
+ debug("***context lost -- should not happen***");
+}
+
+var dataWidth = 256;
+var dataHeight = 256;
+var gl = wtu.create3DContext(canvas);
+var tex = gl.createTexture();
+// start with 1x1 pixel cubemap
+gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+var color = new Uint8Array([128, 192, 255, 255]);
+for (var ii = 0; ii < 6; ++ii) {
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + ii, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, color);
+}
+gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+gl.generateMipmap(gl.TEXTURE_CUBE_MAP); // there's no need to call this but the code doesn't check the size.
+
+var textureData = new Uint8Array(dataWidth * dataHeight * 4);
+
+// The first texture has downlaoded
+var first = 1;
+gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + first, 0, gl.RGBA, dataWidth, dataHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
+
+// Now because the first face downloaded doesn't match the other 5 faces upload the same image to the other 5
+// 1x1 faces
+for (var ii = 0; ii < 6; ++ii) {
+ if (ii !== first) {
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + ii, 0, gl.RGBA, dataWidth, dataHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
+ }
+}
+gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
+
+// Now as each new face comes in add it
+for (var ii = 0; ii < 6; ++ii) {
+ if (ii !== first) {
+ gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
+ gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + ii, 0, gl.RGBA, dataWidth, dataHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, textureData);
+ gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
+ }
+}
+
+gl.flush();
+
+setTimeout(function() {
+ shouldBe("contextWasLost", "false");
+ finishTest();
+}, 1000);
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/fbo-lost-context.html b/dom/canvas/test/webgl-conf/checkout/extra/fbo-lost-context.html
new file mode 100644
index 0000000000..b23113d909
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/fbo-lost-context.html
@@ -0,0 +1,202 @@
+<!--
+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 FBO Lost Context Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../../devtools/src/debug/webgl-debug.js"></script>
+<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 id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+attribute vec2 texCoord0;
+uniform mat4 world;
+varying vec2 texCoord;
+void main()
+{
+ gl_Position = vPosition * world;
+ texCoord = texCoord0;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+uniform sampler2D tex;
+varying vec2 texCoord;
+void main()
+{
+ gl_FragColor = texture2D(tex, texCoord);
+}
+</script>
+<canvas id="canvas" width="1024" height="1024"> </canvas>
+<script>
+"use strict";
+description("This test is to help see if an WebGL app *can* get lost context.");
+
+debug("");
+debug("Canvas.getContext");
+var wtu = WebGLTestUtils;
+var g_worldLoc;
+var g_texLoc;
+var g_textures = [];
+var gl = wtu.create3DContext("canvas");
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ debug("");
+ debug("Checking for out of memory handling.");
+
+ var program = wtu.setupProgram(["vshader", "fshader"], ["vPosition", "texCoord0"]);
+ var size = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
+ debug("max render buffer size: " + size);
+ size = size / 2;
+ debug("size used: " + size);
+
+ var allocateFramebuffers = true;
+ var itervalId;
+ var count = 0;
+
+ gl = WebGLDebugUtils.makeDebugContext(gl, function(err, functionName, args) {
+ window.clearInterval(intervalId);
+ assertMsg(err == gl.OUT_OF_MEMORY,
+ "correctly returns gl.OUT_OF_MEMORY when out of memory");
+ finish();
+ });
+
+ function createFBO() {
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+ 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);
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ size, // width
+ size, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ null); // data
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ tex,
+ 0);
+ var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (status != gl.FRAMEBUFFER_COMPLETE) {
+ testFailed("gl.checkFramebufferStatus() returned " + WebGLTestUtils.glEnumToString(gl, status));
+ }
+ return { fb: fb, tex: tex };
+ }
+
+ gl.disable(gl.DEPTH_TEST);
+
+ var numFBOs = 32;
+ for (var ii = 0; ii < numFBOs; ++ii) {
+ createFBO();
+ var t = createFBO();
+ tex = t.tex;
+ fb = t.fb;
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.scissor(0, 0, size, size);
+ gl.clearColor(0, ii / numFBOs, 1 - ii / numFBOs, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ g_textures.push(tex);
+ }
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1,1,0, 1,1,0, -1,-1,0,
+ -1,-1,0, 1,1,0, 1,-1,0
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(0);
+ gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
+
+ var vertexObject = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vertexObject);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ 0,0, 1,0, 0,1,
+ 0,1, 1,0, 1,1
+ ]), gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(1);
+ gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0);
+
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ g_texLoc = gl.getUniformLocation(gl.program, "tex");
+ gl.uniform1i(g_texLoc, 0);
+ g_worldLoc = gl.getUniformLocation(gl.program, "world");
+ gl.uniformMatrix4fv(g_worldLoc, false, [
+ 0, 0, 0, 0,
+ 0, 0, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1]);
+
+ 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);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+
+ setInterval(render, 1000/60);
+}
+
+var g_angle = 0;
+var g_texIndex = 0;
+function render() {
+ g_angle += 0.1;
+ g_texIndex++;
+ if (g_texIndex >= g_textures.length) {
+ g_texIndex = 0;
+ }
+ gl.bindTexture(gl.TEXTURE_2D, g_textures[g_texIndex]);
+ gl.uniformMatrix4fv(g_worldLoc, false, rotationZ(g_angle));
+ gl.clearColor(1,0,0,1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+}
+
+/**
+ * Creates a 4-by-4 matrix which rotates around the z-axis by the given angle.
+ * @param {number} angle The angle by which to rotate (in radians).
+ * @return {!o3djs.math.Matrix4} The rotation matrix.
+ */
+function rotationZ(angle) {
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+
+ return [
+ c, s, 0, 0,
+ -s, c, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+ ];
+};
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script>
+"use strict";
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/getbuffersubdata-nonblocking-benchmark.html b/dom/canvas/test/webgl-conf/checkout/extra/getbuffersubdata-nonblocking-benchmark.html
new file mode 100644
index 0000000000..e6233ea83f
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/getbuffersubdata-nonblocking-benchmark.html
@@ -0,0 +1,223 @@
+<!--
+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>getBufferSubData non-blocking test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description("Test that getBufferSubData is non-blocking when used with fenceSync");
+
+var wtu = WebGLTestUtils;
+
+var gl = wtu.create3DContext(undefined, undefined, 2);
+
+const srcData = new Uint8Array([ 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0 ]);
+const zeroData = new Uint8Array(8);
+
+const srcBuffer = gl.createBuffer();
+gl.bindBuffer(gl.COPY_READ_BUFFER, srcBuffer);
+gl.bufferData(gl.COPY_READ_BUFFER, srcData, gl.STATIC_DRAW);
+
+const readbackBuffer = gl.createBuffer();
+gl.bindBuffer(gl.COPY_WRITE_BUFFER, readbackBuffer);
+gl.bufferData(gl.COPY_WRITE_BUFFER, 8, gl.STREAM_READ);
+
+// unrelated buffers for tests
+gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer()); // used as copy dst
+gl.bufferData(gl.ARRAY_BUFFER, 8, gl.STATIC_DRAW);
+gl.bindBuffer(gl.UNIFORM_BUFFER, gl.createBuffer()); // used as copy src
+gl.bufferData(gl.UNIFORM_BUFFER, 8, gl.STATIC_DRAW);
+
+const dest = new Uint8Array(8);
+
+// Makes a new "resolvable" Promise
+function resolvable() {
+ let resolve;
+ const promise = new Promise(res => { resolve = res; });
+ promise.resolve = resolve;
+ return promise;
+}
+
+function fence() {
+ const promise = resolvable();
+
+ const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
+ gl.flush();
+ function check() {
+ const status = gl.clientWaitSync(sync, 0, 0);
+ if (status == gl.ALREADY_SIGNALED || status == gl.CONDITION_SATISFIED) {
+ gl.deleteSync(sync);
+ promise.resolve();
+ } else {
+ setTimeout(check, 0);
+ }
+ }
+ setTimeout(check, 0);
+
+ return promise;
+}
+
+function writeToReadbackBuffer() {
+ gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 8);
+}
+
+function timedGetBufferSubData() {
+ dest.fill(0);
+ const t0 = performance.now();
+ gl.getBufferSubData(gl.COPY_WRITE_BUFFER, 0, dest);
+ return (performance.now() - t0);
+}
+
+function timeBlockingReadback() {
+ const promise = resolvable();
+ setTimeout(() => {
+ writeToReadbackBuffer();
+ const tBlocking = timedGetBufferSubData();
+ const tLatency = tBlocking;
+ promise.resolve({latency: tLatency, blocking: tBlocking});
+ }, 0);
+ return promise;
+}
+
+function timeNonblockingReadback() {
+ writeToReadbackBuffer();
+ const tLatency0 = performance.now();
+ return fence().then(() => {
+ const tBlocking = timedGetBufferSubData();
+ const tLatency = performance.now() - tLatency0;
+ return {latency: tLatency, blocking: tBlocking};
+ });
+}
+
+function timeReadbackWithUnrelatedCopy() {
+ writeToReadbackBuffer();
+ const tLatency0 = performance.now();
+ const f = fence();
+ // copy to a buffer unrelated to the readback
+ gl.copyBufferSubData(gl.COPY_READ_BUFFER, gl.ARRAY_BUFFER, 0, 0, 8);
+ return f.then(() => {
+ const tBlocking = timedGetBufferSubData();
+ const tLatency = performance.now() - tLatency0;
+ return {latency: tLatency, blocking: tBlocking};
+ });
+}
+
+function timeReadbackInterrupted() {
+ writeToReadbackBuffer();
+ const tLatency0 = performance.now();
+ const f = fence();
+ // interrupt the readback by inserting another write
+ gl.copyBufferSubData(gl.UNIFORM_BUFFER, gl.COPY_WRITE_BUFFER, 0, 0, 8);
+ return f.then(() => {
+ const tBlocking = timedGetBufferSubData();
+ const tLatency = performance.now() - tLatency0;
+ return {latency: tLatency, blocking: tBlocking};
+ });
+}
+
+function computeMean(timings) {
+ let total = 0;
+ for (let i = 0; i < timings.length; ++i) {
+ total += timings[i];
+ }
+ return total / timings.length;
+}
+
+function measureMean(fn, iterations) {
+ const timingsLatency = Array(iterations);
+ const timingsBlocking = Array(iterations);
+
+ // Chain together `iterations` promises to call `fn` sequentially.
+ let promise = Promise.resolve();
+ for (let i = 0; i < iterations; ++i) {
+ promise = promise
+ .then(fn)
+ .then(t => {
+ timingsLatency[i] = t.latency;
+ timingsBlocking[i] = t.blocking;
+ });
+ }
+
+ return promise.then(() => {
+ const meanLatency = computeMean(timingsLatency);
+ const meanBlocking = computeMean(timingsBlocking);
+ return { latency: meanLatency, blocking: meanBlocking };
+ });
+}
+
+let t_blocking, t_nonblocking;
+let t_unrelated;
+let t_interrupted;
+Promise.resolve()
+ .then(() => {
+ let iterations = 500;
+ debug(`blocking readback: mean over ${iterations} iterations...`);
+ return measureMean(timeBlockingReadback, iterations);
+ })
+ .then(t => {
+ t_blocking = t;
+ debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
+ })
+ .then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
+
+ .then(() => debug(""))
+ .then(() => {
+ let iterations = 500;
+ debug(`nonblocking readback: mean over ${iterations} iterations...`);
+ return measureMean(timeNonblockingReadback, iterations);
+ })
+ .then(t => {
+ t_nonblocking = t;
+ debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
+ })
+ .then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
+
+ .then(() => debug(""))
+ .then(() => {
+ let iterations = 500;
+ debug(`readback interrupted by unrelated read from copy source: mean over ${iterations} iterations...`);
+ return measureMean(timeReadbackWithUnrelatedCopy, iterations);
+ })
+ .then(t => {
+ t_unrelated = t;
+ debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
+ })
+ .then(() => shouldBeTrue("areArraysEqual(dest, srcData)"))
+
+ .then(() => debug(""))
+ .then(() => {
+ let iterations = 500;
+ debug(`readback interrupted by write to readback source: mean over ${iterations} iterations...`);
+ return measureMean(timeReadbackInterrupted, iterations);
+ })
+ .then(t => {
+ t_interrupted = t;
+ debug(`... latency = ${t.latency}ms, blocking = ${t.blocking}ms`);
+ })
+ .then(() => shouldBeTrue("areArraysEqual(dest, zeroData)"))
+
+ .then(() => {
+ debug("");
+ shouldBeTrue("t_nonblocking.blocking < t_blocking.blocking");
+ shouldBeTrue("t_unrelated.blocking < t_blocking.blocking");
+ shouldBeTrue("t_nonblocking.blocking < t_interrupted.blocking");
+ })
+ .then(finishTest);
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/logo-256x256.png b/dom/canvas/test/webgl-conf/checkout/extra/logo-256x256.png
new file mode 100644
index 0000000000..b6a9ef1acd
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/logo-256x256.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-example.html b/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-example.html
new file mode 100644
index 0000000000..1c4fe866f6
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-example.html
@@ -0,0 +1,89 @@
+<!--
+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 Lots of polygons example.</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="1024" height="1024" style="width: 40px; height: 40px;">
+</canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+window.onload = init;
+debug("Tests a WebGL program that draws a bunch of large polygons");
+
+function init() {
+ if (confirm(
+ "After clicking OK your machine may become unresponsive or crash.")) {
+ main();
+ } else {
+ debug("cancelled");
+ }
+}
+
+function main() {
+ var wtu = WebGLTestUtils;
+ var canvas = document.getElementById("example");
+ canvas.addEventListener("webglcontextlost", function(e) { e.preventDefault(); }, false);
+ canvas.addEventListener("webglcontextrestored", function(e) { }, false);
+
+ var gl = wtu.create3DContext(canvas);
+ var program = wtu.setupTexturedQuad(gl);
+
+ assertMsg(gl.getError() == gl.NO_ERROR, "Should be no errors from setup.");
+
+ var tex = gl.createTexture();
+ gl.enable(gl.BLEND);
+ gl.disable(gl.DEPTH_TEST);
+
+ wtu.fillTexture(gl, tex, 4096, 4096, [0, 192, 128, 255], 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after creating 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, "after setting texture params");
+
+ var loc = gl.getUniformLocation(program, "tex");
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after getting tex locations");
+ gl.uniform1i(loc, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after setting tex uniform");
+
+ var numQuads = 100000;
+ var indexBuf = new ArrayBuffer(numQuads * 6);
+ var indices = new Uint8Array(indexBuf);
+ for (var ii = 0; ii < numQuads; ++ii) {
+ var offset = ii * 6;
+ indices[offset + 0] = 0;
+ indices[offset + 1] = 1;
+ indices[offset + 2] = 2;
+ indices[offset + 3] = 3;
+ indices[offset + 4] = 4;
+ indices[offset + 5] = 5;
+ }
+ var indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after creating index buffer");
+ gl.drawElements(gl.TRIANGLES, numQuads * 6, gl.UNSIGNED_BYTE, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after drawing");
+
+ var successfullyParsed = true;
+}
+</script>
+</body>
+</html>
+
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-shader-example.html b/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-shader-example.html
new file mode 100644
index 0000000000..2c57fbe1c3
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/lots-of-polys-shader-example.html
@@ -0,0 +1,155 @@
+<!--
+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 Lots of polygons example.</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>
+<style>
+html, body {
+ width: 100%;
+ height: 100%;
+}
+canvas {
+ border: 1px solid black;
+ width: 95%;
+ height: 80%;
+}
+</style
+</head>
+<body>
+<canvas id="example"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+window.onload = init;
+debug("Tests a WebGL program that draws a bunch of large polygons from a quad mesh");
+
+function init() {
+ if (confirm(
+ "After clicking OK your machine may become unresponsive or crash.")) {
+ main();
+ } else {
+ debug("cancelled");
+ }
+}
+
+function main() {
+ var wtu = WebGLTestUtils;
+ var canvas = document.getElementById("example");
+ var gridRes = 1000;
+ canvas.width = canvas.clientWidth;
+ canvas.heigt = canvas.clientHeight;
+ canvas.addEventListener("webglcontextlost", function(e) { e.preventDefault(); }, false);
+ canvas.addEventListener("webglcontextrestored", function(e) { }, false);
+
+ var gl = wtu.create3DContext(canvas);
+ var program = wtu.setupProgram(
+ gl, ['vshader', 'fshader'], ['vPosition'], [0]);
+
+ wtu.setupIndexedQuad(gl, gridRes, 0, true);
+
+ // make 1 texture since we'd have at least that in CSS shaders
+ var size = 256;
+ var pixels = new Uint8Array(size * size * 4);
+ for (var y = 0; y < size; ++y) {
+ for (var x = 0; x < size; ++x) {
+ var offset = (y * size + x) * 4;
+ pixels[offset + 0] = x * 255 / size;
+ pixels[offset + 1] = y * 255 / size;
+ pixels[offset + 2] = x * y * 255 / (size * size);
+ pixels[offset + 3] = 255;
+ }
+ }
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 256, 0, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+ 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);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+
+ // add test colors.
+ if (false) {
+ var vertsAcross = gridRes + 1;
+ var numQuads = vertsAcross * vertsAcross;
+ var colors = new Float32Array(numQuads * 4);
+ for (var ii = 0; ii < numQuads; ++ii) {
+ var offset = ii * 4;
+ colors[offset + 0] = Math.random();
+ colors[offset + 1] = Math.random();
+ colors[offset + 2] = Math.random();
+ colors[offset + 3] = 1;
+ }
+ var colorLocation = gl.getAttribLocation(program, "color")
+ var buf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
+ gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
+ gl.enableVertexAttribArray(colorLocation);
+ gl.vertexAttribPointer(colorLocation, 3, gl.FLOAT, false, 0, 0);
+ }
+
+ var gridResLoc = gl.getUniformLocation(program, "gridRes");
+ gl.uniform1f(gridResLoc, gridRes);
+
+ assertMsg(gl.getError() == gl.NO_ERROR, "Should be no errors from setup.");
+
+ gl.enable(gl.BLEND);
+ //gl.enable(gl.CULL_FACE);
+ //gl.cullFace(gl.FRONT);
+
+ gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after drawing");
+
+ var successfullyParsed = true;
+}
+</script>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+varying vec2 v_texCoord;
+uniform float gridRes;
+
+#ifdef ADD_TEST_COLORS
+ attribute vec4 color;
+ varying vec4 v_color;
+#endif
+
+void main()
+{
+ // expand each quad to cover the entire element.
+ vec2 p = mod((vPosition.xy * 0.5 + 0.5) * gridRes, 2.0) * 2.0 - 1.0;
+ gl_Position = vec4(p, 0, 1);
+ v_texCoord = vPosition.xy;
+
+#ifdef ADD_TEST_COLORS
+ v_color = color;
+#endif
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 v_color;
+varying vec2 v_texCoord;
+uniform sampler2D tex;
+void main()
+{
+#ifdef ADD_TEST_COLORS
+ gl_FragColor = v_color;
+#else
+ gl_FragColor = texture2D(tex, v_texCoord);
+#endif
+}
+</script>
+</body>
+</html>
+
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/multisample-corruption-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/multisample-corruption-stress.html
new file mode 100644
index 0000000000..a24db37681
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/multisample-corruption-stress.html
@@ -0,0 +1,37 @@
+<!--
+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 Multisample Renderbuffer Corruption Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../jss/webgl-test-utils.js"> </script>
+<script src="../js/tests/iterable-test.js"> </script>
+</head>
+<body>
+<canvas id="example" width="2048" height="2048" style="width: 128px; height: 128px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+
+description(document.title);
+
+var wtu = WebGLTestUtils;
+
+var gl = wtu.create3DContext("example", {antialias: true, preserveDrawingBuffer: true});
+var test = IterableTest.createMultisampleCorruptionTest(gl);
+var iterations = parseInt(wtu.getUrlOptions().iterations, 10) || 100;
+IterableTest.run(test, iterations);
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/offscreen-issue.html b/dom/canvas/test/webgl-conf/checkout/extra/offscreen-issue.html
new file mode 100644
index 0000000000..b35eefbf38
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/offscreen-issue.html
@@ -0,0 +1,43 @@
+<!--
+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">
+<script type="application/javascript">
+
+window.onload = function() {
+ setup("1");
+ setup("2");
+}
+
+function setup(id) {
+ var c = document.getElementById(id);
+ var names = ["webgl", "experimental-webgl"];
+ for (var i = 0; i < names.length; ++i) {
+ gl = canvas.getContext(names[i]);
+ if (gl) {
+ break;
+ }
+ }
+ gl.clearColor(1,0,0,1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ setTimeout(function() {
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(0,0,50,50);
+ gl.clearColor(0,1,0,1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ }, 500);
+}
+</script>
+</head>
+<body>
+<canvas id="1"></canvas>
+<div style="height:4000px;">content that is tall like several articles</div>
+<canvas id="2"></canvas>
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/out-of-bounds-uniform-array-access.html b/dom/canvas/test/webgl-conf/checkout/extra/out-of-bounds-uniform-array-access.html
new file mode 100644
index 0000000000..917361e986
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/out-of-bounds-uniform-array-access.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 out of bounds uniform array access.</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 style="background: #666;">
+<div id="description"></div>
+<div id="console"></div>
+<div>elem mult: <span id="elemMultDisplay"></span></div>
+<input type="range" id="elemMult" value="4" min="0" max="2048" style="width: 100%;"/>
+<div>line width: <span id="lineWidthDisplay"></span></div>
+<input type="range" id="lineWidth" value="512" min="0" max="2540" style="width: 100%;"/>
+<canvas id="example" width="256" height="256" style="background: black;">
+</canvas>
+<script id="vshader" type="x-shader/x-vertex">
+attribute vec4 vPosition;
+varying vec4 v_color;
+uniform float lineWidth;
+uniform int elemMult;
+uniform vec4 someArray[2];
+void main()
+{
+ vec2 texcoord = vec2(vPosition.xy * 0.5 + vec2(0.5, 0.5));
+ int index = int(texcoord.x + texcoord.y * lineWidth) * elemMult;
+ v_color = someArray[index];
+ gl_Position = vPosition;
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+varying vec4 v_color;
+void main()
+{
+ gl_FragColor = v_color * vec4(1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0) + vec4(0,0,0,0.5);
+}
+</script>
+<script>
+"use strict";
+window.onload = main;
+debug("Tests a WebGL program that accesses out of bounds uniform array elements");
+
+function main() {
+ var wtu = WebGLTestUtils;
+ var gl = wtu.create3DContext("example");
+ var program = wtu.setupProgram(
+ gl,
+ ['vshader', 'fshader'],
+ ['vPosition'], [0]);
+ var gridRes = 255;
+ wtu.setupIndexedQuad(gl, gridRes, 0);
+ var lineWidthLoc = gl.getUniformLocation(program, "lineWidth");
+ var elemMultLoc = gl.getUniformLocation(program, "elemMult");
+ assertMsg(gl.getError() == gl.NO_ERROR, "Should be no errors from setup.");
+
+ var lineWidth = 512;
+ var lineWidthElem = document.getElementById("lineWidth");
+ var lineWidthDisplayElem = document.getElementById("lineWidthDisplay");
+
+ lineWidthElem.value = lineWidth;
+
+ lineWidthElem.addEventListener('change', function(event) {
+ //console.log(event.target.value);
+ lineWidth = event.target.value;
+ draw();
+ }, false);
+
+ var elemMult = 4;
+ var elemMultElem = document.getElementById("elemMult");
+ var elemMultDisplayElem = document.getElementById("elemMultDisplay");
+
+ elemMultElem.value = elemMult;
+
+ elemMultElem.addEventListener('change', function(event) {
+ //console.log(event.target.value);
+ elemMult = event.target.value;
+ draw();
+ }, false);
+
+ draw();
+
+ function draw() {
+ lineWidthDisplayElem.innerText = lineWidth;
+ elemMultDisplayElem.innerText = elemMult;
+ gl.uniform1f(lineWidthLoc, lineWidth);
+ gl.uniform1i(elemMultLoc, elemMult);
+ gl.drawElements(gl.TRIANGLES, gridRes * gridRes * 6, gl.UNSIGNED_SHORT, 0);
+ }
+
+ var successfullyParsed = true;
+}
+
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/out-of-memory.html b/dom/canvas/test/webgl-conf/checkout/extra/out-of-memory.html
new file mode 100644
index 0000000000..e02984d44c
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/out-of-memory.html
@@ -0,0 +1,84 @@
+<!--
+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 Out Of Memory Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<script>
+"use strict";
+debug("This tests WebGL running out of memory.");
+
+debug("");
+debug("Canvas.getContext");
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+if (!gl) {
+ testFailed("context does not exist");
+} else {
+ testPassed("context exists");
+
+ debug("");
+ debug("Allocating shaders.");
+
+ function makeBigShader() {
+ var lines = [];
+ var line = "// ";
+ for (var ii = 0; ii < 1024; ++ii) {
+ line += String.fromCharCode(48 + ii % 10);
+ }
+ for (var ii = 0; ii < 1024; ++ii) {
+ lines[ii] = line;
+ }
+ var oneMB = lines.join();
+ for (var ii = 0; ii < 64; ++ii) {
+ lines[ii] = oneMB;
+ }
+ return lines.join("\n");
+ }
+
+ var shaderSource = makeBigShader();
+ debug("created " + Math.floor(shaderSource.length / 1024 / 1024) + "MB shader");
+
+ var intervalId;
+ var count = 0;
+
+ function makeShader() {
+ ++count;
+ debug ("creating shader #" + count + " mem = " + Math.floor(shaderSource.length * count / 1024 / 1024) + "MB");
+ var shader = gl.createShader(gl.VERTEX_SHADER);
+ if (shader == null) {
+ window.clearInterval(intervalId);
+ testPassed("createShader returns null"); // not sure this is a passing
+ finishTest();
+ } else {
+ gl.shaderSource(shader, shaderSource);
+ var err = gl.getError();
+ if (err != gl.NO_ERROR) {
+ window.clearInterval(intervalId);
+ assertMsg(err == gl.OUT_OF_MEMORY, "shaderSource returns OUT_OF_MEMORY");
+ finishTest();
+ }
+ }
+ }
+
+ intervalId = window.setInterval(makeShader, 1000/15);
+}
+</script>
+<script src="../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/out-of-resources.html b/dom/canvas/test/webgl-conf/checkout/extra/out-of-resources.html
new file mode 100644
index 0000000000..8e88c1ebfd
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/out-of-resources.html
@@ -0,0 +1,120 @@
+<!--
+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 Out Of Resources Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../../devtools/src/debug/webgl-debug.js"></script>
+<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>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<canvas id="canvas2" width="2" height="2"> </canvas>
+<script>
+"use strict";
+wtu = WebGLTestUtils;
+window.onload = init;
+debug("Tests a WebGL program that tries to use all of vram.");
+
+function init() {
+ if (confirm(
+ "After clicking OK your machine may become unresponsive or crash.")) {
+ main();
+ } else {
+ debug("cancelled");
+ }
+}
+
+function main() {
+ debug("");
+ debug("Canvas.getContext");
+
+ var gl = wtu.create3DContext("canvas");
+ if (!gl) {
+ testFailed("context does not exist");
+ } else {
+ testPassed("context exists");
+
+ var program = wtu.setupColorQuad(gl);
+ gl.useProgram(program);
+
+ debug("");
+ debug("Checking for out of memory handling.");
+
+ var size = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
+ debug("max render buffer size: " + size);
+
+ var allocateFramebuffers = true;
+ var itervalId;
+ var count = 0;
+
+ gl = WebGLDebugUtils.makeDebugContext(gl, function(err, functionName, args) {
+ assertMsg(err == gl.OUT_OF_MEMORY,
+ "correctly returns gl.OUT_OF_MEMORY when out of memory");
+ stop("got: " + wtu.glEnumToString(gl, err));
+ });
+
+ var fbos = [];
+
+ intervalId = window.setInterval(function() {
+ ++count;
+ var mem = count * size * size * 4;
+ debug("#" + count + " : memory allocated so far " + (mem / 1024 / 1024) + "MB");
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ 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);
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ size, // width
+ size, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ null); // data
+ var err = gl.getError();
+ if (err != gl.NO_ERROR) {
+ stop("got error: " + wtu.glEnumToString(gl, err));
+ return;
+ }
+ if (allocateFramebuffers) {
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ gl.framebufferTexture2D(
+ gl.FRAMEBUFFER,
+ gl.COLOR_ATTACHMENT0,
+ gl.TEXTURE_2D,
+ tex,
+ 0);
+ var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
+ if (status != gl.FRAMEBUFFER_COMPLETE) {
+ stop("got: " + wtu.glEnumToString(gl, status));
+ return;
+ }
+ // use the framebuffer
+ wtu.drawFloatColorQuad(gl, [1, Math.random(), 1, 1]);
+ }
+ }, 200);
+
+ function stop(msg) {
+ debug(msg);
+ window.clearInterval(intervalId);
+ finishTest();
+ }
+ }
+}
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/out-of-vram.html b/dom/canvas/test/webgl-conf/checkout/extra/out-of-vram.html
new file mode 100644
index 0000000000..02f82949f7
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/out-of-vram.html
@@ -0,0 +1,114 @@
+<!--
+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 Out Of VRAM Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<canvas id="canvas" width="2" height="2"> </canvas>
+<script>
+"use strict";
+debug("This tests WebGL running out of vram.");
+
+debug("");
+debug("Canvas.getContext");
+
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("canvas");
+try {
+ var gl = wtu.create3DContext(canvas);
+} catch(e) {
+}
+if (!gl) {
+ testFailed("could not create context");
+} else {
+ testPassed("context exists");
+
+ var args = wtu.getUrlArguments();
+
+ canvas.addEventListener('webglcontextlost', contextLost, false);
+
+ function contextLost(e) {
+ e.preventDefault();
+ debug("***context lost***");
+ }
+
+ function contextRestored(e) {
+ debug("***context restored***");
+ }
+
+ var program = wtu.setupTexturedQuad(gl);
+ gl.useProgram(program);
+
+ debug("");
+ debug("Allocating textures.");
+
+ var intervalId;
+ var count = 0;
+ var textureMem = 0;
+ var textures = [];
+ var size = 2048;
+ var limit = (args.limit ? args.limit : 8192) * 1024 * 1024;
+
+ debug("limit: " + InMB(limit))
+
+ function InMB(v) {
+ return "" + Math.floor(v / 1024 / 1024) + "MB";
+ }
+
+ function makeTexture() {
+ if (gl.isContextLost()) {
+ stop("out of memory");
+ return;
+ }
+ ++count;
+ textureMem += size * size * 4;
+ if (textureMem > limit) {
+ stop("reached limit");
+ return;
+ }
+ debug ("creating texture #" + count + " mem = " + InMB(textureMem));
+ var texture = gl.createTexture();
+ textures.push(texture);
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(gl.TEXTURE_2D,
+ 0, // level
+ gl.RGBA, // internalFormat
+ size, // width
+ size, // height
+ 0, // border
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ null); // data
+ var err = gl.getError();
+ if (err != gl.NO_ERROR) {
+ stop("got error: " + wtu.glEnumToString(gl, err));
+ return;
+ }
+ // use the texture
+ wtu.clearAndDrawUnitQuad(gl);
+ }
+
+ intervalId = window.setInterval(makeTexture, 1000 / 15);
+
+}
+
+function stop(msg) {
+ window.clearInterval(intervalId);
+ testPassed(msg);
+ finishTest();
+}
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/point-no-attributes-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/point-no-attributes-stress.html
new file mode 100644
index 0000000000..f567264907
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/point-no-attributes-stress.html
@@ -0,0 +1,61 @@
+<!--
+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>
+<script id="vshader" type="x-shader/x-vertex">
+void main()
+{
+ gl_PointSize = 1.0;
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
+}
+</script>
+
+<script id="fshader" type="x-shader/x-fragment">
+precision mediump float;
+
+void main()
+{
+ gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
+}
+</script>
+</head>
+<body>
+<canvas id="testbed" width="1" height="1" style="width: 100px; height: 100px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+description('Stress test drawing a huge number of points without enabling any attributes');
+
+debug('This test stresses drawing a large number of points and workarounds created to run WebGL with simulated attrib 0');
+
+if (confirm('After clicking OK your machine may become unresponsive or crash.')) {
+ var wtu = WebGLTestUtils;
+ var gl = wtu.create3DContext('testbed');
+
+ var program1 = wtu.setupProgram(gl, ['vshader', 'fshader']);
+
+ gl.enable(gl.BLEND);
+
+ debug('Draw 2^31 points with a shader that takes no attributes and verify it fills the whole canvas.');
+
+ gl.drawArrays(gl.POINTS, 0, Math.pow(2, 31) - 1);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR);
+ wtu.checkCanvas(gl, [0, 255, 0, 255]);
+}
+
+var successfullyParsed = true;
+</script>
+<script src="../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/program-test-1.html b/dom/canvas/test/webgl-conf/checkout/extra/program-test-1.html
new file mode 100644
index 0000000000..e7cee66c9a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/program-test-1.html
@@ -0,0 +1,80 @@
+<!--
+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">
+<!-- This is a visual test that programs must have both a vertex and
+ fragment shader attached; the fixed function pipeline on the
+ desktop must remain disabled. -->
+<script type="application/javascript">
+function log() {
+ var s = "";
+ for (var i = 0; i < arguments.length; ++i) {
+ s += arguments[i] + " ";
+ }
+ document.getElementById("log").innerHTML += s + "<br>";
+}
+
+function go() {
+ var gl = document.getElementById("c").getContext("experimental-webgl");
+
+ gl.clearColor(0.0, 0.0, 0.0, 0.0);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ var vs = gl.createShader(gl.VERTEX_SHADER);
+ gl.shaderSource(vs, "attribute vec4 aVertex; attribute vec4 aColor; varying vec4 vColor; void main() { vColor = aColor; gl_Position = aVertex; }");
+ gl.compileShader(vs);
+
+ var fs = gl.createShader(gl.FRAGMENT_SHADER);
+ gl.shaderSource(fs, "precision mediump float; varying vec4 vColor; void main() { gl_FragColor = vColor; }");
+ gl.compileShader(fs);
+
+ var prog = gl.createProgram();
+ gl.attachShader(prog, vs);
+ // don't attach a fragment shader -- may use fixed pipeline on desktop if the implementation doesn't check!
+ //gl.attachShader(prog, fs);
+
+ gl.bindAttribLocation(prog, 0, "aVertex");
+ gl.bindAttribLocation(prog, 1, "aColor");
+
+ gl.linkProgram(prog);
+
+ var vbuf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ -1.0, -1.0, 0.0, 1.0,
+ -1.0, 1.0, 0.0, 1.0,
+ 1.0, -1.0, 0.0, 1.0,
+ 1.0, 1.0, 0.0, 1.0]), gl.STATIC_DRAW);
+ gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 0, 0);
+
+ var cbuf = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, cbuf);
+ gl.bufferData(gl.ARRAY_BUFFER, new Uint8Array([255, 0, 0,
+ 0, 255, 0,
+ 0, 0, 255,
+ 255, 255, 0]), gl.STATIC_DRAW);
+ gl.vertexAttribPointer(1, 3, gl.UNSIGNED_BYTE, false, 0, 0);
+
+ gl.enableVertexAttribArray(0);
+ gl.enableVertexAttribArray(1);
+
+ gl.useProgram(prog);
+
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
+
+ log("glError", "0x" + gl.getError().toString(16));
+}
+</script>
+</head>
+
+<body onload="go()">
+<p>Should be green in the rectangle below:</p>
+<canvas style="background: green;" id="c"></canvas>
+<div id="log"></div>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/readpixels-after-alert.html b/dom/canvas/test/webgl-conf/checkout/extra/readpixels-after-alert.html
new file mode 100644
index 0000000000..0fbb9cb159
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/readpixels-after-alert.html
@@ -0,0 +1,65 @@
+<!--
+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 Behavior After Alert and Read Pixels Test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../../devtools/src/debug/webgl-debug.js"></script>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<canvas id="canvas" width="100" height="100"> </canvas>
+<div id="description">Verify WebGL behavior after an alert and then a readPixels()</div>
+<div id="console"></div>
+<script>
+"use strict";
+
+function checkPixels(buf, width, height, thresh) {
+ for (var yy = 0; yy < height; ++yy) {
+ for (var xx = 0; xx < width; ++xx) {
+ var offset = (yy * width + xx) * 4;
+ if (Math.abs(buf[offset] - 255) > thresh ||
+ Math.abs(buf[offset + 1] - 0) > thresh ||
+ Math.abs(buf[offset + 2] - 0) > thresh) {
+ testFailed("Checking pixels");
+ return false;
+ }
+ }
+ }
+ testPassed("Checking pixels");
+ return true;
+}
+
+var wtu = WebGLTestUtils;
+var gl = wtu.create3DContext("canvas");
+gl.clearColor(1, 0, 0, 1);
+gl.clear(gl.COLOR_BUFFER_BIT);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup should succeed");
+
+wtu.checkCanvas(gl, [255, 0, 0, 255], undefined, 3);
+
+alert("Click me to continue");
+
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "setup should succeed");
+wtu.checkCanvas(gl, [255, 0, 0, 255], undefined, 3);
+
+alert("Click me to continue");
+
+gl.clearColor(1, 0, 0, 1);
+gl.clear(gl.COLOR_BUFFER_BIT);
+wtu.checkCanvas(gl, [255, 0, 0, 255], undefined, 3);
+
+debug("");
+var successfullyParsed = true;
+</script>
+<script src="../js/js-test-post.js"></script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample-100.png b/dom/canvas/test/webgl-conf/checkout/extra/sample-100.png
new file mode 100644
index 0000000000..197c869b6d
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-100.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png b/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png
new file mode 100644
index 0000000000..89f2b782a5
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png b/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png
new file mode 100644
index 0000000000..aaee3ce0dc
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png
Binary files differ
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample.svg b/dom/canvas/test/webgl-conf/checkout/extra/sample.svg
new file mode 100644
index 0000000000..dfca524023
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/sample.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+ <rect width="100" height="100" fill="red"/>
+ <circle cx="50" cy="50" r="50" fill="blue"/>
+</svg>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/simulated-attrib-0-bug-test.html b/dom/canvas/test/webgl-conf/checkout/extra/simulated-attrib-0-bug-test.html
new file mode 100644
index 0000000000..83564e824a
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/simulated-attrib-0-bug-test.html
@@ -0,0 +1,36 @@
+<!--
+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>Simulated Attrib 0 Bug Test</title>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+</head>
+<body>
+<div>Simulated Attrib 0 Bug Test</div>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+var wtu = WebGLTestUtils;
+var context = wtu.create3DContext();
+var program = wtu.loadStandardProgram(context);
+
+context.useProgram(program);
+debug("This line attempts to draw with 0x7FFFFFFF triangles with attrib 0 off");
+debug("A buggy simulated attrib 0 impl will probably mult 0x7FFFFFFF * sizeof(vec4)");
+shouldBe("context.drawArrays(context.TRIANGLES, 0, 0x7fffffff);", "undefined");
+debug("This line attempts to draw with 0x7ffffff triangles.");
+debug("A buggy simulated attrib 0 impl will probably pass 0x7FFFFFF0 to glBufferData and then not check that it failed");
+shouldBe("context.drawArrays(context.TRIANGLES, 0, 0x7ffffff);", "undefined");
+debug("")
+debug("NOTE!: You may not see any manifestation here. Check your impl for these issues");
+</script>
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/slow-shader-example.html b/dom/canvas/test/webgl-conf/checkout/extra/slow-shader-example.html
new file mode 100644
index 0000000000..2397bbbec8
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/slow-shader-example.html
@@ -0,0 +1,112 @@
+<!--
+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 Slow Shader example.</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"> </script>
+</head>
+<body>
+<canvas id="example" width="1024" height="1024" style="width: 40px; height: 40px;">
+</canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script id="slow" type="text/something-not-javascript">
+precision mediump float;
+uniform sampler2D tex;
+varying vec2 texCoord;
+void main() {
+ gl_FragColor = texture2D(tex, texture2D(tex, texture2D(tex, texCoord).xy).xy);
+}
+</script>
+<script>
+"use strict";
+window.onload = main;
+
+debug("Tests drawing a very slow shader.");
+var wtu = WebGLTestUtils;
+var canvas = document.getElementById("example");
+canvas.addEventListener("webglcontextlost", function(e) { e.preventDefault(); }, false);
+canvas.addEventListener("webglcontextrestored", function(e) { }, false);
+var gl = wtu.create3DContext(canvas);
+var maxTexSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
+var texSize = Math.min(maxTexSize, 4096);
+debug("Max Texture size: " + maxTexSize);
+debug("Texture size: " + texSize);
+var shaderSource =
+ document.getElementById("slow").text.replace(/\$size/g, texSize + ".0");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after getting a context");
+var program = wtu.setupProgram(
+ gl,
+ [wtu.simpleTextureVertexShader, shaderSource],
+ ['vPosition', 'texCoord0'],
+ [0, 1]);
+wtu.setupUnitQuad(gl, 0, 1);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after program setup");
+var tex = gl.createTexture();
+gl.enable(gl.BLEND);
+gl.disable(gl.DEPTH_TEST);
+
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after creating texture");
+debug("preparing...");
+var numBytes = texSize * texSize * 4;
+var pixelBuf = new ArrayBuffer(numBytes);
+var pixels = new Uint8Array(pixelBuf);
+for (var ii = 0; ii < numBytes; ++ii) {
+ pixels[ii] = Math.random() * 255;
+}
+gl.bindTexture(gl.TEXTURE_2D, tex);
+gl.texImage2D(
+ gl.TEXTURE_2D, 0, gl.RGBA, texSize, texSize, 0,
+ gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after texture setup");
+
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after texture param setting");
+
+var loc = gl.getUniformLocation(program, "tex");
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after getting tex location");
+gl.uniform1i(loc, 0);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after setting tex uniform");
+
+var numQuads = 1000;
+var indexBuf = new ArrayBuffer(numQuads * 6);
+var indices = new Uint8Array(indexBuf);
+for (var ii = 0; ii < numQuads; ++ii) {
+ var offset = ii * 6;
+ indices[offset + 0] = 0;
+ indices[offset + 1] = 1;
+ indices[offset + 2] = 2;
+ indices[offset + 3] = 3;
+ indices[offset + 4] = 4;
+ indices[offset + 5] = 5;
+}
+var indexBuffer = gl.createBuffer();
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
+wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after setting up indices");
+
+function main () {
+ if (confirm(
+ "After clicking OK your machine may become unresponsive or crash.")) {
+ gl.drawElements(gl.TRIANGLES, numQuads * 6, gl.UNSIGNED_BYTE, 0);
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after drawing");
+ } else {
+ debug("cancelled");
+ }
+}
+
+var successfullyParsed = true;
+</script>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.html b/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.html
new file mode 100644
index 0000000000..3c25b7c985
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.html
@@ -0,0 +1,27 @@
+<!--
+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>
+<script src="tex-image-with-video-test.js"></script>
+<script>
+"use strict";
+function testPrologue(gl) {
+ return true;
+}
+</script>
+</head>
+<body onload='generateTest("RGBA", "UNSIGNED_BYTE", testPrologue)()'>
+<canvas id="example" width="32" height="32"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.js b/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.js
new file mode 100644
index 0000000000..01f56b56c3
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/tex-image-with-video-test.js
@@ -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.
+*/
+
+// This block needs to be outside the onload handler in order for this
+// test to run reliably in WebKit's test harness (at least the
+// Chromium port). https://bugs.webkit.org/show_bug.cgi?id=87448
+initTestingHarness();
+
+var old = debug;
+var debug = function(msg) {
+ console.log(msg);
+ old(msg);
+};
+
+function generateTest(pixelFormat, pixelType, prologue) {
+ var wtu = WebGLTestUtils;
+ var gl = null;
+ var textureLoc = null;
+ var successfullyParsed = false;
+
+ // Test each format separately because many browsers implement each
+ // differently. Some might be GPU accelerated, some might not. Etc...
+ var videos = [
+ { src: "../resources/red-green.mp4" , type: 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"', },
+ { src: "../resources/red-green.webmvp8.webm", type: 'video/webm; codecs="vp8, vorbis"', },
+ { src: "../resources/red-green.webmvp9.webm", type: 'video/webm; codecs="vp9"', },
+ { src: "../resources/red-green.theora.ogv", type: 'video/ogg; codecs="theora, vorbis"', },
+ ];
+
+ var videoNdx = 0;
+ var video;
+
+ function runNextVideo() {
+ if (video) {
+ video.pause();
+ }
+
+ if (videoNdx == videos.length) {
+ finishTest();
+ return;
+ }
+
+ var info = videos[videoNdx++];
+ debug("");
+ debug("testing: " + info.type);
+ video = document.createElement("video");
+ video.muted = true;
+ var canPlay = true;
+ if (!video.canPlayType) {
+ testFailed("video.canPlayType required method missing");
+ runNextVideo();
+ return;
+ }
+
+ if(!video.canPlayType(info.type).replace(/no/, '')) {
+ debug(info.type + " unsupported");
+ runNextVideo();
+ return;
+ };
+
+ document.body.appendChild(video);
+ video.type = info.type;
+ video.crossOrigin = 'anonymous';
+ video.src = info.src;
+ wtu.startPlayingAndWaitForVideo(video, runTest);
+ }
+
+ var init = function()
+ {
+ description('Verify texImage2D and texSubImage2D code paths taking video elements (' + pixelFormat + '/' + pixelType + ')');
+
+ gl = wtu.create3DContext("example");
+
+ if (!prologue(gl)) {
+ finishTest();
+ return;
+ }
+
+ var program = wtu.setupTexturedQuad(gl);
+
+ gl.clearColor(0,0,0,1);
+ gl.clearDepth(1);
+
+ textureLoc = gl.getUniformLocation(program, "tex");
+ runNextVideo();
+ }
+
+ function runOneIteration(videoElement, useTexSubImage2D, flipY, topColor, bottomColor)
+ {
+ debug('Testing ' + (useTexSubImage2D ? 'texSubImage2D' : 'texImage2D') +
+ ' with flipY=' + flipY);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ // Disable any writes to the alpha channel
+ gl.colorMask(1, 1, 1, 0);
+ var texture = gl.createTexture();
+ // Bind the texture to texture unit 0
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ // Set up texture parameters
+ 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);
+ // Set up pixel store parameters
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
+ // Upload the videoElement into the texture
+ if (useTexSubImage2D) {
+ // Initialize the texture to black first
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl[pixelFormat],
+ videoElement.videoWidth, videoElement.videoHeight, 0,
+ gl[pixelFormat], gl[pixelType], null);
+ gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl[pixelFormat], gl[pixelType], videoElement);
+ } else {
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl[pixelFormat], gl[pixelFormat], gl[pixelType], videoElement);
+ }
+
+ var c = document.createElement("canvas");
+ c.width = 16;
+ c.height = 16;
+ c.style.border = "1px solid black";
+ var ctx = c.getContext("2d");
+ ctx.drawImage(videoElement, 0, 0, 16, 16);
+ document.body.appendChild(c);
+
+ // Point the uniform sampler to texture unit 0
+ gl.uniform1i(textureLoc, 0);
+ // Draw the triangles
+ wtu.clearAndDrawUnitQuad(gl, [0, 0, 0, 255]);
+ // Check a few pixels near the top and bottom and make sure they have
+ // the right color.
+ var tolerance = 5;
+ debug("Checking lower left corner");
+ wtu.checkCanvasRect(gl, 4, 4, 2, 2, bottomColor,
+ "shouldBe " + bottomColor, tolerance);
+ debug("Checking upper left corner");
+ wtu.checkCanvasRect(gl, 4, gl.canvas.height - 8, 2, 2, topColor,
+ "shouldBe " + topColor, tolerance);
+ }
+
+ function runTest(videoElement)
+ {
+ var red = [255, 0, 0];
+ var green = [0, 255, 0];
+ runOneIteration(videoElement, false, true, red, green);
+ runOneIteration(videoElement, false, false, green, red);
+ runOneIteration(videoElement, true, true, red, green);
+ runOneIteration(videoElement, true, false, green, red);
+
+ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
+
+ runNextVideo();
+ }
+
+ return init;
+}
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/texture-allocation-stress-test.html b/dom/canvas/test/webgl-conf/checkout/extra/texture-allocation-stress-test.html
new file mode 100644
index 0000000000..e27628b621
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/texture-allocation-stress-test.html
@@ -0,0 +1,47 @@
+<!--
+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>Tests that resources allocated by a WebGL context are freed in a reasonable timeframe.</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>
+<canvas id="canvas" width="32" height="32">
+</canvas>
+<script>
+"use strict";
+description();
+
+debug("Creates a WebGL context and textures consuming ~80 MB of video memory, then reloads the page and does it over again.")
+debug("GPU memory usage should be capped. It should not grow unboundedly.")
+
+var wtu = WebGLTestUtils;
+
+var gl = wtu.create3DContext(document.getElementById("canvas"));
+var textures = [];
+
+for (var ii = 0; ii < 20; ++ii) {
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+ null);
+ textures.push(tex);
+}
+
+setTimeout(function() { debug("Reloading..."); window.location.reload(); }, 500);
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/texture-from-camera-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/texture-from-camera-stress.html
new file mode 100644
index 0000000000..0a9816ae07
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/texture-from-camera-stress.html
@@ -0,0 +1,79 @@
+<!--
+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>Stresses the camera-to-texture upload path.</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>
+<canvas id="canvas" width="640" height="360">
+</canvas>
+<script>
+"use strict";
+description();
+
+debug("Repeatedly uploads from a camera video element to a texture, many times per frame.")
+
+const wtu = WebGLTestUtils;
+const gl = wtu.create3DContext(document.getElementById('canvas'));
+const video = document.createElement('video');
+video.src = '';
+video.loop = false;
+video.muted = true;
+video.setAttribute('playsinline', '');
+
+const program = wtu.setupTexturedQuad(gl);
+const textureLoc = gl.getUniformLocation(program, "tex");
+gl.uniform1i(textureLoc, 0);
+
+const 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);
+gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
+
+navigator.mediaDevices.getUserMedia({
+ audio: false,
+ video: {
+ facingMode: 'environment',
+ width: 1280,
+ height: 720,
+ }
+ })
+ .then(mediaStream => {
+ video.srcObject = mediaStream;
+ video.onloadedmetadata = () => {
+ wtu.startPlayingAndWaitForVideo(video, startTestLoop);
+ };
+ });
+
+const urlOptions = wtu.getUrlOptions();
+const uploadsPerFrame = urlOptions.uploadsPerFrame ? urlOptions.uploadsPerFrame : 400;
+debug('');
+debug(`Testing with ${uploadsPerFrame} uploads per frame`);
+
+function startTestLoop() {
+ requestAnimationFrame(startTestLoop);
+ for (let i = 0; i < uploadsPerFrame; ++i) {
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, video);
+ }
+ wtu.clearAndDrawUnitQuad(gl);
+}
+
+var successfullyParsed = true;
+</script>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/texture-sizing.html b/dom/canvas/test/webgl-conf/checkout/extra/texture-sizing.html
new file mode 100644
index 0000000000..efbb65fea7
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/texture-sizing.html
@@ -0,0 +1,229 @@
+<!--
+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>
+<title>Testing resolution of texture uploads</title>
+<style>
+
+.result {
+ margin-bottom: 20px;
+ display: -webkit-flex;
+ display: flex;
+ -webkit-flex-direction: row;
+ flex-direction: row;
+}
+
+.description, .element, .render, .log {
+ border: 1px solid black;
+ margin: 5px;
+ width: 200px;
+ height: 200px;
+ max-width: 200px;
+ max-height: 200px;
+}
+
+.render canvas {
+ width: 200px;
+ height: 200px;
+}
+
+p {
+ margin: 0;
+ padding: 5px 10px;
+}
+.viacss {
+ width: 200px;
+ height: 200px;
+}
+</style>
+<script src="../js/webgl-test-utils.js"> </script>
+<script>
+
+var outOfPageSVG = new Image();
+outOfPageSVG.src = "sample.svg";
+
+function runTest() {
+ var wtu = WebGLTestUtils;
+ var results = document.querySelectorAll(".result");
+ for (var i = 0; i < results.length; i++) {
+ var result = results[i];
+ var img = result.querySelector("img");
+ if (result.classList.contains("out-of-page")) {
+ img = outOfPageSVG;
+ }
+ if (result.classList.contains("set-dimensions")) {
+ img.width = 200;
+ img.height = 200;
+ }
+ var out = result.querySelector(".output");
+ out.innerHTML = "img.width = " + img.width + "<br>img.height = " + img.height + "<br>img.naturalWidth = " + img.naturalWidth + "<br>img.naturalHeight = " + img.naturalHeight;
+
+ var canvas = document.createElement("canvas");
+ canvas.width = 200 * window.devicePixelRatio;
+ canvas.height = 200 * window.devicePixelRatio;
+ result.querySelector(".render").appendChild(canvas);
+ var gl = wtu.create3DContext(canvas);
+ gl.enable(gl.BLEND);
+ gl.disable(gl.DEPTH_TEST);
+
+ var program = wtu.setupSimpleTextureProgram(gl, 0, 1);
+ var buffers = wtu.setupUnitQuad(gl, 0, 1);
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+ 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);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
+ var loc = gl.getUniformLocation(program, "tex");
+ gl.uniform1i(loc, 0);
+
+ wtu.clearAndDrawUnitQuad(gl, [0, 255, 0, 255]);
+ }
+}
+
+window.addEventListener("load", function () {
+ runTest();
+}, false);
+</script>
+</head>
+<body>
+<div class="result">
+ <div class="description">
+ <p>IMG to SVG with 200x200 attributes</p>
+ </div>
+ <div class="element">
+ <img src="sample.svg" width="200" height="200">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG to 100x100 PNG with 200x200 attributes</p>
+ </div>
+ <div class="element">
+ <img src="sample-100.png" width="200" height="200">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG to 200x200 PNG with 200x200 attributes</p>
+ </div>
+ <div class="element">
+ <img src="sample-200.png" width="200" height="200">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG to 400x400 PNG with 200x200 attributes</p>
+ </div>
+ <div class="element">
+ <img src="sample-400.png" width="200" height="200">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG SRCSET to multiple PNGs with 200x200 attributes</p>
+ </div>
+ <div class="element">
+ <img src="sample-100.png" srcset="sample-200.png 1x, sample-400.png 2x" width="200" height="200">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG to SVG with no attributes - 200x200 sizing via CSS</p>
+ </div>
+ <div class="element">
+ <img src="sample.svg" class="viacss">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>IMG to 400x400 PNG with no attributes - 200x200 sizing via CSS</p>
+ </div>
+ <div class="element">
+ <img src="sample-400.png" class="viacss">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result">
+ <div class="description">
+ <p>
+ IMG to SVG with no attributes and no sizing via CSS<br>
+ (although the width and height of the container set a size)
+ </p>
+ </div>
+ <div class="element">
+ <img src="sample.svg">
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result out-of-page">
+ <div class="description">
+ <p>Out of page SVG with no dimensions</p>
+ </div>
+ <div class="element">
+ Not a child of document
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+<div class="result out-of-page set-dimensions">
+ <div class="description">
+ <p>Out of page SVG with 200x200 specified</p>
+ </div>
+ <div class="element">
+ Not a child of document
+ </div>
+ <div class="render">
+ </div>
+ <div class="log">
+ <p class="output"></p>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/webgl-compressed-texture-size-limit-stress.html b/dom/canvas/test/webgl-conf/checkout/extra/webgl-compressed-texture-size-limit-stress.html
new file mode 100644
index 0000000000..9f515be7ac
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/webgl-compressed-texture-size-limit-stress.html
@@ -0,0 +1,33 @@
+<!--
+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 compressed texture size limit stress test</title>
+<link rel="stylesheet" href="../resources/js-test-style.css"/>
+<script src="../js/js-test-pre.js"></script>
+<script src="../js/webgl-test-utils.js"></script>
+<script src="../js/tests/webgl-compressed-texture-size-limit.js"></script>
+</head>
+<body>
+<canvas id="example" width="32" height="32" style="width: 40px; height: 40px;"></canvas>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+"use strict";
+enableJSTestPreVerboseLogging();
+description("Checks size limit of the webgl compressed textures. Variation on the corresponding conformance test that stresses the browser with large ArrayBuffer allocations, and both valid and invalid large texture uploads.")
+
+// Use the theoretical 4 GB - 1 byte limit for ArrayBuffer allocations.
+// The maximum texture size is often so large that it will certainly cause OOM with a cube map, so limit the positive test to a somewhat smaller size, even though this is a stress test.
+runCompressedTextureSizeLimitTest(Math.pow(2, 32) - 1, 8192);
+
+var successfullyParsed = true;
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/webgl-drawelements-validation.html b/dom/canvas/test/webgl-conf/checkout/extra/webgl-drawelements-validation.html
new file mode 100644
index 0000000000..c7e11b8cec
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/webgl-drawelements-validation.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>
+<title>Micro-benchmark for WebGL drawElements index validation</title>
+<style>
+canvas {
+ border: 1px solid #000;
+}
+</style>
+<script src="../js/webgl-test-utils.js"></script>
+<script type="application/javascript">
+"use strict";
+
+var wtu = WebGLTestUtils;
+
+var totalTimeTest1 = 0;
+var totalTimeTest2 = 0;
+var totalTimeTest3 = 0;
+var iterationsLeft = 10; // How many times to run the full test.
+var indexCount = 500001; // Divisible by 3.
+
+var indices = [];
+for (var i = 0; i < indexCount - 1; ++i) {
+ indices.push(0);
+ indices.push(1);
+ indices.push(2);
+ indices.push(3);
+}
+indices.push(4);
+
+var fullIndicesArray = new Uint16Array(indices);
+
+var drawIterations = 50;
+
+var errorsCorrect = true;
+
+var log = function(msg) {
+ console.log(msg);
+ var p = document.createElement('p');
+ p.textContent = msg;
+ document.body.appendChild(p);
+};
+
+var runTestIteration = function() {
+ var canvas = document.createElement('canvas');
+ canvas.width = 10;
+ canvas.height = 10;
+ var gl = wtu.create3DContext(canvas);
+ document.body.appendChild(canvas);
+
+ var location = 0;
+ wtu.setupSimpleColorProgram(gl, location);
+
+ var verts = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, verts);
+ var vertData = new Float32Array([-1, -1,
+ -1, 1,
+ 1, -1,
+ 1, 1]);
+ gl.bufferData(gl.ARRAY_BUFFER, vertData, gl.STATIC_DRAW);
+ gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);
+ gl.enableVertexAttribArray(location);
+
+ var indexBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+ gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, fullIndicesArray, gl.DYNAMIC_DRAW);
+ gl.finish();
+
+ var measureTime = function(f) {
+ var startTime = new Date().getTime();
+ f();
+ var error = gl.getError();
+ var endTime = new Date().getTime();
+ errorsCorrect = errorsCorrect && error == gl.INVALID_OPERATION;
+ return endTime - startTime;
+ };
+
+ // The buffer has at least one out-of-range index from the start,
+ // so only validation will happen, not drawing.
+
+ totalTimeTest1 += measureTime(function() {
+ for (var i = 0; i < drawIterations; ++i) {
+ // Change all data, which will cause complete revalidation of the index buffer.
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, fullIndicesArray);
+ gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
+ }
+ });
+
+ totalTimeTest2 += measureTime(function() {
+ for (var i = 0; i < drawIterations; ++i) {
+ // Change only one index and vary the amount of referenced indices.
+ // These should not be a big problem to a smart implementation.
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, Math.floor(indices.length / 2), new Uint16Array([i + 5]));
+ gl.drawElements(gl.TRIANGLES, indices.length - i * 3, gl.UNSIGNED_SHORT, 0);
+ }
+ });
+
+ totalTimeTest3 += measureTime(function() {
+ for (var i = 0; i < drawIterations; ++i) {
+ // Change data at two indices to cause completely revalidating the index buffer in
+ // current implementations in Chrome and Firefox (as of March 17th 2014).
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, 0, new Uint16Array([i + 5]));
+ gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, indices.length - 1, new Uint16Array([i + 5]));
+ gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
+ }
+ });
+
+ setTimeout(function() {
+ var lose = gl.getExtension('WEBGL_lose_context');
+ lose.loseContext();
+ }, 40);
+};
+
+var runTest = function() {
+ if (iterationsLeft > 0) {
+ runTestIteration();
+ --iterationsLeft;
+ setTimeout(runTest, 500);
+ } else {
+ log("Validation returned correct results: " + errorsCorrect);
+ log('1. Time spent on full buffer updates: ' + totalTimeTest1 + ' ms');
+ log('Indices uploaded and referenced by draw calls processed: ' + Math.round(indices.length * drawIterations / totalTimeTest1) + ' / ms');
+ log('2. Time spent on validating single index updates while range referenced also changes on every draw call: ' + totalTimeTest2 + ' ms');
+ log('Indices referenced by draw calls handled: ' + Math.round(indices.length * drawIterations / totalTimeTest2) + ' / ms');
+ log('3. Time spent on validating single index updates at each end of the buffer (worst case for Firefox implementation as of March 2014, not reflective of real world performance): ' + totalTimeTest3 + ' ms');
+ log('Indices referenced by draw calls handled: ' + Math.round(indices.length * drawIterations / totalTimeTest3) + ' / ms');
+ }
+};
+</script>
+</head>
+<body onload="setTimeout(runTest, 100)">
+<h1>Micro-benchmark for WebGL drawElements index validation</h1>
+<p>Note that these test cases are completely artificial, and their results only very rough indicators of the performance of a specific part of the system.</p>
+<p>The benchmark does not perform any drawing, but rather measures the time the browser takes to upload indices and to check if there are out-of-range indices.</p>
+</body>
+</html>
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/webgl-info.html b/dom/canvas/test/webgl-conf/checkout/extra/webgl-info.html
new file mode 100644
index 0000000000..9929edb178
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/webgl-info.html
@@ -0,0 +1,275 @@
+<!--
+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 Information</title>
+<style type="text/css">
+th, td {
+ padding: 4pt;
+}
+th {
+ text-align: right;
+ min-width: 22em;
+}
+</style>
+<script src="../js/webgl-test-utils.js"> </script>
+<script>
+"use strict";
+window.onload = main;
+
+function createCell(txt, isTh) {
+ var str = txt.toString();
+ if (typeof txt != 'string') {
+ if (txt.length !== undefined) {
+ str = "";
+ for (var ii = 0; ii < txt.length; ++ii) {
+ str += (ii == 0 ? "" : ", ") + txt[ii];
+ }
+ }
+ }
+ var t = document.createTextNode(str);
+ var d = document.createElement("div");
+ var td;
+ if (isTh) {
+ td = document.createElement("th");
+ } else {
+ td = document.createElement("td");
+ }
+ d.appendChild(t);
+ td.appendChild(d);
+ return td;
+}
+
+function createRow(values) {
+ var tr = document.createElement("tr");
+ for (var i = 0; i < values.length; ++i) {
+ var td = createCell(values[i], i == 0);
+ tr.appendChild(td);
+ }
+ return tr;
+}
+
+function main() {
+ var wtu = WebGLTestUtils;
+
+ var canvas = document.getElementById("example");
+ var gl = wtu.create3DContext(canvas);
+ if (!gl) {
+ return;
+ }
+
+ var debugRendererInfoRows = function() {
+ var rows = [];
+ var debugExt = wtu.getExtensionWithKnownPrefixes(gl, 'WEBGL_debug_renderer_info');
+ if (debugExt) {
+ var extPnames = [
+ 'UNMASKED_VENDOR_WEBGL',
+ 'UNMASKED_RENDERER_WEBGL'
+ ];
+ for (var ii = 0; ii < extPnames.length; ++ii) {
+ var pname = extPnames[ii];
+ var value = gl.getParameter(debugExt[pname]);
+ rows.push([pname, value]);
+ }
+ }
+ return rows;
+ };
+
+ var precisionRows = function() {
+ var rows = [];
+
+ var addPrecisionRow = function(shaderType, precision) {
+ var typeStr = shaderType === gl.FRAGMENT_SHADER ? 'fragment' : 'vertex';
+ var precisionStr = 'highp';
+ if (precision == gl.MEDIUM_FLOAT) {
+ precisionStr = 'mediump';
+ } else if (precision == gl.LOW_FLOAT) {
+ precisionStr = 'lowp';
+ }
+ rows.push([typeStr + ' shader ' + precisionStr + ' float', gl.getShaderPrecisionFormat(shaderType, precision).precision + ' mantissa bits']);
+ };
+
+ var fSource = 'precision highp float; uniform float r; void main() { gl_FragColor = vec4(r, 0.0, 0.0, 1.0); }'
+ var f = wtu.loadShader(gl, fSource, gl.FRAGMENT_SHADER);
+ if (!f) {
+ rows.push(['fragment shader highp float', 'not supported']);
+ } else {
+ addPrecisionRow(gl.FRAGMENT_SHADER, gl.HIGH_FLOAT);
+ }
+ addPrecisionRow(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
+ addPrecisionRow(gl.FRAGMENT_SHADER, gl.LOW_FLOAT);
+ addPrecisionRow(gl.VERTEX_SHADER, gl.HIGH_FLOAT);
+ addPrecisionRow(gl.VERTEX_SHADER, gl.MEDIUM_FLOAT);
+ addPrecisionRow(gl.VERTEX_SHADER, gl.LOW_FLOAT);
+ return rows;
+ };
+
+ var renderTargetRows = function() {
+ var rows = [];
+ var oesTextureFloat = wtu.getExtensionWithKnownPrefixes(gl, 'OES_texture_float');
+ var oesTextureHalfFloat = wtu.getExtensionWithKnownPrefixes(gl, 'OES_texture_half_float');
+ var formatsToTest = [
+ {
+ description: 'RGBA UNSIGNED_BYTE',
+ format: gl.RGBA,
+ type: gl.UNSIGNED_BYTE
+ },
+ {
+ description: 'RGB UNSIGNED_BYTE',
+ format: gl.RGB,
+ type: gl.UNSIGNED_BYTE
+ }
+ ];
+ if (oesTextureFloat) {
+ formatsToTest.push({
+ description: 'RGBA FLOAT',
+ format: gl.RGBA,
+ type: gl.FLOAT
+ });
+ formatsToTest.push({
+ description: 'RGB FLOAT (deprecated)',
+ format: gl.RGB,
+ type: gl.FLOAT
+ });
+ }
+ if (oesTextureHalfFloat) {
+ formatsToTest.push({
+ description: 'RGBA HALF_FLOAT_OES',
+ format: gl.RGBA,
+ type: oesTextureHalfFloat.HALF_FLOAT_OES
+ });
+ formatsToTest.push({
+ description: 'RGB HALF_FLOAT_OES',
+ format: gl.RGB,
+ type: oesTextureHalfFloat.HALF_FLOAT_OES
+ });
+ }
+ for (var ii = 0; ii < formatsToTest.length; ++ii) {
+ var fb = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+ var format = formatsToTest[ii];
+ var tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ 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);
+ gl.texImage2D(gl.TEXTURE_2D, 0, format.format, 256, 256, 0, format.format, format.type, null);
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
+ if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) {
+ rows.push([format.description, 'supported']);
+ } else {
+ rows.push([format.description, 'not supported']);
+ }
+ gl.deleteFramebuffer(fb);
+ gl.deleteTexture(tex);
+ }
+ return rows;
+ };
+
+ var info = [
+ {
+ title: 'Renderer',
+ pnames: [
+ 'VERSION',
+ 'VENDOR',
+ 'RENDERER'
+ ],
+ genRows: debugRendererInfoRows
+ },
+ {
+ title: 'Textures and Viewport',
+ pnames: [
+ 'MAX_TEXTURE_SIZE',
+ 'MAX_CUBE_MAP_TEXTURE_SIZE',
+ 'MAX_RENDERBUFFER_SIZE',
+ 'MAX_VIEWPORT_DIMS'
+ ]
+ },
+ {
+ title: 'Shader Variables',
+ pnames: [
+ 'MAX_VARYING_VECTORS',
+ 'MAX_VERTEX_ATTRIBS',
+ 'MAX_VERTEX_UNIFORM_VECTORS',
+ 'MAX_FRAGMENT_UNIFORM_VECTORS',
+ 'MAX_VERTEX_TEXTURE_IMAGE_UNITS',
+ 'MAX_TEXTURE_IMAGE_UNITS',
+ 'MAX_COMBINED_TEXTURE_IMAGE_UNITS'
+ ]
+ },
+ {
+ title: 'Shader Precision',
+ genRows: precisionRows
+ },
+ {
+ title: 'Framebuffer Texture Attachment Support',
+ genRows: renderTargetRows
+ }
+ ];
+
+ // TODO: max anisotropy, framebuffer depth bits, MSAA samples, max multiple render targets buffers, point size, line width
+
+ for (var jj = 0; jj < info.length; ++jj) {
+ var table = document.createElement("table");
+ var tb = document.createElement("tbody");
+ if (info[jj].pnames) {
+ var pnames = info[jj].pnames;
+ for (var ii = 0; ii < pnames.length; ++ii) {
+ var pname = pnames[ii];
+ var value = gl.getParameter(gl[pname]);
+ tb.appendChild(createRow([pname, value]));
+ }
+ }
+ if (info[jj].genRows) {
+ var genRows = info[jj].genRows();
+ for (var ii = 0; ii < genRows.length; ++ii) {
+ tb.appendChild(createRow(genRows[ii]));
+ }
+ }
+ table.appendChild(tb);
+ var header = document.createElement("h2");
+ header.textContent = info[jj].title;
+ document.getElementById("info").appendChild(header);
+ document.getElementById("info").appendChild(table);
+ }
+ var extensionList = document.createElement('ul');
+ var exts = gl.getSupportedExtensions();
+ var extsWithPrefixes = [];
+ while (exts.length > 0) {
+ var prefixedNames = wtu.getExtensionPrefixedNames(exts[0]);
+ var supportedPrefixedNames = [];
+ for (var ii = 0; ii < prefixedNames.length; ++ii) {
+ var index = exts.indexOf(prefixedNames[ii]);
+ if (index >= 0) {
+ supportedPrefixedNames.push(exts[index]);
+ exts.splice(index, 1);
+ }
+ }
+ extsWithPrefixes.push(supportedPrefixedNames.join(" / "));
+ }
+ extsWithPrefixes.sort();
+ for (var ii = 0; ii < extsWithPrefixes.length; ++ii) {
+ var li = document.createElement('li');
+ li.appendChild(document.createTextNode(extsWithPrefixes[ii]));
+ extensionList.appendChild(li);
+ }
+ document.getElementById('extensions').appendChild(extensionList);
+}
+</script>
+</head>
+<body>
+<h1>WebGL Info</h1>
+<div id="info"></div>
+<h2>WebGL Extensions</h2>
+<div id="extensions"></div>
+<canvas id="example" width="256" height="16" style="width: 256px; height: 48px;"></canvas>
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/webgl-translate-shader.html b/dom/canvas/test/webgl-conf/checkout/extra/webgl-translate-shader.html
new file mode 100644
index 0000000000..55176b6ffd
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/webgl-translate-shader.html
@@ -0,0 +1,153 @@
+<!--
+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 Shader Translator</title>
+<style>
+textarea {
+ min-width: 70%;
+ min-height: 200px;
+ font-family: monospace;
+ background: #def;
+}
+#disabled {
+ color: red;
+ font-weight: bold;
+}
+</style>
+<script>
+// Needed by wtu.create3DContext():
+var testFailed = function() {};
+</script>
+<script src="../js/webgl-test-utils.js"> </script>
+<script>
+"use strict";
+window.onload = main;
+
+var gl;
+var debugShaders;
+var enabledExtensions = [];
+
+var translateButton;
+var extButton;
+
+function main() {
+ translateButton = document.getElementById('translate');
+ extButton = document.getElementById('getExts');
+
+ var wtu = WebGLTestUtils;
+ // Prioritize creating a WebGL 2 context if possible - if not, fall back to WebGL 1.
+ gl = wtu.create3DContext(undefined, undefined, 2);
+ if (!gl || !gl.getExtension('WEBGL_debug_shaders'))
+ {
+ gl = wtu.create3DContext();
+ }
+ if (!gl) {
+ disable();
+ return;
+ }
+
+ debugShaders = gl.getExtension('WEBGL_debug_shaders');
+ if (!debugShaders) {
+ disable();
+ }
+}
+
+function disable() {
+ translateButton.disabled = true;
+ extButton.disabled = true;
+ document.getElementById('disabled').style.display = 'block';
+}
+
+function getExtensions() {
+ getExtensionSet([
+ 'EXT_frag_depth',
+ 'EXT_shader_texture_lod',
+ 'OES_standard_derivatives',
+ 'WEBGL_draw_buffers'
+ ]);
+}
+
+function getExtensionSet(shaderExtensions) {
+ for (var i = 0; i < shaderExtensions.length; ++i) {
+ if (enabledExtensions.indexOf(shaderExtensions[i]) < 0) {
+ var ext = gl.getExtension(shaderExtensions[i]);
+ if (ext) {
+ enabledExtensions.push(shaderExtensions[i]);
+ }
+ }
+ }
+ if (enabledExtensions.length > 0) {
+ document.getElementById('enabled-extensions').textContent = enabledExtensions.join(', ');
+ }
+}
+
+function translateShader() {
+ var sourceElement = document.getElementById('original-shader');
+ var source = sourceElement.value;
+
+ var output = document.getElementById('translated-shader');
+ var infoLog = document.getElementById('info-log');
+ var shaderType = document.getElementById('shader-type');
+ infoLog.value = '';
+
+ // Try compiling the source as both vertex and fragment shader and see if either one works
+ var tryCompile = function(type) {
+ var shader = gl.createShader(type);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+ var shaderTypeStr;
+ if (type == gl.FRAGMENT_SHADER) {
+ shaderTypeStr = 'Fragment shader';
+ } else {
+ shaderTypeStr = 'Vertex shader';
+ }
+ if (gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+ shaderType.textContent = shaderTypeStr;
+ var translatedSource = debugShaders.getTranslatedShaderSource(shader);
+ output.value = translatedSource;
+ infoLog.value = gl.getShaderInfoLog(shader);
+ return true;
+ } else {
+ infoLog.value += 'Info log when compiling as ' + shaderTypeStr + ':\n' + gl.getShaderInfoLog(shader) + '\n';
+ return false;
+ }
+ }
+
+ if (!tryCompile(gl.FRAGMENT_SHADER) && !tryCompile(gl.VERTEX_SHADER)) {
+ output.value = 'Invalid shader, compilation failed as both fragment and vertex shader.';
+ shaderType.textContent = 'Shader not';
+ }
+}
+</script>
+</head>
+<body>
+<h1>WebGL Shader Translator</h1>
+<p>This page uses the browser's built-in shader translation facilities to show how a shader
+is changed before being passed on to the underlying platform's graphics driver.</p>
+<p id="disabled" style="display: none;">
+ WebGL or WEBGL_debug_shaders extension is not available on this browser configuration.
+ Use a different browser or look for other alternatives to enable the extension to be able to use this page.
+ The extension might be behind a runtime flag for privacy considerations.
+</p>
+<h2>WebGL GLSL shader</h2>
+<textarea id="original-shader"></textarea>
+<p>
+<input type="button" id="translate" value="Translate" onclick="translateShader()"></input>
+<input type="button" id="getExts" value="Enable supported GLSL extensions" onclick="getExtensions()"></input>
+</p>
+<h2><span id="shader-type">Shader</span> translated for graphics driver</h2>
+<textarea id="translated-shader"></textarea>
+<h2>Enabled shader extensions</h2>
+<p id="enabled-extensions">None</p>
+<h2>Shader info log</h2>
+<textarea id="info-log"></textarea>
+</body>
+</html>
+
diff --git a/dom/canvas/test/webgl-conf/checkout/extra/workload-simulator.html b/dom/canvas/test/webgl-conf/checkout/extra/workload-simulator.html
new file mode 100644
index 0000000000..fa01977321
--- /dev/null
+++ b/dom/canvas/test/webgl-conf/checkout/extra/workload-simulator.html
@@ -0,0 +1,851 @@
+<!DOCTYPE html><meta charset="utf-8">
+<meta name=viewport content="width=900">
+<title>WebGL workload simulator</title>
+<!--
+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.
+-->
+<style>
+body {
+ margin: 0;
+ font-family: monospace;
+ user-select: none;
+ -moz-user-select: -moz-none;
+ -webkit-user-select: none;
+ font-size: 22px;
+ text-size-adjust: none;
+}
+pre { margin: 0; }
+.square img { position: relative; }
+.square {
+ overflow: hidden;
+ border-bottom: 20px solid black;
+ border-right: 20px solid black;
+ display: block;
+}
+
+#fpsSpan { font-size: 30px; }
+#fpsPanel {
+ position: fixed;
+ text-align: right;
+ left: 550px;
+ top: 0px;
+ width: 300px;
+ font-size: 12px;
+ margin: 10px;
+ pointer-events: none;
+}
+
+input, label { white-space: pre; pointer-events: auto; }
+input { margin: 15px 15px; transform: scale(2); }
+input[type="number"] { width: 50px; }
+input[type="range"] {
+ width: 100px;
+ margin: 30px 200px;
+ padding: 0px;
+ transform: scale(4);
+}
+
+.rotate { animation: rotating 2s linear infinite; }
+@keyframes rotating {
+ from { transform: scale(2) rotate(0deg); }
+ to { transform: scale(2) rotate(360deg); }
+}
+
+.bad { color: red; }
+.light { color: #CCCCCC; }
+#warning {
+ color: #FF0000;
+}
+</style>
+
+<canvas id=webglCanvas class=square></canvas>
+<canvas id=canvas class=square style=display:none;></canvas>
+<div id=dom class=square style=display:none;background:white>
+ <img id=img style=width:256px;height:256px;background:black></img>
+</div>
+<div style=margin:2em;margin-top:0>
+<div id=warning></div>
+Drag the WebGL logo.<br>
+<label for=useGl>Renderer: <input type=radio name=renderer id=useGl checked>WebGL</label>
+<label for=use2D><input type=radio name=renderer id=use2D>Canvas 2D</label>
+<label for=useDom><input type=radio name=renderer id=useDom>DOM</label>
+<br>
+<label for=animate><input type=checkbox name=animate id=animate>Animate</label>
+<span id=continuousOptions>
+ <label for=useRaf><input type=radio name=loop id=useRaf checked>requestAnimationFrame</label>
+ <label for=usePostMessage><input type=radio name=loop id=usePostMessage>postMessage</label>
+ <label for=useSetTimeout><input type=radio name=loop id=useSetTimeout>setTimeout</label>
+ <label for=useSetInterval><input type=radio name=loop id=useSetInterval>setInterval</label>
+ <br>
+ <div id=fpsOptions>
+ <input id=fpsSlider type=range min=10 max=240 step=10 value=60>
+ Target FPS: <span id=fpsLabel>60</span>
+ </div>
+</span>
+<details id=canvasOptions>
+ <summary>Canvas options</summary>
+ <div id=canvas2DOptions>
+ Canvas size <input type=number id=canvasSize value=512 min=1> pixels<sup>2</sup>
+ <label for=onscreen><input type=radio name=offscreen id=onscreen checked>Regular canvas</label>
+ <label for=offscreen><input type=radio name=offscreen id=offscreen>OffscreenCanvas (on main thread)</label>
+ <!-- <label for=offscreenWorker><input type=radio name=offscreen id=offscreenWorker>OffscreenCanvas on worker</label> -->
+ <br>
+ <label for=transferControlToOffscreen><input type=checkbox id=transferControlToOffscreen checked>Use transferControlToOffscreen</label>
+ </div>
+</details>
+<div id=glOptions>
+ <details id=glWork>
+ <summary>WebGL rendering work</summary>
+ <div id=pixelsWrapper>
+ <input type=range id=pixels min=65536 max=65536000 value=65536 step=65536>
+ draw <span id=pixelsLabel>64K</span> pixels per view
+ </div>
+ <input type=range id=drawCalls min=0 max=10000 value=0 step=10>
+ using <span id=drawCallsLabel>1</span> draw call(s) per view
+ <br>
+ Multiply the above slider values by:
+ <label for=x1><input type=radio name=multiplier id=x1 checked>1 </label>
+ <label for=x10><input type=radio name=multiplier id=x10>10 </label>
+ <label for=x100><input type=radio name=multiplier id=x100>100 </label>
+ <br>
+ <input type=range id=uploads min=0 max=256 value=0 step=0.25>
+ <span id=uploadLabel>0.00</span> MB glBufferData
+ <br>
+ <label for=finish><input type=checkbox id=finish>glFinish</label>
+ <br>
+ <label for=readPixels><input type=checkbox id=readPixels>glReadPixels</label>
+ </details>
+ <details id=contextCreation>
+ <summary>WebGL context creation options</summary>
+ <label for=useWebGL2><input type=checkbox id=useWebGL2 checked>Use WebGL 2 if available</label>
+ <label for=antialias><input type=checkbox id=antialias checked>Antialias</label>
+ <label for=alpha><input type=checkbox id=alpha checked>Alpha</label>
+ <label for=depth><input type=checkbox id=depth checked>Depth</label>
+ <label for=stencil><input type=checkbox id=stencil>Stencil</label>
+ <label for=premultipliedAlpha><input type=checkbox id=premultipliedAlpha checked>Premultiplied Alpha</label>
+ <label for=preserveDrawingBuffer><input type=checkbox id=preserveDrawingBuffer>Preserve Drawing Buffer</label>
+ <label for=desynchronized><input type=checkbox id=desynchronized>Desynchronized</label>
+ <br>
+ Power preference
+ <label for=ppDefault><input type=radio name=pp id=ppDefault checked>default</label>
+ <label for=lowPower><input type=radio name=pp id=lowPower>low-power</label>
+ <label for=highPerformance><input type=radio name=pp id=highPerformance>high-performance</label>
+ <br>
+ <label for=separateHighPowerContext><input type=checkbox id=separateHighPowerContext>Activate high power GPU (by creating a separate high power context)</label>
+ </details>
+ <div id=multiviewOptions>
+ <input type=range id=views min=1 max=4 value=0 step=1>
+ Draw <span id=viewsLabel>1</span> views(s)
+ <br>
+ <label for=multiview><input type=checkbox id=multiview>Use OVR_multiview2</label>
+ <label for=multiviewCopy><input type=checkbox id=multiviewCopy checked>Copy multiview draw results to main canvas</label>
+ </div>
+ <pre id=contextVersion>
+ </pre>
+ <details id=contextAttributes>
+ <summary>Context attributes</summary>
+ <pre></pre>
+ </details>
+ <details id=supportedExtensions>
+ <summary>Supported Extensions</summary>
+ </details>
+</div>
+<br>
+<input type=range id=jsWork min=0 max=100 value=0>
+<span id=jsWorkLabel>0</span> ms extra Javascript work per frame
+<br>
+<label for=animation><input type=checkbox id=animation>CSS animation</label>
+
+<div id=fpsPanel>
+ <label for=showFps><input type=checkbox id=showFps checked>Show FPS</label>
+ <div id=fps>
+ <span id=fpsSpan></span>
+ <p>
+ <label for=showStats><input type=checkbox id=showStats>More info</label>
+ <div id=stats></div>
+ </div>
+</div>
+
+<iframe id=highPowerFrame style=display:none></iframe>
+
+<h2><center>WebGL workload simulator</center></h2>
+</div>
+
+<script>
+'use strict';
+// Add all elements with ids as global readonly variables.
+for (let element of document.documentElement.querySelectorAll('[id]'))
+ Object.defineProperty(this, element.id, {value: element, writeable: false});
+
+
+/************\
+* Options UI *
+\************/
+
+
+// Set all input elements with values from the query string.
+const controls = document.querySelectorAll('input, details');
+const defaultChecked = {};
+const defaultValues = {};
+const defaultMaxes = {};
+for (const control of controls) {
+ if (!control.id) continue;
+ defaultChecked[control.id] = control.checked;
+ defaultValues[control.id] = control.value;
+ defaultMaxes[control.id] = control.max;
+ const param = window.location.search.match(control.id + '(?:=([^&]*))?');
+ if (param) {
+ if (control.type == 'radio')
+ control.checked = true;
+ else if (control.type == 'checkbox')
+ control.checked = param[1] != 'false';
+ else if (control instanceof HTMLDetailsElement)
+ control.open = true;
+ else
+ control.value = param[1];
+ }
+ control.oninput = updateControls;
+ if (control instanceof HTMLDetailsElement)
+ control.onclick = ()=>setTimeout(updateControls, 0);
+}
+// Some controls require a page reload when changed.
+const reloadControls = ['useWebGL2', 'antialias', 'alpha', 'depth', 'stencil', 'premultipliedAlpha', 'preserveDrawingBuffer', 'desynchronized', 'ppDefault', 'lowPower', 'highPerformance', 'canvasSize', 'onscreen', 'offscreen', 'transferControlToOffscreen'].map(x=>window[x]);
+for (let control of reloadControls) {
+ control.onchange = ()=>{
+ updateControls();
+ callReplaceStateThrottled();
+ location.reload();
+ };
+}
+
+separateHighPowerContext.onchange = ()=>{
+ if (!separateHighPowerContext.checked)
+ highPowerFrame.contentDocument.location.reload();
+ else {
+ const doc = highPowerFrame.contentDocument;
+ const canvas = doc.createElement('canvas');
+ doc.body.appendChild(canvas);
+ canvas.getContext('webgl', {powerPreference: 'high-performance'});
+ }
+}
+separateHighPowerContext.onchange();
+
+let queryString = window.location.search;
+let previousQueryString = queryString;
+let replaceStateScheduled = false;
+function callReplaceStateThrottled() {
+ replaceStateScheduled = false;
+ if (queryString == previousQueryString)
+ return;
+ previousQueryString = queryString;
+ let path = window.location.pathname;
+ history.replaceState(null, null, queryString == '?' ? path : path + queryString);
+}
+const suffixes = ['', 'K', 'M', 'G', 'E']
+const divisors = [];
+for (let i = 0; i < suffixes.length; i++)
+ divisors[i] = Math.pow(10, i * 3);
+const formatSI = (x) => {
+ const order = Math.min(Math.log10(Math.abs(x)) / 3 | 0, suffixes.length);
+ return (x / divisors[order]).toFixed(1) + suffixes[order];
+}
+var multiplier;
+function updateControls() {
+ multiplier = x1.checked ? 1 : x10.checked ? 10 : 100;
+ webglCanvas.style.display = useGl.checked ? 'block' : 'none';
+ canvas.style.display = use2D.checked ? 'block' : 'none';
+ dom.style.display = useDom.checked ? 'block' : 'none';
+ animation.className = animation.checked ? 'rotate' : null;
+ canvasOptions.style.display = useDom.checked ? 'none' : '';
+ transferControlToOffscreen.parentElement.style.display = onscreen.checked ? 'none' : '';
+ continuousOptions.style.display = animate.checked ? '' : 'none';
+ glOptions.style.display = useGl.checked ? '' : 'none';
+ multiviewOptions.style.display = (useGl.checked && multiviewAvailable) ? '' : 'none';
+ fpsOptions.style.display =
+ animate.checked && (useSetTimeout.checked || useSetInterval.checked) ?
+ '' : 'none';
+ fps.style.visibility = showFps.checked ? 'visible' : 'hidden';
+ stats.style.visibility = showStats.checked ? 'visible' : 'hidden';
+ drawCallsLabel.textContent = Math.max(1, drawCalls.value * multiplier);
+ pixelsLabel.textContent = formatSI(pixels.value * multiplier);
+ jsWorkLabel.textContent = jsWork.value;
+ fpsLabel.textContent = fpsSlider.value;
+ uploadLabel.textContent =
+ parseFloat(uploads.value).toFixed(2);
+ viewsLabel.textContent = views.value;
+ // Multiview is currently incompatible with multisampling.
+ if ((views.value > 1 || multiview.checked) && antialias.checked)
+ antialias.click();
+
+ const queryParams = [];
+ for (const control of controls) {
+ if (control.type == 'radio') {
+ if (!defaultChecked[control.id] && control.checked)
+ queryParams.push(control.id);
+ } else if (control.type == 'checkbox') {
+ if (control.checked != defaultChecked[control.id])
+ queryParams.push(defaultChecked[control.id] ? control.id + '=' + control.checked : control.id);
+ } else if (control instanceof HTMLDetailsElement) {
+ if (control.open)
+ queryParams.push(control.id);
+ } else if (control.value != defaultValues[control.id]) {
+ queryParams.push(control.id + '=' + control.value);
+ }
+ }
+ queryString = '?' + queryParams.join('&');
+ if (!replaceStateScheduled) {
+ replaceStateScheduled = true;
+ setTimeout(callReplaceStateThrottled, 200);
+ }
+ render();
+};
+
+
+/**********************\
+* Input event handling *
+\**********************/
+
+
+const imgSize = 256;
+const size = parseInt(canvasSize.value);
+let multiviewAvailable = false;
+let webglVersion;
+
+let mouseDown = false;
+const lastPos = [0, 0];
+document.onmouseup = (e) => { mouseDown = false; }
+document.onmousedown = (e) => {
+ mouseDown = true;
+ lastPos[0] = e.pageX;
+ lastPos[1] = e.pageY;
+};
+document.ontouchstart = (e) => {
+ lastPos[0] = e.touches[0].pageX;
+ lastPos[1] = e.touches[0].pageY;
+}
+const position = [(size - imgSize) / 2, (size - imgSize) / 2];
+let continuousRunning = false;
+let mouseUpdatesThisFrame = 0;
+function mouseMove(e) {
+ mouseUpdatesThisFrame++;
+ countFps("mouse/touchmove event");
+ const xy = [0, 0];
+ if (e.touches) {
+ xy[0] = e.touches[0].pageX;
+ xy[1] = e.touches[0].pageY;
+ } else {
+ xy[0] = e.pageX;
+ xy[1] = e.pageY;
+ }
+ if (e.touches || mouseDown) {
+ for (let i = 0; i < 2; ++i) {
+ position[i] += xy[i] - lastPos[i];
+ position[i] = Math.max(0, Math.min(size - imgSize, position[i]));
+ lastPos[i] = xy[i];
+ }
+ if (!continuousRunning) {
+ render();
+ }
+ }
+}
+document.addEventListener("mousemove", mouseMove, true);
+document.body.addEventListener("touchmove", mouseMove, true);
+for (const element of [dom, canvas, webglCanvas, img])
+ element.onmousedown = element.ontouchstart = (e)=>e.preventDefault();
+
+
+
+/***********\
+* Rendering *
+\***********/
+
+
+webglCanvas.width = webglCanvas.height = canvas.width = canvas.height = size;
+dom.style.width = dom.style.height = size + 'px';
+
+window.onmessage = () => render(false, true);
+
+let ctx;
+let bitmapRenderer;
+let offscreenCanvas;
+let gl;
+let glBitmapRenderer;
+let glOffscreenCanvas;
+let program;
+let buffer;
+const borderSize = 10;
+const vertices = [0, 0, 1, 0, 0, 1, 1, 1];
+const readPixelsArray = new Uint8Array(4);
+let writeBufferArray = new Float32Array(0);
+let interval;
+let intervalFps;
+let timeout = null;
+let rafPending = 0;
+let postMessagePending = 0;
+
+let maxViewsMultiview = 2;
+let multiviewProgram = [];
+let multiviewExt = null;
+let multiviewTexture = null;
+let multiviewFramebuffer = null;
+let viewFramebuffer = [];
+let lastNumViews = 0;
+
+let displayedWarningText = '';
+let warningText = '';
+
+const animationDirection = [1, -1];
+function render(fromRaf, fromPostMessage) {
+ if (fromRaf) rafPending--;
+ if (fromPostMessage) postMessagePending--;
+ // Set up the appropriate render loop callback as specified by the UI, if
+ // continuous rendering is enabled.
+ continuousRunning = animate.checked;
+ if (continuousRunning) {
+ for (let i = 0; i < 2; ++i) {
+ position[i] += animationDirection[i] * 2;
+ if (position[i] > size - imgSize) {
+ position[i] = size - imgSize;
+ animationDirection[i] = -1;
+ }
+ if (position[i] < 0) {
+ position[i] = 0;
+ animationDirection[i] = 1;
+ }
+ }
+ if (useRaf.checked && rafPending == 0) {
+ (window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+ window.webkitRequestAnimationFrame)(function() { render(true); });
+ rafPending++;
+ }
+ if (useSetTimeout.checked) {
+ clearTimeout(timeout);
+ timeout = setTimeout(render, 1 / fpsSlider.value * 1000);
+ }
+ if (useSetInterval.checked) {
+ if (!interval || intervalFps != fpsSlider.value) {
+ clearInterval(interval);
+ intervalFps = fpsSlider.value;
+ interval = setInterval(render, 1 / fpsSlider.value * 1000);
+ }
+ } else {
+ clearInterval(interval);
+ interval = null;
+ }
+ if (usePostMessage.checked) {
+ if (postMessagePending == 0) {
+ ++postMessagePending;
+ window.postMessage('', '*');
+ }
+ }
+ } else {
+ clearInterval(interval);
+ interval = null;
+ }
+
+ countFps("render", mouseUpdatesThisFrame);
+ mouseUpdatesThisFrame = 0;
+
+ // Busy wait for a configurable amount of time.
+ const startMs = Date.now();
+ while (Date.now() - startMs < jsWork.value);
+
+ warningText = '';
+
+ // Initialize GL.
+ if (!gl) {
+ const options = {};
+ for (let option of ['antialias', 'alpha', 'depth', 'stencil', 'premultipliedAlpha', 'preserveDrawingBuffer', 'desynchronized'])
+ options[option] = window[option].checked;
+ options.powerPreference = ppDefault.checked ? 'default' : lowPower.checked ? 'low-power' : 'high-performance';
+ let renderCanvas = webglCanvas;
+ if (offscreen.checked) {
+ if (transferControlToOffscreen.checked) {
+ renderCanvas = webglCanvas.transferControlToOffscreen();
+ } else {
+ glBitmapRenderer = webglCanvas.getContext('bitmaprenderer')
+ renderCanvas = glOffscreenCanvas = new OffscreenCanvas(size, size);
+ }
+ }
+ renderCanvas.width = renderCanvas.height = size;
+ if (useWebGL2.checked)
+ gl = renderCanvas.getContext('webgl2', options);
+ if (gl) {
+ webglVersion = 2;
+ } else {
+ webglVersion = 1;
+ gl = renderCanvas.getContext('webgl', options);
+ const aia = gl.getExtension('ANGLE_instanced_arrays');
+ if (aia)
+ gl.drawArraysInstanced = (a, b, c, d)=>aia.drawArraysInstancedANGLE(a, b, c, d);
+ else {
+ pixels.value = pixels.min;
+ pixelsWrapper.style.display = 'none';
+ gl.drawArraysInstanced = (a, b, c, d)=>gl.drawArrays(a, b, c);
+ }
+ }
+ // Read context info like renderer string and extensions.
+ let renderer = gl.getParameter(gl.RENDERER);
+ let debugRendererInfo = gl.getExtension('WEBGL_debug_renderer_info');
+ if (debugRendererInfo)
+ renderer = gl.getParameter(debugRendererInfo.UNMASKED_RENDERER_WEBGL);
+ contextVersion.textContent = `WebGL Version: ${gl.getParameter(gl.VERSION)}\nRenderer: `;
+ const a = document.createElement('a');
+ a.textContent = renderer;
+ a.href = `https://www.google.com/search?q=${encodeURIComponent(renderer)}`
+ contextVersion.appendChild(a);
+ contextAttributes.getElementsByTagName('pre')[0].textContent = JSON.stringify(gl.getContextAttributes(), 0, 2);
+ for (const e of gl.getSupportedExtensions()) {
+ const a = document.createElement('a');
+ a.textContent = e;
+ a.href = `https://www.khronos.org/registry/webgl/extensions/${e}/`;
+ supportedExtensions.appendChild(a);
+ supportedExtensions.appendChild(document.createElement('br'));
+ }
+
+ // Setup texture
+ const tex = gl.createTexture();
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));
+ img.onload = ()=>{
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
+ gl.generateMipmap(gl.TEXTURE_2D);
+ render();
+ };
+
+ function setupProgram(vsSource, fsSource, attribs, uniforms) {
+ let prog = gl.createProgram();
+ function compileShader(source, type) {
+ const shader = gl.createShader(type);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
+ console.log(gl.getShaderInfoLog(shader));
+ gl.attachShader(prog, shader);
+ }
+ compileShader(vsSource, gl.VERTEX_SHADER);
+ compileShader(fsSource, gl.FRAGMENT_SHADER);
+ for (let i = 0; i < attribs.length; ++i)
+ gl.bindAttribLocation(prog, i, attribs[i]);
+ gl.linkProgram(prog);
+ if (!gl.getProgramParameter(prog, gl.LINK_STATUS))
+ console.log(gl.getProgramInfoLog(prog));
+ for (const attrib of attribs)
+ prog[attrib] = gl.getAttribLocation(prog, attrib);
+ for (const uniform of uniforms)
+ prog[uniform] = gl.getUniformLocation(prog, uniform);
+ return prog;
+ }
+
+ program = setupProgram(`
+ attribute vec2 position;
+ varying vec2 texCoord;
+ uniform vec2 offset;
+ uniform float size;
+ void main() {
+ gl_Position = vec4(position * size + offset + vec2(size - 1., 1. - size), 0, 1);
+ texCoord = vec2(position.x, 1. - position.y);
+ }`,`
+ precision mediump float;
+ varying vec2 texCoord;
+ uniform vec4 colorAddition;
+ uniform sampler2D tex;
+ void main() {
+ gl_FragColor = texture2D(tex, texCoord) + colorAddition * 0.5;
+ }`,
+ ['position', 'texCoordIn'], ['offset', 'tex', 'size', 'colorAddition']);
+ gl.useProgram(program);
+ gl.uniform1i(program.tex, 0);
+
+ if (webglVersion >= 2 && gl.getExtension('OVR_multiview2')) {
+ multiviewAvailable = true;
+ let ext = gl.getExtension('OVR_multiview2')
+ maxViewsMultiview = Math.min(gl.getParameter(ext.MAX_VIEWS_OVR), 16);
+ views.max = maxViewsMultiview;
+ for (let i = 0; i < maxViewsMultiview; ++i) {
+ multiviewProgram[i] = setupProgram(`#version 300 es
+ #extension GL_OVR_multiview2 : enable
+ layout(num_views=${i + 1}) in;
+ in vec2 position;
+ out vec2 texCoord;
+ uniform vec2 offset;
+ uniform float size;
+ void main() {
+ gl_Position = vec4(position * size + offset + vec2(size - 1., 1. - size), 0, 1);
+ texCoord = vec2(position.x, 1. - position.y);
+ }`, `#version 300 es
+ #extension GL_OVR_multiview2 : enable
+ precision mediump float;
+ in vec2 texCoord;
+ out vec4 my_FragColor;
+ uniform sampler2D tex;
+ void main() {
+ vec4 colorAddition = vec4(((gl_ViewID_OVR & 0x4u) != 0u) ? 1.0 : 0.0,
+ ((gl_ViewID_OVR & 0x2u) != 0u) ? 1.0 : 0.0,
+ ((gl_ViewID_OVR & 0x1u) != 0u) ? 1.0 : 0.0,
+ 1.0);
+ my_FragColor = texture(tex, texCoord) + colorAddition * 0.5;
+ }`,
+ ['position', 'texCoordIn'], ['offset', 'tex', 'size']);
+ gl.useProgram(multiviewProgram[i]);
+ gl.uniform1i(multiviewProgram[i].tex, 0);
+ }
+ }
+
+ // Setup vertex buffer
+ buffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STREAM_DRAW);
+ gl.enableVertexAttribArray(program.position);
+ gl.vertexAttribPointer(program.position, 2, gl.FLOAT, false, 0, 0);
+
+ // Setup drawing state
+ gl.viewport(0, 0, size, size);
+ gl.clearColor(1, 1, 1, 1);
+ gl.disable(gl.SCISSOR_TEST);
+ gl.disable(gl.DEPTH_TEST);
+ gl.disable(gl.STENCIL_TEST);
+ gl.disable(gl.BLEND);
+ gl.disable(gl.CULL_FACE);
+
+ updateControls();
+ } // End GL init
+
+ if (useDom.checked) {
+ img.style.left = position[0] + 'px';
+ img.style.top = position[1] + 'px';
+ return;
+ }
+ if (use2D.checked) {
+ if (!ctx) {
+ if (offscreen.checked) {
+ if (transferControlToOffscreen.checked) {
+ offscreenCanvas = canvas.transferControlToOffscreen();
+ } else {
+ bitmapRenderer = canvas.getContext('bitmaprenderer');
+ offscreenCanvas = new OffscreenCanvas(size, size);
+ }
+ ctx = offscreenCanvas.getContext('2d');
+ } else {
+ ctx = canvas.getContext('2d');
+ }
+ }
+ ctx.fillStyle = 'white';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ try {
+ ctx.drawImage(img, position[0], position[1]);
+ } catch (e) {
+ ctx.fillStyle = 'black';
+ ctx.fillRect(position[0], position[1], imgSize, imgSize);
+ }
+ if (offscreen.checked && !transferControlToOffscreen.checked && bitmapRenderer && offscreenCanvas) {
+ bitmapRenderer.transferFromImageBitmap(offscreenCanvas.transferToImageBitmap());
+ }
+ return;
+ }
+
+ // Upload some data to test the PCI bottleneck.
+ if (uploads.value > 0) {
+ if (writeBufferArray.length * 4 != uploads.value * 1024 * 1024) {
+ writeBufferArray = new Float32Array(uploads.value * 1024 * 1024 / 4);
+ // We want to actually use this data in rendering so the graphics driver
+ // can't optimize away the upload. Fill the first few bytes with our real
+ // vertex data.
+ writeBufferArray.set(vertices, 0);
+ }
+ gl.bufferData(gl.ARRAY_BUFFER, writeBufferArray, gl.STREAM_DRAW);
+ }
+
+ // Actually draw the map.
+ const numDrawCalls = Math.max(1, drawCalls.value * multiplier);
+ const numViews = multiviewAvailable ? Math.min(views.value, maxViewsMultiview) : 1;
+ const instances = pixels.value / 64000 * multiplier;
+ const instancesPerCall = Math.max(1, instances / numDrawCalls | 0);
+ const instancesFirstCall = instancesPerCall + numDrawCalls % instancesPerCall;
+ // Set up to scissor out all but one pixel of the map.
+ gl.scissor(position[0], size - position[1] - 1, 1, 1);
+
+ if (multiviewAvailable && (multiview.checked || numViews > 1) && !multiviewFramebuffer) {
+ multiviewTexture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, multiviewTexture);
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, size, size, maxViewsMultiview);
+ multiviewExt = gl.getExtension('OVR_multiview2');
+ multiviewFramebuffer = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, multiviewFramebuffer);
+ multiviewExt.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, multiviewTexture, 0, 0, numViews);
+ lastNumViews = numViews;
+ for (let i = 0; i < maxViewsMultiview; ++i) {
+ viewFramebuffer[i] = gl.createFramebuffer();
+ gl.bindFramebuffer(gl.FRAMEBUFFER, viewFramebuffer[i]);
+ gl.framebufferTextureLayer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, multiviewTexture, 0, i);
+ }
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.bindTexture(gl.TEXTURE_2D_ARRAY, null);
+ }
+
+ let usedProgram = program;
+ if (multiviewAvailable && multiview.checked) {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, multiviewFramebuffer);
+ if (numViews != lastNumViews) {
+ multiviewExt.framebufferTextureMultiviewOVR(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, multiviewTexture, 0, 0, numViews);
+ lastNumViews = numViews;
+ }
+ usedProgram = multiviewProgram[numViews - 1];
+ } else {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ }
+
+ gl.useProgram(usedProgram);
+ gl.uniform2f(usedProgram.offset,
+ (position[0] - imgSize) / size * 2,
+ -position[1] / size * 2);
+ gl.uniform1f(usedProgram.size, imgSize * 2 / size);
+
+ if (usedProgram == program) {
+ gl.uniform4f(program.colorAddition, 0.0, 0.0, 0.0, 1.0);
+ }
+
+ for (let viewIndex = 0; viewIndex < numViews; ++viewIndex) {
+ let scissorEnabled = false;
+ if (multiviewAvailable && !multiview.checked && numViews > 1) {
+ gl.bindFramebuffer(gl.FRAMEBUFFER, viewFramebuffer[viewIndex]);
+ gl.uniform4f(program.colorAddition, (viewIndex & 4) ? 1.0 : 0.0, (viewIndex & 2) ? 1.0 : 0.0, (viewIndex & 1) ? 1.0 : 0.0, 1.0);
+ gl.disable(gl.SCISSOR_TEST);
+ }
+ gl.clearColor(1, 1, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, instancesFirstCall);
+ var instancesDrawn = instancesFirstCall;
+ for (let i = 1; i < numDrawCalls; i++) {
+ if (instancesDrawn > instances && !scissorEnabled) {
+ // If we've drawn all of the requested pixels already, enable the scissor
+ // test so we only draw one pixel per draw call for the rest of the calls.
+ scissorEnabled = true;
+ gl.enable(gl.SCISSOR_TEST);
+ }
+ gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, instancesPerCall);
+ instancesDrawn += instancesPerCall;
+ }
+ if (multiviewAvailable && multiview.checked) {
+ break;
+ }
+ }
+ gl.disable(gl.SCISSOR_TEST);
+
+ if (multiviewAvailable && (multiview.checked || numViews > 1)) {
+ if (multiviewCopy.checked) {
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
+ gl.clearColor(1, 1, 1, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ let gridCellsPerRow = Math.ceil(Math.sqrt(numViews));
+ for (let i = 0; i < numViews; ++i) {
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, viewFramebuffer[i]);
+ let x = i % gridCellsPerRow;
+ let y = Math.floor(i / gridCellsPerRow);
+ x *= size / gridCellsPerRow;
+ y *= size / gridCellsPerRow;
+ gl.blitFramebuffer(0, 0, size, size, x, y, x + size / gridCellsPerRow, y + size / gridCellsPerRow, gl.COLOR_BUFFER_BIT, gl.NEAREST);
+ }
+ } else {
+ warningText = 'NOTE: Offscreen multiview rendering active - rendering not copied to canvas';
+ }
+ }
+
+ if (finish.checked) {
+ gl.finish();
+ }
+ if (readPixels.checked) {
+ gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, readPixelsArray);
+ }
+ if (offscreen.checked && !transferControlToOffscreen.checked && glOffscreenCanvas && glBitmapRenderer) {
+ glBitmapRenderer.transferFromImageBitmap(glOffscreenCanvas.transferToImageBitmap());
+ }
+}
+
+
+/**************\
+* FPS counters *
+\**************/
+
+
+const counters = {};
+function countFps(name, mouseEventsThisFrame) {
+ let counter = counters[name];
+ if (!counter) {
+ counter = { history: [Date.now() - 16], name: name, count: 0 };
+ counters[name] = counter;
+ }
+ const history = counter.history;
+ history.push(Date.now());
+ while (history.length > 2 &&
+ history[0] + 1000 < history[history.length - 1]) {
+ counter.history.shift();
+ }
+ let averageMs = 0;
+ let maxMs = .1;
+ let minMs = 99999999;
+ for (let i = 1; i < history.length; i++) {
+ let diff = history[i] - history[i - 1];
+ averageMs += diff;
+ maxMs = Math.max(maxMs, diff);
+ minMs = Math.min(minMs, diff);
+ }
+ averageMs /= history.length - 1;
+ counter.fps = 1000 / averageMs;
+ counter.minFps = 1000 / maxMs;
+ counter.maxFps = 1000 / minMs;
+ counter.count++;
+
+ if (mouseEventsThisFrame !== undefined) {
+ counter.mouseEvents = counter.mouseEvents || { multiple: 0, zero: 0 };
+ if (mouseEventsThisFrame > 1) {
+ counter.mouseEvents.multiple++;
+ } else if (mouseEventsThisFrame == 0) {
+ counter.mouseEvents.zero++;
+ }
+ }
+
+ if (showFps.checked) {
+ fpsSpan.innerText = counters['render'].fps.toFixed();
+ if (showStats.checked) {
+ let text = "";
+ for (let key in counters) {
+ counter = counters[key];
+ text += "<b>" + counter.name + "</b><br>";
+ text += counter.fps.toFixed() + " avg FPS<br>";
+ text += "<div class=" +
+ (counter.maxFps - counter.fps > 10 ? "bad" : "") + ">" +
+ counter.maxFps.toFixed() + " max FPS</div>";
+ text += "<div class=" +
+ (counter.fps - counter.minFps > 10 ? "bad" : "") + ">" +
+ counter.minFps.toFixed() + " min FPS</div>";
+ if (counter.mouseEvents) {
+ text += "<div>" + counter.mouseEvents.zero +
+ " frame(s) with no mouse/touch events";
+ text += "<div>" + counter.mouseEvents.multiple +
+ " frame(s) with multiple mouse/touch events";
+ }
+ text += "<div class=light>" + counter.count + " frames</div>";
+ }
+ stats.innerHTML = text;
+ }
+ }
+ if (warningText != displayedWarningText) {
+ warning.textContent = warningText;
+ displayedWarningText = warningText;
+ }
+}
+
+updateControls();
+
+img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEAAgMAAAAhHED1AAAADFBMVEUAAAA5AAByAACZAAADU26qAAAFIUlEQVR42u2ZPW7jRhTHOVQkFiwEFU4RFTwCj0Am8AG8wHIDrC8QZBH4BAGJNGkdIL7BLpCo8Q1CpkqrDRCkVWEvkKSIChVUQHLy3pshRZlDeUhVCeZf6GOt+XHmvf+8+VjLMjIyMjIyMjIyMjIyMjIyMjIyMjL6j2txEUWX45u/uueo6l04qvlUNCfE9yPaf8zbendme84fB7af8ad6O6g9SzsAfjsEcNNtz6sBuVhylR7OGsCgQXhHzf5crVaCuNftQHvgd2LkF28GdKHVgcdD4C7udbvQ6sCRg9mNZheW6vZEyHUAaW/agKDhBadurxjwNN3oh1BpvGWpb6K12uOJ7gh6Mjbb6I6gL2Ffaeag1zKzZ/Jgn4wA6uo0wJUpgI8fvaDCPrCq+wcPBegaxsthgFgANvSRAEXnNxcaE2lL4UwwJh37v+F7DOTl5SkX7AiQWdaEPgI5CltRwhFy5bxwDwBGqXCoM/gtaw0Sba4G1DbKCbBBwOYYMKuzrAaIGOYIsAngSkc0AK/uoRogfLhJCxw9/mz+FCAeUfYAZBLWcUnx3OEDs2OAHGMPQBo5FIA0R0ByBIBf7EMojoka4EgfBxWOPi7QmeERwMEA2viiBIgsFtAMR++XXYBL0ySF+CoBcxliD/7oVV6JEwJm1UsCsCtKQkEzZqsGeNLHCPBLDx4WV9YsheoCgBTt45MzPXhVAnzpkjkEKSjn8HQIp4++YvwXykpAxpr3AQICZDDSzIpzF36SFpTakPG/qUrE5Itp9FINEC5JINZrADiYrJwykzH5l7SZEycAIQHS3QTb7ea8gqAD4J8U/5UnpwCpBEwwTVubrwHg89sZ3wEgdGjkGgD025bhU9fwAV0Z51TawJqHZicBTD5+A11JIfY+hBLewJqif2i2kwCL72x06xaCgXmblwwLi18yXUAKwc8gjg5/j3lzK4ZvXjUIkJAVfsa8OZwA80p7CHGBLgoA8A3/LYquOU0msOYzgNoH0sc+vH4mSsSnCHA5pVEHIGYSvPKJADDMPwDQiSz6/DQgseRchtdqUvdAAMRcsPsAQV2zPaomUFOqJz2IaTZO+gB+vTDOqZ5BVSsn/P0CRADIQkALjdMHqAsKNMWKCnW1sMX0E1mo4BE5FbbidEmTNR1ec5sGHREA4jKnmuj3Adx61XCoH7i2kINcYSSIi0sxTvsAdVnHphtaBGACb7HvDL8HBf7hAddHBNxFEVQm5cIiKj8uVFucwzSL4A0mJlYmPD0UVrMKq5Y2ubZb2A+oywBh0C8bOyN3cQ1gGr0IO5Nhi4CMvq4hYHfXVJHgbd1sohrA7MPvWcdJtPYm5MxMZGZTF1URpipuAOzXv7LuBgOfHRIgES0SVm/+6EMetAA/Zd0tDhR1LjqU0O67hHb3MmL4jLXfAnzdBkzqLRKuz3JpXWLmGP9W7p8BWFmf/GCtSG+t76Kkm4bqKDVfiqO7fINl6er5jeatNVbeYZ84Ts7wg7Z6r7sZu92vg1COPXA0e9W+E8cXz42hPrJUYw9dzcH1QX0oDHUTybkqWkuNw7PdHH1Dxd3OWv/Uo7g6mqaVjhXcvssndq/n0NYVzOPx8znXM2jrEmh/iOSrbgnVugX6kU5bC3EzmGhOCPU91JA5qr6IGjBFZ2dehVnWtaL9flBZuDnrMk8m/YzrRAVh+LVs61J35LUue920/+NqXIVdvKZxrKJz7sYXC/MfBEZGRkZGRkZGRkZGRkZGRkZGRkb/N/0Lo92VZbHxh60AAAAASUVORK5CYII=';
+</script>