@lightningjs/renderer 3.0.0-beta8 → 3.0.0-beta9

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.
Files changed (28) hide show
  1. package/dist/src/core/CoreNode.js +6 -0
  2. package/dist/src/core/CoreNode.js.map +1 -1
  3. package/dist/src/core/CoreTextureManager.d.ts +4 -9
  4. package/dist/src/core/CoreTextureManager.js +37 -75
  5. package/dist/src/core/CoreTextureManager.js.map +1 -1
  6. package/dist/src/core/TextureMemoryManager.d.ts +1 -1
  7. package/dist/src/core/TextureMemoryManager.js +25 -2
  8. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  9. package/dist/src/core/animations/CoreAnimationController.js +4 -3
  10. package/dist/src/core/animations/CoreAnimationController.js.map +1 -1
  11. package/dist/src/core/lib/WebGlContextWrapper.d.ts +19 -2
  12. package/dist/src/core/lib/WebGlContextWrapper.js +56 -25
  13. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  14. package/dist/src/core/renderers/webgl/WebGlShaderProgram.d.ts +4 -2
  15. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +31 -13
  16. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
  17. package/dist/src/core/textures/Texture.d.ts +4 -0
  18. package/dist/src/core/textures/Texture.js +9 -3
  19. package/dist/src/core/textures/Texture.js.map +1 -1
  20. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  21. package/package.json +1 -1
  22. package/src/core/CoreNode.ts +6 -0
  23. package/src/core/CoreTextureManager.ts +36 -96
  24. package/src/core/TextureMemoryManager.ts +31 -4
  25. package/src/core/animations/CoreAnimationController.ts +5 -3
  26. package/src/core/lib/WebGlContextWrapper.ts +69 -75
  27. package/src/core/renderers/webgl/WebGlShaderProgram.ts +37 -14
  28. package/src/core/textures/Texture.ts +12 -3
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import { isProductionEnvironment } from '../utils.js';
20
20
  import type { Stage } from './Stage.js';
21
- import { TextureType, type Texture } from './textures/Texture.js';
21
+ import { Texture, TextureType, type TextureState } from './textures/Texture.js';
22
22
  import { bytesToMb } from './utils.js';
23
23
 
24
24
  export interface TextureMemoryManagerSettings {
@@ -237,6 +237,12 @@ export class TextureMemoryManager {
237
237
  continue;
238
238
  }
239
239
 
240
+ // Skip textures that are in transitional states - we only want to clean up
241
+ // textures that are in a stable state (loaded, failed, or freed)
242
+ if (Texture.TRANSITIONAL_TEXTURE_STATES.includes(texture.state)) {
243
+ continue;
244
+ }
245
+
240
246
  this.destroyTexture(texture);
241
247
  }
242
248
  }
@@ -247,6 +253,12 @@ export class TextureMemoryManager {
247
253
  * @param texture - The texture to destroy
248
254
  */
249
255
  destroyTexture(texture: Texture) {
256
+ if (this.debugLogging === true) {
257
+ console.log(
258
+ `[TextureMemoryManager] Destroying texture. State: ${texture.state}`,
259
+ );
260
+ }
261
+
250
262
  const txManager = this.stage.txManager;
251
263
  txManager.removeTextureFromQueue(texture);
252
264
  txManager.removeTextureFromCache(texture);
@@ -296,6 +308,12 @@ export class TextureMemoryManager {
296
308
  break;
297
309
  }
298
310
 
311
+ // Skip textures that are in transitional states - we only want to clean up
312
+ // textures that are in a stable state (loaded, failed, or freed)
313
+ if (Texture.TRANSITIONAL_TEXTURE_STATES.includes(texture.state)) {
314
+ break;
315
+ }
316
+
299
317
  this.destroyTexture(texture);
300
318
  }
301
319
  }
@@ -320,6 +338,15 @@ export class TextureMemoryManager {
320
338
  );
321
339
  }
322
340
 
341
+ // Note: We skip textures in transitional states during cleanup:
342
+ // - 'initial': These textures haven't started loading yet
343
+ // - 'fetching': These textures are in the process of being fetched
344
+ // - 'fetched': These textures have been fetched but not yet uploaded to GPU
345
+ // - 'loading': These textures are being uploaded to the GPU
346
+ //
347
+ // For 'failed' and 'freed' states, we only remove them from the tracking
348
+ // arrays without trying to free GPU resources that don't exist.
349
+
323
350
  // try a quick cleanup first
