stats-gl 3.8.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.
@@ -0,0 +1,201 @@
1
+ /**
2
+ * StatsGLNode - TSL Node capture for stats-gl (WebGPU only)
3
+ *
4
+ * Usage:
5
+ * import { statsGL } from 'stats-gl/addons/StatsGLNode.js';
6
+ * import { addMethodChaining } from 'three/tsl';
7
+ *
8
+ * addMethodChaining('toStatsGL', statsGL);
9
+ *
10
+ * // Simple capture:
11
+ * someNode.toStatsGL('Name', stats);
12
+ *
13
+ * // With callback for transformed capture:
14
+ * depthNode.toStatsGL('Depth', stats, () => linearDepthNode);
15
+ */
16
+
17
+ import { addMethodChaining, nodeObject, vec3, vec4 } from 'three/tsl';
18
+ import { CanvasTarget, LinearSRGBColorSpace, NodeMaterial, NoToneMapping, QuadMesh, RendererUtils } from 'three/webgpu';
19
+
20
+ /**
21
+ * Capture data stored on stats instance
22
+ */
23
+ class CaptureData {
24
+ constructor(name, node, stats, callback) {
25
+ this.name = name;
26
+ this.node = node;
27
+ this.stats = stats;
28
+ this.callback = callback;
29
+ this.canvas = null;
30
+ this.canvasTarget = null;
31
+ this.quad = null;
32
+ this.material = null;
33
+ this.initialized = false;
34
+ this.size = 90;
35
+ }
36
+
37
+ init(renderer) {
38
+ if (this.initialized) return true;
39
+
40
+ try {
41
+ // Determine which node to capture
42
+ let captureNode = this.node;
43
+ if (this.callback !== null) {
44
+ captureNode = this.callback(this.node);
45
+ }
46
+ captureNode = nodeObject(captureNode);
47
+
48
+ // Create canvas
49
+ this.canvas = document.createElement('canvas');
50
+ this.canvas.width = this.canvas.height = this.size;
51
+
52
+ // Create canvas target (must set pixelRatio before size)
53
+ this.canvasTarget = new CanvasTarget(this.canvas);
54
+ this.canvasTarget.setPixelRatio(window.devicePixelRatio);
55
+ this.canvasTarget.setSize(this.size, this.size);
56
+
57
+ // Create material - use vec4(vec3(node), 1) like Inspector does
58
+ // For testing: use vec4(1, 0, 0, 1) for red
59
+ let output = vec4(vec3(captureNode), 1);
60
+ // Mark as inspector context
61
+ output = output.context({ inspector: true });
62
+
63
+ this.material = new NodeMaterial();
64
+ this.material.outputNode = output;
65
+
66
+ this.quad = new QuadMesh(this.material);
67
+
68
+ // Create panel if not exists
69
+ if (!this.stats.texturePanels.has(this.name)) {
70
+ this.stats.addTexturePanel(this.name);
71
+ }
72
+
73
+ this.initialized = true;
74
+ return true;
75
+ } catch (e) {
76
+ console.warn('StatsGL: Failed to initialize capture for', this.name, e);
77
+ return false;
78
+ }
79
+ }
80
+
81
+ async capture(renderer) {
82
+ if (!this.initialized && !this.init(renderer)) return;
83
+
84
+ try {
85
+ // Save renderer state (like Inspector's Viewer does)
86
+ const previousCanvasTarget = renderer.getCanvasTarget();
87
+ const state = RendererUtils.resetRendererState(renderer);
88
+
89
+ // Set rendering parameters
90
+ renderer.toneMapping = NoToneMapping;
91
+ renderer.outputColorSpace = LinearSRGBColorSpace;
92
+
93
+ // Set our canvas target
94
+ renderer.setCanvasTarget(this.canvasTarget);
95
+
96
+ // Render the quad
97
+ this.quad.render(renderer);
98
+
99
+ // Restore original canvas target and state
100
+ renderer.setCanvasTarget(previousCanvasTarget);
101
+ RendererUtils.restoreRendererState(renderer, state);
102
+
103
+ // Create bitmap from canvas
104
+ const bitmap = await createImageBitmap(this.canvas);
105
+ const panel = this.stats.texturePanels.get(this.name);
106
+ if (panel) {
107
+ panel.updateTexture(bitmap);
108
+ }
109
+ } catch (e) {
110
+ console.warn('StatsGL: Failed to capture for', this.name, e);
111
+ }
112
+ }
113
+
114
+ dispose() {
115
+ if (this.material && this.material.dispose) {
116
+ this.material.dispose();
117
+ }
118
+ if (this.canvas && this.canvas.parentNode) {
119
+ this.canvas.parentNode.removeChild(this.canvas);
120
+ }
121
+ this.canvas = null;
122
+ this.canvasTarget = null;
123
+ this.quad = null;
124
+ this.material = null;
125
+ this.initialized = false;
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Register a TSL node for capture. Returns the original node unchanged.
131
+ * Capture happens in stats.update() after the main render.
132
+ *
133
+ * @param {Node} node - The node to capture
134
+ * @param {string} name - Panel name/label
135
+ * @param {Stats} stats - Stats instance (must have called stats.init(renderer))
136
+ * @param {Function|null} [callback=null] - Optional callback to transform the node for capture
137
+ * @returns {Node} The original node (passthrough)
138
+ */
139
+ export function statsGL(node, name, stats, callback = null) {
140
+ node = nodeObject(node);
141
+
142
+ // Create capture data
143
+ const captureData = new CaptureData(name, node, stats, callback);
144
+
145
+ // Register with stats
146
+ if (!stats._statsGLCaptures) {
147
+ stats._statsGLCaptures = new Map();
148
+ }
149
+ stats._statsGLCaptures.set(name, captureData);
150
+
151
+ // Return original node unchanged - this is a side effect only
152
+ return node;
153
+ }
154
+
155
+ /**
156
+ * Call this in stats.update() to capture all registered nodes.
157
+ * The renderer must be passed from stats.init().
158
+ */
159
+ export function captureStatsGLNodes(stats, renderer) {
160
+ const captures = stats._statsGLCaptures;
161
+ if (!captures || captures.size === 0) return;
162
+
163
+ for (const captureData of captures.values()) {
164
+ captureData.capture(renderer);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Remove a registered TSL node capture
170
+ * @param {Stats} stats - Stats instance
171
+ * @param {string} name - Name of the capture to remove
172
+ */
173
+ export function removeStatsGL(stats, name) {
174
+ const captures = stats._statsGLCaptures;
175
+ if (!captures) return;
176
+
177
+ const captureData = captures.get(name);
178
+ if (captureData) {
179
+ captureData.dispose();
180
+ captures.delete(name);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Dispose all registered TSL node captures
186
+ * @param {Stats} stats - Stats instance
187
+ */
188
+ export function disposeStatsGLCaptures(stats) {
189
+ const captures = stats._statsGLCaptures;
190
+ if (!captures) return;
191
+
192
+ for (const captureData of captures.values()) {
193
+ captureData.dispose();
194
+ }
195
+ captures.clear();
196
+ }
197
+
198
+
199
+
200
+ // User sets up chaining for TSL nodes
201
+ addMethodChaining('toStatsGL', statsGL);
@@ -0,0 +1,198 @@
1
+ /**
2
+ * StatsGLNodeWorker - TSL Node capture for stats-gl in Web Workers (WebGPU only)
3
+ *
4
+ * Usage:
5
+ * import { statsGLWorker, flushCaptures } from 'stats-gl/addons/StatsGLNodeWorker.js';
6
+ * import { addMethodChaining } from 'three/tsl';
7
+ *
8
+ * addMethodChaining('toStatsGL', statsGLWorker);
9
+ *
10
+ * // Simple capture (name only, no stats instance needed):
11
+ * someNode.toStatsGL('Name');
12
+ *
13
+ * // With callback for transformed capture:
14
+ * depthNode.toStatsGL('Depth', null, () => linearDepthNode);
15
+ *
16
+ * // In render loop after postProcessing.render():
17
+ * const captures = await flushCaptures(renderer);
18
+ * for (const { name, bitmap } of captures) {
19
+ * self.postMessage({ type: 'texture', name, bitmap }, [bitmap]);
20
+ * }
21
+ */
22
+
23
+ import { addMethodChaining, nodeObject, vec3, vec4 } from 'three/tsl';
24
+ import { CanvasTarget, LinearSRGBColorSpace, NodeMaterial, NoToneMapping, QuadMesh, RendererUtils } from 'three/webgpu';
25
+
26
+ /**
27
+ * Capture data for worker environment (uses OffscreenCanvas)
28
+ */
29
+ class WorkerCaptureData {
30
+ constructor(name, node, callback) {
31
+ this.name = name;
32
+ this.node = node;
33
+ this.callback = callback;
34
+ this.canvas = null; // OffscreenCanvas
35
+ this.canvasTarget = null;
36
+ this.quad = null;
37
+ this.material = null;
38
+ this.initialized = false;
39
+ this.size = 90;
40
+ }
41
+
42
+ init(renderer) {
43
+ if (this.initialized) return true;
44
+
45
+ try {
46
+ // Determine which node to capture
47
+ let captureNode = this.node;
48
+ if (this.callback !== null) {
49
+ captureNode = this.callback(this.node);
50
+ }
51
+ captureNode = nodeObject(captureNode);
52
+
53
+ // Create OffscreenCanvas (worker-compatible)
54
+ this.canvas = new OffscreenCanvas(this.size, this.size);
55
+
56
+ // Create canvas target - set pixelRatio to 1 for workers (no window.devicePixelRatio)
57
+ // Pass false for updateStyle since OffscreenCanvas has no style property
58
+ this.canvasTarget = new CanvasTarget(this.canvas);
59
+ this.canvasTarget.setPixelRatio(1);
60
+ this.canvasTarget.setSize(this.size, this.size, false);
61
+
62
+ // Create material - use vec4(vec3(node), 1) like Inspector does
63
+ let output = vec4(vec3(captureNode), 1);
64
+ // Mark as inspector context
65
+ output = output.context({ inspector: true });
66
+
67
+ this.material = new NodeMaterial();
68
+ this.material.outputNode = output;
69
+
70
+ this.quad = new QuadMesh(this.material);
71
+
72
+ this.initialized = true;
73
+ return true;
74
+ } catch (e) {
75
+ console.warn('StatsGLWorker: Failed to initialize capture for', this.name, e);
76
+ return false;
77
+ }
78
+ }
79
+
80
+ async capture(renderer) {
81
+ if (!this.initialized && !this.init(renderer)) return null;
82
+
83
+ try {
84
+ // Save renderer state (like Inspector's Viewer does)
85
+ const previousCanvasTarget = renderer.getCanvasTarget();
86
+ const state = RendererUtils.resetRendererState(renderer);
87
+
88
+ // Set rendering parameters
89
+ renderer.toneMapping = NoToneMapping;
90
+ renderer.outputColorSpace = LinearSRGBColorSpace;
91
+
92
+ // Set our canvas target
93
+ renderer.setCanvasTarget(this.canvasTarget);
94
+
95
+ // Render the quad
96
+ this.quad.render(renderer);
97
+
98
+ // Restore original canvas target and state
99
+ renderer.setCanvasTarget(previousCanvasTarget);
100
+ RendererUtils.restoreRendererState(renderer, state);
101
+
102
+ // Create bitmap from OffscreenCanvas for transfer
103
+ return await createImageBitmap(this.canvas);
104
+ } catch (e) {
105
+ console.warn('StatsGLWorker: Failed to capture for', this.name, e);
106
+ return null;
107
+ }
108
+ }
109
+
110
+ dispose() {
111
+ if (this.material && this.material.dispose) {
112
+ this.material.dispose();
113
+ }
114
+ this.canvas = null;
115
+ this.canvasTarget = null;
116
+ this.quad = null;
117
+ this.material = null;
118
+ this.initialized = false;
119
+ }
120
+ }
121
+
122
+ // Global registry of captures for worker environment
123
+ const workerCaptures = new Map();
124
+
125
+ /**
126
+ * Register a TSL node for capture in worker environment. Returns the original node unchanged.
127
+ *
128
+ * @param {Node} node - The node to capture
129
+ * @param {string} name - Panel name/label
130
+ * @param {null} _stats - Unused (for API compatibility with main thread version)
131
+ * @param {Function|null} [callback=null] - Optional callback to transform the node for capture
132
+ * @returns {Node} The original node (passthrough)
133
+ */
134
+ export function statsGLWorker(node, name, _stats = null, callback = null) {
135
+ node = nodeObject(node);
136
+
137
+ // Create capture data
138
+ const captureData = new WorkerCaptureData(name, node, callback);
139
+
140
+ // Register in global map
141
+ workerCaptures.set(name, captureData);
142
+
143
+ // Return original node unchanged - this is a side effect only
144
+ return node;
145
+ }
146
+
147
+ /**
148
+ * Flush all pending captures and return ImageBitmaps for transfer to main thread.
149
+ * Call this after your render loop.
150
+ *
151
+ * @param {WebGPURenderer} renderer - The WebGPU renderer
152
+ * @returns {Promise<Array<{name: string, bitmap: ImageBitmap}>>} Array of captures ready for postMessage
153
+ */
154
+ export async function flushCaptures(renderer) {
155
+ const results = [];
156
+
157
+ for (const [name, captureData] of workerCaptures) {
158
+ const bitmap = await captureData.capture(renderer);
159
+ if (bitmap) {
160
+ results.push({ name, bitmap });
161
+ }
162
+ }
163
+
164
+ return results;
165
+ }
166
+
167
+ /**
168
+ * Remove a registered capture
169
+ * @param {string} name - Name of the capture to remove
170
+ */
171
+ export function removeCapture(name) {
172
+ const captureData = workerCaptures.get(name);
173
+ if (captureData) {
174
+ captureData.dispose();
175
+ workerCaptures.delete(name);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Dispose all registered captures
181
+ */
182
+ export function disposeAllCaptures() {
183
+ for (const captureData of workerCaptures.values()) {
184
+ captureData.dispose();
185
+ }
186
+ workerCaptures.clear();
187
+ }
188
+
189
+ /**
190
+ * Get list of registered capture names
191
+ * @returns {string[]} Array of capture names
192
+ */
193
+ export function getCaptureNames() {
194
+ return Array.from(workerCaptures.keys());
195
+ }
196
+
197
+ // Register method chaining for TSL nodes
198
+ addMethodChaining('toStatsGL', statsGLWorker);