зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1041158 - Properly cleanup the framebuffer binding after generating a screenshot from a webgl context, r=rcampbell
This commit is contained in:
Родитель
545b119ad1
Коммит
85551493a0
|
@ -5,6 +5,7 @@ support-files =
|
|||
doc_simple-canvas-bitmasks.html
|
||||
doc_simple-canvas-deep-stack.html
|
||||
doc_simple-canvas-transparent.html
|
||||
doc_webgl-bindings.html
|
||||
doc_webgl-enum.html
|
||||
head.js
|
||||
|
||||
|
@ -17,6 +18,7 @@ support-files =
|
|||
[browser_canvas-actor-test-07.js]
|
||||
[browser_canvas-actor-test-08.js]
|
||||
[browser_canvas-actor-test-09.js]
|
||||
[browser_canvas-actor-test-10.js]
|
||||
[browser_canvas-frontend-call-highlight.js]
|
||||
[browser_canvas-frontend-call-list.js]
|
||||
[browser_canvas-frontend-call-search.js]
|
||||
|
|
|
@ -18,9 +18,7 @@ function ifTestingSupported() {
|
|||
ok(true, "Target automatically navigated when the front was set up.");
|
||||
|
||||
let snapshotActor = yield front.recordAnimationFrame();
|
||||
|
||||
let animationOverview = yield snapshotActor.getOverview();
|
||||
|
||||
let functionCalls = animationOverview.calls;
|
||||
|
||||
is(functionCalls[0].name, "clearRect",
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the correct framebuffer, renderbuffer and textures are re-bound
|
||||
* after generating screenshots using the actor.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let [target, debuggee, front] = yield initCanavsDebuggerBackend(WEBGL_BINDINGS_URL);
|
||||
|
||||
let navigated = once(target, "navigate");
|
||||
|
||||
yield front.setup({ reload: true });
|
||||
ok(true, "The front was setup up successfully.");
|
||||
|
||||
yield navigated;
|
||||
ok(true, "Target automatically navigated when the front was set up.");
|
||||
|
||||
let snapshotActor = yield front.recordAnimationFrame();
|
||||
let animationOverview = yield snapshotActor.getOverview();
|
||||
let functionCalls = animationOverview.calls;
|
||||
|
||||
let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
|
||||
is(firstScreenshot.index, -1,
|
||||
"The first screenshot didn't encounter any draw call.");
|
||||
is(firstScreenshot.width, 128,
|
||||
"The first screenshot has the correct width.");
|
||||
is(firstScreenshot.height, 128,
|
||||
"The first screenshot has the correct height.");
|
||||
is(firstScreenshot.flipped, true,
|
||||
"The first screenshot has the correct 'flipped' flag.");
|
||||
is(firstScreenshot.pixels.length, 0,
|
||||
"The first screenshot should be empty.");
|
||||
|
||||
let gl = debuggee.gl;
|
||||
is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
|
||||
"The debuggee's gl context framebuffer wasn't changed.");
|
||||
is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
|
||||
"The debuggee's gl context renderbuffer wasn't changed.");
|
||||
is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
|
||||
"The debuggee's gl context texture binding wasn't changed.");
|
||||
|
||||
let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
|
||||
is(secondScreenshot.index, 1,
|
||||
"The second screenshot has the correct index.");
|
||||
is(secondScreenshot.width, 128,
|
||||
"The second screenshot has the correct width.");
|
||||
is(secondScreenshot.height, 128,
|
||||
"The second screenshot has the correct height.");
|
||||
is(secondScreenshot.flipped, true,
|
||||
"The second screenshot has the correct 'flipped' flag.");
|
||||
is(secondScreenshot.pixels.length, 128 * 128,
|
||||
"The second screenshot should not be empty.");
|
||||
is(new Uint8Array(secondScreenshot.pixels.buffer)[0], 0,
|
||||
"The second screenshot has the correct red component.");
|
||||
is(new Uint8Array(secondScreenshot.pixels.buffer)[1], 0,
|
||||
"The second screenshot has the correct green component.");
|
||||
is(new Uint8Array(secondScreenshot.pixels.buffer)[2], 255,
|
||||
"The second screenshot has the correct blue component.");
|
||||
is(new Uint8Array(secondScreenshot.pixels.buffer)[3], 255,
|
||||
"The second screenshot has the correct alpha component.");
|
||||
|
||||
let gl = debuggee.gl;
|
||||
is(gl.getParameter(gl.FRAMEBUFFER_BINDING), debuggee.customFramebuffer,
|
||||
"The debuggee's gl context framebuffer still wasn't changed.");
|
||||
is(gl.getParameter(gl.RENDERBUFFER_BINDING), debuggee.customRenderbuffer,
|
||||
"The debuggee's gl context renderbuffer still wasn't changed.");
|
||||
is(gl.getParameter(gl.TEXTURE_BINDING_2D), debuggee.customTexture,
|
||||
"The debuggee's gl context texture binding still wasn't changed.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>WebGL editor test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas" width="128" height="128"></canvas>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
let canvas, gl;
|
||||
let customFramebuffer;
|
||||
let customRenderbuffer;
|
||||
let customTexture;
|
||||
|
||||
window.onload = function() {
|
||||
canvas = document.querySelector("canvas");
|
||||
gl = canvas.getContext("webgl", { preserveDrawingBuffer: true });
|
||||
gl.clearColor(1.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
customFramebuffer = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, customFramebuffer);
|
||||
|
||||
customRenderbuffer = gl.createRenderbuffer();
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, customRenderbuffer);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 128, 128);
|
||||
|
||||
customTexture = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, customTexture);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_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, 128, 128, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||
|
||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, customTexture, 0);
|
||||
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, customRenderbuffer);
|
||||
|
||||
gl.clearColor(0.0, 1.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
drawScene();
|
||||
}
|
||||
|
||||
function drawScene() {
|
||||
gl.clearColor(0.0, 0.0, 1.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
window.requestAnimationFrame(drawScene);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -30,6 +30,7 @@ const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
|
|||
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
|
||||
const SIMPLE_CANVAS_DEEP_STACK_URL = EXAMPLE_URL + "doc_simple-canvas-deep-stack.html";
|
||||
const WEBGL_ENUM_URL = EXAMPLE_URL + "doc_webgl-enum.html";
|
||||
const WEBGL_BINDINGS_URL = EXAMPLE_URL + "doc_webgl-bindings.html";
|
||||
|
||||
// All tests are asynchronous.
|
||||
waitForExplicitFinish();
|
||||
|
|
|
@ -148,7 +148,7 @@ let FrameSnapshotActor = protocol.ActorClass({
|
|||
// To get a screenshot, replay all the steps necessary to render the frame,
|
||||
// by invoking the context calls up to and including the specified one.
|
||||
// This will be done in a custom framebuffer in case of a WebGL context.
|
||||
let { replayContext, lastDrawCallIndex } = ContextUtils.replayAnimationFrame({
|
||||
let replayData = ContextUtils.replayAnimationFrame({
|
||||
contextType: global,
|
||||
canvas: canvas,
|
||||
calls: calls,
|
||||
|
@ -156,22 +156,23 @@ let FrameSnapshotActor = protocol.ActorClass({
|
|||
last: index
|
||||
});
|
||||
|
||||
let { replayContext, lastDrawCallIndex, doCleanup } = replayData;
|
||||
let screenshot;
|
||||
|
||||
// Depending on the canvas' context, generating a screenshot is done
|
||||
// in different ways. In case of the WebGL context, we also need to reset
|
||||
// the framebuffer binding to the default value.
|
||||
// in different ways.
|
||||
if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
|
||||
screenshot = ContextUtils.getPixelsForWebGL(replayContext);
|
||||
replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, null);
|
||||
screenshot.flipped = true;
|
||||
}
|
||||
// In case of 2D contexts, no additional special treatment is necessary.
|
||||
else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
|
||||
} else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
|
||||
screenshot = ContextUtils.getPixelsFor2D(replayContext);
|
||||
screenshot.flipped = false;
|
||||
}
|
||||
|
||||
// In case of the WebGL context, we also need to reset the framebuffer
|
||||
// binding to the original value, after generating the screenshot.
|
||||
doCleanup();
|
||||
|
||||
screenshot.index = lastDrawCallIndex;
|
||||
return screenshot;
|
||||
}, {
|
||||
|
@ -368,7 +369,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
|
|||
let index = this._lastDrawCallIndex;
|
||||
let width = this._lastContentCanvasWidth;
|
||||
let height = this._lastContentCanvasHeight;
|
||||
let flipped = this._lastThumbnailFlipped;
|
||||
let flipped = !!this._lastThumbnailFlipped; // undefined -> false
|
||||
let pixels = ContextUtils.getPixelStorage()["32bit"];
|
||||
let lastDrawCallScreenshot = {
|
||||
index: index,
|
||||
|
@ -584,29 +585,35 @@ let ContextUtils = {
|
|||
* @param number last
|
||||
* The last (inclusive) function call to end at.
|
||||
* @return object
|
||||
* The context on which the specified calls were invoked and the
|
||||
* last registered draw call's index.
|
||||
* The context on which the specified calls were invoked, the
|
||||
* last registered draw call's index and a cleanup function, which
|
||||
* needs to be called whenever any potential followup work is finished.
|
||||
*/
|
||||
replayAnimationFrame: function({ contextType, canvas, calls, first, last }) {
|
||||
let w = canvas.width;
|
||||
let h = canvas.height;
|
||||
|
||||
let replayCanvas;
|
||||
let replayContext;
|
||||
let customFramebuffer;
|
||||
let lastDrawCallIndex = -1;
|
||||
let doCleanup = () => {};
|
||||
|
||||
// In case of WebGL contexts, rendering will be done offscreen, in a
|
||||
// custom framebuffer, but on the provided canvas context.
|
||||
// custom framebuffer, but using the same provided context. This is
|
||||
// necessary because it's very memory-unfriendly to rebuild all the
|
||||
// required GL state (like recompiling shaders, setting global flags, etc.)
|
||||
// in an entirely new canvas. However, special care is needed to not
|
||||
// permanently affect the existing GL state in the process.
|
||||
if (contextType == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
|
||||
replayCanvas = canvas;
|
||||
replayContext = this.getWebGLContext(replayCanvas);
|
||||
customFramebuffer = this.createBoundFramebuffer(replayContext, w, h);
|
||||
let gl = replayContext = this.getWebGLContext(canvas);
|
||||
let { newFramebuffer, oldFramebuffer } = this.createBoundFramebuffer(gl, w, h);
|
||||
customFramebuffer = newFramebuffer;
|
||||
doCleanup = () => gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
|
||||
}
|
||||
// In case of 2D contexts, draw everything on a separate canvas context.
|
||||
else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) {
|
||||
let contentDocument = canvas.ownerDocument;
|
||||
replayCanvas = contentDocument.createElement("canvas");
|
||||
let replayCanvas = contentDocument.createElement("canvas");
|
||||
replayCanvas.width = w;
|
||||
replayCanvas.height = h;
|
||||
replayContext = replayCanvas.getContext("2d");
|
||||
|
@ -621,23 +628,24 @@ let ContextUtils = {
|
|||
// to the default value, since we want to perform the rendering offscreen.
|
||||
if (name == "bindFramebuffer" && args[1] == null) {
|
||||
replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer);
|
||||
continue;
|
||||
}
|
||||
if (type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
replayContext[name].apply(replayContext, args);
|
||||
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
|
||||
replayContext[name] = args;
|
||||
} else {
|
||||
if (type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
replayContext[name].apply(replayContext, args);
|
||||
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
|
||||
replayContext[name] = args;
|
||||
} else {
|
||||
// Ignore getter calls.
|
||||
}
|
||||
if (CanvasFront.DRAW_CALLS.has(name)) {
|
||||
lastDrawCallIndex = i;
|
||||
}
|
||||
// Ignore getter calls.
|
||||
}
|
||||
if (CanvasFront.DRAW_CALLS.has(name)) {
|
||||
lastDrawCallIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
replayContext: replayContext,
|
||||
lastDrawCallIndex: lastDrawCallIndex
|
||||
lastDrawCallIndex: lastDrawCallIndex,
|
||||
doCleanup: doCleanup
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -692,16 +700,21 @@ let ContextUtils = {
|
|||
* The generated framebuffer object.
|
||||
*/
|
||||
createBoundFramebuffer: function(gl, width, height) {
|
||||
let framebuffer = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
||||
let oldFramebuffer = gl.getParameter(gl.FRAMEBUFFER_BINDING);
|
||||
let oldRenderbufferBinding = gl.getParameter(gl.RENDERBUFFER_BINDING);
|
||||
let oldTextureBinding = gl.getParameter(gl.TEXTURE_BINDING_2D);
|
||||
|
||||
// Use a texture as the color rendebuffer attachment, since consumenrs of
|
||||
let newFramebuffer = gl.createFramebuffer();
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, newFramebuffer);
|
||||
|
||||
// Use a texture as the color rendebuffer attachment, since consumers of
|
||||
// this function will most likely want to read the rendered pixels back.
|
||||
let colorBuffer = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, colorBuffer);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
||||
gl.generateMipmap(gl.TEXTURE_2D);
|
||||
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);
|
||||
|
||||
let depthBuffer = gl.createRenderbuffer();
|
||||
|
@ -711,10 +724,10 @@ let ContextUtils = {
|
|||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorBuffer, 0);
|
||||
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
|
||||
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, null);
|
||||
gl.bindTexture(gl.TEXTURE_2D, oldTextureBinding);
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbufferBinding);
|
||||
|
||||
return framebuffer;
|
||||
return { oldFramebuffer, newFramebuffer };
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче