@lightningjs/renderer 3.0.0-beta17 → 3.0.0-beta19

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 (151) hide show
  1. package/dist/src/core/Autosizer.d.ts +29 -0
  2. package/dist/src/core/Autosizer.js +178 -0
  3. package/dist/src/core/Autosizer.js.map +1 -0
  4. package/dist/src/core/CoreNode.d.ts +44 -26
  5. package/dist/src/core/CoreNode.js +253 -150
  6. package/dist/src/core/CoreNode.js.map +1 -1
  7. package/dist/src/core/CoreTextNode.d.ts +1 -0
  8. package/dist/src/core/CoreTextNode.js +8 -4
  9. package/dist/src/core/CoreTextNode.js.map +1 -1
  10. package/dist/src/core/CoreTextureManager.d.ts +16 -2
  11. package/dist/src/core/CoreTextureManager.js +38 -51
  12. package/dist/src/core/CoreTextureManager.js.map +1 -1
  13. package/dist/src/core/Stage.d.ts +18 -2
  14. package/dist/src/core/Stage.js +46 -15
  15. package/dist/src/core/Stage.js.map +1 -1
  16. package/dist/src/core/TextureError.d.ts +11 -0
  17. package/dist/src/core/TextureError.js +37 -0
  18. package/dist/src/core/TextureError.js.map +1 -0
  19. package/dist/src/core/TextureMemoryManager.d.ts +2 -4
  20. package/dist/src/core/TextureMemoryManager.js +80 -95
  21. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  22. package/dist/src/core/lib/WebGlContextWrapper.d.ts +11 -0
  23. package/dist/src/core/lib/WebGlContextWrapper.js +34 -0
  24. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  25. package/dist/src/core/lib/textureCompression.d.ts +14 -2
  26. package/dist/src/core/lib/textureCompression.js +320 -67
  27. package/dist/src/core/lib/textureCompression.js.map +1 -1
  28. package/dist/src/core/platforms/web/WebPlatform.js +2 -2
  29. package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
  30. package/dist/src/core/renderers/CoreContextTexture.d.ts +1 -0
  31. package/dist/src/core/renderers/CoreContextTexture.js.map +1 -1
  32. package/dist/src/core/renderers/CoreRenderer.d.ts +3 -1
  33. package/dist/src/core/renderers/CoreRenderer.js +1 -0
  34. package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
  35. package/dist/src/core/renderers/CoreShaderNode.d.ts +6 -0
  36. package/dist/src/core/renderers/CoreShaderNode.js +2 -0
  37. package/dist/src/core/renderers/CoreShaderNode.js.map +1 -1
  38. package/dist/src/core/renderers/canvas/CanvasTexture.d.ts +1 -0
  39. package/dist/src/core/renderers/canvas/CanvasTexture.js +4 -1
  40. package/dist/src/core/renderers/canvas/CanvasTexture.js.map +1 -1
  41. package/dist/src/core/renderers/webgl/WebGlCtxRenderTexture.d.ts +2 -0
  42. package/dist/src/core/renderers/webgl/WebGlCtxRenderTexture.js +6 -0
  43. package/dist/src/core/renderers/webgl/WebGlCtxRenderTexture.js.map +1 -1
  44. package/dist/src/core/renderers/webgl/WebGlCtxTexture.d.ts +11 -0
  45. package/dist/src/core/renderers/webgl/WebGlCtxTexture.js +51 -14
  46. package/dist/src/core/renderers/webgl/WebGlCtxTexture.js.map +1 -1
  47. package/dist/src/core/renderers/webgl/WebGlRenderOp.d.ts +2 -1
  48. package/dist/src/core/renderers/webgl/WebGlRenderOp.js +2 -0
  49. package/dist/src/core/renderers/webgl/WebGlRenderOp.js.map +1 -1
  50. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +1 -1
  51. package/dist/src/core/renderers/webgl/WebGlRenderer.js +57 -61
  52. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  53. package/dist/src/core/renderers/webgl/WebGlShaderProgram.d.ts +1 -0
  54. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +12 -0
  55. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
  56. package/dist/src/core/shaders/canvas/RadialGradient.js +1 -1
  57. package/dist/src/core/shaders/canvas/RadialGradient.js.map +1 -1
  58. package/dist/src/core/shaders/templates/RadialGradientTemplate.d.ts +6 -4
  59. package/dist/src/core/shaders/templates/RadialGradientTemplate.js.map +1 -1
  60. package/dist/src/core/shaders/webgl/LinearGradient.js +2 -1
  61. package/dist/src/core/shaders/webgl/LinearGradient.js.map +1 -1
  62. package/dist/src/core/shaders/webgl/RadialGradient.js +8 -7
  63. package/dist/src/core/shaders/webgl/RadialGradient.js.map +1 -1
  64. package/dist/src/core/shaders/webgl/Rounded.js +3 -1
  65. package/dist/src/core/shaders/webgl/Rounded.js.map +1 -1
  66. package/dist/src/core/shaders/webgl/RoundedWithBorder.js +3 -2
  67. package/dist/src/core/shaders/webgl/RoundedWithBorder.js.map +1 -1
  68. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +3 -2
  69. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js.map +1 -1
  70. package/dist/src/core/shaders/webgl/RoundedWithShadow.js +2 -1
  71. package/dist/src/core/shaders/webgl/RoundedWithShadow.js.map +1 -1
  72. package/dist/src/core/text-rendering/CanvasTextRenderer.js +1 -1
  73. package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
  74. package/dist/src/core/text-rendering/SdfFontHandler.js +1 -1
  75. package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
  76. package/dist/src/core/textures/ColorTexture.d.ts +1 -1
  77. package/dist/src/core/textures/ColorTexture.js +2 -3
  78. package/dist/src/core/textures/ColorTexture.js.map +1 -1
  79. package/dist/src/core/textures/ImageTexture.d.ts +7 -1
  80. package/dist/src/core/textures/ImageTexture.js +20 -36
  81. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  82. package/dist/src/core/textures/NoiseTexture.d.ts +1 -1
  83. package/dist/src/core/textures/NoiseTexture.js +2 -2
  84. package/dist/src/core/textures/NoiseTexture.js.map +1 -1
  85. package/dist/src/core/textures/RenderTexture.d.ts +1 -1
  86. package/dist/src/core/textures/RenderTexture.js +2 -2
  87. package/dist/src/core/textures/RenderTexture.js.map +1 -1
  88. package/dist/src/core/textures/SubTexture.d.ts +2 -4
  89. package/dist/src/core/textures/SubTexture.js +13 -31
  90. package/dist/src/core/textures/SubTexture.js.map +1 -1
  91. package/dist/src/core/textures/Texture.d.ts +67 -8
  92. package/dist/src/core/textures/Texture.js +127 -15
  93. package/dist/src/core/textures/Texture.js.map +1 -1
  94. package/dist/src/core/utils.d.ts +1 -1
  95. package/dist/src/main-api/Renderer.d.ts +15 -2
  96. package/dist/src/main-api/Renderer.js +19 -12
  97. package/dist/src/main-api/Renderer.js.map +1 -1
  98. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  99. package/package.json +1 -1
  100. package/src/core/CoreNode.test.ts +0 -1
  101. package/src/core/CoreNode.ts +296 -177
  102. package/src/core/CoreTextNode.ts +9 -4
  103. package/src/core/CoreTextureManager.ts +69 -57
  104. package/src/core/Stage.ts +54 -18
  105. package/src/core/TextureError.ts +46 -0
  106. package/src/core/TextureMemoryManager.ts +95 -122
  107. package/src/core/lib/WebGlContextWrapper.ts +40 -0
  108. package/src/core/lib/collectionUtils.ts +118 -0
  109. package/src/core/lib/textureCompression.ts +433 -77
  110. package/src/core/platforms/web/WebPlatform.ts +2 -2
  111. package/src/core/renderers/CoreContextTexture.ts +1 -0
  112. package/src/core/renderers/CoreRenderer.ts +3 -2
  113. package/src/core/renderers/CoreShaderNode.ts +7 -0
  114. package/src/core/renderers/canvas/CanvasTexture.ts +5 -1
  115. package/src/core/renderers/webgl/WebGlCtxRenderTexture.ts +8 -0
  116. package/src/core/renderers/webgl/WebGlCtxTexture.ts +58 -15
  117. package/src/core/renderers/webgl/WebGlRenderOp.ts +4 -1
  118. package/src/core/renderers/webgl/WebGlRenderer.ts +66 -68
  119. package/src/core/renderers/webgl/WebGlShaderProgram.ts +16 -0
  120. package/src/core/shaders/canvas/RadialGradient.ts +1 -1
  121. package/src/core/shaders/templates/RadialGradientTemplate.ts +6 -4
  122. package/src/core/shaders/webgl/LinearGradient.ts +2 -1
  123. package/src/core/shaders/webgl/RadialGradient.ts +8 -7
  124. package/src/core/shaders/webgl/Rounded.ts +3 -1
  125. package/src/core/shaders/webgl/RoundedWithBorder.ts +3 -2
  126. package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +3 -2
  127. package/src/core/shaders/webgl/RoundedWithShadow.ts +2 -1
  128. package/src/core/text-rendering/CanvasTextRenderer.ts +1 -1
  129. package/src/core/text-rendering/SdfFontHandler.ts +1 -1
  130. package/src/core/textures/ColorTexture.ts +6 -4
  131. package/src/core/textures/ImageTexture.ts +35 -42
  132. package/src/core/textures/NoiseTexture.ts +6 -4
  133. package/src/core/textures/RenderTexture.ts +6 -4
  134. package/src/core/textures/SubTexture.ts +17 -38
  135. package/src/core/textures/Texture.ts +159 -20
  136. package/src/main-api/Renderer.ts +36 -14
  137. package/dist/src/core/animations/Animation.d.ts +0 -16
  138. package/dist/src/core/animations/Animation.js +0 -111
  139. package/dist/src/core/animations/Animation.js.map +0 -1
  140. package/dist/src/core/animations/CoreTransition.d.ts +0 -24
  141. package/dist/src/core/animations/CoreTransition.js +0 -63
  142. package/dist/src/core/animations/CoreTransition.js.map +0 -1
  143. package/dist/src/core/animations/Playback.d.ts +0 -62
  144. package/dist/src/core/animations/Playback.js +0 -155
  145. package/dist/src/core/animations/Playback.js.map +0 -1
  146. package/dist/src/core/animations/Transition.d.ts +0 -25
  147. package/dist/src/core/animations/Transition.js +0 -63
  148. package/dist/src/core/animations/Transition.js.map +0 -1
  149. package/dist/src/core/animations/utils.d.ts +0 -2
  150. package/dist/src/core/animations/utils.js +0 -137
  151. package/dist/src/core/animations/utils.js.map +0 -1
