stats-gl 3.8.0 → 4.0.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 +360 -86
- package/addons/StatsGLNode.js +201 -0
- package/addons/StatsGLNodeWorker.js +198 -0
- package/dist/Flamingo.glb +0 -0
- package/dist/LittlestTokyo.glb +0 -0
- package/dist/core.cjs +421 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.js +421 -0
- package/dist/core.js.map +1 -0
- package/dist/main.cjs +406 -241
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +404 -240
- package/dist/main.js.map +1 -1
- package/dist/panel.cjs +12 -3
- package/dist/panel.cjs.map +1 -1
- package/dist/panel.js +12 -3
- package/dist/panel.js.map +1 -1
- package/dist/panelTexture.cjs +93 -0
- package/dist/panelTexture.cjs.map +1 -0
- package/dist/panelTexture.js +93 -0
- package/dist/panelTexture.js.map +1 -0
- package/dist/profiler.cjs +95 -0
- package/dist/profiler.cjs.map +1 -0
- package/dist/profiler.js +95 -0
- package/dist/profiler.js.map +1 -0
- package/dist/stats-gl.d.ts +332 -71
- package/dist/statsGLNode.cjs +89 -0
- package/dist/statsGLNode.cjs.map +1 -0
- package/dist/statsGLNode.js +89 -0
- package/dist/statsGLNode.js.map +1 -0
- package/dist/textureCapture.cjs +283 -0
- package/dist/textureCapture.cjs.map +1 -0
- package/dist/textureCapture.js +283 -0
- package/dist/textureCapture.js.map +1 -0
- package/lib/core.ts +579 -0
- package/lib/main.ts +506 -401
- package/lib/panel.ts +18 -4
- package/lib/panelTexture.ts +122 -0
- package/lib/profiler.ts +124 -0
- package/lib/statsGLNode.ts +124 -0
- package/lib/textureCapture.ts +403 -0
- package/lib/webgpu.d.ts +190 -0
- package/package.json +9 -5
package/lib/main.ts
CHANGED
|
@@ -1,78 +1,33 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { StatsCore, StatsCoreOptions, StatsData, AverageData } from './core';
|
|
2
2
|
import { Panel } from './panel';
|
|
3
3
|
import { PanelVSync } from './panelVsync';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
import { PanelTexture } from './panelTexture';
|
|
5
|
+
import {
|
|
6
|
+
TextureCaptureWebGL,
|
|
7
|
+
TextureCaptureWebGPU,
|
|
8
|
+
extractWebGLSource,
|
|
9
|
+
extractWebGPUSource,
|
|
10
|
+
ThreeTextureSource,
|
|
11
|
+
DEFAULT_PREVIEW_WIDTH,
|
|
12
|
+
DEFAULT_PREVIEW_HEIGHT
|
|
13
|
+
} from './textureCapture';
|
|
14
|
+
|
|
15
|
+
interface StatsOptions extends StatsCoreOptions {
|
|
15
16
|
minimal?: boolean;
|
|
16
17
|
horizontal?: boolean;
|
|
17
18
|
mode?: number;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
interface QueryInfo {
|
|
21
|
-
query: WebGLQuery;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface AverageData {
|
|
25
|
-
logs: number[];
|
|
26
|
-
graph: number[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
|
|
30
21
|
interface VSyncInfo {
|
|
31
22
|
refreshRate: number;
|
|
32
23
|
frameTime: number;
|
|
33
24
|
}
|
|
34
25
|
|
|
35
|
-
|
|
36
|
-
interface InfoData {
|
|
37
|
-
render: {
|
|
38
|
-
timestamp: number;
|
|
39
|
-
};
|
|
40
|
-
compute: {
|
|
41
|
-
timestamp: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
class Stats {
|
|
26
|
+
class Stats extends StatsCore {
|
|
46
27
|
public dom: HTMLDivElement;
|
|
47
28
|
public mode: number;
|
|
48
29
|
public horizontal: boolean;
|
|
49
30
|
public minimal: boolean;
|
|
50
|
-
public trackGPU: boolean;
|
|
51
|
-
public trackHz: boolean;
|
|
52
|
-
public trackFPS: boolean;
|
|
53
|
-
public trackCPT: boolean;
|
|
54
|
-
public samplesLog: number;
|
|
55
|
-
public samplesGraph: number;
|
|
56
|
-
public precision: number;
|
|
57
|
-
public logsPerSecond: number;
|
|
58
|
-
public graphsPerSecond: number;
|
|
59
|
-
|
|
60
|
-
public gl: WebGL2RenderingContext | null = null;
|
|
61
|
-
public ext: any | null = null;
|
|
62
|
-
public info?: InfoData;
|
|
63
|
-
private activeQuery: WebGLQuery | null = null;
|
|
64
|
-
private gpuQueries: QueryInfo[] = [];
|
|
65
|
-
private threeRendererPatched = false;
|
|
66
|
-
|
|
67
|
-
private beginTime: number;
|
|
68
|
-
private prevCpuTime: number;
|
|
69
|
-
private frameTimes: number[] = []; // Store frame timestamps
|
|
70
|
-
|
|
71
|
-
private renderCount = 0;
|
|
72
|
-
|
|
73
|
-
private totalCpuDuration = 0;
|
|
74
|
-
private totalGpuDuration = 0;
|
|
75
|
-
private totalGpuDurationCompute = 0;
|
|
76
31
|
|
|
77
32
|
private _panelId: number;
|
|
78
33
|
private fpsPanel: Panel | null = null;
|
|
@@ -80,18 +35,25 @@ class Stats {
|
|
|
80
35
|
private gpuPanel: Panel | null = null;
|
|
81
36
|
private gpuPanelCompute: Panel | null = null;
|
|
82
37
|
private vsyncPanel: PanelVSync | null = null;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
public
|
|
87
|
-
|
|
38
|
+
private workerCpuPanel: Panel | null = null;
|
|
39
|
+
|
|
40
|
+
// Texture panel support
|
|
41
|
+
public texturePanels: Map<string, PanelTexture> = new Map();
|
|
42
|
+
private texturePanelRow: HTMLDivElement | null = null;
|
|
43
|
+
private textureCaptureWebGL: TextureCaptureWebGL | null = null;
|
|
44
|
+
private textureCaptureWebGPU: TextureCaptureWebGPU | null = null;
|
|
45
|
+
private textureSourcesWebGL: Map<string, { target: ThreeTextureSource; framebuffer: WebGLFramebuffer; width: number; height: number }> = new Map();
|
|
46
|
+
private textureSourcesWebGPU: Map<string, any> = new Map(); // GPUTexture
|
|
47
|
+
private texturePreviewWidth = DEFAULT_PREVIEW_WIDTH;
|
|
48
|
+
private texturePreviewHeight = DEFAULT_PREVIEW_HEIGHT;
|
|
49
|
+
private lastRendererWidth = 0;
|
|
50
|
+
private lastRendererHeight = 0;
|
|
51
|
+
private textureUpdatePending = false;
|
|
88
52
|
|
|
89
53
|
private updateCounter = 0;
|
|
90
|
-
private prevGraphTime: number;
|
|
91
54
|
private lastMin: { [key: string]: number } = {};
|
|
92
55
|
private lastMax: { [key: string]: number } = {};
|
|
93
56
|
private lastValue: { [key: string]: number } = {};
|
|
94
|
-
private prevTextTime: number;
|
|
95
57
|
|
|
96
58
|
private readonly VSYNC_RATES: VSyncInfo[] = [
|
|
97
59
|
{ refreshRate: 60, frameTime: 16.67 },
|
|
@@ -104,13 +66,17 @@ class Stats {
|
|
|
104
66
|
];
|
|
105
67
|
private detectedVSync: VSyncInfo | null = null;
|
|
106
68
|
private frameTimeHistory: number[] = [];
|
|
107
|
-
private readonly HISTORY_SIZE = 120;
|
|
108
|
-
private readonly VSYNC_THRESHOLD = 0.05;
|
|
69
|
+
private readonly HISTORY_SIZE = 120;
|
|
70
|
+
private readonly VSYNC_THRESHOLD = 0.05;
|
|
109
71
|
private lastFrameTime: number = 0;
|
|
110
72
|
|
|
111
|
-
|
|
73
|
+
private externalData: StatsData | null = null;
|
|
74
|
+
private hasNewExternalData = false;
|
|
75
|
+
private isWorker = false;
|
|
76
|
+
private averageWorkerCpu: AverageData = { logs: [], graph: [] };
|
|
112
77
|
|
|
113
78
|
static Panel = Panel;
|
|
79
|
+
static PanelTexture = PanelTexture;
|
|
114
80
|
|
|
115
81
|
constructor({
|
|
116
82
|
trackGPU = false,
|
|
@@ -126,38 +92,40 @@ class Stats {
|
|
|
126
92
|
horizontal = true,
|
|
127
93
|
mode = 0
|
|
128
94
|
}: StatsOptions = {}) {
|
|
95
|
+
super({
|
|
96
|
+
trackGPU,
|
|
97
|
+
trackCPT,
|
|
98
|
+
trackHz,
|
|
99
|
+
trackFPS,
|
|
100
|
+
logsPerSecond,
|
|
101
|
+
graphsPerSecond,
|
|
102
|
+
samplesLog,
|
|
103
|
+
samplesGraph,
|
|
104
|
+
precision
|
|
105
|
+
});
|
|
106
|
+
|
|
129
107
|
this.mode = mode;
|
|
130
108
|
this.horizontal = horizontal;
|
|
131
109
|
this.minimal = minimal;
|
|
132
|
-
|
|
133
|
-
this.trackCPT = trackCPT;
|
|
134
|
-
this.trackHz = trackHz;
|
|
135
|
-
this.trackFPS = trackFPS;
|
|
136
|
-
this.samplesLog = samplesLog;
|
|
137
|
-
this.samplesGraph = samplesGraph;
|
|
138
|
-
this.precision = precision;
|
|
139
|
-
this.logsPerSecond = logsPerSecond;
|
|
140
|
-
this.graphsPerSecond = graphsPerSecond;
|
|
141
|
-
const prevGraphTime = performance.now();
|
|
142
|
-
this.prevGraphTime = prevGraphTime
|
|
143
|
-
|
|
144
|
-
// Initialize DOM
|
|
110
|
+
|
|
145
111
|
this.dom = document.createElement('div');
|
|
146
112
|
this.initializeDOM();
|
|
147
113
|
|
|
148
|
-
|
|
149
|
-
this.beginTime = performance.now();
|
|
150
|
-
this.prevTextTime = this.beginTime;
|
|
151
|
-
|
|
152
|
-
this.prevCpuTime = this.beginTime;
|
|
114
|
+
this._panelId = 0;
|
|
153
115
|
|
|
154
|
-
this._panelId = 0
|
|
155
|
-
// Create panels
|
|
156
116
|
if (this.trackFPS) {
|
|
157
117
|
this.fpsPanel = this.addPanel(new Stats.Panel('FPS', '#0ff', '#002'));
|
|
158
118
|
this.msPanel = this.addPanel(new Stats.Panel('CPU', '#0f0', '#020'));
|
|
159
119
|
}
|
|
160
120
|
|
|
121
|
+
if (this.trackGPU) {
|
|
122
|
+
this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (this.trackCPT) {
|
|
126
|
+
this.gpuPanelCompute = this.addPanel(new Stats.Panel('CPT', '#e1e1e1', '#212121'));
|
|
127
|
+
}
|
|
128
|
+
|
|
161
129
|
if (this.trackHz === true) {
|
|
162
130
|
this.vsyncPanel = new PanelVSync('', '#f0f', '#202');
|
|
163
131
|
this.dom.appendChild(this.vsyncPanel.canvas);
|
|
@@ -167,7 +135,6 @@ class Stats {
|
|
|
167
135
|
this.setupEventListeners();
|
|
168
136
|
}
|
|
169
137
|
|
|
170
|
-
|
|
171
138
|
private initializeDOM(): void {
|
|
172
139
|
this.dom.style.cssText = `
|
|
173
140
|
position: fixed;
|
|
@@ -196,165 +163,232 @@ class Stats {
|
|
|
196
163
|
private handleResize = (): void => {
|
|
197
164
|
if (this.fpsPanel) this.resizePanel(this.fpsPanel);
|
|
198
165
|
if (this.msPanel) this.resizePanel(this.msPanel);
|
|
166
|
+
if (this.workerCpuPanel) this.resizePanel(this.workerCpuPanel);
|
|
199
167
|
if (this.gpuPanel) this.resizePanel(this.gpuPanel);
|
|
200
168
|
if (this.gpuPanelCompute) this.resizePanel(this.gpuPanelCompute);
|
|
201
169
|
};
|
|
202
170
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Compute and update texture preview dimensions based on renderer aspect ratio
|
|
173
|
+
*/
|
|
174
|
+
private updateTexturePreviewDimensions(): void {
|
|
175
|
+
if (!this.renderer) return;
|
|
176
|
+
|
|
177
|
+
const rendererWidth = this.renderer.domElement?.width || 0;
|
|
178
|
+
const rendererHeight = this.renderer.domElement?.height || 0;
|
|
179
|
+
|
|
180
|
+
// Skip if dimensions unchanged
|
|
181
|
+
if (rendererWidth === this.lastRendererWidth && rendererHeight === this.lastRendererHeight) {
|
|
208
182
|
return;
|
|
209
183
|
}
|
|
184
|
+
if (rendererWidth === 0 || rendererHeight === 0) return;
|
|
210
185
|
|
|
211
|
-
|
|
212
|
-
|
|
186
|
+
this.lastRendererWidth = rendererWidth;
|
|
187
|
+
this.lastRendererHeight = rendererHeight;
|
|
213
188
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
189
|
+
// Compute preview size maintaining aspect ratio
|
|
190
|
+
// Base dimensions: 90x48 panel, compute to fit source aspect
|
|
191
|
+
const sourceAspect = rendererWidth / rendererHeight;
|
|
192
|
+
const panelAspect = DEFAULT_PREVIEW_WIDTH / DEFAULT_PREVIEW_HEIGHT;
|
|
193
|
+
|
|
194
|
+
let newWidth: number;
|
|
195
|
+
let newHeight: number;
|
|
196
|
+
|
|
197
|
+
if (sourceAspect > panelAspect) {
|
|
198
|
+
// Source wider than panel - fit to width
|
|
199
|
+
newWidth = DEFAULT_PREVIEW_WIDTH;
|
|
200
|
+
newHeight = Math.round(DEFAULT_PREVIEW_WIDTH / sourceAspect);
|
|
219
201
|
} else {
|
|
220
|
-
|
|
202
|
+
// Source taller than panel - fit to height
|
|
203
|
+
newHeight = DEFAULT_PREVIEW_HEIGHT;
|
|
204
|
+
newWidth = Math.round(DEFAULT_PREVIEW_HEIGHT * sourceAspect);
|
|
221
205
|
}
|
|
222
|
-
}
|
|
223
206
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
207
|
+
// Ensure minimum dimensions
|
|
208
|
+
newWidth = Math.max(newWidth, 16);
|
|
209
|
+
newHeight = Math.max(newHeight, 16);
|
|
210
|
+
|
|
211
|
+
if (newWidth !== this.texturePreviewWidth || newHeight !== this.texturePreviewHeight) {
|
|
212
|
+
this.texturePreviewWidth = newWidth;
|
|
213
|
+
this.texturePreviewHeight = newHeight;
|
|
228
214
|
|
|
229
|
-
|
|
230
|
-
|
|
215
|
+
// Resize capture helpers
|
|
216
|
+
if (this.textureCaptureWebGL) {
|
|
217
|
+
this.textureCaptureWebGL.resize(newWidth, newHeight);
|
|
218
|
+
}
|
|
219
|
+
if (this.textureCaptureWebGPU) {
|
|
220
|
+
this.textureCaptureWebGPU.resize(newWidth, newHeight);
|
|
231
221
|
}
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
222
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
renderer.backend.trackTimestamp = true;
|
|
241
|
-
if (!renderer._initialized) {
|
|
242
|
-
await renderer.init();
|
|
243
|
-
}
|
|
244
|
-
if (renderer.hasFeature('timestamp-query')) {
|
|
245
|
-
this.initializeWebGPUPanels();
|
|
246
|
-
}
|
|
223
|
+
// Update panel source sizes
|
|
224
|
+
for (const panel of this.texturePanels.values()) {
|
|
225
|
+
panel.setSourceSize(rendererWidth, rendererHeight);
|
|
247
226
|
}
|
|
248
|
-
this.info = renderer.info;
|
|
249
|
-
this.patchThreeWebGPU(renderer);
|
|
250
|
-
return true;
|
|
251
227
|
}
|
|
252
|
-
return false;
|
|
253
228
|
}
|
|
254
229
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'));
|
|
258
|
-
}
|
|
259
|
-
if (this.trackCPT) {
|
|
260
|
-
this.gpuPanelCompute = this.addPanel(new Stats.Panel('CPT', '#e1e1e1', '#212121'));
|
|
261
|
-
}
|
|
230
|
+
protected override onWebGPUTimestampSupported(): void {
|
|
231
|
+
// Panels already created in constructor
|
|
262
232
|
}
|
|
263
233
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
): boolean {
|
|
267
|
-
if (canvasOrGL instanceof WebGL2RenderingContext) {
|
|
268
|
-
this.gl = canvasOrGL;
|
|
269
|
-
} else if (
|
|
270
|
-
canvasOrGL instanceof HTMLCanvasElement ||
|
|
271
|
-
canvasOrGL instanceof OffscreenCanvas
|
|
272
|
-
) {
|
|
273
|
-
this.gl = canvasOrGL.getContext('webgl2');
|
|
274
|
-
if (!this.gl) {
|
|
275
|
-
console.error('Stats: Unable to obtain WebGL2 context.');
|
|
276
|
-
return false;
|
|
277
|
-
}
|
|
278
|
-
} else {
|
|
279
|
-
console.error(
|
|
280
|
-
'Stats: Invalid input type. Expected WebGL2RenderingContext, HTMLCanvasElement, or OffscreenCanvas.'
|
|
281
|
-
);
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
return true;
|
|
234
|
+
protected override onGPUTrackingInitialized(): void {
|
|
235
|
+
// Panel already created in constructor
|
|
285
236
|
}
|
|
286
237
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (this.ext) {
|
|
291
|
-
this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'));
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
238
|
+
public setData(data: StatsData): void {
|
|
239
|
+
this.externalData = data;
|
|
240
|
+
this.hasNewExternalData = true;
|
|
295
241
|
|
|
296
|
-
|
|
297
|
-
this.
|
|
242
|
+
// Dynamically add worker CPU panel right after main CPU panel
|
|
243
|
+
if (!this.isWorker && this.msPanel) {
|
|
244
|
+
this.isWorker = true;
|
|
298
245
|
|
|
299
|
-
|
|
246
|
+
this.workerCpuPanel = new Stats.Panel('WRK', '#f90', '#220');
|
|
247
|
+
const insertPosition = this.msPanel.id + 1;
|
|
248
|
+
this.workerCpuPanel.id = insertPosition;
|
|
300
249
|
|
|
301
|
-
|
|
302
|
-
this.
|
|
303
|
-
|
|
250
|
+
// Shift IDs of panels that come after
|
|
251
|
+
if (this.gpuPanel && this.gpuPanel.id >= insertPosition) {
|
|
252
|
+
this.gpuPanel.id++;
|
|
253
|
+
this.resizePanel(this.gpuPanel);
|
|
254
|
+
}
|
|
255
|
+
if (this.gpuPanelCompute && this.gpuPanelCompute.id >= insertPosition) {
|
|
256
|
+
this.gpuPanelCompute.id++;
|
|
257
|
+
this.resizePanel(this.gpuPanelCompute);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Insert canvas after msPanel in DOM
|
|
261
|
+
const msCanvas = this.msPanel.canvas;
|
|
262
|
+
if (msCanvas.nextSibling) {
|
|
263
|
+
this.dom.insertBefore(this.workerCpuPanel.canvas, msCanvas.nextSibling);
|
|
264
|
+
} else {
|
|
265
|
+
this.dom.appendChild(this.workerCpuPanel.canvas);
|
|
266
|
+
}
|
|
304
267
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, this.activeQuery);
|
|
268
|
+
this.resizePanel(this.workerCpuPanel);
|
|
269
|
+
this._panelId++;
|
|
308
270
|
}
|
|
309
271
|
}
|
|
310
272
|
|
|
311
|
-
public
|
|
312
|
-
this.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
this.
|
|
316
|
-
this.activeQuery = null;
|
|
273
|
+
public update(): void {
|
|
274
|
+
if (this.externalData) {
|
|
275
|
+
this.updateFromExternalData();
|
|
276
|
+
} else {
|
|
277
|
+
this.updateFromInternalData();
|
|
317
278
|
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private updateFromExternalData(): void {
|
|
282
|
+
const data = this.externalData!;
|
|
318
283
|
|
|
284
|
+
// Track main thread CPU (measures from begin() call if user called it)
|
|
319
285
|
this.endProfiling('cpu-started', 'cpu-finished', 'cpu-duration');
|
|
320
|
-
|
|
286
|
+
this.addToAverage(this.totalCpuDuration, this.averageCpu);
|
|
287
|
+
this.totalCpuDuration = 0;
|
|
321
288
|
|
|
322
|
-
|
|
289
|
+
// Only add worker data when new message arrived
|
|
290
|
+
if (this.hasNewExternalData) {
|
|
291
|
+
this.addToAverage(data.cpu, this.averageWorkerCpu);
|
|
292
|
+
this.addToAverage(data.fps, this.averageFps);
|
|
293
|
+
this.addToAverage(data.gpu, this.averageGpu);
|
|
294
|
+
this.addToAverage(data.gpuCompute, this.averageGpuCompute);
|
|
295
|
+
this.hasNewExternalData = false;
|
|
296
|
+
}
|
|
323
297
|
|
|
298
|
+
this.renderPanels();
|
|
299
|
+
}
|
|
324
300
|
|
|
301
|
+
private updateFromInternalData(): void {
|
|
325
302
|
this.endProfiling('cpu-started', 'cpu-finished', 'cpu-duration');
|
|
326
303
|
|
|
327
|
-
if (
|
|
304
|
+
if (this.webgpuNative) {
|
|
305
|
+
// Native WebGPU: resolve timestamps async
|
|
306
|
+
this.resolveTimestampsAsync();
|
|
307
|
+
} else if (!this.info) {
|
|
328
308
|
this.processGpuQueries();
|
|
329
309
|
} else {
|
|
330
310
|
this.processWebGPUTimestamps();
|
|
331
311
|
}
|
|
332
312
|
|
|
333
|
-
this.updateAverages()
|
|
313
|
+
this.updateAverages();
|
|
334
314
|
this.resetCounters();
|
|
315
|
+
this.renderPanels();
|
|
335
316
|
}
|
|
336
317
|
|
|
337
|
-
private
|
|
338
|
-
|
|
339
|
-
|
|
318
|
+
private renderPanels(): void {
|
|
319
|
+
const currentTime = performance.now();
|
|
320
|
+
|
|
321
|
+
// Only calculate FPS locally when not using worker data
|
|
322
|
+
if (!this.isWorker) {
|
|
323
|
+
this.frameTimes.push(currentTime);
|
|
324
|
+
|
|
325
|
+
while (this.frameTimes.length > 0 && this.frameTimes[0] <= currentTime - 1000) {
|
|
326
|
+
this.frameTimes.shift();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const fps = Math.round(this.frameTimes.length);
|
|
330
|
+
this.addToAverage(fps, this.averageFps);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const shouldUpdateText = currentTime >= this.prevTextTime + 1000 / this.logsPerSecond;
|
|
334
|
+
const shouldUpdateGraph = currentTime >= this.prevGraphTime + 1000 / this.graphsPerSecond;
|
|
335
|
+
|
|
336
|
+
const suffix = this.isWorker ? ' ⛭' : '';
|
|
337
|
+
this.updatePanelComponents(this.fpsPanel, this.averageFps, 0, shouldUpdateText, shouldUpdateGraph, suffix);
|
|
338
|
+
// Main thread CPU (no suffix)
|
|
339
|
+
this.updatePanelComponents(this.msPanel, this.averageCpu, this.precision, shouldUpdateText, shouldUpdateGraph, '');
|
|
340
|
+
// Worker CPU panel (with ⛭ suffix)
|
|
341
|
+
if (this.workerCpuPanel && this.isWorker) {
|
|
342
|
+
this.updatePanelComponents(this.workerCpuPanel, this.averageWorkerCpu, this.precision, shouldUpdateText, shouldUpdateGraph, ' ⛭');
|
|
343
|
+
}
|
|
344
|
+
if (this.gpuPanel) {
|
|
345
|
+
this.updatePanelComponents(this.gpuPanel, this.averageGpu, this.precision, shouldUpdateText, shouldUpdateGraph, suffix);
|
|
346
|
+
}
|
|
347
|
+
if (this.trackCPT && this.gpuPanelCompute) {
|
|
348
|
+
this.updatePanelComponents(this.gpuPanelCompute, this.averageGpuCompute, this.precision, shouldUpdateText, shouldUpdateGraph, suffix);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (shouldUpdateText) {
|
|
352
|
+
this.prevTextTime = currentTime;
|
|
353
|
+
}
|
|
354
|
+
if (shouldUpdateGraph) {
|
|
355
|
+
this.prevGraphTime = currentTime;
|
|
356
|
+
|
|
357
|
+
// Update texture panels at graph rate (prevent overlapping async updates)
|
|
358
|
+
if (this.texturePanels.size > 0 && !this.textureUpdatePending) {
|
|
359
|
+
this.textureUpdatePending = true;
|
|
360
|
+
this.updateTexturePanels().finally(() => {
|
|
361
|
+
this.textureUpdatePending = false;
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Capture StatsGL nodes (registered by addon)
|
|
366
|
+
this.captureStatsGLNodes();
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (this.vsyncPanel !== null) {
|
|
370
|
+
this.detectVSync(currentTime);
|
|
371
|
+
|
|
372
|
+
const vsyncValue = this.detectedVSync?.refreshRate || 0;
|
|
373
|
+
|
|
374
|
+
if (shouldUpdateText && vsyncValue > 0) {
|
|
375
|
+
this.vsyncPanel.update(vsyncValue, vsyncValue);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
340
378
|
}
|
|
341
379
|
|
|
342
|
-
|
|
380
|
+
protected override resetCounters(): void {
|
|
343
381
|
this.renderCount = 0;
|
|
344
382
|
this.totalCpuDuration = 0;
|
|
345
|
-
this.beginTime =
|
|
383
|
+
this.beginTime = performance.now();
|
|
346
384
|
}
|
|
347
385
|
|
|
348
386
|
resizePanel(panel: Panel) {
|
|
349
|
-
|
|
350
387
|
panel.canvas.style.position = 'absolute';
|
|
351
388
|
|
|
352
389
|
if (this.minimal) {
|
|
353
|
-
|
|
354
390
|
panel.canvas.style.display = 'none';
|
|
355
|
-
|
|
356
391
|
} else {
|
|
357
|
-
|
|
358
392
|
panel.canvas.style.display = 'block';
|
|
359
393
|
if (this.horizontal) {
|
|
360
394
|
panel.canvas.style.top = '0px';
|
|
@@ -362,96 +396,267 @@ class Stats {
|
|
|
362
396
|
} else {
|
|
363
397
|
panel.canvas.style.left = '0px';
|
|
364
398
|
panel.canvas.style.top = panel.id * panel.HEIGHT / panel.PR + 'px';
|
|
365
|
-
|
|
366
399
|
}
|
|
367
400
|
}
|
|
368
|
-
|
|
369
401
|
}
|
|
370
|
-
addPanel(panel: Panel) {
|
|
371
402
|
|
|
403
|
+
addPanel(panel: Panel) {
|
|
372
404
|
if (panel.canvas) {
|
|
373
|
-
|
|
374
405
|
this.dom.appendChild(panel.canvas);
|
|
375
406
|
panel.id = this._panelId;
|
|
376
407
|
this.resizePanel(panel);
|
|
377
|
-
|
|
378
408
|
this._panelId++;
|
|
379
409
|
}
|
|
380
|
-
|
|
381
410
|
return panel;
|
|
382
|
-
|
|
383
411
|
}
|
|
384
412
|
|
|
385
413
|
showPanel(id: number) {
|
|
386
|
-
|
|
387
414
|
for (let i = 0; i < this.dom.children.length; i++) {
|
|
388
415
|
const child = this.dom.children[i] as HTMLElement;
|
|
389
|
-
|
|
390
416
|
child.style.display = i === id ? 'block' : 'none';
|
|
391
|
-
|
|
392
417
|
}
|
|
393
|
-
|
|
394
418
|
this.mode = id;
|
|
419
|
+
}
|
|
395
420
|
|
|
421
|
+
// ==========================================================================
|
|
422
|
+
// Texture Panel API
|
|
423
|
+
// ==========================================================================
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Add a new texture preview panel
|
|
427
|
+
* @param name - Label for the texture panel
|
|
428
|
+
* @returns The created PanelTexture instance
|
|
429
|
+
*/
|
|
430
|
+
public addTexturePanel(name: string): PanelTexture {
|
|
431
|
+
// Create texture panel row if not exists
|
|
432
|
+
if (!this.texturePanelRow) {
|
|
433
|
+
this.texturePanelRow = document.createElement('div');
|
|
434
|
+
this.texturePanelRow.style.cssText = `
|
|
435
|
+
position: absolute;
|
|
436
|
+
top: 48px;
|
|
437
|
+
left: 0;
|
|
438
|
+
display: flex;
|
|
439
|
+
flex-direction: row;
|
|
440
|
+
`;
|
|
441
|
+
this.dom.appendChild(this.texturePanelRow);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const panel = new PanelTexture(name);
|
|
445
|
+
panel.canvas.style.position = 'relative';
|
|
446
|
+
panel.canvas.style.left = '';
|
|
447
|
+
panel.canvas.style.top = '';
|
|
448
|
+
this.texturePanelRow.appendChild(panel.canvas);
|
|
449
|
+
this.texturePanels.set(name, panel);
|
|
450
|
+
|
|
451
|
+
return panel;
|
|
396
452
|
}
|
|
397
453
|
|
|
398
|
-
|
|
454
|
+
/**
|
|
455
|
+
* Set texture source for a panel (Three.js render target)
|
|
456
|
+
* Auto-detects WebGL/WebGPU and extracts native handles
|
|
457
|
+
* @param name - Panel name
|
|
458
|
+
* @param source - Three.js RenderTarget or native texture
|
|
459
|
+
*/
|
|
460
|
+
public setTexture(name: string, source: ThreeTextureSource | any): void {
|
|
461
|
+
// Update preview dimensions based on current renderer
|
|
462
|
+
this.updateTexturePreviewDimensions();
|
|
463
|
+
|
|
464
|
+
// Initialize capture helpers if needed
|
|
465
|
+
if (this.gl && !this.textureCaptureWebGL) {
|
|
466
|
+
this.textureCaptureWebGL = new TextureCaptureWebGL(this.gl, this.texturePreviewWidth, this.texturePreviewHeight);
|
|
467
|
+
}
|
|
468
|
+
if (this.gpuDevice && !this.textureCaptureWebGPU) {
|
|
469
|
+
this.textureCaptureWebGPU = new TextureCaptureWebGPU(this.gpuDevice, this.texturePreviewWidth, this.texturePreviewHeight);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const panel = this.texturePanels.get(name);
|
|
473
|
+
|
|
474
|
+
// Handle Three.js WebGLRenderTarget
|
|
475
|
+
if ((source as ThreeTextureSource).isWebGLRenderTarget && this.gl) {
|
|
476
|
+
const webglSource = extractWebGLSource(source as ThreeTextureSource, this.gl);
|
|
477
|
+
if (webglSource) {
|
|
478
|
+
this.textureSourcesWebGL.set(name, {
|
|
479
|
+
target: source as ThreeTextureSource,
|
|
480
|
+
...webglSource
|
|
481
|
+
});
|
|
482
|
+
// Set source aspect ratio on panel
|
|
483
|
+
if (panel) {
|
|
484
|
+
panel.setSourceSize(webglSource.width, webglSource.height);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
399
489
|
|
|
490
|
+
// Handle Three.js WebGPU RenderTarget
|
|
491
|
+
if ((source as ThreeTextureSource).isRenderTarget && this.gpuBackend) {
|
|
492
|
+
const gpuTexture = extractWebGPUSource(source as ThreeTextureSource, this.gpuBackend);
|
|
493
|
+
if (gpuTexture) {
|
|
494
|
+
this.textureSourcesWebGPU.set(name, gpuTexture);
|
|
495
|
+
// Set source aspect ratio on panel (use source dimensions if available)
|
|
496
|
+
if (panel && (source as ThreeTextureSource).width && (source as ThreeTextureSource).height) {
|
|
497
|
+
panel.setSourceSize((source as ThreeTextureSource).width!, (source as ThreeTextureSource).height!);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
400
502
|
|
|
401
|
-
|
|
503
|
+
// Handle raw GPUTexture (check for createView method)
|
|
504
|
+
if (source && typeof source.createView === 'function') {
|
|
505
|
+
this.textureSourcesWebGPU.set(name, source);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
402
508
|
|
|
403
|
-
|
|
509
|
+
// Handle raw WebGLFramebuffer (need width/height from user)
|
|
510
|
+
// For raw FBOs, user should call setTextureWebGL directly
|
|
511
|
+
}
|
|
404
512
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
513
|
+
/**
|
|
514
|
+
* Set WebGL framebuffer source with explicit dimensions
|
|
515
|
+
* @param name - Panel name
|
|
516
|
+
* @param framebuffer - WebGL framebuffer
|
|
517
|
+
* @param width - Texture width
|
|
518
|
+
* @param height - Texture height
|
|
519
|
+
*/
|
|
520
|
+
public setTextureWebGL(name: string, framebuffer: WebGLFramebuffer, width: number, height: number): void {
|
|
521
|
+
// Update preview dimensions based on current renderer
|
|
522
|
+
this.updateTexturePreviewDimensions();
|
|
523
|
+
|
|
524
|
+
if (this.gl && !this.textureCaptureWebGL) {
|
|
525
|
+
this.textureCaptureWebGL = new TextureCaptureWebGL(this.gl, this.texturePreviewWidth, this.texturePreviewHeight);
|
|
526
|
+
}
|
|
527
|
+
this.textureSourcesWebGL.set(name, {
|
|
528
|
+
target: { isWebGLRenderTarget: true } as ThreeTextureSource,
|
|
529
|
+
framebuffer,
|
|
530
|
+
width,
|
|
531
|
+
height
|
|
532
|
+
});
|
|
533
|
+
// Set source aspect ratio on panel
|
|
534
|
+
const panel = this.texturePanels.get(name);
|
|
535
|
+
if (panel) {
|
|
536
|
+
panel.setSourceSize(width, height);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
409
539
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
540
|
+
/**
|
|
541
|
+
* Set texture from ImageBitmap (for worker mode)
|
|
542
|
+
* @param name - Panel name
|
|
543
|
+
* @param bitmap - ImageBitmap transferred from worker
|
|
544
|
+
* @param sourceWidth - Optional source texture width for aspect ratio
|
|
545
|
+
* @param sourceHeight - Optional source texture height for aspect ratio
|
|
546
|
+
*/
|
|
547
|
+
public setTextureBitmap(name: string, bitmap: ImageBitmap, sourceWidth?: number, sourceHeight?: number): void {
|
|
548
|
+
const panel = this.texturePanels.get(name);
|
|
549
|
+
if (panel) {
|
|
550
|
+
// Set source size for proper aspect ratio if provided
|
|
551
|
+
if (sourceWidth !== undefined && sourceHeight !== undefined) {
|
|
552
|
+
panel.setSourceSize(sourceWidth, sourceHeight);
|
|
553
|
+
}
|
|
554
|
+
panel.updateTexture(bitmap);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Remove a texture panel
|
|
560
|
+
* @param name - Panel name to remove
|
|
561
|
+
*/
|
|
562
|
+
public removeTexturePanel(name: string): void {
|
|
563
|
+
const panel = this.texturePanels.get(name);
|
|
564
|
+
if (panel) {
|
|
565
|
+
panel.dispose();
|
|
566
|
+
panel.canvas.remove();
|
|
567
|
+
this.texturePanels.delete(name);
|
|
568
|
+
this.textureSourcesWebGL.delete(name);
|
|
569
|
+
this.textureSourcesWebGPU.delete(name);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Capture and update all texture panels
|
|
575
|
+
* Called automatically during renderPanels at graphsPerSecond rate
|
|
576
|
+
*/
|
|
577
|
+
private async updateTexturePanels(): Promise<void> {
|
|
578
|
+
// Check for renderer dimension changes every frame
|
|
579
|
+
this.updateTexturePreviewDimensions();
|
|
580
|
+
|
|
581
|
+
// Update WebGL textures
|
|
582
|
+
if (this.textureCaptureWebGL) {
|
|
583
|
+
for (const [name, source] of this.textureSourcesWebGL) {
|
|
584
|
+
const panel = this.texturePanels.get(name);
|
|
585
|
+
if (panel) {
|
|
586
|
+
// Re-extract framebuffer for Three.js targets (may change each frame)
|
|
587
|
+
let framebuffer = source.framebuffer;
|
|
588
|
+
let width = source.width;
|
|
589
|
+
let height = source.height;
|
|
590
|
+
|
|
591
|
+
if (source.target.isWebGLRenderTarget && source.target.__webglFramebuffer) {
|
|
592
|
+
framebuffer = source.target.__webglFramebuffer;
|
|
593
|
+
width = source.target.width || width;
|
|
594
|
+
height = source.target.height || height;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const bitmap = await this.textureCaptureWebGL.capture(framebuffer, width, height, name);
|
|
598
|
+
if (bitmap) {
|
|
599
|
+
panel.updateTexture(bitmap);
|
|
600
|
+
}
|
|
416
601
|
}
|
|
417
602
|
}
|
|
418
|
-
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Update WebGPU textures
|
|
606
|
+
if (this.textureCaptureWebGPU) {
|
|
607
|
+
for (const [name, gpuTexture] of this.textureSourcesWebGPU) {
|
|
608
|
+
const panel = this.texturePanels.get(name);
|
|
609
|
+
if (panel) {
|
|
610
|
+
const bitmap = await this.textureCaptureWebGPU.capture(gpuTexture);
|
|
611
|
+
if (bitmap) {
|
|
612
|
+
panel.updateTexture(bitmap);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Capture StatsGL nodes registered by the addon
|
|
621
|
+
*/
|
|
622
|
+
private captureStatsGLNodes(): void {
|
|
623
|
+
const captures = (this as any)._statsGLCaptures as Map<string, any> | undefined;
|
|
624
|
+
if (!captures || captures.size === 0 || !this.renderer) return;
|
|
419
625
|
|
|
626
|
+
for (const captureData of captures.values()) {
|
|
627
|
+
if (captureData.capture) {
|
|
628
|
+
captureData.capture(this.renderer);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
420
631
|
}
|
|
632
|
+
|
|
421
633
|
private detectVSync(currentTime: number): void {
|
|
422
634
|
if (this.lastFrameTime === 0) {
|
|
423
635
|
this.lastFrameTime = currentTime;
|
|
424
636
|
return;
|
|
425
637
|
}
|
|
426
638
|
|
|
427
|
-
// Calculate frame time
|
|
428
639
|
const frameTime = currentTime - this.lastFrameTime;
|
|
429
640
|
this.lastFrameTime = currentTime;
|
|
430
641
|
|
|
431
|
-
// Add to histories
|
|
432
642
|
this.frameTimeHistory.push(frameTime);
|
|
433
643
|
if (this.frameTimeHistory.length > this.HISTORY_SIZE) {
|
|
434
644
|
this.frameTimeHistory.shift();
|
|
435
645
|
}
|
|
436
646
|
|
|
437
|
-
// Only start detection when we have enough samples
|
|
438
647
|
if (this.frameTimeHistory.length < 60) return;
|
|
439
648
|
|
|
440
|
-
// Calculate average frame time
|
|
441
649
|
const avgFrameTime = this.frameTimeHistory.reduce((a, b) => a + b) / this.frameTimeHistory.length;
|
|
442
650
|
|
|
443
|
-
// Calculate frame time stability (standard deviation)
|
|
444
651
|
const variance = this.frameTimeHistory.reduce((acc, time) =>
|
|
445
652
|
acc + Math.pow(time - avgFrameTime, 2), 0) / this.frameTimeHistory.length;
|
|
446
653
|
const stability = Math.sqrt(variance);
|
|
447
654
|
|
|
448
|
-
|
|
449
|
-
if (stability > 2) { // 2ms stability threshold
|
|
655
|
+
if (stability > 2) {
|
|
450
656
|
this.detectedVSync = null;
|
|
451
657
|
return;
|
|
452
658
|
}
|
|
453
659
|
|
|
454
|
-
// Find the closest VSync rate based on frame time
|
|
455
660
|
let closestMatch: VSyncInfo | null = null;
|
|
456
661
|
let smallestDiff = Infinity;
|
|
457
662
|
|
|
@@ -468,54 +673,6 @@ class Stats {
|
|
|
468
673
|
} else {
|
|
469
674
|
this.detectedVSync = null;
|
|
470
675
|
}
|
|
471
|
-
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
endInternal() {
|
|
475
|
-
const currentTime = performance.now();
|
|
476
|
-
|
|
477
|
-
this.frameTimes.push(currentTime);
|
|
478
|
-
|
|
479
|
-
// Remove frames older than 1 second
|
|
480
|
-
while (this.frameTimes.length > 0 && this.frameTimes[0] <= currentTime - 1000) {
|
|
481
|
-
this.frameTimes.shift();
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Calculate FPS based on frames in the last second
|
|
485
|
-
const fps = Math.round(this.frameTimes.length);
|
|
486
|
-
|
|
487
|
-
this.addToAverage(fps, this.averageFps);
|
|
488
|
-
|
|
489
|
-
const shouldUpdateText = currentTime >= this.prevTextTime + 1000 / this.logsPerSecond;
|
|
490
|
-
const shouldUpdateGraph = currentTime >= this.prevGraphTime + 1000 / this.graphsPerSecond;
|
|
491
|
-
|
|
492
|
-
this.updatePanelComponents(this.fpsPanel, this.averageFps, 0, shouldUpdateText, shouldUpdateGraph);
|
|
493
|
-
this.updatePanelComponents(this.msPanel, this.averageCpu, this.precision, shouldUpdateText, shouldUpdateGraph);
|
|
494
|
-
if (this.gpuPanel) {
|
|
495
|
-
this.updatePanelComponents(this.gpuPanel, this.averageGpu, this.precision, shouldUpdateText, shouldUpdateGraph);
|
|
496
|
-
}
|
|
497
|
-
if (this.trackCPT && this.gpuPanelCompute) {
|
|
498
|
-
this.updatePanelComponents(this.gpuPanelCompute, this.averageGpuCompute, this.precision, shouldUpdateText, shouldUpdateGraph);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
if (shouldUpdateText) {
|
|
502
|
-
this.prevTextTime = currentTime;
|
|
503
|
-
}
|
|
504
|
-
if (shouldUpdateGraph) {
|
|
505
|
-
this.prevGraphTime = currentTime;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
if (this.vsyncPanel !== null) {
|
|
509
|
-
this.detectVSync(currentTime);
|
|
510
|
-
|
|
511
|
-
const vsyncValue = this.detectedVSync?.refreshRate || 0;
|
|
512
|
-
|
|
513
|
-
if (shouldUpdateText && vsyncValue > 0) {
|
|
514
|
-
this.vsyncPanel.update(vsyncValue, vsyncValue);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
return currentTime;
|
|
519
676
|
}
|
|
520
677
|
|
|
521
678
|
private updatePanelComponents(
|
|
@@ -523,25 +680,26 @@ class Stats {
|
|
|
523
680
|
averageArray: { logs: number[], graph: number[] },
|
|
524
681
|
precision: number,
|
|
525
682
|
shouldUpdateText: boolean,
|
|
526
|
-
shouldUpdateGraph: boolean
|
|
683
|
+
shouldUpdateGraph: boolean,
|
|
684
|
+
suffix = ''
|
|
527
685
|
) {
|
|
528
686
|
if (!panel || averageArray.logs.length === 0) return;
|
|
529
687
|
|
|
530
|
-
//
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
this.
|
|
688
|
+
// Use panel.id as key to avoid collision between panels with same name
|
|
689
|
+
const key = String(panel.id);
|
|
690
|
+
|
|
691
|
+
if (!(key in this.lastMin)) {
|
|
692
|
+
this.lastMin[key] = Infinity;
|
|
693
|
+
this.lastMax[key] = 0;
|
|
694
|
+
this.lastValue[key] = 0;
|
|
535
695
|
}
|
|
536
696
|
|
|
537
697
|
const currentValue = averageArray.logs[averageArray.logs.length - 1];
|
|
538
698
|
|
|
539
|
-
this.lastMax[
|
|
540
|
-
this.lastMin[
|
|
541
|
-
|
|
542
|
-
this.lastValue[panel.name] = this.lastValue[panel.name] * 0.7 + currentValue * 0.3;
|
|
699
|
+
this.lastMax[key] = Math.max(...averageArray.logs);
|
|
700
|
+
this.lastMin[key] = Math.min(this.lastMin[key], currentValue);
|
|
701
|
+
this.lastValue[key] = this.lastValue[key] * 0.7 + currentValue * 0.3;
|
|
543
702
|
|
|
544
|
-
// Calculate graph max considering both recent values and graph history
|
|
545
703
|
const graphMax = Math.max(
|
|
546
704
|
Math.max(...averageArray.logs),
|
|
547
705
|
...averageArray.graph.slice(-this.samplesGraph)
|
|
@@ -549,16 +707,15 @@ class Stats {
|
|
|
549
707
|
|
|
550
708
|
this.updateCounter++;
|
|
551
709
|
|
|
552
|
-
// Update text if it's time
|
|
553
710
|
if (shouldUpdateText) {
|
|
554
711
|
panel.update(
|
|
555
|
-
this.lastValue[
|
|
556
|
-
this.lastMax[
|
|
557
|
-
precision
|
|
712
|
+
this.lastValue[key],
|
|
713
|
+
this.lastMax[key],
|
|
714
|
+
precision,
|
|
715
|
+
suffix
|
|
558
716
|
);
|
|
559
717
|
}
|
|
560
718
|
|
|
561
|
-
// Update graph if it's time
|
|
562
719
|
if (shouldUpdateGraph) {
|
|
563
720
|
panel.updateGraph(
|
|
564
721
|
currentValue,
|
|
@@ -567,84 +724,35 @@ class Stats {
|
|
|
567
724
|
}
|
|
568
725
|
}
|
|
569
726
|
|
|
570
|
-
private beginProfiling(marker: string): void {
|
|
571
|
-
if (window.performance) {
|
|
572
|
-
try {
|
|
573
|
-
window.performance.clearMarks(marker);
|
|
574
|
-
window.performance.mark(marker);
|
|
575
|
-
} catch (error) {
|
|
576
|
-
console.debug('Stats: Performance marking failed:', error);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
private endProfiling(startMarker: string | PerformanceMeasureOptions | undefined, endMarker: string | undefined, measureName: string): void {
|
|
582
|
-
if (!window.performance || !endMarker || !startMarker) return;
|
|
583
|
-
|
|
584
|
-
try {
|
|
585
|
-
// First check if the start mark exists
|
|
586
|
-
const entries = window.performance.getEntriesByName(startMarker as string, 'mark');
|
|
587
|
-
if (entries.length === 0) {
|
|
588
|
-
// If start mark doesn't exist, create it now with the same timestamp as end
|
|
589
|
-
this.beginProfiling(startMarker as string);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
// Create the end mark
|
|
593
|
-
window.performance.clearMarks(endMarker);
|
|
594
|
-
window.performance.mark(endMarker);
|
|
595
|
-
|
|
596
|
-
// Clear any existing measure with the same name
|
|
597
|
-
window.performance.clearMeasures(measureName);
|
|
598
|
-
|
|
599
|
-
// Create the measurement
|
|
600
|
-
const cpuMeasure = performance.measure(measureName, startMarker, endMarker);
|
|
601
|
-
this.totalCpuDuration += cpuMeasure.duration;
|
|
602
|
-
|
|
603
|
-
// Clean up
|
|
604
|
-
window.performance.clearMarks(startMarker as string);
|
|
605
|
-
window.performance.clearMarks(endMarker);
|
|
606
|
-
window.performance.clearMeasures(measureName);
|
|
607
|
-
} catch (error) {
|
|
608
|
-
console.debug('Stats: Performance measurement failed:', error);
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
727
|
updatePanel(panel: { update: any; updateGraph: any; name: string; } | null, averageArray: { logs: number[], graph: number[] }, precision = 2) {
|
|
613
728
|
if (!panel || averageArray.logs.length === 0) return;
|
|
614
729
|
|
|
615
730
|
const currentTime = performance.now();
|
|
616
731
|
|
|
617
|
-
// Initialize tracking for this panel if not exists
|
|
618
732
|
if (!(panel.name in this.lastMin)) {
|
|
619
733
|
this.lastMin[panel.name] = Infinity;
|
|
620
734
|
this.lastMax[panel.name] = 0;
|
|
621
735
|
this.lastValue[panel.name] = 0;
|
|
622
736
|
}
|
|
623
737
|
|
|
624
|
-
// Get the current value and recent max
|
|
625
738
|
const currentValue = averageArray.logs[averageArray.logs.length - 1];
|
|
626
739
|
const recentMax = Math.max(...averageArray.logs.slice(-30));
|
|
627
740
|
|
|
628
|
-
// Update running statistics
|
|
629
741
|
this.lastMin[panel.name] = Math.min(this.lastMin[panel.name], currentValue);
|
|
630
742
|
this.lastMax[panel.name] = Math.max(this.lastMax[panel.name], currentValue);
|
|
631
743
|
|
|
632
|
-
// Smooth the display value
|
|
633
744
|
this.lastValue[panel.name] = this.lastValue[panel.name] * 0.7 + currentValue * 0.3;
|
|
634
745
|
|
|
635
|
-
// Calculate graph scaling value
|
|
636
746
|
const graphMax = Math.max(recentMax, ...averageArray.graph.slice(-this.samplesGraph));
|
|
637
747
|
|
|
638
748
|
this.updateCounter++;
|
|
639
749
|
|
|
640
|
-
// Reset min/max periodically
|
|
641
750
|
if (this.updateCounter % (this.logsPerSecond * 2) === 0) {
|
|
642
751
|
this.lastMax[panel.name] = recentMax;
|
|
643
752
|
this.lastMin[panel.name] = currentValue;
|
|
644
753
|
}
|
|
645
754
|
|
|
646
755
|
if (panel.update) {
|
|
647
|
-
// Check if it's time to update the text (based on logsPerSecond)
|
|
648
756
|
if (currentTime >= this.prevCpuTime + 1000 / this.logsPerSecond) {
|
|
649
757
|
panel.update(
|
|
650
758
|
this.lastValue[panel.name],
|
|
@@ -655,7 +763,6 @@ class Stats {
|
|
|
655
763
|
);
|
|
656
764
|
}
|
|
657
765
|
|
|
658
|
-
// Check if it's time to update the graph (based on graphsPerSecond)
|
|
659
766
|
if (currentTime >= this.prevGraphTime + 1000 / this.graphsPerSecond) {
|
|
660
767
|
panel.updateGraph(
|
|
661
768
|
currentValue,
|
|
@@ -666,82 +773,80 @@ class Stats {
|
|
|
666
773
|
}
|
|
667
774
|
}
|
|
668
775
|
|
|
669
|
-
private updateAverages(): void {
|
|
670
|
-
|
|
671
|
-
this.addToAverage(this.totalCpuDuration, this.averageCpu);
|
|
672
|
-
this.addToAverage(this.totalGpuDuration, this.averageGpu);
|
|
673
|
-
// Add GPU Compute to the main update flow
|
|
674
|
-
if (this.info && this.totalGpuDurationCompute !== undefined) {
|
|
675
|
-
this.addToAverage(this.totalGpuDurationCompute, this.averageGpuCompute);
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
addToAverage(value: number, averageArray: { logs: any; graph: any; }) {
|
|
680
|
-
// Validate value
|
|
681
|
-
// if (value === undefined || value === null || isNaN(value) || value === 0) {
|
|
682
|
-
// return;
|
|
683
|
-
// }
|
|
684
|
-
|
|
685
|
-
// Store raw values for logs
|
|
686
|
-
averageArray.logs.push(value);
|
|
687
|
-
if (averageArray.logs.length > this.samplesLog) {
|
|
688
|
-
averageArray.logs = averageArray.logs.slice(-this.samplesLog);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// For graph, store raw values
|
|
692
|
-
averageArray.graph.push(value);
|
|
693
|
-
if (averageArray.graph.length > this.samplesGraph) {
|
|
694
|
-
averageArray.graph = averageArray.graph.slice(-this.samplesGraph);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
|
|
698
776
|
get domElement() {
|
|
699
|
-
// patch for some use case in threejs
|
|
700
777
|
return this.dom;
|
|
701
|
-
|
|
702
778
|
}
|
|
703
779
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
originalAnimationLoop.call(this);
|
|
715
|
-
|
|
780
|
+
/**
|
|
781
|
+
* Dispose of all resources. Call when done using Stats.
|
|
782
|
+
*/
|
|
783
|
+
public override dispose(): void {
|
|
784
|
+
// Remove event listeners
|
|
785
|
+
if (this.minimal) {
|
|
786
|
+
this.dom.removeEventListener('click', this.handleClick);
|
|
787
|
+
} else {
|
|
788
|
+
window.removeEventListener('resize', this.handleResize);
|
|
716
789
|
}
|
|
717
790
|
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
// Override the render method on the prototype
|
|
729
|
-
renderer.render = function (scene: THREE.Scene, camera: THREE.Camera) {
|
|
730
|
-
|
|
731
|
-
statsInstance.begin(); // Start tracking for this render call
|
|
791
|
+
// Dispose texture capture helpers
|
|
792
|
+
if (this.textureCaptureWebGL) {
|
|
793
|
+
this.textureCaptureWebGL.dispose();
|
|
794
|
+
this.textureCaptureWebGL = null;
|
|
795
|
+
}
|
|
796
|
+
if (this.textureCaptureWebGPU) {
|
|
797
|
+
this.textureCaptureWebGPU.dispose();
|
|
798
|
+
this.textureCaptureWebGPU = null;
|
|
799
|
+
}
|
|
732
800
|
|
|
733
|
-
|
|
734
|
-
|
|
801
|
+
// Dispose all texture panels
|
|
802
|
+
for (const panel of this.texturePanels.values()) {
|
|
803
|
+
panel.dispose();
|
|
804
|
+
}
|
|
805
|
+
this.texturePanels.clear();
|
|
806
|
+
this.textureSourcesWebGL.clear();
|
|
807
|
+
this.textureSourcesWebGPU.clear();
|
|
735
808
|
|
|
736
|
-
|
|
809
|
+
// Dispose StatsGL captures if any
|
|
810
|
+
const captures = (this as any)._statsGLCaptures as Map<string, any> | undefined;
|
|
811
|
+
if (captures) {
|
|
812
|
+
for (const captureData of captures.values()) {
|
|
813
|
+
if (captureData.dispose) {
|
|
814
|
+
captureData.dispose();
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
captures.clear();
|
|
818
|
+
}
|
|
737
819
|
|
|
738
|
-
|
|
820
|
+
// Remove DOM element
|
|
821
|
+
if (this.texturePanelRow) {
|
|
822
|
+
this.texturePanelRow.remove();
|
|
823
|
+
this.texturePanelRow = null;
|
|
824
|
+
}
|
|
825
|
+
this.dom.remove();
|
|
739
826
|
|
|
827
|
+
// Clear panel references
|
|
828
|
+
this.fpsPanel = null;
|
|
829
|
+
this.msPanel = null;
|
|
830
|
+
this.gpuPanel = null;
|
|
831
|
+
this.gpuPanelCompute = null;
|
|
832
|
+
this.vsyncPanel = null;
|
|
833
|
+
this.workerCpuPanel = null;
|
|
740
834
|
|
|
741
|
-
|
|
835
|
+
// Clear tracking arrays
|
|
836
|
+
this.frameTimeHistory.length = 0;
|
|
837
|
+
this.averageWorkerCpu.logs.length = 0;
|
|
838
|
+
this.averageWorkerCpu.graph.length = 0;
|
|
742
839
|
|
|
840
|
+
// Call parent dispose
|
|
841
|
+
super.dispose();
|
|
743
842
|
}
|
|
744
843
|
}
|
|
745
844
|
|
|
746
845
|
|
|
747
846
|
export default Stats;
|
|
847
|
+
export type { StatsData } from './core';
|
|
848
|
+
export { StatsProfiler } from './profiler';
|
|
849
|
+
export { PanelTexture } from './panelTexture';
|
|
850
|
+
export { TextureCaptureWebGL, TextureCaptureWebGPU } from './textureCapture';
|
|
851
|
+
export { StatsGLCapture } from './statsGLNode';
|
|
852
|
+
|