/* 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. */ // Some variables that will be used in this file var canvas; var gl; var OES_vertex_array_object; var uniformLocation; var WEBGL_lose_context; var buffer; var framebuffer; var program; var renderbuffer; var shader; var texture; var arrayBuffer; var arrayBufferView; var vertexArrayObject; var imageData; var float32array; var int32array; var OES_texture_float; var new_WEBGL_lose_context; var allowRestore; var contextLostEventFired; var contextRestoredEventFired; var newExtension; function compareGLError(glError, evalStr) { var exception; try { eval(evalStr); } catch (e) { exception = e; } if (exception) { return false; } else { if (gl.getError() == glError) return true; return false; } } function contextCreation(contextType) { canvas = new OffscreenCanvas(10, 10); gl = canvas.getContext(contextType); if (contextType == 'webgl') { if (gl instanceof WebGLRenderingContext) return true; return false; } else if (contextType == 'webgl2') { if (gl instanceof WebGL2RenderingContext) return true; return false; } else { return false; } } function transferredOffscreenCanvasCreation(placeholder, width, height) { placeholder.width = width; placeholder.height = height; return placeholder.transferControlToOffscreen(); } function assertWidthAndHeight(entity, entityName, width, height) { if (entity.width == width && entity.height == height) { testPassed("The width and height of " + entityName + " are correct."); return; } var errMsg = ""; if (entity.width != width) { errMsg += "The width of " + entityName + " is " + entity.width + " while expected value is " + width + ". "; } if (entity.height != height) { errMsg += "The height of " + entityName + " is " + entity.height + " while expected value is " + height + ". "; } testFailed(errMsg); } var webgl1Methods = [ "getContextAttributes", "activeTexture", "attachShader", "bindAttribLocation", "bindBuffer", "bindFramebuffer", "bindRenderbuffer", "bindTexture", "blendColor", "blendEquation", "blendEquationSeparate", "blendFunc", "blendFuncSeparate", "bufferData", "bufferSubData", "checkFramebufferStatus", "clear", "clearColor", "clearDepth", "clearStencil", "colorMask", "compileShader", "compressedTexImage2D", "compressedTexSubImage2D", "copyTexImage2D", "copyTexSubImage2D", "createBuffer", "createFramebuffer", "createProgram", "createRenderbuffer", "createShader", "createTexture", "cullFace", "deleteBuffer", "deleteFramebuffer", "deleteProgram", "deleteRenderbuffer", "deleteShader", "deleteTexture", "depthFunc", "depthMask", "depthRange", "detachShader", "disable", "disableVertexAttribArray", "drawArrays", "drawElements", "enable", "enableVertexAttribArray", "finish", "flush", "framebufferRenderbuffer", "framebufferTexture2D", "frontFace", "generateMipmap", "getActiveAttrib", "getActiveUniform", "getAttachedShaders", "getAttribLocation", "getParameter", "getBufferParameter", "getError", "getExtension", "getFramebufferAttachmentParameter", "getProgramParameter", "getProgramInfoLog", "getRenderbufferParameter", "getShaderParameter", "getShaderInfoLog", "getShaderPrecisionFormat", "getShaderSource", "getSupportedExtensions", "getTexParameter", "getUniform", "getUniformLocation", "getVertexAttrib", "getVertexAttribOffset", "hint", "isBuffer", "isContextLost", "isEnabled", "isFramebuffer", "isProgram", "isRenderbuffer", "isShader", "isTexture", "lineWidth", "linkProgram", "pixelStorei", "polygonOffset", "readPixels", "renderbufferStorage", "sampleCoverage", "scissor", "shaderSource", "stencilFunc", "stencilFuncSeparate", "stencilMask", "stencilMaskSeparate", "stencilOp", "stencilOpSeparate", "texImage2D", "texParameterf", "texParameteri", "texSubImage2D", "uniform1f", "uniform1fv", "uniform1i", "uniform1iv", "uniform2f", "uniform2fv", "uniform2i", "uniform2iv", "uniform3f", "uniform3fv", "uniform3i", "uniform3iv", "uniform4f", "uniform4fv", "uniform4i", "uniform4iv", "uniformMatrix2fv", "uniformMatrix3fv", "uniformMatrix4fv", "useProgram", "validateProgram", "vertexAttrib1f", "vertexAttrib1fv", "vertexAttrib2f", "vertexAttrib2fv", "vertexAttrib3f", "vertexAttrib3fv", "vertexAttrib4f", "vertexAttrib4fv", "vertexAttribPointer", "viewport", ]; var webgl2Methods = [ "getBufferSubData", "copyBufferSubData", "blitFramebuffer", "framebufferTextureLayer", "getInternalformatParameter", "invalidateFramebuffer", "invalidateSubFramebuffer", "readBuffer", "renderbufferStorageMultisample", "texImage3D", "texStorage2D", "texStorage3D", "texSubImage3D", "copyTexSubImage3D", "compressedTexImage3D", "compressedTexSubImage3D", "getFragDataLocation", "uniform1ui", "uniform2ui", "uniform3ui", "uniform4ui", "uniform1uiv", "uniform2uiv", "uniform3uiv", "uniform4uiv", "uniformMatrix2x3fv", "uniformMatrix3x2fv", "uniformMatrix2x4fv", "uniformMatrix4x2fv", "uniformMatrix3x4fv", "uniformMatrix4x3fv", "vertexAttribI4i", "vertexAttribI4iv", "vertexAttribI4ui", "vertexAttribI4uiv", "vertexAttribIPointer", "vertexAttribDivisor", "drawArraysInstanced", "drawElementsInstanced", "drawRangeElements", "drawBuffers", "clearBufferiv", "clearBufferuiv", "clearBufferfv", "clearBufferfi", "createQuery", "deleteQuery", "isQuery", "beginQuery", "endQuery", "getQuery", "getQueryParameter", "createSampler", "deleteSampler", "isSampler", "bindSampler", "samplerParameteri", "samplerParameterf", "getSamplerParameter", "fenceSync", "isSync", "deleteSync", "clientWaitSync", "waitSync", "getSyncParameter", "createTransformFeedback", "deleteTransformFeedback", "isTransformFeedback", "bindTransformFeedback", "beginTransformFeedback", "endTransformFeedback", "transformFeedbackVaryings", "getTransformFeedbackVarying", "pauseTransformFeedback", "resumeTransformFeedback", "bindBufferBase", "bindBufferRange", "getIndexedParameter", "getUniformIndices", "getActiveUniforms", "getUniformBlockIndex", "getActiveUniformBlockParameter", "getActiveUniformBlockName", "uniformBlockBinding", "createVertexArray", "deleteVertexArray", "isVertexArray", "bindVertexArray", ]; function assertFunction(v, f) { try { if (typeof v[f] != "function") { return false; } else { return true; } } catch(e) { return false; } } function testAPIs(contextType) { canvas = new OffscreenCanvas(10, 10); gl = canvas.getContext(contextType); var passed = true; var methods; if (contextType == 'webgl') methods = webgl1Methods; else methods = webgl1Methods.concat(webgl2Methods); for (var i=0; i { const v = eval(s); return !v; }); if (failedTests.length) { console.log({failedTests}); return false; } // "Is" queries should all return false. if (gl.isBuffer(buffer) || gl.isEnabled(gl.BLEND) || gl.isFramebuffer(framebuffer) || gl.isProgram(program) || gl.isRenderbuffer(renderbuffer) || gl.isShader(shader) || gl.isTexture(texture)) return false; if (gl.getError() != gl.NO_ERROR) return false; // test extensions if (OES_vertex_array_object) { if (!compareGLError(gl.NO_ERROR, "OES_vertex_array_object.bindVertexArrayOES(vertexArrayObject)") || !compareGLError(gl.NO_ERROR, "OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)") || !compareGLError(gl.NO_ERROR, "OES_vertex_array_object.deleteVertexArrayOES(vertexArrayObject)")) return false; if (!OES_vertex_array_object.createVertexArrayOES()) return false; } return true; } function testValidContext() { if (gl.isContextLost()) return false; arrayBuffer = new ArrayBuffer(4); arrayBufferView = new Int8Array(arrayBuffer); // Generate resources for testing. buffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buffer); framebuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); program = setupSimpleTextureProgram(gl); renderbuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer); shader = gl.createShader(gl.VERTEX_SHADER); texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); if (gl.getError() != gl.NO_ERROR) return false; // Test is queries that will later be false if (!compareGLError(gl.NO_ERROR, "gl.enable(gl.BLEND)")) return false; if (!gl.isBuffer(buffer) || !gl.isEnabled(gl.BLEND) || !gl.isFramebuffer(framebuffer) || !gl.isProgram(program) || !gl.isRenderbuffer(renderbuffer) || !gl.isShader(shader) || !gl.isTexture(texture)) return false; if (OES_vertex_array_object) { vertexArrayObject = OES_vertex_array_object.createVertexArrayOES(); if (gl.getError() != gl.NO_ERROR) return false; if (!OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)) return false; } return true; } function setupTest() { canvas = new OffscreenCanvas(10, 10); gl = canvas.getContext('webgl'); WEBGL_lose_context = gl.getExtension("WEBGL_lose_context"); if (!WEBGL_lose_context) return false; // Try to get a few extensions OES_vertex_array_object = gl.getExtension("OES_vertex_array_object"); OES_texture_float = gl.getExtension("OES_texture_float"); return true; } function testOriginalContext() { if (gl.isContextLost()) return false; if (gl.getError() != gl.NO_ERROR) return false; return true; } function testLostContext(e) { if (contextLostEventFired) return false; contextLostEventFired = true; if (!gl.isContextLost()) return false; if (gl.getError() != gl.NO_ERROR) return false; if (allowRestore) e.preventDefault(); return true; } function testLosingAndRestoringContext() { return new Promise(function(resolve, reject) { if (!setupTest()) reject("Test failed"); canvas.addEventListener("webglcontextlost", function(e) { if (!testLostContext(e)) reject("Test failed"); // restore the context after this event has exited. setTimeout(function() { if (!compareGLError(gl.NO_ERROR, "WEBGL_lose_context.restoreContext()")) reject("Test failed"); // The context should still be lost. It will not get restored until the // webglrestorecontext event is fired. if (!gl.isContextLost()) reject("Test failed"); if (gl.getError() != gl.NO_ERROR) reject("Test failed"); // gl methods should still be no-ops if (!compareGLError(gl.NO_ERROR, "gl.blendFunc(gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP)")) reject("Test failed"); }, 0); }); canvas.addEventListener("webglcontextrestored", function() { if (!testRestoredContext()) reject("Test failed: !testRestoredContext()"); else resolve("Test passed"); }); allowRestore = true; contextLostEventFired = false; contextRestoredEventFired = false; if (!testOriginalContext()) reject("Test failed"); WEBGL_lose_context.loseContext(); // The context should be lost immediately. if (!gl.isContextLost()) reject("Test failed"); if (gl.getError() != gl.CONTEXT_LOST_WEBGL) reject("Test failed"); if (gl.getError() != gl.NO_ERROR) reject("Test failed"); // gl methods should be no-ops if (!compareGLError(gl.NO_ERROR, "gl.blendFunc(gl.TEXTURE_2D, gl.TEXTURE_CUBE_MAP)")) reject("Test failed"); // but the event should not have been fired. if (contextLostEventFired) reject("Test failed"); }); } function reGetExtensionAndTestForProperty(gl, name, expectProperty) { var newExtension = gl.getExtension(name); // NOTE: while getting a extension after context lost/restored is allowed to fail // for the purpose the conformance tests it is not. // // Hypothetically the user can switch GPUs live. For example on Windows, install 2 GPUs, // then in the control panen enable 1, disable the others and visa versa. Since the GPUs // have different capabilities one or the other may not support a particlar extension. // // But, for the purpose of the conformance tests the context is expected to restore // on the same GPU and therefore the extensions that succeeded previously should // succeed on restore. if (newExtension == null) return false; if (expectProperty) { if (!(newExtension.webglTestProperty === true)) return false; } else { if (!(newExtension.webglTestProperty === undefined)) return false; } return newExtension; } function testOESTextureFloat() { if (OES_texture_float) { // Extension must still be lost. var tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex); if (!compareGLError(gl.INVALID_ENUM, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.FLOAT, null)")) return false; // Try re-enabling extension OES_texture_float = reGetExtensionAndTestForProperty(gl, "OES_texture_float", false); if (!compareGLError(gl.NO_ERROR, "gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.FLOAT, null)")) return false; return true; } } function testOESVertexArrayObject() { if (OES_vertex_array_object) { // Extension must still be lost. if (!OES_vertex_array_object.createVertexArrayOES()) { console.error("!OES_vertex_array_object.createVertexArrayOES()"); return false; } // Try re-enabling extension var old_OES_vertex_array_object = OES_vertex_array_object; OES_vertex_array_object = reGetExtensionAndTestForProperty(gl, "OES_vertex_array_object", false); if (!OES_vertex_array_object.createVertexArrayOES()) { console.error("!OES_vertex_array_object.createVertexArrayOES() 2"); return false; } if (!old_OES_vertex_array_object.createVertexArrayOES()) { console.error("!old_OES_vertex_array_object.createVertexArrayOES()"); return false; } return true; } } function testExtensions() { if (!testOESTextureFloat() || !testOESVertexArrayObject()) return false; return true; } function testRestoredContext() { if (contextRestoredEventFired) return false; contextRestoredEventFired = true; if (gl.isContextLost()) return false; if (gl.getError() != gl.NO_ERROR) return false; if (!testExtensions()) return false; return true; }