| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2015 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/extras/chrome/cc/picture.html"> |
| <link rel="import" href="/extras/chrome/cc/render_pass.html"> |
| <link rel="import" href="/extras/chrome/cc/tile.html"> |
| <link rel="import" href="/extras/chrome/cc/debug_colors.html"> |
| <link rel="import" href="/extras/chrome/cc/util.html"> |
| <link rel="import" href="/base/color.html"> |
| <link rel="import" href="/base/properties.html"> |
| <link rel="import" href="/base/raf.html"> |
| <link rel="import" href="/base/quad.html"> |
| <link rel="import" href="/base/range.html"> |
| <link rel="import" href="/base/ui/quad_stack_view.html"> |
| <link rel="import" href="/base/ui/info_bar.html"> |
| |
| <style> |
| * /deep/ layer-tree-quad-stack-view { |
| position: relative; |
| } |
| |
| * /deep/ layer-tree-quad-stack-view > top-controls { |
| -webkit-flex: 0 0 auto; |
| background-image: -webkit-gradient(linear, |
| 0 0, 100% 0, |
| from(#E5E5E5), |
| to(#D1D1D1)); |
| border-bottom: 1px solid #8e8e8e; |
| border-top: 1px solid white; |
| display: flex; |
| flex-flow: row wrap; |
| flex-direction: row; |
| font-size: 14px; |
| padding-left: 2px; |
| overflow: hidden; |
| } |
| |
| * /deep/ layer-tree-quad-stack-view > top-controls input[type='checkbox'] { |
| vertical-align: -2px; |
| } |
| |
| * /deep/ layer-tree-quad-stack-view > .what-rasterized { |
| color: -webkit-link; |
| cursor: pointer; |
| text-decoration: underline; |
| position: absolute; |
| bottom: 10px; |
| left: 10px; |
| } |
| |
| * /deep/ layer-tree-quad-stack-view > #input-event { |
| content: url('./images/input-event.png'); |
| display: none; |
| } |
| |
| </style> |
| |
| <template id='layer-tree-quad-stack-view-template'> |
| <img id='input-event'/> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| /** |
| * @fileoverview Graphical view of LayerTreeImpl, with controls for |
| * type of layer content shown and info bar for content-loading warnings. |
| */ |
| tr.exportTo('tr.e.cc', function() { |
| |
| var THIS_DOC = document.currentScript.ownerDocument; |
| var TILE_HEATMAP_TYPE = {}; |
| TILE_HEATMAP_TYPE.NONE = 'none'; |
| TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY = 'scheduledPriority'; |
| TILE_HEATMAP_TYPE.USING_GPU_MEMORY = 'usingGpuMemory'; |
| |
| function createTileRectsSelectorBaseOptions() { |
| return [{label: 'None', value: 'none'}, |
| {label: 'Coverage Rects', value: 'coverage'}]; |
| } |
| |
| var bytesToRoundedMegabytes = tr.e.cc.bytesToRoundedMegabytes; |
| |
| |
| /** |
| * @constructor |
| */ |
| var LayerTreeQuadStackView = tr.b.ui.define('layer-tree-quad-stack-view'); |
| |
| LayerTreeQuadStackView.prototype = { |
| __proto__: HTMLDivElement.prototype, |
| |
| decorate: function() { |
| this.isRenderPassQuads_ = false; |
| this.pictureAsImageData_ = {}; // Maps picture.guid to PictureAsImageData. |
| this.messages_ = []; |
| this.controls_ = document.createElement('top-controls'); |
| this.infoBar_ = document.createElement('tr-b-ui-info-bar'); |
| this.quadStackView_ = new tr.b.ui.QuadStackView(); |
| this.quadStackView_.addEventListener( |
| 'selectionchange', this.onQuadStackViewSelectionChange_.bind(this)); |
| this.extraHighlightsByLayerId_ = undefined; |
| this.inputEventImageData_ = undefined; |
| |
| var m = tr.b.ui.MOUSE_SELECTOR_MODE; |
| var mms = this.quadStackView_.mouseModeSelector; |
| mms.settingsKey = 'tr.e.cc.layerTreeQuadStackView.mouseModeSelector'; |
| mms.setKeyCodeForMode(m.SELECTION, 'Z'.charCodeAt(0)); |
| mms.setKeyCodeForMode(m.PANSCAN, 'X'.charCodeAt(0)); |
| mms.setKeyCodeForMode(m.ZOOM, 'C'.charCodeAt(0)); |
| mms.setKeyCodeForMode(m.ROTATE, 'V'.charCodeAt(0)); |
| |
| var node = tr.b.instantiateTemplate( |
| '#layer-tree-quad-stack-view-template', THIS_DOC); |
| this.appendChild(node); |
| this.appendChild(this.controls_); |
| this.appendChild(this.infoBar_); |
| this.appendChild(this.quadStackView_); |
| |
| this.tileRectsSelector_ = tr.b.ui.createSelector( |
| this, 'howToShowTiles', |
| 'layerView.howToShowTiles', 'none', |
| createTileRectsSelectorBaseOptions()); |
| this.controls_.appendChild(this.tileRectsSelector_); |
| |
| var tileHeatmapText = tr.b.ui.createSpan({ |
| textContent: 'Tile heatmap:' |
| }); |
| this.controls_.appendChild(tileHeatmapText); |
| |
| var tileHeatmapSelector = tr.b.ui.createSelector( |
| this, 'tileHeatmapType', |
| 'layerView.tileHeatmapType', TILE_HEATMAP_TYPE.NONE, |
| [{label: 'None', |
| value: TILE_HEATMAP_TYPE.NONE}, |
| {label: 'Scheduled Priority', |
| value: TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY}, |
| {label: 'Is using GPU memory', |
| value: TILE_HEATMAP_TYPE.USING_GPU_MEMORY} |
| ]); |
| this.controls_.appendChild(tileHeatmapSelector); |
| |
| var showOtherLayersCheckbox = tr.b.ui.createCheckBox( |
| this, 'showOtherLayers', |
| 'layerView.showOtherLayers', true, |
| 'Other layers/passes'); |
| showOtherLayersCheckbox.title = |
| 'When checked, show all layers, selected or not.'; |
| this.controls_.appendChild(showOtherLayersCheckbox); |
| |
| var showInvalidationsCheckbox = tr.b.ui.createCheckBox( |
| this, 'showInvalidations', |
| 'layerView.showInvalidations', true, |
| 'Invalidations'); |
| showInvalidationsCheckbox.title = |
| 'When checked, compositing invalidations are highlighted in red'; |
| this.controls_.appendChild(showInvalidationsCheckbox); |
| |
| var showUnrecordedRegionCheckbox = tr.b.ui.createCheckBox( |
| this, 'showUnrecordedRegion', |
| 'layerView.showUnrecordedRegion', true, |
| 'Unrecorded area'); |
| showUnrecordedRegionCheckbox.title = |
| 'When checked, unrecorded areas are highlighted in yellow'; |
| this.controls_.appendChild(showUnrecordedRegionCheckbox); |
| |
| var showBottlenecksCheckbox = tr.b.ui.createCheckBox( |
| this, 'showBottlenecks', |
| 'layerView.showBottlenecks', true, |
| 'Bottlenecks'); |
| showBottlenecksCheckbox.title = |
| 'When checked, scroll bottlenecks are highlighted'; |
| this.controls_.appendChild(showBottlenecksCheckbox); |
| |
| var showLayoutRectsCheckbox = tr.b.ui.createCheckBox( |
| this, 'showLayoutRects', |
| 'layerView.showLayoutRects', false, |
| 'Layout rects'); |
| showLayoutRectsCheckbox.title = |
| 'When checked, shows rects for regions where layout happened'; |
| this.controls_.appendChild(showLayoutRectsCheckbox); |
| |
| var showContentsCheckbox = tr.b.ui.createCheckBox( |
| this, 'showContents', |
| 'layerView.showContents', true, |
| 'Contents'); |
| showContentsCheckbox.title = |
| 'When checked, show the rendered contents inside the layer outlines'; |
| this.controls_.appendChild(showContentsCheckbox); |
| |
| var showAnimationBoundsCheckbox = tr.b.ui.createCheckBox( |
| this, 'showAnimationBounds', |
| 'layerView.showAnimationBounds', false, |
| 'Animation Bounds'); |
| showAnimationBoundsCheckbox.title = 'When checked, show a border around' + |
| ' a layer showing the extent of its animation.'; |
| this.controls_.appendChild(showAnimationBoundsCheckbox); |
| |
| var showInputEventsCheckbox = tr.b.ui.createCheckBox( |
| this, 'showInputEvents', |
| 'layerView.showInputEvents', true, |
| 'Input events'); |
| showInputEventsCheckbox.title = 'When checked, input events are ' + |
| 'displayed as circles.'; |
| this.controls_.appendChild(showInputEventsCheckbox); |
| |
| this.whatRasterizedLink_ = document.createElement('a'); |
| this.whatRasterizedLink_.classList.add('what-rasterized'); |
| this.whatRasterizedLink_.textContent = 'What rasterized?'; |
| this.whatRasterizedLink_.addEventListener( |
| 'click', this.onWhatRasterizedLinkClicked_.bind(this)); |
| this.appendChild(this.whatRasterizedLink_); |
| }, |
| |
| get layerTreeImpl() { |
| return this.layerTreeImpl_; |
| }, |
| |
| set isRenderPassQuads(newValue) { |
| this.isRenderPassQuads_ = newValue; |
| }, |
| |
| set layerTreeImpl(layerTreeImpl) { |
| if (this.layerTreeImpl_ === layerTreeImpl) |
| return; |
| |
| // FIXME(pdr): We may want to clear pictureAsImageData_ here to save |
| // memory at the cost of performance. Note that |
| // pictureAsImageData_ will be cleared when this is |
| // destructed, but this view might live for several |
| // layerTreeImpls. |
| this.layerTreeImpl_ = layerTreeImpl; |
| this.selection = undefined; |
| }, |
| |
| get extraHighlightsByLayerId() { |
| return this.extraHighlightsByLayerId_; |
| }, |
| |
| set extraHighlightsByLayerId(extraHighlightsByLayerId) { |
| this.extraHighlightsByLayerId_ = extraHighlightsByLayerId; |
| this.scheduleUpdateContents_(); |
| }, |
| |
| get showOtherLayers() { |
| return this.showOtherLayers_; |
| }, |
| |
| set showOtherLayers(show) { |
| this.showOtherLayers_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showAnimationBounds() { |
| return this.showAnimationBounds_; |
| }, |
| |
| set showAnimationBounds(show) { |
| this.showAnimationBounds_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showInputEvents() { |
| return this.showInputEvents_; |
| }, |
| |
| set showInputEvents(show) { |
| this.showInputEvents_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showContents() { |
| return this.showContents_; |
| }, |
| |
| set showContents(show) { |
| this.showContents_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showInvalidations() { |
| return this.showInvalidations_; |
| }, |
| |
| set showInvalidations(show) { |
| this.showInvalidations_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showUnrecordedRegion() { |
| return this.showUnrecordedRegion_; |
| }, |
| |
| set showUnrecordedRegion(show) { |
| this.showUnrecordedRegion_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showBottlenecks() { |
| return this.showBottlenecks_; |
| }, |
| |
| set showBottlenecks(show) { |
| this.showBottlenecks_ = show; |
| this.updateContents_(); |
| }, |
| |
| get showLayoutRects() { |
| return this.showLayoutRects_; |
| }, |
| |
| set showLayoutRects(show) { |
| this.showLayoutRects_ = show; |
| this.updateContents_(); |
| }, |
| |
| get howToShowTiles() { |
| return this.howToShowTiles_; |
| }, |
| |
| set howToShowTiles(val) { |
| // Make sure val is something we expect. |
| console.assert( |
| (val === 'none') || |
| (val === 'coverage') || |
| !isNaN(parseFloat(val))); |
| |
| this.howToShowTiles_ = val; |
| this.updateContents_(); |
| }, |
| |
| get tileHeatmapType() { |
| return this.tileHeatmapType_; |
| }, |
| |
| set tileHeatmapType(val) { |
| this.tileHeatmapType_ = val; |
| this.updateContents_(); |
| }, |
| |
| get selection() { |
| return this.selection_; |
| }, |
| |
| set selection(selection) { |
| if (this.selection === selection) |
| return; |
| this.selection_ = selection; |
| tr.b.dispatchSimpleEvent(this, 'selection-change'); |
| this.updateContents_(); |
| }, |
| |
| regenerateContent: function() { |
| this.updateTilesSelector_(); |
| this.updateContents_(); |
| }, |
| |
| loadDataForImageElement_: function(image, callback) { |
| var imageContent = window.getComputedStyle(image).content; |
| image.src = imageContent.replace(/url\((.*)\)/, '$1'); |
| image.onload = function() { |
| var canvas = document.createElement('canvas'); |
| var ctx = canvas.getContext('2d'); |
| canvas.width = image.width; |
| canvas.height = image.height; |
| ctx.drawImage(image, 0, 0); |
| var imageData = ctx.getImageData( |
| 0, 0, canvas.width, canvas.height); |
| callback(imageData); |
| } |
| }, |
| |
| onQuadStackViewSelectionChange_: function(e) { |
| var selectableQuads = e.quads.filter(function(q) { |
| return q.selectionToSetIfClicked !== undefined; |
| }); |
| if (selectableQuads.length == 0) { |
| this.selection = undefined; |
| return; |
| } |
| |
| // Sort the quads low to high on stackingGroupId. |
| selectableQuads.sort(function(x, y) { |
| var z = x.stackingGroupId - y.stackingGroupId; |
| if (z != 0) |
| return z; |
| return x.selectionToSetIfClicked.specicifity - |
| y.selectionToSetIfClicked.specicifity; |
| }); |
| |
| // TODO(nduca): Support selecting N things at once. |
| var quadToSelect = selectableQuads[selectableQuads.length - 1]; |
| this.selection = quadToSelect.selectionToSetIfClicked; |
| }, |
| |
| scheduleUpdateContents_: function() { |
| if (this.updateContentsPending_) |
| return; |
| this.updateContentsPending_ = true; |
| tr.b.requestAnimationFrameInThisFrameIfPossible( |
| this.updateContents_, this); |
| }, |
| |
| updateContents_: function() { |
| if (!this.layerTreeImpl_) { |
| this.quadStackView_.headerText = 'No tree'; |
| this.quadStackView_.quads = []; |
| return; |
| } |
| |
| |
| var status = this.computePictureLoadingStatus_(); |
| if (!status.picturesComplete) |
| return; |
| |
| var lthi = this.layerTreeImpl_.layerTreeHostImpl; |
| var lthiInstance = lthi.objectInstance; |
| var worldViewportRect = tr.b.Rect.fromXYWH( |
| 0, 0, |
| lthi.deviceViewportSize.width, lthi.deviceViewportSize.height); |
| this.quadStackView_.deviceRect = worldViewportRect; |
| if (this.isRenderPassQuads_) |
| this.quadStackView_.quads = this.generateRenderPassQuads(); |
| else |
| this.quadStackView_.quads = this.generateLayerQuads(); |
| |
| this.updateWhatRasterizedLinkState_(); |
| |
| var message = ''; |
| if (lthi.tilesHaveGpuMemoryUsageInfo) { |
| var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes; |
| var otherTreeUsageInBytes = lthi.gpuMemoryUsageInBytes - |
| thisTreeUsageInBytes; |
| message += bytesToRoundedMegabytes(thisTreeUsageInBytes) + |
| 'MB on this tree'; |
| if (otherTreeUsageInBytes) { |
| message += ', ' + |
| bytesToRoundedMegabytes(otherTreeUsageInBytes) + |
| 'MB on the other tree'; |
| } |
| } else { |
| if (this.layerTreeImpl_) { |
| var thisTreeUsageInBytes = this.layerTreeImpl_.gpuMemoryUsageInBytes; |
| message += bytesToRoundedMegabytes(thisTreeUsageInBytes) + |
| 'MB on this tree'; |
| |
| if (this.layerTreeImpl_.otherTree) { |
| // Older Chromes don't report enough data to know how much memory is |
| // being used across both trees. We know the memory consumed by each |
| // tree, but there is resource sharing *between the trees* so we |
| // can't simply sum up the per-tree costs. We need either the total |
| // plus one tree, to guess the unique on the other tree, etc. Newer |
| // chromes report memory per tile, which allows LTHI to compute the |
| // total tile memory usage, letting us figure things out properly. |
| message += ', ???MB on other tree. '; |
| } |
| } |
| } |
| |
| if (lthi.args.tileManagerBasicState) { |
| var tmgs = lthi.args.tileManagerBasicState.globalState; |
| message += ' (softMax=' + |
| bytesToRoundedMegabytes(tmgs.softMemoryLimitInBytes) + |
| 'MB, hardMax=' + |
| bytesToRoundedMegabytes(tmgs.hardMemoryLimitInBytes) + 'MB, ' + |
| tmgs.memoryLimitPolicy + ')'; |
| |
| } else { |
| // Old Chromes do not have a globalState on the LTHI dump. |
| // But they do issue a DidManage event wiht the globalstate. Find that |
| // event so that we show some global state. |
| var thread = lthi.snapshottedOnThread; |
| var didManageTilesSlices = thread.sliceGroup.slices.filter(function(s) { |
| if (s.category !== 'tr.e.cc') |
| return false; |
| if (s.title !== 'DidManage') |
| return false; |
| if (s.end > lthi.ts) |
| return false; |
| return true; |
| }); |
| didManageTilesSlices.sort(function(x, y) { |
| return x.end - y.end; |
| }); |
| if (didManageTilesSlices.length > 0) { |
| var newest = didManageTilesSlices[didManageTilesSlices.length - 1]; |
| var tmgs = newest.args.state.global_state; |
| message += ' (softMax=' + |
| bytesToRoundedMegabytes(tmgs.soft_memory_limit_in_bytes) + |
| 'MB, hardMax=' + |
| bytesToRoundedMegabytes(tmgs.hard_memory_limit_in_bytes) + 'MB, ' + |
| tmgs.memory_limit_policy + ')'; |
| } |
| } |
| |
| if (this.layerTreeImpl_.otherTree) |
| message += ' (Another tree exists)'; |
| |
| |
| if (message.length) |
| this.quadStackView_.headerText = message; |
| else |
| this.quadStackView_.headerText = undefined; |
| |
| this.updateInfoBar_(status.messages); |
| }, |
| |
| updateTilesSelector_: function() { |
| var data = createTileRectsSelectorBaseOptions(); |
| |
| if (this.layerTreeImpl_) { |
| // First get all of the scales information from LTHI. |
| var lthi = this.layerTreeImpl_.layerTreeHostImpl; |
| var scaleNames = lthi.getContentsScaleNames(); |
| for (var scale in scaleNames) { |
| data.push({ |
| label: 'Scale ' + scale + ' (' + scaleNames[scale] + ')', |
| value: scale |
| }); |
| } |
| } |
| |
| // Then create a new selector and replace the old one. |
| var new_selector = tr.b.ui.createSelector( |
| this, 'howToShowTiles', |
| 'layerView.howToShowTiles', 'none', |
| data); |
| this.controls_.replaceChild(new_selector, this.tileRectsSelector_); |
| this.tileRectsSelector_ = new_selector; |
| }, |
| |
| computePictureLoadingStatus_: function() { |
| // Figure out if we can draw the quads yet. While we're at it, figure out |
| // if we have any warnings we need to show. |
| var layers = this.layers; |
| var status = { |
| messages: [], |
| picturesComplete: true |
| }; |
| if (this.showContents) { |
| var hasPendingRasterizeImage = false; |
| var firstPictureError = undefined; |
| var hasMissingLayerRect = false; |
| var hasUnresolvedPictureRef = false; |
| for (var i = 0; i < layers.length; i++) { |
| var layer = layers[i]; |
| for (var ir = 0; ir < layer.pictures.length; ++ir) { |
| var picture = layer.pictures[ir]; |
| |
| if (picture.idRef) { |
| hasUnresolvedPictureRef = true; |
| continue; |
| } |
| if (!picture.layerRect) { |
| hasMissingLayerRect = true; |
| continue; |
| } |
| |
| var pictureAsImageData = this.pictureAsImageData_[picture.guid]; |
| if (!pictureAsImageData) { |
| hasPendingRasterizeImage = true; |
| this.pictureAsImageData_[picture.guid] = |
| tr.e.cc.PictureAsImageData.Pending(this); |
| picture.rasterize( |
| {stopIndex: undefined}, |
| function(pictureImageData) { |
| var picture_ = pictureImageData.picture; |
| this.pictureAsImageData_[picture_.guid] = pictureImageData; |
| this.scheduleUpdateContents_(); |
| }.bind(this)); |
| continue; |
| } |
| if (pictureAsImageData.isPending()) { |
| hasPendingRasterizeImage = true; |
| continue; |
| } |
| if (pictureAsImageData.error) { |
| if (!firstPictureError) |
| firstPictureError = pictureAsImageData.error; |
| break; |
| } |
| } |
| } |
| if (hasPendingRasterizeImage) { |
| status.picturesComplete = false; |
| } else { |
| if (hasUnresolvedPictureRef) { |
| status.messages.push({ |
| header: 'Missing picture', |
| details: 'Your trace didnt have pictures for every layer. ' + |
| 'Old chrome versions had this problem'}); |
| } |
| if (hasMissingLayerRect) { |
| status.messages.push({ |
| header: 'Missing layer rect', |
| details: 'Your trace may be corrupt or from a very old ' + |
| 'Chrome revision.'}); |
| } |
| if (firstPictureError) { |
| status.messages.push({ |
| header: 'Cannot rasterize', |
| details: firstPictureError}); |
| } |
| } |
| } |
| if (this.showInputEvents && this.layerTreeImpl.tracedInputLatencies && |
| this.inputEventImageData_ === undefined) { |
| var image = this.querySelector('#input-event'); |
| if (!image.src) { |
| this.loadDataForImageElement_(image, function(imageData) { |
| this.inputEventImageData_ = imageData; |
| this.updateContentsPending_ = false; |
| this.scheduleUpdateContents_(); |
| }.bind(this)); |
| } |
| status.picturesComplete = false; |
| } |
| return status; |
| }, |
| |
| get selectedRenderPass() { |
| if (this.selection) |
| return this.selection.renderPass_; |
| }, |
| |
| get selectedLayer() { |
| if (this.selection) { |
| var selectedLayerId = this.selection.associatedLayerId; |
| return this.layerTreeImpl_.findLayerWithId(selectedLayerId); |
| } |
| }, |
| |
| get renderPasses() { |
| var renderPasses = |
| this.layerTreeImpl.layerTreeHostImpl.args.frame.renderPasses; |
| if (!this.showOtherLayers) { |
| var selectedRenderPass = this.selectedRenderPass; |
| if (selectedRenderPass) |
| renderPasses = [selectedRenderPass]; |
| } |
| return renderPasses; |
| }, |
| |
| get layers() { |
| var layers = this.layerTreeImpl.renderSurfaceLayerList; |
| if (!this.showOtherLayers) { |
| var selectedLayer = this.selectedLayer; |
| if (selectedLayer) |
| layers = [selectedLayer]; |
| } |
| return layers; |
| }, |
| |
| appendImageQuads_: function(quads, layer, layerQuad) { |
| // Generate image quads for the layer |
| for (var ir = 0; ir < layer.pictures.length; ++ir) { |
| var picture = layer.pictures[ir]; |
| if (!picture.layerRect) |
| continue; |
| |
| var unitRect = picture.layerRect.asUVRectInside(layer.bounds); |
| var iq = layerQuad.projectUnitRect(unitRect); |
| |
| var pictureData = this.pictureAsImageData_[picture.guid]; |
| if (this.showContents && pictureData && pictureData.imageData) { |
| iq.imageData = pictureData.imageData; |
| iq.borderColor = 'rgba(0,0,0,0)'; |
| } else { |
| iq.imageData = undefined; |
| } |
| |
| iq.stackingGroupId = layerQuad.stackingGroupId; |
| quads.push(iq); |
| } |
| }, |
| |
| appendAnimationQuads_: function(quads, layer, layerQuad) { |
| if (!layer.animationBoundsRect) |
| return; |
| |
| var rect = layer.animationBoundsRect; |
| var abq = tr.b.Quad.fromRect(rect); |
| |
| abq.backgroundColor = 'rgba(164,191,48,0.5)'; |
| abq.borderColor = 'rgba(205,255,0,0.75)'; |
| abq.borderWidth = 3.0; |
| abq.stackingGroupId = layerQuad.stackingGroupId; |
| abq.selectionToSetIfClicked = new tr.e.cc.AnimationRectSelection( |
| layer, rect); |
| quads.push(abq); |
| }, |
| |
| appendInvalidationQuads_: function(quads, layer, layerQuad) { |
| if (layer.layerTreeImpl.hasSourceFrameBeenDrawnBefore) |
| return; |
| |
| // Generate the invalidation rect quads. |
| for (var ir = 0; ir < layer.annotatedInvalidation.rects.length; ir++) { |
| var rect = layer.annotatedInvalidation.rects[ir]; |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var iq = layerQuad.projectUnitRect(unitRect); |
| iq.backgroundColor = 'rgba(0, 255, 0, 0.1)'; |
| if (rect.reason === 'renderer insertion') |
| iq.backgroundColor = 'rgba(0, 255, 128, 0.1)'; |
| iq.borderColor = 'rgba(0, 255, 0, 1)'; |
| iq.stackingGroupId = layerQuad.stackingGroupId; |
| iq.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, 'Invalidation rect (' + rect.reason + ')', rect, rect); |
| quads.push(iq); |
| } |
| |
| // Show unannotated invalidation rect quads if no annotated rects are |
| // available. |
| if (layer.annotatedInvalidation.rects.length === 0) { |
| for (var ir = 0; ir < layer.invalidation.rects.length; ir++) { |
| var rect = layer.invalidation.rects[ir]; |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var iq = layerQuad.projectUnitRect(unitRect); |
| iq.backgroundColor = 'rgba(0, 255, 0, 0.1)'; |
| iq.borderColor = 'rgba(0, 255, 0, 1)'; |
| iq.stackingGroupId = layerQuad.stackingGroupId; |
| iq.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, 'Invalidation rect', rect, rect); |
| quads.push(iq); |
| } |
| } |
| }, |
| |
| appendUnrecordedRegionQuads_: function(quads, layer, layerQuad) { |
| // Generate the unrecorded region quads. |
| for (var ir = 0; ir < layer.unrecordedRegion.rects.length; ir++) { |
| var rect = layer.unrecordedRegion.rects[ir]; |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var iq = layerQuad.projectUnitRect(unitRect); |
| iq.backgroundColor = 'rgba(240, 230, 140, 0.3)'; |
| iq.borderColor = 'rgba(240, 230, 140, 1)'; |
| iq.stackingGroupId = layerQuad.stackingGroupId; |
| iq.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, 'Unrecorded area', rect, rect); |
| quads.push(iq); |
| } |
| }, |
| |
| appendBottleneckQuads_: function(quads, layer, layerQuad, stackingGroupId) { |
| function processRegion(region, label, borderColor) { |
| var backgroundColor = borderColor.clone(); |
| backgroundColor.a = 0.4 * (borderColor.a || 1.0); |
| |
| if (!region || !region.rects) |
| return; |
| |
| for (var ir = 0; ir < region.rects.length; ir++) { |
| var rect = region.rects[ir]; |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var iq = layerQuad.projectUnitRect(unitRect); |
| iq.backgroundColor = backgroundColor.toString(); |
| iq.borderColor = borderColor.toString(); |
| iq.borderWidth = 4.0; |
| iq.stackingGroupId = stackingGroupId; |
| iq.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, label, rect, rect); |
| quads.push(iq); |
| } |
| } |
| |
| processRegion(layer.touchEventHandlerRegion, 'Touch listener', |
| tr.b.Color.fromString('rgb(228, 226, 27)')); |
| processRegion(layer.wheelEventHandlerRegion, 'Wheel listener', |
| tr.b.Color.fromString('rgb(176, 205, 29)')); |
| processRegion(layer.nonFastScrollableRegion, 'Repaints on scroll', |
| tr.b.Color.fromString('rgb(213, 134, 32)')); |
| }, |
| |
| appendTileCoverageRectQuads_: function( |
| quads, layer, layerQuad, heatmapType) { |
| if (!layer.tileCoverageRects) |
| return; |
| |
| var tiles = []; |
| for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) { |
| var tile = layer.tileCoverageRects[ct].tile; |
| if (tile !== undefined) |
| tiles.push(tile); |
| } |
| |
| var lthi = this.layerTreeImpl_.layerTreeHostImpl; |
| var minMax = |
| this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType); |
| var heatmapResult = |
| this.computeHeatmapColors_(tiles, minMax, heatmapType); |
| var heatIndex = 0; |
| |
| for (var ct = 0; ct < layer.tileCoverageRects.length; ++ct) { |
| var rect = layer.tileCoverageRects[ct].geometryRect; |
| rect = rect.scale(1.0 / layer.geometryContentsScale); |
| |
| var tile = layer.tileCoverageRects[ct].tile; |
| |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var quad = layerQuad.projectUnitRect(unitRect); |
| |
| quad.backgroundColor = 'rgba(0, 0, 0, 0)'; |
| quad.stackingGroupId = layerQuad.stackingGroupId; |
| var type = tr.e.cc.tileTypes.missing; |
| if (tile) { |
| type = tile.getTypeForLayer(layer); |
| quad.backgroundColor = heatmapResult[heatIndex].color; |
| ++heatIndex; |
| } |
| |
| quad.borderColor = tr.e.cc.tileBorder[type].color; |
| quad.borderWidth = tr.e.cc.tileBorder[type].width; |
| var label; |
| if (tile) |
| label = 'coverageRect'; |
| else |
| label = 'checkerboard coverageRect'; |
| quad.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, label, rect, layer.tileCoverageRects[ct]); |
| |
| quads.push(quad); |
| } |
| }, |
| |
| appendLayoutRectQuads_: function(quads, layer, layerQuad) { |
| if (!layer.layoutRects) { |
| return; |
| } |
| |
| for (var ct = 0; ct < layer.layoutRects.length; ++ct) { |
| var rect = layer.layoutRects[ct].geometryRect; |
| rect = rect.scale(1.0 / layer.geometryContentsScale); |
| |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var quad = layerQuad.projectUnitRect(unitRect); |
| |
| quad.backgroundColor = 'rgba(0, 0, 0, 0)'; |
| quad.stackingGroupId = layerQuad.stackingGroupId; |
| |
| quad.borderColor = 'rgba(0, 0, 200, 0.7)'; |
| quad.borderWidth = 2; |
| var label; |
| label = 'Layout rect'; |
| quad.selectionToSetIfClicked = new tr.e.cc.LayerRectSelection( |
| layer, label, rect); |
| |
| quads.push(quad); |
| } |
| }, |
| |
| getValueForHeatmap_: function(tile, heatmapType) { |
| if (heatmapType == TILE_HEATMAP_TYPE.SCHEDULED_PRIORITY) { |
| return tile.scheduledPriority == 0 ? |
| undefined : |
| tile.scheduledPriority; |
| } else if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) { |
| if (tile.isSolidColor) |
| return 0.5; |
| return tile.isUsingGpuMemory ? 0 : 1; |
| } |
| }, |
| |
| getMinMaxForHeatmap_: function(tiles, heatmapType) { |
| var range = new tr.b.Range(); |
| if (heatmapType == TILE_HEATMAP_TYPE.USING_GPU_MEMORY) { |
| range.addValue(0); |
| range.addValue(1); |
| return range; |
| } |
| |
| for (var i = 0; i < tiles.length; ++i) { |
| var value = this.getValueForHeatmap_(tiles[i], heatmapType); |
| if (value === undefined) |
| continue; |
| range.addValue(value); |
| } |
| if (range.range === 0) |
| range.addValue(1); |
| return range; |
| }, |
| |
| computeHeatmapColors_: function(tiles, minMax, heatmapType) { |
| var min = minMax.min; |
| var max = minMax.max; |
| |
| var color = function(value) { |
| var hue = 120 * (1 - (value - min) / (max - min)); |
| if (hue < 0) |
| hue = 0; |
| return 'hsla(' + hue + ', 100%, 50%, 0.5)'; |
| }; |
| |
| var values = []; |
| for (var i = 0; i < tiles.length; ++i) { |
| var tile = tiles[i]; |
| var value = this.getValueForHeatmap_(tile, heatmapType); |
| var res = { |
| value: value, |
| color: value !== undefined ? color(value) : undefined |
| }; |
| values.push(res); |
| } |
| |
| return values; |
| }, |
| |
| appendTilesWithScaleQuads_: function( |
| quads, layer, layerQuad, scale, heatmapType) { |
| var lthi = this.layerTreeImpl_.layerTreeHostImpl; |
| |
| var tiles = []; |
| for (var i = 0; i < lthi.activeTiles.length; ++i) { |
| var tile = lthi.activeTiles[i]; |
| |
| if (Math.abs(tile.contentsScale - scale) > 1e-6) |
| continue; |
| |
| // TODO(vmpstr): Make the stiching of tiles and layers a part of |
| // tile construction (issue 346) |
| if (layer.layerId != tile.layerId) |
| continue; |
| |
| tiles.push(tile); |
| } |
| |
| var minMax = |
| this.getMinMaxForHeatmap_(lthi.activeTiles, heatmapType); |
| var heatmapResult = |
| this.computeHeatmapColors_(tiles, minMax, heatmapType); |
| |
| for (var i = 0; i < tiles.length; ++i) { |
| var tile = tiles[i]; |
| var rect = tile.layerRect; |
| if (!tile.layerRect) |
| continue; |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var quad = layerQuad.projectUnitRect(unitRect); |
| |
| quad.backgroundColor = 'rgba(0, 0, 0, 0)'; |
| quad.stackingGroupId = layerQuad.stackingGroupId; |
| |
| var type = tile.getTypeForLayer(layer); |
| quad.borderColor = tr.e.cc.tileBorder[type].color; |
| quad.borderWidth = tr.e.cc.tileBorder[type].width; |
| |
| quad.backgroundColor = heatmapResult[i].color; |
| var data = { |
| tileType: type |
| }; |
| if (heatmapType !== TILE_HEATMAP_TYPE.NONE) |
| data[heatmapType] = heatmapResult[i].value; |
| quad.selectionToSetIfClicked = new tr.e.cc.TileSelection(tile, data); |
| quads.push(quad); |
| } |
| }, |
| |
| appendHighlightQuadsForLayer_: function( |
| quads, layer, layerQuad, highlights) { |
| highlights.forEach(function(highlight) { |
| var rect = highlight.rect; |
| |
| var unitRect = rect.asUVRectInside(layer.bounds); |
| var quad = layerQuad.projectUnitRect(unitRect); |
| |
| var colorId = tr.b.ui.getColorIdForGeneralPurposeString( |
| highlight.colorKey); |
| colorId += tr.b.ui.getColorPaletteHighlightIdBoost(); |
| |
| var color = tr.b.Color.fromString(tr.b.ui.getColorPalette()[colorId]); |
| |
| var quadForDrawing = quad.clone(); |
| quadForDrawing.backgroundColor = color.withAlpha(0.5).toString(); |
| quadForDrawing.borderColor = color.withAlpha(1.0).darken().toString(); |
| quadForDrawing.stackingGroupId = layerQuad.stackingGroupId; |
| quads.push(quadForDrawing); |
| |
| }, this); |
| }, |
| |
| generateRenderPassQuads: function() { |
| if (!this.layerTreeImpl.layerTreeHostImpl.args.frame) |
| return []; |
| var renderPasses = this.renderPasses; |
| if (!renderPasses) |
| return []; |
| |
| var quads = []; |
| for (var i = 0; i < renderPasses.length; ++i) { |
| var quadList = renderPasses[i].quadList; |
| for (var j = 0; j < quadList.length; ++j) { |
| var drawQuad = quadList[j]; |
| var quad = drawQuad.rectAsTargetSpaceQuad.clone(); |
| quad.borderColor = 'rgb(170, 204, 238)'; |
| quad.borderWidth = 2; |
| quad.stackingGroupId = i; |
| quads.push(quad); |
| } |
| } |
| return quads; |
| }, |
| |
| generateLayerQuads: function() { |
| this.updateContentsPending_ = false; |
| |
| // Generate the quads for the view. |
| var layers = this.layers; |
| var quads = []; |
| var nextStackingGroupId = 0; |
| var alreadyVisitedLayerIds = {}; |
| |
| |
| var selectionHighlightsByLayerId; |
| if (this.selection) |
| selectionHighlightsByLayerId = this.selection.highlightsByLayerId; |
| else |
| selectionHighlightsByLayerId = {}; |
| |
| var extraHighlightsByLayerId = this.extraHighlightsByLayerId || {}; |
| |
| for (var i = 1; i <= layers.length; i++) { |
| // Generate quads back-to-front. |
| var layer = layers[layers.length - i]; |
| alreadyVisitedLayerIds[layer.layerId] = true; |
| if (layer.objectInstance.name == 'cc::NinePatchLayerImpl') |
| continue; |
| |
| var layerQuad = layer.layerQuad.clone(); |
| if (layer.usingGpuRasterization) { |
| var pixelRatio = window.devicePixelRatio || 1; |
| layerQuad.borderWidth = 2.0 * pixelRatio; |
| layerQuad.borderColor = 'rgba(154,205,50,0.75)'; |
| } else { |
| layerQuad.borderColor = 'rgba(0,0,0,0.75)'; |
| } |
| layerQuad.stackingGroupId = nextStackingGroupId++; |
| layerQuad.selectionToSetIfClicked = new tr.e.cc.LayerSelection(layer); |
| layerQuad.layer = layer; |
| if (this.showOtherLayers && this.selectedLayer == layer) |
| layerQuad.upperBorderColor = 'rgb(156,189,45)'; |
| |
| if (this.showAnimationBounds) |
| this.appendAnimationQuads_(quads, layer, layerQuad); |
| |
| this.appendImageQuads_(quads, layer, layerQuad); |
| quads.push(layerQuad); |
| |
| |
| if (this.showInvalidations) |
| this.appendInvalidationQuads_(quads, layer, layerQuad); |
| if (this.showUnrecordedRegion) |
| this.appendUnrecordedRegionQuads_(quads, layer, layerQuad); |
| if (this.showBottlenecks) |
| this.appendBottleneckQuads_(quads, layer, layerQuad, |
| layerQuad.stackingGroupId); |
| if (this.showLayoutRects) |
| this.appendLayoutRectQuads_(quads, layer, layerQuad); |
| |
| if (this.howToShowTiles === 'coverage') { |
| this.appendTileCoverageRectQuads_( |
| quads, layer, layerQuad, this.tileHeatmapType); |
| } else if (this.howToShowTiles !== 'none') { |
| this.appendTilesWithScaleQuads_( |
| quads, layer, layerQuad, |
| this.howToShowTiles, this.tileHeatmapType); |
| } |
| |
| var highlights; |
| highlights = extraHighlightsByLayerId[layer.layerId]; |
| if (highlights) { |
| this.appendHighlightQuadsForLayer_( |
| quads, layer, layerQuad, highlights); |
| } |
| |
| highlights = selectionHighlightsByLayerId[layer.layerId]; |
| if (highlights) { |
| this.appendHighlightQuadsForLayer_( |
| quads, layer, layerQuad, highlights); |
| } |
| } |
| |
| this.layerTreeImpl.iterLayers(function(layer, depth, isMask, isReplica) { |
| if (!this.showOtherLayers && this.selectedLayer != layer) |
| return; |
| if (alreadyVisitedLayerIds[layer.layerId]) |
| return; |
| var layerQuad = layer.layerQuad; |
| var stackingGroupId = nextStackingGroupId++; |
| if (this.showBottlenecks) |
| this.appendBottleneckQuads_(quads, layer, layerQuad, stackingGroupId); |
| }, this); |
| |
| var tracedInputLatencies = this.layerTreeImpl.tracedInputLatencies; |
| if (this.showInputEvents && tracedInputLatencies) { |
| for (var i = 0; i < tracedInputLatencies.length; i++) { |
| var coordinatesArray = tracedInputLatencies[i].args.data.coordinates; |
| for (var j = 0; j < coordinatesArray.length; j++) { |
| var inputQuad = tr.b.Quad.fromXYWH( |
| coordinatesArray[j].x - 25, |
| coordinatesArray[j].y - 25, |
| 50, |
| 50); |
| inputQuad.borderColor = 'rgba(0, 0, 0, 0)'; |
| inputQuad.imageData = this.inputEventImageData_; |
| quads.push(inputQuad); |
| } |
| } |
| } |
| |
| return quads; |
| }, |
| |
| updateInfoBar_: function(infoBarMessages) { |
| if (infoBarMessages.length) { |
| this.infoBar_.removeAllButtons(); |
| this.infoBar_.message = 'Some problems were encountered...'; |
| this.infoBar_.addButton('More info...', function(e) { |
| var overlay = new tr.b.ui.Overlay(); |
| overlay.textContent = ''; |
| infoBarMessages.forEach(function(message) { |
| var title = document.createElement('h3'); |
| title.textContent = message.header; |
| |
| var details = document.createElement('div'); |
| details.textContent = message.details; |
| |
| overlay.appendChild(title); |
| overlay.appendChild(details); |
| }); |
| overlay.visible = true; |
| |
| e.stopPropagation(); |
| return false; |
| }); |
| this.infoBar_.visible = true; |
| } else { |
| this.infoBar_.removeAllButtons(); |
| this.infoBar_.message = ''; |
| this.infoBar_.visible = false; |
| } |
| }, |
| |
| getWhatRasterized_: function() { |
| var lthi = this.layerTreeImpl_.layerTreeHostImpl; |
| var renderProcess = lthi.objectInstance.parent; |
| var tasks = []; |
| renderProcess.iterateAllEvents(function(event) { |
| if (!(event instanceof tr.model.Slice)) |
| return; |
| |
| var tile = tr.e.cc.getTileFromRasterTaskSlice(event); |
| if (tile === undefined) |
| return false; |
| |
| if (tile.containingSnapshot == lthi) |
| tasks.push(event); |
| }, this); |
| return tasks; |
| }, |
| |
| updateWhatRasterizedLinkState_: function() { |
| var tasks = this.getWhatRasterized_(); |
| if (tasks.length) { |
| this.whatRasterizedLink_.textContent = tasks.length + ' raster tasks'; |
| this.whatRasterizedLink_.style.display = ''; |
| } else { |
| this.whatRasterizedLink_.textContent = ''; |
| this.whatRasterizedLink_.style.display = 'none'; |
| } |
| }, |
| |
| onWhatRasterizedLinkClicked_: function() { |
| var tasks = this.getWhatRasterized_(); |
| var event = new tr.c.RequestSelectionChangeEvent(); |
| event.selection = new tr.c.Selection(tasks); |
| this.dispatchEvent(event); |
| } |
| }; |
| |
| return { |
| LayerTreeQuadStackView: LayerTreeQuadStackView |
| }; |
| }); |
| </script> |