1<!DOCTYPE html> 2<!-- 3Copyright (c) 2013 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7 8<link rel="import" href="/tracing/extras/chrome/cc/picture_as_image_data.html"> 9<link rel="import" href="/tracing/extras/chrome/cc/util.html"> 10<link rel="import" href="/tracing/base/guid.html"> 11<link rel="import" href="/tracing/base/rect.html"> 12<link rel="import" href="/tracing/base/raf.html"> 13<link rel="import" href="/tracing/model/object_instance.html"> 14 15<script> 16'use strict'; 17 18tr.exportTo('tr.e.cc', function() { 19 var ObjectSnapshot = tr.model.ObjectSnapshot; 20 21 // Number of pictures created. Used as an uniqueId because we are immutable. 22 var PictureCount = 0; 23 var OPS_TIMING_ITERATIONS = 3; 24 25 function Picture(skp64, layerRect) { 26 this.skp64_ = skp64; 27 this.layerRect_ = layerRect; 28 29 this.guid_ = tr.b.GUID.allocate(); 30 } 31 32 Picture.prototype = { 33 get canSave() { 34 return true; 35 }, 36 37 get layerRect() { 38 return this.layerRect_; 39 }, 40 41 get guid() { 42 return this.guid_; 43 }, 44 45 getBase64SkpData: function() { 46 return this.skp64_; 47 }, 48 49 getOps: function() { 50 if (!PictureSnapshot.CanGetOps()) { 51 console.error(PictureSnapshot.HowToEnablePictureDebugging()); 52 return undefined; 53 } 54 55 var ops = window.chrome.skiaBenchmarking.getOps({ 56 skp64: this.skp64_, 57 params: { 58 layer_rect: this.layerRect_.toArray() 59 } 60 }); 61 62 if (!ops) 63 console.error('Failed to get picture ops.'); 64 65 return ops; 66 }, 67 68 getOpTimings: function() { 69 if (!PictureSnapshot.CanGetOpTimings()) { 70 console.error(PictureSnapshot.HowToEnablePictureDebugging()); 71 return undefined; 72 } 73 74 var opTimings = window.chrome.skiaBenchmarking.getOpTimings({ 75 skp64: this.skp64_, 76 params: { 77 layer_rect: this.layerRect_.toArray() 78 } 79 }); 80 81 if (!opTimings) 82 console.error('Failed to get picture op timings.'); 83 84 return opTimings; 85 }, 86 87 /** 88 * Tag each op with the time it takes to rasterize. 89 * 90 * FIXME: We should use real statistics to get better numbers here, see 91 * https://code.google.com/p/trace-viewer/issues/detail?id=357 92 * 93 * @param {Array} ops Array of Skia operations. 94 * @return {Array} Skia ops where op.cmd_time contains the associated time 95 * for a given op. 96 */ 97 tagOpsWithTimings: function(ops) { 98 var opTimings = new Array(); 99 for (var iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) { 100 opTimings[iteration] = this.getOpTimings(); 101 if (!opTimings[iteration] || !opTimings[iteration].cmd_times) 102 return ops; 103 if (opTimings[iteration].cmd_times.length != ops.length) 104 return ops; 105 } 106 107 for (var opIndex = 0; opIndex < ops.length; opIndex++) { 108 var min = Number.MAX_VALUE; 109 for (var i = 0; i < OPS_TIMING_ITERATIONS; i++) 110 min = Math.min(min, opTimings[i].cmd_times[opIndex]); 111 ops[opIndex].cmd_time = min; 112 } 113 114 return ops; 115 }, 116 117 /** 118 * Rasterize the picture. 119 * 120 * @param {{opt_stopIndex: number, params}} The SkPicture operation to 121 * rasterize up to. If not defined, the entire SkPicture is rasterized. 122 * @param {{opt_showOverdraw: bool, params}} Defines whether pixel overdraw 123 should be visualized in the image. 124 * @param {function(tr.e.cc.PictureAsImageData)} The callback function that 125 * is called after rasterization is complete or fails. 126 */ 127 rasterize: function(params, rasterCompleteCallback) { 128 if (!PictureSnapshot.CanRasterize() || !PictureSnapshot.CanGetOps()) { 129 rasterCompleteCallback(new tr.e.cc.PictureAsImageData( 130 this, tr.e.cc.PictureSnapshot.HowToEnablePictureDebugging())); 131 return; 132 } 133 134 var raster = window.chrome.skiaBenchmarking.rasterize( 135 { 136 skp64: this.skp64_, 137 params: { 138 layer_rect: this.layerRect_.toArray() 139 } 140 }, 141 { 142 stop: params.stopIndex === undefined ? -1 : params.stopIndex, 143 overdraw: !!params.showOverdraw, 144 params: { } 145 }); 146 147 if (raster) { 148 var canvas = document.createElement('canvas'); 149 var ctx = canvas.getContext('2d'); 150 canvas.width = raster.width; 151 canvas.height = raster.height; 152 var imageData = ctx.createImageData(raster.width, raster.height); 153 imageData.data.set(new Uint8ClampedArray(raster.data)); 154 rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, imageData)); 155 } else { 156 var error = 'Failed to rasterize picture. ' + 157 'Your recording may be from an old Chrome version. ' + 158 'The SkPicture format is not backward compatible.'; 159 rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, error)); 160 } 161 } 162 }; 163 164 function LayeredPicture(pictures) { 165 this.guid_ = tr.b.GUID.allocate(); 166 this.pictures_ = pictures; 167 this.layerRect_ = undefined; 168 } 169 170 LayeredPicture.prototype = { 171 __proto__: Picture.prototype, 172 173 get canSave() { 174 return false; 175 }, 176 177 get typeName() { 178 return 'cc::LayeredPicture'; 179 }, 180 181 get layerRect() { 182 if (this.layerRect_ !== undefined) 183 return this.layerRect_; 184 185 this.layerRect_ = { 186 x: 0, 187 y: 0, 188 width: 0, 189 height: 0 190 }; 191 192 for (var i = 0; i < this.pictures_.length; ++i) { 193 var rect = this.pictures_[i].layerRect; 194 this.layerRect_.x = Math.min(this.layerRect_.x, rect.x); 195 this.layerRect_.y = Math.min(this.layerRect_.y, rect.y); 196 this.layerRect_.width = 197 Math.max(this.layerRect_.width, rect.x + rect.width); 198 this.layerRect_.height = 199 Math.max(this.layerRect_.height, rect.y + rect.height); 200 } 201 return this.layerRect_; 202 }, 203 204 get guid() { 205 return this.guid_; 206 }, 207 208 getBase64SkpData: function() { 209 throw new Error('Not available with a LayeredPicture.'); 210 }, 211 212 getOps: function() { 213 var ops = []; 214 for (var i = 0; i < this.pictures_.length; ++i) 215 ops = ops.concat(this.pictures_[i].getOps()); 216 return ops; 217 }, 218 219 getOpTimings: function() { 220 var opTimings = this.pictures_[0].getOpTimings(); 221 for (var i = 1; i < this.pictures_.length; ++i) { 222 var timings = this.pictures_[i].getOpTimings(); 223 opTimings.cmd_times = opTimings.cmd_times.concat(timings.cmd_times); 224 opTimings.total_time += timings.total_time; 225 } 226 return opTimings; 227 }, 228 229 tagOpsWithTimings: function(ops) { 230 var opTimings = new Array(); 231 for (var iteration = 0; iteration < OPS_TIMING_ITERATIONS; iteration++) { 232 opTimings[iteration] = this.getOpTimings(); 233 if (!opTimings[iteration] || !opTimings[iteration].cmd_times) 234 return ops; 235 } 236 237 for (var opIndex = 0; opIndex < ops.length; opIndex++) { 238 var min = Number.MAX_VALUE; 239 for (var i = 0; i < OPS_TIMING_ITERATIONS; i++) 240 min = Math.min(min, opTimings[i].cmd_times[opIndex]); 241 ops[opIndex].cmd_time = min; 242 } 243 return ops; 244 }, 245 246 rasterize: function(params, rasterCompleteCallback) { 247 this.picturesAsImageData_ = []; 248 var rasterCallback = function(pictureAsImageData) { 249 this.picturesAsImageData_.push(pictureAsImageData); 250 if (this.picturesAsImageData_.length !== this.pictures_.length) 251 return; 252 253 var canvas = document.createElement('canvas'); 254 var ctx = canvas.getContext('2d'); 255 canvas.width = this.layerRect.width; 256 canvas.height = this.layerRect.height; 257 258 // TODO(dsinclair): Verify these finish in the order started. 259 // Do the rasterize calls run sync or asyn? As the imageData 260 // going to be in the same order as the pictures_ list? 261 for (var i = 0; i < this.picturesAsImageData_.length; ++i) { 262 ctx.putImageData(this.picturesAsImageData_[i].imageData, 263 this.pictures_[i].layerRect.x, 264 this.pictures_[i].layerRect.y); 265 } 266 this.picturesAsImageData_ = []; 267 268 rasterCompleteCallback(new tr.e.cc.PictureAsImageData(this, 269 ctx.getImageData(this.layerRect.x, this.layerRect.y, 270 this.layerRect.width, this.layerRect.height))); 271 }.bind(this); 272 273 for (var i = 0; i < this.pictures_.length; ++i) 274 this.pictures_[i].rasterize(params, rasterCallback); 275 } 276 }; 277 278 279 /** 280 * @constructor 281 */ 282 function PictureSnapshot() { 283 ObjectSnapshot.apply(this, arguments); 284 } 285 286 PictureSnapshot.HasSkiaBenchmarking = function() { 287 return tr.isExported('chrome.skiaBenchmarking'); 288 } 289 290 PictureSnapshot.CanRasterize = function() { 291 if (!PictureSnapshot.HasSkiaBenchmarking()) 292 return false; 293 if (!window.chrome.skiaBenchmarking.rasterize) 294 return false; 295 return true; 296 } 297 298 PictureSnapshot.CanGetOps = function() { 299 if (!PictureSnapshot.HasSkiaBenchmarking()) 300 return false; 301 if (!window.chrome.skiaBenchmarking.getOps) 302 return false; 303 return true; 304 } 305 306 PictureSnapshot.CanGetOpTimings = function() { 307 if (!PictureSnapshot.HasSkiaBenchmarking()) 308 return false; 309 if (!window.chrome.skiaBenchmarking.getOpTimings) 310 return false; 311 return true; 312 } 313 314 PictureSnapshot.CanGetInfo = function() { 315 if (!PictureSnapshot.HasSkiaBenchmarking()) 316 return false; 317 if (!window.chrome.skiaBenchmarking.getInfo) 318 return false; 319 return true; 320 } 321 322 PictureSnapshot.HowToEnablePictureDebugging = function() { 323 if (tr.isHeadless) 324 return 'Pictures only work in chrome'; 325 326 var usualReason = [ 327 'For pictures to show up, you need to have Chrome running with ', 328 '--enable-skia-benchmarking. Please restart chrome with this flag ', 329 'and try again.' 330 ].join(''); 331 332 if (!tr.isExported('global.chrome.skiaBenchmarking')) 333 return usualReason; 334 if (!global.chrome.skiaBenchmarking.rasterize) 335 return 'Your chrome is old'; 336 if (!global.chrome.skiaBenchmarking.getOps) 337 return 'Your chrome is old: skiaBenchmarking.getOps not found'; 338 if (!global.chrome.skiaBenchmarking.getOpTimings) 339 return 'Your chrome is old: skiaBenchmarking.getOpTimings not found'; 340 if (!global.chrome.skiaBenchmarking.getInfo) 341 return 'Your chrome is old: skiaBenchmarking.getInfo not found'; 342 return 'Rasterizing is on'; 343 } 344 345 PictureSnapshot.prototype = { 346 __proto__: ObjectSnapshot.prototype, 347 348 preInitialize: function() { 349 tr.e.cc.preInitializeObject(this); 350 this.rasterResult_ = undefined; 351 }, 352 353 initialize: function() { 354 // If we have an alias args, that means this picture was represented 355 // by an alias, and the real args is in alias.args. 356 if (this.args.alias) 357 this.args = this.args.alias.args; 358 359 if (!this.args.params.layerRect) 360 throw new Error('Missing layer rect'); 361 362 this.layerRect_ = this.args.params.layerRect; 363 this.picture_ = new Picture(this.args.skp64, this.args.params.layerRect); 364 }, 365 366 set picture(picture) { 367 this.picture_ = picture; 368 }, 369 370 get canSave() { 371 return this.picture_.canSave; 372 }, 373 374 get layerRect() { 375 return this.layerRect_ ? this.layerRect_ : this.picture_.layerRect; 376 }, 377 378 get guid() { 379 return this.picture_.guid; 380 }, 381 382 getBase64SkpData: function() { 383 return this.picture_.getBase64SkpData(); 384 }, 385 386 getOps: function() { 387 return this.picture_.getOps(); 388 }, 389 390 getOpTimings: function() { 391 return this.picture_.getOpTimings(); 392 }, 393 394 tagOpsWithTimings: function(ops) { 395 return this.picture_.tagOpsWithTimings(ops); 396 }, 397 398 rasterize: function(params, rasterCompleteCallback) { 399 this.picture_.rasterize(params, rasterCompleteCallback); 400 } 401 }; 402 403 ObjectSnapshot.register( 404 PictureSnapshot, 405 {typeNames: ['cc::Picture']}); 406 407 return { 408 PictureSnapshot: PictureSnapshot, 409 Picture: Picture, 410 LayeredPicture: LayeredPicture 411 }; 412}); 413</script> 414