@@ -109,6 +109,12 @@ export interface ImageTextureProps {
109
109
  * @default null
110
110
  */
111
111
  sy?: number | null;
112
+ /**
113
+ * Maximum number of times to retry loading the image if it fails.
114
+ *
115
+ * @default 5
116
+ */
117
+ maxRetryCount?: number;
112
118
  }
113
119
 
114
120
  /**
@@ -131,11 +137,15 @@ export class ImageTexture extends Texture {
131
137
  public props: Required<ImageTextureProps>;
132
138
  public override type: TextureType = TextureType.image;
133
139
 
134
- constructor(txManager: CoreTextureManager, props: ImageTextureProps) {
140
+ constructor(
141
+ txManager: CoreTextureManager,
142
+ props: Required<ImageTextureProps>,
143
+ ) {
135
144
  super(txManager);
136
145
 
137
146
  this.platform = txManager.platform;
138
- this.props = ImageTexture.resolveDefaults(props);
147
+ this.props = props;
148
+ this.maxRetryCount = props.maxRetryCount as number;
139
149
  }
140
150
 
141
151
  hasAlphaChannel(mimeType: string) {
@@ -152,14 +162,19 @@ export class ImageTexture extends Texture {
152
162
  return new Promise<{
153
163
  data: HTMLImageElement | null;
154
164
  premultiplyAlpha: boolean;
155
- }>((resolve) => {
165
+ }>((resolve, reject) => {
156
166
  img.onload = () => {
157
167
  resolve({ data: img, premultiplyAlpha: hasAlpha });
158
168
  };
159
169
 
160
- img.onerror = () => {
161
- console.warn('Image loading failed, returning fallback object.');
162
- resolve({ data: null, premultiplyAlpha: hasAlpha });
170
+ img.onerror = (err) => {
171
+ const errorMessage =
172
+ err instanceof Error
173
+ ? err.message
174
+ : err instanceof Event
175
+ ? `Image loading failed for ${img.src}`
176
+ : 'Unknown image loading error';
177
+ reject(new Error(`Image loading failed: ${errorMessage}`));
163
178
  };
164
179
 
165
180
  if (src instanceof Blob) {
@@ -253,17 +268,12 @@ export class ImageTexture extends Texture {
253
268
  }
254
269
 
255
270
  override async getTextureSource(): Promise<TextureData> {
256
- let resp;
271
+ let resp: TextureData;
257
272
  try {
258
273
  resp = await this.determineImageTypeAndLoadImage();
259
274
  } catch (e) {
260
275
  this.setState('failed', e as Error);
261
276
 
262
- // log error only in development
263
- if (isProductionEnvironment === false) {
264
- console.error('ImageTexture:', e);
265
- }
266
-
267
277
  return {
268
278
  data: null,
269
279
  };
@@ -276,23 +286,6 @@ export class ImageTexture extends Texture {
276
286
  };
277
287
  }
278
288
 
279
- let w, h;
280
- // check if resp.data is typeof Uint8ClampedArray else
281
- // use resp.data.width and resp.data.height
282
- if (resp.data instanceof Uint8Array) {
283
- w = this.props.w ?? 0;
284
- h = this.props.h ?? 0;
285
- } else {
286
- w = resp.data?.width ?? (this.props.w || 0);
287
- h = resp.data?.height ?? (this.props.h || 0);
288
- }
289
-
290
- // we're loaded!
291
- this.setState('fetched', {
292
- w,
293
- h,
294
- });
295
-
296
289
  return {
297
290
  data: resp.data,
298
291
  premultiplyAlpha: this.props.premultiplyAlpha ?? true,
@@ -375,26 +368,25 @@ export class ImageTexture extends Texture {
375
368
  * @returns The cache key as a string, or `false` if the key cannot be generated.
376
369
  */
