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