author | Victor Porof <vporof@mozilla.com> |
Tue, 22 Jul 2014 12:43:24 -0400 | |
changeset 195538 | 3d091524a1b8b81722ef94b1bab23d70cf2e5a02 |
parent 195537 | fb21d6d2fdfa4b9cec9d48221a6fb1033d1d6117 |
child 195539 | ab11981ffeb0522984fa0fbbcd318748843061c6 |
push id | 27185 |
push user | kwierso@gmail.com |
push date | Wed, 23 Jul 2014 01:05:43 +0000 |
treeherder | mozilla-central@5683746bac22 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rcampbell |
bugs | 1041225 |
milestone | 34.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/canvasdebugger/canvasdebugger.js +++ b/browser/devtools/canvasdebugger/canvasdebugger.js @@ -202,18 +202,18 @@ let SnapshotsListView = Heritage.extend( * The newly inserted item. */ addSnapshot: function() { let contents = document.createElement("hbox"); contents.className = "snapshot-item"; 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"; title.setAttribute("value", L10N.getFormatStr("snapshotsList.itemLabel", this.itemCount + 1)); let calls = document.createElement("label"); calls.className = "plain snapshot-item-calls"; @@ -707,24 +707,26 @@ let CallsListView = Heritage.extend(Widg /** * Displays an image in the rendering preview of this container, generated * for the specified draw call in the recorded animation frame snapshot. * * @param array screenshot * 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); }, /** * Populates this container's footer with a list of thumbnails, one generated * for each draw call in the recorded animation frame snapshot. * @@ -749,18 +751,18 @@ let CallsListView = Heritage.extend(Widg * @param array thumbnail * A single "snapshot-image" instance received from the backend. */ appendThumbnail: function(thumbnail) { let { index, width, height, flipped, pixels } = thumbnail; 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"; thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index); thumbnailNode.setAttribute("index", index); this._filmstrip.appendChild(thumbnailNode); },
--- a/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js +++ b/browser/devtools/canvasdebugger/test/browser_canvas-actor-test-10.js @@ -19,43 +19,55 @@ function ifTestingSupported() { 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, + 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."); 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."); + 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."); 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, @@ -63,12 +75,20 @@ function ifTestingSupported() { 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."); + 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(); }
--- a/browser/devtools/canvasdebugger/test/doc_webgl-bindings.html +++ b/browser/devtools/canvasdebugger/test/doc_webgl-bindings.html @@ -4,17 +4,17 @@ <html> <head> <meta charset="utf-8"/> <title>WebGL editor test page</title> </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"; let canvas, gl; let customFramebuffer; let customRenderbuffer; let customTexture; @@ -25,29 +25,30 @@ 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); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, 1024, 1024); 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.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); drawScene(); } function drawScene() { gl.clearColor(0.0, 0.0, 1.0, 1.0);
--- a/toolkit/devtools/server/actors/canvas.js +++ b/toolkit/devtools/server/actors/canvas.js @@ -75,16 +75,17 @@ protocol.types.addType("uint32-array", { /** * Type describing a thumbnail or screenshot in a recorded animation frame. */ protocol.types.addDictType("snapshot-image", { index: "number", width: "number", height: "number", + scaling: "number", flipped: "boolean", pixels: "uint32-array" }); /** * Type describing an overview of a recorded animation frame. */ protocol.types.addDictType("snapshot-overview", { @@ -151,33 +152,35 @@ let FrameSnapshotActor = protocol.ActorC let replayData = ContextUtils.replayAnimationFrame({ contextType: global, canvas: canvas, calls: calls, first: 0, 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; } // In case of the WebGL context, we also need to reset the framebuffer // binding to the original value, after generating the screenshot. doCleanup(); + screenshot.scaling = replayContextScaling; screenshot.index = lastDrawCallIndex; return screenshot; }, { request: { call: Arg(0, "function-call") }, response: { screenshot: RetVal("snapshot-image") } }) }); @@ -370,16 +373,17 @@ let CanvasActor = exports.CanvasActor = let width = this._lastContentCanvasWidth; let height = this._lastContentCanvasHeight; let flipped = !!this._lastThumbnailFlipped; // undefined -> false let pixels = ContextUtils.getPixelStorage()["32bit"]; let animationFrameEndScreenshot = { index: index, width: width, height: height, + scaling: 1, flipped: flipped, pixels: pixels.subarray(0, width * height) }; // Wrap the function calls and screenshot in a FrameSnapshotActor instance, // which will resolve the promise returned by `recordAnimationFrame`. let frameSnapshot = new FrameSnapshotActor(this.conn, { canvas: this._lastDrawCallCanvas, @@ -402,17 +406,17 @@ let CanvasActor = exports.CanvasActor = let global = functionCall.meta.global; let contentCanvas = this._lastDrawCallCanvas = caller.canvas; let index = this._lastDrawCallIndex = functionCalls.indexOf(functionCall); let w = this._lastContentCanvasWidth = contentCanvas.width; 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 // the respective function call actor with this additional data. if (global == CallWatcherFront.CANVAS_WEBGL_CONTEXT) { // Check if drawing to a custom framebuffer (when rendering to texture). // Don't create a thumbnail in this particular case. let framebufferBinding = caller.getParameter(caller.FRAMEBUFFER_BINDING); @@ -523,29 +527,29 @@ let ContextUtils = { * The source pixel data height. * @param number dstHeight [optional] * The desired resized pixel data height. * @return object * An objet containing the resized pixels width, height and data. */ 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); // If the resized image ends up being completely transparent, returning // an empty array will skip some redundant serialization cycles. let isTransparent = true; 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]; if (color) { isTransparent = false; } } } @@ -589,66 +593,97 @@ let ContextUtils = { * 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 replayContext; + let replayContextScaling; + let customViewport; let customFramebuffer; let lastDrawCallIndex = -1; let doCleanup = () => {}; // In case of WebGL contexts, rendering will be done offscreen, in a // 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) { + // 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) { let contentDocument = canvas.ownerDocument; let replayCanvas = contentDocument.createElement("canvas"); 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. for (let i = first; i <= last; i++) { let { type, name, args } = calls[i].details; // Prevent WebGL context calls that try to reset the framebuffer binding // to the default value, since we want to perform the rendering offscreen. if (name == "bindFramebuffer" && args[1] == null) { 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) { replayContext[name] = args; } else { // Ignore getter calls. } if (CanvasFront.DRAW_CALLS.has(name)) { lastDrawCallIndex = i; } } return { replayContext: replayContext, + replayContextScaling: replayContextScaling, + replayViewport: customViewport, lastDrawCallIndex: lastDrawCallIndex, doCleanup: doCleanup }; }, /** * Gets an object containing a buffer large enough to hold width * height * pixels, assuming 32bit/pixel and 4 color components. @@ -723,16 +758,30 @@ 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, oldTextureBinding); 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 }; } }; /** * The corresponding Front object for the CanvasActor. */ let CanvasFront = exports.CanvasFront = protocol.FrontClass(CanvasActor, { initialize: function(client, { canvasActor }) { @@ -743,18 +792,18 @@ let CanvasFront = exports.CanvasFront = /** * Constants. */ 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, height: 0, pixels: [] }; /**