@needle-tools/gltf-progressive 3.3.5 → 3.4.0-beta.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/extension.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { BufferGeometry, Mesh, Texture, TextureLoader } from "three";
2
2
  import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
3
3
  import { addDracoAndKTX2Loaders } from "./loaders.js";
4
- import { getParam, PromiseQueue, resolveUrl } from "./utils.internal.js";
4
+ import { determineTextureMemoryInBytes, getParam, PromiseQueue, resolveUrl } from "./utils.internal.js";
5
5
  import { getRaycastMesh, registerRaycastMesh } from "./utils.js";
6
6
  // All of this has to be removed
7
7
  // import { getRaycastMesh, setRaycastMesh } from "../../engine_physics.js";
@@ -11,6 +11,7 @@ import { debug } from "./lods.debug.js";
11
11
  import { getWorker } from "./worker/loader.mainthread.js";
12
12
  const useWorker = getParam("gltf-progressive-worker");
13
13
  const reduceMipmaps = getParam("gltf-progressive-reduce-mipmaps");
14
+ const debugGC = getParam("gltf-progressive-gc");
14
15
  const $progressiveTextureExtension = Symbol("needle-progressive-texture");
15
16
  export const EXTENSION_NAME = "NEEDLE_progressive";
16
17
  // #region EXT
@@ -334,29 +335,37 @@ export class NEEDLE_progressive {
334
335
  if (assignedLOD && assignedLOD?.level < level) {
335
336
  if (debug === "verbose")
336
337
  console.warn("Assigned texture level is already higher: ", assignedLOD.level, level, material, assigned, tex);
338
+ // Dispose the newly loaded texture since we're not using it
339
+ // (the assigned texture is higher quality, so we reject the new one)
340
+ // Note: We dispose directly here (not via untrackTextureUsage) because this texture
341
+ // was never tracked/used - it was rejected immediately upon loading
342
+ if (tex && tex !== assigned) {
343
+ if (debug || debugGC) {
344
+ console.log(`[gltf-progressive] Disposing rejected lower-quality texture LOD ${level} (assigned is ${assignedLOD.level})`, tex.uuid);
345
+ }
346
+ tex.dispose();
347
+ }
337
348
  return null;
338
349
  }
339
350
  // assigned.dispose();
340
351
  }
341
- // Since we're switching LOD level for the texture based on distance we can avoid uploading all the mipmaps
342
- if (reduceMipmaps && tex.mipmaps) {
343
- const prevCount = tex.mipmaps.length;
344
- tex.mipmaps.length = Math.min(tex.mipmaps.length, 3);
345
- if (prevCount !== tex.mipmaps.length) {
346
- if (debug)
347
- console.debug(`Reduced mipmap count from ${prevCount} to ${tex.mipmaps.length} for ${tex.uuid}: ${tex.image?.width}x${tex.image?.height}.`);
352
+ // Track reference count for new texture
353
+ this.trackTextureUsage(tex);
354
+ // Untrack the old texture (may dispose if ref count hits 0)
355
+ // This prevents accumulation of GPU VRAM while waiting for garbage collection
356
+ if (assigned && assigned !== tex) {
357
+ const wasDisposed = this.untrackTextureUsage(assigned);
358
+ if (wasDisposed && (debug || debugGC)) {
359
+ const assignedLOD = this.getAssignedLODInformation(assigned);
360
+ console.log(`[gltf-progressive] Disposed old texture LOD ${assignedLOD?.level ?? '?'} → ${level} for ${material.name || material.type}.${slot}`, assigned.uuid);
348
361
  }
349
362
  }
350
363
  material[slot] = tex;
351
364
  }
352
- // check if the old texture is still used by other objects
353
- // if not we dispose it...
354
- // this could also be handled elsewhere and not be done immediately
355
- // const users = getResourceUserCount(current);
356
- // if (!users) {
357
- // if (debug) console.log("Progressive: Dispose texture", current.name, current.source.data, current.uuid);
358
- // current?.dispose();
359
- // }
365
+ // Note: We use reference counting above to track texture usage across multiple materials.
366
+ // When the reference count hits zero, GPU memory (VRAM) is freed immediately via gl.deleteTexture(),
367
+ // not waiting for JavaScript garbage collection which may take seconds/minutes.
368
+ // This handles cases where the same texture is shared across multiple materials/objects.
360
369
  }
361
370
  // this.onProgressiveLoadEnd(info);
