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 base.requireStylesheet('cc.layer_tree_quad_stack_viewer'); 13 14 base.require('base.properties'); 15 base.require('base.raf'); 16 base.require('cc.constants'); 17 base.require('cc.picture'); 18 base.require('ui.quad_stack_viewer'); 19 base.require('ui.info_bar'); 20 21 22 base.exportTo('cc', function() { 23 var constants = { 24 // The ratio of quad stack pixels to world pixels before CSS scaling. 25 QUAD_STACK_SCALE: 0.5 26 }; 27 28 /** 29 * @constructor 30 */ 31 var LayerTreeQuadStackViewer = ui.define('layer-tree-quad-stack-viewer'); 32 33 LayerTreeQuadStackViewer.prototype = { 34 __proto__: HTMLDivElement.prototype, 35 36 decorate: function() { 37 this.pictureAsCanvas_ = {}; // Maps picture.guid to PictureAsCanvas. 38 this.quads_ = []; 39 this.messages_ = []; 40 this.controls_ = document.createElement('top-controls'); 41 this.infoBar_ = new ui.InfoBar(); 42 this.quadStackViewer_ = new ui.QuadStackViewer(); 43 44 this.appendChild(this.controls_); 45 this.appendChild(this.infoBar_); 46 this.appendChild(this.quadStackViewer_); 47 48 var scaleSelector = ui.createSelector( 49 this.quadStackViewer_, 'scale', 50 'layerViewer.scale', 0.375, 51 [{label: '6.25%', value: 0.0625}, 52 {label: '12.5%', value: 0.125}, 53 {label: '25%', value: 0.25}, 54 {label: '37.5%', value: 0.375}, 55 {label: '50%', value: 0.5}, 56 {label: '75%', value: 0.75}, 57 {label: '100%', value: 1}, 58 {label: '200%', value: 2} 59 ]); 60 this.controls_.appendChild(scaleSelector); 61 62 var showOtherLayersCheckbox = ui.createCheckBox( 63 this, 'showOtherLayers', 64 'layerViewer.showOtherLayers', true, 65 'Show other layers'); 66 this.controls_.appendChild(showOtherLayersCheckbox); 67 68 var showInvalidationsCheckbox = ui.createCheckBox( 69 this, 'showInvalidations', 70 'layerViewer.showInvalidations', true, 71 'Show invalidations'); 72 this.controls_.appendChild(showInvalidationsCheckbox); 73 74 var showContentsCheckbox = ui.createCheckBox( 75 this, 'showContents', 76 'layerViewer.showContents', true, 77 'Show contents'); 78 this.controls_.appendChild(showContentsCheckbox); 79 }, 80 81 get layerTreeImpl() { 82 return this.layerTreeImpl_; 83 }, 84 85 set layerTreeImpl(layerTreeImpl) { 86 // FIXME(pdr): We may want to clear pictureAsCanvas_ here to save memory 87 // at the cost of performance. Note that pictureAsCanvas_ will 88 // be cleared when this is destructed, but this view might 89 // live for several layerTreeImpls. 90 this.layerTreeImpl_ = layerTreeImpl; 91 this.selection = null; 92 this.updateContents_(); 93 }, 94 95 get showOtherLayers() { 96 return this.showOtherLayers_; 97 }, 98 99 set showOtherLayers(show) { 100 this.showOtherLayers_ = show; 101 this.updateContents_(); 102 }, 103 104 get showContents() { 105 return this.showContents_; 106 }, 107 108 set showContents(show) { 109 this.showContents_ = show; 110 this.updateContents_(); 111 }, 112 113 get showInvalidations() { 114 return this.showInvalidations_; 115 }, 116 117 set showInvalidations(show) { 118 this.showInvalidations_ = show; 119 this.updateContents_(); 120 }, 121 122 get selection() { 123 return this.selection_; 124 }, 125 126 set selection(selection) { 127 base.setPropertyAndDispatchChange(this, 'selection', selection); 128 this.updateContents_(); 129 }, 130 131 scheduleUpdateContents_: function() { 132 if (this.updateContentsPending_) 133 return; 134 this.updateContentsPending_ = true; 135 base.requestAnimationFrameInThisFrameIfPossible( 136 this.updateContents_, this); 137 }, 138 139 updateContents_: function() { 140 if (!this.layerTreeImpl_) 141 return; 142 143 if (this.pictureLoadingComplete_()) 144 this.generateQuads_(); 145 }, 146 147 pictureLoadingComplete_: function() { 148 // Figure out if we can draw the quads yet. While we're at it, figure out 149 // if we have any warnings we need to show. 150 var layers = this.layers; 151 var messages = []; 152 if (this.showContents) { 153 var hasPendingRasterizeImage = false; 154 var firstPictureError = undefined; 155 var hasMissingLayerRect = false; 156 var hasUnresolvedPictureRef = false; 157 for (var i = 0; i < layers.length; i++) { 158 var layer = layers[i]; 159 for (var ir = layer.pictures.length - 1; ir >= 0; ir--) { 160 var picture = layer.pictures[ir]; 161 162 if (picture.idRef) { 163 hasUnresolvedPictureRef = true; 164 continue; 165 } 166 if (!picture.layerRect) { 167 hasMissingLayerRect = true; 168 continue; 169 } 170 171 var pictureAsCanvas = this.pictureAsCanvas_[picture.guid]; 172 if (!pictureAsCanvas) { 173 hasPendingRasterizeImage = true; 174 this.pictureAsCanvas_[picture.guid] = 175 cc.PictureAsCanvas.Pending(this); 176 picture.rasterize( 177 {stopIndex: undefined}, 178 function(pictureAsCanvas) { 179 var picture_ = pictureAsCanvas.picture; 180 this.pictureAsCanvas_[picture_.guid] = pictureAsCanvas; 181 this.scheduleUpdateContents_(); 182 }.bind(this)); 183 continue; 184 } 185 if (pictureAsCanvas.isPending()) { 186 hasPendingRasterizeImage = true; 187 continue; 188 } 189 if (pictureAsCanvas.error) { 190 if (!firstPictureError) 191 firstPictureError = pictureAsCanvas.error; 192 break; 193 } 194 } 195 } 196 if (hasPendingRasterizeImage) 197 return false; 198 199 if (hasUnresolvedPictureRef) { 200 messages.push({ 201 header: 'Missing picture', 202 details: 'Your trace didnt have pictures for every layer. ' + 203 'Old chrome versions had this problem'}); 204 } 205 if (hasMissingLayerRect) { 206 messages.push({ 207 header: 'Missing layer rect', 208 details: 'Your trace may be corrupt or from a very old ' + 209 'Chrome revision.'}); 210 } 211 if (firstPictureError) { 212 messages.push({ 213 header: 'Cannot rasterize', 214 details: firstPictureError}); 215 } 216 } 217 this.messages_ = messages; 218 return true; 219 }, 220 221 get selectedLayer() { 222 if (this.selection) { 223 var selectedLayerId = this.selection.associatedLayerId; 224 return this.layerTreeImpl_.findLayerWithId(selectedLayerId); 225 } 226 }, 227 228 get layers() { 229 var layers = this.layerTreeImpl.renderSurfaceLayerList; 230 if (!this.showOtherLayers) { 231 var selectedLayer = this.selectedLayer; 232 if (selectedLayer) 233 layers = [selectedLayer]; 234 } 235 return layers; 236 }, 237 238 appendImageQuads_: function(layer, layerQuad) { 239 // Generate image quads for the layer 240 for (var ir = layer.pictures.length - 1; ir >= 0; ir--) { 241 var picture = layer.pictures[ir]; 242 if (!picture.layerRect) 243 continue; 244 245 var unitRect = picture.layerRect.asUVRectInside(layer.bounds); 246 var iq = layerQuad.projectUnitRect(unitRect); 247 248 var pictureAsCanvas = this.pictureAsCanvas_[picture.guid]; 249 if (this.showContents && pictureAsCanvas.canvas) 250 iq.canvas = pictureAsCanvas.canvas; 251 else 252 iq.canvas = undefined; 253 254 iq.stackingGroupId = layerQuad.stackingGroupId; 255 this.quads_.push(iq); 256 } 257 }, 258 259 appendInvalidationQuads_: function(layer, layerQuad) { 260 // Generate the invalidation rect quads. 261 for (var ir = 0; ir < layer.invalidation.rects.length; ir++) { 262 var rect = layer.invalidation.rects[ir]; 263 var unitRect = rect.asUVRectInside(layer.bounds); 264 var iq = layerQuad.projectUnitRect(unitRect); 265 iq.backgroundColor = 'rgba(255, 0, 0, 0.1)'; 266 iq.borderColor = 'rgba(255, 0, 0, 1)'; 267 iq.stackingGroupId = layerQuad.stackingGroupId; 268 this.quads_.push(iq); 269 } 270 }, 271 272 appendSelectionQuads_: function(layer, layerQuad) { 273 var selection = this.selection; 274 var quad = this.layerTreeImpl_.whichTree == constants.ACTIVE_TREE ? 275 selection.quadIfActive : selection.quadIfPending; 276 if (!quad) 277 return []; 278 279 var colorId = tracing.getStringColorId(selection.title); 280 colorId += tracing.getColorPaletteHighlightIdBoost(); 281 282 var color = base.Color.fromString(tracing.getColorPalette()[colorId]); 283 284 var quadForDrawing = quad.clone(); 285 quadForDrawing.backgroundColor = color.withAlpha(0.5).toString(); 286 quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString(); 287 quadForDrawing.stackingGroupId = layerQuad.stackingGroupId; 288 return [quadForDrawing]; 289 }, 290 291 generateQuads_: function() { 292 this.updateContentsPending_ = false; 293 294 // Generate the quads for the view. 295 var layers = this.layers; 296 this.quads_ = []; 297 for (var i = 0; i < layers.length; i++) { 298 var layer = layers[i]; 299 300 var layerQuad = layer.layerQuad.clone(); 301 layerQuad.borderColor = 'rgba(0,0,0,0.75)'; 302 layerQuad.stackingGroupId = i; 303 if (this.showOtherLayers && this.selectedLayer == layer) 304 layerQuad.upperBorderColor = 'rgb(156,189,45)'; 305 306 this.appendImageQuads_(layer, layerQuad); 307 308 if (this.showInvalidations) { 309 this.appendInvalidationQuads_(layer, layerQuad); 310 } 311 312 // Push the layer quad last. 313 this.quads_.push(layerQuad); 314 315 if (this.selectedLayer === layer) { 316 this.appendSelectionQuads_(layer, layerQuad); 317 } 318 } 319 var lthi = this.layerTreeImpl_.layerTreeHostImpl; 320 var lthiInstance = lthi.objectInstance; 321 var worldViewportRect = base.Rect.FromXYWH(0, 0, 322 lthi.deviceViewportSize.width, lthi.deviceViewportSize.height); 323 this.quadStackViewer_.quadStack.initialize( 324 lthiInstance.allLayersBBox.asRect(), worldViewportRect); 325 326 this.quadStackViewer_.quadStack.quads = this.quads_; 327 328 this.updateInfoBar_(this.messages_); 329 }, 330 331 updateInfoBar_: function(infoBarMessages) { 332 if (infoBarMessages.length) { 333 this.infoBar_.removeAllButtons(); 334 this.infoBar_.message = 'Some problems were encountered...'; 335 this.infoBar_.addButton('More info...', function() { 336 var overlay = new ui.Overlay(); 337 overlay.textContent = ''; 338 infoBarMessages.forEach(function(message) { 339 var title = document.createElement('h3'); 340 title.textContent = message.header; 341 342 var details = document.createElement('div'); 343 details.textContent = message.details; 344 345 overlay.appendChild(title); 346 overlay.appendChild(details); 347 }); 348 overlay.visible = true; 349 overlay.obeyCloseEvents = true; 350 }); 351 this.infoBar_.visible = true; 352 } else { 353 this.infoBar_.removeAllButtons(); 354 this.infoBar_.message = ''; 355 this.infoBar_.visible = false; 356 } 357 } 358 }; 359 360 return { 361 LayerTreeQuadStackViewer: LayerTreeQuadStackViewer 362 }; 363 }); 364