@itwin/core-frontend
Version:
iTwin.js frontend components
174 lines • 7.84 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.
*--------------------------------------------------------------------------------------------*/
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