362
371
  return tex;
@@ -493,7 +502,7 @@ export class NEEDLE_progressive {
493
502
  const key = ext.guid;
494
503
  NEEDLE_progressive.assignLODInformation(url, tex, key, level, index);
495
504
  NEEDLE_progressive.lodInfos.set(key, ext);
496
- NEEDLE_progressive.lowresCache.set(key, tex);
505
+ NEEDLE_progressive.lowresCache.set(key, new WeakRef(tex));
497
506
  };
498
507
  /**
499
508
  * Register a mesh with LOD information
@@ -511,12 +520,15 @@ export class NEEDLE_progressive {
511
520
  console.log("> Progressive: register mesh " + mesh.name, { index, uuid: mesh.uuid }, ext, mesh);
512
521
  NEEDLE_progressive.assignLODInformation(url, geometry, key, level, index);
513
522
  NEEDLE_progressive.lodInfos.set(key, ext);
514
- let existing = NEEDLE_progressive.lowresCache.get(key);
515
- if (existing)
523
+ const existingRef = NEEDLE_progressive.lowresCache.get(key);
524
+ let existing = existingRef?.deref();
525
+ if (existing) {
516
526
  existing.push(mesh.geometry);
517
- else
527
+ }
528
+ else {
518
529
  existing = [mesh.geometry];
519
- NEEDLE_progressive.lowresCache.set(key, existing);
530
+ }
531
+ NEEDLE_progressive.lowresCache.set(key, new WeakRef(existing));
520
532
  if (level > 0 && !getRaycastMesh(mesh)) {
521
533
  registerRaycastMesh(mesh, geometry);
522
534
  }
@@ -524,12 +536,180 @@ export class NEEDLE_progressive {
524
536
  plugin.onRegisteredNewMesh?.(mesh, ext);
525
537
  }
526
538
  };
539
+ /**
540
+ * Dispose cached resources to free memory.
541
+ * Call this when a model is removed from the scene to allow garbage collection of its LOD resources.
542
+ * Calls three.js `.dispose()` on cached Textures and BufferGeometries to free GPU memory.
543
+ * Also clears reference counts for disposed textures.
544
+ * @param guid Optional GUID to dispose resources for a specific model. If omitted, all cached resources are cleared.
545
+ */
546
+ static dispose(guid) {
547
+ if (guid) {
548
+ this.lodInfos.delete(guid);
549
+ // Dispose lowres cache entries (original proxy resources)
550
+ const lowresRef = this.lowresCache.get(guid);
551
+ if (lowresRef) {
552
+ const lowres = lowresRef.deref();
553
+ if (lowres) {
554
+ if (lowres.isTexture) {
555
+ const tex = lowres;
556
+ this.textureRefCounts.delete(tex.uuid); // Clear ref count
557
+ tex.dispose();
558
+ }
559
+ else if (Array.isArray(lowres)) {
560
+ for (const geo of lowres)
561
+ geo.dispose();
562
+ }
563
+ }
564
+ this.lowresCache.delete(guid);
565
+ }
566
+ // Dispose previously loaded LOD entries
567
+ for (const [key, entry] of this.cache) {
568
+ if (key.includes(guid)) {
569
+ this._disposeCacheEntry(entry);
570
+ this.cache.delete(key);
571
+ }
572
+ }
573
+ }
574
+ else {
575
+ this.lodInfos.clear();
576
+ for (const [, entryRef] of this.lowresCache) {
577
+ const entry = entryRef.deref();
578
+ if (entry) {
579
+ if (entry.isTexture) {
580
+ const tex = entry;
581
+ this.textureRefCounts.delete(tex.uuid); // Clear ref count
582
+ tex.dispose();
583
+ }
584
+ else if (Array.isArray(entry)) {
585
+ for (const geo of entry)
586
+ geo.dispose();
587
+ }
588
+ }
589
+ }
590
+ this.lowresCache.clear();
591
+ for (const [, entry] of this.cache) {
592
+ this._disposeCacheEntry(entry);
593
+ }
594
+ this.cache.clear();
595
+ // Clear all texture reference counts when disposing everything
596
+ this.textureRefCounts.clear();
597
+ }
598
+ }
599
+ /** Dispose a single cache entry's three.js resource(s) to free GPU memory. */
600
+ static _disposeCacheEntry(entry) {
601
+ if (entry instanceof WeakRef) {
602
+ // Single resource — deref and dispose if still alive
603
+ const resource = entry.deref();
604
+ if (resource) {
605
+ // Clear ref count for textures
606
+ if (resource.isTexture) {
607
+ this.textureRefCounts.delete(resource.uuid);
608
+ }
609
+ resource.dispose();
610
+ }
611
+ }
612
+ else {
613
+ // Promise — may be in-flight or already resolved.
614
+ // Attach disposal to run after resolution.
615
+ entry.then(resource => {
616
+ if (resource) {
617
+ if (Array.isArray(resource)) {
618
+ for (const geo of resource)
619
+ geo.dispose();
620
+ }
621
+ else {
622
+ // Clear ref count for textures
623
+ if (resource.isTexture) {
624
+ this.textureRefCounts.delete(resource.uuid);
625
+ }
626
+ resource.dispose();
627
+ }
628
+ }
629
+ }).catch(() => { });
630
+ }
631
+ }
527
632
  /** A map of key = asset uuid and value = LOD information */
