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