stats-gl 2.2.8 → 2.3.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/lib/main.ts CHANGED
@@ -1,156 +1,284 @@
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
+ this.samplesLog = samplesLog;
89
+ this.samplesGraph = samplesGraph;
90
+ this.precision = precision;
91
+ this.logsPerSecond = logsPerSecond;
66
92
 
67
- this.beginTime = (performance || Date).now();
93
+ // Initialize DOM
94
+ this.dom = document.createElement('div');
95
+ this.initializeDOM();
96
+
97
+ // Initialize timing
98
+ this.beginTime = performance.now();
68
99
  this.prevTime = this.beginTime;
69
100
  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
101
 
102
+ // Create panels
88
103
  this.fpsPanel = this.addPanel(new Stats.Panel('FPS', '#0ff', '#002'), 0);
89
104
  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
105
 
98
- if (this.minimal) {
106
+ this.setupEventListeners();
107
+ }
99
108
 
100
- this.dom.addEventListener('click', (event) => {
109
+ private initializeDOM(): void {
110
+ this.dom.style.cssText = `
111
+ position: fixed;
112
+ top: 0;
113
+ left: 0;
114
+ opacity: 0.9;
115
+ z-index: 10000;
116
+ ${this.minimal ? 'cursor: pointer;' : ''}
117
+ `;
118
+ }
101
119
 
102
- event.preventDefault();
103
- this.showPanel(++this.mode % this.dom.children.length);
120
+ private setupEventListeners(): void {
121
+ if (this.minimal) {
122
+ this.dom.addEventListener('click', this.handleClick);
123
+ this.showPanel(this.mode);
124
+ } else {
125
+ window.addEventListener('resize', this.handleResize);
126
+ }
127
+ }
104
128
 
105
- }, false);
129
+ private handleClick = (event: MouseEvent): void => {
130
+ event.preventDefault();
131
+ this.showPanel(++this.mode % this.dom.children.length);
132
+ };
133
+
134
+ private handleResize = (): void => {
135
+ this.resizePanel(this.fpsPanel, 0);
136
+ this.resizePanel(this.msPanel, 1);
137
+ if (this.gpuPanel) this.resizePanel(this.gpuPanel, 2);
138
+ if (this.gpuPanelCompute) this.resizePanel(this.gpuPanelCompute, 3);
139
+ };
140
+
141
+ public async init(
142
+ canvasOrGL: WebGL2RenderingContext | HTMLCanvasElement | OffscreenCanvas | any
143
+ ): Promise<void> {
144
+ if (!canvasOrGL) {
145
+ console.error('Stats: The "canvas" parameter is undefined.');
146
+ return;
147
+ }
106
148
 
107
- this.mode = mode;
108
- this.showPanel(this.mode);
149
+ if (this.handleThreeRenderer(canvasOrGL)) return;
150
+ if (await this.handleWebGPURenderer(canvasOrGL)) return;
151
+ if (!this.initializeWebGL(canvasOrGL)) return;
109
152
 
110
- } else {
153
+ }
111
154
 
