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.
@@ -0,0 +1,403 @@
1
+ // Texture capture utilities for WebGPU and WebGL2
2
+
3
+ export interface TextureCaptureSource {
4
+ // WebGL2
5
+ framebuffer?: WebGLFramebuffer;
6
+ width?: number;
7
+ height?: number;
8
+ // WebGPU
9
+ gpuTexture?: GPUTexture;
10
+ // Three.js WebGLRenderTarget
11
+ isWebGLRenderTarget?: boolean;
12
+ // Three.js RenderTarget (WebGPU)
13
+ isRenderTarget?: boolean;
14
+ texture?: any;
15
+ __webglFramebuffer?: WebGLFramebuffer;
16
+ }
17
+
18
+ // Default preview dimensions (matches full PANEL size for texture panels)
19
+ const DEFAULT_PREVIEW_WIDTH = 90;
20
+ const DEFAULT_PREVIEW_HEIGHT = 48;
21
+
22
+ // =============================================================================
23
+ // WebGL2 Texture Capture with PBO double-buffering
24
+ // =============================================================================
25
+
26
+ export class TextureCaptureWebGL {
27
+ private gl: WebGL2RenderingContext;
28
+ private previewFbo: WebGLFramebuffer | null = null;
29
+ private previewTexture: WebGLTexture | null = null;
30
+ private pixels: Uint8Array;
31
+ private flippedPixels: Uint8Array;
32
+ private previewWidth: number;
33
+ private previewHeight: number;
34
+
35
+ constructor(gl: WebGL2RenderingContext, width = DEFAULT_PREVIEW_WIDTH, height = DEFAULT_PREVIEW_HEIGHT) {
36
+ this.gl = gl;
37
+ this.previewWidth = width;
38
+ this.previewHeight = height;
39
+ this.pixels = new Uint8Array(width * height * 4);
40
+ this.flippedPixels = new Uint8Array(width * height * 4);
41
+ this.initResources();
42
+ }
43
+
44
+ /**
45
+ * Resize preview dimensions
46
+ */
47
+ resize(width: number, height: number): void {
48
+ if (width === this.previewWidth && height === this.previewHeight) return;
49
+
50
+ this.previewWidth = width;
51
+ this.previewHeight = height;
52
+ this.pixels = new Uint8Array(width * height * 4);
53
+ this.flippedPixels = new Uint8Array(width * height * 4);
54
+
55
+ // Recreate resources with new dimensions
56
+ this.dispose();
57
+ this.initResources();
58
+ }
59
+
60
+ private initResources(): void {
61
+ const gl = this.gl;
62
+
63
+ // Create preview texture and FBO for downscaling
64
+ this.previewTexture = gl.createTexture();
65
+ gl.bindTexture(gl.TEXTURE_2D, this.previewTexture);
66
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, this.previewWidth, this.previewHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
67
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
68
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
69
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
70
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
71
+
72
+ this.previewFbo = gl.createFramebuffer();
73
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.previewFbo);
74
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.previewTexture, 0);
75
+
76
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
77
+ gl.bindTexture(gl.TEXTURE_2D, null);
78
+ }
79
+
80
+ public async capture(
81
+ source: WebGLFramebuffer | null,
82
+ sourceWidth: number,
83
+ sourceHeight: number,
84
+ _sourceId: string = 'default'
85
+ ): Promise<ImageBitmap | null> {
86
+ const gl = this.gl;
87
+
88
+ // Save current state
89
+ const prevReadFbo = gl.getParameter(gl.READ_FRAMEBUFFER_BINDING);
90
+ const prevDrawFbo = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
91
+
92
+ // Blit source to preview FBO with LINEAR filtering (downscale)
93
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, source);
94
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.previewFbo);
95
+ gl.blitFramebuffer(
96
+ 0, 0, sourceWidth, sourceHeight,
97
+ 0, 0, this.previewWidth, this.previewHeight,
98
+ gl.COLOR_BUFFER_BIT,
99
+ gl.LINEAR
100
+ );
101
+
102
+ // Synchronous read - fine for small preview
103
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.previewFbo);
104
+ gl.readPixels(0, 0, this.previewWidth, this.previewHeight, gl.RGBA, gl.UNSIGNED_BYTE, this.pixels);
105
+
106
+ // Restore state
107
+ gl.bindFramebuffer(gl.READ_FRAMEBUFFER, prevReadFbo);
108
+ gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, prevDrawFbo);
109
+
110
+ // Flip Y axis (WebGL has origin at bottom-left)
111
+ const flipped = this.flipY(this.pixels, this.previewWidth, this.previewHeight);
112
+ const imageData = new ImageData(new Uint8ClampedArray(flipped), this.previewWidth, this.previewHeight);
113
+
114
+ return createImageBitmap(imageData);
115
+ }
116
+
117
+ private flipY(pixels: Uint8Array, width: number, height: number): Uint8Array {
118
+ const rowSize = width * 4;
119
+ for (let y = 0; y < height; y++) {
120
+ const srcOffset = y * rowSize;
121
+ const dstOffset = (height - 1 - y) * rowSize;
122
+ this.flippedPixels.set(pixels.subarray(srcOffset, srcOffset + rowSize), dstOffset);
123
+ }
124
+ return this.flippedPixels;
125
+ }
126
+
127
+ public removeSource(_sourceId: string): void {
128
+ // No per-source state in sync mode
129
+ }
130
+
131
+ public dispose(): void {
132
+ const gl = this.gl;
133
+ if (this.previewFbo) {
134
+ gl.deleteFramebuffer(this.previewFbo);
135
+ this.previewFbo = null;
136
+ }
137
+ if (this.previewTexture) {
138
+ gl.deleteTexture(this.previewTexture);
139
+ this.previewTexture = null;
140
+ }
141
+ }
142
+ }
143
+
144
+ // =============================================================================
145
+ // WebGPU Texture Capture with blit pipeline and staging buffer
146
+ // =============================================================================
147
+
148
+ export class TextureCaptureWebGPU {
149
+ private device: GPUDevice;
150
+ private previewTexture: GPUTexture | null = null;
151
+ private stagingBuffer: GPUBuffer | null = null;
152
+ private blitPipeline: GPURenderPipeline | null = null;
153
+ private sampler: GPUSampler | null = null;
154
+ private bindGroupLayout: GPUBindGroupLayout | null = null;
155
+ private initialized = false;
156
+ private previewWidth: number;
157
+ private previewHeight: number;
158
+ private pixelsBuffer: Uint8ClampedArray;
159
+
160
+ constructor(device: GPUDevice, width = DEFAULT_PREVIEW_WIDTH, height = DEFAULT_PREVIEW_HEIGHT) {
161
+ this.device = device;
162
+ this.previewWidth = width;
163
+ this.previewHeight = height;
164
+ this.pixelsBuffer = new Uint8ClampedArray(width * height * 4);
165
+ }
166
+
167
+ /**
168
+ * Resize preview dimensions
169
+ */
170
+ resize(width: number, height: number): void {
171
+ if (width === this.previewWidth && height === this.previewHeight) return;
172
+
173
+ this.previewWidth = width;
174
+ this.previewHeight = height;
175
+ this.pixelsBuffer = new Uint8ClampedArray(width * height * 4);
176
+
177
+ // Dispose texture and buffer (they need new dimensions)
178
+ if (this.previewTexture) this.previewTexture.destroy();
179
+ if (this.stagingBuffer) this.stagingBuffer.destroy();
180
+ this.previewTexture = null;
181
+ this.stagingBuffer = null;
182
+
183
+ // Recreate on next capture
184
+ if (this.initialized) {
185
+ this.createSizeResources();
186
+ }
187
+ }
188
+
189
+ private createSizeResources(): void {
190
+ const device = this.device;
191
+
192
+ // Create preview texture
193
+ this.previewTexture = device.createTexture({
194
+ size: { width: this.previewWidth, height: this.previewHeight },
195
+ format: 'rgba8unorm',
196
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC
197
+ });
198
+
199
+ // Create staging buffer for readback
200
+ const bytesPerRow = Math.ceil(this.previewWidth * 4 / 256) * 256;
201
+ this.stagingBuffer = device.createBuffer({
202
+ size: bytesPerRow * this.previewHeight,
203
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
204
+ });
205
+ }
206
+
207
+ private async initResources(): Promise<void> {
208
+ if (this.initialized) return;
209
+
210
+ const device = this.device;
211
+
212
+ this.createSizeResources();
213
+
214
+ // Create sampler
215
+ this.sampler = device.createSampler({
216
+ minFilter: 'linear',
217
+ magFilter: 'linear'
218
+ });
219
+
220
+ // Create blit shader
221
+ const shaderModule = device.createShaderModule({
222
+ code: `
223
+ @group(0) @binding(0) var texSampler: sampler;
224
+ @group(0) @binding(1) var texInput: texture_2d<f32>;
225
+
226
+ struct VertexOutput {
227
+ @builtin(position) position: vec4f,
228
+ @location(0) uv: vec2f
229
+ }
230
+
231
+ @vertex
232
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
233
+ var positions = array<vec2f, 3>(
234
+ vec2f(-1.0, -1.0),
235
+ vec2f(3.0, -1.0),
236
+ vec2f(-1.0, 3.0)
237
+ );
238
+ var uvs = array<vec2f, 3>(
239
+ vec2f(0.0, 1.0),
240
+ vec2f(2.0, 1.0),
241
+ vec2f(0.0, -1.0)
242
+ );
243
+
244
+ var output: VertexOutput;
245
+ output.position = vec4f(positions[vertexIndex], 0.0, 1.0);
246
+ output.uv = uvs[vertexIndex];
247
+ return output;
248
+ }
249
+
250
+ @fragment
251
+ fn fragmentMain(@location(0) uv: vec2f) -> @location(0) vec4f {
252
+ return textureSample(texInput, texSampler, uv);
253
+ }
254
+ `
255
+ });
256
+
257
+ // Create bind group layout
258
+ this.bindGroupLayout = device.createBindGroupLayout({
259
+ entries: [
260
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
261
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } }
262
+ ]
263
+ });
264
+
265
+ // Create pipeline
266
+ this.blitPipeline = device.createRenderPipeline({
267
+ layout: device.createPipelineLayout({ bindGroupLayouts: [this.bindGroupLayout] }),
268
+ vertex: {
269
+ module: shaderModule,
270
+ entryPoint: 'vertexMain'
271
+ },
272
+ fragment: {
273
+ module: shaderModule,
274
+ entryPoint: 'fragmentMain',
275
+ targets: [{ format: 'rgba8unorm' }]
276
+ },
277
+ primitive: { topology: 'triangle-list' }
278
+ });
279
+
280
+ this.initialized = true;
281
+ }
282
+
283
+ public async capture(source: GPUTexture): Promise<ImageBitmap | null> {
284
+ await this.initResources();
285
+
286
+ if (!this.previewTexture || !this.stagingBuffer || !this.blitPipeline || !this.sampler || !this.bindGroupLayout) {
287
+ return null;
288
+ }
289
+
290
+ const device = this.device;
291
+
292
+ // Create bind group for source texture
293
+ const bindGroup = device.createBindGroup({
294
+ layout: this.bindGroupLayout,
295
+ entries: [
296
+ { binding: 0, resource: this.sampler },
297
+ { binding: 1, resource: source.createView() }
298
+ ]
299
+ });
300
+
301
+ // Blit source to preview texture
302
+ const commandEncoder = device.createCommandEncoder();
303
+
304
+ const renderPass = commandEncoder.beginRenderPass({
305
+ colorAttachments: [{
306
+ view: this.previewTexture.createView(),
307
+ loadOp: 'clear',
308
+ storeOp: 'store',
309
+ clearValue: { r: 0, g: 0, b: 0, a: 1 }
310
+ }]
311
+ });
312
+
313
+ renderPass.setPipeline(this.blitPipeline);
314
+ renderPass.setBindGroup(0, bindGroup);
315
+ renderPass.draw(3);
316
+ renderPass.end();
317
+
318
+ // Copy to staging buffer
319
+ const bytesPerRow = Math.ceil(this.previewWidth * 4 / 256) * 256;
320
+ commandEncoder.copyTextureToBuffer(
321
+ { texture: this.previewTexture },
322
+ { buffer: this.stagingBuffer, bytesPerRow },
323
+ { width: this.previewWidth, height: this.previewHeight }
324
+ );
325
+
326
+ device.queue.submit([commandEncoder.finish()]);
327
+
328
+ // Map and read staging buffer
329
+ await this.stagingBuffer.mapAsync(GPUMapMode.READ);
330
+ const data = new Uint8Array(this.stagingBuffer.getMappedRange());
331
+
332
+ // Copy data (accounting for row alignment) into pre-allocated buffer
333
+ for (let y = 0; y < this.previewHeight; y++) {
334
+ const srcOffset = y * bytesPerRow;
335
+ const dstOffset = y * this.previewWidth * 4;
336
+ this.pixelsBuffer.set(data.subarray(srcOffset, srcOffset + this.previewWidth * 4), dstOffset);
337
+ }
338
+
339
+ this.stagingBuffer.unmap();
340
+
341
+ // ImageData needs its own Uint8ClampedArray - create from pre-allocated buffer
342
+ const imageData = new ImageData(new Uint8ClampedArray(this.pixelsBuffer), this.previewWidth, this.previewHeight);
343
+ return createImageBitmap(imageData);
344
+ }
345
+
346
+ public dispose(): void {
347
+ if (this.previewTexture) this.previewTexture.destroy();
348
+ if (this.stagingBuffer) this.stagingBuffer.destroy();
349
+ this.previewTexture = null;
350
+ this.stagingBuffer = null;
351
+ this.blitPipeline = null;
352
+ this.sampler = null;
353
+ this.bindGroupLayout = null;
354
+ this.initialized = false;
355
+ }
356
+ }
357
+
358
+ // =============================================================================
359
+ // Three.js helper to extract native handles
360
+ // =============================================================================
361
+
362
+ export interface ThreeTextureSource {
363
+ // WebGLRenderTarget
364
+ isWebGLRenderTarget?: boolean;
365
+ __webglFramebuffer?: WebGLFramebuffer;
366
+ width?: number;
367
+ height?: number;
368
+ // WebGPU RenderTarget
369
+ isRenderTarget?: boolean;
370
+ texture?: { isTexture?: boolean };
371
+ }
372
+
373
+ export interface ThreeRendererBackend {
374
+ device?: GPUDevice;
375
+ get?: (texture: any) => { texture?: GPUTexture };
376
+ }
377
+
378
+ export function extractWebGLSource(
379
+ target: ThreeTextureSource,
380
+ gl: WebGL2RenderingContext
381
+ ): { framebuffer: WebGLFramebuffer; width: number; height: number } | null {
382
+ if (target.isWebGLRenderTarget && target.__webglFramebuffer) {
383
+ return {
384
+ framebuffer: target.__webglFramebuffer,
385
+ width: target.width || 1,
386
+ height: target.height || 1
387
+ };
388
+ }
389
+ return null;
390
+ }
391
+
392
+ export function extractWebGPUSource(
393
+ target: ThreeTextureSource,
394
+ backend: ThreeRendererBackend
395
+ ): GPUTexture | null {
396
+ if (target.isRenderTarget && target.texture && backend.get) {
397
+ const textureData = backend.get(target.texture);
398
+ return textureData?.texture || null;
399
+ }
400
+ return null;
401
+ }
402
+
403
+ export { DEFAULT_PREVIEW_WIDTH, DEFAULT_PREVIEW_HEIGHT };
@@ -0,0 +1,190 @@
1
+ // WebGPU type declarations for stats-gl
2
+ // These are minimal declarations for the types used in texture capture and timestamp queries
3
+
4
+ interface GPUDevice {
5
+ createTexture(descriptor: GPUTextureDescriptor): GPUTexture;
6
+ createBuffer(descriptor: GPUBufferDescriptor): GPUBuffer;
7
+ createSampler(descriptor?: GPUSamplerDescriptor): GPUSampler;
8
+ createShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule;
9
+ createBindGroupLayout(descriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout;
10
+ createPipelineLayout(descriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout;
11
+ createRenderPipeline(descriptor: GPURenderPipelineDescriptor): GPURenderPipeline;
12
+ createBindGroup(descriptor: GPUBindGroupDescriptor): GPUBindGroup;
13
+ createCommandEncoder(): GPUCommandEncoder;
14
+ createQuerySet(descriptor: GPUQuerySetDescriptor): GPUQuerySet;
15
+ queue: GPUQueue;
16
+ features: GPUSupportedFeatures;
17
+ }
18
+
19
+ interface GPUSupportedFeatures {
20
+ has(feature: string): boolean;
21
+ }
22
+
23
+ interface GPUQuerySet {
24
+ destroy(): void;
25
+ }
26
+
27
+ interface GPUQuerySetDescriptor {
28
+ type: string;
29
+ count: number;
30
+ }
31
+
32
+ interface GPUTexture {
33
+ createView(): GPUTextureView;
34
+ destroy(): void;
35
+ }
36
+
37
+ interface GPUTextureView {}
38
+
39
+ interface GPUBuffer {
40
+ mapAsync(mode: number): Promise<void>;
41
+ getMappedRange(): ArrayBuffer;
42
+ unmap(): void;
43
+ destroy(): void;
44
+ }
45
+
46
+ interface GPUSampler {}
47
+
48
+ interface GPUShaderModule {}
49
+
50
+ interface GPUBindGroupLayout {}
51
+
52
+ interface GPUPipelineLayout {}
53
+
54
+ interface GPURenderPipeline {}
55
+
56
+ interface GPUBindGroup {}
57
+
58
+ interface GPUQueue {
59
+ submit(commandBuffers: GPUCommandBuffer[]): void;
60
+ }
61
+
62
+ interface GPUCommandEncoder {
63
+ beginRenderPass(descriptor: GPURenderPassDescriptor): GPURenderPassEncoder;
64
+ copyTextureToBuffer(source: GPUImageCopyTexture, destination: GPUImageCopyBuffer, copySize: GPUExtent3D): void;
65
+ copyBufferToBuffer(source: GPUBuffer, sourceOffset: number, destination: GPUBuffer, destinationOffset: number, size: number): void;
66
+ resolveQuerySet(querySet: GPUQuerySet, firstQuery: number, queryCount: number, destination: GPUBuffer, destinationOffset: number): void;
67
+ finish(): GPUCommandBuffer;
68
+ }
69
+
70
+ interface GPURenderPassEncoder {
71
+ setPipeline(pipeline: GPURenderPipeline): void;
72
+ setBindGroup(index: number, bindGroup: GPUBindGroup): void;
73
+ draw(vertexCount: number): void;
74
+ end(): void;
75
+ }
76
+
77
+ interface GPUCommandBuffer {}
78
+
79
+ interface GPUTextureDescriptor {
80
+ size: { width: number; height: number };
81
+ format: string;
82
+ usage: number;
83
+ }
84
+
85
+ interface GPUBufferDescriptor {
86
+ size: number;
87
+ usage: number;
88
+ }
89
+
90
+ interface GPUSamplerDescriptor {
91
+ minFilter?: string;
92
+ magFilter?: string;
93
+ }
94
+
95
+ interface GPUShaderModuleDescriptor {
96
+ code: string;
97
+ }
98
+
99
+ interface GPUBindGroupLayoutDescriptor {
100
+ entries: GPUBindGroupLayoutEntry[];
101
+ }
102
+
103
+ interface GPUBindGroupLayoutEntry {
104
+ binding: number;
105
+ visibility: number;
106
+ sampler?: { type: string };
107
+ texture?: { sampleType: string };
108
+ }
109
+
110
+ interface GPUPipelineLayoutDescriptor {
111
+ bindGroupLayouts: GPUBindGroupLayout[];
112
+ }
113
+
114
+ interface GPURenderPipelineDescriptor {
115
+ layout: GPUPipelineLayout;
116
+ vertex: {
117
+ module: GPUShaderModule;
118
+ entryPoint: string;
119
+ };
120
+ fragment: {
121
+ module: GPUShaderModule;
122
+ entryPoint: string;
123
+ targets: { format: string }[];
124
+ };
125
+ primitive: {
126
+ topology: string;
127
+ };
128
+ }
129
+
130
+ interface GPUBindGroupDescriptor {
131
+ layout: GPUBindGroupLayout;
132
+ entries: GPUBindGroupEntry[];
133
+ }
134
+
135
+ interface GPUBindGroupEntry {
136
+ binding: number;
137
+ resource: GPUSampler | GPUTextureView;
138
+ }
139
+
140
+ interface GPURenderPassDescriptor {
141
+ colorAttachments: GPURenderPassColorAttachment[];
142
+ timestampWrites?: GPURenderPassTimestampWrites;
143
+ }
144
+
145
+ interface GPURenderPassTimestampWrites {
146
+ querySet: GPUQuerySet;
147
+ beginningOfPassWriteIndex?: number;
148
+ endOfPassWriteIndex?: number;
149
+ }
150
+
151
+ interface GPURenderPassColorAttachment {
152
+ view: GPUTextureView;
153
+ loadOp: string;
154
+ storeOp: string;
155
+ clearValue: { r: number; g: number; b: number; a: number };
156
+ }
157
+
158
+ interface GPUImageCopyTexture {
159
+ texture: GPUTexture;
160
+ }
161
+
162
+ interface GPUImageCopyBuffer {
163
+ buffer: GPUBuffer;
164
+ bytesPerRow: number;
165
+ }
166
+
167
+ interface GPUExtent3D {
168
+ width: number;
169
+ height: number;
170
+ }
171
+
172
+ declare const GPUTextureUsage: {
173
+ RENDER_ATTACHMENT: number;
174
+ COPY_SRC: number;
175
+ };
176
+
177
+ declare const GPUBufferUsage: {
178
+ COPY_DST: number;
179
+ COPY_SRC: number;
180
+ MAP_READ: number;
181
+ QUERY_RESOLVE: number;
182
+ };
183
+
184
+ declare const GPUShaderStage: {
185
+ FRAGMENT: number;
186
+ };
187
+
188
+ declare const GPUMapMode: {
189
+ READ: number;
190
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stats-gl",
3
- "version": "3.7.0",
3
+ "version": "4.0.0",
4
4
  "type": "module",
5
5
  "author": "Renaud ROHLINGER (https://github.com/RenaudRohlinger)",
6
6
  "homepage": "https://github.com/RenaudRohlinger/stats-gl",
@@ -8,17 +8,19 @@
8
8
  "license": "MIT",
9
9
  "files": [
10
10
  "dist/*",
11
- "lib/*"
11
+ "lib/*",
12
+ "addons/*"
12
13
  ],
13
14
  "types": "./dist/stats-gl.d.ts",
14
15
  "main": "./dist/main.cjs",
15
16
  "module": "./dist/main.js",
16
17
  "exports": {
17
- ".": {
18
+ ".": {
18
19
  "types": "./dist/stats-gl.d.ts",
19
20
  "require": "./dist/main.cjs",
20
21
  "import": "./dist/main.js"
21
- }
22
+ },
23
+ "./addons/*": "./addons/*"
22
24
  },
23
25
  "sideEffects": false,
24
26
  "scripts": {