blob: 88d9950fd1411e6431d0c0931846874b58eb03b5 [file] [edit]
// Tests the queries that involve the default framebuffer. Each query must have one deterministic
// answer for a given context configuration. The answer must not depend on whether the default
// framebuffer storage is allocated, on whether the default framebuffer is multisampled or
// preserved, on which compositor buffer is current, or on whether the context was restored.
//
// The output does not print the implementation dependent values, such as the sample count or the
// maximum number of draw buffers, so that the expectations are the same on all implementations.
//
// Requires js-test.js and webgl-test-utils.js.
// The errors that the GPU process reports are printed asynchronously, which would make the output
// depend on timing. The tests check the errors with getError(). Workers do not have internals.settings,
// so the page disables the errors before starting the worker.
if (self.internals && internals.settings)
internals.settings.setWebGLErrorsToConsoleEnabled(false);
var wtu = WebGLTestUtils;
var canvas;
var gl;
var extension;
var drawBuffersExtension;
var attributes;
var framebuffer;
var renderbuffer;
var readCanvas;
var readContext;
var contextLostEventCount;
// The checks evaluate their expressions in the global scope, so the values they use are global.
var queryResult;
var pixel = new Uint8Array(4);
var stateIndependentQueries;
var sameAnswersAsOtherAntialiasAndPreserveDrawingBufferValues;
function valueString(value)
{
if (value === null || value === undefined)
return "" + value;
if (ArrayBuffer.isView(value))
return "[" + Array.from(value).join(",") + "]";
return "" + value;
}
function clearErrors()
{
while (gl.getError() != gl.NO_ERROR) { }
}
function isWebGL2()
{
return gl instanceof WebGL2RenderingContext;
}
function maxDrawBuffers()
{
if (isWebGL2())
return gl.getParameter(gl.MAX_DRAW_BUFFERS);
if (drawBuffersExtension)
return gl.getParameter(drawBuffersExtension.MAX_DRAW_BUFFERS_WEBGL);
return 0;
}
function drawBuffer0()
{
return isWebGL2() ? gl.DRAW_BUFFER0 : drawBuffersExtension.DRAW_BUFFER0_WEBGL;
}
function drawBuffersAfterTheFirstAreNone()
{
for (var i = 1; i < maxDrawBuffers(); ++i) {
if (gl.getParameter(drawBuffer0() + i) !== gl.NONE)
return false;
}
return true;
}
// Checks that the expression generates the error and returns null.
function shouldGenerateErrorAndBeNull(expression, error)
{
wtu.shouldGenerateGLError(gl, error, "queryResult = " + expression);
shouldBeNull("queryResult");
}
// The implementation read format of the default framebuffer. Without alpha, the implementation may
// choose either RGB or RGBA.
function shouldBeDefaultFramebufferReadFormat(expression)
{
if (attributes.alpha)
shouldBe(expression, "gl.RGBA");
else
shouldBeTrue("[gl.RGB, gl.RGBA].includes(" + expression + ")");
}
// The values of the queries that must stay the same in every state of one configuration. The
// drawing buffer size, the viewport and the scissor box are checked separately, since a resize
// changes them.
function collectStateIndependentQueries()
{
var result = {};
var names = ["RED_BITS", "GREEN_BITS", "BLUE_BITS", "ALPHA_BITS", "DEPTH_BITS", "STENCIL_BITS", "SAMPLE_BUFFERS", "SAMPLES", "IMPLEMENTATION_COLOR_READ_FORMAT", "IMPLEMENTATION_COLOR_READ_TYPE"];
for (var name of names)
result[name] = valueString(gl.getParameter(gl[name]));
result.checkFramebufferStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
if (isWebGL2()) {
result.checkDrawFramebufferStatus = gl.checkFramebufferStatus(gl.DRAW_FRAMEBUFFER);
result.checkReadFramebufferStatus = gl.checkFramebufferStatus(gl.READ_FRAMEBUFFER);
result.READ_BUFFER = gl.getParameter(gl.READ_BUFFER);
}
var drawBuffers = [];
for (var i = 0; i < maxDrawBuffers(); ++i)
drawBuffers.push(gl.getParameter(drawBuffer0() + i));
result.drawBuffers = drawBuffers.join(",");
clearErrors();
return JSON.stringify(result);
}
// Q1, Q2, Q3 and Q4: bit depths, multisampling, implementation read format and completeness.
function checkFramebufferParameters()
{
shouldBe("gl.getParameter(gl.RED_BITS)", "8");
shouldBe("gl.getParameter(gl.GREEN_BITS)", "8");
shouldBe("gl.getParameter(gl.BLUE_BITS)", "8");
shouldBe("gl.getParameter(gl.ALPHA_BITS)", attributes.alpha ? "8" : "0");
if (!attributes.depth)
shouldBe("gl.getParameter(gl.DEPTH_BITS)", "0");
else if (isWebGL2())
shouldBe("gl.getParameter(gl.DEPTH_BITS)", "24");
else
shouldBeOneOfValues("gl.getParameter(gl.DEPTH_BITS)", [16, 24]);
shouldBe("gl.getParameter(gl.STENCIL_BITS)", attributes.stencil ? "8" : "0");
shouldBe("gl.getParameter(gl.SAMPLE_BUFFERS)", attributes.antialias ? "1" : "0");
if (attributes.antialias) {
shouldBeGreaterThan("gl.getParameter(gl.SAMPLES)", "0");
if (isWebGL2())
shouldBeTrue("Array.from(gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES)).includes(gl.getParameter(gl.SAMPLES))");
} else
shouldBe("gl.getParameter(gl.SAMPLES)", "0");
shouldBeDefaultFramebufferReadFormat("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)");
shouldBe("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE)", "gl.UNSIGNED_BYTE");
wtu.framebufferStatusShouldBe(gl, gl.FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, "FRAMEBUFFER");
if (isWebGL2()) {
wtu.framebufferStatusShouldBe(gl, gl.DRAW_FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, "DRAW_FRAMEBUFFER");
wtu.framebufferStatusShouldBe(gl, gl.READ_FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, "READ_FRAMEBUFFER");
}
}
// Q5 and Q6: getFramebufferAttachmentParameter with the default framebuffer bound. The queries
// that are expected to generate errors run only when includeErrorCases is set, so that they are not
// repeated in every state.
function checkAttachmentParameters(includeErrorCases)
{
if (!isWebGL2()) {
if (!includeErrorCases)
return;
shouldGenerateErrorAndBeNull("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", gl.INVALID_OPERATION);
shouldGenerateErrorAndBeNull("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", gl.INVALID_OPERATION);
return;
}
var sizes = {
FRAMEBUFFER_ATTACHMENT_RED_SIZE: attachment => attachment == "BACK" ? 8 : 0,
FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: attachment => attachment == "BACK" ? 8 : 0,
FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: attachment => attachment == "BACK" ? 8 : 0,
FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: attachment => attachment == "BACK" && attributes.alpha ? 8 : 0,
FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: attachment => attachment == "DEPTH" ? 24 : 0,
FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: attachment => attachment == "STENCIL" ? 8 : 0,
};
var notApplicable = ["FRAMEBUFFER_ATTACHMENT_OBJECT_NAME", "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL", "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE", "FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER"];
for (var target of ["FRAMEBUFFER", "DRAW_FRAMEBUFFER", "READ_FRAMEBUFFER"]) {
for (var attachment of ["BACK", "DEPTH", "STENCIL"]) {
var exists = attachment == "BACK" || (attachment == "DEPTH" && attributes.depth) || (attachment == "STENCIL" && attributes.stencil);
var expression = pname => "gl.getFramebufferAttachmentParameter(gl." + target + ", gl." + attachment + ", gl." + pname + ")";
if (!exists) {
shouldBe(expression("FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE"), "gl.NONE");
if (!includeErrorCases)
continue;
shouldGenerateErrorAndBeNull(expression("FRAMEBUFFER_ATTACHMENT_RED_SIZE"), gl.INVALID_OPERATION);
shouldGenerateErrorAndBeNull(expression("FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"), gl.INVALID_OPERATION);
continue;
}
shouldBe(expression("FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE"), "gl.FRAMEBUFFER_DEFAULT");
for (var pname in sizes)
shouldBe(expression(pname), "" + sizes[pname](attachment));
shouldBe(expression("FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"), "gl.UNSIGNED_NORMALIZED");
shouldBe(expression("FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING"), "gl.LINEAR");
if (!includeErrorCases)
continue;
for (var pname of notApplicable)
shouldGenerateErrorAndBeNull(expression(pname), gl.INVALID_ENUM);
}
if (!includeErrorCases)
continue;
for (var attachment of ["COLOR_ATTACHMENT0", "DEPTH_ATTACHMENT", "STENCIL_ATTACHMENT", "DEPTH_STENCIL_ATTACHMENT"])
shouldGenerateErrorAndBeNull("gl.getFramebufferAttachmentParameter(gl." + target + ", gl." + attachment + ", gl.FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE)", gl.INVALID_ENUM);
}
// The sizes must agree with the getParameter() bit depths.
if (attributes.depth)
shouldBe("gl.getFramebufferAttachmentParameter(gl.FRAMEBUFFER, gl.DEPTH, gl.FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE)", "gl.getParameter(gl.DEPTH_BITS)");
}
// Q7 and Q8: draw buffer and read buffer state, and the bindings.
function checkDrawReadBuffersAndBindings()
{
if (maxDrawBuffers()) {
shouldBe("gl.getParameter(drawBuffer0())", "gl.BACK");
shouldBeTrue("drawBuffersAfterTheFirstAreNone()");
}
if (isWebGL2())
shouldBe("gl.getParameter(gl.READ_BUFFER)", "gl.BACK");
shouldBeNull("gl.getParameter(gl.FRAMEBUFFER_BINDING)");
if (isWebGL2()) {
shouldBeNull("gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING)");
shouldBeNull("gl.getParameter(gl.READ_FRAMEBUFFER_BINDING)");
}
}
// Q9: the drawing buffer size and the context attributes.
function checkSizeAndAttributes(expectedWidth, expectedHeight)
{
shouldBe("gl.drawingBufferWidth", "" + expectedWidth);
shouldBe("gl.drawingBufferHeight", "" + expectedHeight);
shouldBe("JSON.stringify(gl.getContextAttributes())", "JSON.stringify(attributes)");
}
function checkNoErrorsAndNotLost()
{
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after the queries");
shouldBeFalse("gl.isContextLost()");
shouldBe("contextLostEventCount", "0");
}
// The size of the allocated default framebuffer storage, or null if it can't be observed.
function allocatedSize()
{
if (!self.internals || !internals.webglDefaultFramebufferAllocatedSize)
return null;
return internals.webglDefaultFramebufferAllocatedSize(gl).join("x");
}
// expectedAllocatedSize is the size of the default framebuffer storage that is allocated in this
// state, or "0x0" if none. The queries must not allocate or reallocate the storage.
function checkState(stateName, expectedWidth, expectedHeight, expectedAllocatedSize)
{
debug(stateName + ":");
// Print the read format that the implementation chose, so that the expectations record it. The
// other states must give the same answer as the fresh state.
if (stateName == "fresh" && !attributes.alpha)
debug("IMPLEMENTATION_COLOR_READ_FORMAT: " + wtu.glEnumToString(gl, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)));
checkFramebufferParameters();
checkAttachmentParameters(stateName == "fresh");
checkDrawReadBuffersAndBindings();
checkSizeAndAttributes(expectedWidth, expectedHeight);
checkNoErrorsAndNotLost();
shouldBe("collectStateIndependentQueries()", "stateIndependentQueries");
if (allocatedSize() !== null)
shouldBeEqualToString("allocatedSize()", expectedAllocatedSize);
// Reading allocates the storage.
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel)");
}
// Q10: the viewport and the scissor box are set to the drawing buffer size at creation, and are
// not changed by a resize.
function checkViewportAndScissor(width, height)
{
var expected = "[0, 0, " + width + ", " + height + "]";
shouldBe("Array.from(gl.getParameter(gl.VIEWPORT))", expected);
shouldBe("Array.from(gl.getParameter(gl.SCISSOR_BOX))", expected);
}
// Q7: DRAW_BUFFER0 and READ_BUFFER can be set to NONE and back.
function checkSetDrawAndReadBufferNone()
{
debug("DRAW_BUFFER0 and READ_BUFFER set to NONE and back:");
if (maxDrawBuffers()) {
var drawBuffers = isWebGL2() ? "gl.drawBuffers" : "drawBuffersExtension.drawBuffersWEBGL";
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, drawBuffers + "([gl.NONE])");
shouldBe("gl.getParameter(drawBuffer0())", "gl.NONE");
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, drawBuffers + "([gl.BACK])");
shouldBe("gl.getParameter(drawBuffer0())", "gl.BACK");
}
if (isWebGL2()) {
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readBuffer(gl.NONE)");
shouldBe("gl.getParameter(gl.READ_BUFFER)", "gl.NONE");
// Q12: the implementation read format and type of a framebuffer with read buffer NONE.
shouldGenerateErrorAndBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)", gl.INVALID_OPERATION);
shouldGenerateErrorAndBeNull("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE)", gl.INVALID_OPERATION);
wtu.shouldGenerateGLError(gl, gl.NO_ERROR, "gl.readBuffer(gl.BACK)");
shouldBe("gl.getParameter(gl.READ_BUFFER)", "gl.BACK");
shouldBeDefaultFramebufferReadFormat("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)");
}
checkNoErrorsAndNotLost();
}
// Q11: with a user framebuffer bound for drawing or for reading, the queries answer for the
// framebuffer bound to the respective binding point.
function checkMixedBindings()
{
if (!isWebGL2())
return;
framebuffer = gl.createFramebuffer();
renderbuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, 2, 2);
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, framebuffer);
gl.framebufferRenderbuffer(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, renderbuffer);
debug("user framebuffer for drawing, default framebuffer for reading:");
shouldBeDefaultFramebufferReadFormat("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)");
shouldBe("gl.getParameter(gl.DEPTH_BITS)", "0");
shouldBe("gl.getParameter(gl.STENCIL_BITS)", "0");
shouldBe("gl.getParameter(gl.ALPHA_BITS)", "8");
shouldBe("gl.getParameter(gl.SAMPLES)", "0");
wtu.framebufferStatusShouldBe(gl, gl.READ_FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, "READ_FRAMEBUFFER");
debug("default framebuffer for drawing, user framebuffer for reading:");
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, null);
gl.bindFramebuffer(gl.READ_FRAMEBUFFER, framebuffer);
shouldBe("gl.getParameter(gl.DEPTH_BITS)", attributes.depth ? "24" : "0");
shouldBe("gl.getParameter(gl.STENCIL_BITS)", attributes.stencil ? "8" : "0");
shouldBe("gl.getParameter(gl.ALPHA_BITS)", attributes.alpha ? "8" : "0");
shouldBe("gl.getParameter(gl.SAMPLE_BUFFERS)", attributes.antialias ? "1" : "0");
shouldBe("gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT)", "gl.RGBA");
wtu.framebufferStatusShouldBe(gl, gl.DRAW_FRAMEBUFFER, gl.FRAMEBUFFER_COMPLETE, "DRAW_FRAMEBUFFER");
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.deleteFramebuffer(framebuffer);
gl.deleteRenderbuffer(renderbuffer);
framebuffer = null;
renderbuffer = null;
checkNoErrorsAndNotLost();
}
// Q13: the queries do not lose a pending error.
function checkPendingErrorIsKept()
{
debug("a pending error is kept across the queries:");
gl.enable(0x1234);
for (var name of ["RED_BITS", "ALPHA_BITS", "DEPTH_BITS", "STENCIL_BITS", "SAMPLES", "SAMPLE_BUFFERS", "IMPLEMENTATION_COLOR_READ_FORMAT", "IMPLEMENTATION_COLOR_READ_TYPE"])
gl.getParameter(gl[name]);
gl.checkFramebufferStatus(gl.FRAMEBUFFER);
wtu.glErrorShouldBe(gl, gl.INVALID_ENUM, "the pending error");
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "after the pending error");
}
var vertexShaderSource = "attribute vec4 position; void main() { gl_Position = position; gl_PointSize = 1.0; }";
var fragmentShaderSource = "precision mediump float; void main() { gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); }";
function draw()
{
gl.clearColor(1, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
var program = gl.createProgram();
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.disableVertexAttribArray(0);
gl.vertexAttrib4f(0, 0, 0, 0, 1);
gl.drawArrays(gl.POINTS, 0, 1);
gl.useProgram(null);
gl.deleteProgram(program);
gl.deleteShader(vertexShader);
gl.deleteShader(fragmentShader);
}
// The animation frame callbacks run before the canvases are presented in the rendering update. A
// task posted from the callback runs after the rendering update.
function afterNextRenderingUpdate()
{
return new Promise(resolve => requestAnimationFrame(() => setTimeout(resolve, 0)));
}
async function present()
{
await afterNextRenderingUpdate();
}
function loseAndRestoreContext()
{
return new Promise(function(resolve, reject) {
canvas.addEventListener("webglcontextlost", function(event) {
event.preventDefault();
setTimeout(function() { extension.restoreContext(); }, 0);
}, { once: true });
canvas.addEventListener("webglcontextrestored", function() { resolve(); }, { once: true });
setTimeout(function() { reject(new Error("timed out waiting for the context to be restored")); }, 10000);
extension.loseContext();
});
}
// Loses the context, then drops the canvas, the context and the objects of the previous
// configuration and collects them, so that the number of the live contexts does not grow to the
// limit after which the implementation recycles the oldest contexts.
function releaseContext()
{
if (gl && !gl.isContextLost()) {
var loseContext = gl.getExtension("WEBGL_lose_context");
if (loseContext)
loseContext.loseContext();
}
if (canvas && canvas.parentNode)
canvas.parentNode.removeChild(canvas);
canvas = null;
gl = null;
extension = null;
drawBuffersExtension = null;
attributes = null;
framebuffer = null;
renderbuffer = null;
readCanvas = null;
readContext = null;
queryResult = null;
gc();
}
function configurationString(parameters)
{
return "alpha: " + parameters.alpha + ", premultipliedAlpha: " + parameters.premultipliedAlpha + ", antialias: " + parameters.antialias + ", preserveDrawingBuffer: " + parameters.preserveDrawingBuffer + ", depth: " + parameters.depth + ", stencil: " + parameters.stencil;
}
// Signatures of the queries per configuration, keyed by the configuration without antialias and
// preserveDrawingBuffer, to check that the aliased, multisampled and preserved default framebuffers
// give the same answers.
var configurationSignatures = {};
function checkSameAsOtherConfigurations(version, parameters)
{
var key = JSON.stringify({ version: version, alpha: parameters.alpha, premultipliedAlpha: parameters.premultipliedAlpha, depth: parameters.depth, stencil: parameters.stencil });
var signature = JSON.parse(collectStateIndependentQueries());
delete signature.SAMPLE_BUFFERS;
delete signature.SAMPLES;
signature = JSON.stringify(signature);
if (!(key in configurationSignatures)) {
configurationSignatures[key] = signature;
return;
}
sameAnswersAsOtherAntialiasAndPreserveDrawingBufferValues = configurationSignatures[key] == signature;
shouldBeTrue("sameAnswersAsOtherAntialiasAndPreserveDrawingBufferValues");
}
async function runConfiguration(version, parameters)
{
releaseContext();
debug("");
debug("WebGL " + version + ", " + configurationString(parameters));
canvas = document.createElement("canvas");
canvas.width = 2;
canvas.height = 2;
document.body.appendChild(canvas);
contextLostEventCount = 0;
// Count the context loss events of this canvas only, not the ones of the canvases of the
// earlier configurations.
var thisCanvas = canvas;
canvas.addEventListener("webglcontextlost", () => { if (canvas == thisCanvas) ++contextLostEventCount; });
gl = canvas.getContext(version == 2 ? "webgl2" : "webgl", parameters);
if (!gl) {
testFailed("Unable to create the context.");
return;
}
extension = gl.getExtension("WEBGL_lose_context");
if (version == 1)
drawBuffersExtension = gl.getExtension("WEBGL_draw_buffers");
attributes = gl.getContextAttributes();
if (attributes.antialias != parameters.antialias) {
debug("Skipped: antialias is not available.");
return;
}
stateIndependentQueries = collectStateIndependentQueries();
checkState("fresh", 2, 2, "0x0");
checkViewportAndScissor(2, 2);
checkSameAsOtherConfigurations(version, parameters);
checkSetDrawAndReadBufferNone();
checkMixedBindings();
checkPendingErrorIsKept();
draw();
checkState("drawn", 2, 2, "2x2");
await present();
checkState("presented", 2, 2, "2x2");
canvas.toDataURL();
readCanvas = document.createElement("canvas");
readCanvas.width = 2;
readCanvas.height = 2;
readContext = readCanvas.getContext("2d");
readContext.drawImage(canvas, 0, 0);
checkState("after internal reads", 2, 2, "2x2");
canvas.width = 3;
canvas.height = 3;
// The storage is reallocated to the new size when it is next used.
checkState("resized", 3, 3, "2x2");
checkViewportAndScissor(2, 2);
await loseAndRestoreContext();
contextLostEventCount = 0;
// The extensions must be enabled again after a restore.
if (version == 1)
drawBuffersExtension = gl.getExtension("WEBGL_draw_buffers");
checkState("restored", 3, 3, "0x0");
canvas.width = 0;
canvas.height = 0;
// The readPixels() of the previous state allocated the storage. A size that shrinks the storage
// reallocates it right away.
checkState("zero size canvas", 1, 1, "1x1");
}
// Q15: the same queries on an OffscreenCanvas context, on the main thread or in a worker.
// transferToImageBitmap() uses the display buffer, like presenting a canvas element.
async function runOffscreenConfiguration(version, parameters)
{
releaseContext();
debug("");
debug("OffscreenCanvas, WebGL " + version + ", " + configurationString(parameters));
canvas = new OffscreenCanvas(2, 2);
contextLostEventCount = 0;
// Count the context loss events of this canvas only, not the ones of the canvases of the
// earlier configurations.
var thisCanvas = canvas;
canvas.addEventListener("webglcontextlost", () => { if (canvas == thisCanvas) ++contextLostEventCount; });
gl = canvas.getContext(version == 2 ? "webgl2" : "webgl", parameters);
if (!gl) {
testFailed("Unable to create the context.");
return;
}
if (version == 1)
drawBuffersExtension = gl.getExtension("WEBGL_draw_buffers");
attributes = gl.getContextAttributes();
if (attributes.antialias != parameters.antialias) {
debug("Skipped: antialias is not available.");
return;
}
stateIndependentQueries = collectStateIndependentQueries();
checkState("fresh", 2, 2, "0x0");
draw();
checkState("drawn", 2, 2, "2x2");
canvas.transferToImageBitmap().close();
checkState("after transferToImageBitmap()", 2, 2, "2x2");
canvas.width = 3;
canvas.height = 3;
checkState("resized", 3, 3, "2x2");
}
// The context configurations: every combination of the alpha variant, depth, stencil, antialias and
// preserveDrawingBuffer for WebGL 1 and WebGL 2. premultipliedAlpha has no effect without alpha.
function generateConfigurations()
{
var alphaVariants = [
{ alpha: true, premultipliedAlpha: true },
{ alpha: true, premultipliedAlpha: false },
{ alpha: false, premultipliedAlpha: true },
];
var configurations = [];
for (var version of [1, 2]) {
for (var alphaVariant of alphaVariants) {
for (var depth of [true, false]) {
for (var stencil of [true, false]) {
for (var antialias of [false, true]) {
for (var preserveDrawingBuffer of [false, true])
configurations.push({ version: version, parameters: { alpha: alphaVariant.alpha, premultipliedAlpha: alphaVariant.premultipliedAlpha, antialias: antialias, preserveDrawingBuffer: preserveDrawingBuffer, depth: depth, stencil: stencil } });
}
}
}
}
}
return configurations;
}
// Runs slice number `slice` of `sliceCount` equal slices of the runs of each of
// runConfigurationFunctions for every context configuration.
async function runConfigurations(runConfigurationFunctions, slice, sliceCount)
{
var runs = [];
for (var runConfigurationFunction of runConfigurationFunctions) {
for (var configuration of generateConfigurations())
runs.push({ runConfigurationFunction: runConfigurationFunction, configuration: configuration });
}
runs = runs.slice(Math.floor(runs.length * (slice - 1) / sliceCount), Math.floor(runs.length * slice / sliceCount));
try {
for (var run of runs)
await run.runConfigurationFunction(run.configuration.version, run.configuration.parameters);
} catch (error) {
testFailed("" + error);
}
releaseContext();
finishJSTest();
}
// Runs a slice of the tests with a canvas element and, when available, with an OffscreenCanvas.
function runDefaultFramebufferQueryTests(slice, sliceCount)
{
return runConfigurations(self.OffscreenCanvas ? [runConfiguration, runOffscreenConfiguration] : [runConfiguration], slice, sliceCount);
}
// Runs a slice of the tests with an OffscreenCanvas, for example in a worker.
function runOffscreenDefaultFramebufferQueryTests(slice, sliceCount)
{
return runConfigurations([runOffscreenConfiguration], slice, sliceCount);
}