324
351
  this.cleanupQuick(critical);
325
352
 
@@ -356,9 +383,9 @@ export class TextureMemoryManager {
356
383
  const renderableMemUsed = [...this.loadedTextures.keys()].reduce(
357
384
  (acc, texture) => {
358
385
  renderableTexturesLoaded += texture.renderable ? 1 : 0;
359
- return (
360
- acc + (texture.renderable ? this.loadedTextures.get(texture)! : 0)
361
- );
386
+ // Get the memory used by the texture, defaulting to 0 if not found
387
+ const textureMemory = this.loadedTextures.get(texture) ?? 0;
388
+ return acc + (texture.renderable ? textureMemory : 0);
362
389
  },
363
390
  this.baselineMemoryAllocation,
364
391
  );
@@ -125,7 +125,6 @@ export class CoreAnimationController
125
125
  }
126
126
 
127
127
  private onFinished(this: CoreAnimationController): void {
128
- assertTruthy(this.stoppedResolve);
129
128
  // If the animation is looping, then we need to restart it.
130
129
  const { loop, stopMethod } = this.animation.settings;
131
130
 
@@ -143,8 +142,11 @@ export class CoreAnimationController
143
142
  this.unregisterAnimation();
144
143
 
145
144
  // resolve promise
146
- this.stoppedResolve();
147
- this.stoppedResolve = null;
145
+ if (this.stoppedResolve !== null) {
146
+ this.stoppedResolve();
147
+ this.stoppedResolve = null;
148
+ }
149
+
148
150
  this.emit('stopped', this);
149
151
  this.state = 'stopped';
150
152
  }
