vireglass 2.2.0 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/{adapters-DugC6Qri.d.cts → adapters-BOPIRMnL.d.cts} +3 -1
- package/dist/{adapters-BfeAopGO.d.ts → adapters-B_amRBqc.d.ts} +3 -1
- package/dist/{chunk-2XJJO7SC.js → chunk-UDPJLNFW.js} +25 -5
- package/dist/chunk-UDPJLNFW.js.map +1 -0
- package/dist/{chunk-H5QTBF67.js → chunk-V47YU75P.js} +2 -2
- package/dist/{chunk-H5QTBF67.js.map → chunk-V47YU75P.js.map} +1 -1
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +1 -1
- package/dist/dom.d.ts +1 -1
- package/dist/dom.js +1 -1
- package/dist/dom.js.map +1 -1
- package/dist/index.cjs +22 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -4
- package/dist/index.d.ts +13 -4
- package/dist/index.js +2 -2
- package/dist/web/index.cjs +282 -152
- package/dist/web/index.cjs.map +1 -1
- package/dist/web/index.d.cts +89 -3
- package/dist/web/index.d.ts +89 -3
- package/dist/web/index.js +255 -154
- package/dist/web/index.js.map +1 -1
- package/package.json +4 -2
- package/src/adapters.ts +3 -1
- package/src/dom/index.ts +6 -5
- package/src/targets/glsl.ts +31 -2
- package/src/web/gl.ts +57 -4
- package/src/web/gpu-timer.ts +111 -0
- package/src/web/index.ts +1 -0
- package/src/web/renderer.ts +305 -218
- package/dist/chunk-2XJJO7SC.js.map +0 -1
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// GPU timing for `VireGlassRenderer.getLastGpuMs()`, isolated from the renderer so it can be
|
|
2
|
+
// unit-tested against a fake: `EXT_disjoint_timer_query_webgl2` is unavailable in the headless
|
|
3
|
+
// Chromium the shader gates run in, so nothing in this repository otherwise exercises the ring
|
|
4
|
+
// logic at all.
|
|
5
|
+
|
|
6
|
+
/** The two tokens the extension adds; everything else the timer needs is core WebGL2. */
|
|
7
|
+
export type GpuTimerExtension = {
|
|
8
|
+
readonly TIME_ELAPSED_EXT: number;
|
|
9
|
+
readonly GPU_DISJOINT_EXT: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** The slice of WebGL2 this module needs, kept narrow so a test can fake it without a real GL
|
|
13
|
+
* context. A `WebGL2RenderingContext` satisfies this structurally — no cast needed at the call
|
|
14
|
+
* site in `renderer.ts`. */
|
|
15
|
+
export type GpuTimerContext = {
|
|
16
|
+
readonly QUERY_RESULT_AVAILABLE: number;
|
|
17
|
+
readonly QUERY_RESULT: number;
|
|
18
|
+
getExtension(name: string): unknown;
|
|
19
|
+
createQuery(): WebGLQuery | null;
|
|
20
|
+
deleteQuery(query: WebGLQuery): void;
|
|
21
|
+
beginQuery(target: number, query: WebGLQuery): void;
|
|
22
|
+
endQuery(target: number): void;
|
|
23
|
+
getQueryParameter(query: WebGLQuery, pname: number): unknown;
|
|
24
|
+
getParameter(pname: number): unknown;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Queries in flight at once. `EXT_disjoint_timer_query_webgl2` results arrive asynchronously,
|
|
28
|
+
* often a few frames late, so one query per frame would mean starting a new query before the
|
|
29
|
+
* previous one's result is back — undefined by spec. */
|
|
30
|
+
export const GPU_TIMER_RING_SIZE = 3;
|
|
31
|
+
|
|
32
|
+
type TimerSlot = { query: WebGLQuery; pending: boolean };
|
|
33
|
+
|
|
34
|
+
export type GpuTimer = {
|
|
35
|
+
/** Runs `fn` as one timed sample. Always calls the matching `endQuery` even if `fn` throws, so
|
|
36
|
+
* a frame that fails partway through (a broken backdrop pass, a validation error) never leaves
|
|
37
|
+
* a query open for the next call to trip over. */
|
|
38
|
+
time<T>(fn: () => T): T;
|
|
39
|
+
/** `null` without the extension or while the ring hasn't produced a non-disjoint result yet. */
|
|
40
|
+
getLastMs(): number | null;
|
|
41
|
+
destroy(): void;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/** Missing the extension (most headless/CI setups, and every browser but Chromium-family in
|
|
45
|
+
* practice) degrades every method here to a no-op / `null` — never an error. */
|
|
46
|
+
export function createGpuTimer(gl: GpuTimerContext): GpuTimer {
|
|
47
|
+
const ext = gl.getExtension('EXT_disjoint_timer_query_webgl2') as GpuTimerExtension | null;
|
|
48
|
+
const slots: TimerSlot[] = ext
|
|
49
|
+
? Array.from({ length: GPU_TIMER_RING_SIZE }, () => ({
|
|
50
|
+
query: gl.createQuery() as WebGLQuery,
|
|
51
|
+
pending: false,
|
|
52
|
+
}))
|
|
53
|
+
: [];
|
|
54
|
+
let cursor = 0;
|
|
55
|
+
let active: TimerSlot | null = null;
|
|
56
|
+
let lastMs: number | null = null;
|
|
57
|
+
|
|
58
|
+
function poll(): void {
|
|
59
|
+
if (!ext) return;
|
|
60
|
+
for (const slot of slots) {
|
|
61
|
+
if (!slot.pending) continue;
|
|
62
|
+
if (!gl.getQueryParameter(slot.query, gl.QUERY_RESULT_AVAILABLE)) continue;
|
|
63
|
+
slot.pending = false;
|
|
64
|
+
// A disjoint event (e.g. the GPU clock changed) invalidates every query since the last
|
|
65
|
+
// check — the sample is discarded, not reported as zero or stale.
|
|
66
|
+
if (gl.getParameter(ext.GPU_DISJOINT_EXT)) continue;
|
|
67
|
+
const ns = gl.getQueryParameter(slot.query, gl.QUERY_RESULT) as number;
|
|
68
|
+
lastMs = ns / 1e6;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function begin(): void {
|
|
73
|
+
active = null;
|
|
74
|
+
if (!ext || slots.length === 0) return;
|
|
75
|
+
poll();
|
|
76
|
+
const slot = slots[cursor];
|
|
77
|
+
// The ring hasn't caught up (this slot's previous result isn't back yet) — skip timing this
|
|
78
|
+
// frame rather than reuse a query that's still in flight.
|
|
79
|
+
if (slot.pending) return;
|
|
80
|
+
gl.beginQuery(ext.TIME_ELAPSED_EXT, slot.query);
|
|
81
|
+
active = slot;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function end(): void {
|
|
85
|
+
if (!ext || !active) return;
|
|
86
|
+
gl.endQuery(ext.TIME_ELAPSED_EXT);
|
|
87
|
+
active.pending = true;
|
|
88
|
+
cursor = (cursor + 1) % slots.length;
|
|
89
|
+
active = null;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function time<T>(fn: () => T): T {
|
|
93
|
+
begin();
|
|
94
|
+
try {
|
|
95
|
+
return fn();
|
|
96
|
+
} finally {
|
|
97
|
+
end();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getLastMs(): number | null {
|
|
102
|
+
poll();
|
|
103
|
+
return lastMs;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function destroy(): void {
|
|
107
|
+
for (const slot of slots) gl.deleteQuery(slot.query);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { time, getLastMs, destroy };
|
|
111
|
+
}
|
package/src/web/index.ts
CHANGED