diff options
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/extra')
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 Binary files differnew file mode 100644 index 0000000000..4ec9751070 --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/50x50pixel-black-with-red-triangle.png 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 Binary files differnew file mode 100644 index 0000000000..ba80dbe760 --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/canvas-compositing-test.png 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 Binary files differnew file mode 100644 index 0000000000..b6a9ef1acd --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/logo-256x256.png 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 Binary files differnew file mode 100644 index 0000000000..197c869b6d --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-100.png diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png b/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png Binary files differnew file mode 100644 index 0000000000..89f2b782a5 --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-200.png diff --git a/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png b/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png Binary files differnew file mode 100644 index 0000000000..aaee3ce0dc --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/extra/sample-400.png 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> |