Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / trace_viewer / cc / layer_tree_quad_stack_view.js
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 /**
8  * @fileoverview Graphical view of  LayerTreeImpl, with controls for
9  * type of layer content shown and info bar for content-loading warnings.
10  */
11
12 tvcm.requireStylesheet('cc.layer_tree_quad_stack_view');
13
14 tvcm.require('tvcm.color');
15 tvcm.require('tvcm.properties');
16 tvcm.require('tvcm.raf');
17 tvcm.require('tvcm.quad');
18 tvcm.require('tvcm.range');
19 tvcm.require('cc.picture');
20 tvcm.require('cc.render_pass');
21 tvcm.require('cc.tile');
22 tvcm.require('cc.debug_colors');
23 tvcm.require('tvcm.ui.quad_stack_view');
24 tvcm.require('tvcm.ui.info_bar');
25
26
27 tvcm.exportTo('cc', function() {
28
29   var TILE_HEATMAP_TYPE = {};
30   TILE_HEATMAP_TYPE.NONE = 0;
31   TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY = 1;
32   TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE = 2;
33   TILE_HEATMAP_TYPE.TIME_TO_VISIBLE = 3;
34   TILE_HEATMAP_TYPE.USING_GPU_MEMORY = 4;
35
36   function createTileRectsSelectorBaseOptions() {
37     return [{label: 'None', value: 'none'},
38             {label: 'Coverage Rects', value: 'coverage'}];
39   }
40
41   /**
42    * @constructor
43    */
44   var LayerTreeQuadStackView = tvcm.ui.define('layer-tree-quad-stack-view');
45
46   LayerTreeQuadStackView.prototype = {
47     __proto__: HTMLDivElement.prototype,
48
49     decorate: function() {
50       this.isRenderPassQuads_ = false;
51       this.pictureAsImageData_ = {}; // Maps picture.guid to PictureAsImageData.
52       this.messages_ = [];
53       this.controls_ = document.createElement('top-controls');
54       this.infoBar_ = new tvcm.ui.InfoBar();
55       this.quadStackView_ = new tvcm.ui.QuadStackView();
56       this.quadStackView_.addEventListener(
57           'selectionchange', this.onQuadStackViewSelectionChange_.bind(this));
58
59       var m = tvcm.ui.MOUSE_SELECTOR_MODE;
60       var mms = this.quadStackView_.mouseModeSelector;
61       mms.settingsKey = 'cc.layerTreeQuadStackView.mouseModeSelector';
62       mms.setKeyCodeForMode(m.SELECTION, 'Z'.charCodeAt(0));
63       mms.setKeyCodeForMode(m.PANSCAN, 'X'.charCodeAt(0));
64       mms.setKeyCodeForMode(m.ZOOM, 'C'.charCodeAt(0));
65       mms.setKeyCodeForMode(m.ROTATE, 'V'.charCodeAt(0));
66
67       this.appendChild(this.controls_);
68       this.appendChild(this.infoBar_);
69       this.appendChild(this.quadStackView_);
70
71       this.tileRectsSelector_ = tvcm.ui.createSelector(
72           this, 'howToShowTiles',
73           'layerView.howToShowTiles', 'none',
74           createTileRectsSelectorBaseOptions());
75       this.controls_.appendChild(this.tileRectsSelector_);
76
77       var tileHeatmapText = tvcm.ui.createSpan({
78         textContent: 'Tile heatmap:'
79       });
80       this.controls_.appendChild(tileHeatmapText);
81
82       var tileHeatmapSelector = tvcm.ui.createSelector(
83           this, 'tileHeatmapType',
84           'layerView.tileHeatmapType', TILE_HEATMAP_TYPE.NONE,
85           [{label: 'None',
86             value: TILE_HEATMAP_TYPE.NONE},
87            {label: 'Scheduled Priority',
88             value: TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY},
89            {label: 'Distance to Visible',
90             value: TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE},
91            {label: 'Time to Visible',
92             value: TILE_HEATMAP_TYPE.TIME_TO_VISIBLE},
93            {label: 'Is using GPU memory',
94             value: TILE_HEATMAP_TYPE.USING_GPU_MEMORY}
95           ]);
96       this.controls_.appendChild(tileHeatmapSelector);
97
98       var showOtherLayersCheckbox = tvcm.ui.createCheckBox(
99           this, 'showOtherLayers',
100           'layerView.showOtherLayers', true,
101           'Other layers/passes');
102       showOtherLayersCheckbox.title =
103           'When checked, show all layers, selected or not.';
104       this.controls_.appendChild(showOtherLayersCheckbox);
105
106       var showInvalidationsCheckbox = tvcm.ui.createCheckBox(
107           this, 'showInvalidations',
108           'layerView.showInvalidations', true,
109           'Invalidations');
110       showInvalidationsCheckbox.title =
111           'When checked, compositing invalidations are highlighted in red';
112       this.controls_.appendChild(showInvalidationsCheckbox);
113
114       var showUnrecordedRegionCheckbox = tvcm.ui.createCheckBox(
115           this, 'showUnrecordedRegion',
116           'layerView.showUnrecordedRegion', true,
117           'Unrecorded area');
118       showUnrecordedRegionCheckbox.title =
119           'When checked, unrecorded areas are highlighted in yellow';
120       this.controls_.appendChild(showUnrecordedRegionCheckbox);
121
122       var showBottlenecksCheckbox = tvcm.ui.createCheckBox(
123           this, 'showBottlenecks',
124           'layerView.showBottlenecks', true,
125           'Bottlenecks');
126       showBottlenecksCheckbox.title =
127           'When checked, scroll bottlenecks are highlighted';
128       this.controls_.appendChild(showBottlenecksCheckbox);
129
130       var showLayoutRectsCheckbox = tvcm.ui.createCheckBox(
131           this, 'showLayoutRects',
132           'layerView.showLayoutRects', false,
133           'Layout rects');
134       showLayoutRectsCheckbox.title =
135           'When checked, shows rects for regions where layout happened';
136       this.controls_.appendChild(showLayoutRectsCheckbox);
137
138       var showContentsCheckbox = tvcm.ui.createCheckBox(
139           this, 'showContents',
140           'layerView.showContents', true,
141           'Contents');
142       showContentsCheckbox.title =
143           'When checked, show the rendered contents inside the layer outlines';
144       this.controls_.appendChild(showContentsCheckbox);
145
146       var showAnimationBoundsCheckbox = tvcm.ui.createCheckBox(
147           this, 'showAnimationBounds',
148           'layerView.showAnimationBounds', false,
149           'Animation Bounds');
150       showAnimationBoundsCheckbox.title = 'When checked, show a border around' +
151           ' a layer showing the extent of its animation.';
152       this.controls_.appendChild(showAnimationBoundsCheckbox);
153     },
154
155     get layerTreeImpl() {
156       return this.layerTreeImpl_;
157     },
158
159     set whichTree(whichTree) {
160       this.whichTree_ = whichTree;
161     },
162
163     set isRenderPassQuads(newValue) {
164       this.isRenderPassQuads_ = newValue;
165     },
166
167     set layerTreeImpl(layerTreeImpl) {
168       // FIXME(pdr): We may want to clear pictureAsImageData_ here to save
169       //             memory at the cost of performance. Note that
170       //             pictureAsImageData_ will be cleared when this is
171       //             destructed, but this view might live for several
172       //             layerTreeImpls.
173       this.layerTreeImpl_ = layerTreeImpl;
174       this.selection = undefined;
175     },
176
177     get showOtherLayers() {
178       return this.showOtherLayers_;
179     },
180
181     set showOtherLayers(show) {
182       this.showOtherLayers_ = show;
183       this.updateContents_();
184     },
185
186     get showAnimationBounds() {
187       return this.showAnimationBounds_;
188     },
189
190     set showAnimationBounds(show) {
191       this.showAnimationBounds_ = show;
192       this.updateContents_();
193     },
194
195     get showContents() {
196       return this.showContents_;
197     },
198
199     set showContents(show) {
200       this.showContents_ = show;
201       this.updateContents_();
202     },
203
204     get showInvalidations() {
205       return this.showInvalidations_;
206     },
207
208     set showInvalidations(show) {
209       this.showInvalidations_ = show;
210       this.updateContents_();
211     },
212
213     get showUnrecordedRegion() {
214       return this.showUnrecordedRegion_;
215     },
216
217     set showUnrecordedRegion(show) {
218       this.showUnrecordedRegion_ = show;
219       this.updateContents_();
220     },
221
222     get showBottlenecks() {
223       return this.showBottlenecks_;
224     },
225
226     set showBottlenecks(show) {
227       this.showBottlenecks_ = show;
228       this.updateContents_();
229     },
230
231     get showLayoutRects() {
232       return this.showLayoutRects_;
233     },
234
235     set showLayoutRects(show) {
236       this.showLayoutRects_ = show;
237       this.updateContents_();
238     },
239
240     get howToShowTiles() {
241       return this.howToShowTiles_;
242     },
243
244     set howToShowTiles(val) {
245       // Make sure val is something we expect.
246       console.assert(
247           (val === 'none') ||
248           (val === 'coverage') ||
249           !isNaN(parseFloat(val)));
250
251       this.howToShowTiles_ = val;
252       this.updateContents_();
253     },
254
255     get tileHeatmapType() {
256       return this.tileHeatmapType_;
257     },
258
259     set tileHeatmapType(val) {
260       this.tileHeatmapType_ = val;
261       this.updateContents_();
262     },
263
264     get selection() {
265       return this.selection_;
266     },
267
268     set selection(selection) {
269       tvcm.setPropertyAndDispatchChange(this, 'selection', selection);
270       this.updateContents_();
271     },
272
273     regenerateContent: function() {
274       this.updateTilesSelector_();
275       this.updateContents_();
276     },
277
278     onQuadStackViewSelectionChange_: function(e) {
279       var selectableQuads = e.quads.filter(function(q) {
280         return q.selectionToSetIfClicked !== undefined;
281       });
282       if (selectableQuads.length == 0) {
283         this.selection = undefined;
284         return;
285       }
286
287       // Sort the quads low to high on stackingGroupId.
288       selectableQuads.sort(function(x, y) {
289         var z = x.stackingGroupId - y.stackingGroupId;
290         if (z != 0)
291           return z;
292         return x.selectionToSetIfClicked.specicifity -
293             y.selectionToSetIfClicked.specicifity;
294       });
295
296       // TODO(nduca): Support selecting N things at once.
297       var quadToSelect = selectableQuads[selectableQuads.length - 1];
298       this.selection = quadToSelect.selectionToSetIfClicked;
299     },
300
301     scheduleUpdateContents_: function() {
302       if (this.updateContentsPending_)
303         return;
304       this.updateContentsPending_ = true;
305       tvcm.requestAnimationFrameInThisFrameIfPossible(
306           this.updateContents_, this);
307     },
308
309     updateContents_: function() {
310       if (!this.layerTreeImpl_)
311         return;
312
313
314       var status = this.computePictureLoadingStatus_();
315       if (!status.picturesComplete)
316         return;
317
318       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
319       var lthiInstance = lthi.objectInstance;
320       var worldViewportRect = tvcm.Rect.fromXYWH(
321           0, 0,
322           lthi.deviceViewportSize.width, lthi.deviceViewportSize.height);
323       this.quadStackView_.deviceRect = worldViewportRect;
324       if (this.isRenderPassQuads_)
325         this.quadStackView_.quads = this.generateRenderPassQuads();
326       else
327         this.quadStackView_.quads = this.generateLayerQuads();
328
329       this.updateInfoBar_(status.messages);
330     },
331
332     updateTilesSelector_: function() {
333       var data = createTileRectsSelectorBaseOptions();
334
335       if (this.layerTreeImpl_) {
336         // First get all of the scales information from LTHI.
337         var lthi = this.layerTreeImpl_.layerTreeHostImpl;
338         var scaleNames = lthi.getContentsScaleNames();
339         for (var scale in scaleNames) {
340           data.push({
341             label: 'Scale ' + scale + ' (' + scaleNames[scale] + ')',
342             value: scale
343           });
344         }
345       }
346
347       // Then create a new selector and replace the old one.
348       var new_selector = tvcm.ui.createSelector(
349           this, 'howToShowTiles',
350           'layerView.howToShowTiles', 'none',
351           data);
352       this.controls_.replaceChild(new_selector, this.tileRectsSelector_);
353       this.tileRectsSelector_ = new_selector;
354     },
355
356     computePictureLoadingStatus_: function() {
357       // Figure out if we can draw the quads yet. While we're at it, figure out
358       // if we have any warnings we need to show.
359       var layers = this.layers;
360       var status = {
361         messages: [],
362         picturesComplete: true
363       };
364       if (this.showContents) {
365         var hasPendingRasterizeImage = false;
366         var firstPictureError = undefined;
367         var hasMissingLayerRect = false;
368         var hasUnresolvedPictureRef = false;
369         for (var i = 0; i < layers.length; i++) {
370           var layer = layers[i];
371           for (var ir = 0; ir < layer.pictures.length; ++ir) {
372             var picture = layer.pictures[ir];
373
374             if (picture.idRef) {
375               hasUnresolvedPictureRef = true;
376               continue;
377             }
378             if (!picture.layerRect) {
379               hasMissingLayerRect = true;
380               continue;
381             }
382
383             var pictureAsImageData = this.pictureAsImageData_[picture.guid];
384             if (!pictureAsImageData) {
385               hasPendingRasterizeImage = true;
386               this.pictureAsImageData_[picture.guid] =
387                   cc.PictureAsImageData.Pending(this);
388               picture.rasterize(
389                   {stopIndex: undefined},
390                   function(pictureImageData) {
391                     var picture_ = pictureImageData.picture;
392                     this.pictureAsImageData_[picture_.guid] = pictureImageData;
393                     this.scheduleUpdateContents_();
394                   }.bind(this));
395               continue;
396             }
397             if (pictureAsImageData.isPending()) {
398               hasPendingRasterizeImage = true;
399               continue;
400             }
401             if (pictureAsImageData.error) {
402               if (!firstPictureError)
403                 firstPictureError = pictureAsImageData.error;
404               break;
405             }
406           }
407         }
408         if (hasPendingRasterizeImage) {
409           status.picturesComplete = false;
410         } else {
411           if (hasUnresolvedPictureRef) {
412             status.messages.push({
413               header: 'Missing picture',
414               details: 'Your trace didnt have pictures for every layer. ' +
415                   'Old chrome versions had this problem'});
416           }
417           if (hasMissingLayerRect) {
418             status.messages.push({
419               header: 'Missing layer rect',
420               details: 'Your trace may be corrupt or from a very old ' +
421                   'Chrome revision.'});
422           }
423           if (firstPictureError) {
424             status.messages.push({
425               header: 'Cannot rasterize',
426               details: firstPictureError});
427           }
428         }
429       }
430       return status;
431     },
432
433     get selectedRenderPass() {
434       if (this.selection)
435         return this.selection.renderPass_;
436     },
437
438     get selectedLayer() {
439       if (this.selection) {
440         var selectedLayerId = this.selection.associatedLayerId;
441         return this.layerTreeImpl_.findLayerWithId(selectedLayerId);
442       }
443     },
444
445     get renderPasses() {
446       var renderPasses =
447           this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses;
448       if (!this.showOtherLayers) {
449         var selectedRenderPass = this.selectedRenderPass;
450         if (selectedRenderPass)
451           renderPasses = [selectedRenderPass];
452       }
453       return renderPasses;
454     },
455
456     get layers() {
457       var layers = this.layerTreeImpl.renderSurfaceLayerList;
458       if (!this.showOtherLayers) {
459         var selectedLayer = this.selectedLayer;
460         if (selectedLayer)
461           layers = [selectedLayer];
462       }
463       return layers;
464     },
465
466     appendImageQuads_: function(quads, layer, layerQuad) {
467       // Generate image quads for the layer
468       for (var ir = 0; ir < layer.pictures.length; ++ir) {
469         var picture = layer.pictures[ir];
470         if (!picture.layerRect)
471           continue;
472
473         var unitRect = picture.layerRect.asUVRectInside(layer.bounds);
474         var iq = layerQuad.projectUnitRect(unitRect);
475
476         var pictureData = this.pictureAsImageData_[picture.guid];
477         if (this.showContents && pictureData && pictureData.imageData) {
478           iq.imageData = pictureData.imageData;
479           iq.borderColor = 'rgba(0,0,0,0)';
480         } else {
481           iq.imageData = undefined;
482         }
483
484         iq.stackingGroupId = layerQuad.stackingGroupId;
485         quads.push(iq);
486       }
487     },
488
489     appendAnimationQuads_: function(quads, layer, layerQuad) {
490       if (!layer.animationBoundsRect)
491         return;
492
493       var rect = layer.animationBoundsRect;
494       var abq = tvcm.Quad.fromRect(rect);
495
496       abq.backgroundColor = 'rgba(164,191,48,0.5)';
497       abq.borderColor = 'rgba(205,255,0,0.75)';
498       abq.borderWidth = 3.0;
499       abq.stackingGroupId = layerQuad.stackingGroupId;
500       abq.selectionToSetIfClicked = new cc.AnimationRectSelection(
501           layer, rect);
502       quads.push(abq);
503     },
504
505     appendInvalidationQuads_: function(quads, layer, layerQuad) {
506       // Generate the invalidation rect quads.
507       for (var ir = 0; ir < layer.invalidation.rects.length; ir++) {
508         var rect = layer.invalidation.rects[ir];
509         var unitRect = rect.asUVRectInside(layer.bounds);
510         var iq = layerQuad.projectUnitRect(unitRect);
511         iq.backgroundColor = 'rgba(255, 0, 0, 0.1)';
512         iq.borderColor = 'rgba(255, 0, 0, 1)';
513         iq.stackingGroupId = layerQuad.stackingGroupId;
514         iq.selectionToSetIfClicked = new cc.LayerRectSelection(
515             layer, 'Invalidation rect', rect, rect);
516         quads.push(iq);
517       }
518     },
519
520     appendUnrecordedRegionQuads_: function(quads, layer, layerQuad) {
521       // Generate the unrecorded region quads.
522       for (var ir = 0; ir < layer.unrecordedRegion.rects.length; ir++) {
523         var rect = layer.unrecordedRegion.rects[ir];
524         var unitRect = rect.asUVRectInside(layer.bounds);
525         var iq = layerQuad.projectUnitRect(unitRect);
526         iq.backgroundColor = 'rgba(240, 230, 140, 0.3)';
527         iq.borderColor = 'rgba(240, 230, 140, 1)';
528         iq.stackingGroupId = layerQuad.stackingGroupId;
529         iq.selectionToSetIfClicked = new cc.LayerRectSelection(
530             layer, 'Unrecorded area', rect, rect);
531         quads.push(iq);
532       }
533     },
534
535     appendBottleneckQuads_: function(quads, layer, layerQuad, stackingGroupId) {
536       function processRegion(region, label, borderColor) {
537         var backgroundColor = borderColor.clone();
538         backgroundColor.a = 0.4 * (borderColor.a || 1.0);
539
540         for (var ir = 0; ir < region.rects.length; ir++) {
541           var rect = region.rects[ir];
542           var unitRect = rect.asUVRectInside(layer.bounds);
543           var iq = layerQuad.projectUnitRect(unitRect);
544           iq.backgroundColor = backgroundColor.toString();
545           iq.borderColor = borderColor.toString();
546           iq.borderWidth = 4.0;
547           iq.stackingGroupId = stackingGroupId;
548           iq.selectionToSetIfClicked = new cc.LayerRectSelection(
549               layer, label, rect, rect);
550           quads.push(iq);
551         }
552       }
553
554       processRegion(layer.touchEventHandlerRegion, 'Touch listener',
555                     tvcm.Color.fromString('rgb(228, 226, 27)'));
556       processRegion(layer.wheelEventHandlerRegion, 'Wheel listener',
557                     tvcm.Color.fromString('rgb(176, 205, 29)'));
558       processRegion(layer.nonFastScrollableRegion, 'Repaints on scroll',
559                     tvcm.Color.fromString('rgb(213, 134, 32)'));
560     },
561
562     appendTileCoverageRectQuads_: function(
563         quads, layer, layerQuad, heatmapType) {
564       if (!layer.tileCoverageRects)
565         return;
566
567       var tiles = [];
568       for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
569         var tile = layer.tileCoverageRects[ct].tile;
570         if (tile !== undefined)
571           tiles.push(tile);
572       }
573
574       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
575       var minMax =
576           this.getMinMaxForHeatmap_(lthi.tiles, heatmapType);
577       var heatmapColors =
578           this.computeHeatmapColors_(tiles, minMax, heatmapType);
579       var heatIndex = 0;
580
581       for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) {
582         var rect = layer.tileCoverageRects[ct].geometryRect;
583         rect = rect.scale(1.0 / layer.geometryContentsScale);
584
585         var tile = layer.tileCoverageRects[ct].tile;
586
587         var unitRect = rect.asUVRectInside(layer.bounds);
588         var quad = layerQuad.projectUnitRect(unitRect);
589
590         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
591         quad.stackingGroupId = layerQuad.stackingGroupId;
592         var type = cc.tileTypes.missing;
593         if (tile) {
594           type = tile.getTypeForLayer(layer);
595           quad.backgroundColor = heatmapColors[heatIndex];
596           ++heatIndex;
597         }
598
599         quad.borderColor = cc.tileBorder[type].color;
600         quad.borderWidth = cc.tileBorder[type].width;
601         var label;
602         if (tile)
603           label = 'coverageRect';
604         else
605           label = 'checkerboard coverageRect';
606         quad.selectionToSetIfClicked = new cc.LayerRectSelection(
607             layer, label, rect, layer.tileCoverageRects[ct]);
608
609         quads.push(quad);
610       }
611     },
612
613     appendLayoutRectQuads_: function(quads, layer, layerQuad) {
614       if (!layer.layoutRects) {
615         return;
616       }
617
618       for (var ct = 0; ct < layer.layoutRects.length; ++ct) {
619         var rect = layer.layoutRects[ct].geometryRect;
620         rect = rect.scale(1.0 / layer.geometryContentsScale);
621
622         var unitRect = rect.asUVRectInside(layer.bounds);
623         var quad = layerQuad.projectUnitRect(unitRect);
624
625         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
626         quad.stackingGroupId = layerQuad.stackingGroupId;
627
628         quad.borderColor = 'rgba(0, 0, 200, 0.7)';
629         quad.borderWidth = 2;
630         var label;
631         label = 'Layout rect';
632         quad.selectionToSetIfClicked = new cc.LayerRectSelection(
633             layer, label, rect);
634
635         quads.push(quad);
636       }
637     },
638
639     getValueForHeatmap_: function(tile, heatmapType) {
640       if (heatmapType == TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY) {
641         return tile.scheduledPriority == 0 ?
642             undefined :
643             tile.scheduledPriority;
644       } else if (heatmapType == TILE_HEATMAP_TYPE.DISTANCE_TO_VISIBLE) {
645         return tile.distanceToVisible;
646       } else if (heatmapType == TILE_HEATMAP_TYPE.TIME_TO_VISIBLE) {
647         return Math.min(5, tile.timeToVisible);
648       } else if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
649         if (tile.isSolidColor)
650           return 0.5;
651         return tile.isUsingGpuMemory ? 0 : 1;
652       }
653     },
654
655     getMinMaxForHeatmap_: function(tiles, heatmapType) {
656       var range = new tvcm.Range();
657       if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) {
658         range.addValue(0);
659         range.addValue(1);
660         return range;
661       }
662
663       for (var i = 0; i < tiles.length; ++i) {
664         var value = this.getValueForHeatmap_(tiles[i], heatmapType);
665         if (value == undefined)
666           continue;
667         range.addValue(value);
668       }
669       if (range.range == 0)
670         range.addValue(1);
671       return range;
672     },
673
674     computeHeatmapColors_: function(tiles, minMax, heatmapType) {
675       var min = minMax.min;
676       var max = minMax.max;
677
678       var color = function(value) {
679         var hue = 120 * (1 - (value - min) / (max - min));
680         if (hue < 0)
681           hue = 0;
682         return 'hsla(' + hue + ', 100%, 50%, 0.5)';
683       };
684
685       var values = [];
686       for (var i = 0; i < tiles.length; ++i) {
687         var tile = tiles[i];
688         var value = this.getValueForHeatmap_(tile, heatmapType);
689         if (value !== undefined)
690           values.push(color(value));
691         else
692           values.push(undefined);
693       }
694
695       return values;
696     },
697
698     appendTilesWithScaleQuads_: function(
699         quads, layer, layerQuad, scale, heatmapType) {
700       var lthi = this.layerTreeImpl_.layerTreeHostImpl;
701
702       var tiles = [];
703       for (var i = 0; i < lthi.tiles.length; ++i) {
704         var tile = lthi.tiles[i];
705
706         if (Math.abs(tile.contentsScale - scale) > 1e-6)
707           continue;
708
709         // TODO(vmpstr): Make the stiching of tiles and layers a part of
710         // tile construction (issue 346)
711         if (layer.layerId != tile.layerId)
712           continue;
713
714         tiles.push(tile);
715       }
716
717       var minMax =
718           this.getMinMaxForHeatmap_(lthi.tiles, heatmapType);
719       var heatmapColors =
720           this.computeHeatmapColors_(tiles, minMax, heatmapType);
721
722       for (var i = 0; i < tiles.length; ++i) {
723         var tile = tiles[i];
724         var rect = tile.layerRect;
725         if (!tile.layerRect)
726           continue;
727         var unitRect = rect.asUVRectInside(layer.bounds);
728         var quad = layerQuad.projectUnitRect(unitRect);
729
730         quad.backgroundColor = 'rgba(0, 0, 0, 0)';
731         quad.stackingGroupId = layerQuad.stackingGroupId;
732
733         var type = tile.getTypeForLayer(layer);
734         quad.borderColor = cc.tileBorder[type].color;
735         quad.borderWidth = cc.tileBorder[type].width;
736
737         quad.backgroundColor = heatmapColors[i];
738         quad.selectionToSetIfClicked = new cc.TileSelection(tile);
739         quads.push(quad);
740       }
741     },
742
743     appendSelectionQuads_: function(quads, layer, layerQuad) {
744       var selection = this.selection;
745       var rect = selection.layerRect;
746       if (!rect)
747         return [];
748
749       var unitRect = rect.asUVRectInside(layer.bounds);
750       var quad = layerQuad.projectUnitRect(unitRect);
751
752       var colorId = tvcm.ui.getStringColorId(selection.title);
753       colorId += tvcm.ui.getColorPaletteHighlightIdBoost();
754
755       var color = tvcm.Color.fromString(tvcm.ui.getColorPalette()[colorId]);
756
757       var quadForDrawing = quad.clone();
758       quadForDrawing.backgroundColor = color.withAlpha(0.5).toString();
759       quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString();
760       quadForDrawing.stackingGroupId = layerQuad.stackingGroupId;
761       quads.push(quadForDrawing);
762     },
763
764     generateRenderPassQuads: function() {
765       if (!this.layerTreeImpl.layerTreeHostImpl.args.frame)
766         return [];
767       var renderPasses = this.renderPasses;
768       if (!renderPasses)
769         return [];
770
771       var quads = [];
772       for (var i = 0; i < renderPasses.length; ++i) {
773         var quadList = renderPasses[i].quadList;
774         for (var j = 0; j < quadList.length; ++j) {
775           var drawQuad = quadList[j];
776           var quad = drawQuad.rectAsTargetSpaceQuad.clone();
777           quad.borderColor = 'rgb(170, 204, 238)';
778           quad.borderWidth = 2;
779           quad.stackingGroupId = i;
780           quads.push(quad);
781         }
782       }
783       return quads;
784     },
785
786     generateLayerQuads: function() {
787       this.updateContentsPending_ = false;
788
789       // Generate the quads for the view.
790       var layers = this.layers;
791       var quads = [];
792       var nextStackingGroupId = 0;
793       var alreadyVisitedLayerIds = {};
794
795       for (var i = 1; i <= layers.length; i++) {
796         // Generate quads back-to-front.
797         var layer = layers[layers.length - i];
798         alreadyVisitedLayerIds[layer.layerId] = true;
799         if (layer.objectInstance.name == 'cc::NinePatchLayerImpl')
800           continue;
801
802         var layerQuad = layer.layerQuad.clone();
803         if (layer.usingGpuRasterization) {
804           var pixelRatio = window.devicePixelRatio || 1;
805           layerQuad.borderWidth = 2.0 * pixelRatio;
806           layerQuad.borderColor = 'rgba(154,205,50,0.75)';
807         } else {
808           layerQuad.borderColor = 'rgba(0,0,0,0.75)';
809         }
810         layerQuad.stackingGroupId = nextStackingGroupId++;
811         layerQuad.selectionToSetIfClicked = new cc.LayerSelection(layer);
812         layerQuad.layer = layer;
813         if (this.showOtherLayers && this.selectedLayer == layer)
814           layerQuad.upperBorderColor = 'rgb(156,189,45)';
815
816         if (this.showAnimationBounds)
817           this.appendAnimationQuads_(quads, layer, layerQuad);
818
819         this.appendImageQuads_(quads, layer, layerQuad);
820         quads.push(layerQuad);
821
822
823         if (this.showInvalidations)
824           this.appendInvalidationQuads_(quads, layer, layerQuad);
825         if (this.showUnrecordedRegion)
826           this.appendUnrecordedRegionQuads_(quads, layer, layerQuad);
827         if (this.showBottlenecks)
828           this.appendBottleneckQuads_(quads, layer, layerQuad,
829                                       layerQuad.stackingGroupId);
830         if (this.showLayoutRects)
831           this.appendLayoutRectQuads_(quads, layer, layerQuad);
832
833         if (this.howToShowTiles === 'coverage') {
834           this.appendTileCoverageRectQuads_(
835               quads, layer, layerQuad, this.tileHeatmapType);
836         } else if (this.howToShowTiles !== 'none') {
837           this.appendTilesWithScaleQuads_(
838               quads, layer, layerQuad,
839               this.howToShowTiles, this.tileHeatmapType);
840         }
841
842         if (this.selectedLayer === layer)
843           this.appendSelectionQuads_(quads, layer, layerQuad);
844       }
845
846       this.layerTreeImpl.iterLayers(function(layer, depth, isMask, isReplica) {
847         if (!this.showOtherLayers && this.selectedLayer != layer)
848           return;
849         if (alreadyVisitedLayerIds[layer.layerId])
850           return;
851         var layerQuad = layer.layerQuad;
852         var stackingGroupId = nextStackingGroupId++;
853         if (this.showBottlenecks)
854           this.appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId);
855       }, this);
856
857       return quads;
858     },
859
860     updateInfoBar_: function(infoBarMessages) {
861       if (infoBarMessages.length) {
862         this.infoBar_.removeAllButtons();
863         this.infoBar_.message = 'Some problems were encountered...';
864         this.infoBar_.addButton('More info...', function(e) {
865           var overlay = new tvcm.ui.Overlay();
866           overlay.textContent = '';
867           infoBarMessages.forEach(function(message) {
868             var title = document.createElement('h3');
869             title.textContent = message.header;
870
871             var details = document.createElement('div');
872             details.textContent = message.details;
873
874             overlay.appendChild(title);
875             overlay.appendChild(details);
876           });
877           overlay.visible = true;
878
879           e.stopPropagation();
880           return false;
881         });
882         this.infoBar_.visible = true;
883       } else {
884         this.infoBar_.removeAllButtons();
885         this.infoBar_.message = '';
886         this.infoBar_.visible = false;
887       }
888     }
889   };
890
891   return {
892     LayerTreeQuadStackView: LayerTreeQuadStackView
893   };
894 });