@@ -50,6 +50,7 @@ export class WebGlContextWrapper {
50
50
  private boundArrayBuffer: WebGLBuffer | null;
51
51
  private boundElementArrayBuffer: WebGLBuffer | null;
52
52
  private curProgram: WebGLProgram | null;
53
+ private curUniformLocations: Record<string, WebGLUniformLocation> = {};
53
54
  //#endregion Cached WebGL State
54
55
 
55
56
  //#region Canvas
@@ -689,16 +690,23 @@ export class WebGlContextWrapper {
689
690
  * @param program
690
691
  * @returns object with numbers
691
692
  */
692
- getUniformLocations(program: WebGLProgram): Record<string, number> {
693
+ getUniformLocations(
694
+ program: WebGLProgram,
695
+ ): Record<string, WebGLUniformLocation> {
693
696
  const gl = this.gl;
694
697
  const length = gl.getProgramParameter(
695
698
  program,
696
699
  gl.ACTIVE_UNIFORMS,
697
700
  ) as number;
698
- const result = {} as Record<string, number>;
701
+ const result = {} as Record<string, WebGLUniformLocation>;
699
702
  for (let i = 0; i < length; i++) {
700
- const { name } = gl.getActiveUniform(program, i) as WebGLActiveInfo;
701
- result[name] = i;
703
+ const info = gl.getActiveUniform(program, i) as WebGLActiveInfo;
704
+ //remove bracket + value from uniform name;
705
+ let name = info.name.replace(/\[.*?\]/g, '');
706
+ result[name] = gl.getUniformLocation(
707
+ program,
708
+ name,
709
+ ) as WebGLUniformLocation;
702
710
  }
703
711
  return result;
704
712
  }
@@ -730,12 +738,16 @@ export class WebGlContextWrapper {
730
738
  * @param program
731
739
  * @returns
732
740
  */
733
- useProgram(program: WebGLProgram | null) {
741
+ useProgram(
742
+ program: WebGLProgram | null,
743
+ uniformLocations: Record<string, WebGLUniformLocation>,
744
+ ) {
734
745
  if (this.curProgram === program) {
735
746
  return;
736
747
  }
737
748
  this.gl.useProgram(program);
738
749
  this.curProgram = program;
750
+ this.curUniformLocations = uniformLocations;
739
751
  }
740
752
 
741
753
  /**
@@ -745,10 +757,7 @@ export class WebGlContextWrapper {
745
757
  * @param v0 - The value to set.
746
758
  */
747
759
  uniform1f(location: string, v0: number) {
748
- this.gl.uniform1f(
749
- this.gl.getUniformLocation(this.curProgram!, location),
750
- v0,
751
- );
760
+ this.gl.uniform1f(this.curUniformLocations[location] || null, v0);
752
761
  }
753
762
 
754
763
  /**
@@ -758,10 +767,7 @@ export class WebGlContextWrapper {
758
767
  * @param value - The array of values to set.
759
768
  */
760
769
  uniform1fv(location: string, value: Float32Array) {
761
- this.gl.uniform1fv(
762
- this.gl.getUniformLocation(this.curProgram!, location),
763
- value,
764
- );
770
+ this.gl.uniform1fv(this.curUniformLocations[location] || null, value);
765
771
  }
766
772
 
767
773
  /**
@@ -771,10 +777,7 @@ export class WebGlContextWrapper {
771
777
  * @param v0 - The value to set.
772
778
  */
773
779
  uniform1i(location: string, v0: number) {
774
- this.gl.uniform1i(
775
- this.gl.getUniformLocation(this.curProgram!, location),
776
- v0,
777
- );
780
+ this.gl.uniform1i(this.curUniformLocations[location] || null, v0);
778
781
  }
779
782
 
780
783
  /**
@@ -784,10 +787,7 @@ export class WebGlContextWrapper {
784
787
  * @param value - The array of values to set.
785
788
  */
786
789
  uniform1iv(location: string, value: Int32Array) {
787
- this.gl.uniform1iv(
788
- this.gl.getUniformLocation(this.curProgram!, location),
789
- value,
790
- );
790
+ this.gl.uniform1iv(this.curUniformLocations[location] || null, value);
791
791
  }
792
792
 
793
793
  /**
@@ -798,11 +798,7 @@ export class WebGlContextWrapper {
798
798
  * @param v1 - The second component of the vector.
799
799
  */
800
800
  uniform2f(location: string, v0: number, v1: number) {
801
- this.gl.uniform2f(
802
- this.gl.getUniformLocation(this.curProgram!, location),
803
- v0,
804
- v1,
805
- );
801
+ this.gl.uniform2f(this.curUniformLocations[location] || null, v0, v1);
806
802
  }
807
803
 
808
804
  /**
@@ -813,7 +809,7 @@ export class WebGlContextWrapper {
813
809
  */
814
810
  uniform2fa(location: string, value: Vec2) {
815
811
  this.gl.uniform2f(
816
- this.gl.getUniformLocation(this.curProgram!, location),
812
+ this.curUniformLocations[location] || null,
817
813
  value[0],
818
814
  value[1],
819
815
  );
@@ -826,10 +822,7 @@ export class WebGlContextWrapper {
826
822
  * @param value - The array of vec2 values to set.
827
823
  */
828
824
  uniform2fv(location: string, value: Float32Array) {
829
- this.gl.uniform2fv(
830
- this.gl.getUniformLocation(this.curProgram!, location),
831
- value,
832
- );
825
+ this.gl.uniform2fv(this.curUniformLocations[location] || null, value);
833
826
  }
834
827
 
835
828
  /**
@@ -840,11 +833,7 @@ export class WebGlContextWrapper {
840
833
  * @param v1 - The second component of the vector.
841
834
  */
842
835
  uniform2i(location: string, v0: number, v1: number) {
843
- this.gl.uniform2i(
844
- this.gl.getUniformLocation(this.curProgram!, location),
845
- v0,
846
- v1,
847
- );
836
+ this.gl.uniform2i(this.curUniformLocations[location] || null, v0, v1);
848
837
  }
849
838
 
850
839
  /**
@@ -854,10 +843,7 @@ export class WebGlContextWrapper {
854
843
  * @param value - The array of ivec2 values to set.
855
844
  */
856
845
  uniform2iv(location: string, value: Int32Array) {
857
- this.gl.uniform2iv(
858
- this.gl.getUniformLocation(this.curProgram!, location),
859
- value,
860
- );
846
+ this.gl.uniform2iv(this.curUniformLocations[location] || null, value);
861
847
  }
862
848
 
863
849
  /**
@@ -869,12 +855,7 @@ export class WebGlContextWrapper {
869
855
  * @param v2 - The third component of the vector.
870
856
  */
871
857
  uniform3f(location: string, v0: number, v1: number, v2: number) {
872
- this.gl.uniform3f(
873
- this.gl.getUniformLocation(this.curProgram!, location),
874
- v0,
875
- v1,
876
- v2,
877
- );
858
+ this.gl.uniform3f(this.curUniformLocations[location] || null, v0, v1, v2);
878
859
  }
879
860
 
880
861
  /**
@@ -885,7 +866,7 @@ export class WebGlContextWrapper {
885
866
  */
886
867
  uniform3fa(location: string, value: Vec3) {
887
868
  this.gl.uniform3f(
888
- this.gl.getUniformLocation(this.curProgram!, location),
869
+ this.curUniformLocations[location] || null,
889
870
  value[0],
890
871
  value[1],
891
872
  value[2],
@@ -899,10 +880,7 @@ export class WebGlContextWrapper {
899
880
  * @param value - The array of vec3 values to set.
900
881
  */
901
882
  uniform3fv(location: string, value: Float32Array) {
902
- this.gl.uniform3fv(
903
- this.gl.getUniformLocation(this.curProgram!, location),
904
- value,
905
- );
883
+ this.gl.uniform3fv(this.curUniformLocations[location] || null, value);
906
884
  }
907
885
 
908
886
  /**
@@ -914,12 +892,7 @@ export class WebGlContextWrapper {
914
892
  * @param v2 - The third component of the vector.
915
893
  */
916
894
  uniform3i(location: string, v0: number, v1: number, v2: number) {
917
- this.gl.uniform3i(
918
- this.gl.getUniformLocation(this.curProgram!, location),
919
- v0,
920
- v1,
921
- v2,
922
- );
895
+ this.gl.uniform3i(this.curUniformLocations[location] || null, v0, v1, v2);
923
896
  }
924
897
 
925
898
  /**
@@ -929,10 +902,7 @@ export class WebGlContextWrapper {
929
902
  * @param value - The array of ivec3 values to set.
930
903
  */
931
904
  uniform3iv(location: string, value: Int32Array) {
932
- this.gl.uniform3iv(
933
- this.gl.getUniformLocation(this.curProgram!, location),
934
- value,
935
- );
905
+ this.gl.uniform3iv(this.curUniformLocations[location] || null, value);
936
906
  }
937
907
 
938
908
  /**
@@ -946,7 +916,7 @@ export class WebGlContextWrapper {
946
916
  */
947
917
  uniform4f(location: string, v0: number, v1: number, v2: number, v3: number) {
948
918
  this.gl.uniform4f(
949
- this.gl.getUniformLocation(this.curProgram!, location),
919
+ this.curUniformLocations[location] || null,
950
920
  v0,
951
921
  v1,
952
922
  v2,
@@ -962,7 +932,7 @@ export class WebGlContextWrapper {
962
932
  */
963
933
  uniform4fa(location: string, value: Vec4) {
964
934
  this.gl.uniform4f(
965
- this.gl.getUniformLocation(this.curProgram!, location),
935
+ this.curUniformLocations[location] || null,
966
936
  value[0],
967
937
  value[1],
968
938
  value[2],
@@ -977,10 +947,7 @@ export class WebGlContextWrapper {
977
947
  * @param value - The array of vec4 values to set.
978
948
  */
979
949
  uniform4fv(location: string, value: Float32Array) {
980
- this.gl.uniform4fv(
981
- this.gl.getUniformLocation(this.curProgram!, location),
982
- value,
983
- );
950
+ this.gl.uniform4fv(this.curUniformLocations[location] || null, value);
984
951
  }
985
952
 
986
953
  /**
@@ -994,7 +961,7 @@ export class WebGlContextWrapper {
994
961
  */
995
962
  uniform4i(location: string, v0: number, v1: number, v2: number, v3: number) {
996
963
  this.gl.uniform4i(
997
- this.gl.getUniformLocation(this.curProgram!, location),
964
+ this.curUniformLocations[location] || null,
998
965
  v0,
999
966
  v1,
1000
967
  v2,
@@ -1009,10 +976,7 @@ export class WebGlContextWrapper {
1009
976
  * @param value - The array of ivec4 values to set.
1010
977
  */
1011
978
  uniform4iv(location: string, value: Int32Array) {
1012
- this.gl.uniform4iv(
1013
- this.gl.getUniformLocation(this.curProgram!, location),
1014
- value,
1015
- );
979
+ this.gl.uniform4iv(this.curUniformLocations[location] || null, value);
1016
980
  }
1017
981
 
1018
982
  /**
@@ -1024,7 +988,7 @@ export class WebGlContextWrapper {
1024
988
  */
1025
989
  uniformMatrix2fv(location: string, value: Float32Array) {
1026
990
  this.gl.uniformMatrix2fv(
1027
- this.gl.getUniformLocation(this.curProgram!, location),
991
+ this.curUniformLocations[location] || null,
1028
992
  false,
1029
993
  value,
1030
994
  );
@@ -1037,7 +1001,7 @@ export class WebGlContextWrapper {
1037
1001
  */
1038
1002
  uniformMatrix3fv(location: string, value: Float32Array) {
1039
1003
  this.gl.uniformMatrix3fv(
1040
- this.gl.getUniformLocation(this.curProgram!, location),
1004
+ this.curUniformLocations[location] || null,
1041
1005
  false,
1042
1006
  value,
1043
1007
  );
@@ -1050,7 +1014,7 @@ export class WebGlContextWrapper {
1050
1014
  */
1051
1015
  uniformMatrix4fv(location: string, value: Float32Array) {
1052
1016
  this.gl.uniformMatrix4fv(
1053
- this.gl.getUniformLocation(this.curProgram!, location),
1017
+ this.curUniformLocations[location] || null,
1054
1018
  false,
1055
1019
  value,
1056
1020
  );
@@ -1319,6 +1283,36 @@ export class WebGlContextWrapper {
1319
1283
  deleteShader(shader: WebGLShader) {
1320
1284
  this.gl.deleteShader(shader);
1321
1285
  }
1286
+
1287
+ /**
1288
+ * ```
1289
+ * gl.deleteBuffer(buffer);
1290
+ * ```
1291
+ *
1292
+ * @param buffer - The buffer to delete
1293
+ */
1294
+ deleteBuffer(buffer: WebGLBuffer) {
1295
+ const { gl } = this;
1296
+ gl.deleteBuffer(buffer);
1297
+
1298
+ // Reset bound buffers if they match the deleted buffer
1299
+ if (this.boundArrayBuffer === buffer) {
1300
+ this.boundArrayBuffer = null;
1301
+ }
1302
+ }
1303
+
1304
+ /**
1305
+ * ```
1306
+ * gl.deleteVertexArray(vertexArray);
1307
+ * ```
1308
+ *
1309
+ * @param vertexArray - The vertex array object to delete
1310
+ */
1311
+ deleteVertexArray(vertexArray: WebGLVertexArrayObject) {
1312
+ if (this.isWebGl2()) {
1313
+ (this.gl as WebGL2RenderingContext).deleteVertexArray(vertexArray);
1314
+ }
1315
+ }
1322
1316
  }
1323
1317
 
1324
1318
  // prettier-ignore
@@ -35,8 +35,7 @@ import {
35
35
  } from './internal/ShaderUtils.js';
36
36
 
37
37
  export class WebGlShaderProgram implements CoreShaderProgram {
38
- protected boundBufferCollection: BufferCollection | null = null;
39
- protected program: WebGLProgram;
38
+ protected program: WebGLProgram | null;
40
39
  /**
41
40
  * Vertex Array Object
42
41
  *
@@ -47,9 +46,11 @@ export class WebGlShaderProgram implements CoreShaderProgram {
47
46
  protected renderer: WebGlRenderer;
48
47
  protected glw: WebGlContextWrapper;
49
48
  protected attributeLocations: Record<string, number>;
49
+ protected uniformLocations: Record<string, WebGLUniformLocation> | null;
50
50
  protected lifecycle: Pick<WebGlShaderType, 'update' | 'canBatch'>;
51
51
  protected useSystemAlpha = false;
52
52
  protected useSystemDimensions = false;
53
+ public isDestroyed = false;
53
54
  supportsIndexedTextures = false;
54
55
 
55
56
  constructor(
@@ -117,10 +118,10 @@ export class WebGlShaderProgram implements CoreShaderProgram {
117
118
  this.program = program;
118
119
  this.attributeLocations = glw.getAttributeLocations(program);
119
120
 
120
- this.useSystemAlpha =
121
- this.glw.getUniformLocation(program, 'u_alpha') !== null;
122
- this.useSystemDimensions =
123
- this.glw.getUniformLocation(program, 'u_dimensions') !== null;
121
+ const uniLocs = (this.uniformLocations = glw.getUniformLocations(program));
122
+
123
+ this.useSystemAlpha = uniLocs['u_alpha'] !== undefined;
124
+ this.useSystemDimensions = uniLocs['u_dimensions'] !== undefined;
124
125
 
125
126
  this.lifecycle = {
126
127
  update: config.update,
@@ -133,7 +134,7 @@ export class WebGlShaderProgram implements CoreShaderProgram {
133
134
  }
134
135
 
135
136
  disableAttributes() {
136
- const { glw } = this;
137
+ const glw = this.glw;
137
138
  const attribs = Object.keys(this.attributeLocations);
138
139
  const attribLen = attribs.length;
139
140
  for (let i = 0; i < attribLen; i++) {
@@ -221,13 +222,13 @@ export class WebGlShaderProgram implements CoreShaderProgram {
221
222
  );
222
223
  }
223
224
 
224
- // if (this.useSystemAlpha) {
225
- this.glw.uniform1f('u_alpha', renderOp.alpha);
226
- // }
225
+ if (this.useSystemAlpha === true) {
226
+ this.glw.uniform1f('u_alpha', renderOp.alpha);
227
+ }
227
228
 
228
- // if (this.useSystemDimensions) {
229
- this.glw.uniform2f('u_dimensions', renderOp.width, renderOp.height);
230
- // }
229
+ if (this.useSystemDimensions === true) {
230
+ this.glw.uniform2f('u_dimensions', renderOp.width, renderOp.height);
231
+ }
231
232
 
232
233
  /**temporary fix to make sdf texts work */
233
234
  if (renderOp.sdfShaderProps !== undefined) {
@@ -306,7 +307,10 @@ export class WebGlShaderProgram implements CoreShaderProgram {
306
307
  }
307
308
 
308
309
  attach(): void {
309
- this.glw.useProgram(this.program);
310
+ if (this.isDestroyed === true) {
311
+ return;
312
+ }
313
+ this.glw.useProgram(this.program, this.uniformLocations!);
310
314
  if (this.glw.isWebGl2() && this.vao) {
311
315
  this.glw.bindVertexArray(this.vao);
312
316
  }
@@ -315,4 +319,23 @@ export class WebGlShaderProgram implements CoreShaderProgram {
315
319
  detach(): void {
316
320
  this.disableAttributes();
317
321
  }
322
+
323
+ destroy() {
324
+ if (this.isDestroyed === true) {
325
+ return;
326
+ }
327
+ const glw = this.glw;
328
+
329
+ this.detach();
330
+
331
+ glw.deleteProgram(this.program!);
332
+ this.program = null;
333
+ this.uniformLocations = null;
334
+
335
+ const attribs = Object.keys(this.attributeLocations);
336
+ const attribLen = attribs.length;
337
+ for (let i = 0; i < attribLen; i++) {
338
+ this.glw.deleteBuffer(attribs[i]!);
339
+ }
340
+ }
318
341
  }
@@ -144,6 +144,12 @@ export abstract class Texture extends EventEmitter {
144
144
  private _dimensions: Dimensions | null = null;
145
145
  private _error: Error | null = null;
146
146
 
147
+ /**
148
+ * Texture states that are considered transitional and should be skipped during cleanup
149
+ */
150
+ public static readonly TRANSITIONAL_TEXTURE_STATES: readonly TextureState[] =
151
+ ['fetching', 'fetched', 'loading'];
152
+
147
153
  // aggregate state
148
154
  public state: TextureState = 'initial';
149
155
 
@@ -258,10 +264,13 @@ export abstract class Texture extends EventEmitter {
258
264
  * cleaned up.
259
265
  */
260
266
  destroy(): void {
261
- this.removeAllListeners();
262
- this.free();
267
+ // Only free GPU resources if we're in a state where they exist
268
+ if (this.state === 'loaded') {
269
+ this.free();
270
+ }
271
+
272
+ // Always free texture data regardless of state
263
273
  this.freeTextureData();
264
- this.renderableOwners.clear();
265
274
  }
266
275
 
267
276
  /**