UNPKG

@itwin/core-frontend

Version:
174 lines 7.84 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.GLTimer = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); class DisjointTimerExtension { _e; // EXT_disjoint_timer_query, not available in lib.dom.d.ts _context; constructor(system) { this._e = system.disjointTimerQuery; this._context = system.context; } get isSupported() { return this._e !== undefined; } didDisjointEventHappen() { return this._context.getParameter(this._e.GPU_DISJOINT_EXT); } createQuery() { return this._context.createQuery(); } deleteQuery(q) { this._context.deleteQuery(q); } beginQuery(q) { this._context.beginQuery(this._e.TIME_ELAPSED_EXT, q); } endQuery() { this._context.endQuery(this._e.TIME_ELAPSED_EXT); } isResultAvailable(q) { return this._context.getQueryParameter(q, this._context.QUERY_RESULT_AVAILABLE); } getResult(q) { return this._context.getQueryParameter(q, this._context.QUERY_RESULT); } } /** Record GPU hardware queries to profile independent of CPU. * * This is a wrapper around EXT_disjoint_timer_query. The extension should be available in the following browsers: * * Chrome 67 and later * * Chromium-based Edge * * Firefox (with webgl.enable-privileged-extensions set to true in about:config) * * EXT_disjoint_timer_query only supports one active query per context without nesting. This wrapper keeps an internal stack to make * nesting work. * * The extension API makes timestamps look like a better solution than disjoint timers, but they are not actually supported. * See https://bugs.chromium.org/p/chromium/issues/detail?id=595172 * @internal */ class GLTimer { _extension; _queryStack; _resultsCallback; constructor(system) { this._extension = new DisjointTimerExtension(system); this._queryStack = []; this._resultsCallback = undefined; } // This class is necessarily a singleton per context because of the underlying extension it wraps. // System is expected to call create in its constructor. static create(system) { return new GLTimer(system); } get isSupported() { return this._extension.isSupported; } set resultsCallback(callback) { if (this._queryStack.length !== 0) throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Do not set resultsCallback when a frame is already being drawn"); this._resultsCallback = callback; } beginOperation(label) { if (!this._resultsCallback) return; this.pushQuery(label); } endOperation() { if (!this._resultsCallback) return; if (this._queryStack.length === 0) throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Mismatched calls to beginOperation/endOperation"); this.popQuery(); } beginFrame() { if (!this._resultsCallback) return; if (this._queryStack.length !== 0) throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Already recording timing for a frame"); const query = this._extension.createQuery(); this._extension.beginQuery(query); this._queryStack.push({ label: "Total", query, children: [] }); } endFrame() { if (!this._resultsCallback) return; if (this._queryStack.length !== 1) throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Missing at least one endOperation call"); this._extension.endQuery(); // We verified that this._queryStack.length === 1 above, so we can safely pop it. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const root = this._queryStack.pop(); const userCallback = this._resultsCallback; const queryCallback = () => { if (this._extension.didDisjointEventHappen()) { // Have to throw away results for this frame after disjoint event occurs. this.cleanupAfterDisjointEvent(root); return; } // It takes one or more frames for results to become available. // Only checking time for root since it will always be the last query completed. // If there are any sibling queries then we will just check the last one. const finalQuery = (undefined === root.siblingQueries ? root.query : root.siblingQueries[root.siblingQueries.length - 1]); if (!this._extension.isResultAvailable(finalQuery)) { setTimeout(queryCallback, 0); return; } const processQueryEntry = (queryEntry) => { const time = this._extension.getResult(queryEntry.query); this._extension.deleteQuery(queryEntry.query); const result = { label: queryEntry.label, nanoseconds: time }; if (undefined !== queryEntry.siblingQueries) { for (const sib of queryEntry.siblingQueries) { const sibTime = this._extension.getResult(sib); this._extension.deleteQuery(sib); result.nanoseconds += sibTime; } queryEntry.siblingQueries = undefined; } if (queryEntry.children === undefined) return result; result.children = []; for (const child of queryEntry.children) { const childResult = processQueryEntry(child); result.children.push(childResult); result.nanoseconds += childResult.nanoseconds; } return result; }; userCallback(processQueryEntry(root)); }; setTimeout(queryCallback, 0); } cleanupAfterDisjointEvent(queryEntry) { this._extension.deleteQuery(queryEntry.query); if (undefined !== queryEntry.siblingQueries) { for (const sib of queryEntry.siblingQueries) this._extension.deleteQuery(sib); queryEntry.siblingQueries = undefined; } if (!queryEntry.children) return; for (const child of queryEntry.children) this.cleanupAfterDisjointEvent(child); } pushQuery(label) { this._extension.endQuery(); const query = this._extension.createQuery(); this._extension.beginQuery(query); const activeQuery = this._queryStack[this._queryStack.length - 1]; const queryEntry = { label, query }; this._queryStack.push(queryEntry); if (activeQuery.children === undefined) activeQuery.children = [queryEntry]; else activeQuery.children.push(queryEntry); } popQuery() { this._extension.endQuery(); this._queryStack.pop(); const lastStackIndex = this._queryStack.length - 1; const activeQuery = this._queryStack[lastStackIndex]; if (undefined === activeQuery.siblingQueries) activeQuery.siblingQueries = []; const newQuery = this._extension.createQuery(); activeQuery.siblingQueries.push(newQuery); this._extension.beginQuery(newQuery); } } exports.GLTimer = GLTimer; //# sourceMappingURL=GLTimer.js.map