@zkmake/three-meter 0.3.1 → 0.4.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/README.md +16 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +11 -8
- package/dist/index.js.map +1 -1
- package/dist/{mount-perf-hud-CX9DrxjA.js → mount-perf-hud-Rf98QNxM.js} +162 -3
- package/dist/mount-perf-hud-Rf98QNxM.js.map +1 -0
- package/dist/{mount-perf-hud-B3wBLrcu.d.ts → mount-perf-hud-v6kLGJUX.d.ts} +16 -2
- package/dist/{performance-monitor-WGLWbxOZ.d.ts → performance-monitor-Br7x3cPt.d.ts} +34 -3
- package/dist/{performance-monitor-qswCj9Nl.js → performance-monitor-C8dhWJWR.js} +126 -4
- package/dist/performance-monitor-C8dhWJWR.js.map +1 -0
- package/dist/react.d.ts +8 -4
- package/dist/react.js +28 -10
- package/dist/react.js.map +1 -1
- package/dist/styles.css +76 -1
- package/dist/ui.d.ts +2 -2
- package/dist/ui.js +1 -1
- package/package.json +1 -1
- package/dist/mount-perf-hud-CX9DrxjA.js.map +0 -1
- package/dist/performance-monitor-qswCj9Nl.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { t as PerformanceMonitor, u as TimingMetric } from "./performance-monitor-Br7x3cPt.js";
|
|
2
2
|
//#region src/ui/dock-panel.d.ts
|
|
3
3
|
declare const EDGES: readonly ["left", "right", "top", "bottom"];
|
|
4
4
|
type ScreenEdge = (typeof EDGES)[number];
|
|
@@ -82,12 +82,15 @@ declare class HudTheme {
|
|
|
82
82
|
type HudSelection = {
|
|
83
83
|
dim: boolean;
|
|
84
84
|
graphs: ReadonlySet<TimingMetric>;
|
|
85
|
+
/** Version, three revision, backend and GPU footer in the compact HUD. */
|
|
86
|
+
info: boolean;
|
|
85
87
|
numbers: ReadonlySet<string>;
|
|
86
88
|
theme: ThemeMode | null;
|
|
87
89
|
};
|
|
88
90
|
type HudDefaults = {
|
|
89
91
|
dim?: boolean;
|
|
90
92
|
graphs?: readonly TimingMetric[];
|
|
93
|
+
info?: boolean;
|
|
91
94
|
numbers?: readonly string[];
|
|
92
95
|
};
|
|
93
96
|
type HudSettingsOptions = {
|
|
@@ -106,6 +109,8 @@ declare class HudSettings {
|
|
|
106
109
|
get selection(): HudSelection;
|
|
107
110
|
get dim(): boolean;
|
|
108
111
|
setDim(enabled: boolean): void;
|
|
112
|
+
get info(): boolean;
|
|
113
|
+
setInfo(enabled: boolean): void;
|
|
109
114
|
/** The user's pick in the panel; `null` means none, so the consumer's option applies. */
|
|
110
115
|
get theme(): ThemeMode | null;
|
|
111
116
|
setTheme(mode: ThemeMode | null): void;
|
|
@@ -155,7 +160,13 @@ declare class PerformanceView {
|
|
|
155
160
|
private readonly statValueEls;
|
|
156
161
|
private readonly statCheckboxes;
|
|
157
162
|
private dimCheckbox;
|
|
163
|
+
private infoCheckbox;
|
|
158
164
|
private readonly themeRadios;
|
|
165
|
+
private footerThreeEl;
|
|
166
|
+
private footerHardwareEl;
|
|
167
|
+
private footerBackendEl;
|
|
168
|
+
private footerGpuEl;
|
|
169
|
+
private lastEnvironmentKey;
|
|
159
170
|
private hudGraphsEl;
|
|
160
171
|
private hudGridEl;
|
|
161
172
|
private readonly hudGraphs;
|
|
@@ -172,6 +183,8 @@ declare class PerformanceView {
|
|
|
172
183
|
dispose(): void;
|
|
173
184
|
private frame;
|
|
174
185
|
private render;
|
|
186
|
+
/** The monitor caches the environment once its backend settles; until then this re-reads. */
|
|
187
|
+
private renderFooter;
|
|
175
188
|
private renderHud;
|
|
176
189
|
private formatTiming;
|
|
177
190
|
private build;
|
|
@@ -180,6 +193,7 @@ declare class PerformanceView {
|
|
|
180
193
|
/** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
|
|
181
194
|
private onThemeChanged;
|
|
182
195
|
private buildCheckbox;
|
|
196
|
+
private applyInfoClass;
|
|
183
197
|
private applyModeClass;
|
|
184
198
|
private resizeCanvases;
|
|
185
199
|
}
|
|
@@ -230,4 +244,4 @@ type PerfHudHandle = {
|
|
|
230
244
|
declare const mountPerfHud: (monitor: PerformanceMonitor, options?: MountPerfHudOptions) => PerfHudHandle;
|
|
231
245
|
//#endregion
|
|
232
246
|
export { dockPanel as C, ScreenEdge as S, DefaultPlacement as _, PerformanceViewMode as a, DockPanelOptions as b, HudDefaults as c, HudSettingsOptions as d, HudTheme as f, isThemeMode as g, ThemeMode as h, PerformanceView as i, HudSelection as l, THEME_MODES as m, PerfHudHandle as n, PerformanceViewOptions as o, ResolvedTheme as p, mountPerfHud as r, DEFAULT_STORAGE_KEY as s, MountPerfHudOptions as t, HudSettings as u, DockAlign as v, Placement as x, DockHandle as y };
|
|
233
|
-
//# sourceMappingURL=mount-perf-hud-
|
|
247
|
+
//# sourceMappingURL=mount-perf-hud-v6kLGJUX.d.ts.map
|
|
@@ -24,13 +24,31 @@ type ResourceCounts = {
|
|
|
24
24
|
programs: number;
|
|
25
25
|
};
|
|
26
26
|
type Sample = {
|
|
27
|
+
/** Smoothed. Intervals over a second (hidden tab, breakpoint) are left out. */
|
|
27
28
|
fps: number;
|
|
28
|
-
/**
|
|
29
|
+
/**
|
|
30
|
+
* JS time in ms from `begin()` to the return of the frame's last `render()`:
|
|
31
|
+
* your update code plus the render dispatch. Falls back to `end()` when no
|
|
32
|
+
* render ran.
|
|
33
|
+
*/
|
|
29
34
|
cpu: number;
|
|
30
35
|
gpu: GpuTiming;
|
|
31
36
|
render: RenderCounts;
|
|
32
37
|
resources: ResourceCounts;
|
|
33
38
|
};
|
|
39
|
+
/** `webgl` is WebGL 1, which three dropped in r163. */
|
|
40
|
+
type RenderBackend = "webgpu" | "webgl2" | "webgl";
|
|
41
|
+
/** What the monitor is running on, for bug reports. `null` where it can't be read. */
|
|
42
|
+
type Environment = {
|
|
43
|
+
/** `null` until `WebGPURenderer` finishes `init()`. */
|
|
44
|
+
backend: RenderBackend | null;
|
|
45
|
+
/** `WebGPURenderer` asked for WebGPU and fell back to its WebGL2 backend. */
|
|
46
|
+
fallback: boolean;
|
|
47
|
+
/** Adapter or unmasked renderer name, e.g. `Apple M3 Pro`. Browsers may hide it. */
|
|
48
|
+
gpu: string | null;
|
|
49
|
+
/** three's revision from `window.__THREE__`, e.g. `"186"`. */
|
|
50
|
+
three: string | null;
|
|
51
|
+
};
|
|
34
52
|
/**
|
|
35
53
|
* The slice of a three renderer the monitor needs. WebGPU's `info.render` uses
|
|
36
54
|
* `drawCalls` for this frame; WebGL's uses `calls`. `programs` is a cache array
|
|
@@ -84,12 +102,18 @@ type PerformanceMonitorOptions = {
|
|
|
84
102
|
* While attached, `renderer.info.autoReset` is off and `info` is reset in
|
|
85
103
|
* {@link begin}; host code that reads `renderer.info` mid-frame sees this
|
|
86
104
|
* frame's counts only. {@link dispose} restores both.
|
|
105
|
+
*
|
|
106
|
+
* CPU time runs from {@link begin} to the return of the frame's last
|
|
107
|
+
* `render()` call, which the `render` patch stamps. So it holds even when
|
|
108
|
+
* {@link end} is only reached on the next tick, as in the R3F sampler: it is
|
|
109
|
+
* the JS work of the frame, not the interval to the next one.
|
|
87
110
|
*/
|
|
88
111
|
declare class PerformanceMonitor {
|
|
89
112
|
private readonly renderer;
|
|
90
113
|
private readonly gpuTimer;
|
|
91
114
|
private readonly originalRender;
|
|
92
115
|
private renderPasses;
|
|
116
|
+
private lastRenderEndAt;
|
|
93
117
|
private readonly fpsHistory;
|
|
94
118
|
private readonly cpuHistory;
|
|
95
119
|
private readonly gpuHistory;
|
|
@@ -98,14 +122,21 @@ declare class PerformanceMonitor {
|
|
|
98
122
|
private frameFps;
|
|
99
123
|
private smoothedFps;
|
|
100
124
|
private sample;
|
|
125
|
+
private environment;
|
|
101
126
|
constructor(options: PerformanceMonitorOptions);
|
|
102
127
|
begin(): void;
|
|
103
128
|
end(): void;
|
|
104
129
|
getSample(): Sample;
|
|
105
130
|
getHistory(metric: TimingMetric): readonly number[];
|
|
131
|
+
/**
|
|
132
|
+
* three revision, backend and GPU name. Cached once the backend is known;
|
|
133
|
+
* before `WebGPURenderer.init()` settles it, `backend` is `null` and the
|
|
134
|
+
* next call reads again.
|
|
135
|
+
*/
|
|
136
|
+
getEnvironment(): Environment;
|
|
106
137
|
dispose(): void;
|
|
107
138
|
private buildEmptySample;
|
|
108
139
|
}
|
|
109
140
|
//#endregion
|
|
110
|
-
export {
|
|
111
|
-
//# sourceMappingURL=performance-monitor-
|
|
141
|
+
export { PerformanceMonitorOptions as a, ResourceCounts as c, PerfRenderer as i, Sample as l, Environment as n, RenderBackend as o, GpuTiming as r, RenderCounts as s, PerformanceMonitor as t, TimingMetric as u };
|
|
142
|
+
//# sourceMappingURL=performance-monitor-Br7x3cPt.d.ts.map
|
|
@@ -1,3 +1,95 @@
|
|
|
1
|
+
//#region src/core/environment.ts
|
|
2
|
+
/** three sets `window.__THREE__` to its revision on import (and warns on a second copy). */
|
|
3
|
+
const readThreeRevision = () => {
|
|
4
|
+
const revision = globalThis.__THREE__;
|
|
5
|
+
return typeof revision === "string" && revision !== "" ? revision : null;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Trim a WebGL renderer string to the part a person recognises. Chrome wraps
|
|
9
|
+
* it in ANGLE, e.g. `ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 (0x00002786)
|
|
10
|
+
* Direct3D11 vs_5_0 ps_5_0, D3D11)` or `ANGLE (Apple, ANGLE Metal Renderer:
|
|
11
|
+
* Apple M3 Pro, Unspecified Version)`.
|
|
12
|
+
*/
|
|
13
|
+
const cleanGpuName = (raw) => {
|
|
14
|
+
let name = raw.trim();
|
|
15
|
+
const angle = /^ANGLE \((.*)\)$/.exec(name);
|
|
16
|
+
if (angle) {
|
|
17
|
+
const parts = angle[1].split(", ");
|
|
18
|
+
name = parts[1] ?? parts[0];
|
|
19
|
+
}
|
|
20
|
+
return name.replace(/^ANGLE [\w ]+ Renderer: /, "").replace(/ \(0x[0-9a-f]+\)/i, "").replace(/ (Direct3D\d*|OpenGL|Vulkan|Metal)\b.*$/, "").trim();
|
|
21
|
+
};
|
|
22
|
+
const webglGpuName = (gl) => {
|
|
23
|
+
const ext = gl.getExtension("WEBGL_debug_renderer_info");
|
|
24
|
+
const raw = gl.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : gl.RENDERER);
|
|
25
|
+
return typeof raw === "string" && raw !== "" && raw !== "WebKit WebGL" ? cleanGpuName(raw) : null;
|
|
26
|
+
};
|
|
27
|
+
/** WebGPU reports vendors lowercase (`apple`, `nvidia`). */
|
|
28
|
+
const VENDOR_NAMES = {
|
|
29
|
+
amd: "AMD",
|
|
30
|
+
apple: "Apple",
|
|
31
|
+
arm: "Arm",
|
|
32
|
+
intel: "Intel",
|
|
33
|
+
nvidia: "NVIDIA",
|
|
34
|
+
qualcomm: "Qualcomm"
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* `GPUDevice.adapterInfo`. Chrome leaves `description` empty to limit
|
|
38
|
+
* fingerprinting, so this is usually vendor + architecture: `Apple metal-3`.
|
|
39
|
+
*/
|
|
40
|
+
const webgpuGpuName = (info) => {
|
|
41
|
+
if (!info) return null;
|
|
42
|
+
if (info.description) return info.description;
|
|
43
|
+
const name = [info.vendor ? VENDOR_NAMES[info.vendor] ?? info.vendor : "", info.architecture].filter(Boolean).join(" ");
|
|
44
|
+
return name === "" ? null : name;
|
|
45
|
+
};
|
|
46
|
+
const isWebglContext = (value) => typeof WebGL2RenderingContext !== "undefined" && value instanceof WebGL2RenderingContext || typeof WebGLRenderingContext !== "undefined" && value instanceof WebGLRenderingContext;
|
|
47
|
+
const webglBackend = (gl) => typeof WebGL2RenderingContext !== "undefined" && gl instanceof WebGL2RenderingContext ? "webgl2" : "webgl";
|
|
48
|
+
const readEnvironment = (renderer) => {
|
|
49
|
+
const three = readThreeRevision();
|
|
50
|
+
const backend = renderer.backend;
|
|
51
|
+
if (backend) {
|
|
52
|
+
if (backend.isWebGPUBackend) {
|
|
53
|
+
const device = backend.device;
|
|
54
|
+
return device ? {
|
|
55
|
+
backend: "webgpu",
|
|
56
|
+
fallback: false,
|
|
57
|
+
gpu: webgpuGpuName(device.adapterInfo),
|
|
58
|
+
three
|
|
59
|
+
} : {
|
|
60
|
+
backend: null,
|
|
61
|
+
fallback: false,
|
|
62
|
+
gpu: null,
|
|
63
|
+
three
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
if (backend.isWebGLBackend && isWebglContext(backend.gl)) return {
|
|
67
|
+
backend: webglBackend(backend.gl),
|
|
68
|
+
fallback: backend.parameters?.forceWebGL !== true,
|
|
69
|
+
gpu: webglGpuName(backend.gl),
|
|
70
|
+
three
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
backend: null,
|
|
74
|
+
fallback: false,
|
|
75
|
+
gpu: null,
|
|
76
|
+
three
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const context = renderer.getContext?.();
|
|
80
|
+
return isWebglContext(context) ? {
|
|
81
|
+
backend: webglBackend(context),
|
|
82
|
+
fallback: false,
|
|
83
|
+
gpu: webglGpuName(context),
|
|
84
|
+
three
|
|
85
|
+
} : {
|
|
86
|
+
backend: null,
|
|
87
|
+
fallback: false,
|
|
88
|
+
gpu: null,
|
|
89
|
+
three
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
//#endregion
|
|
1
93
|
//#region src/core/gpu-timer.ts
|
|
2
94
|
var GpuTimer = class GpuTimer {
|
|
3
95
|
lastMs = 0;
|
|
@@ -130,6 +222,12 @@ const DEFAULT_GPU_POOL_SIZE = 5;
|
|
|
130
222
|
const ATTACHED = /* @__PURE__ */ new WeakSet();
|
|
131
223
|
const DEFAULT_HISTORY_SIZE = 120;
|
|
132
224
|
const FPS_SMOOTHING = .1;
|
|
225
|
+
/**
|
|
226
|
+
* A frame interval past this is a stall (hidden tab, breakpoint, laptop lid),
|
|
227
|
+
* not a slow frame. It is left out of FPS so one gap doesn't flatten the
|
|
228
|
+
* graph's scale for the whole history window.
|
|
229
|
+
*/
|
|
230
|
+
const STALL_MS = 1e3;
|
|
133
231
|
const frameCalls = (info) => info.render.drawCalls ?? info.render.calls;
|
|
134
232
|
const programCount = (info) => info.memory.programs ?? info.programs?.length ?? 0;
|
|
135
233
|
/**
|
|
@@ -141,12 +239,18 @@ const programCount = (info) => info.memory.programs ?? info.programs?.length ??
|
|
|
141
239
|
* While attached, `renderer.info.autoReset` is off and `info` is reset in
|
|
142
240
|
* {@link begin}; host code that reads `renderer.info` mid-frame sees this
|
|
143
241
|
* frame's counts only. {@link dispose} restores both.
|
|
242
|
+
*
|
|
243
|
+
* CPU time runs from {@link begin} to the return of the frame's last
|
|
244
|
+
* `render()` call, which the `render` patch stamps. So it holds even when
|
|
245
|
+
* {@link end} is only reached on the next tick, as in the R3F sampler: it is
|
|
246
|
+
* the JS work of the frame, not the interval to the next one.
|
|
144
247
|
*/
|
|
145
248
|
var PerformanceMonitor = class {
|
|
146
249
|
renderer;
|
|
147
250
|
gpuTimer;
|
|
148
251
|
originalRender;
|
|
149
252
|
renderPasses = 0;
|
|
253
|
+
lastRenderEndAt = null;
|
|
150
254
|
fpsHistory;
|
|
151
255
|
cpuHistory;
|
|
152
256
|
gpuHistory;
|
|
@@ -155,6 +259,7 @@ var PerformanceMonitor = class {
|
|
|
155
259
|
frameFps = 0;
|
|
156
260
|
smoothedFps = 0;
|
|
157
261
|
sample;
|
|
262
|
+
environment = null;
|
|
158
263
|
constructor(options) {
|
|
159
264
|
const { renderer, trackGPU = true, gpuQueryPoolSize = DEFAULT_GPU_POOL_SIZE, historySize = DEFAULT_HISTORY_SIZE } = options;
|
|
160
265
|
if (ATTACHED.has(renderer)) throw new Error("three-meter: a PerformanceMonitor is already attached to this renderer. dispose() it first.");
|
|
@@ -164,7 +269,11 @@ var PerformanceMonitor = class {
|
|
|
164
269
|
this.originalRender = renderer.render;
|
|
165
270
|
renderer.render = (...args) => {
|
|
166
271
|
this.renderPasses += 1;
|
|
167
|
-
|
|
272
|
+
try {
|
|
273
|
+
return this.originalRender.apply(renderer, args);
|
|
274
|
+
} finally {
|
|
275
|
+
this.lastRenderEndAt = performance.now();
|
|
276
|
+
}
|
|
168
277
|
};
|
|
169
278
|
this.gpuTimer = trackGPU ? GpuTimer.create(renderer, gpuQueryPoolSize) : null;
|
|
170
279
|
this.fpsHistory = new RingBuffer(historySize);
|
|
@@ -176,16 +285,18 @@ var PerformanceMonitor = class {
|
|
|
176
285
|
const now = performance.now();
|
|
177
286
|
if (this.lastBeginAt !== null) {
|
|
178
287
|
const frameMs = now - this.lastBeginAt;
|
|
179
|
-
|
|
288
|
+
if (frameMs > 0 && frameMs <= STALL_MS) this.frameFps = 1e3 / frameMs;
|
|
180
289
|
}
|
|
181
290
|
this.lastBeginAt = now;
|
|
182
291
|
this.cpuStart = now;
|
|
292
|
+
this.lastRenderEndAt = null;
|
|
183
293
|
this.renderPasses = 0;
|
|
184
294
|
this.renderer.info.reset();
|
|
185
295
|
this.gpuTimer?.begin();
|
|
186
296
|
}
|
|
187
297
|
end() {
|
|
188
|
-
const
|
|
298
|
+
const now = performance.now();
|
|
299
|
+
const cpu = (this.lastRenderEndAt ?? now) - this.cpuStart;
|
|
189
300
|
this.gpuTimer?.end();
|
|
190
301
|
this.gpuTimer?.poll();
|
|
191
302
|
const gpuMs = this.gpuTimer?.getMs() ?? 0;
|
|
@@ -225,6 +336,17 @@ var PerformanceMonitor = class {
|
|
|
225
336
|
case "gpu": return this.gpuHistory.toArray();
|
|
226
337
|
}
|
|
227
338
|
}
|
|
339
|
+
/**
|
|
340
|
+
* three revision, backend and GPU name. Cached once the backend is known;
|
|
341
|
+
* before `WebGPURenderer.init()` settles it, `backend` is `null` and the
|
|
342
|
+
* next call reads again.
|
|
343
|
+
*/
|
|
344
|
+
getEnvironment() {
|
|
345
|
+
if (this.environment) return this.environment;
|
|
346
|
+
const environment = Object.freeze(readEnvironment(this.renderer));
|
|
347
|
+
if (environment.backend !== null) this.environment = environment;
|
|
348
|
+
return environment;
|
|
349
|
+
}
|
|
228
350
|
dispose() {
|
|
229
351
|
ATTACHED.delete(this.renderer);
|
|
230
352
|
this.renderer.info.autoReset = true;
|
|
@@ -260,4 +382,4 @@ var PerformanceMonitor = class {
|
|
|
260
382
|
//#endregion
|
|
261
383
|
export { RingBuffer as n, PerformanceMonitor as t };
|
|
262
384
|
|
|
263
|
-
//# sourceMappingURL=performance-monitor-
|
|
385
|
+
//# sourceMappingURL=performance-monitor-C8dhWJWR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance-monitor-C8dhWJWR.js","names":[],"sources":["../src/core/environment.ts","../src/core/gpu-timer.ts","../src/core/ring-buffer.ts","../src/core/performance-monitor.ts"],"sourcesContent":["/**\n * What the monitor is running on: three revision, rendering backend, GPU.\n * Read from the renderer and the page, never from an import of three.\n *\n * `WebGPURenderer` settles its backend in `init()`, so until then the backend\n * reads `null`; callers poll and the monitor caches once it is known.\n */\nimport type { Environment, PerfRenderer, RenderBackend } from \"./types.ts\";\n\ntype BackendShape = {\n device?: { adapterInfo?: AdapterInfoShape } | null;\n gl?: unknown;\n isWebGLBackend?: boolean;\n isWebGPUBackend?: boolean;\n parameters?: { forceWebGL?: boolean };\n};\n\ntype AdapterInfoShape = {\n architecture?: string;\n description?: string;\n device?: string;\n vendor?: string;\n};\n\ntype DebugRendererInfoExt = { readonly UNMASKED_RENDERER_WEBGL: number };\n\n/** three sets `window.__THREE__` to its revision on import (and warns on a second copy). */\nconst readThreeRevision = (): string | null => {\n const revision = (globalThis as { __THREE__?: unknown }).__THREE__;\n\n return typeof revision === \"string\" && revision !== \"\" ? revision : null;\n};\n\n/**\n * Trim a WebGL renderer string to the part a person recognises. Chrome wraps\n * it in ANGLE, e.g. `ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 (0x00002786)\n * Direct3D11 vs_5_0 ps_5_0, D3D11)` or `ANGLE (Apple, ANGLE Metal Renderer:\n * Apple M3 Pro, Unspecified Version)`.\n */\nconst cleanGpuName = (raw: string): string => {\n let name = raw.trim();\n const angle = /^ANGLE \\((.*)\\)$/.exec(name);\n\n if (angle) {\n const parts = angle[1]!.split(\", \");\n name = parts[1] ?? parts[0]!;\n }\n\n return name\n .replace(/^ANGLE [\\w ]+ Renderer: /, \"\")\n .replace(/ \\(0x[0-9a-f]+\\)/i, \"\")\n .replace(/ (Direct3D\\d*|OpenGL|Vulkan|Metal)\\b.*$/, \"\")\n .trim();\n};\n\nconst webglGpuName = (gl: WebGLRenderingContext | WebGL2RenderingContext): string | null => {\n // Firefox deprecates the extension but already returns the unmasked name from RENDERER.\n const ext = gl.getExtension(\"WEBGL_debug_renderer_info\") as DebugRendererInfoExt | null;\n const raw = gl.getParameter(ext ? ext.UNMASKED_RENDERER_WEBGL : gl.RENDERER) as unknown;\n\n return typeof raw === \"string\" && raw !== \"\" && raw !== \"WebKit WebGL\" ? cleanGpuName(raw) : null;\n};\n\n/** WebGPU reports vendors lowercase (`apple`, `nvidia`). */\nconst VENDOR_NAMES: Record<string, string> = {\n amd: \"AMD\",\n apple: \"Apple\",\n arm: \"Arm\",\n intel: \"Intel\",\n nvidia: \"NVIDIA\",\n qualcomm: \"Qualcomm\",\n};\n\n/**\n * `GPUDevice.adapterInfo`. Chrome leaves `description` empty to limit\n * fingerprinting, so this is usually vendor + architecture: `Apple metal-3`.\n */\nconst webgpuGpuName = (info: AdapterInfoShape | undefined): string | null => {\n if (!info) {\n return null;\n }\n\n if (info.description) {\n return info.description;\n }\n\n const vendor = info.vendor ? (VENDOR_NAMES[info.vendor] ?? info.vendor) : \"\";\n const name = [vendor, info.architecture].filter(Boolean).join(\" \");\n\n return name === \"\" ? null : name;\n};\n\nconst isWebglContext = (value: unknown): value is WebGLRenderingContext | WebGL2RenderingContext =>\n (typeof WebGL2RenderingContext !== \"undefined\" && value instanceof WebGL2RenderingContext) ||\n (typeof WebGLRenderingContext !== \"undefined\" && value instanceof WebGLRenderingContext);\n\nconst webglBackend = (gl: WebGLRenderingContext | WebGL2RenderingContext): RenderBackend =>\n typeof WebGL2RenderingContext !== \"undefined\" && gl instanceof WebGL2RenderingContext\n ? \"webgl2\"\n : \"webgl\";\n\nconst readEnvironment = (renderer: PerfRenderer): Environment => {\n const three = readThreeRevision();\n const backend = renderer.backend as BackendShape | undefined;\n\n // WebGPURenderer. Read the backend's own fields: `getContext()` before\n // `init()` is what the GPU timer learned not to touch.\n if (backend) {\n if (backend.isWebGPUBackend) {\n const device = backend.device;\n\n return device\n ? { backend: \"webgpu\", fallback: false, gpu: webgpuGpuName(device.adapterInfo), three }\n : { backend: null, fallback: false, gpu: null, three };\n }\n\n if (backend.isWebGLBackend && isWebglContext(backend.gl)) {\n return {\n backend: webglBackend(backend.gl),\n fallback: backend.parameters?.forceWebGL !== true,\n gpu: webglGpuName(backend.gl),\n three,\n };\n }\n\n return { backend: null, fallback: false, gpu: null, three };\n }\n\n // WebGLRenderer.\n const context = renderer.getContext?.();\n\n return isWebglContext(context)\n ? { backend: webglBackend(context), fallback: false, gpu: webglGpuName(context), three }\n : { backend: null, fallback: false, gpu: null, three };\n};\n\nexport { cleanGpuName, readEnvironment };\n","/**\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 { readEnvironment } from \"./environment.ts\";\nimport { GpuTimer } from \"./gpu-timer.ts\";\nimport { RingBuffer } from \"./ring-buffer.ts\";\nimport type {\n Environment,\n PerfRenderer,\n PerformanceMonitorOptions,\n Sample,\n TimingMetric,\n} 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/**\n * A frame interval past this is a stall (hidden tab, breakpoint, laptop lid),\n * not a slow frame. It is left out of FPS so one gap doesn't flatten the\n * graph's scale for the whole history window.\n */\nconst STALL_MS = 1000;\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 *\n * CPU time runs from {@link begin} to the return of the frame's last\n * `render()` call, which the `render` patch stamps. So it holds even when\n * {@link end} is only reached on the next tick, as in the R3F sampler: it is\n * the JS work of the frame, not the interval to the next one.\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 lastRenderEndAt: number | null = null;\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 private environment: Environment | null = null;\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 try {\n return this.originalRender.apply(renderer, args);\n } finally {\n this.lastRenderEndAt = performance.now();\n }\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\n // A stall keeps the previous reading rather than logging a near-zero frame.\n if (frameMs > 0 && frameMs <= STALL_MS) {\n this.frameFps = 1000 / frameMs;\n }\n }\n\n this.lastBeginAt = now;\n this.cpuStart = now;\n this.lastRenderEndAt = null;\n this.renderPasses = 0;\n this.renderer.info.reset();\n this.gpuTimer?.begin();\n }\n\n end() {\n const now = performance.now();\n // Without a render this frame there is nothing better than \"until end()\".\n const cpu = (this.lastRenderEndAt ?? 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 /**\n * three revision, backend and GPU name. Cached once the backend is known;\n * before `WebGPURenderer.init()` settles it, `backend` is `null` and the\n * next call reads again.\n */\n getEnvironment(): Environment {\n if (this.environment) {\n return this.environment;\n }\n\n const environment = Object.freeze(readEnvironment(this.renderer));\n\n if (environment.backend !== null) {\n this.environment = environment;\n }\n\n return environment;\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
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as MountPerfHudOptions } from "./mount-perf-hud-
|
|
1
|
+
import { a as PerformanceMonitorOptions, t as PerformanceMonitor } from "./performance-monitor-Br7x3cPt.js";
|
|
2
|
+
import { t as MountPerfHudOptions } from "./mount-perf-hud-v6kLGJUX.js";
|
|
3
3
|
//#region src/react/monitor-store.d.ts
|
|
4
4
|
type MonitorListener = (monitor: PerformanceMonitor | null) => void;
|
|
5
5
|
type PerfMonitorStore = {
|
|
@@ -18,8 +18,12 @@ type PerfHudProps = Omit<MountPerfHudOptions, "settings"> & {
|
|
|
18
18
|
/**
|
|
19
19
|
* DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup
|
|
20
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
|
-
*
|
|
21
|
+
* published a monitor and tears it down when that sampler unmounts.
|
|
22
|
+
*
|
|
23
|
+
* `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`
|
|
24
|
+
* is compared by value, so an inline `{{ edge: "right" }}` literal is fine.
|
|
25
|
+
* The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,
|
|
26
|
+
* `store`) remount the HUD when they change.
|
|
23
27
|
*/
|
|
24
28
|
declare function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }: PerfHudProps): null;
|
|
25
29
|
//#endregion
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as PerformanceMonitor } from "./performance-monitor-
|
|
2
|
-
import { t as mountPerfHud } from "./mount-perf-hud-
|
|
1
|
+
import { t as PerformanceMonitor } from "./performance-monitor-C8dhWJWR.js";
|
|
2
|
+
import { t as mountPerfHud } from "./mount-perf-hud-Rf98QNxM.js";
|
|
3
3
|
import { useEffect, useRef } from "react";
|
|
4
4
|
import { useFrame, useThree } from "@react-three/fiber";
|
|
5
5
|
//#region src/react/monitor-store.ts
|
|
@@ -26,23 +26,34 @@ const defaultPerfMonitorStore = createPerfMonitorStore();
|
|
|
26
26
|
/**
|
|
27
27
|
* DOM overlay. Render it *outside* `<Canvas>`, or Fiber would treat its markup
|
|
28
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
|
-
*
|
|
29
|
+
* published a monitor and tears it down when that sampler unmounts.
|
|
30
|
+
*
|
|
31
|
+
* `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`
|
|
32
|
+
* is compared by value, so an inline `{{ edge: "right" }}` literal is fine.
|
|
33
|
+
* The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,
|
|
34
|
+
* `store`) remount the HUD when they change.
|
|
31
35
|
*/
|
|
32
36
|
function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshHz, storageKey, store, theme }) {
|
|
33
37
|
const source = store ?? defaultPerfMonitorStore;
|
|
34
38
|
const handleRef = useRef(null);
|
|
35
39
|
const themeRef = useRef(theme);
|
|
40
|
+
const modeRef = useRef(mode);
|
|
41
|
+
const labelRef = useRef(label);
|
|
42
|
+
const placementEdge = defaultPlacement?.edge;
|
|
43
|
+
const placementAlign = defaultPlacement?.align;
|
|
36
44
|
useEffect(() => {
|
|
37
45
|
const attach = (monitor) => {
|
|
38
46
|
handleRef.current?.dispose();
|
|
39
47
|
handleRef.current = null;
|
|
40
48
|
if (!monitor) return;
|
|
41
49
|
handleRef.current = mountPerfHud(monitor, {
|
|
42
|
-
defaultPlacement
|
|
50
|
+
defaultPlacement: placementEdge ? {
|
|
51
|
+
align: placementAlign,
|
|
52
|
+
edge: placementEdge
|
|
53
|
+
} : void 0,
|
|
43
54
|
injectStyles,
|
|
44
|
-
label,
|
|
45
|
-
mode,
|
|
55
|
+
label: labelRef.current,
|
|
56
|
+
mode: modeRef.current,
|
|
46
57
|
parent,
|
|
47
58
|
refreshHz,
|
|
48
59
|
storageKey,
|
|
@@ -57,11 +68,10 @@ function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshH
|
|
|
57
68
|
handleRef.current = null;
|
|
58
69
|
};
|
|
59
70
|
}, [
|
|
60
|
-
defaultPlacement,
|
|
61
71
|
injectStyles,
|
|
62
|
-
label,
|
|
63
|
-
mode,
|
|
64
72
|
parent,
|
|
73
|
+
placementAlign,
|
|
74
|
+
placementEdge,
|
|
65
75
|
refreshHz,
|
|
66
76
|
source,
|
|
67
77
|
storageKey
|
|
@@ -70,6 +80,14 @@ function PerfHud({ defaultPlacement, injectStyles, label, mode, parent, refreshH
|
|
|
70
80
|
themeRef.current = theme;
|
|
71
81
|
handleRef.current?.setTheme(theme ?? "system");
|
|
72
82
|
}, [theme]);
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
modeRef.current = mode;
|
|
85
|
+
handleRef.current?.setMode(mode ?? "compact");
|
|
86
|
+
}, [mode]);
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
labelRef.current = label;
|
|
89
|
+
handleRef.current?.element.setAttribute("aria-label", label ?? "Performance");
|
|
90
|
+
}, [label]);
|
|
73
91
|
return null;
|
|
74
92
|
}
|
|
75
93
|
//#endregion
|
package/dist/react.js.map
CHANGED
|
@@ -1 +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, useRef } from \"react\";\n\nimport {\n mountPerfHud,\n type MountPerfHudOptions,\n type PerfHudHandle,\n} 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. `
|
|
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, useRef } from \"react\";\n\nimport {\n mountPerfHud,\n type MountPerfHudOptions,\n type PerfHudHandle,\n} 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 *\n * `theme`, `mode` and `label` apply live through the handle. `defaultPlacement`\n * is compared by value, so an inline `{{ edge: \"right\" }}` literal is fine.\n * The remaining props (`storageKey`, `parent`, `refreshHz`, `injectStyles`,\n * `store`) remount the HUD when they change.\n */\nfunction PerfHud({\n defaultPlacement,\n injectStyles,\n label,\n mode,\n parent,\n refreshHz,\n storageKey,\n store,\n theme,\n}: PerfHudProps) {\n const source = store ?? defaultPerfMonitorStore;\n const handleRef = useRef<PerfHudHandle | null>(null);\n // The mount effect reads these through refs so changing them never remounts;\n // the live effects below keep the refs current and push changes to the handle.\n const themeRef = useRef(theme);\n const modeRef = useRef(mode);\n const labelRef = useRef(label);\n // Primitives, so an inline `{{ edge: \"right\" }}` literal doesn't remount each render.\n const placementEdge = defaultPlacement?.edge;\n const placementAlign = defaultPlacement?.align;\n\n useEffect(() => {\n const attach = (monitor: ReturnType<PerfMonitorStore[\"get\"]>) => {\n handleRef.current?.dispose();\n handleRef.current = null;\n\n if (!monitor) {\n return;\n }\n\n handleRef.current = mountPerfHud(monitor, {\n defaultPlacement: placementEdge\n ? { align: placementAlign, edge: placementEdge }\n : undefined,\n injectStyles,\n label: labelRef.current,\n mode: modeRef.current,\n parent,\n refreshHz,\n storageKey,\n theme: themeRef.current,\n });\n };\n\n const unsubscribe = source.subscribe(attach);\n attach(source.get());\n\n return () => {\n unsubscribe();\n handleRef.current?.dispose();\n handleRef.current = null;\n };\n }, [injectStyles, parent, placementAlign, placementEdge, refreshHz, source, storageKey]);\n\n useEffect(() => {\n themeRef.current = theme;\n handleRef.current?.setTheme(theme ?? \"system\");\n }, [theme]);\n\n useEffect(() => {\n modeRef.current = mode;\n handleRef.current?.setMode(mode ?? \"compact\");\n }, [mode]);\n\n useEffect(() => {\n labelRef.current = label;\n handleRef.current?.element.setAttribute(\"aria-label\", label ?? \"Performance\");\n }, [label]);\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;;;;;;;;;;;;;AChBvD,SAAS,QAAQ,EACf,kBACA,cACA,OACA,MACA,QACA,WACA,YACA,OACA,SACe;CACf,MAAM,SAAS,SAAS;CACxB,MAAM,YAAY,OAA6B,IAAI;CAGnD,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,UAAU,OAAO,IAAI;CAC3B,MAAM,WAAW,OAAO,KAAK;CAE7B,MAAM,gBAAgB,kBAAkB;CACxC,MAAM,iBAAiB,kBAAkB;CAEzC,gBAAgB;EACd,MAAM,UAAU,YAAiD;GAC/D,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;GAEpB,IAAI,CAAC,SACH;GAGF,UAAU,UAAU,aAAa,SAAS;IACxC,kBAAkB,gBACd;KAAE,OAAO;KAAgB,MAAM;IAAc,IAC7C,KAAA;IACJ;IACA,OAAO,SAAS;IAChB,MAAM,QAAQ;IACd;IACA;IACA;IACA,OAAO,SAAS;GAClB,CAAC;EACH;EAEA,MAAM,cAAc,OAAO,UAAU,MAAM;EAC3C,OAAO,OAAO,IAAI,CAAC;EAEnB,aAAa;GACX,YAAY;GACZ,UAAU,SAAS,QAAQ;GAC3B,UAAU,UAAU;EACtB;CACF,GAAG;EAAC;EAAc;EAAQ;EAAgB;EAAe;EAAW;EAAQ;CAAU,CAAC;CAEvF,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,SAAS,SAAS,QAAQ;CAC/C,GAAG,CAAC,KAAK,CAAC;CAEV,gBAAgB;EACd,QAAQ,UAAU;EAClB,UAAU,SAAS,QAAQ,QAAQ,SAAS;CAC9C,GAAG,CAAC,IAAI,CAAC;CAET,gBAAgB;EACd,SAAS,UAAU;EACnB,UAAU,SAAS,QAAQ,aAAa,cAAc,SAAS,aAAa;CAC9E,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO;AACT;;;;;;;;;AC7EA,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"}
|
package/dist/styles.css
CHANGED
|
@@ -242,15 +242,90 @@
|
|
|
242
242
|
text-align: right;
|
|
243
243
|
}
|
|
244
244
|
|
|
245
|
+
.perf-monitor__footer {
|
|
246
|
+
display: flex;
|
|
247
|
+
align-items: flex-start;
|
|
248
|
+
gap: 6px;
|
|
249
|
+
padding: 8px 6px 0;
|
|
250
|
+
border-top: 1px solid var(--perf-border);
|
|
251
|
+
color: var(--perf-muted);
|
|
252
|
+
font-size: 10px;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.perf-monitor__footer-body {
|
|
256
|
+
display: flex;
|
|
257
|
+
flex: 1;
|
|
258
|
+
flex-direction: column;
|
|
259
|
+
gap: 4px;
|
|
260
|
+
min-width: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.perf-monitor__footer-row {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
justify-content: space-between;
|
|
267
|
+
gap: 8px;
|
|
268
|
+
min-width: 0;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.perf-monitor__footer-row[hidden],
|
|
272
|
+
.perf-monitor__badge[hidden] {
|
|
273
|
+
display: none;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.perf-monitor__footer-gpu {
|
|
277
|
+
min-width: 0;
|
|
278
|
+
overflow: hidden;
|
|
279
|
+
text-overflow: ellipsis;
|
|
280
|
+
white-space: nowrap;
|
|
281
|
+
color: var(--perf-fg-dim);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.perf-monitor__badge {
|
|
285
|
+
flex: none;
|
|
286
|
+
padding: 0 5px;
|
|
287
|
+
border: 1px solid var(--perf-border);
|
|
288
|
+
border-radius: 3px;
|
|
289
|
+
color: var(--perf-fg-dim);
|
|
290
|
+
letter-spacing: 0.04em;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.perf-monitor__badge.is-fallback {
|
|
294
|
+
border-color: #f59e0b;
|
|
295
|
+
color: #f59e0b;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.perf-monitor[data-theme="light"] .perf-monitor__badge.is-fallback {
|
|
299
|
+
border-color: #d97706;
|
|
300
|
+
color: #b45309;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.perf-monitor__link {
|
|
304
|
+
color: inherit;
|
|
305
|
+
text-decoration: none;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
.perf-monitor__link:hover,
|
|
309
|
+
.perf-monitor__link:focus-visible {
|
|
310
|
+
color: var(--perf-accent);
|
|
311
|
+
text-decoration: underline;
|
|
312
|
+
}
|
|
313
|
+
|
|
245
314
|
.perf-monitor--full .perf-monitor__hud {
|
|
246
315
|
display: none;
|
|
247
316
|
}
|
|
248
317
|
|
|
249
318
|
.perf-monitor--compact .perf-monitor__graphs,
|
|
250
|
-
.perf-monitor--compact .perf-monitor__section
|
|
319
|
+
.perf-monitor--compact .perf-monitor__section,
|
|
320
|
+
.perf-monitor--compact:not(.perf-monitor--info) .perf-monitor__footer,
|
|
321
|
+
.perf-monitor--compact .perf-monitor__footer > .perf-monitor__checkbox {
|
|
251
322
|
display: none;
|
|
252
323
|
}
|
|
253
324
|
|
|
325
|
+
.perf-monitor--compact .perf-monitor__footer {
|
|
326
|
+
padding: 6px 2px 2px;
|
|
327
|
+
}
|
|
328
|
+
|
|
254
329
|
.perf-monitor--compact {
|
|
255
330
|
padding: 4px 8px;
|
|
256
331
|
}
|