528
633
  static lodInfos = new Map();
529
- /** cache of already loaded mesh lods */
530
- static previouslyLoaded = new Map();
531
- /** this contains the geometry/textures that were originally loaded */
634
+ /** cache of already loaded mesh lods. Uses WeakRef for single resources to allow garbage collection when unused. */
635
+ static cache = new Map();
636
+ /** this contains the geometry/textures that were originally loaded. Uses WeakRef to allow garbage collection when unused. */
532
637
  static lowresCache = new Map();
638
+ /** Reference counting for textures to track usage across multiple materials/objects */
639
+ static textureRefCounts = new Map();
640
+ /**
641
+ * FinalizationRegistry to automatically clean up `previouslyLoaded` cache entries
642
+ * when their associated three.js resources are garbage collected by the browser.
643
+ * The held value is the cache key string used in `previouslyLoaded`.
644
+ */
645
+ static _resourceRegistry = new FinalizationRegistry((cacheKey) => {
646
+ const entry = NEEDLE_progressive.cache.get(cacheKey);
647
+ if (debug || debugGC)
648
+ console.debug(`[gltf-progressive] Memory: Resource GC'd\n${cacheKey}`);
649
+ // Only delete if the entry is still a WeakRef and the resource is gone
650
+ if (entry instanceof WeakRef) {
651
+ const derefed = entry.deref();
652
+ if (!derefed) {
653
+ NEEDLE_progressive.cache.delete(cacheKey);
654
+ if (debug || debugGC)
655
+ console.log(`[gltf-progressive] ↪ Cache entry deleted (GC)`);
656
+ }
657
+ }
658
+ });
659
+ /**
660
+ * Track texture usage by incrementing reference count
661
+ */
662
+ static trackTextureUsage(texture) {
663
+ const uuid = texture.uuid;
664
+ const count = this.textureRefCounts.get(uuid) || 0;
665
+ this.textureRefCounts.set(uuid, count + 1);
666
+ if (debug === "verbose") {
667
+ console.log(`[gltf-progressive] Track texture ${uuid}, refCount: ${count} → ${count + 1}`);
668
+ }
669
+ }
670
+ /**
671
+ * Untrack texture usage by decrementing reference count.
672
+ * Automatically disposes the texture when reference count reaches zero.
673
+ * @returns true if the texture was disposed, false otherwise
674
+ */
675
+ static untrackTextureUsage(texture) {
676
+ const uuid = texture.uuid;
677
+ const count = this.textureRefCounts.get(uuid);
678
+ if (!count) {
679
+ // Texture wasn't tracked, dispose immediately (safe fallback)
680
+ if (debug === "verbose" || debugGC) {
681
+ logDebugInfo(`[gltf-progressive] Memory: Untrack untracked texture (dispose immediately)`, 0);
682
+ }
683
+ texture.dispose();
684
+ return true;
685
+ }
686
+ const newCount = count - 1;
687
+ if (newCount <= 0) {
688
+ this.textureRefCounts.delete(uuid);
689
+ if (debug || debugGC) {
690
+ logDebugInfo(`[gltf-progressive] Memory: Dispose texture`, newCount);
691
+ }
692
+ texture.dispose();
693
+ return true;
694
+ }
695
+ else {
696
+ this.textureRefCounts.set(uuid, newCount);
697
+ if (debug === "verbose") {
698
+ logDebugInfo(`[gltf-progressive] Memory: Untrack texture`, newCount);
699
+ }
700
+ return false;
701
+ }
702
+ function logDebugInfo(prefix, newCount) {
703
+ let width = texture.image?.width || texture.source?.data?.width || 0;
704
+ let height = texture.image?.height || texture.source?.data?.height || 0;
705
+ const textureSize = width && height ? `${width}x${height}` : "N/A";
706
+ let memorySize = "N/A";
707
+ if (width && height) {
708
+ memorySize = `~${(determineTextureMemoryInBytes(texture) / (1024 * 1024)).toFixed(2)} MB`;
709
+ }
710
+ console.log(`${prefix} — ${texture.name} ${textureSize} (${memorySize}), refCount: ${count} → ${newCount}\n${uuid}`);
711
+ }
712
+ }
533
713
  static workers = [];
