@itwin/frontend-devtools
Version:
Debug menu and supporting UI widgets
206 lines • 8.96 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* 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
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.TileMemoryBreakdown = void 0;
const core_frontend_1 = require("@itwin/core-frontend");
const CheckBox_1 = require("../ui/CheckBox");
const MemoryTracker_1 = require("./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 core_frontend_1.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 core_frontend_1.IModelApp.viewManager) {
imodels.add(vp.iModel);
const tiles = core_frontend_1.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}: ${(0, MemoryTracker_1.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
*/
class TileMemoryBreakdown {
_tracer = new TileMemoryTracer();
_div;
_curIntervalId;
_statsElements = [];
_totalsElements = [];
/** Construct a new breakdown widget as a child of the specified parent element. */
constructor(parent) {
(0, CheckBox_1.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 core_frontend_1.IModelApp.tileAdmin.unselectedLoadedTiles) {
++numUnselected;
unselectedBytes += unselected.bytesUsed;
}
let numSelected = 0;
let selectedBytes = 0;
for (const selected of core_frontend_1.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", core_frontend_1.IModelApp.tileAdmin.totalTileContentBytes);
}
}
exports.TileMemoryBreakdown = TileMemoryBreakdown;
//# sourceMappingURL=TileMemoryBreakdown.js.map