stats-gl 3.8.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -86
- package/addons/StatsGLNode.js +201 -0
- package/addons/StatsGLNodeWorker.js +198 -0
- package/dist/Flamingo.glb +0 -0
- package/dist/LittlestTokyo.glb +0 -0
- package/dist/core.cjs +421 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.js +421 -0
- package/dist/core.js.map +1 -0
- package/dist/main.cjs +406 -241
- package/dist/main.cjs.map +1 -1
- package/dist/main.js +404 -240
- package/dist/main.js.map +1 -1
- package/dist/panel.cjs +12 -3
- package/dist/panel.cjs.map +1 -1
- package/dist/panel.js +12 -3
- package/dist/panel.js.map +1 -1
- package/dist/panelTexture.cjs +93 -0
- package/dist/panelTexture.cjs.map +1 -0
- package/dist/panelTexture.js +93 -0
- package/dist/panelTexture.js.map +1 -0
- package/dist/profiler.cjs +95 -0
- package/dist/profiler.cjs.map +1 -0
- package/dist/profiler.js +95 -0
- package/dist/profiler.js.map +1 -0
- package/dist/stats-gl.d.ts +332 -71
- package/dist/statsGLNode.cjs +89 -0
- package/dist/statsGLNode.cjs.map +1 -0
- package/dist/statsGLNode.js +89 -0
- package/dist/statsGLNode.js.map +1 -0
- package/dist/textureCapture.cjs +283 -0
- package/dist/textureCapture.cjs.map +1 -0
- package/dist/textureCapture.js +283 -0
- package/dist/textureCapture.js.map +1 -0
- package/lib/core.ts +579 -0
- package/lib/main.ts +506 -401
- package/lib/panel.ts +18 -4
- package/lib/panelTexture.ts +122 -0
- package/lib/profiler.ts +124 -0
- package/lib/statsGLNode.ts +124 -0
- package/lib/textureCapture.ts +403 -0
- package/lib/webgpu.d.ts +190 -0
- package/package.json +9 -5
|
@@ -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);
|
|
Binary file
|
|
Binary file
|