stats-gl 2.2.8 → 2.3.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/lib/main.ts CHANGED
@@ -1,156 +1,287 @@
1
- import Panel from "./panel";
2
1
  import * as THREE from 'three';
3
- export interface AverageArray {
2
+ import { Panel } from './panel';
3
+
4
+ interface StatsOptions {
5
+ trackGPU?: boolean;
6
+ logsPerSecond?: number;
7
+ samplesLog?: number;
8
+ samplesGraph?: number;
9
+ precision?: number;
10
+ minimal?: boolean;
11
+ horizontal?: boolean;
12
+ mode?: number;
13
+ }
14
+
15
+ interface QueryInfo {
16
+ query: WebGLQuery;
17
+ }
18
+
19
+ interface AverageData {
4
20
  logs: number[];
5
21
  graph: number[];
6
22
  }
7
23
 
24
+ interface InfoData {
25
+ render: {
26
+ timestamp: number;
27
+ };
28
+ compute: {
29
+ timestamp: number;
30
+ };
31
+ }
8
32
 
9
33
  class Stats {
10
- totalCpuDuration: number = 0;
11
- totalGpuDuration: number = 0;
12
- totalGpuDurationCompute: number = 0;
13
- totalFps: number = 0;
14
- mode: number;
15
- info: any;
16
- dom: HTMLDivElement;
17
- minimal: boolean;
18
- horizontal: boolean;
19
- beginTime: number;
20
- prevTime: number;
21
- prevCpuTime: number;
22
- frames: number;
23
- averageCpu: AverageArray;
24
- averageGpu: AverageArray;
25
- averageGpuCompute: AverageArray;
26
- queryCreated: boolean;
27
- isRunningCPUProfiling: boolean;
28
- fpsPanel: Panel;
29
- static Panel: typeof Panel = Panel;
30
- msPanel: Panel;
31
- gpuPanel: Panel | null;
32
- gpuPanelCompute: Panel | null;
33
- samplesLog: number;
34
- samplesGraph: number;
35
- logsPerSecond: number;
36
- activeQuery: WebGLQuery | null = null;
37
-
38
- precision: number;
39
- gl: WebGL2RenderingContext | null;
40
- ext: any;
41
- query: WebGLQuery | null;
42
- disjoint: any;
43
- ns: any;
44
- threeRendererPatched: boolean;
45
- gpuQueries: { query: WebGLQuery }[] = [];
46
- renderCount: number = 0;
47
-
48
- constructor({ logsPerSecond = 20, samplesLog = 100, samplesGraph = 10, precision = 2, minimal = false, horizontal = true, mode = 0 } = {}) {
49
-
34
+ private dom: HTMLDivElement;
35
+ private mode: number;
36
+ private horizontal: boolean;
37
+ private minimal: boolean;
38
+ private trackGPU: boolean;
39
+ private samplesLog: number;
40
+ private samplesGraph: number;
41
+ private precision: number;
42
+ private logsPerSecond: number;
43
+
44
+ private gl: WebGL2RenderingContext | null = null;
45
+ private ext: any | null = null;
46
+ private info?: InfoData;
47
+ private activeQuery: WebGLQuery | null = null;
48
+ private gpuQueries: QueryInfo[] = [];
49
+ private threeRendererPatched = false;
50
+
51
+ private beginTime: number;
52
+ private prevTime: number;
53
+ private prevCpuTime: number;
54
+ private frames = 0;
55
+ private renderCount = 0;
56
+ private isRunningCPUProfiling = false;
57
+
58
+ private totalCpuDuration = 0;
59
+ private totalGpuDuration = 0;
60
+ private totalGpuDurationCompute = 0;
61
+ private totalFps = 0;
62
+
63
+ private fpsPanel: Panel;
64
+ private msPanel: Panel;
65
+ private gpuPanel: Panel | null = null;
66
+ private gpuPanelCompute: Panel | null = null;
67
+
68
+ private averageCpu: AverageData = { logs: [], graph: [] };
69
+ private averageGpu: AverageData = { logs: [], graph: [] };
70
+ private averageGpuCompute: AverageData = { logs: [], graph: [] };
71
+
72
+ static Panel = Panel;
73
+
74
+ constructor({
75
+ trackGPU = false,
76
+ logsPerSecond = 20,
77
+ samplesLog = 100,
78
+ samplesGraph = 10,
79
+ precision = 2,
80
+ minimal = false,
81
+ horizontal = true,
82
+ mode = 0
83
+ }: StatsOptions = {}) {
50
84
  this.mode = mode;
51
85
  this.horizontal = horizontal;
52
- this.dom = document.createElement('div');
53
- this.dom.style.cssText = 'position:fixed;top:0;left:0;opacity:0.9;z-index:10000;';
54
-
55
- if (minimal) {
56
-
57
- this.dom.style.cssText += 'cursor:pointer';
58
-
59
- }
60
-
61
- this.gl = null;
62
- this.query = null;
63
-
64
- this.isRunningCPUProfiling = false;
65
86
  this.minimal = minimal;
87
+ this.trackGPU = trackGPU;
88
+ console.log('trackGPU', trackGPU);
89
+ this.samplesLog = samplesLog;
90
+ this.samplesGraph = samplesGraph;
91
+ this.precision = precision;
92
+ this.logsPerSecond = logsPerSecond;
66
93
 
67
- this.beginTime = (performance || Date).now();
94
+ // Initialize DOM
95
+ this.dom = document.createElement('div');
96
+ this.initializeDOM();
97
+
98
+ // Initialize timing
99
+ this.beginTime = performance.now();
68
100
  this.prevTime = this.beginTime;
69
101
  this.prevCpuTime = this.beginTime;
70
- this.frames = 0;
71
- this.renderCount = 0;
72
- this.threeRendererPatched = false;
73
- this.averageCpu = {
74
- logs: [],
75
- graph: []
76
- };
77
- this.averageGpu = {
78
- logs: [],
79
- graph: []
80
- };
81
- this.averageGpuCompute = {
82
- logs: [],
83
- graph: []
84
- };
85
-
86
- this.queryCreated = false;
87
102
 
103
+ // Create panels
88
104
  this.fpsPanel = this.addPanel(new Stats.Panel('FPS', '#0ff', '#002'), 0);
89
105
  this.msPanel = this.addPanel(new Stats.Panel('CPU', '#0f0', '#020'), 1);
90
- this.gpuPanel = null;
91
- this.gpuPanelCompute = null;
92
-
93
- this.samplesLog = samplesLog;
94
- this.samplesGraph = samplesGraph;
95
- this.precision = precision;
96
- this.logsPerSecond = logsPerSecond;
97
106
 
98
- if (this.minimal) {
107
+ this.setupEventListeners();
108
+ }
99
109
 
100
- this.dom.addEventListener('click', (event) => {
110
+ private initializeDOM(): void {
111
+ this.dom.style.cssText = `
112
+ position: fixed;
113
+ top: 0;
114
+ left: 0;
115
+ opacity: 0.9;
116
+ z-index: 10000;
117
+ ${this.minimal ? 'cursor: pointer;' : ''}
118
+ `;
119
+ }
101
120
 
102
- event.preventDefault();
103
- this.showPanel(++this.mode % this.dom.children.length);
121
+ private setupEventListeners(): void {
122
+ if (this.minimal) {
123
+ this.dom.addEventListener('click', this.handleClick);
124
+ this.showPanel(this.mode);
125
+ } else {
126
+ window.addEventListener('resize', this.handleResize);
127
+ }
128
+ }
104
129
 
105
- }, false);
130
+ private handleClick = (event: MouseEvent): void => {
131
+ event.preventDefault();
132
+ this.showPanel(++this.mode % this.dom.children.length);
133
+ };
134
+
135
+ private handleResize = (): void => {
136
+ this.resizePanel(this.fpsPanel, 0);
137
+ this.resizePanel(this.msPanel, 1);
138
+ if (this.gpuPanel) this.resizePanel(this.gpuPanel, 2);
139
+ if (this.gpuPanelCompute) this.resizePanel(this.gpuPanelCompute, 3);
140
+ };
141
+
142
+ public async init(
143
+ canvasOrGL: WebGL2RenderingContext | HTMLCanvasElement | OffscreenCanvas | any
144
+ ): Promise<void> {
145
+ if (!canvasOrGL) {
146
+ console.error('Stats: The "canvas" parameter is undefined.');
147
+ return;
148
+ }
106
149
 
107
- this.mode = mode;
108
- this.showPanel(this.mode);
150
+ if (this.handleThreeRenderer(canvasOrGL)) return;
151
+ if (await this.handleWebGPURenderer(canvasOrGL)) return;
152
+ if (!this.initializeWebGL(canvasOrGL)) return;
109
153
 
110
- } else {
154
+ }
111
155
 
112
- window.addEventListener('resize', () => {
156
+ private handleThreeRenderer(renderer: any): boolean {
157
+ if (renderer.isWebGLRenderer && !this.threeRendererPatched) {
158
+ this.patchThreeRenderer(renderer);
159
+ this.gl = renderer.getContext();
113
160
 
114
- this.resizePanel(this.fpsPanel, 0);
115
- this.resizePanel(this.msPanel, 1);
161
+ if (this.trackGPU) {
162
+ this.initializeGPUTracking();
163
+ }
164
+ return true;
165
+ }
166
+ return false;
167
+ }
116
168
 
117
- if (this.gpuPanel) {
118
- this.resizePanel(this.gpuPanel, 2);
119
- }
120
- if (this.gpuPanelCompute) {
121
- this.resizePanel(this.gpuPanelCompute, 3);
169
+ private async handleWebGPURenderer(renderer: any): Promise<boolean> {
170
+ console.log('renderer', renderer);
171
+ if (renderer.isWebGPURenderer) {
172
+ if (this.trackGPU) {
173
+ console.log('trackGPU', this.trackGPU);
174
+ renderer.backend.trackTimestamp = true;
175
+ if (await renderer.hasFeatureAsync('timestamp-query')) {
176
+ this.initializeWebGPUPanels();
122
177
  }
123
- })
178
+ }
179
+ this.info = renderer.info;
180
+ return true;
124
181
  }
182
+ return false;
183
+ }
125
184
 
185
+ private initializeWebGPUPanels(): void {
186
+ this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
187
+ this.gpuPanelCompute = this.addPanel(
188
+ new Stats.Panel('CPT', '#e1e1e1', '#212121'),
189
+ 3
190
+ );
191
+ }
126
192
 
193
+ private initializeWebGL(
194
+ canvasOrGL: WebGL2RenderingContext | HTMLCanvasElement | OffscreenCanvas
195
+ ): boolean {
196
+ if (canvasOrGL instanceof WebGL2RenderingContext) {
197
+ this.gl = canvasOrGL;
198
+ } else if (
199
+ canvasOrGL instanceof HTMLCanvasElement ||
200
+ canvasOrGL instanceof OffscreenCanvas
201
+ ) {
202
+ this.gl = canvasOrGL.getContext('webgl2');
203
+ if (!this.gl) {
204
+ console.error('Stats: Unable to obtain WebGL2 context.');
205
+ return false;
206
+ }
207
+ } else {
208
+ console.error(
209
+ 'Stats: Invalid input type. Expected WebGL2RenderingContext, HTMLCanvasElement, or OffscreenCanvas.'
210
+ );
211
+ return false;
212
+ }
213
+ return true;
127
214
  }
128
215
 
129
- patchThreeRenderer(renderer: any) {
216
+ private initializeGPUTracking(): void {
217
+ if (this.gl) {
218
+ this.ext = this.gl.getExtension('EXT_disjoint_timer_query_webgl2');
219
+ if (this.ext) {
220
+ this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
221
+ }
222
+ }
223
+ }
130
224
 
131
- // Store the original render method
132
- const originalRenderMethod = renderer.render;
225
+ public begin(): void {
226
+ if (!this.isRunningCPUProfiling) {
227
+ this.beginProfiling('cpu-started');
228
+ }
133
229
 
134
- // Reference to the stats instance
135
- const statsInstance = this;
230
+ if (!this.gl || !this.ext) return;
136
231
 
137
- // Override the render method on the prototype
138
- renderer.render = function (scene: THREE.Scene, camera: THREE.Camera) {
232
+ if (this.activeQuery) {
233
+ this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
234
+ }
139
235
 
236
+ this.activeQuery = this.gl.createQuery();
237
+ if (this.activeQuery) {
238
+ this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, this.activeQuery);
239
+ }
240
+ }
140
241
 
141
- statsInstance.begin(); // Start tracking for this render call
242
+ public end(): void {
243
+ this.renderCount++;
244
+ if (this.gl && this.ext && this.activeQuery) {
245
+ this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
246
+ this.gpuQueries.push({ query: this.activeQuery });
247
+ this.activeQuery = null;
248
+ }
249
+ }
142
250
 
143
- // Call the original render method
144
- originalRenderMethod.call(this, scene, camera);
251
+ public update(): void {
252
+ if (!this.info) {
253
+ this.processGpuQueries();
254
+ } else {
255
+ this.processWebGPUTimestamps();
256
+ }
145
257
 
146
- statsInstance.end(); // End tracking for this render call
147
- };
258
+ this.endProfiling('cpu-started', 'cpu-finished', 'cpu-duration');
259
+ this.updateAverages();
260
+ this.resetCounters();
261
+ }
148
262
 
263
+ private processWebGPUTimestamps(): void {
264
+ this.totalGpuDuration = this.info!.render.timestamp;
265
+ this.totalGpuDurationCompute = this.info!.compute.timestamp;
266
+ this.addToAverage(this.totalGpuDurationCompute, this.averageGpuCompute);
267
+ }
149
268
 
150
- this.threeRendererPatched = true;
269
+ private updateAverages(): void {
270
+ this.addToAverage(this.totalCpuDuration, this.averageCpu);
271
+ this.addToAverage(this.totalGpuDuration, this.averageGpu);
272
+ }
151
273
 
274
+ private resetCounters(): void {
275
+ this.renderCount = 0;
276
+ if (this.totalCpuDuration === 0) {
277
+ this.beginProfiling('cpu-started');
278
+ }
279
+ this.totalCpuDuration = 0;
280
+ this.totalFps = 0;
281
+ this.beginTime = this.endInternal();
152
282
  }
153
283
 
284
+
154
285
  resizePanel(panel: Panel, offset: number) {
155
286
 
156
287
  panel.canvas.style.position = 'absolute';
@@ -171,8 +302,8 @@ class Stats {
171
302
 
172
303
  }
173
304
  }
174
- }
175
305
 
306
+ }
176
307
  addPanel(panel: Panel, offset: number) {
177
308
 
178
309
  if (panel.canvas) {
@@ -200,98 +331,6 @@ class Stats {
200
331
 
201
332
  }
202
333
 
203
- async init(canvasOrGL: any) {
204
- if (!canvasOrGL) {
205
- console.error('Stats: The "canvas" parameter is undefined.');
206
- return;
207
- }
208
-
209
-
210
- // if ((canvasOrGL as any).isWebGPURenderer && !this.threeRendererPatched) {
211
- // TODO Color GPU Analytic in another color than yellow to know webgpu or webgl context (blue)
212
- // const canvas: any = canvasOrGL
213
- // this.patchThreeRenderer(canvas as any);
214
- // this.gl = canvas.getContext();
215
- // } else
216
- if ((canvasOrGL as any).isWebGLRenderer && !this.threeRendererPatched) {
217
- const canvas: any = canvasOrGL
218
- this.patchThreeRenderer(canvas as any);
219
- this.gl = canvas.getContext();
220
- } else if (!this.gl && canvasOrGL instanceof WebGL2RenderingContext) {
221
- this.gl = canvasOrGL;
222
- }
223
-
224
- if (canvasOrGL.isWebGPURenderer) {
225
-
226
- canvasOrGL.backend.trackTimestamp = true
227
-
228
- if (await canvasOrGL.hasFeatureAsync('timestamp-query')) {
229
- this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
230
- this.gpuPanelCompute = this.addPanel(new Stats.Panel('CPT', '#e1e1e1', '#212121'), 3);
231
- this.info = canvasOrGL.info
232
- }
233
- return;
234
- }
235
- // Check if canvasOrGL is already a WebGL2RenderingContext
236
-
237
-
238
- // Handle HTMLCanvasElement and OffscreenCanvas
239
- else if (!this.gl && canvasOrGL instanceof HTMLCanvasElement || canvasOrGL instanceof OffscreenCanvas) {
240
- this.gl = canvasOrGL.getContext('webgl2') as WebGL2RenderingContext;
241
- if (!this.gl) {
242
- console.error('Stats: Unable to obtain WebGL2 context.');
243
- return;
244
- }
245
- } else if (!this.gl) {
246
- console.error('Stats: Invalid input type. Expected WebGL2RenderingContext, HTMLCanvasElement, or OffscreenCanvas.');
247
- return;
248
- }
249
-
250
- // Get the extension
251
- this.ext = this.gl.getExtension('EXT_disjoint_timer_query_webgl2');
252
- if (this.ext) {
253
- this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
254
- }
255
- }
256
-
257
-
258
- begin() {
259
-
260
- if (!this.isRunningCPUProfiling) {
261
- this.beginProfiling('cpu-started');
262
- }
263
-
264
- if (!this.gl || !this.ext) return;
265
-
266
- if (this.gl && this.ext) {
267
- if (this.activeQuery) {
268
- // End the previous query if it's still active
269
- this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
270
- }
271
-
272
- this.activeQuery = this.gl.createQuery();
273
- if (this.activeQuery !== null) {
274
- this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, this.activeQuery);
275
- }
276
- }
277
- }
278
-
279
-
280
-
281
- end() {
282
-
283
- // Increase render count
284
- this.renderCount++;
285
-
286
- if (this.gl && this.ext && this.activeQuery) {
287
- this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
288
- // Add the active query to the gpuQueries array and reset it
289
- this.gpuQueries.push({ query: this.activeQuery });
290
- this.activeQuery = null;
291
- }
292
-
293
- }
294
-
295
334
  processGpuQueries() {
296
335
 
297
336
 
@@ -316,39 +355,6 @@ class Stats {
316
355
 
317
356
  }
318
357
 
319
- update() {
320
-
321
- if (this.info === undefined) {
322
- this.processGpuQueries();
323
- } else {
324
-
325
- this.totalGpuDuration = this.info.render.timestamp
326
- this.totalGpuDurationCompute = this.info.compute.timestamp
327
- this.addToAverage(this.totalGpuDurationCompute, this.averageGpuCompute);
328
-
329
- }
330
-
331
- this.endProfiling('cpu-started', 'cpu-finished', 'cpu-duration');
332
-
333
- // Calculate the total duration of CPU and GPU work for this frame
334
- this.addToAverage(this.totalCpuDuration, this.averageCpu);
335
- this.addToAverage(this.totalGpuDuration, this.averageGpu);
336
-
337
- this.renderCount = 0;
338
-
339
- // If this.totalCpuDuration is 0, it means that the CPU query was not created and stats.begin() never called/overrided
340
- if (this.totalCpuDuration === 0) {
341
- this.beginProfiling('cpu-started');
342
- }
343
-
344
- this.totalCpuDuration = 0;
345
-
346
- this.totalFps = 0;
347
-
348
- this.beginTime = this.endInternal()
349
-
350
- }
351
-
352
358
  endInternal() {
353
359
 
354
360
  this.frames++;
@@ -464,13 +470,30 @@ class Stats {
464
470
 
465
471
  }
466
472
 
467
- get container() { // @deprecated
473
+ patchThreeRenderer(renderer: any) {
468
474
 
469
- console.warn('Stats: Deprecated! this.container as been replaced to this.dom ')
470
- return this.dom;
475
+ // Store the original render method
476
+ const originalRenderMethod = renderer.render;
471
477
 
472
- }
478
+ // Reference to the stats instance
479
+ const statsInstance = this;
480
+
481
+ // Override the render method on the prototype
482
+ renderer.render = function (scene: THREE.Scene, camera: THREE.Camera) {
473
483
 
484
+
485
+ statsInstance.begin(); // Start tracking for this render call
486
+
487
+ // Call the original render method
488
+ originalRenderMethod.call(this, scene, camera);
489
+
490
+ statsInstance.end(); // End tracking for this render call
491
+ };
492
+
493
+
494
+ this.threeRendererPatched = true;
495
+
496
+ }
474
497
  }
475
498
 
476
499
 
package/lib/panel.ts CHANGED
@@ -19,8 +19,8 @@ class Panel {
19
19
  this.name = name;
20
20
  this.fg = fg;
21
21
  this.bg = bg;
22
- this.PR = Math.round( window.devicePixelRatio || 1 );
23
-
22
+ this.PR = Math.round(window.devicePixelRatio || 1);
23
+
24
24
  this.WIDTH = 90 * this.PR;
25
25
  this.HEIGHT = 48 * this.PR;
26
26
  this.TEXT_X = 3 * this.PR;
@@ -84,4 +84,4 @@ class Panel {
84
84
  }
85
85
  };
86
86
 
87
- export default Panel;
87
+ export { Panel };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stats-gl",
3
- "version": "2.2.8",
3
+ "version": "2.3.0",
4
4
  "type": "module",
5
5
  "author": "Renaud ROHLINGER (https://github.com/RenaudRohlinger)",
6
6
  "homepage": "https://github.com/RenaudRohlinger/stats-gl",
@@ -14,7 +14,7 @@
14
14
  "main": "./dist/main.cjs",
15
15
  "module": "./dist/main.js",
16
16
  "exports": {
17
- ".": {
17
+ ".": {
18
18
  "types": "./dist/stats-gl.d.ts",
19
19
  "require": "./dist/main.cjs",
20
20
  "import": "./dist/main.js"
@@ -28,15 +28,19 @@
28
28
  "preview": "vite preview"
29
29
  },
30
30
  "dependencies": {
31
- "@types/three": "^0.163.0"
31
+ "@types/three": "*",
32
+ "three": "^0.170.0"
32
33
  },
33
34
  "devDependencies": {
34
- "fs-extra": "^11.1.1",
35
+ "fs-extra": "^11.2.0",
35
36
  "path": "^0.12.7",
36
- "rollup": "^4.12.1",
37
- "rollup-plugin-dts": "^5.3.0",
38
- "three": "^0.163.0",
39
- "typescript": "^5.0.2",
40
- "vite": "^4.5.2"
37
+ "rollup": "^4.24.3",
38
+ "rollup-plugin-dts": "^5.3.1",
39
+ "typescript": "^5.6.3",
40
+ "vite": "^4.5.5"
41
+ },
42
+ "peerDependencies": {
43
+ "three": "*",
44
+ "@types/three": "*"
41
45
  }
42
46
  }