UNPKG

@itwin/frontend-devtools

Version:

Debug menu and supporting UI widgets

202 lines • 8.67 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Widgets */ import { IModelApp, RenderMemory } from "@itwin/core-frontend"; import { createCheckBox } from "../ui/CheckBox"; import { formatMemory } from "./MemoryTracker"; var TileMemorySelector; (function (TileMemorySelector) { TileMemorySelector[TileMemorySelector["Selected"] = 0] = "Selected"; TileMemorySelector[TileMemorySelector["Ancestors"] = 1] = "Ancestors"; TileMemorySelector[TileMemorySelector["Descendants"] = 2] = "Descendants"; TileMemorySelector[TileMemorySelector["Orphaned"] = 3] = "Orphaned"; TileMemorySelector[TileMemorySelector["Total"] = 4] = "Total"; TileMemorySelector[TileMemorySelector["Count"] = 5] = "Count"; })(TileMemorySelector || (TileMemorySelector = {})); class TileMemoryTracer { _stats = new RenderMemory.Statistics(); _processedTiles = new Set(); counters = []; numSelected = 0; constructor() { for (let i = 0; i < TileMemorySelector.Count; i++) this.counters.push({ numTiles: 0, bytesUsed: 0 }); } update() { this.reset(); const imodels = new Set(); const selectedTiles = new Set(); for (const vp of IModelApp.viewManager) { imodels.add(vp.iModel); const tiles = IModelApp.tileAdmin.getTilesForUser(vp)?.selected; if (tiles) for (const tile of tiles) selectedTiles.add(tile); } for (const selected of selectedTiles) this.add(selected, TileMemorySelector.Selected); for (const selected of selectedTiles) { this.processParent(selected.parent); this.processChildren(selected.children); } for (const imodel of imodels) { imodel.tiles.forEachTreeOwner((owner) => { const tree = owner.tileTree; if (tree) this.processOrphan(tree.rootTile); }); } this.counters[TileMemorySelector.Total].numTiles = this.counters.reduce((accum, counter) => accum + counter.numTiles, 0); this.counters[TileMemorySelector.Total].bytesUsed = this.counters.reduce((accum, counter) => accum + counter.bytesUsed, 0); } reset() { this._processedTiles.clear(); this.numSelected = 0; for (const counter of this.counters) counter.numTiles = counter.bytesUsed = 0; } add(tile, selector) { this._processedTiles.add(tile); this._stats.clear(); tile.collectStatistics(this._stats, false); const bytesUsed = this._stats.totalBytes; if (bytesUsed > 0) { const counter = this.counters[selector]; ++counter.numTiles; counter.bytesUsed += bytesUsed; } } processParent(parent) { if (parent && !this._processedTiles.has(parent)) { this.add(parent, TileMemorySelector.Ancestors); this.processParent(parent.parent); } } processChildren(children) { if (!children) return; for (const child of children) { if (!this._processedTiles.has(child)) { this.add(child, TileMemorySelector.Descendants); this.processChildren(child.children); } } } processOrphan(tile) { if (!this._processedTiles.has(tile)) this.add(tile, TileMemorySelector.Orphaned); const children = tile.children; if (children) for (const child of children) this.processOrphan(child); } } const statsLabels = ["Selected", "Ancestors", "Descendants", "Orphaned", "Total"]; function format(count, label, bytesUsed) { return `${count} ${label}: ${formatMemory(bytesUsed)}`; } /** Breaks down the GPU memory allocated to tiles into categories displayed in two panes. * The left-hand pane obtains its information from [TileAdmin]($frontend), displaying the number of selected and unselected tiles that are consuming graphics memory along with the amount of memory consumed. * The right-hand pane computes its information from the set of all [TileTree]($frontend)s, broken down into groups: * - "Selected" - tiles that have been selected for display in at least one viewport. * - "Ancestors" - tiles not selected for display, but have descendents selected for display. * - "Descendants" - tiles not selected for display, but have ancestors selected for display. * - "Orphans" - tiles not selected for display that have no ancestors nor descendants selected for display. * - "Total" - totals of the above categories. * * Each row of data shows `(number of tiles) Group Name: amount of memory consumed by group`. * The number of tiles in each group includes only those tiles that are consuming a non-zero amount of GPU memory. * The widget includes a checkbox to toggle tracking and display of these statistics. * @beta */ export class TileMemoryBreakdown { _tracer = new TileMemoryTracer(); _div; _curIntervalId; _statsElements = []; _totalsElements = []; /** Construct a new breakdown widget as a child of the specified parent element. */ constructor(parent) { createCheckBox({ parent, name: "Tile Memory Breakdown", id: "tileMemoryBreakdown", handler: (_cb) => this.toggle(), }); parent.appendChild(this._div = document.createElement("div")); this._div.style.display = "none"; this._div.style.textAlign = "right"; const table = document.createElement("table"); table.style.width = "100%"; table.setAttribute("border", "1"); this._div.appendChild(table); const row = document.createElement("tr"); const leftCell = document.createElement("td"); const rightCell = document.createElement("td"); leftCell.style.width = rightCell.style.width = "50%"; row.appendChild(leftCell); row.appendChild(rightCell); table.appendChild(row); for (let i = 0; i < TileMemorySelector.Count; i++) { const div = document.createElement("div"); const elem = document.createElement("text"); this._statsElements.push(elem); div.appendChild(elem); rightCell.appendChild(div); } for (let i = 0; i < 3; i++) { const div = document.createElement("div"); const elem = document.createElement("text"); this._totalsElements.push(elem); div.appendChild(elem); leftCell.appendChild(div); } } [Symbol.dispose]() { this.clearInterval(); } toggle() { if (undefined !== this._curIntervalId) { this._div.style.display = "none"; this.clearInterval(); } else { this._div.style.display = "block"; this.update(); this._curIntervalId = window.setInterval(() => this.update(), 500); } } clearInterval() { if (undefined !== this._curIntervalId) { window.clearInterval(this._curIntervalId); this._curIntervalId = undefined; } } update() { this._tracer.update(); for (let i = 0; i < this._statsElements.length; i++) { const counter = this._tracer.counters[i]; this._statsElements[i].innerText = format(counter.numTiles, statsLabels[i], counter.bytesUsed); } let numUnselected = 0; let unselectedBytes = 0; for (const unselected of IModelApp.tileAdmin.unselectedLoadedTiles) { ++numUnselected; unselectedBytes += unselected.bytesUsed; } let numSelected = 0; let selectedBytes = 0; for (const selected of IModelApp.tileAdmin.selectedLoadedTiles) { ++numSelected; selectedBytes += selected.bytesUsed; } this._totalsElements[0].innerText = format(numSelected, "Selected", selectedBytes); this._totalsElements[1].innerText = format(numUnselected, "Unselected", unselectedBytes); this._totalsElements[2].innerText = format(numSelected + numUnselected, "Total", IModelApp.tileAdmin.totalTileContentBytes); } } //# sourceMappingURL=TileMemoryBreakdown.js.map