112
- window.addEventListener('resize', () => {
155
+ private handleThreeRenderer(renderer: any): boolean {
156
+ if (renderer.isWebGLRenderer && !this.threeRendererPatched) {
157
+ this.patchThreeRenderer(renderer);
158
+ this.gl = renderer.getContext();
113
159
 
114
- this.resizePanel(this.fpsPanel, 0);
115
- this.resizePanel(this.msPanel, 1);
160
+ if (this.trackGPU) {
161
+ this.initializeGPUTracking();
162
+ }
163
+ return true;
164
+ }
165
+ return false;
166
+ }
116
167
 
117
- if (this.gpuPanel) {
118
- this.resizePanel(this.gpuPanel, 2);
119
- }
120
- if (this.gpuPanelCompute) {
121
- this.resizePanel(this.gpuPanelCompute, 3);
168
+ private async handleWebGPURenderer(renderer: any): Promise<boolean> {
169
+ if (renderer.isWebGPURenderer) {
170
+ if (this.trackGPU) {
171
+ renderer.backend.trackTimestamp = true;
172
+ if (await renderer.hasFeatureAsync('timestamp-query')) {
173
+ this.initializeWebGPUPanels();
122
174
  }
123
- })
175
+ }
176
+ this.info = renderer.info;
177
+ return true;
124
178
  }
179
+ return false;
180
+ }
125
181
 
182
+ private initializeWebGPUPanels(): void {
183
+ this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
184
+ this.gpuPanelCompute = this.addPanel(
185
+ new Stats.Panel('CPT', '#e1e1e1', '#212121'),
186
+ 3
187
+ );
188
+ }
126
189
 
190
+ private initializeWebGL(
191
+ canvasOrGL: WebGL2RenderingContext | HTMLCanvasElement | OffscreenCanvas
192
+ ): boolean {
193
+ if (canvasOrGL instanceof WebGL2RenderingContext) {
194
+ this.gl = canvasOrGL;
195
+ } else if (
196
+ canvasOrGL instanceof HTMLCanvasElement ||
197
+ canvasOrGL instanceof OffscreenCanvas
198
+ ) {
199
+ this.gl = canvasOrGL.getContext('webgl2');
200
+ if (!this.gl) {
201
+ console.error('Stats: Unable to obtain WebGL2 context.');
202
+ return false;
203
+ }
204
+ } else {
205
+ console.error(
206
+ 'Stats: Invalid input type. Expected WebGL2RenderingContext, HTMLCanvasElement, or OffscreenCanvas.'
207
+ );
208
+ return false;
209
+ }
210
+ return true;
127
211
  }
128
212
 
129
- patchThreeRenderer(renderer: any) {
213
+ private initializeGPUTracking(): void {
214
+ if (this.gl) {
215
+ this.ext = this.gl.getExtension('EXT_disjoint_timer_query_webgl2');
216
+ if (this.ext) {
217
+ this.gpuPanel = this.addPanel(new Stats.Panel('GPU', '#ff0', '#220'), 2);
218
+ }
219
+ }
220
+ }
130
221
 
131
- // Store the original render method
132
- const originalRenderMethod = renderer.render;
222
+ public begin(): void {
223
+ if (!this.isRunningCPUProfiling) {
224
+ this.beginProfiling('cpu-started');
225
+ }
133
226
 
134
- // Reference to the stats instance
135
- const statsInstance = this;
227
+ if (!this.gl || !this.ext) return;
136
228
 
137
- // Override the render method on the prototype
138
- renderer.render = function (scene: THREE.Scene, camera: THREE.Camera) {
229
+ if (this.activeQuery) {
230
+ this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
231
+ }
139
232
 
233
+ this.activeQuery = this.gl.createQuery();
234
+ if (this.activeQuery) {
235
+ this.gl.beginQuery(this.ext.TIME_ELAPSED_EXT, this.activeQuery);
236
+ }
237
+ }
140
238
 
141
- statsInstance.begin(); // Start tracking for this render call
239
+ public end(): void {
240
+ this.renderCount++;
241
+ if (this.gl && this.ext && this.activeQuery) {
242
+ this.gl.endQuery(this.ext.TIME_ELAPSED_EXT);
243
+ this.gpuQueries.push({ query: this.activeQuery });
244
+ this.activeQuery = null;
245
+ }
246
+ }
142
247
 
143
- // Call the original render method
144
- originalRenderMethod.call(this, scene, camera);
248
+ public update(): void {
249
+ if (!this.info) {
250
+ this.processGpuQueries();
251
+ } else {
252
+ this.processWebGPUTimestamps();
253
+ }
145
254
 
146
- statsInstance.end(); // End tracking for this render call
147
- };
255
+ this.endProfiling('cpu-started', 'cpu-finished', 'cpu-duration');
256
+ this.updateAverages();
257
+ this.resetCounters();
258
+ }
148
259
 
260
+ private processWebGPUTimestamps(): void {
261
+ this.totalGpuDuration = this.info!.render.timestamp;
262
+ this.totalGpuDurationCompute = this.info!.compute.timestamp;
263
+ this.addToAverage(this.totalGpuDurationCompute, this.averageGpuCompute);
264
+ }
149
265
 
150
- this.threeRendererPatched = true;
266
+ private updateAverages(): void {
267
+ this.addToAverage(this.totalCpuDuration, this.averageCpu);
268
+ this.addToAverage(this.totalGpuDuration, this.averageGpu);
269
+ }
151
270
 
271
+ private resetCounters(): void {
272
+ this.renderCount = 0;
273
+ if (this.totalCpuDuration === 0) {
274
+ this.beginProfiling('cpu-started');
275
+ }
276
+ this.totalCpuDuration = 0;
277
+ this.totalFps = 0;
278
+ this.beginTime = this.endInternal();
152
279
  }
153
280
 
281
+
154
282
  resizePanel(panel: Panel, offset: number) {
155
283
 
156
284
  panel.canvas.style.position = 'absolute';
@@ -171,8 +299,8 @@ class Stats {
171
299
 
172
300
  }
173
301
  }
174
- }
175
302
 
303
+ }
176
304
  addPanel(panel: Panel, offset: number) {
177
305
 
178
306
  if (panel.canvas) {
@@ -200,98 +328,6 @@ class Stats {
200
328
 
201
329
  }
202
330
 
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
331
  processGpuQueries() {
296
332
 
297
333
 
@@ -316,39 +352,6 @@ class Stats {
316
352
 
317
353
  }
318
354
 
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
355
  endInternal() {
353
356
 
354
357
  this.frames++;
@@ -464,13 +467,30 @@ class Stats {
464
467
 
465
468
  }
466
469
 
467
- get container() { // @deprecated
470
+ patchThreeRenderer(renderer: any) {
468
471
 
469
- console.warn('Stats: Deprecated! this.container as been replaced to this.dom ')
470
- return this.dom;
472
+ // Store the original render method
473
+ const originalRenderMethod = renderer.render;
471
474
 
472
- }
475
+ // Reference to the stats instance
476
+ const statsInstance = this;
477
+
478
+ // Override the render method on the prototype
479
+ renderer.render = function (scene: THREE.Scene, camera: THREE.Camera) {
473
480
 
481
+
482
+ statsInstance.begin(); // Start tracking for this render call
483
+
484
+ // Call the original render method
485
+ originalRenderMethod.call(this, scene, camera);
486
+
487
+ statsInstance.end(); // End tracking for this render call
488
+ };
489
+
490
+
491
+ this.threeRendererPatched = true;
492
+
493
+ }
474
494
  }
475
495
 
476
496
 
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.1",
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
  }