1<!DOCTYPE html>
2<!--
3Copyright (c) 2015 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/base/color.html">
9<link rel="import" href="/tracing/base/quad.html">
10<link rel="import" href="/tracing/base/raf.html">
11<link rel="import" href="/tracing/base/range.html">
12<link rel="import" href="/tracing/extras/chrome/cc/debug_colors.html">
13<link rel="import" href="/tracing/extras/chrome/cc/picture.html">
14<link rel="import" href="/tracing/extras/chrome/cc/render_pass.html">
15<link rel="import" href="/tracing/extras/chrome/cc/tile.html">
16<link rel="import" href="/tracing/extras/chrome/cc/util.html">
17<link rel="import" href="/tracing/model/event_set.html">
18<link rel="import" href="/tracing/ui/base/info_bar.html">
19<link rel="import" href="/tracing/ui/base/quad_stack_view.html">
20<link rel="import" href="/tracing/ui/base/utils.html">
21
22<style>
23* /deep/ tr-ui-e-chrome-cc-layer-tree-quad-stack-view {
24  position: relative;
25}
26
27* /deep/ tr-ui-e-chrome-cc-layer-tree-quad-stack-view > top-controls {
28  -webkit-flex: 0 0 auto;
29  background-image: -webkit-gradient(linear,
30                                     0 0, 100% 0,
31                                     from(#E5E5E5),
32                                     to(#D1D1D1));
33  border-bottom: 1px solid #8e8e8e;
34  border-top: 1px solid white;
35  display: flex;
36  flex-flow: row wrap;
37  flex-direction: row;
38  font-size:  14px;
39  padding-left: 2px;
40  overflow: hidden;
41}
42
43* /deep/ tr-ui-e-chrome-cc-layer-tree-quad-stack-view >
44      top-controls input[type='checkbox'] {
45  vertical-align: -2px;
46}
47
48* /deep/ tr-ui-e-chrome-cc-layer-tree-quad-stack-view > .what-rasterized {
49  color: -webkit-link;
50  cursor: pointer;
51  text-decoration: underline;
52  position: absolute;
53  bottom: 10px;
54  left: 10px;
55}
56
57* /deep/ tr-ui-e-chrome-cc-layer-tree-quad-stack-view > #input-event {
58  content: url('./images/input-event.png');
59  display: none;
60}
61</style>
62
63<template id='tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template'>
64  <img id='input-event'/>
65</template>
66
67<script>
68'use strict';
69
70/**
71 * @fileoverview Graphical view of  LayerTreeImpl, with controls for
72 * type of layer content shown and info bar for content-loading warnings.
73 */
74tr.exportTo('tr.ui.e.chrome.cc', function() {
75  var ColorScheme = tr.b.ColorScheme;
76
77  var THIS_DOC = document.currentScript.ownerDocument;
78  var TILE_HEATMAP_TYPE = {};
79  TILE_HEATMAP_TYPE.NONE = 'none';
80  TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY = 'scheduledPriority';
81  TILE_HEATMAP_TYPE.USING_GPU_MEMORY = 'usingGpuMemory';
82
83  var cc = tr.ui.e.chrome.cc;
84
85  function createTileRectsSelectorBaseOptions() {
86    return [{label: 'None', value: 'none'},
87            {label: 'Coverage Rects', value: 'coverage'}];
88  }
89
90  var bytesToRoundedMegabytes = tr.e.cc.bytesToRoundedMegabytes;
91
92
93  /**
94   * @constructor
95   */
96  var LayerTreeQuadStackView =
97      tr.ui.b.define('tr-ui-e-chrome-cc-layer-tree-quad-stack-view');
98
99  LayerTreeQuadStackView.prototype = {
100    __proto__: HTMLDivElement.prototype,
101
102    decorate: function() {
103      this.isRenderPassQuads_ = false;
104      this.pictureAsImageData_ = {}; // Maps picture.guid to PictureAsImageData.
105      this.messages_ = [];
106      this.controls_ = document.createElement('top-controls');
107      this.infoBar_ = document.createElement('tr-ui-b-info-bar');
108      this.quadStackView_ = new tr.ui.b.QuadStackView();
109      this.quadStackView_.addEventListener(
110          'selectionchange', this.onQuadStackViewSelectionChange_.bind(this));
111      this.extraHighlightsByLayerId_ = undefined;
112      this.inputEventImageData_ = undefined;
113
114      var m = tr.ui.b.MOUSE_SELECTOR_MODE;
115      var mms = this.quadStackView_.mouseModeSelector;
116      mms.settingsKey = 'tr.e.cc.layerTreeQuadStackView.mouseModeSelector';
117      mms.setKeyCodeForMode(m.SELECTION, 'Z'.charCodeAt(0));
118      mms.setKeyCodeForMode(m.PANSCAN, 'X'.charCodeAt(0));
119      mms.setKeyCodeForMode(m.ZOOM, 'C'.charCodeAt(0));
120      mms.setKeyCodeForMode(m.ROTATE, 'V'.charCodeAt(0));
121
122      var node = tr.ui.b.instantiateTemplate(
123          '#tr-ui-e-chrome-cc-layer-tree-quad-stack-view-template', THIS_DOC);
124      this.appendChild(node);
125      this.appendChild(this.controls_);
126      this.appendChild(this.infoBar_);
127      this.appendChild(this.quadStackView_);
128
129      this.tileRectsSelector_ = tr.ui.b.createSelector(
130          this, 'howToShowTiles',
131          'layerView.howToShowTiles', 'none',
132          createTileRectsSelectorBaseOptions());
133      this.controls_.appendChild(this.tileRectsSelector_);
134
135      var tileHeatmapText = tr.ui.b.createSpan({
136        textContent: 'Tile heatmap:'
137      });
138      this.controls_.appendChild(tileHeatmapText);
139
140      var tileHeatmapSelector = tr.ui.b.createSelector(
141          this, 'tileHeatmapType',
142          'layerView.tileHeatmapType', TILE_HEATMAP_TYPE.NONE,
143          [{label: 'None',
144            value: TILE_HEATMAP_TYPE.NONE},
145           {label: 'Scheduled Priority',
146            value: TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},
147           {label: 'Is using GPU memory',
148            value: TILE_HEATMAP_TYPE.USING_GPU_MEMORY}
149          ]);
150      this.controls_.appendChild(tileHeatmapSelector);
151
152      var showOtherLayersCheckbox = tr.ui.b.createCheckBox(
153          this, 'showOtherLayers',
154          'layerView.showOtherLayers', true,
155          'Other layers/passes');
156      showOtherLayersCheckbox.title =
157          'When checked, show all layers, selected or not.';
158      this.controls_.appendChild(showOtherLayersCheckbox);
159
160      var showInvalidationsCheckbox = tr.ui.b.createCheckBox(
161          this, 'showInvalidations',
162          'layerView.showInvalidations', true,
163          'Invalidations');
164      showInvalidationsCheckbox.title =
165          'When checked, compositing invalidations are highlighted in red';
166      this.controls_.appendChild(showInvalidationsCheckbox);
167
168      var showUnrecordedRegionCheckbox = tr.ui.b.createCheckBox(
169          this, 'showUnrecordedRegion',
170          'layerView.showUnrecordedRegion', true,
171          'Unrecorded area');
172      showUnrecordedRegionCheckbox.title =
173          'When checked, unrecorded areas are highlighted in yellow';
174      this.controls_.appendChild(showUnrecordedRegionCheckbox);
175
176      var showBottlenecksCheckbox = tr.ui.b.createCheckBox(
177          this, 'showBottlenecks',
178          'layerView.showBottlenecks', true,
179          'Bottlenecks');
180      showBottlenecksCheckbox.title =
181          'When checked, scroll bottlenecks are highlighted';
182      this.controls_.appendChild(showBottlenecksCheckbox);
183
184      var showLayoutRectsCheckbox = tr.ui.b.createCheckBox(
185          this, 'showLayoutRects',
186          'layerView.showLayoutRects', false,
187          'Layout rects');
188      showLayoutRectsCheckbox.title =
189          'When checked, shows rects for regions where layout happened';
190      this.controls_.appendChild(showLayoutRectsCheckbox);
191
192      var showContentsCheckbox = tr.ui.b.createCheckBox(
193          this, 'showContents',
194          'layerView.showContents', true,
195          'Contents');
196      showContentsCheckbox.title =
197          'When checked, show the rendered contents inside the layer outlines';
198      this.controls_.appendChild(showContentsCheckbox);
199
200      var showAnimationBoundsCheckbox = tr.ui.b.createCheckBox(
201          this, 'showAnimationBounds',
202          'layerView.showAnimationBounds', false,
203          'Animation Bounds');
204      showAnimationBoundsCheckbox.title = 'When checked, show a border around' +
205          ' a layer showing the extent of its animation.';
206      this.controls_.appendChild(showAnimationBoundsCheckbox);
207
208      var showInputEventsCheckbox = tr.ui.b.createCheckBox(
209          this, 'showInputEvents',
210          'layerView.showInputEvents', true,
211          'Input events');
212      showInputEventsCheckbox.title = 'When checked, input events are ' +
213          'displayed as circles.';
214      this.controls_.appendChild(showInputEventsCheckbox);
215
216      this.whatRasterizedLink_ = document.createElement('a');
217      this.whatRasterizedLink_.classList.add('what-rasterized');
218      this.whatRasterizedLink_.textContent = 'What rasterized?';
219      this.whatRasterizedLink_.addEventListener(
220          'click', this.onWhatRasterizedLinkClicked_.bind(this));
221      this.appendChild(this.whatRasterizedLink_);
222    },
223
224    get layerTreeImpl() {
225      return this.layerTreeImpl_;
226    },
227
228    set isRenderPassQuads(newValue) {
229      this.isRenderPassQuads_ = newValue;
230    },
231
232    set layerTreeImpl(layerTreeImpl) {
233      if (this.layerTreeImpl_ === layerTreeImpl)
234        return;
235
236      // FIXME(pdr): We may want to clear pictureAsImageData_ here to save
237      //             memory at the cost of performance. Note that
238      //             pictureAsImageData_ will be cleared when this is
239      //             destructed, but this view might live for several
240      //             layerTreeImpls.
241      this.layerTreeImpl_ = layerTreeImpl;
242      this.selection = undefined;
243    },
244
245    get extraHighlightsByLayerId() {
246      return this.extraHighlightsByLayerId_;
247    },
248
249    set extraHighlightsByLayerId(extraHighlightsByLayerId) {
250      this.extraHighlightsByLayerId_ = extraHighlightsByLayerId;
251      this.scheduleUpdateContents_();
252    },
253
254    get showOtherLayers() {
255      return this.showOtherLayers_;
256    },
257
258    set showOtherLayers(show) {
259      this.showOtherLayers_ = show;
260      this.updateContents_();
261    },
262
263    get showAnimationBounds() {
264      return this.showAnimationBounds_;
265    },
266
267    set showAnimationBounds(show) {
268      this.showAnimationBounds_ = show;
269      this.updateContents_();
270    },
271
272    get showInputEvents() {
273      return this.showInputEvents_;
274    },
275
276    set showInputEvents(show) {
277      this.showInputEvents_ = show;
278      this.updateContents_();
279    },
280
281    get showContents() {
282      return this.showContents_;
283    },
284
285    set showContents(show) {
286      this.showContents_ = show;
287      this.updateContents_();
288    },
289
290    get showInvalidations() {
291      return this.showInvalidations_;
292    },
293
294    set showInvalidations(show) {
295      this.showInvalidations_ = show;
296      this.updateContents_();
297    },
298
299    get showUnrecordedRegion() {
300      return this.showUnrecordedRegion_;
301    },
302
303    set showUnrecordedRegion(show) {
304      this.showUnrecordedRegion_ = show;
305      this.updateContents_();
306    },
307
308    get showBottlenecks() {
309      return this.showBottlenecks_;
310    },
311
312    set showBottlenecks(show) {
313      this.showBottlenecks_ = show;
314      this.updateContents_();
315    },
316
317    get showLayoutRects() {
318      return this.showLayoutRects_;
319    },
320
321    set showLayoutRects(show) {
322      this.showLayoutRects_ = show;
323      this.updateContents_();
324    },
325
326    get howToShowTiles() {
327      return this.howToShowTiles_;
328    },
329
330    set howToShowTiles(val) {
331      // Make sure val is something we expect.
332      console.assert(
333          (val === 'none') ||
334          (val === 'coverage') ||
335          !isNaN(parseFloat(val)));
336
337      this.howToShowTiles_ = val;
338      this.updateContents_();
339    },
340
341    get tileHeatmapType() {
342      return this.tileHeatmapType_;
343    },
344
345    set tileHeatmapType(val) {
346      this.tileHeatmapType_ = val;
347      this.updateContents_();
348    },
349
350    get selection() {
351      return this.selection_;
352    },
353
354    set selection(selection) {
355      if (this.selection === selection)
356        return;
357      this.selection_ = selection;
358      tr.b.dispatchSimpleEvent(this, 'selection-change');
359      this.updateContents_();
360    },
361
362    regenerateContent: function() {
363      this.updateTilesSelector_();
364      this.updateContents_();
365    },
366
367    loadDataForImageElement_: function(image, callback) {
368      var imageContent = window.getComputedStyle(image).content;
369      image.src = tr.ui.b.extractUrlString(imageContent);
370      image.onload = function() {
371        var canvas = document.createElement('canvas');
372        var ctx = canvas.getContext('2d');
373        canvas.width = image.width;
374        canvas.height = image.height;
375        ctx.drawImage(image, 0, 0);
376        var imageData = ctx.getImageData(
377            0, 0, canvas.width, canvas.height);
378        callback(imageData);
379      }
380    },
381
382    onQuadStackViewSelectionChange_: function(e) {
383      var selectableQuads = e.quads.filter(function(q) {
384        return q.selectionToSetIfClicked !== undefined;
385      });
386      if (selectableQuads.length == 0) {
387        this.selection = undefined;
388        return;
389      }
390
391      // Sort the quads low to high on stackingGroupId.
392      selectableQuads.sort(function(x, y) {
393        var z = x.stackingGroupId - y.stackingGroupId;
394        if (z != 0)
395          return z;
396        return x.selectionToSetIfClicked.specicifity -
397            y.selectionToSetIfClicked.specicifity;
398      });
399
400      // TODO(nduca): Support selecting N things at once.
401      var quadToSelect = selectableQuads[selectableQuads.length - 1];
402      this.selection = quadToSelect.selectionToSetIfClicked;
403    },
404
405    scheduleUpdateContents_: function() {
406      if (this.updateContentsPending_)
407        return;
408      this.updateContentsPending_ = true;
409      tr.b.requestAnimationFrameInThisFrameIfPossible(
410          this.updateContents_, this);
411    },
412
413    updateContents_: function() {
414      if (!this.layerTreeImpl_) {
415        this.quadStackView_.headerText = 'No tree';
416        this.quadStackView_.quads = [];
417        return;
418      }
419
420
421      var status = this.computePictureLoadingStatus_();
422      if (!status.picturesComplete)
423        return;
424
425      var lthi = this.layerTreeImpl_.layerTreeHostImpl;
426      var lthiInstance = lthi.objectInstance;
427      var worldViewportRect = tr.b.Rect.fromXYWH(
428          0, 0,
429          lthi.deviceViewportSize.width, lthi.deviceViewportSize.height);
430      this.quadStackView_.deviceRect = worldViewportRect;
431      if (this.isRenderPassQuads_)
432        this.quadStackView_.quads = this.generateRenderPassQuads();
433      else
434        this.quadStackView_.quads = this.generateLayerQuads();
435
436      this.updateWhatRasterizedLinkState_();
437
438      var message = '';
439      if (lthi.tilesHaveGpuMemoryUsageInfo) {
440        var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes;
441        var otherTreeUsageInBytes = lthi.gpuMemoryUsageInBytes -
442            thisTreeUsageInBytes;
443        message += bytesToRoundedMegabytes(thisTreeUsageInBytes) +
444                'MB on this tree';
445        if (otherTreeUsageInBytes) {
446          message += ', ' +
447              bytesToRoundedMegabytes(otherTreeUsageInBytes) +
448              'MB on the other tree';
449        }
450      } else {
451        if (this.layerTreeImpl_) {
452          var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes;
453          message += bytesToRoundedMegabytes(thisTreeUsageInBytes) +
454              'MB on this tree';
455
456          if (this.layerTreeImpl_.otherTree) {
457            // Older Chromes don't report enough data to know how much memory is
458            // being used across both trees. We know the memory consumed by each
459            // tree, but there is resource sharing *between the trees* so we
460            // can't simply sum up the per-tree costs. We need either the total
461            // plus one tree, to guess the unique on the other tree, etc. Newer
462            // chromes report memory per tile, which allows LTHI to compute the
463            // total tile memory usage, letting us figure things out properly.
464            message += ', ???MB on other tree. ';
465          }
466        }
467      }
468
469      if (lthi.args.tileManagerBasicState) {
470        var tmgs = lthi.args.tileManagerBasicState.globalState;
471        message += ' (softMax=' +
472          bytesToRoundedMegabytes(tmgs.softMemoryLimitInBytes) +
473          'MB, hardMax=' +
474          bytesToRoundedMegabytes(tmgs.hardMemoryLimitInBytes) + 'MB, ' +
475          tmgs.memoryLimitPolicy + ')';
476
477      } else {
478        // Old Chromes do not have a globalState on the LTHI dump.
479        // But they do issue a DidManage event wiht the globalstate. Find that
480        // event so that we show some global state.
481        var thread = lthi.snapshottedOnThread;
482        var didManageTilesSlices = thread.sliceGroup.slices.filter(function(s) {
483          if (s.category !== 'tr.e.cc')
484            return false;
485          if (s.title !== 'DidManage')
486            return false;
487          if (s.end > lthi.ts)
488            return false;
489          return true;
490        });
491        didManageTilesSlices.sort(function(x, y) {
492          return x.end - y.end;
493        });
494        if (didManageTilesSlices.length > 0) {
495          var newest = didManageTilesSlices[didManageTilesSlices.length - 1];
496          var tmgs = newest.args.state.global_state;
497          message += ' (softMax=' +
498            bytesToRoundedMegabytes(tmgs.soft_memory_limit_in_bytes) +
499            'MB, hardMax=' +
500            bytesToRoundedMegabytes(tmgs.hard_memory_limit_in_bytes) + 'MB, ' +
501            tmgs.memory_limit_policy + ')';
502        }
503      }
504
505      if (this.layerTreeImpl_.otherTree)
506        message += ' (Another tree exists)';
507
508
509      if (message.length)
510        this.quadStackView_.headerText = message;
511      else
512        this.quadStackView_.headerText = undefined;
513
514      this.updateInfoBar_(status.messages);
515    },
516
517    updateTilesSelector_: function() {
518      var data = createTileRectsSelectorBaseOptions();
519
520      if (this.layerTreeImpl_) {
521        // First get all of the scales information from LTHI.
522        var lthi = this.layerTreeImpl_.layerTreeHostImpl;
523        var scaleNames = lthi.getContentsScaleNames();
524        for (var scale in scaleNames) {
525          data.push({
526            label: 'Scale ' + scale + ' (' + scaleNames[scale] + ')',
527            value: scale
528          });
529        }
530      }
531
532      // Then create a new selector and replace the old one.
533      var new_selector = tr.ui.b.createSelector(
534          this, 'howToShowTiles',
535          'layerView.howToShowTiles', 'none',
536          data);
537      this.controls_.replaceChild(new_selector, this.tileRectsSelector_);
538      this.tileRectsSelector_ = new_selector;
539    },
540
541    computePictureLoadingStatus_: function() {
542      // Figure out if we can draw the quads yet. While we're at it, figure out
543      // if we have any warnings we need to show.
544      var layers = this.layers;
545      var status = {
546        messages: [],
547        picturesComplete: true
548      };
549      if (this.showContents) {
550        var hasPendingRasterizeImage = false;
551        var firstPictureError = undefined;
552        var hasMissingLayerRect = false;
553        var hasUnresolvedPictureRef = false;
554        for (var i = 0; i < layers.length; i++) {
555          var layer = layers[i];
556          for (var ir = 0; ir < layer.pictures.length; ++ir) {
557            var picture = layer.pictures[ir];
558
559            if (picture.idRef) {
560              hasUnresolvedPictureRef = true;
561              continue;
562            }
563            if (!picture.layerRect) {
564              hasMissingLayerRect = true;
565              continue;
566            }
567
568            var pictureAsImageData = this.pictureAsImageData_[picture.guid];
569            if (!pictureAsImageData) {
570              hasPendingRasterizeImage = true;
571              this.pictureAsImageData_[picture.guid] =
572                  tr.e.cc.PictureAsImageData.Pending(this);
573              picture.rasterize(
574                  {stopIndex: undefined},
575                  function(pictureImageData) {
576                    var picture_ = pictureImageData.picture;
577                    this.pictureAsImageData_[picture_.guid] = pictureImageData;
578                    this.scheduleUpdateContents_();
579                  }.bind(this));
580              continue;
581            }
582            if (pictureAsImageData.isPending()) {
583              hasPendingRasterizeImage = true;
584              continue;
585            }
586            if (pictureAsImageData.error) {
587              if (!firstPictureError)
588                firstPictureError = pictureAsImageData.error;
589              break;
590            }
591          }
592        }
593        if (hasPendingRasterizeImage) {
594          status.picturesComplete = false;
595        } else {
596          if (hasUnresolvedPictureRef) {
597            status.messages.push({
598              header: 'Missing picture',
599              details: 'Your trace didnt have pictures for every layer. ' +
600                  'Old chrome versions had this problem'});
601          }
602          if (hasMissingLayerRect) {
603            status.messages.push({
604              header: 'Missing layer rect',
605              details: 'Your trace may be corrupt or from a very old ' +
606                  'Chrome revision.'});
607          }
608          if (firstPictureError) {
609            status.messages.push({
610              header: 'Cannot rasterize',
611              details: firstPictureError});
612          }
613        }
614      }
615      if (this.showInputEvents && this.layerTreeImpl.tracedInputLatencies &&
616          this.inputEventImageData_ === undefined) {
617        var image = this.querySelector('#input-event');
618        if (!image.src) {
619          this.loadDataForImageElement_(image, function(imageData) {
620            this.inputEventImageData_ = imageData;
621            this.updateContentsPending_ = false;
622            this.scheduleUpdateContents_();
623          }.bind(this));
624        }
625        status.picturesComplete = false;
626      }
627      return status;
628    },
629
630    get selectedRenderPass() {
631      if (this.selection)
632        return this.selection.renderPass_;
633    },
634
635    get selectedLayer() {
636      if (this.selection) {
637        var selectedLayerId = this.selection.associatedLayerId;
638        return this.layerTreeImpl_.findLayerWithId(selectedLayerId);
639      }
640    },
641
642    get renderPasses() {
643      var renderPasses =
644          this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses;
645      if (!this.showOtherLayers) {
646        var selectedRenderPass = this.selectedRenderPass;
647        if (selectedRenderPass)
648          renderPasses = [selectedRenderPass];
649      }
650      return renderPasses;
651    },
652
653    get layers() {
654      var layers = this.layerTreeImpl.renderSurfaceLayerList;
655      if (!this.showOtherLayers) {
656        var selectedLayer = this.selectedLayer;
657        if (selectedLayer)
658          layers = [selectedLayer];
659      }
660      return layers;
661    },
662
663    appendImageQuads_: function(quads, layer, layerQuad) {
664      // Generate image quads for the layer
665      for (var ir = 0; ir < layer.pictures.length; ++ir) {
666        var picture = layer.pictures[ir];
667        if (!picture.layerRect)
668          continue;
669
670        var unitRect = picture.layerRect.asUVRectInside(layer.bounds);
671        var iq = layerQuad.projectUnitRect(unitRect);
672
673        var pictureData = this.pictureAsImageData_[picture.guid];
674        if (this.showContents && pictureData && pictureData.imageData) {
675          iq.imageData = pictureData.imageData;
676          iq.borderColor = 'rgba(0,0,0,0)';
677        } else {
678          iq.imageData = undefined;
679        }
680
681        iq.stackingGroupId = layerQuad.stackingGroupId;
682        quads.push(iq);
683      }
684    },
685
686    appendAnimationQuads_: function(quads, layer, layerQuad) {
687      if (!layer.animationBoundsRect)
688        return;
689
690      var rect = layer.animationBoundsRect;
691      var abq = tr.b.Quad.fromRect(rect);
692
693      abq.backgroundColor = 'rgba(164,191,48,0.5)';
694      abq.borderColor = 'rgba(205,255,0,0.75)';
695      abq.borderWidth = 3.0;
696      abq.stackingGroupId = layerQuad.stackingGroupId;
697      abq.selectionToSetIfClicked = new cc.AnimationRectSelection(
698          layer, rect);
699      quads.push(abq);
700    },
701
702    appendInvalidationQuads_: function(quads, layer, layerQuad) {
703      if (layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore)
704        return;
705
706      // Generate the invalidation rect quads.
707      for (var ir = 0; ir < layer.annotatedInvalidation.rects.length; ir++) {
708        var rect = layer.annotatedInvalidation.rects[ir];
709        var unitRect = rect.asUVRectInside(layer.bounds);
710        var iq = layerQuad.projectUnitRect(unitRect);
711        iq.backgroundColor = 'rgba(0, 255, 0, 0.1)';
712        if (rect.reason === 'renderer insertion')
713            iq.backgroundColor = 'rgba(0, 255, 128, 0.1)';
714        iq.borderColor = 'rgba(0, 255, 0, 1)';
715        iq.stackingGroupId = layerQuad.stackingGroupId;
716        iq.selectionToSetIfClicked = new cc.LayerRectSelection(
717            layer, 'Invalidation rect (' + rect.reason + ')', rect, rect);
718        quads.push(iq);
719      }
720
721      // Show unannotated invalidation rect quads if no annotated rects are
722      // available.
723      if (layer.annotatedInvalidation.rects.length === 0) {
724        for (var ir = 0; ir < layer.invalidation.rects.length; ir++) {
725          var rect = layer.invalidation.rects[ir];
726          var unitRect = rect.asUVRectInside(layer.bounds);
727          var iq = layerQuad.projectUnitRect(unitRect);
728          iq.backgroundColor = 'rgba(0, 255, 0, 0.1)';
729          iq.borderColor = 'rgba(0, 255, 0, 1)';
730          iq.stackingGroupId = layerQuad.stackingGroupId;
731          iq.selectionToSetIfClicked = new cc.LayerRectSelection(
732              layer, 'Invalidation rect', rect, rect);
733          quads.push(iq);
734        }
735      }
736    },
737
738    appendUnrecordedRegionQuads_: function(quads, layer, layerQuad) {
739      // Generate the unrecorded region quads.
740      for (var ir = 0; ir < layer.unrecordedRegion.rects.length; ir++) {
741        var rect = layer.unrecordedRegion.rects[ir];
742        var unitRect = rect.asUVRectInside(layer.bounds);
743        var iq = layerQuad.projectUnitRect(unitRect);
744        iq.backgroundColor = 'rgba(240, 230, 140, 0.3)';
745        iq.borderColor = 'rgba(240, 230, 140, 1)';
746        iq.stackingGroupId = layerQuad.stackingGroupId;
747        iq.selectionToSetIfClicked = new cc.LayerRectSelection(
748            layer, 'Unrecorded area', rect, rect);
749        quads.push(iq);
750      }
751    },
752
753    appendBottleneckQuads_: function(quads, layer, layerQuad, stackingGroupId) {
754      function processRegion(region, label, borderColor) {
755        var backgroundColor = borderColor.clone();
756        backgroundColor.a = 0.4 * (borderColor.a || 1.0);
757
758        if (!region || !region.rects)
759          return;
760
761        for (var ir = 0; ir < region.rects.length; ir++) {
762          var rect = region.rects[ir];
763          var unitRect = rect.asUVRectInside(layer.bounds);
764          var iq = layerQuad.projectUnitRect(unitRect);
765          iq.backgroundColor = backgroundColor.toString();
766          iq.borderColor = borderColor.toString();
767          iq.borderWidth = 4.0;
768          iq.stackingGroupId = stackingGroupId;
769          iq.selectionToSetIfClicked = new cc.LayerRectSelection(
770              layer, label, rect, rect);
771          quads.push(iq);
772        }
773      }
774
775      processRegion(layer.touchEventHandlerRegion, 'Touch listener',
776                    tr.b.Color.fromString('rgb(228, 226, 27)'));
777      processRegion(layer.wheelEventHandlerRegion, 'Wheel listener',
778                    tr.b.Color.fromString('rgb(176, 205, 29)'));
779      processRegion(layer.nonFastScrollableRegion, 'Repaints on scroll',
780                    tr.b.Color.fromString('rgb(213, 134, 32)'));
781    },
782
783    appendTileCoverageRectQuads_: function(
784        quads, layer, layerQuad, heatmapType) {
785      if (!layer.tileCoverageRects)
786        return;
787
788      var tiles = [];
789      for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
790        var tile = layer.tileCoverageRects[ct].tile;
791        if (tile !== undefined)
792          tiles.push(tile);
793      }
794
795      var lthi = this.layerTreeImpl_.layerTreeHostImpl;
796      var minMax =
797          this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
798      var heatmapResult =
799          this.computeHeatmapColors_(tiles, minMax, heatmapType);
800      var heatIndex = 0;
801
802      for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
803        var rect = layer.tileCoverageRects[ct].geometryRect;
804        rect = rect.scale(1.0 / layer.geometryContentsScale);
805
806        var tile = layer.tileCoverageRects[ct].tile;
807
808        var unitRect = rect.asUVRectInside(layer.bounds);
809        var quad = layerQuad.projectUnitRect(unitRect);
810
811        quad.backgroundColor = 'rgba(0, 0, 0, 0)';
812        quad.stackingGroupId = layerQuad.stackingGroupId;
813        var type = tr.e.cc.tileTypes.missing;
814        if (tile) {
815          type = tile.getTypeForLayer(layer);
816          quad.backgroundColor = heatmapResult[heatIndex].color;
817          ++heatIndex;
818        }
819
820        quad.borderColor = tr.e.cc.tileBorder[type].color;
821        quad.borderWidth = tr.e.cc.tileBorder[type].width;
822        var label;
823        if (tile)
824          label = 'coverageRect';
825        else
826          label = 'checkerboard coverageRect';
827        quad.selectionToSetIfClicked = new cc.LayerRectSelection(
828            layer, label, rect, layer.tileCoverageRects[ct]);
829
830        quads.push(quad);
831      }
832    },
833
834    appendLayoutRectQuads_: function(quads, layer, layerQuad) {
835      if (!layer.layoutRects) {
836        return;
837      }
838
839      for (var ct = 0; ct < layer.layoutRects.length; ++ct) {
840        var rect = layer.layoutRects[ct].geometryRect;
841        rect = rect.scale(1.0 / layer.geometryContentsScale);
842
843        var unitRect = rect.asUVRectInside(layer.bounds);
844        var quad = layerQuad.projectUnitRect(unitRect);
845
846        quad.backgroundColor = 'rgba(0, 0, 0, 0)';
847        quad.stackingGroupId = layerQuad.stackingGroupId;
848
849        quad.borderColor = 'rgba(0, 0, 200, 0.7)';
850        quad.borderWidth = 2;
851        var label;
852        label = 'Layout rect';
853        quad.selectionToSetIfClicked = new cc.LayerRectSelection(
854            layer, label, rect);
855
856        quads.push(quad);
857      }
858    },
859
860    getValueForHeatmap_: function(tile, heatmapType) {
861      if (heatmapType == TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY) {
862        return tile.scheduledPriority == 0 ?
863            undefined :
864            tile.scheduledPriority;
865      } else if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
866        if (tile.isSolidColor)
867          return 0.5;
868        return tile.isUsingGpuMemory ? 0 : 1;
869      }
870    },
871
872    getMinMaxForHeatmap_: function(tiles, heatmapType) {
873      var range = new tr.b.Range();
874      if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
875        range.addValue(0);
876        range.addValue(1);
877        return range;
878      }
879
880      for (var i = 0; i < tiles.length; ++i) {
881        var value = this.getValueForHeatmap_(tiles[i], heatmapType);
882        if (value === undefined)
883          continue;
884        range.addValue(value);
885      }
886      if (range.range === 0)
887        range.addValue(1);
888      return range;
889    },
890
891    computeHeatmapColors_: function(tiles, minMax, heatmapType) {
892      var min = minMax.min;
893      var max = minMax.max;
894
895      var color = function(value) {
896        var hue = 120 * (1 - (value - min) / (max - min));
897        if (hue < 0)
898          hue = 0;
899        return 'hsla(' + hue + ', 100%, 50%, 0.5)';
900      };
901
902      var values = [];
903      for (var i = 0; i < tiles.length; ++i) {
904        var tile = tiles[i];
905        var value = this.getValueForHeatmap_(tile, heatmapType);
906        var res = {
907          value: value,
908          color: value !== undefined ? color(value) : undefined
909        };
910        values.push(res);
911      }
912
913      return values;
914    },
915
916    appendTilesWithScaleQuads_: function(
917        quads, layer, layerQuad, scale, heatmapType) {
918      var lthi = this.layerTreeImpl_.layerTreeHostImpl;
919
920      var tiles = [];
921      for (var i = 0; i < lthi.activeTiles.length; ++i) {
922        var tile = lthi.activeTiles[i];
923
924        if (Math.abs(tile.contentsScale - scale) > 1e-6)
925          continue;
926
927        // TODO(vmpstr): Make the stiching of tiles and layers a part of
928        // tile construction (issue 346)
929        if (layer.layerId != tile.layerId)
930          continue;
931
932        tiles.push(tile);
933      }
934
935      var minMax =
936          this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType);
937      var heatmapResult =
938          this.computeHeatmapColors_(tiles, minMax, heatmapType);
939
940      for (var i = 0; i < tiles.length; ++i) {
941        var tile = tiles[i];
942        var rect = tile.layerRect;
943        if (!tile.layerRect)
944          continue;
945        var unitRect = rect.asUVRectInside(layer.bounds);
946        var quad = layerQuad.projectUnitRect(unitRect);
947
948        quad.backgroundColor = 'rgba(0, 0, 0, 0)';
949        quad.stackingGroupId = layerQuad.stackingGroupId;
950
951        var type = tile.getTypeForLayer(layer);
952        quad.borderColor = tr.e.cc.tileBorder[type].color;
953        quad.borderWidth = tr.e.cc.tileBorder[type].width;
954
955        quad.backgroundColor = heatmapResult[i].color;
956        var data = {
957          tileType: type
958        };
959        if (heatmapType !== TILE_HEATMAP_TYPE.NONE)
960          data[heatmapType] = heatmapResult[i].value;
961        quad.selectionToSetIfClicked = new cc.TileSelection(tile, data);
962        quads.push(quad);
963      }
964    },
965
966    appendHighlightQuadsForLayer_: function(
967        quads, layer, layerQuad, highlights) {
968      highlights.forEach(function(highlight) {
969        var rect = highlight.rect;
970
971        var unitRect = rect.asUVRectInside(layer.bounds);
972        var quad = layerQuad.projectUnitRect(unitRect);
973
974        var colorId = ColorScheme.getColorIdForGeneralPurposeString(
975            highlight.colorKey);
976        colorId += ColorScheme.properties.brightenedOffsets[0];
977
978        var color = ColorScheme.colors[colorId];
979
980        var quadForDrawing = quad.clone();
981        quadForDrawing.backgroundColor = color.withAlpha(0.5).toString();
982        quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString();
983        quadForDrawing.stackingGroupId = layerQuad.stackingGroupId;
984        quads.push(quadForDrawing);
985
986      }, this);
987    },
988
989    generateRenderPassQuads: function() {
990      if (!this.layerTreeImpl.layerTreeHostImpl.args.frame)
991        return [];
992      var renderPasses = this.renderPasses;
993      if (!renderPasses)
994        return [];
995
996      var quads = [];
997      for (var i = 0; i < renderPasses.length; ++i) {
998        var quadList = renderPasses[i].quadList;
999        for (var j = 0; j < quadList.length; ++j) {
1000          var drawQuad = quadList[j];
1001          var quad = drawQuad.rectAsTargetSpaceQuad.clone();
1002          quad.borderColor = 'rgb(170, 204, 238)';
1003          quad.borderWidth = 2;
1004          quad.stackingGroupId = i;
1005          quads.push(quad);
1006        }
1007      }
1008      return quads;
1009    },
1010
1011    generateLayerQuads: function() {
1012      this.updateContentsPending_ = false;
1013
1014      // Generate the quads for the view.
1015      var layers = this.layers;
1016      var quads = [];
1017      var nextStackingGroupId = 0;
1018      var alreadyVisitedLayerIds = {};
1019
1020
1021      var selectionHighlightsByLayerId;
1022      if (this.selection)
1023        selectionHighlightsByLayerId = this.selection.highlightsByLayerId;
1024      else
1025        selectionHighlightsByLayerId = {};
1026
1027      var extraHighlightsByLayerId = this.extraHighlightsByLayerId || {};
1028
1029      for (var i = 1; i <= layers.length; i++) {
1030        // Generate quads back-to-front.
1031        var layer = layers[layers.length - i];
1032        alreadyVisitedLayerIds[layer.layerId] = true;
1033        if (layer.objectInstance.name == 'cc::NinePatchLayerImpl')
1034          continue;
1035
1036        var layerQuad = layer.layerQuad.clone();
1037        if (layer.usingGpuRasterization) {
1038          var pixelRatio = window.devicePixelRatio || 1;
1039          layerQuad.borderWidth = 2.0 * pixelRatio;
1040          layerQuad.borderColor = 'rgba(154,205,50,0.75)';
1041        } else {
1042          layerQuad.borderColor = 'rgba(0,0,0,0.75)';
1043        }
1044        layerQuad.stackingGroupId = nextStackingGroupId++;
1045        layerQuad.selectionToSetIfClicked = new cc.LayerSelection(layer);
1046        layerQuad.layer = layer;
1047        if (this.showOtherLayers && this.selectedLayer == layer)
1048          layerQuad.upperBorderColor = 'rgb(156,189,45)';
1049
1050        if (this.showAnimationBounds)
1051          this.appendAnimationQuads_(quads, layer, layerQuad);
1052
1053        this.appendImageQuads_(quads, layer, layerQuad);
1054        quads.push(layerQuad);
1055
1056
1057        if (this.showInvalidations)
1058          this.appendInvalidationQuads_(quads, layer, layerQuad);
1059        if (this.showUnrecordedRegion)
1060          this.appendUnrecordedRegionQuads_(quads, layer, layerQuad);
1061        if (this.showBottlenecks)
1062          this.appendBottleneckQuads_(quads, layer, layerQuad,
1063                                      layerQuad.stackingGroupId);
1064        if (this.showLayoutRects)
1065          this.appendLayoutRectQuads_(quads, layer, layerQuad);
1066
1067        if (this.howToShowTiles === 'coverage') {
1068          this.appendTileCoverageRectQuads_(
1069              quads, layer, layerQuad, this.tileHeatmapType);
1070        } else if (this.howToShowTiles !== 'none') {
1071          this.appendTilesWithScaleQuads_(
1072              quads, layer, layerQuad,
1073              this.howToShowTiles, this.tileHeatmapType);
1074        }
1075
1076        var highlights;
1077        highlights = extraHighlightsByLayerId[layer.layerId];
1078        if (highlights) {
1079          this.appendHighlightQuadsForLayer_(
1080              quads, layer, layerQuad, highlights);
1081        }
1082
1083        highlights = selectionHighlightsByLayerId[layer.layerId];
1084        if (highlights) {
1085          this.appendHighlightQuadsForLayer_(
1086              quads, layer, layerQuad, highlights);
1087        }
1088      }
1089
1090      this.layerTreeImpl.iterLayers(function(layer, depth, isMask, isReplica) {
1091        if (!this.showOtherLayers && this.selectedLayer != layer)
1092          return;
1093        if (alreadyVisitedLayerIds[layer.layerId])
1094          return;
1095        var layerQuad = layer.layerQuad;
1096        var stackingGroupId = nextStackingGroupId++;
1097        if (this.showBottlenecks)
1098          this.appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId);
1099      }, this);
1100
1101      var tracedInputLatencies = this.layerTreeImpl.tracedInputLatencies;
1102      if (this.showInputEvents && tracedInputLatencies) {
1103        for (var i = 0; i < tracedInputLatencies.length; i++) {
1104          var coordinatesArray = tracedInputLatencies[i].args.data.coordinates;
1105          for (var j = 0; j < coordinatesArray.length; j++) {
1106            var inputQuad = tr.b.Quad.fromXYWH(
1107                coordinatesArray[j].x - 25,
1108                coordinatesArray[j].y - 25,
1109                50,
1110                50);
1111            inputQuad.borderColor = 'rgba(0, 0, 0, 0)';
1112            inputQuad.imageData = this.inputEventImageData_;
1113            quads.push(inputQuad);
1114          }
1115        }
1116      }
1117
1118      return quads;
1119    },
1120
1121    updateInfoBar_: function(infoBarMessages) {
1122      if (infoBarMessages.length) {
1123        this.infoBar_.removeAllButtons();
1124        this.infoBar_.message = 'Some problems were encountered...';
1125        this.infoBar_.addButton('More info...', function(e) {
1126          var overlay = new tr.ui.b.Overlay();
1127          overlay.textContent = '';
1128          infoBarMessages.forEach(function(message) {
1129            var title = document.createElement('h3');
1130            title.textContent = message.header;
1131
1132            var details = document.createElement('div');
1133            details.textContent = message.details;
1134
1135            overlay.appendChild(title);
1136            overlay.appendChild(details);
1137          });
1138          overlay.visible = true;
1139
1140          e.stopPropagation();
1141          return false;
1142        });
1143        this.infoBar_.visible = true;
1144      } else {
1145        this.infoBar_.removeAllButtons();
1146        this.infoBar_.message = '';
1147        this.infoBar_.visible = false;
1148      }
1149    },
1150
1151    getWhatRasterized_: function() {
1152      var lthi = this.layerTreeImpl_.layerTreeHostImpl;
1153      var renderProcess = lthi.objectInstance.parent;
1154      var tasks = [];
1155      renderProcess.iterateAllEvents(function(event) {
1156        if (!(event instanceof tr.model.Slice))
1157          return;
1158
1159        var tile = tr.e.cc.getTileFromRasterTaskSlice(event);
1160        if (tile === undefined)
1161          return false;
1162
1163        if (tile.containingSnapshot == lthi)
1164          tasks.push(event);
1165      }, this);
1166      return tasks;
1167    },
1168
1169    updateWhatRasterizedLinkState_: function() {
1170      var tasks = this.getWhatRasterized_();
1171      if (tasks.length) {
1172        this.whatRasterizedLink_.textContent = tasks.length + ' raster tasks';
1173        this.whatRasterizedLink_.style.display = '';
1174      } else {
1175        this.whatRasterizedLink_.textContent = '';
1176        this.whatRasterizedLink_.style.display = 'none';
1177      }
1178    },
1179
1180    onWhatRasterizedLinkClicked_: function() {
1181      var tasks = this.getWhatRasterized_();
1182      var event = new tr.model.RequestSelectionChangeEvent();
1183      event.selection = new tr.model.EventSet(tasks);
1184      this.dispatchEvent(event);
1185    }
1186  };
1187
1188  return {
1189    LayerTreeQuadStackView: LayerTreeQuadStackView
1190  };
1191});
1192</script>
1193