UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

255 lines (240 loc) 8.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _utils = require("../../../utils.js"); var _TimestampQueryPool = _interopRequireDefault(require("../../common/TimestampQueryPool.js")); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Manages a pool of WebGL timestamp queries for performance measurement. * Handles creation, execution, and resolution of timer queries using WebGL extensions. * * @augments TimestampQueryPool */ class WebGLTimestampQueryPool extends _TimestampQueryPool.default { /** * Creates a new WebGL timestamp query pool. * * @param {WebGLRenderingContext|WebGL2RenderingContext} gl - The WebGL context. * @param {string} type - The type identifier for this query pool. * @param {number} [maxQueries=2048] - Maximum number of queries this pool can hold. */ constructor(gl, type, maxQueries = 2048) { super(maxQueries); this.gl = gl; this.type = type; // Check for timer query extensions this.ext = gl.getExtension('EXT_disjoint_timer_query_webgl2') || gl.getExtension('EXT_disjoint_timer_query'); if (!this.ext) { console.warn('EXT_disjoint_timer_query not supported; timestamps will be disabled.'); this.trackTimestamp = false; return; } // Create query objects this.queries = []; for (let i = 0; i < this.maxQueries; i++) { this.queries.push(gl.createQuery()); } this.activeQuery = null; this.queryStates = new Map(); // Track state of each query: 'inactive', 'started', 'ended' } /** * Allocates a pair of queries for a given render context. * * @param {Object} renderContext - The render context to allocate queries for. * @returns {?number} The base offset for the allocated queries, or null if allocation failed. */ allocateQueriesForContext(renderContext) { if (!this.trackTimestamp) return null; // Check if we have enough space for a new query pair if (this.currentQueryIndex + 2 > this.maxQueries) { (0, _utils.warnOnce)(`WebGPUTimestampQueryPool [${this.type}]: Maximum number of queries exceeded, when using trackTimestamp it is necessary to resolves the queries via renderer.resolveTimestampsAsync( THREE.TimestampQuery.${this.type.toUpperCase()} ).`); return null; } const baseOffset = this.currentQueryIndex; this.currentQueryIndex += 2; // Initialize query states this.queryStates.set(baseOffset, 'inactive'); this.queryOffsets.set(renderContext.id, baseOffset); return baseOffset; } /** * Begins a timestamp query for the specified render context. * * @param {Object} renderContext - The render context to begin timing for. */ beginQuery(renderContext) { if (!this.trackTimestamp || this.isDisposed) { return; } const baseOffset = this.queryOffsets.get(renderContext.id); if (baseOffset == null) { return; } // Don't start a new query if there's an active one if (this.activeQuery !== null) { return; } const query = this.queries[baseOffset]; if (!query) { return; } try { // Only begin if query is inactive if (this.queryStates.get(baseOffset) === 'inactive') { this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query); this.activeQuery = baseOffset; this.queryStates.set(baseOffset, 'started'); } } catch (error) { console.error('Error in beginQuery:', error); this.activeQuery = null; this.queryStates.set(baseOffset, 'inactive'); } } /** * Ends the active timestamp query for the specified render context. * * @param {Object} renderContext - The render context to end timing for. * @param {string} renderContext.id - Unique identifier for the render context. */ endQuery(renderContext) { if (!this.trackTimestamp || this.isDisposed) { return; } const baseOffset = this.queryOffsets.get(renderContext.id); if (baseOffset == null) { return; } // Only end if this is the active query if (this.activeQuery !== baseOffset) { return; } try { this.gl.endQuery(this.ext.TIME_ELAPSED_EXT); this.queryStates.set(baseOffset, 'ended'); this.activeQuery = null; } catch (error) { console.error('Error in endQuery:', error); // Reset state on error this.queryStates.set(baseOffset, 'inactive'); this.activeQuery = null; } } /** * Asynchronously resolves all completed queries and returns the total duration. * * @async * @returns {Promise<number>} The total duration in milliseconds, or the last valid value if resolution fails. */ async resolveQueriesAsync() { if (!this.trackTimestamp || this.pendingResolve) { return this.lastValue; } this.pendingResolve = true; try { // Wait for all ended queries to complete const resolvePromises = []; for (const [baseOffset, state] of this.queryStates) { if (state === 'ended') { const query = this.queries[baseOffset]; resolvePromises.push(this.resolveQuery(query)); } } if (resolvePromises.length === 0) { return this.lastValue; } const results = await Promise.all(resolvePromises); const totalDuration = results.reduce((acc, val) => acc + val, 0); // Store the last valid result this.lastValue = totalDuration; // Reset states this.currentQueryIndex = 0; this.queryOffsets.clear(); this.queryStates.clear(); this.activeQuery = null; return totalDuration; } catch (error) { console.error('Error resolving queries:', error); return this.lastValue; } finally { this.pendingResolve = false; } } /** * Resolves a single query, checking for completion and disjoint operation. * * @async * @param {WebGLQuery} query - The query object to resolve. * @returns {Promise<number>} The elapsed time in milliseconds. */ async resolveQuery(query) { return new Promise(resolve => { if (this.isDisposed) { resolve(this.lastValue); return; } let timeoutId; let isResolved = false; const cleanup = () => { if (timeoutId) { clearTimeout(timeoutId); timeoutId = null; } }; const finalizeResolution = value => { if (!isResolved) { isResolved = true; cleanup(); resolve(value); } }; const checkQuery = () => { if (this.isDisposed) { finalizeResolution(this.lastValue); return; } try { // Check if the GPU timer was disjoint (i.e., timing was unreliable) const disjoint = this.gl.getParameter(this.ext.GPU_DISJOINT_EXT); if (disjoint) { finalizeResolution(this.lastValue); return; } const available = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT_AVAILABLE); if (!available) { timeoutId = setTimeout(checkQuery, 1); return; } const elapsed = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT); resolve(Number(elapsed) / 1e6); // Convert nanoseconds to milliseconds } catch (error) { console.error('Error checking query:', error); resolve(this.lastValue); } }; checkQuery(); }); } /** * Releases all resources held by this query pool. * This includes deleting all query objects and clearing internal state. */ dispose() { if (this.isDisposed) { return; } this.isDisposed = true; if (!this.trackTimestamp) return; for (const query of this.queries) { this.gl.deleteQuery(query); } this.queries = []; this.queryStates.clear(); this.queryOffsets.clear(); this.lastValue = 0; this.activeQuery = null; } } var _default = exports.default = WebGLTimestampQueryPool;