openpencil/packages/core/src/profiler/gpu-timer.ts
2026-05-18 18:58:30 +03:00

93 lines
2.2 KiB
TypeScript

interface EXTDisjointTimerQuery {
TIME_ELAPSED_EXT: 0x88bf
GPU_DISJOINT_EXT: 0x8fbb
}
const MAX_PENDING_QUERIES = 4
export class GPUTimer {
private gl: WebGL2RenderingContext | null
private ext: EXTDisjointTimerQuery | null = null
private pending: WebGLQuery[] = []
private activeQuery: WebGLQuery | null = null
private _lastGpuTimeMs = Number.NaN
get available(): boolean {
return this.ext !== null
}
get lastGpuTimeMs(): number {
return this._lastGpuTimeMs
}
constructor(gl: WebGL2RenderingContext | null) {
this.gl = gl
if (gl) {
this.ext =
(gl.getExtension('EXT_disjoint_timer_query_webgl2') as EXTDisjointTimerQuery | null) ?? null
}
}
beginFrame(): void {
if (!this.gl || !this.ext) return
if (this.pending.length >= MAX_PENDING_QUERIES) return
const query = this.gl.createQuery()
this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query)
this.activeQuery = query
}
endFrame(): void {
if (!this.gl || !this.ext || !this.activeQuery) return
this.gl.endQuery(this.ext.TIME_ELAPSED_EXT)
this.pending.push(this.activeQuery)
this.activeQuery = null
}
pollResults(): number | null {
if (!this.gl || !this.ext) return null
const disjoint = this.gl.getParameter(this.ext.GPU_DISJOINT_EXT) as boolean
let result: number | null = null
const remaining: WebGLQuery[] = []
for (const query of this.pending) {
const ready = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT_AVAILABLE) as boolean
if (ready) {
if (!disjoint) {
const ns = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT) as number
this._lastGpuTimeMs = ns / 1_000_000
result = this._lastGpuTimeMs
}
this.gl.deleteQuery(query)
} else {
remaining.push(query)
}
}
this.pending = remaining
return result
}
destroy(): void {
if (!this.gl) return
if (this.activeQuery) {
if (this.ext) {
this.gl.endQuery(this.ext.TIME_ELAPSED_EXT)
}
this.gl.deleteQuery(this.activeQuery)
this.activeQuery = null
}
for (const query of this.pending) {
this.gl.deleteQuery(query)
}
this.pending = []
}
}