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