зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1041225 - Generating a screenshot is very slow when the content canvas has obnoxious dimensions, r=rcampbell
This commit is contained in:
Родитель
80412ceb9d
Коммит
a8901b62ce
|
@ -207,8 +207,8 @@ let SnapshotsListView = Heritage.extend(WidgetMethods, {
|
|||
|
||||
let thumbnail = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnail.className = "snapshot-item-thumbnail";
|
||||
thumbnail.width = CanvasFront.THUMBNAIL_HEIGHT;
|
||||
thumbnail.height = CanvasFront.THUMBNAIL_HEIGHT;
|
||||
thumbnail.width = CanvasFront.THUMBNAIL_SIZE;
|
||||
thumbnail.height = CanvasFront.THUMBNAIL_SIZE;
|
||||
|
||||
let title = document.createElement("label");
|
||||
title.className = "plain snapshot-item-title";
|
||||
|
@ -712,14 +712,16 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||
* A single "snapshot-image" instance received from the backend.
|
||||
*/
|
||||
showScreenshot: function(screenshot) {
|
||||
let { index, width, height, flipped, pixels } = screenshot;
|
||||
let { index, width, height, scaling, flipped, pixels } = screenshot;
|
||||
|
||||
let screenshotNode = $("#screenshot-image");
|
||||
screenshotNode.setAttribute("flipped", flipped);
|
||||
drawBackground("screenshot-rendering", width, height, pixels);
|
||||
|
||||
let dimensionsNode = $("#screenshot-dimensions");
|
||||
dimensionsNode.setAttribute("value", ~~width + " x " + ~~height);
|
||||
let actualWidth = (width / scaling) | 0;
|
||||
let actualHeight = (height / scaling) | 0;
|
||||
dimensionsNode.setAttribute("value", actualWidth + " x " + actualHeight);
|
||||
|
||||
window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
|
||||
},
|
||||
|
@ -754,8 +756,8 @@ let CallsListView = Heritage.extend(WidgetMethods, {
|
|||
|
||||
let thumbnailNode = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnailNode.setAttribute("flipped", flipped);
|
||||
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_HEIGHT, width);
|
||||
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_HEIGHT, height);
|
||||
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width);
|
||||
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height);
|
||||
drawImage(thumbnailNode, width, height, pixels, { centered: true });
|
||||
|
||||
thumbnailNode.className = "filmstrip-thumbnail";
|
||||
|
|
|
@ -24,9 +24,11 @@ function ifTestingSupported() {
|
|||
let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
|
||||
is(firstScreenshot.index, -1,
|
||||
"The first screenshot didn't encounter any draw call.");
|
||||
is(firstScreenshot.width, 128,
|
||||
is(firstScreenshot.scaling, 0.25,
|
||||
"The first screenshot has the correct scaling.");
|
||||
is(firstScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
|
||||
"The first screenshot has the correct width.");
|
||||
is(firstScreenshot.height, 128,
|
||||
is(firstScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
|
||||
"The first screenshot has the correct height.");
|
||||
is(firstScreenshot.flipped, true,
|
||||
"The first screenshot has the correct 'flipped' flag.");
|
||||
|
@ -40,17 +42,27 @@ function ifTestingSupported() {
|
|||
"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.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[0], 128,
|
||||
"The debuggee's gl context viewport's left coord. wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[1], 256,
|
||||
"The debuggee's gl context viewport's left coord. wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[2], 384,
|
||||
"The debuggee's gl context viewport's left coord. wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[3], 512,
|
||||
"The debuggee's gl context viewport's left coord. 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,
|
||||
is(secondScreenshot.width, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
|
||||
"The second screenshot has the correct width.");
|
||||
is(secondScreenshot.height, 128,
|
||||
is(secondScreenshot.height, CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT,
|
||||
"The second screenshot has the correct height.");
|
||||
is(secondScreenshot.scaling, 0.25,
|
||||
"The second screenshot has the correct scaling.");
|
||||
is(secondScreenshot.flipped, true,
|
||||
"The second screenshot has the correct 'flipped' flag.");
|
||||
is(secondScreenshot.pixels.length, 128 * 128,
|
||||
is(secondScreenshot.pixels.length, Math.pow(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, 2),
|
||||
"The second screenshot should not be empty.");
|
||||
is(new Uint8Array(secondScreenshot.pixels.buffer)[0], 0,
|
||||
"The second screenshot has the correct red component.");
|
||||
|
@ -68,6 +80,14 @@ function ifTestingSupported() {
|
|||
"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.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[0], 128,
|
||||
"The debuggee's gl context viewport's left coord. still wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[1], 256,
|
||||
"The debuggee's gl context viewport's left coord. still wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[2], 384,
|
||||
"The debuggee's gl context viewport's left coord. still wasn't changed.");
|
||||
is(gl.getParameter(gl.VIEWPORT)[3], 512,
|
||||
"The debuggee's gl context viewport's left coord. still wasn't changed.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<canvas id="canvas" width="128" height="128"></canvas>
|
||||
<canvas id="canvas" width="1024" height="1024"></canvas>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
@ -30,7 +30,7 @@
|
|||
|
||||
customRenderbuffer = gl.createRenderbuffer();
|
||||
gl.bindRenderbuffer(gl.RENDERBUFFER, customRenderbuffer);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 128, 128);
|
||||
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1024, 1024);
|
||||
|
||||
customTexture = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, customTexture);
|
||||
|
@ -38,11 +38,12 @@
|
|||
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.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1024, 1024, 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.viewport(128, 256, 384, 512);
|
||||
gl.clearColor(0.0, 1.0, 0.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
|
||||
|
|
|
@ -80,6 +80,7 @@ protocol.types.addDictType("snapshot-image", {
|
|||
index: "number",
|
||||
width: "number",
|
||||
height: "number",
|
||||
scaling: "number",
|
||||
flipped: "boolean",
|
||||
pixels: "uint32-array"
|
||||
});
|
||||
|
@ -156,16 +157,17 @@ let FrameSnapshotActor = protocol.ActorClass({
|
|||
last: index
|
||||
});
|
||||
|
||||
let { replayContext, lastDrawCallIndex, doCleanup } = replayData;
|
||||
let { replayContext, replayContextScaling, lastDrawCallIndex, doCleanup } = replayData;
|
||||
let [left, top, width, height] = replayData.replayViewport;
|
||||
let screenshot;
|
||||
|
||||
// Depending on the canvas' context, generating a screenshot is done
|
||||
// in different ways.
|
||||
if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) {
|
||||
screenshot = ContextUtils.getPixelsForWebGL(replayContext);
|
||||
screenshot = ContextUtils.getPixelsForWebGL(replayContext, left, top, width, height);
|
||||
screenshot.flipped = true;
|
||||
} else if (global == CallWatcherFront.CANVAS_2D_CONTEXT) {
|
||||
screenshot = ContextUtils.getPixelsFor2D(replayContext);
|
||||
screenshot = ContextUtils.getPixelsFor2D(replayContext, left, top, width, height);
|
||||
screenshot.flipped = false;
|
||||
}
|
||||
|
||||
|
@ -173,6 +175,7 @@ let FrameSnapshotActor = protocol.ActorClass({
|
|||
// binding to the original value, after generating the screenshot.
|
||||
doCleanup();
|
||||
|
||||
screenshot.scaling = replayContextScaling;
|
||||
screenshot.index = lastDrawCallIndex;
|
||||
return screenshot;
|
||||
}, {
|
||||
|
@ -375,6 +378,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
|
|||
index: index,
|
||||
width: width,
|
||||
height: height,
|
||||
scaling: 1,
|
||||
flipped: flipped,
|
||||
pixels: pixels.subarray(0, width * height)
|
||||
};
|
||||
|
@ -407,7 +411,7 @@ let CanvasActor = exports.CanvasActor = protocol.ActorClass({
|
|||
let h = this._lastContentCanvasHeight = contentCanvas.height;
|
||||
|
||||
// To keep things fast, generate images of small and fixed dimensions.
|
||||
let dimensions = CanvasFront.THUMBNAIL_HEIGHT;
|
||||
let dimensions = CanvasFront.THUMBNAIL_SIZE;
|
||||
let thumbnail;
|
||||
|
||||
// Create a thumbnail on every draw call on the canvas context, to augment
|
||||
|
@ -528,7 +532,7 @@ let ContextUtils = {
|
|||
*/
|
||||
resizePixels: function(srcPixels, srcWidth, srcHeight, dstHeight) {
|
||||
let screenshotRatio = dstHeight / srcHeight;
|
||||
let dstWidth = Math.floor(srcWidth * screenshotRatio);
|
||||
let dstWidth = (srcWidth * screenshotRatio) | 0;
|
||||
|
||||
// Use a plain array instead of a Uint32Array to make serializing faster.
|
||||
let dstPixels = new Array(dstWidth * dstHeight);
|
||||
|
@ -539,8 +543,8 @@ let ContextUtils = {
|
|||
|
||||
for (let dstX = 0; dstX < dstWidth; dstX++) {
|
||||
for (let dstY = 0; dstY < dstHeight; dstY++) {
|
||||
let srcX = Math.floor(dstX / screenshotRatio);
|
||||
let srcY = Math.floor(dstY / screenshotRatio);
|
||||
let srcX = (dstX / screenshotRatio) | 0;
|
||||
let srcY = (dstY / screenshotRatio) | 0;
|
||||
let cPos = srcX + srcWidth * srcY;
|
||||
let dPos = dstX + dstWidth * dstY;
|
||||
let color = dstPixels[dPos] = srcPixels[cPos];
|
||||
|
@ -594,6 +598,8 @@ let ContextUtils = {
|
|||
let h = canvas.height;
|
||||
|
||||
let replayContext;
|
||||
let replayContextScaling;
|
||||
let customViewport;
|
||||
let customFramebuffer;
|
||||
let lastDrawCallIndex = -1;
|
||||
let doCleanup = () => {};
|
||||
|
@ -605,10 +611,27 @@ let ContextUtils = {
|
|||
// 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) {
|
||||
// To keep things fast, replay the context calls on a framebuffer
|
||||
// of smaller dimensions than the actual canvas (maximum 512x512 pixels).
|
||||
let scaling = Math.min(CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT, h) / h;
|
||||
replayContextScaling = scaling;
|
||||
w = (w * scaling) | 0;
|
||||
h = (h * scaling) | 0;
|
||||
|
||||
// Fetch the same WebGL context and bind a new framebuffer.
|
||||
let gl = replayContext = this.getWebGLContext(canvas);
|
||||
let { newFramebuffer, oldFramebuffer } = this.createBoundFramebuffer(gl, w, h);
|
||||
customFramebuffer = newFramebuffer;
|
||||
doCleanup = () => gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
|
||||
|
||||
// Set the viewport to match the new framebuffer's dimensions.
|
||||
let { newViewport, oldViewport } = this.setCustomViewport(gl, w, h);
|
||||
customViewport = newViewport;
|
||||
|
||||
// Revert the framebuffer and viewport to the original values.
|
||||
doCleanup = () => {
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, oldFramebuffer);
|
||||
gl.viewport.apply(gl, oldViewport);
|
||||
};
|
||||
}
|
||||
// In case of 2D contexts, draw everything on a separate canvas context.
|
||||
else if (contextType == CallWatcherFront.CANVAS_2D_CONTEXT) {
|
||||
|
@ -617,7 +640,8 @@ let ContextUtils = {
|
|||
replayCanvas.width = w;
|
||||
replayCanvas.height = h;
|
||||
replayContext = replayCanvas.getContext("2d");
|
||||
replayContext.clearRect(0, 0, w, h);
|
||||
replayContextScaling = 1;
|
||||
customViewport = [0, 0, w, h];
|
||||
}
|
||||
|
||||
// Replay all the context calls up to and including the specified one.
|
||||
|
@ -630,6 +654,15 @@ let ContextUtils = {
|
|||
replayContext.bindFramebuffer(replayContext.FRAMEBUFFER, customFramebuffer);
|
||||
continue;
|
||||
}
|
||||
// Also prevent WebGL context calls that try to change the viewport
|
||||
// while our custom framebuffer is bound.
|
||||
if (name == "viewport") {
|
||||
let framebufferBinding = replayContext.getParameter(replayContext.FRAMEBUFFER_BINDING);
|
||||
if (framebufferBinding == customFramebuffer) {
|
||||
replayContext.viewport.apply(replayContext, customViewport);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
replayContext[name].apply(replayContext, args);
|
||||
} else if (type == CallWatcherFront.SETTER_FUNCTION) {
|
||||
|
@ -644,6 +677,8 @@ let ContextUtils = {
|
|||
|
||||
return {
|
||||
replayContext: replayContext,
|
||||
replayContextScaling: replayContextScaling,
|
||||
replayViewport: customViewport,
|
||||
lastDrawCallIndex: lastDrawCallIndex,
|
||||
doCleanup: doCleanup
|
||||
};
|
||||
|
@ -728,6 +763,20 @@ let ContextUtils = {
|
|||
gl.bindRenderbuffer(gl.RENDERBUFFER, oldRenderbufferBinding);
|
||||
|
||||
return { oldFramebuffer, newFramebuffer };
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the viewport of the drawing buffer for a WebGL context.
|
||||
* @param WebGLRenderingContext gl
|
||||
* @param number width
|
||||
* @param number height
|
||||
*/
|
||||
setCustomViewport: function(gl, width, height) {
|
||||
let oldViewport = XPCNativeWrapper.unwrap(gl.getParameter(gl.VIEWPORT));
|
||||
let newViewport = [0, 0, width, height];
|
||||
gl.viewport.apply(gl, newViewport);
|
||||
|
||||
return { oldViewport, newViewport };
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -748,8 +797,8 @@ CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
|
|||
CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS);
|
||||
CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS);
|
||||
CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS);
|
||||
CanvasFront.THUMBNAIL_HEIGHT = 50; // px
|
||||
CanvasFront.SCREENSHOT_HEIGHT_MAX = 256; // px
|
||||
CanvasFront.THUMBNAIL_SIZE = 50; // px
|
||||
CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT = 256; // px
|
||||
CanvasFront.INVALID_SNAPSHOT_IMAGE = {
|
||||
index: -1,
|
||||
width: 0,
|
||||
|
|
Загрузка…
Ссылка в новой задаче