@zkmake/three-meter 0.1.0
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/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/mount-perf-hud-CduRNym5.js +1176 -0
- package/dist/mount-perf-hud-CduRNym5.js.map +1 -0
- package/dist/mount-perf-hud-Dw8KNQTt.d.ts +156 -0
- package/dist/performance-monitor-WGLWbxOZ.d.ts +111 -0
- package/dist/performance-monitor-qswCj9Nl.js +263 -0
- package/dist/performance-monitor-qswCj9Nl.js.map +1 -0
- package/dist/react.d.ts +39 -0
- package/dist/react.js +114 -0
- package/dist/react.js.map +1 -0
- package/dist/styles.css +300 -0
- package/dist/ui.d.ts +11 -0
- package/dist/ui.js +2 -0
- package/package.json +104 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
//#region src/core/gpu-timer.ts
|
|
2
|
+
var GpuTimer = class GpuTimer {
|
|
3
|
+
lastMs = 0;
|
|
4
|
+
webgpu;
|
|
5
|
+
webgpuInFlight = false;
|
|
6
|
+
gl;
|
|
7
|
+
ext;
|
|
8
|
+
free = [];
|
|
9
|
+
pending = [];
|
|
10
|
+
active = null;
|
|
11
|
+
constructor(webgpu, gl, ext, poolSize) {
|
|
12
|
+
this.webgpu = webgpu;
|
|
13
|
+
this.gl = gl;
|
|
14
|
+
this.ext = ext;
|
|
15
|
+
if (!gl) return;
|
|
16
|
+
for (let index = 0; index < poolSize; index += 1) {
|
|
17
|
+
const query = gl.createQuery();
|
|
18
|
+
if (query) this.free.push(query);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
static create(renderer, poolSize) {
|
|
22
|
+
if (renderer.backend?.trackTimestamp && renderer.resolveTimestampsAsync) return new GpuTimer(renderer, null, null, poolSize);
|
|
23
|
+
const context = renderer.getContext?.();
|
|
24
|
+
if (!(context instanceof WebGL2RenderingContext)) return null;
|
|
25
|
+
const ext = context.getExtension("EXT_disjoint_timer_query_webgl2");
|
|
26
|
+
return ext ? new GpuTimer(null, context, ext, poolSize) : null;
|
|
27
|
+
}
|
|
28
|
+
begin() {
|
|
29
|
+
if (this.webgpu || !this.gl || !this.ext) return;
|
|
30
|
+
const query = this.free.pop();
|
|
31
|
+
if (!query) {
|
|
32
|
+
this.active = null;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
this.active = query;
|
|
36
|
+
this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query);
|
|
37
|
+
}
|
|
38
|
+
end() {
|
|
39
|
+
if (this.webgpu || !this.gl || !this.ext || !this.active) return;
|
|
40
|
+
this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
|
|
41
|
+
this.pending.push(this.active);
|
|
42
|
+
this.active = null;
|
|
43
|
+
}
|
|
44
|
+
poll() {
|
|
45
|
+
if (this.webgpu) {
|
|
46
|
+
this.pollWebgpu();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.pollWebgl();
|
|
50
|
+
}
|
|
51
|
+
getMs() {
|
|
52
|
+
return this.lastMs;
|
|
53
|
+
}
|
|
54
|
+
dispose() {
|
|
55
|
+
if (this.gl && this.ext && this.active) {
|
|
56
|
+
this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
|
|
57
|
+
this.active = null;
|
|
58
|
+
}
|
|
59
|
+
if (this.gl) {
|
|
60
|
+
for (const query of this.free) this.gl.deleteQuery(query);
|
|
61
|
+
for (const query of this.pending) this.gl.deleteQuery(query);
|
|
62
|
+
}
|
|
63
|
+
this.free.length = 0;
|
|
64
|
+
this.pending.length = 0;
|
|
65
|
+
}
|
|
66
|
+
pollWebgpu() {
|
|
67
|
+
const resolve = this.webgpu?.resolveTimestampsAsync;
|
|
68
|
+
if (!resolve || this.webgpuInFlight) return;
|
|
69
|
+
this.webgpuInFlight = true;
|
|
70
|
+
resolve.call(this.webgpu, "render").then((ms) => {
|
|
71
|
+
if (typeof ms === "number" && Number.isFinite(ms)) this.lastMs = ms;
|
|
72
|
+
this.webgpuInFlight = false;
|
|
73
|
+
}).catch(() => {
|
|
74
|
+
this.webgpuInFlight = false;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
pollWebgl() {
|
|
78
|
+
if (!this.gl || !this.ext) return;
|
|
79
|
+
if (this.gl.getParameter(this.ext.GPU_DISJOINT_EXT)) {
|
|
80
|
+
while (this.pending.length > 0) this.free.push(this.pending.shift());
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
while (this.pending.length > 0) {
|
|
84
|
+
const query = this.pending[0];
|
|
85
|
+
if (!this.gl.getQueryParameter(query, this.gl.QUERY_RESULT_AVAILABLE)) break;
|
|
86
|
+
this.pending.shift();
|
|
87
|
+
const ns = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT);
|
|
88
|
+
this.lastMs = ns / 1e6;
|
|
89
|
+
this.free.push(query);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/core/ring-buffer.ts
|
|
95
|
+
/**
|
|
96
|
+
* Fixed-capacity numeric ring buffer. Backs the per-frame timing history the
|
|
97
|
+
* view reads to draw sparklines. Push is O(1); `toArray()` returns values in
|
|
98
|
+
* chronological (oldest → newest) order.
|
|
99
|
+
*/
|
|
100
|
+
var RingBuffer = class {
|
|
101
|
+
data;
|
|
102
|
+
capacity;
|
|
103
|
+
head = 0;
|
|
104
|
+
count = 0;
|
|
105
|
+
constructor(capacity) {
|
|
106
|
+
this.capacity = capacity;
|
|
107
|
+
this.data = new Float32Array(capacity);
|
|
108
|
+
}
|
|
109
|
+
push(value) {
|
|
110
|
+
this.data[this.head] = value;
|
|
111
|
+
this.head = (this.head + 1) % this.capacity;
|
|
112
|
+
if (this.count < this.capacity) this.count += 1;
|
|
113
|
+
}
|
|
114
|
+
/** Values oldest → newest. Length grows up to capacity as samples accumulate. */
|
|
115
|
+
toArray() {
|
|
116
|
+
const out = Array.from({ length: this.count });
|
|
117
|
+
const start = (this.head - this.count + this.capacity) % this.capacity;
|
|
118
|
+
for (let index = 0; index < this.count; index += 1) out[index] = this.data[(start + index) % this.capacity];
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
clear() {
|
|
122
|
+
this.head = 0;
|
|
123
|
+
this.count = 0;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
//#endregion
|
|
127
|
+
//#region src/core/performance-monitor.ts
|
|
128
|
+
const DEFAULT_GPU_POOL_SIZE = 5;
|
|
129
|
+
/** One monitor per renderer: a second would stack the `render` patch and double-count passes. */
|
|
130
|
+
const ATTACHED = /* @__PURE__ */ new WeakSet();
|
|
131
|
+
const DEFAULT_HISTORY_SIZE = 120;
|
|
132
|
+
const FPS_SMOOTHING = .1;
|
|
133
|
+
const frameCalls = (info) => info.render.drawCalls ?? info.render.calls;
|
|
134
|
+
const programCount = (info) => info.memory.programs ?? info.programs?.length ?? 0;
|
|
135
|
+
/**
|
|
136
|
+
* Collector of three.js frame metrics. Bracket each frame with {@link begin}/
|
|
137
|
+
* {@link end}, or let {@link wrapAnimationLoop} or the R3F `PerfSampler` do it.
|
|
138
|
+
* Works on WebGPURenderer (drawCalls, memory.programs, timestamp queries) and
|
|
139
|
+
* on a WebGL renderer (calls, programs[], timer-query ext).
|
|
140
|
+
*
|
|
141
|
+
* While attached, `renderer.info.autoReset` is off and `info` is reset in
|
|
142
|
+
* {@link begin}; host code that reads `renderer.info` mid-frame sees this
|
|
143
|
+
* frame's counts only. {@link dispose} restores both.
|
|
144
|
+
*/
|
|
145
|
+
var PerformanceMonitor = class {
|
|
146
|
+
renderer;
|
|
147
|
+
gpuTimer;
|
|
148
|
+
originalRender;
|
|
149
|
+
renderPasses = 0;
|
|
150
|
+
fpsHistory;
|
|
151
|
+
cpuHistory;
|
|
152
|
+
gpuHistory;
|
|
153
|
+
cpuStart = 0;
|
|
154
|
+
lastBeginAt = null;
|
|
155
|
+
frameFps = 0;
|
|
156
|
+
smoothedFps = 0;
|
|
157
|
+
sample;
|
|
158
|
+
constructor(options) {
|
|
159
|
+
const { renderer, trackGPU = true, gpuQueryPoolSize = DEFAULT_GPU_POOL_SIZE, historySize = DEFAULT_HISTORY_SIZE } = options;
|
|
160
|
+
if (ATTACHED.has(renderer)) throw new Error("three-meter: a PerformanceMonitor is already attached to this renderer. dispose() it first.");
|
|
161
|
+
ATTACHED.add(renderer);
|
|
162
|
+
this.renderer = renderer;
|
|
163
|
+
renderer.info.autoReset = false;
|
|
164
|
+
this.originalRender = renderer.render;
|
|
165
|
+
renderer.render = (...args) => {
|
|
166
|
+
this.renderPasses += 1;
|
|
167
|
+
return this.originalRender.apply(renderer, args);
|
|
168
|
+
};
|
|
169
|
+
this.gpuTimer = trackGPU ? GpuTimer.create(renderer, gpuQueryPoolSize) : null;
|
|
170
|
+
this.fpsHistory = new RingBuffer(historySize);
|
|
171
|
+
this.cpuHistory = new RingBuffer(historySize);
|
|
172
|
+
this.gpuHistory = new RingBuffer(historySize);
|
|
173
|
+
this.sample = this.buildEmptySample();
|
|
174
|
+
}
|
|
175
|
+
begin() {
|
|
176
|
+
const now = performance.now();
|
|
177
|
+
if (this.lastBeginAt !== null) {
|
|
178
|
+
const frameMs = now - this.lastBeginAt;
|
|
179
|
+
this.frameFps = frameMs > 0 ? 1e3 / frameMs : 0;
|
|
180
|
+
}
|
|
181
|
+
this.lastBeginAt = now;
|
|
182
|
+
this.cpuStart = now;
|
|
183
|
+
this.renderPasses = 0;
|
|
184
|
+
this.renderer.info.reset();
|
|
185
|
+
this.gpuTimer?.begin();
|
|
186
|
+
}
|
|
187
|
+
end() {
|
|
188
|
+
const cpu = performance.now() - this.cpuStart;
|
|
189
|
+
this.gpuTimer?.end();
|
|
190
|
+
this.gpuTimer?.poll();
|
|
191
|
+
const gpuMs = this.gpuTimer?.getMs() ?? 0;
|
|
192
|
+
this.smoothedFps = this.smoothedFps === 0 ? this.frameFps : this.smoothedFps + (this.frameFps - this.smoothedFps) * FPS_SMOOTHING;
|
|
193
|
+
this.fpsHistory.push(this.frameFps);
|
|
194
|
+
this.cpuHistory.push(cpu);
|
|
195
|
+
this.gpuHistory.push(gpuMs);
|
|
196
|
+
const info = this.renderer.info;
|
|
197
|
+
this.sample = Object.freeze({
|
|
198
|
+
cpu,
|
|
199
|
+
fps: this.smoothedFps,
|
|
200
|
+
gpu: {
|
|
201
|
+
available: this.gpuTimer !== null,
|
|
202
|
+
ms: gpuMs
|
|
203
|
+
},
|
|
204
|
+
render: {
|
|
205
|
+
calls: frameCalls(info),
|
|
206
|
+
lines: info.render.lines,
|
|
207
|
+
passes: this.renderPasses,
|
|
208
|
+
points: info.render.points,
|
|
209
|
+
triangles: info.render.triangles
|
|
210
|
+
},
|
|
211
|
+
resources: {
|
|
212
|
+
geometries: info.memory.geometries,
|
|
213
|
+
programs: programCount(info),
|
|
214
|
+
textures: info.memory.textures
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
getSample() {
|
|
219
|
+
return this.sample;
|
|
220
|
+
}
|
|
221
|
+
getHistory(metric) {
|
|
222
|
+
switch (metric) {
|
|
223
|
+
case "fps": return this.fpsHistory.toArray();
|
|
224
|
+
case "cpu": return this.cpuHistory.toArray();
|
|
225
|
+
case "gpu": return this.gpuHistory.toArray();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
dispose() {
|
|
229
|
+
ATTACHED.delete(this.renderer);
|
|
230
|
+
this.renderer.info.autoReset = true;
|
|
231
|
+
this.renderer.render = this.originalRender;
|
|
232
|
+
this.gpuTimer?.dispose();
|
|
233
|
+
this.fpsHistory.clear();
|
|
234
|
+
this.cpuHistory.clear();
|
|
235
|
+
this.gpuHistory.clear();
|
|
236
|
+
}
|
|
237
|
+
buildEmptySample() {
|
|
238
|
+
return Object.freeze({
|
|
239
|
+
cpu: 0,
|
|
240
|
+
fps: 0,
|
|
241
|
+
gpu: {
|
|
242
|
+
available: this.gpuTimer !== null,
|
|
243
|
+
ms: 0
|
|
244
|
+
},
|
|
245
|
+
render: {
|
|
246
|
+
calls: 0,
|
|
247
|
+
lines: 0,
|
|
248
|
+
passes: 0,
|
|
249
|
+
points: 0,
|
|
250
|
+
triangles: 0
|
|
251
|
+
},
|
|
252
|
+
resources: {
|
|
253
|
+
geometries: 0,
|
|
254
|
+
programs: 0,
|
|
255
|
+
textures: 0
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
//#endregion
|
|
261
|
+
export { RingBuffer as n, PerformanceMonitor as t };
|
|
262
|
+
|
|
263
|
+
//# sourceMappingURL=performance-monitor-qswCj9Nl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance-monitor-qswCj9Nl.js","names":[],"sources":["../src/core/gpu-timer.ts","../src/core/ring-buffer.ts","../src/core/performance-monitor.ts"],"sourcesContent":["/**\n * Real GPU frame timing.\n *\n * WebGPU: Three's `trackTimestamp` + `resolveTimestampsAsync` (ms, a frame or\n * two late). Never calls `getExtension`: that is what remounted the canvas.\n *\n * WebGL2 fallback (Safari without WebGPU, or a forced GL backend):\n * `EXT_disjoint_timer_query_webgl2`, same pool-of-queries pattern as the\n * platform-typing monitor.\n */\nimport type { PerfRenderer } from \"./types.ts\";\n\ntype DisjointTimerQueryExt = {\n readonly GPU_DISJOINT_EXT: number;\n readonly TIME_ELAPSED_EXT: number;\n};\n\nclass GpuTimer {\n private lastMs = 0;\n private readonly webgpu: PerfRenderer | null;\n private webgpuInFlight = false;\n private readonly gl: WebGL2RenderingContext | null;\n private readonly ext: DisjointTimerQueryExt | null;\n private readonly free: WebGLQuery[] = [];\n private readonly pending: WebGLQuery[] = [];\n private active: WebGLQuery | null = null;\n\n private constructor(\n webgpu: PerfRenderer | null,\n gl: WebGL2RenderingContext | null,\n ext: DisjointTimerQueryExt | null,\n poolSize: number,\n ) {\n this.webgpu = webgpu;\n this.gl = gl;\n this.ext = ext;\n\n if (!gl) {\n return;\n }\n\n for (let index = 0; index < poolSize; index += 1) {\n const query = gl.createQuery();\n\n if (query) {\n this.free.push(query);\n }\n }\n }\n\n static create(renderer: PerfRenderer, poolSize: number): GpuTimer | null {\n const backend = renderer.backend as { trackTimestamp?: boolean } | undefined;\n\n if (backend?.trackTimestamp && renderer.resolveTimestampsAsync) {\n return new GpuTimer(renderer, null, null, poolSize);\n }\n\n const context = renderer.getContext?.();\n\n if (!(context instanceof WebGL2RenderingContext)) {\n return null;\n }\n\n const ext = context.getExtension(\n \"EXT_disjoint_timer_query_webgl2\",\n ) as DisjointTimerQueryExt | null;\n\n return ext ? new GpuTimer(null, context, ext, poolSize) : null;\n }\n\n begin() {\n if (this.webgpu || !this.gl || !this.ext) {\n return;\n }\n\n const query = this.free.pop();\n\n if (!query) {\n this.active = null;\n\n return;\n }\n\n this.active = query;\n this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, query);\n }\n\n end() {\n if (this.webgpu || !this.gl || !this.ext || !this.active) {\n return;\n }\n\n this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);\n this.pending.push(this.active);\n this.active = null;\n }\n\n poll() {\n if (this.webgpu) {\n this.pollWebgpu();\n\n return;\n }\n\n this.pollWebgl();\n }\n\n getMs() {\n return this.lastMs;\n }\n\n dispose() {\n if (this.gl && this.ext && this.active) {\n this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);\n this.active = null;\n }\n\n if (this.gl) {\n for (const query of this.free) {\n this.gl.deleteQuery(query);\n }\n\n for (const query of this.pending) {\n this.gl.deleteQuery(query);\n }\n }\n\n this.free.length = 0;\n this.pending.length = 0;\n }\n\n private pollWebgpu() {\n const resolve = this.webgpu?.resolveTimestampsAsync;\n\n if (!resolve || this.webgpuInFlight) {\n return;\n }\n\n this.webgpuInFlight = true;\n\n void resolve\n .call(this.webgpu, \"render\")\n .then((ms) => {\n if (typeof ms === \"number\" && Number.isFinite(ms)) {\n this.lastMs = ms;\n }\n\n this.webgpuInFlight = false;\n })\n .catch(() => {\n this.webgpuInFlight = false;\n });\n }\n\n private pollWebgl() {\n if (!this.gl || !this.ext) {\n return;\n }\n\n const disjoint = this.gl.getParameter(this.ext.GPU_DISJOINT_EXT) as boolean;\n\n if (disjoint) {\n while (this.pending.length > 0) {\n this.free.push(this.pending.shift()!);\n }\n\n return;\n }\n\n while (this.pending.length > 0) {\n const query = this.pending[0]!;\n const ready = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT_AVAILABLE) as boolean;\n\n if (!ready) {\n break;\n }\n\n this.pending.shift();\n const ns = this.gl.getQueryParameter(query, this.gl.QUERY_RESULT) as number;\n this.lastMs = ns / 1e6;\n this.free.push(query);\n }\n }\n}\n\nexport { GpuTimer };\n","/**\n * Fixed-capacity numeric ring buffer. Backs the per-frame timing history the\n * view reads to draw sparklines. Push is O(1); `toArray()` returns values in\n * chronological (oldest → newest) order.\n */\nclass RingBuffer {\n private readonly data: Float32Array;\n private readonly capacity: number;\n private head = 0;\n private count = 0;\n\n constructor(capacity: number) {\n this.capacity = capacity;\n this.data = new Float32Array(capacity);\n }\n\n push(value: number) {\n this.data[this.head] = value;\n this.head = (this.head + 1) % this.capacity;\n\n if (this.count < this.capacity) {\n this.count += 1;\n }\n }\n\n /** Values oldest → newest. Length grows up to capacity as samples accumulate. */\n toArray(): number[] {\n const out = Array.from<number>({ length: this.count });\n const start = (this.head - this.count + this.capacity) % this.capacity;\n\n for (let index = 0; index < this.count; index += 1) {\n out[index] = this.data[(start + index) % this.capacity]!;\n }\n\n return out;\n }\n\n clear() {\n this.head = 0;\n this.count = 0;\n }\n}\n\nexport { RingBuffer };\n","import { GpuTimer } from \"./gpu-timer.ts\";\nimport { RingBuffer } from \"./ring-buffer.ts\";\nimport type { PerfRenderer, PerformanceMonitorOptions, Sample, TimingMetric } from \"./types.ts\";\n\nconst DEFAULT_GPU_POOL_SIZE = 5;\n/** One monitor per renderer: a second would stack the `render` patch and double-count passes. */\nconst ATTACHED = new WeakSet<PerfRenderer>();\nconst DEFAULT_HISTORY_SIZE = 120;\nconst FPS_SMOOTHING = 0.1;\n\nconst frameCalls = (info: PerfRenderer[\"info\"]) => info.render.drawCalls ?? info.render.calls;\n\nconst programCount = (info: PerfRenderer[\"info\"]) =>\n info.memory.programs ?? info.programs?.length ?? 0;\n\n/**\n * Collector of three.js frame metrics. Bracket each frame with {@link begin}/\n * {@link end}, or let {@link wrapAnimationLoop} or the R3F `PerfSampler` do it.\n * Works on WebGPURenderer (drawCalls, memory.programs, timestamp queries) and\n * on a WebGL renderer (calls, programs[], timer-query ext).\n *\n * While attached, `renderer.info.autoReset` is off and `info` is reset in\n * {@link begin}; host code that reads `renderer.info` mid-frame sees this\n * frame's counts only. {@link dispose} restores both.\n */\nclass PerformanceMonitor {\n private readonly renderer: PerfRenderer;\n private readonly gpuTimer: GpuTimer | null;\n private readonly originalRender: PerfRenderer[\"render\"];\n private renderPasses = 0;\n private readonly fpsHistory: RingBuffer;\n private readonly cpuHistory: RingBuffer;\n private readonly gpuHistory: RingBuffer;\n private cpuStart = 0;\n private lastBeginAt: number | null = null;\n private frameFps = 0;\n private smoothedFps = 0;\n private sample: Sample;\n\n constructor(options: PerformanceMonitorOptions) {\n const {\n renderer,\n trackGPU = true,\n gpuQueryPoolSize = DEFAULT_GPU_POOL_SIZE,\n historySize = DEFAULT_HISTORY_SIZE,\n } = options;\n\n if (ATTACHED.has(renderer)) {\n throw new Error(\n \"three-meter: a PerformanceMonitor is already attached to this renderer. dispose() it first.\",\n );\n }\n\n ATTACHED.add(renderer);\n this.renderer = renderer;\n renderer.info.autoReset = false;\n this.originalRender = renderer.render;\n\n renderer.render = (...args: never[]) => {\n this.renderPasses += 1;\n\n return this.originalRender.apply(renderer, args);\n };\n\n this.gpuTimer = trackGPU ? GpuTimer.create(renderer, gpuQueryPoolSize) : null;\n this.fpsHistory = new RingBuffer(historySize);\n this.cpuHistory = new RingBuffer(historySize);\n this.gpuHistory = new RingBuffer(historySize);\n this.sample = this.buildEmptySample();\n }\n\n begin() {\n const now = performance.now();\n\n if (this.lastBeginAt !== null) {\n const frameMs = now - this.lastBeginAt;\n this.frameFps = frameMs > 0 ? 1000 / frameMs : 0;\n }\n\n this.lastBeginAt = now;\n this.cpuStart = now;\n this.renderPasses = 0;\n this.renderer.info.reset();\n this.gpuTimer?.begin();\n }\n\n end() {\n const now = performance.now();\n const cpu = now - this.cpuStart;\n\n this.gpuTimer?.end();\n this.gpuTimer?.poll();\n\n const gpuMs = this.gpuTimer?.getMs() ?? 0;\n\n this.smoothedFps =\n this.smoothedFps === 0\n ? this.frameFps\n : this.smoothedFps + (this.frameFps - this.smoothedFps) * FPS_SMOOTHING;\n\n this.fpsHistory.push(this.frameFps);\n this.cpuHistory.push(cpu);\n this.gpuHistory.push(gpuMs);\n\n const info = this.renderer.info;\n\n this.sample = Object.freeze({\n cpu,\n fps: this.smoothedFps,\n gpu: { available: this.gpuTimer !== null, ms: gpuMs },\n render: {\n calls: frameCalls(info),\n lines: info.render.lines,\n passes: this.renderPasses,\n points: info.render.points,\n triangles: info.render.triangles,\n },\n resources: {\n geometries: info.memory.geometries,\n programs: programCount(info),\n textures: info.memory.textures,\n },\n });\n }\n\n getSample(): Sample {\n return this.sample;\n }\n\n getHistory(metric: TimingMetric): readonly number[] {\n switch (metric) {\n case \"fps\":\n return this.fpsHistory.toArray();\n case \"cpu\":\n return this.cpuHistory.toArray();\n case \"gpu\":\n return this.gpuHistory.toArray();\n }\n }\n\n dispose() {\n ATTACHED.delete(this.renderer);\n this.renderer.info.autoReset = true;\n this.renderer.render = this.originalRender;\n this.gpuTimer?.dispose();\n this.fpsHistory.clear();\n this.cpuHistory.clear();\n this.gpuHistory.clear();\n }\n\n private buildEmptySample(): Sample {\n return Object.freeze({\n cpu: 0,\n fps: 0,\n gpu: { available: this.gpuTimer !== null, ms: 0 },\n render: { calls: 0, lines: 0, passes: 0, points: 0, triangles: 0 },\n resources: { geometries: 0, programs: 0, textures: 0 },\n });\n }\n}\n\nexport { PerformanceMonitor };\n"],"mappings":"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"}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { i as PerformanceMonitorOptions, t as PerformanceMonitor } from "./performance-monitor-WGLWbxOZ.js";
|
|
2
|
+
import { t as MountPerfHudOptions } from "./mount-perf-hud-Dw8KNQTt.js";
|
|
3
|
+
//#region src/react/monitor-store.d.ts
|
|
4
|
+
type MonitorListener = (monitor: PerformanceMonitor | null) => void;
|
|
5
|
+
type PerfMonitorStore = {
|
|
6
|
+
get: () => PerformanceMonitor | null;
|
|
7
|
+
set: (monitor: PerformanceMonitor | null) => void;
|
|
8
|
+
subscribe: (listener: MonitorListener) => () => void;
|
|
9
|
+
};
|
|
10
|
+
declare const createPerfMonitorStore: () => PerfMonitorStore;
|
|
11
|
+
declare const defaultPerfMonitorStore: PerfMonitorStore;
|
|
12
|
+
//#endregion
|
|
13
|
+
//#region src/react/perf-hud.d.ts
|
|
14
|
+
type PerfHudProps = Omit<MountPerfHudOptions, "settings"> & {
|
|
15
|
+
/** Must match the `PerfSampler`'s. Default: the shared store. */
|
|
16
|
+
store?: PerfMonitorStore;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup
|
|
20
|
+
* as three objects. Mounts the dockable HUD whenever a `PerfSampler` has
|
|
21
|
+
* published a monitor and tears it down when that sampler unmounts.
|
|
22
|
+
*/
|
|
23
|
+
declare function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store }: PerfHudProps): null;
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/react/perf-sampler.d.ts
|
|
26
|
+
type PerfSamplerProps = Omit<PerformanceMonitorOptions, "renderer"> & {
|
|
27
|
+
/** Where the monitor is published for `PerfHud`. Default: the shared store. */
|
|
28
|
+
store?: PerfMonitorStore;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* In-canvas collector. Renders nothing. Fiber runs every `useFrame` before
|
|
32
|
+
* `gl.render`, so the previous frame is closed here and the next one opens, and
|
|
33
|
+
* GPU queries then span the render that just ran. The monitor is created in
|
|
34
|
+
* an effect so StrictMode's double mount attaches and detaches cleanly.
|
|
35
|
+
*/
|
|
36
|
+
declare function PerfSampler({ gpuQueryPoolSize, historySize, store, trackGPU }: PerfSamplerProps): null;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { PerfHud, type PerfHudProps, type PerfMonitorStore, PerfSampler, type PerfSamplerProps, createPerfMonitorStore, defaultPerfMonitorStore };
|
|
39
|
+
//# sourceMappingURL=react.d.ts.map
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { t as PerformanceMonitor } from "./performance-monitor-qswCj9Nl.js";
|
|
2
|
+
import { t as mountPerfHud } from "./mount-perf-hud-CduRNym5.js";
|
|
3
|
+
import { useEffect, useRef } from "react";
|
|
4
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
5
|
+
//#region src/react/monitor-store.ts
|
|
6
|
+
const createPerfMonitorStore = () => {
|
|
7
|
+
let current = null;
|
|
8
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
9
|
+
return {
|
|
10
|
+
get: () => current,
|
|
11
|
+
set: (monitor) => {
|
|
12
|
+
current = monitor;
|
|
13
|
+
for (const listener of listeners) listener(monitor);
|
|
14
|
+
},
|
|
15
|
+
subscribe: (listener) => {
|
|
16
|
+
listeners.add(listener);
|
|
17
|
+
return () => {
|
|
18
|
+
listeners.delete(listener);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
const defaultPerfMonitorStore = createPerfMonitorStore();
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/react/perf-hud.tsx
|
|
26
|
+
/**
|
|
27
|
+
* DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup
|
|
28
|
+
* as three objects. Mounts the dockable HUD whenever a `PerfSampler` has
|
|
29
|
+
* published a monitor and tears it down when that sampler unmounts.
|
|
30
|
+
*/
|
|
31
|
+
function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store }) {
|
|
32
|
+
const source = store ?? defaultPerfMonitorStore;
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
let dispose = null;
|
|
35
|
+
const attach = (monitor) => {
|
|
36
|
+
dispose?.();
|
|
37
|
+
dispose = null;
|
|
38
|
+
if (!monitor) return;
|
|
39
|
+
dispose = mountPerfHud(monitor, {
|
|
40
|
+
defaultPlacement,
|
|
41
|
+
injectStyles,
|
|
42
|
+
label,
|
|
43
|
+
mode,
|
|
44
|
+
parent,
|
|
45
|
+
refreshHz,
|
|
46
|
+
storageKey
|
|
47
|
+
}).dispose;
|
|
48
|
+
};
|
|
49
|
+
const unsubscribe = source.subscribe(attach);
|
|
50
|
+
attach(source.get());
|
|
51
|
+
return () => {
|
|
52
|
+
unsubscribe();
|
|
53
|
+
dispose?.();
|
|
54
|
+
};
|
|
55
|
+
}, [
|
|
56
|
+
defaultPlacement,
|
|
57
|
+
injectStyles,
|
|
58
|
+
label,
|
|
59
|
+
mode,
|
|
60
|
+
parent,
|
|
61
|
+
refreshHz,
|
|
62
|
+
source,
|
|
63
|
+
storageKey
|
|
64
|
+
]);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/react/perf-sampler.tsx
|
|
69
|
+
/**
|
|
70
|
+
* In-canvas collector. Renders nothing. Fiber runs every `useFrame` before
|
|
71
|
+
* `gl.render`, so the previous frame is closed here and the next one opens, and
|
|
72
|
+
* GPU queries then span the render that just ran. The monitor is created in
|
|
73
|
+
* an effect so StrictMode's double mount attaches and detaches cleanly.
|
|
74
|
+
*/
|
|
75
|
+
function PerfSampler({ gpuQueryPoolSize, historySize, store, trackGPU }) {
|
|
76
|
+
const renderer = useThree((state) => state.gl);
|
|
77
|
+
const monitorRef = useRef(null);
|
|
78
|
+
const startedRef = useRef(false);
|
|
79
|
+
const target = store ?? defaultPerfMonitorStore;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
const monitor = new PerformanceMonitor({
|
|
82
|
+
gpuQueryPoolSize,
|
|
83
|
+
historySize,
|
|
84
|
+
renderer,
|
|
85
|
+
trackGPU
|
|
86
|
+
});
|
|
87
|
+
monitorRef.current = monitor;
|
|
88
|
+
startedRef.current = false;
|
|
89
|
+
target.set(monitor);
|
|
90
|
+
return () => {
|
|
91
|
+
target.set(null);
|
|
92
|
+
monitorRef.current = null;
|
|
93
|
+
monitor.dispose();
|
|
94
|
+
};
|
|
95
|
+
}, [
|
|
96
|
+
gpuQueryPoolSize,
|
|
97
|
+
historySize,
|
|
98
|
+
renderer,
|
|
99
|
+
target,
|
|
100
|
+
trackGPU
|
|
101
|
+
]);
|
|
102
|
+
useFrame(() => {
|
|
103
|
+
const monitor = monitorRef.current;
|
|
104
|
+
if (!monitor) return;
|
|
105
|
+
if (startedRef.current) monitor.end();
|
|
106
|
+
startedRef.current = true;
|
|
107
|
+
monitor.begin();
|
|
108
|
+
});
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
//#endregion
|
|
112
|
+
export { PerfHud, PerfSampler, createPerfMonitorStore, defaultPerfMonitorStore };
|
|
113
|
+
|
|
114
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":[],"sources":["../src/react/monitor-store.ts","../src/react/perf-hud.tsx","../src/react/perf-sampler.tsx"],"sourcesContent":["/**\n * Hands the in-canvas monitor to the DOM-side HUD. A React context can't do\n * it: R3F's `<Canvas>` is its own reconciler root, so a provider outside it\n * isn't visible inside without a bridge. A tiny external store crosses that\n * boundary for free. One default store; pass your own to both components to\n * run two canvases on one page.\n */\nimport type { PerformanceMonitor } from \"../core/performance-monitor.ts\";\n\ntype MonitorListener = (monitor: PerformanceMonitor | null) => void;\n\ntype PerfMonitorStore = {\n get: () => PerformanceMonitor | null;\n set: (monitor: PerformanceMonitor | null) => void;\n subscribe: (listener: MonitorListener) => () => void;\n};\n\nconst createPerfMonitorStore = (): PerfMonitorStore => {\n let current: PerformanceMonitor | null = null;\n const listeners = new Set<MonitorListener>();\n\n return {\n get: () => current,\n set: (monitor) => {\n current = monitor;\n\n for (const listener of listeners) {\n listener(monitor);\n }\n },\n subscribe: (listener) => {\n listeners.add(listener);\n\n return () => {\n listeners.delete(listener);\n };\n },\n };\n};\n\nconst defaultPerfMonitorStore = createPerfMonitorStore();\n\nexport { createPerfMonitorStore, defaultPerfMonitorStore };\nexport type { PerfMonitorStore };\n","import { useEffect } from \"react\";\n\nimport { mountPerfHud, type MountPerfHudOptions } from \"../ui/mount-perf-hud.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfHudProps = Omit<MountPerfHudOptions, \"settings\"> & {\n /** Must match the `PerfSampler`'s. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup\n * as three objects. Mounts the dockable HUD whenever a `PerfSampler` has\n * published a monitor and tears it down when that sampler unmounts.\n */\nfunction PerfHud({\n defaultPlacement,\n injectStyles,\n label,\n mode,\n parent,\n refreshHz,\n storageKey,\n store,\n}: PerfHudProps) {\n const source = store ?? defaultPerfMonitorStore;\n\n useEffect(() => {\n let dispose: (() => void) | null = null;\n\n const attach = (monitor: ReturnType<PerfMonitorStore[\"get\"]>) => {\n dispose?.();\n dispose = null;\n\n if (!monitor) {\n return;\n }\n\n dispose = mountPerfHud(monitor, {\n defaultPlacement,\n injectStyles,\n label,\n mode,\n parent,\n refreshHz,\n storageKey,\n }).dispose;\n };\n\n const unsubscribe = source.subscribe(attach);\n attach(source.get());\n\n return () => {\n unsubscribe();\n dispose?.();\n };\n }, [defaultPlacement, injectStyles, label, mode, parent, refreshHz, source, storageKey]);\n\n return null;\n}\n\nexport { PerfHud };\nexport type { PerfHudProps };\n","import { useFrame, useThree } from \"@react-three/fiber\";\nimport { useEffect, useRef } from \"react\";\n\nimport { PerformanceMonitor } from \"../core/performance-monitor.ts\";\nimport type { PerformanceMonitorOptions, PerfRenderer } from \"../core/types.ts\";\nimport { defaultPerfMonitorStore, type PerfMonitorStore } from \"./monitor-store.ts\";\n\ntype PerfSamplerProps = Omit<PerformanceMonitorOptions, \"renderer\"> & {\n /** Where the monitor is published for `PerfHud`. Default: the shared store. */\n store?: PerfMonitorStore;\n};\n\n/**\n * In-canvas collector. Renders nothing. Fiber runs every `useFrame` before\n * `gl.render`, so the previous frame is closed here and the next one opens, and\n * GPU queries then span the render that just ran. The monitor is created in\n * an effect so StrictMode's double mount attaches and detaches cleanly.\n */\nfunction PerfSampler({ gpuQueryPoolSize, historySize, store, trackGPU }: PerfSamplerProps) {\n const renderer = useThree((state) => state.gl);\n const monitorRef = useRef<PerformanceMonitor | null>(null);\n const startedRef = useRef(false);\n const target = store ?? defaultPerfMonitorStore;\n\n useEffect(() => {\n const monitor = new PerformanceMonitor({\n gpuQueryPoolSize,\n historySize,\n renderer: renderer as unknown as PerfRenderer,\n trackGPU,\n });\n monitorRef.current = monitor;\n startedRef.current = false;\n target.set(monitor);\n\n return () => {\n target.set(null);\n monitorRef.current = null;\n monitor.dispose();\n };\n }, [gpuQueryPoolSize, historySize, renderer, target, trackGPU]);\n\n useFrame(() => {\n const monitor = monitorRef.current;\n\n if (!monitor) {\n return;\n }\n\n if (startedRef.current) {\n monitor.end();\n }\n\n startedRef.current = true;\n monitor.begin();\n });\n\n return null;\n}\n\nexport { PerfSampler };\nexport type { PerfSamplerProps };\n"],"mappings":";;;;;AAiBA,MAAM,+BAAiD;CACrD,IAAI,UAAqC;CACzC,MAAM,4BAAY,IAAI,IAAqB;CAE3C,OAAO;EACL,WAAW;EACX,MAAM,YAAY;GAChB,UAAU;GAEV,KAAK,MAAM,YAAY,WACrB,SAAS,OAAO;EAEpB;EACA,YAAY,aAAa;GACvB,UAAU,IAAI,QAAQ;GAEtB,aAAa;IACX,UAAU,OAAO,QAAQ;GAC3B;EACF;CACF;AACF;AAEA,MAAM,0BAA0B,uBAAuB;;;;;;;;ACzBvD,SAAS,QAAQ,EACf,kBACA,cACA,OACA,MACA,QACA,WACA,YACA,SACe;CACf,MAAM,SAAS,SAAS;CAExB,gBAAgB;EACd,IAAI,UAA+B;EAEnC,MAAM,UAAU,YAAiD;GAC/D,UAAU;GACV,UAAU;GAEV,IAAI,CAAC,SACH;GAGF,UAAU,aAAa,SAAS;IAC9B;IACA;IACA;IACA;IACA;IACA;IACA;GACF,CAAC,CAAC,CAAC;EACL;EAEA,MAAM,cAAc,OAAO,UAAU,MAAM;EAC3C,OAAO,OAAO,IAAI,CAAC;EAEnB,aAAa;GACX,YAAY;GACZ,UAAU;EACZ;CACF,GAAG;EAAC;EAAkB;EAAc;EAAO;EAAM;EAAQ;EAAW;EAAQ;CAAU,CAAC;CAEvF,OAAO;AACT;;;;;;;;;ACzCA,SAAS,YAAY,EAAE,kBAAkB,aAAa,OAAO,YAA8B;CACzF,MAAM,WAAW,UAAU,UAAU,MAAM,EAAE;CAC7C,MAAM,aAAa,OAAkC,IAAI;CACzD,MAAM,aAAa,OAAO,KAAK;CAC/B,MAAM,SAAS,SAAS;CAExB,gBAAgB;EACd,MAAM,UAAU,IAAI,mBAAmB;GACrC;GACA;GACU;GACV;EACF,CAAC;EACD,WAAW,UAAU;EACrB,WAAW,UAAU;EACrB,OAAO,IAAI,OAAO;EAElB,aAAa;GACX,OAAO,IAAI,IAAI;GACf,WAAW,UAAU;GACrB,QAAQ,QAAQ;EAClB;CACF,GAAG;EAAC;EAAkB;EAAa;EAAU;EAAQ;CAAQ,CAAC;CAE9D,eAAe;EACb,MAAM,UAAU,WAAW;EAE3B,IAAI,CAAC,SACH;EAGF,IAAI,WAAW,SACb,QAAQ,IAAI;EAGd,WAAW,UAAU;EACrB,QAAQ,MAAM;CAChB,CAAC;CAED,OAAO;AACT"}
|