<!-- /* ** Copyright (c) 2015 The Khronos Group Inc. ** ** Permission is hereby granted, free of charge, to any person obtaining a ** copy of this software and/or associated documentation files (the ** "Materials"), to deal in the Materials without restriction, including ** without limitation the rights to use, copy, modify, merge, publish, ** distribute, sublicense, and/or sell copies of the Materials, and to ** permit persons to whom the Materials are furnished to do so, subject to ** the following conditions: ** ** The above copyright notice and this permission notice shall be included ** in all copies or substantial portions of the Materials. ** ** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. */ --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="../../resources/js-test-style.css"/> <script src="../../js/js-test-pre.js"></script> <script src="../../js/webgl-test-utils.js"></script> </head> <body> <div id="description"></div> <div id="console"></div> <script> "use strict"; var wtu = WebGLTestUtils; var gl; function checkFramebuffer(expected) { var actual = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (expected.indexOf(actual) < 0) { var msg = "checkFramebufferStatus expects ["; for (var index = 0; index < expected.length; ++index) { msg += wtu.glEnumToString(gl, expected[index]); if (index + 1 < expected.length) msg += ", "; } msg += "], was " + wtu.glEnumToString(gl, actual); testFailed(msg); } else { var msg = "checkFramebufferStatus got " + wtu.glEnumToString(gl, actual) + " as expected"; testPassed(msg); } } function checkBufferBits(attachment0, attachment1) { if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) return; var haveDepthBuffer = attachment0 == gl.DEPTH_ATTACHMENT || attachment0 == gl.DEPTH_STENCIL_ATTACHMENT || attachment1 == gl.DEPTH_ATTACHMENT || attachment1 == gl.DEPTH_STENCIL_ATTACHMENT; var haveStencilBuffer = attachment0 == gl.STENCIL_ATTACHMENT || attachment0 == gl.DEPTH_STENCIL_ATTACHMENT || attachment1 == gl.STENCIL_ATTACHMENT || attachment1 == gl.DEPTH_STENCIL_ATTACHMENT; shouldBeTrue("gl.getParameter(gl.RED_BITS) + gl.getParameter(gl.GREEN_BITS) + " + "gl.getParameter(gl.BLUE_BITS) + gl.getParameter(gl.ALPHA_BITS) >= 16"); if (haveDepthBuffer) shouldBeTrue("gl.getParameter(gl.DEPTH_BITS) >= 16"); else shouldBeTrue("gl.getParameter(gl.DEPTH_BITS) == 0"); if (haveStencilBuffer) shouldBeTrue("gl.getParameter(gl.STENCIL_BITS) >= 8"); else shouldBeTrue("gl.getParameter(gl.STENCIL_BITS) == 0"); } function testFramebufferWebGL1RequiredCombinations() { debug("Checking combinations of framebuffer attachments required to be valid by WebGL 1"); // Per discussion with the OpenGL ES working group, the following framebuffer attachment // combinations are required to work in all WebGL 1 implementations: // 1. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture // 2. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_ATTACHMENT = DEPTH_COMPONENT16 renderbuffer // 3. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_STENCIL_ATTACHMENT = DEPTH_STENCIL renderbuffer var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); var width = 64; var height = 64; // 1. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(); // 2. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_ATTACHMENT = DEPTH_COMPONENT16 renderbuffer var renderbuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_ATTACHMENT); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, null); // 3. COLOR_ATTACHMENT0 = RGBA/UNSIGNED_BYTE texture + DEPTH_STENCIL_ATTACHMENT = DEPTH_STENCIL renderbuffer gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, width, height); gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, renderbuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_STENCIL_ATTACHMENT); // Clean up gl.deleteRenderbuffer(renderbuffer); gl.deleteTexture(texture); gl.deleteFramebuffer(fbo); } function testDepthStencilAttachmentBehaviors() { debug(""); debug("Checking ES3 DEPTH_STENCIL_ATTACHMENT behaviors are implemented for WebGL 2"); // DEPTH_STENCIL_ATTACHMENT is treated as an independent attachment point in WebGL 1; // however, in WebGL 2, it is treated as an alias for DEPTH_ATTACHMENT + STENCIL_ATTACHMENT. var size = 16; var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); var colorBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, size, size); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); var depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size); var stencilBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, stencilBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX8, size, size); var depthStencilBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthStencilBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, size, size); wtu.glErrorShouldBe(gl, gl.NO_ERROR); debug("color + depth"); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_ATTACHMENT); debug("color + depth + stencil: depth != stencil"); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, stencilBuffer); checkFramebuffer([gl.FRAMEBUFFER_UNSUPPORTED]); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, null); debug("color + depth: DEPTH_STENCIL for DEPTH_ATTACHMENT"); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_ATTACHMENT); debug("color + depth + stencil: DEPTH_STENCIL for DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_STENCIL_ATTACHMENT); debug("color + depth_stencil"); var texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH24_STENCIL8, size, size, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, texture, 0); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, null, 0); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_STENCIL_ATTACHMENT); debug("DEPTH_STENCIL_ATTACHMENT overwrites DEPTH_ATTACHMENT/STENCIL_ATTACHMENT") gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, null); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(); debug("STENCIL_ATTACHMENT overwrites stencil set by DEPTH_STENCIL_ATTACHMENT") gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, depthStencilBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, null); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); checkBufferBits(gl.DEPTH_ATTACHMENT); } function testFramebufferIncompleteAttachment() { var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); var colorBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); debug(""); debug("Wrong storage type for type of attachment should be FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 16, 16); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT]); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); debug(""); debug("0 size attachment should be FRAMEBUFFER_INCOMPLETE_ATTACHMENT"); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 0, 0); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT]); gl.deleteRenderbuffer(colorBuffer); gl.deleteFramebuffer(fbo); wtu.glErrorShouldBe(gl, gl.NO_ERROR); } function testFramebufferIncompleteMissingAttachment() { debug(""); debug("No attachments should be INCOMPLETE_FRAMEBUFFER_MISSING_ATTACHMENT"); var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT]); var colorBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, null); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT]); gl.deleteRenderbuffer(colorBuffer); gl.deleteFramebuffer(fbo); wtu.glErrorShouldBe(gl, gl.NO_ERROR); } function testFramebufferWithImagesOfDifferentSizes() { debug(""); debug("Attachments of different sizes should NOT be allowed"); var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); var colorBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 16); var depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 16, 16); wtu.glErrorShouldBe(gl, gl.NO_ERROR); checkFramebuffer([gl.FRAMEBUFFER_COMPLETE]); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 32, 16); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS]); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 16, 32); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS]); wtu.glErrorShouldBe(gl, gl.NO_ERROR); var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 16, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); wtu.glErrorShouldBe(gl, gl.NO_ERROR); if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) { gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 32, 16, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS]); } gl.deleteTexture(tex); gl.deleteRenderbuffer(depthBuffer); gl.deleteRenderbuffer(colorBuffer); gl.deleteFramebuffer(fbo); wtu.glErrorShouldBe(gl, gl.NO_ERROR); } function testUsingIncompleteFramebuffer() { debug(""); debug("Test drawing or reading from an incomplete framebuffer"); var program = wtu.setupTexturedQuad(gl); var tex = gl.createTexture(); wtu.fillTexture(gl, tex, 1, 1, [0,255,0,255]); var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT]); debug(""); debug("Drawing or reading from an incomplete framebuffer should generate INVALID_FRAMEBUFFER_OPERATION"); testRenderingAndReading(); var colorBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, colorBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 0, 0); checkFramebuffer([gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT]); debug(""); debug("Drawing or reading from an incomplete framebuffer should generate INVALID_FRAMEBUFFER_OPERATION"); testRenderingAndReading(); function testRenderingAndReading() { wtu.glErrorShouldBe(gl, gl.NO_ERROR); wtu.clearAndDrawUnitQuad(gl); wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "drawArrays with incomplete framebuffer"); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "readPixels from incomplete framebuffer"); // copyTexImage and copyTexSubImage can be either INVALID_FRAMEBUFFER_OPERATION because // the framebuffer is invalid OR INVALID_OPERATION because in the case of no attachments // the framebuffer is not of a compatible type. gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 1, 1); wtu.glErrorShouldBe(gl, [gl.INVALID_FRAMEBUFFER_OPERATION, gl.INVALID_OPERATION], "copyTexImage2D from incomplete framebuffer"); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 1, 1, 0); wtu.glErrorShouldBe(gl, [gl.INVALID_FRAMEBUFFER_OPERATION, gl.INVALID_OPERATION], "copyTexSubImage2D from incomplete framebuffer"); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); wtu.glErrorShouldBe(gl, gl.INVALID_FRAMEBUFFER_OPERATION, "clear with incomplete framebuffer"); } gl.deleteRenderbuffer(colorBuffer); gl.deleteFramebuffer(fbo); gl.deleteTexture(tex); gl.deleteProgram(program); } function testReadingFromMissingAttachment() { debug(""); debug("Test drawing or reading from a framebuffer with no color image"); var fbo = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fbo); var size = 16; // The only scenario we can verify is an attempt to read or copy // from a missing color attachment while the framebuffer is still // complete. var depthBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer); gl.framebufferRenderbuffer( gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, size, size); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "After depth renderbuffer setup"); if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE) { // The FBO has no color attachment. ReadPixels, CopyTexImage2D, // and CopyTexSubImage2D should all generate INVALID_OPERATION. wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Before ReadPixels from missing attachment"); gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4)); wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "After ReadPixels from missing attachment"); var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Before CopyTexImage2D from missing attachment"); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, size, size, 0); wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "After CopyTexImage2D from missing attachment"); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Before CopyTexSubImage2D from missing attachment"); gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, size, size); wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "After CopyTexSubImage2D from missing attachment"); gl.deleteTexture(tex); } gl.deleteRenderbuffer(depthBuffer); gl.deleteFramebuffer(fbo); } description("Test framebuffer object attachment behaviors"); shouldBeNonNull("gl = wtu.create3DContext(undefined, undefined, 2)"); testFramebufferWebGL1RequiredCombinations(); testDepthStencilAttachmentBehaviors(); testFramebufferIncompleteAttachment(); testFramebufferIncompleteMissingAttachment(); testFramebufferWithImagesOfDifferentSizes(); testUsingIncompleteFramebuffer(); testReadingFromMissingAttachment(); debug("") var successfullyParsed = true; </script> <script src="../../js/js-test-post.js"></script> </body> </html>