| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/extras/cc/picture_as_image_data.html"> |
| <link rel="import" href="/extras/cc/util.html"> |
| <link rel="import" href="/base/guid.html"> |
| <link rel="import" href="/base/rect.html"> |
| <link rel="import" href="/base/raf.html"> |
| <link rel="import" href="/core/trace_model/object_instance.html"> |
| |
| <script> |
| 'use strict'; |
| |
| tv.exportTo('tv.e.cc', function() { |
| var ObjectSnapshot = tv.c.trace_model.ObjectSnapshot; |
| |
| // Number of pictures created. Used as an uniqueId because we are immutable. |
| var PictureCount = 0; |
| var OPS_TIMING_ITERATIONS = 3; |
| |
| function Picture(skp64, layerRect) { |
| this.skp64_ = skp64; |
| this.layerRect_ = layerRect; |
| |
| this.guid_ = tv.b.GUID.allocate(); |
| } |
| |
| Picture.prototype = { |
| get canSave() { |
| return true; |
| }, |
| |
| get layerRect() { |
| return this.layerRect_; |
| }, |
| |
| get guid() { |
| return this.guid_; |
| }, |
| |
| getBase64SkpData: function() { |
| return this.skp64_; |
| }, |
| |
| getOps: function() { |
| if (!PictureSnapshot.CanGetOps()) { |
| console.error(PictureSnapshot.HowToEnablePictureDebugging()); |
| return undefined; |
| } |
| |
| var ops = window.chrome.skiaBenchmarking.getOps({ |
| skp64: this.skp64_, |
| params: { |
| layer_rect: this.layerRect_.toArray() |
| } |
| }); |
| |
| if (!ops) |
| console.error('Failed to get picture ops.'); |
| |
| return ops; |
| }, |
| |
| getOpTimings: function() { |
| if (!PictureSnapshot.CanGetOpTimings()) { |
| console.error(PictureSnapshot.HowToEnablePictureDebugging()); |
| return undefined; |
| } |
| |
| var opTimings = window.chrome.skiaBenchmarking.getOpTimings({ |
| skp64: this.skp64_, |
| params: { |
| layer_rect: this.layerRect_.toArray() |
| } |
| }); |
| |
| if (!opTimings) |
| console.error('Failed to get picture op timings.'); |
| |
| return opTimings; |
| }, |
| |
| /** |
| * Tag each op with the time it takes to rasterize. |
| * |
| * FIXME: We should use real statistics to get better numbers here, see |
| * https://code.google.com/p/trace-viewer/issues/detail?id=357 |
| * |
| * @param {Array} ops Array of Skia operations. |
| * @return {Array} Skia ops where op.cmd_time contains the associated time |
| * for a given op. |
| */ |
| tagOpsWithTimings: function(ops) { |
| var opTimings = new Array(); |
| for (var iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) { |
| opTimings[iteration] = this.getOpTimings(); |
| if (!opTimings[iteration] || !opTimings[iteration].cmd_times) |
| return ops; |
| if (opTimings[iteration].cmd_times.length != ops.length) |
| return ops; |
| } |
| |
| for (var opIndex = 0; opIndex < ops.length; opIndex++) { |
| var min = Number.MAX_VALUE; |
| for (var i = 0; i < OPS_TIMING_ITERATIONS; i++) |
| min = Math.min(min, opTimings[i].cmd_times[opIndex]); |
| ops[opIndex].cmd_time = min; |
| } |
| |
| return ops; |
| }, |
| |
| /** |
| * Rasterize the picture. |
| * |
| * @param {{opt_stopIndex: number, params}} The SkPicture operation to |
| * rasterize up to. If not defined, the entire SkPicture is rasterized. |
| * @param {{opt_showOverdraw: bool, params}} Defines whether pixel overdraw |
| should be visualized in the image. |
| * @param {function(tv.e.cc.PictureAsImageData)} The callback function that |
| * is called after rasterization is complete or fails. |
| */ |
| rasterize: function(params, rasterCompleteCallback) { |
| if (!PictureSnapshot.CanRasterize() || !PictureSnapshot.CanGetOps()) { |
| rasterCompleteCallback(new tv.e.cc.PictureAsImageData( |
| this, tv.e.cc.PictureSnapshot.HowToEnablePictureDebugging())); |
| return; |
| } |
| |
| var raster = window.chrome.skiaBenchmarking.rasterize( |
| { |
| skp64: this.skp64_, |
| params: { |
| layer_rect: this.layerRect_.toArray() |
| } |
| }, |
| { |
| stop: params.stopIndex === undefined ? -1 : params.stopIndex, |
| overdraw: !!params.showOverdraw, |
| params: { } |
| }); |
| |
| if (raster) { |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| canvas.width = raster.width; |
| canvas.height = raster.height; |
| var imageData = ctx.createImageData(raster.width, raster.height); |
| imageData.data.set(new Uint8ClampedArray(raster.data)); |
| rasterCompleteCallback(new tv.e.cc.PictureAsImageData(this, imageData)); |
| } else { |
| var error = 'Failed to rasterize picture. ' + |
| 'Your recording may be from an old Chrome version. ' + |
| 'The SkPicture format is not backward compatible.'; |
| rasterCompleteCallback(new tv.e.cc.PictureAsImageData(this, error)); |
| } |
| } |
| }; |
| |
| function LayeredPicture(pictures) { |
| this.guid_ = tv.b.GUID.allocate(); |
| this.pictures_ = pictures; |
| this.layerRect_ = undefined; |
| } |
| |
| LayeredPicture.prototype = { |
| __proto__: Picture.prototype, |
| |
| get canSave() { |
| return false; |
| }, |
| |
| get typeName() { |
| return 'cc::LayeredPicture'; |
| }, |
| |
| get layerRect() { |
| if (this.layerRect_ !== undefined) |
| return this.layerRect_; |
| |
| this.layerRect_ = { |
| x: 0, |
| y: 0, |
| width: 0, |
| height: 0 |
| }; |
| |
| for (var i = 0; i < this.pictures_.length; ++i) { |
| var rect = this.pictures_[i].layerRect; |
| this.layerRect_.x = Math.min(this.layerRect_.x, rect.x); |
| this.layerRect_.y = Math.min(this.layerRect_.y, rect.y); |
| this.layerRect_.width = |
| Math.max(this.layerRect_.width, rect.x + rect.width); |
| this.layerRect_.height = |
| Math.max(this.layerRect_.height, rect.y + rect.height); |
| } |
| return this.layerRect_; |
| }, |
| |
| get guid() { |
| return this.guid_; |
| }, |
| |
| getBase64SkpData: function() { |
| throw new Error('Not available with a LayeredPicture.'); |
| }, |
| |
| getOps: function() { |
| var ops = []; |
| for (var i = 0; i < this.pictures_.length; ++i) |
| ops = ops.concat(this.pictures_[i].getOps()); |
| return ops; |
| }, |
| |
| getOpTimings: function() { |
| var opTimings = this.pictures_[0].getOpTimings(); |
| for (var i = 1; i < this.pictures_.length; ++i) { |
| var timings = this.pictures_[i].getOpTimings(); |
| opTimings.cmd_times = opTimings.cmd_times.concat(timings.cmd_times); |
| opTimings.total_time += timings.total_time; |
| } |
| return opTimings; |
| }, |
| |
| tagOpsWithTimings: function(ops) { |
| var opTimings = new Array(); |
| for (var iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) { |
| opTimings[iteration] = this.getOpTimings(); |
| if (!opTimings[iteration] || !opTimings[iteration].cmd_times) |
| return ops; |
| } |
| |
| for (var opIndex = 0; opIndex < ops.length; opIndex++) { |
| var min = Number.MAX_VALUE; |
| for (var i = 0; i < OPS_TIMING_ITERATIONS; i++) |
| min = Math.min(min, opTimings[i].cmd_times[opIndex]); |
| ops[opIndex].cmd_time = min; |
| } |
| return ops; |
| }, |
| |
| rasterize: function(params, rasterCompleteCallback) { |
| this.picturesAsImageData_ = []; |
| var rasterCallback = function(pictureAsImageData) { |
| this.picturesAsImageData_.push(pictureAsImageData); |
| if (this.picturesAsImageData_.length !== this.pictures_.length) |
| return; |
| |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| canvas.width = this.layerRect.width; |
| canvas.height = this.layerRect.height; |
| |
| // TODO(dsinclair): Verify these finish in the order started. |
| // Do the rasterize calls run sync or asyn? As the imageData |
| // going to be in the same order as the pictures_ list? |
| for (var i = 0; i < this.picturesAsImageData_.length; ++i) { |
| ctx.putImageData(this.picturesAsImageData_[i].imageData, |
| this.pictures_[i].layerRect.x, |
| this.pictures_[i].layerRect.y); |
| } |
| this.picturesAsImageData_ = []; |
| |
| rasterCompleteCallback(new tv.e.cc.PictureAsImageData(this, |
| ctx.getImageData(this.layerRect.x, this.layerRect.y, |
| this.layerRect.width, this.layerRect.height))); |
| }.bind(this); |
| |
| for (var i = 0; i < this.pictures_.length; ++i) |
| this.pictures_[i].rasterize(params, rasterCallback); |
| } |
| }; |
| |
| |
| /** |
| * @constructor |
| */ |
| function PictureSnapshot() { |
| ObjectSnapshot.apply(this, arguments); |
| } |
| |
| PictureSnapshot.HasSkiaBenchmarking = function() { |
| if (!window.chrome) |
| return false; |
| if (!window.chrome.skiaBenchmarking) |
| return false; |
| return true; |
| } |
| |
| PictureSnapshot.CanRasterize = function() { |
| if (!PictureSnapshot.HasSkiaBenchmarking()) |
| return false; |
| if (!window.chrome.skiaBenchmarking.rasterize) |
| return false; |
| return true; |
| } |
| |
| PictureSnapshot.CanGetOps = function() { |
| if (!PictureSnapshot.HasSkiaBenchmarking()) |
| return false; |
| if (!window.chrome.skiaBenchmarking.getOps) |
| return false; |
| return true; |
| } |
| |
| PictureSnapshot.CanGetOpTimings = function() { |
| if (!PictureSnapshot.HasSkiaBenchmarking()) |
| return false; |
| if (!window.chrome.skiaBenchmarking.getOpTimings) |
| return false; |
| return true; |
| } |
| |
| PictureSnapshot.CanGetInfo = function() { |
| if (!PictureSnapshot.HasSkiaBenchmarking()) |
| return false; |
| if (!window.chrome.skiaBenchmarking.getInfo) |
| return false; |
| return true; |
| } |
| |
| PictureSnapshot.HowToEnablePictureDebugging = function() { |
| var usualReason = [ |
| 'For pictures to show up, you need to have Chrome running with ', |
| '--enable-skia-benchmarking. Please restart chrome with this flag ', |
| 'and try again.' |
| ].join(''); |
| |
| if (!window.chrome) |
| return usualReason; |
| if (!window.chrome.skiaBenchmarking) |
| return usualReason; |
| if (!window.chrome.skiaBenchmarking.rasterize) |
| return 'Your chrome is old'; |
| if (!window.chrome.skiaBenchmarking.getOps) |
| return 'Your chrome is old: skiaBenchmarking.getOps not found'; |
| if (!window.chrome.skiaBenchmarking.getOpTimings) |
| return 'Your chrome is old: skiaBenchmarking.getOpTimings not found'; |
| if (!window.chrome.skiaBenchmarking.getInfo) |
| return 'Your chrome is old: skiaBenchmarking.getInfo not found'; |
| return 'Rasterizing is on'; |
| } |
| |
| PictureSnapshot.prototype = { |
| __proto__: ObjectSnapshot.prototype, |
| |
| preInitialize: function() { |
| tv.e.cc.preInitializeObject(this); |
| this.rasterResult_ = undefined; |
| }, |
| |
| initialize: function() { |
| // If we have an alias args, that means this picture was represented |
| // by an alias, and the real args is in alias.args. |
| if (this.args.alias) |
| this.args = this.args.alias.args; |
| |
| if (!this.args.params.layerRect) |
| throw new Error('Missing layer rect'); |
| |
| this.layerRect_ = this.args.params.layerRect; |
| this.picture_ = new Picture(this.args.skp64, this.args.params.layerRect); |
| }, |
| |
| set picture(picture) { |
| this.picture_ = picture; |
| }, |
| |
| get canSave() { |
| return this.picture_.canSave; |
| }, |
| |
| get layerRect() { |
| return this.layerRect_ ? this.layerRect_ : this.picture_.layerRect; |
| }, |
| |
| get guid() { |
| return this.picture_.guid; |
| }, |
| |
| getBase64SkpData: function() { |
| return this.picture_.getBase64SkpData(); |
| }, |
| |
| getOps: function() { |
| return this.picture_.getOps(); |
| }, |
| |
| getOpTimings: function() { |
| return this.picture_.getOpTimings(); |
| }, |
| |
| tagOpsWithTimings: function(ops) { |
| return this.picture_.tagOpsWithTimings(ops); |
| }, |
| |
| rasterize: function(params, rasterCompleteCallback) { |
| this.picture_.rasterize(params, rasterCompleteCallback); |
| } |
| }; |
| |
| ObjectSnapshot.register( |
| PictureSnapshot, |
| {typeNames: ['cc::Picture']}); |
| |
| return { |
| PictureSnapshot: PictureSnapshot, |
| Picture: Picture, |
| LayeredPicture: LayeredPicture |
| }; |
| }); |
| </script> |