Source code

Revision control

Copy as Markdown

Other Tools

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<!--
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.
-->
<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>
"use strict";
var wtu;
var canvas;
var gl;
var shouldGenerateGLError;
var extensionName;
var extension;
var buffer;
var framebuffer;
var program;
var renderbuffer;
var shader;
var texture;
var uniformLocation;
var arrayBuffer;
var arrayBufferView
var image;
var video;
var canvas2d;
var ctx2d;
var imageData;
var float32array;
var int32array;
var OES_vertex_array_object;
var vertexArrayObject;
var secondCanvas;
var secondGL;
function init()
{
wtu = WebGLTestUtils;
canvas = document.getElementById("canvas");
gl = wtu.create3DContext(canvas);
secondCanvas = document.getElementById("canvas2");
secondGL = wtu.create3DContext(secondCanvas);
shouldGenerateGLError = wtu.shouldGenerateGLError;
description("Tests behavior under a lost context");
// call testValidContext() before checking for the extension, because this is where we check
// for the isContextLost() method, which we want to do regardless of the extension's presence.
testValidContext();
extensionName = wtu.getSupportedExtensionWithKnownPrefixes(gl, "WEBGL_lose_context");
if (!extensionName) {
debug("Could not find WEBGL_lose_context extension");
finishTest();
return false;
}
extension = gl.getExtension(extensionName);
// need an extension that exposes new API methods.
OES_vertex_array_object = wtu.getExtensionWithKnownPrefixes(gl, "OES_vertex_array_object");
canvas.addEventListener("webglcontextlost", testLostContext, false);
// We need to initialize |uniformLocation| before losing context.
// Otherwise gl.getUniform() when context is lost will throw.
uniformLocation = gl.getUniformLocation(program, "tex");
loseContext();
}
function loseContext()
{
debug("");
debug("Lose context");
// Note: this will cause the context to be lost, but the
// webglcontextlost event listener to be queued.
extension.loseContext();
debug("");
}
function testValidContext()
{
debug("Test valid context");
shouldBeFalse("gl.isContextLost()");
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 = wtu.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);
shouldBe("gl.getError()", "gl.NO_ERROR");
// Test is queries that will later be false
shouldGenerateGLError(gl, gl.NO_ERROR, "gl.enable(gl.BLEND)");
shouldBeTrue("gl.isBuffer(buffer)");
shouldBeTrue("gl.isEnabled(gl.BLEND)");
shouldBeTrue("gl.isFramebuffer(framebuffer)");
shouldBeTrue("gl.isProgram(program)");
shouldBeTrue("gl.isRenderbuffer(renderbuffer)");
shouldBeTrue("gl.isShader(shader)");
shouldBeTrue("gl.isTexture(texture)");
if (OES_vertex_array_object) {
vertexArrayObject = OES_vertex_array_object.createVertexArrayOES();
shouldBe("gl.getError()", "gl.NO_ERROR");
shouldBeTrue("OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)");
}
}
function testGLNOErrorFunctions(tests) {
tests.forEach(function(test) {
shouldGenerateGLError(gl, gl.NO_ERROR, test);
});
}
function testFunctionsThatReturnNULL(tests) {
tests.forEach(function(test) {
shouldBeNull(test);
});
}
function testUploadingLostContextToTexture() {
debug("Testing uploading a canvas with a lost WebGL context to a texture");
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Should be no errors at beginning");
let texture = secondGL.createTexture();
secondGL.bindTexture(secondGL.TEXTURE_2D, texture);
secondGL.texImage2D(secondGL.TEXTURE_2D, 0, secondGL.RGBA, secondGL.RGBA, secondGL.UNSIGNED_BYTE, canvas);
wtu.glErrorShouldBe(secondGL, [secondGL.INVALID_OPERATION, secondGL.NO_ERROR], "Should not crash when uploading canvas with lost WebGL context to a texture");
}
function testLostContext()
{
debug("Test lost context");
// Functions with special return values.
shouldBeTrue("gl.isContextLost()");
shouldBe("gl.getError()", "gl.CONTEXT_LOST_WEBGL");
shouldBe("gl.getError()", "gl.NO_ERROR");
shouldBe("gl.checkFramebufferStatus(gl.FRAMEBUFFER)", "gl.FRAMEBUFFER_UNSUPPORTED");
shouldBe("gl.getAttribLocation(program, 'u_modelViewProjMatrix')", "-1");
shouldBe("gl.getVertexAttribOffset(0, gl.VERTEX_ATTRIB_ARRAY_POINTER)", "0");
// Attempt resize of lost context should succeed, still lost.
gl.canvas.width = gl.canvas.width; // Try to hit any same-size resize path.
shouldBeTrue("gl.isContextLost()");
const newSize = gl.canvas.width + 1;
gl.canvas.width = newSize;
shouldBe("gl.canvas.width", newSize);
shouldBeTrue("gl.isContextLost()");
// Test the extension itself.
shouldGenerateGLError(gl, gl.INVALID_OPERATION, "extension.loseContext()");
image = document.createElement("img");
video = document.createElement("video");
canvas2d = document.createElement("canvas");
ctx2d = canvas2d.getContext("2d");
imageData = ctx2d.createImageData(1, 1);
float32array = new Float32Array(1);
int32array = new Int32Array(1);
// Functions returning void should return immediately.
// This is untestable, but we can at least be sure they cause no errors
// and the codepaths are exercised.
var voidTests = [
"gl.activeTexture(gl.TEXTURE0)",
"gl.attachShader(program, shader)",
"gl.bindBuffer(gl.ARRAY_BUFFER, buffer)",
"gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer)",
"gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer)",
"gl.bindTexture(gl.TEXTURE_2D, texture)",
"gl.blendColor(1.0, 1.0, 1.0, 1.0)",
"gl.blendEquation(gl.FUNC_ADD)",
"gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD)",
"gl.blendFunc(gl.ONE, gl.ONE)",
"gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE)",
"gl.bufferData(gl.ARRAY_BUFFER, 0, gl.STATIC_DRAW)",
"gl.bufferData(gl.ARRAY_BUFFER, arrayBufferView, gl.STATIC_DRAW)",
"gl.bufferData(gl.ARRAY_BUFFER, arrayBuffer, gl.STATIC_DRAW)",
"gl.bufferSubData(gl.ARRAY_BUFFRE, 0, arrayBufferView)",
"gl.bufferSubData(gl.ARRAY_BUFFRE, 0, arrayBuffer)",
"gl.clear(gl.COLOR_BUFFER_BIT)",
"gl.clearColor(1, 1, 1, 1)",
"gl.clearDepth(1)",
"gl.clearStencil(0)",
"gl.colorMask(1, 1, 1, 1)",
"gl.compileShader(shader)",
"gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 0, 0, 0)",
"gl.copyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, 0, 0)",
"gl.cullFace(gl.FRONT)",
"gl.deleteBuffer(buffer)",
"gl.deleteFramebuffer(framebuffer)",
"gl.deleteProgram(program)",
"gl.deleteRenderbuffer(renderbuffer)",
"gl.deleteShader(shader)",
"gl.deleteTexture(texture)",
"gl.depthFunc(gl.NEVER)",
"gl.depthMask(0)",
"gl.depthRange(0, 1)",
"gl.detachShader(program, shader)",
"gl.disable(gl.BLEND)",
"gl.disableVertexAttribArray(0)",
"gl.drawArrays(gl.POINTS, 0, 0)",
"gl.drawElements(gl.POINTS, 0, gl.UNSIGNED_SHORT, 0)",
"gl.enable(gl.BLEND)",
"gl.enableVertexAttribArray(0)",
"gl.finish()",
"gl.flush()",
"gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer)",
"gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0)",
"gl.frontFace(gl.CW)",
"gl.generateMipmap(gl.TEXTURE_2D)",
"gl.hint(gl.GENERATE_MIPMAP_HINT, gl.FASTEST)",
"gl.lineWidth(0)",
"gl.linkProgram(program)",
"gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0)",
"gl.polygonOffset(0, 0)",
"gl.readPixels(0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
"gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, 0, 0)",
"gl.sampleCoverage(0, 0)",
"gl.scissor(0, 0, 0, 0)",
"gl.shaderSource(shader, '')",
"gl.stencilFunc(gl.NEVER, 0, 0)",
"gl.stencilFuncSeparate(gl.FRONT, gl.NEVER, 0, 0)",
"gl.stencilMask(0)",
"gl.stencilMaskSeparate(gl.FRONT, 0)",
"gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP)",
"gl.stencilOpSeparate(gl.FRONT, gl.KEEP, gl.KEEP, gl.KEEP)",
"gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
"gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageData)",
"gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)",
"gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas2d)",
"gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, video)",
"gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)",
"gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)",
"gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, arrayBufferView)",
"gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, imageData)",
"gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, image)",
"gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, canvas2d)",
"gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video)",
"gl.uniform1f(uniformLocation, 0)",
"gl.uniform1fv(uniformLocation, float32array)",
"gl.uniform1fv(uniformLocation, [0])",
"gl.uniform1i(uniformLocation, 0)",
"gl.uniform1iv(uniformLocation, int32array)",
"gl.uniform1iv(uniformLocation, [0])",
"gl.uniform2f(uniformLocation, 0, 0)",
"gl.uniform2fv(uniformLocation, float32array)",
"gl.uniform2fv(uniformLocation, [0, 0])",
"gl.uniform2i(uniformLocation, 0, 0)",
"gl.uniform2iv(uniformLocation, int32array)",
"gl.uniform2iv(uniformLocation, [0, 0])",
"gl.uniform3f(uniformLocation, 0, 0, 0)",
"gl.uniform3fv(uniformLocation, float32array)",
"gl.uniform3fv(uniformLocation, [0, 0, 0])",
"gl.uniform3i(uniformLocation, 0, 0, 0)",
"gl.uniform3iv(uniformLocation, int32array)",
"gl.uniform3iv(uniformLocation, [0, 0, 0])",
"gl.uniform4f(uniformLocation, 0, 0, 0, 0)",
"gl.uniform4fv(uniformLocation, float32array)",
"gl.uniform4fv(uniformLocation, [0, 0, 0, 0])",
"gl.uniform4i(uniformLocation, 0, 0, 0, 0)",
"gl.uniform4iv(uniformLocation, int32array)",
"gl.uniform4iv(uniformLocation, [0, 0, 0, 0])",
"gl.uniformMatrix2fv(uniformLocation, false, float32array)",
"gl.uniformMatrix2fv(uniformLocation, false, [0, 0, 0, 0])",
"gl.uniformMatrix3fv(uniformLocation, false, float32array)",
"gl.uniformMatrix3fv(uniformLocation, false, [0, 0, 0, 0, 0, 0, 0, 0, 0])",
"gl.uniformMatrix4fv(uniformLocation, false, float32array)",
"gl.uniformMatrix4fv(uniformLocation, false, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])",
"gl.useProgram(program)",
"gl.validateProgram(program)",
"gl.vertexAttrib1f(0, 0)",
"gl.vertexAttrib1fv(0, float32array)",
"gl.vertexAttrib1fv(0, [0])",
"gl.vertexAttrib2f(0, 0, 0)",
"gl.vertexAttrib2fv(0, float32array)",
"gl.vertexAttrib2fv(0, [0, 0])",
"gl.vertexAttrib3f(0, 0, 0, 0)",
"gl.vertexAttrib3fv(0, float32array)",
"gl.vertexAttrib3fv(0, [0, 0, 0])",
"gl.vertexAttrib4f(0, 0, 0, 0, 0)",
"gl.vertexAttrib4fv(0, float32array)",
"gl.vertexAttrib4fv(0, [0, 0, 0, 0])",
"gl.vertexAttribPointer(0, 0, gl.FLOAT, false, 0, 0)",
"gl.viewport(0, 0, 0, 0)",
];
testGLNOErrorFunctions(voidTests);
// Functions return nullable values should all return null.
var nullTests = [
"gl.getActiveAttrib(program, 0)",
"gl.getActiveUniform(program, 0)",
"gl.getAttachedShaders(program)",
"gl.getBufferParameter(gl.ARRAY_BUFFER, gl.BUFFER_SIZE)",
"gl.getContextAttributes()",
"gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME)",
"gl.getParameter(gl.CURRENT_PROGRAM)",
"gl.getProgramInfoLog(program)",
"gl.getProgramParameter(program, gl.LINK_STATUS)",
"gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH)",
"gl.getShaderInfoLog(shader)",
"gl.getShaderParameter(shader, gl.SHADER_TYPE)",
"gl.getShaderSource(shader)",
"gl.getTexParameter(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S)",
"gl.getUniform(program, uniformLocation)",
"gl.getUniformLocation(program, 'vPosition')",
"gl.getVertexAttrib(0, gl.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING)",
"gl.getSupportedExtensions()",
"gl.getExtension('" + extensionName + "')",
];
testFunctionsThatReturnNULL(nullTests);
[
[() => gl.createBuffer(), x => gl.bindBuffer(gl.ARRAY_BUFFER, x), x => gl.isBuffer(x)],
[() => gl.createFramebuffer(), x => gl.bindFramebuffer(gl.FRAMEBUFFER, x), x => gl.isFramebuffer(x)],
[() => gl.createProgram(), x => undefined, x => gl.isProgram(x)],
[() => gl.createRenderbuffer(), x => gl.bindRenderbuffer(gl.RENDERBUFFER, x), x => gl.isRenderbuffer(x)],
[() => gl.createShader(gl.VERTEX_SHADER), x => undefined, x => gl.isShader(x)],
[() => gl.createTexture(), x => gl.bindTexture(gl.TEXTURE_2D, x), x => gl.isTexture(x)],
].forEach(([fn_create, fn_bind, fn_is]) => {
const x = fn_create();
assertMsg(x, `${fn_create.toString()} -> non-null`);
assertMsg(fn_is(x) === false, `[before bind] ${fn_is.toString()} -> false`);
fn_bind(x);
fn_bind(null);
assertMsg(fn_is(x) === false, `[after bind] ${fn_is.toString()} -> false`);
});
// "Is" queries should all return false.
shouldBeFalse("gl.isEnabled(gl.BLEND)");
shouldBe("gl.getError()", "gl.NO_ERROR");
// test extensions
if (OES_vertex_array_object) {
testGLNOErrorFunctions(
[
"OES_vertex_array_object.bindVertexArrayOES(vertexArrayObject)",
"OES_vertex_array_object.isVertexArrayOES(vertexArrayObject)",
"OES_vertex_array_object.deleteVertexArrayOES(vertexArrayObject)",
]);
[
[() => OES_vertex_array_object.createVertexArrayOES(), x => OES_vertex_array_object.isVertexArrayOES(x), x => OES_vertex_array_object.isVertexArrayOES(x)],
].forEach(([fn_create, fn_bind, fn_is]) => {
const x = fn_create();
assertMsg(x, `${fn_create.toString()} -> non-null`);
assertMsg(fn_is(x) === false, `[before bind] ${fn_is.toString()} -> false`);
fn_bind(x);
fn_bind(null);
assertMsg(fn_is(x) === false, `[after bind] ${fn_is.toString()} -> false`);
});
}
testUploadingLostContextToTexture();
debug("");
finishTest();
}
</script>
</head>
<body onload="init()">
<div id="description"></div>
<div id="console"></div>
<canvas id="canvas">
<canvas id="canvas2">
</body>
</html>