377
370
  static override makeCacheKey(props: ImageTextureProps): string | false {
378
- const resolvedProps = ImageTexture.resolveDefaults(props);
379
371
  // Only cache key-able textures; prioritise key
380
- const key = resolvedProps.key || resolvedProps.src;
372
+ const key = props.key || props.src;
381
373
  if (typeof key !== 'string') {
382
374
  return false;
383
375
  }
384
376
 
385
- // if we have source dimensions, cache the texture separately
386
- let dimensionProps = '';
387
- if (resolvedProps.sh !== null && resolvedProps.sw !== null) {
388
- dimensionProps += ',';
389
- dimensionProps += resolvedProps.sx ?? '';
390
- dimensionProps += resolvedProps.sy ?? '';
391
- dimensionProps += resolvedProps.sw || '';
392
- dimensionProps += resolvedProps.sh || '';
377
+ let cacheKey = `ImageTexture,${key},${props.premultiplyAlpha ?? 'true'},${
378
+ props.maxRetryCount
379
+ }`;
380
+
381
+ if (props.sh !== null && props.sw !== null) {
382
+ cacheKey += ',';
383
+ cacheKey += props.sx ?? '';
384
+ cacheKey += props.sy ?? '';
385
+ cacheKey += props.sw || '';
386
+ cacheKey += props.sh || '';
393
387
  }
394
388
 
395
- return `ImageTexture,${key},${
396
- resolvedProps.premultiplyAlpha ?? 'true'
397
- }${dimensionProps}`;
389
+ return cacheKey;
398
390
  }
399
391
 
400
392
  static override resolveDefaults(
@@ -411,6 +403,7 @@ export class ImageTexture extends Texture {
411
403
  sy: props.sy ?? null,
412
404
  sw: props.sw ?? null,
413
405
  sh: props.sh ?? null,
406
+ maxRetryCount: props.maxRetryCount ?? 5,
414
407
  };
415
408
  }
416
409
 
@@ -58,9 +58,12 @@ export class NoiseTexture extends Texture {
58
58
 
59
59
  public override type: TextureType = TextureType.noise;
60
60
 
61
- constructor(txManager: CoreTextureManager, props: NoiseTextureProps) {
61
+ constructor(
62
+ txManager: CoreTextureManager,
63
+ props: Required<NoiseTextureProps>,
64
+ ) {
62
65
  super(txManager);
63
- this.props = NoiseTexture.resolveDefaults(props);
66
+ this.props = props;
64
67
  }
65
68
 
66
69
  override async getTextureSource(): Promise<TextureData> {
@@ -75,8 +78,7 @@ export class NoiseTexture extends Texture {
75
78
  pixelData8[i + 3] = 255;
76
79
  }
77
80
 
78
- this.setState('fetched');
79
-
81
+ // Noise Texture data ready - dimensions will be set during upload
80
82
  return {
81
83
  data: new ImageData(pixelData8, w, h),
82
84
  };
@@ -42,9 +42,12 @@ export class RenderTexture extends Texture {
42
42
 
43
43
  public override type: TextureType = TextureType.renderToTexture;
44
44
 
45
- constructor(txManager: CoreTextureManager, props?: RenderTextureProps) {
45
+ constructor(
46
+ txManager: CoreTextureManager,
47
+ props: Required<RenderTextureProps>,
48
+ ) {
46
49
  super(txManager);
47
- this.props = RenderTexture.resolveDefaults(props || {});
50
+ this.props = props;
48
51
  }
49
52
 
50
53
  get w() {
@@ -64,8 +67,7 @@ export class RenderTexture extends Texture {
64
67
  }
65
68
 
66
69
  override async getTextureSource(): Promise<TextureData> {
67
- this.setState('fetched');
68
-
70
+ // Render texture data ready - dimensions will be set during upload
69
71
  return {
70
72
  data: null,
71
73
  premultiplyAlpha: null,
@@ -30,6 +30,8 @@ import {
30
30
  type TextureState,
31
31
  } from './Texture.js';
32
32
 
33
+ let subTextureId = 0;
34
+
33
35
  /**
34
36
  * Properties of the {@link SubTexture}
35
37
  */
@@ -83,10 +85,11 @@ export class SubTexture extends Texture {
83
85
  parentTexture: Texture;
84
86
 
85
87
  public override type: TextureType = TextureType.subTexture;
88
+ public subtextureId = `subtexture-${subTextureId++}`;
86
89
 
87
- constructor(txManager: CoreTextureManager, props: SubTextureProps) {
90
+ constructor(txManager: CoreTextureManager, props: Required<SubTextureProps>) {
88
91
  super(txManager);
89
- this.props = SubTexture.resolveDefaults(props || {});
92
+ this.props = props;
90
93
 
91
94
  assertTruthy(this.props.texture, 'SubTexture requires a parent texture');
92
95
  assertTruthy(
@@ -97,8 +100,8 @@ export class SubTexture extends Texture {
97
100
  // Resolve parent texture from cache or fallback to provided texture
98
101
  this.parentTexture = txManager.resolveParentTexture(this.props.texture);
99
102
 
100
- if (this.renderableOwners.size > 0) {
101
- this.parentTexture.setRenderableOwner(this, true);
103
+ if (this.renderableOwners.length > 0) {
104
+ this.parentTexture.setRenderableOwner(this.subtextureId, true);
102
105
  }
103
106
 
104
107
  // If parent texture is already loaded / failed, trigger loaded event manually
@@ -107,23 +110,17 @@ export class SubTexture extends Texture {
107
110
  // synchronous task after calling loadTexture()
108
111
  queueMicrotask(() => {
109
112
  const parentTx = this.parentTexture;
110
- if (parentTx.state === 'loaded') {
111
- this.onParentTxLoaded(parentTx, parentTx.dimensions!);
112
- } else if (parentTx.state === 'fetching') {
113
- this.onParentTxFetching();
114
- } else if (parentTx.state === 'fetched') {
115
- this.onParentTxFetched();
113
+ if (parentTx.state === 'loaded' && parentTx.dimensions !== null) {
114
+ this.onParentTxLoaded(parentTx, parentTx.dimensions);
116
115
  } else if (parentTx.state === 'loading') {
117
116
  this.onParentTxLoading();
118
- } else if (parentTx.state === 'failed') {
119
- this.onParentTxFailed(parentTx, parentTx.error!);
117
+ } else if (parentTx.state === 'failed' && parentTx.error !== null) {
118
+ this.onParentTxFailed(parentTx, parentTx.error);
120
119
  } else if (parentTx.state === 'freed') {
121
120
  this.onParentTxFreed();
122
121
  }
123
122
 
124
- parentTx.on('fetched', this.onParentTxFetched);
125
123
  parentTx.on('loading', this.onParentTxLoading);
126
- parentTx.on('fetching', this.onParentTxFetching);
127
124
  parentTx.on('loaded', this.onParentTxLoaded);
128
125
  parentTx.on('failed', this.onParentTxFailed);
129
126
  parentTx.on('freed', this.onParentTxFreed);
@@ -133,51 +130,33 @@ export class SubTexture extends Texture {
133
130
  private onParentTxLoaded: TextureLoadedEventHandler = () => {
134
131
  // We ignore the parent's passed dimensions, and simply use the SubTexture's
135
132
  // configured dimensions (because that's all that matters here)
136
- this.forwardParentTxState('loaded', {
133
+ this.setState('loaded', {
137
134
  w: this.props.w,
138
135
  h: this.props.h,
139
136
  });
140
137
  };
141
138
 
142
139
  private onParentTxFailed: TextureFailedEventHandler = (target, error) => {
143
- this.forwardParentTxState('failed', error);
144
- };
145
-
146
- private onParentTxFetched = () => {
147
- this.forwardParentTxState('fetched', {
148
- w: this.props.w,
149
- h: this.props.h,
150
- });
151
- };
152
-
153
- private onParentTxFetching = () => {
154
- this.forwardParentTxState('fetching');
140
+ this.retryCount = this.parentTexture.retryCount - 1;
141
+ this.setState('failed', error);
155
142
  };
156
143
 
157
144
  private onParentTxLoading = () => {
158
- this.forwardParentTxState('loading');
145
+ this.setState('loading');
159
146
  };
160
147
 
161
148
  private onParentTxFreed = () => {
162
- this.forwardParentTxState('freed');
149
+ this.setState('freed');
163
150
  };
164
151
 
165
- private forwardParentTxState(
166
- state: TextureState,
167
- errorOrDimensions?: Error | Dimensions,
168
- ) {
169
- this.setState(state, errorOrDimensions);
170
- }
171
-
172
152
  override onChangeIsRenderable(isRenderable: boolean): void {
173
153
  // Propagate the renderable owner change to the parent texture
174
- this.parentTexture.setRenderableOwner(this, isRenderable);
154
+ this.parentTexture.setRenderableOwner(this.subtextureId, isRenderable);
175
155
  }
176
156
 
177
157
  override async getTextureSource(): Promise<TextureData> {
178
158
  // Check if parent texture is loaded
179
159
  return new Promise((resolve, reject) => {
180
- this.setState('fetched');
181
160
  resolve({
182
161
  data: this.props,
183
162
  });
@@ -54,12 +54,12 @@ export interface CompressedData {
54
54
  /**
55
55
  * All mipmap levels
56
56
  */
57
- mipmaps?: ArrayBuffer[];
57
+ mipmaps: ArrayBuffer[];
58
58
 
59
59
  /**
60
60
  * Supported container types ('pvr' or 'ktx').
61
61
  */
62
- type: 'pvr' | 'ktx';
62
+ type: 'pvr' | 'ktx' | 'astc';
63
63
 
64
64
  /**
65
65
  * The width of the compressed texture in pixels. Defaults to 0.
@@ -72,6 +72,15 @@ export interface CompressedData {
72
72
  * The height of the compressed texture in pixels.
73
73
  **/
74
74
  h: number;
75
+
76
+ /**
77
+ * block info compressed texture format
78
+ */
79
+ blockInfo: {
80
+ width: number;
81
+ height: number;
82
+ bytes: number;
83
+ };
75
84
  }
76
85
 
77
86
  /**
@@ -144,16 +153,10 @@ export abstract class Texture extends EventEmitter {
144
153
  private _dimensions: Dimensions | null = null;
145
154
  private _error: Error | null = null;
146
155
 
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
-
153
156
  // aggregate state
154
157
  public state: TextureState = 'initial';
155
158
 
156
- readonly renderableOwners = new Set<unknown>();
159
+ readonly renderableOwners: any[] = [];
157
160
 
158
161
  readonly renderable: boolean = false;
159
162
 
@@ -165,8 +168,37 @@ export abstract class Texture extends EventEmitter {
165
168
 
166
169
  public textureData: TextureData | null = null;
167
170
 
171
+ public memUsed = 0;
172
+
173
+ /**
174
+ * Memory used by this texture in bytes
175
+ *
176
+ * @remarks
177
+ * This is tracked by the TextureMemoryManager and updated when the texture
178
+ * is loaded/freed. Set to 0 when texture is not loaded.
179
+ */
180
+ public retryCount = 0;
181
+ public maxRetryCount: number;
182
+
183
+ /**
184
+ * Timestamp when texture was created (for startup grace period)
185
+ */
186
+ private createdAt: number = Date.now();
187
+
188
+ /**
189
+ * Flag to track if grace period has expired to avoid repeated Date.now() calls
190
+ */
191
+ private gracePeriodExpired: boolean = false;
192
+
193
+ /**
194
+ * Grace period in milliseconds to prevent premature cleanup during app startup
195
+ * This helps prevent race conditions when bounds calculation is delayed
196
+ */
197
+ private static readonly STARTUP_GRACE_PERIOD = 2000; // 2 seconds
198
+
168
199
  constructor(protected txManager: CoreTextureManager) {
169
200
  super();
201
+ this.maxRetryCount = this.txManager.maxRetryCount;
170
202
  }
171
203
 
172
204
  get dimensions(): Dimensions | null {
@@ -177,6 +209,59 @@ export abstract class Texture extends EventEmitter {
177
209
  return this._error;
178
210
  }
179
211
 
212
+ /**
213
+ * Checks if the texture is within the startup grace period.
214
+ * During this period, textures are protected from cleanup to prevent
215
+ * race conditions during app initialization.
216
+ */
217
+ isWithinStartupGracePeriod(): boolean {
218
+ // If grace period already expired, return false immediately
219
+ if (this.gracePeriodExpired === true) {
220
+ return false;
221
+ }
222
+
223
+ // Check if grace period has expired now
224
+ const hasExpired =
225
+ Date.now() - this.createdAt >= Texture.STARTUP_GRACE_PERIOD;
226
+
227
+ if (hasExpired) {
228
+ // Cache the result to avoid future Date.now() calls
229
+ this.gracePeriodExpired = true;
230
+ return false;
231
+ }
232
+
233
+ return true;
234
+ }
235
+
236
+ /**
237
+ * Checks if the texture can be safely cleaned up.
238
+ * Considers the renderable state, startup grace period, and renderable owners.
239
+ */
240
+ canBeCleanedUp(): boolean {
241
+ // Never cleanup if explicitly prevented
242
+ if (this.preventCleanup) {
243
+ return false;
244
+ }
245
+
246
+ // Don't cleanup if still within startup grace period
247
+ if (this.isWithinStartupGracePeriod()) {
248
+ return false;
249
+ }
250
+
251
+ // Don't cleanup if not renderable
252
+ if (this.renderable === true) {
253
+ return false;
254
+ }
255
+
256
+ // Don't cleanup if there are still renderable owners
257
+ if (this.renderableOwners.length > 0) {
258
+ return false;
259
+ }
260
+
261
+ // Safe to cleanup
262
+ return true;
263
+ }
264
+
180
265
  /**
181
266
  * Add/remove an owner to/from the Texture based on its renderability.
182
267
  *
@@ -191,33 +276,43 @@ export abstract class Texture extends EventEmitter {
191
276
  * @param owner
192
277
  * @param renderable
193
278
  */
194
- setRenderableOwner(owner: unknown, renderable: boolean): void {
195
- const oldSize = this.renderableOwners.size;
279
+ setRenderableOwner(owner: string | number, renderable: boolean): void {
280
+ const oldSize = this.renderableOwners.length;
281
+ const hasOwnerIndex = this.renderableOwners.indexOf(owner);
196
282
 
197
283
  if (renderable === true) {
198
- if (this.renderableOwners.has(owner) === false) {
284
+ if (hasOwnerIndex === -1) {
199
285
  // Add the owner to the set
200
- this.renderableOwners.add(owner);
286
+ this.renderableOwners.push(owner);
201
287
  }
202
288
 
203
- const newSize = this.renderableOwners.size;
204
- if (newSize > oldSize && newSize === 1) {
289
+ const newSize = this.renderableOwners.length;
290
+ if (oldSize !== newSize && newSize === 1) {
205
291
  (this.renderable as boolean) = true;
206
292
  this.onChangeIsRenderable?.(true);
207
293
  this.load();
208
294
  }
209
295
  } else {
210
- this.renderableOwners.delete(owner);
211
- const newSize = this.renderableOwners.size;
212
- if (newSize < oldSize && newSize === 0) {
296
+ if (hasOwnerIndex !== -1) {
297
+ this.renderableOwners.splice(hasOwnerIndex, 1);
298
+ }
299
+
300
+ const newSize = this.renderableOwners.length;
301
+ if (oldSize !== newSize && newSize === 0) {
213
302
  (this.renderable as boolean) = false;
214
303
  this.onChangeIsRenderable?.(false);
215
- this.txManager.orphanTexture(this);
304
+
305
+ // note, not doing a cleanup here, cleanup is managed by the Stage/TextureMemoryManager
306
+ // when it deems appropriate based on memory pressure
216
307
  }
217
308
  }
218
309
  }
219
310
 
220
311
  load(): void {
312
+ if (this.retryCount > this.maxRetryCount) {
313
+ // We've exceeded the max retry count, do not attempt to load again
314
+ return;
315
+ }
221
316
  this.txManager.loadTexture(this);
222
317
  }
223
318
 
@@ -256,6 +351,18 @@ export abstract class Texture extends EventEmitter {
256
351
  this.ctxTexture?.free();
257
352
  }
258
353
 
354
+ /**
355
+ * Release the texture data and core context texture for this Texture without changing state.
356
+ *
357
+ * @remarks
358
+ * The ctxTexture is created by the renderer and lives on the GPU.
359
+ */
360
+ release(): void {
361
+ this.ctxTexture?.release();
362
+ this.ctxTexture = undefined;
363
+ this.freeTextureData();
364
+ }
365
+
259
366
  /**
260
367
  * Destroy the texture.
261
368
  *
@@ -281,7 +388,7 @@ export abstract class Texture extends EventEmitter {
281
388
  * e.g. ImageData that is downloaded from a URL.
282
389
  */
283
390
  freeTextureData(): void {
284
- this.textureData = null;
391
+ queueMicrotask(this.freeTextureDataTask);
285
392
  }
286
393
 
287
394
  public setState(
@@ -308,6 +415,18 @@ export abstract class Texture extends EventEmitter {
308
415
  } else if (state === 'failed') {
309
416
  this._error = errorOrDimensions as Error;
310
417
  payload = this._error;
418
+
419
+ // increment the retry count for the texture
420
+ // this is used to compare against maxRetryCount, if set
421
+ // to determine if we should try loading again
422
+ this.retryCount += 1;
423
+
424
+ queueMicrotask(this.releaseTask);
425
+ } else if (state === 'loading') {
426
+ this._error = null;
427
+ this._dimensions = null;
428
+ } else {
429
+ this._error = null;
311
430
  }
312
431
 
313
432
  // emit the new state
@@ -333,6 +452,26 @@ export abstract class Texture extends EventEmitter {
333
452
  return this.textureData;
334
453
  }
335
454
 
455
+ /**
456
+ * Task for queueMicrotask to free texture data.
457
+ *
458
+ * @remarks
459
+ * This method is called in a microtask to free the texture data.
460
+ */
461
+ private freeTextureDataTask = (): void => {
462
+ this.textureData = null;
463
+ };
464
+
465
+ /**
466
+ * Task for queueMicrotask to release the texture.
467
+ *
468
+ * @remarks
469
+ * This method is called in a microtask to release the texture.
470
+ */
471
+ private releaseTask = (): void => {
472
+ this.release();
473
+ };
474
+
336
475
  /**
337
476
  * Get the texture source for this texture.
338
477
  *
@@ -425,6 +425,20 @@ export type RendererMainSettings = RendererRuntimeSettings & {
425
425
  * @defaultValue `null`
426
426
  */
427
427
  platform: typeof Platform | null;
428
+
429
+ /**
430
+ * Number of times to retry loading a failed texture
431
+ *
432
+ * @remarks
433
+ * When a texture fails to load, Lightning will retry up to this many times
434
+ * before permanently giving up. Each retry will clear the texture ownership
435
+ * and then re-establish it to trigger a new load attempt.
436
+ *
437
+ * Set to null to disable retries. Set to 0 to always try once and never retry.
438
+ * This is typically only used on ImageTexture instances.
439
+ *
440
+ */
441
+ maxRetryCount?: number;
428
442
  };
429
443
 
430
444
  /**
@@ -493,7 +507,7 @@ export class RendererMain extends EventEmitter {
493
507
  */
494
508
  constructor(
495
509
  settings: Partial<RendererMainSettings>,
496
- target: string | HTMLElement,
510
+ target?: string | HTMLElement,
497
511
  ) {
498
512
  super();
499
513
 
@@ -508,7 +522,7 @@ export class RendererMain extends EventEmitter {
508
522
  boundsMargin: settings.boundsMargin || 0,
509
523
  deviceLogicalPixelRatio: settings.deviceLogicalPixelRatio || 1,
510
524
  devicePhysicalPixelRatio:
511
- settings.devicePhysicalPixelRatio || window.devicePixelRatio,
525
+ settings.devicePhysicalPixelRatio || window.devicePixelRatio || 1,
512
526
  clearColor: settings.clearColor ?? 0x00000000,
513
527
  fpsUpdateInterval: settings.fpsUpdateInterval || 0,
514
528
  targetFPS: settings.targetFPS || 0,
@@ -525,6 +539,7 @@ export class RendererMain extends EventEmitter {
525
539
  canvas: settings.canvas,
526
540
  createImageBitmapSupport: settings.createImageBitmapSupport || 'full',
527
541
  platform: settings.platform || null,
542
+ maxRetryCount: settings.maxRetryCount ?? 5,
528
543
  };
529
544
 
530
545
  const {
@@ -582,24 +597,31 @@ export class RendererMain extends EventEmitter {
582
597
  textureProcessingTimeLimit: settings.textureProcessingTimeLimit!,
583
598
  createImageBitmapSupport: settings.createImageBitmapSupport!,
584
599
  platform,
600
+ maxRetryCount: settings.maxRetryCount ?? 5,
585
601
  });
586
602
 
587
603
  // Extract the root node
588
604
  this.root = this.stage.root as unknown as INode;
589
605
 
590
606
  // Get the target element and attach the canvas to it
591
- let targetEl: HTMLElement | null;
592
- if (typeof target === 'string') {
593
- targetEl = document.getElementById(target);
594
- } else {
595
- targetEl = target;
596
- }
607
+ if (target) {
608
+ let targetEl: HTMLElement | null;
609
+ if (typeof target === 'string') {
610
+ targetEl = document.getElementById(target);
611
+ } else {
612
+ targetEl = target;
613
+ }
597
614
 
598
- if (!targetEl) {
599
- throw new Error('Could not find target element');
600
- }
615
+ if (!targetEl) {
616
+ throw new Error('Could not find target element');
617
+ }
601
618
 
602
- targetEl.appendChild(canvas);
619
+ targetEl.appendChild(canvas);
620
+ } else if (settings.canvas !== canvas) {
621
+ throw new Error(
622
+ 'New canvas element could not be appended to undefined target',
623
+ );
624
+ }
603
625
 
604
626
  // Initialize inspector (if enabled)
605
627
  if (inspector && isProductionEnvironment === false) {
@@ -832,8 +854,8 @@ export class RendererMain extends EventEmitter {
832
854
  * **NOTE3**: This will not cleanup textures that are marked as `preventCleanup`.
833
855
  * **NOTE4**: This has nothing to do with the garbage collection of JavaScript.
834
856
  */
835
- cleanup(aggressive: boolean = false) {
836
- this.stage.cleanup(aggressive);
857
+ cleanup() {
858
+ this.stage.cleanup();
837
859
  }
838
860
 
839
861
  /**