534
714
  static _workersIndex = 0;
535
715
  static async getOrLoadLOD(current, level) {
@@ -567,8 +747,18 @@ export class NEEDLE_progressive {
567
747
  useLowRes = true;
568
748
  }
569
749
  if (useLowRes) {
570
- const lowres = this.lowresCache.get(LODKEY);
571
- return lowres;
750
+ const lowresRef = this.lowresCache.get(LODKEY);
751
+ if (lowresRef) {
752
+ const lowres = lowresRef.deref();
753
+ if (lowres)
754
+ return lowres;
755
+ // Resource was GC'd, remove stale entry
756
+ this.lowresCache.delete(LODKEY);
757
+ if (debug)
758
+ console.log(`[gltf-progressive] Lowres cache entry was GC'd: ${LODKEY}`);
759
+ }
760
+ // Fallback to current if lowres was GC'd
761
+ return null;
572
762
  }
573
763
  }
574
764
  /** the unresolved LOD url */
@@ -592,42 +782,73 @@ export class NEEDLE_progressive {
592
782
  // check if the requested file has already been loaded
593
783
  const KEY = lod_url + "_" + lodInfo.guid;
594
784
  const slot = await this.queue.slot(lod_url);
595
- // check if the requested file is currently being loaded
596
- const existing = this.previouslyLoaded.get(KEY);
785
+ // check if the requested file is currently being loaded or was previously loaded
786
+ const existing = this.cache.get(KEY);
597
787
  if (existing !== undefined) {
598
788
  if (debugverbose)
599
789
  console.log(`LOD ${level} was already loading/loaded: ${KEY}`);
600
- let res = await existing.catch(err => {
601
- console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
602
- return null;
603
- });
604
- let resouceIsDisposed = false;
605
- if (res == null) {
606
- // if the resource is null the last loading result didnt succeed (maybe because the url doesnt exist)
607
- // in which case we don't attempt to load it again
608
- }
609
- else if (res instanceof Texture && current instanceof Texture) {
610
- // check if the texture has been disposed or not
611
- if (res.image?.data || res.source?.data) {
612
- res = this.copySettings(current, res);
613
- }
614
- // if it has been disposed we need to load it again
615
- else {
616
- resouceIsDisposed = true;
617
- this.previouslyLoaded.delete(KEY);
790
+ if (existing instanceof WeakRef) {
791
+ // Previously resolved resource check if still alive in memory
792
+ const derefed = existing.deref();
793
+ if (derefed) {
794
+ let res = derefed;
795
+ let resourceIsDisposed = false;
796
+ if (res instanceof Texture && current instanceof Texture) {
797
+ if (res.image?.data || res.source?.data) {
798
+ res = this.copySettings(current, res);
799
+ }
800
+ else {
801
+ resourceIsDisposed = true;
802
+ }
803
+ }
804
+ else if (res instanceof BufferGeometry && current instanceof BufferGeometry) {
805
+ if (!res.attributes.position?.array) {
806
+ resourceIsDisposed = true;
807
+ }
808
+ }
809
+ if (!resourceIsDisposed) {
810
+ return res;
811
+ }
618
812
  }
813
+ // Resource was garbage collected or disposed — remove stale entry and re-load
814
+ this.cache.delete(KEY);
815
+ if (debug)
816
+ console.log(`[gltf-progressive] Re-loading GC'd/disposed resource: ${KEY}`);
619
817
  }
620
- else if (res instanceof BufferGeometry && current instanceof BufferGeometry) {
621
- if (res.attributes.position?.array) {
622
- // the geometry is OK
818
+ else {
819
+ // Promise — loading in progress or previously completed
820
+ let res = await existing.catch(err => {
821
+ console.error(`Error loading LOD ${level} from ${lod_url}\n`, err);
822
+ return null;
823
+ });
824
+ let resouceIsDisposed = false;
825
+ if (res == null) {
826
+ // if the resource is null the last loading result didnt succeed (maybe because the url doesnt exist)
827
+ // in which case we don't attempt to load it again
623
828
  }
624
- else {
625
- resouceIsDisposed = true;
626
- this.previouslyLoaded.delete(KEY);
829
+ else if (res instanceof Texture && current instanceof Texture) {
830
+ // check if the texture has been disposed or not
831
+ if (res.image?.data || res.source?.data) {
832
+ res = this.copySettings(current, res);
833
+ }
834
+ // if it has been disposed we need to load it again
835
+ else {
836
+ resouceIsDisposed = true;
837
+ this.cache.delete(KEY);
838
+ }
839
+ }
840
+ else if (res instanceof BufferGeometry && current instanceof BufferGeometry) {
841
+ if (res.attributes.position?.array) {
842
+ // the geometry is OK
843
+ }
844
+ else {
845
+ resouceIsDisposed = true;
846
+ this.cache.delete(KEY);
847
+ }
848
+ }
849
+ if (!resouceIsDisposed) {
850
+ return res;
627
851
  }
628
- }
629
- if (!resouceIsDisposed) {
630
- return res;
631
852
  }
632
853
  }
633
854
  // #region loading
@@ -774,9 +995,33 @@ export class NEEDLE_progressive {
774
995
  // we could not find a texture or mesh with the given guid
775
996
  return resolve(null);
776
997
  });
777
- this.previouslyLoaded.set(KEY, request);
998
+ this.cache.set(KEY, request);
778
999
  slot.use(request);
779
1000
  const res = await request;
1001
+ // Optimize cache entry: replace loading promise with lightweight reference.
1002
+ // This releases closure variables captured during the loading function.
1003
+ if (res != null) {
1004
+ if (res instanceof Texture) {
1005
+ // For Texture resources, use WeakRef to allow garbage collection.
1006
+ // The FinalizationRegistry will auto-clean this entry when the resource is GC'd.
1007
+ this.cache.set(KEY, new WeakRef(res));
1008
+ NEEDLE_progressive._resourceRegistry.register(res, KEY);
1009
+ }
1010
+ else if (Array.isArray(res)) {
1011
+ // For BufferGeometry[] (multi-primitive meshes), use a resolved promise.
1012
+ // This keeps geometries in memory as they should not be GC'd (mesh LODs stay cached).
1013
+ this.cache.set(KEY, Promise.resolve(res));
1014
+ }
1015
+ else {
1016
+ // For single BufferGeometry, keep in memory (don't use WeakRef)
1017
+ this.cache.set(KEY, Promise.resolve(res));
1018
+ }
1019
+ }
1020
+ else {
1021
+ // Failed load — replace with clean resolved promise to release loading closure.
1022
+ // Keeping the entry prevents retrying (existing behavior).
1023
+ this.cache.set(KEY, Promise.resolve(null));
1024
+ }
780
1025
  return res;
781
1026
  }
782
1027
  else {
@@ -1,3 +1,4 @@
1
+ import { Texture } from "three";
1
2
  export declare function isDebugMode(): string | boolean;
2
3
  export declare function getParam(name: string): boolean | string;
3
4
  export declare function resolveUrl(source: string | undefined, uri: string): string;
@@ -33,3 +34,8 @@ export declare class PromiseQueue<T = any> {
33
34
  private add;
34
35
  private internalUpdate;
35
36
  }
37
+ export declare function determineTextureMemoryInBytes(texture: Texture): number;
38
+ /**
39
+ * Detect the GPU memory of the current device. This is a very rough estimate based on the renderer information, and may not be accurate. It returns the estimated memory in MB, or `undefined` if it cannot be detected.
40
+ */
41
+ export declare function detectGPUMemory(): number | undefined;
@@ -115,3 +115,95 @@ export class PromiseQueue {
115
115
  }
116
116
  }
117
117
  }
118
+ // #region Texture Memory
119
+ export function determineTextureMemoryInBytes(texture) {
120
+ const width = texture.image?.width ?? 0;
121
+ const height = texture.image?.height ?? 0;
122
+ const depth = texture.image?.depth ?? 1;
123
+ const mipLevels = Math.floor(Math.log2(Math.max(width, height, depth))) + 1;
124
+ const bytesPerPixel = getBytesPerPixel(texture);
125
+ const totalBytes = (width * height * depth * bytesPerPixel * (1 - Math.pow(0.25, mipLevels))) / (1 - 0.25);
126
+ return totalBytes;
127
+ }
128
+ function getBytesPerPixel(texture) {
129
+ // Determine channel count from format
130
+ let channels = 4; // Default RGBA
131
+ const format = texture.format;
132
+ if (format === 1024)
133
+ channels = 1; // RedFormat
134
+ else if (format === 1025)
135
+ channels = 1; // RedIntegerFormat
136
+ else if (format === 1026)
137
+ channels = 2; // RGFormat
138
+ else if (format === 1027)
139
+ channels = 2; // RGIntegerFormat
140
+ else if (format === 1022)
141
+ channels = 3; // RGBFormat
142
+ else if (format === 1029)
143
+ channels = 3; // RGBIntegerFormat
144
+ else if (format === 1023)
145
+ channels = 4; // RGBAFormat
146
+ else if (format === 1033)
147
+ channels = 4; // RGBAIntegerFormat
148
+ // Determine bytes per channel from type
149
+ let bytesPerChannel = 1; // UnsignedByteType default
150
+ const type = texture.type;
151
+ if (type === 1009)
152
+ bytesPerChannel = 1; // UnsignedByteType
153
+ else if (type === 1010)
154
+ bytesPerChannel = 1; // ByteType
155
+ else if (type === 1011)
156
+ bytesPerChannel = 2; // ShortType
157
+ else if (type === 1012)
158
+ bytesPerChannel = 2; // UnsignedShortType
159
+ else if (type === 1013)
160
+ bytesPerChannel = 4; // IntType
161
+ else if (type === 1014)
162
+ bytesPerChannel = 4; // UnsignedIntType
163
+ else if (type === 1015)
164
+ bytesPerChannel = 4; // FloatType
165
+ else if (type === 1016)
166
+ bytesPerChannel = 2; // HalfFloatType
167
+ const bytesPerPixel = channels * bytesPerChannel;
168
+ return bytesPerPixel;
169
+ }
170
+ // #region GPU
171
+ let rendererInfo;
172
+ /**
173
+ * Detect the GPU memory of the current device. This is a very rough estimate based on the renderer information, and may not be accurate. It returns the estimated memory in MB, or `undefined` if it cannot be detected.
174
+ */
175
+ export function detectGPUMemory() {
176
+ if (rendererInfo !== undefined) {
177
+ return rendererInfo?.estimatedMemory;
178
+ }
179
+ const canvas = document.createElement('canvas');
180
+ const powerPreference = "high-performance";
181
+ const gl = canvas.getContext('webgl', { powerPreference }) || canvas.getContext('experimental-webgl', { powerPreference });
182
+ if (!gl) {
183
+ return undefined;
184
+ }
185
+ if ("getExtension" in gl) {
186
+ const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
187
+ if (debugInfo) {
188
+ const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL);
189
+ const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
190
+ // Estimate memory based on renderer information (this is a very rough estimate)
191
+ let estimatedMemory = 512;
192
+ if (/NVIDIA/i.test(renderer)) {
193
+ estimatedMemory = 2048;
194
+ }
195
+ else if (/AMD/i.test(renderer)) {
196
+ estimatedMemory = 1024;
197
+ }
198
+ else if (/Intel/i.test(renderer)) {
199
+ estimatedMemory = 512;
200
+ }
201
+ rendererInfo = { vendor, renderer, estimatedMemory };
202
+ return estimatedMemory;
203
+ }
204
+ }
205
+ else {
206
+ rendererInfo = null;
207
+ }
208
+ return undefined;
209
+ }
package/lib/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // replaced at build time
2
- export const version = "3.3.5";
2
+ export const version = "3.4.0-beta.1";
3
3
  globalThis["GLTF_PROGRESSIVE_VERSION"] = version;
4
4
  console.debug(`[gltf-progressive] version ${version || "-"}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@needle-tools/gltf-progressive",
3
- "version": "3.3.5",
3
+ "version": "3.4.0-beta.1",
4
4
  "description": "three.js support for loading glTF or GLB files that contain progressive loading data",
5
5
  "homepage": "https://needle.tools",
6
6
  "author": {