@lightningjs/renderer 2.17.0 → 2.18.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.
- package/dist/src/core/CoreNode.js +7 -5
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextureManager.d.ts +14 -8
- package/dist/src/core/CoreTextureManager.js +33 -59
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/Stage.d.ts +3 -3
- package/dist/src/core/Stage.js +9 -14
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.d.ts +21 -17
- package/dist/src/core/TextureMemoryManager.js +99 -106
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/lib/WebGlContextWrapper.d.ts +10 -0
- package/dist/src/core/lib/WebGlContextWrapper.js +32 -0
- package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
- package/dist/src/core/lib/textureCompression.js +13 -6
- package/dist/src/core/lib/textureCompression.js.map +1 -1
- package/dist/src/core/platform.js +4 -1
- package/dist/src/core/platform.js.map +1 -1
- package/dist/src/core/renderers/CoreContextTexture.d.ts +1 -0
- package/dist/src/core/renderers/CoreContextTexture.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.d.ts +1 -0
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +4 -3
- package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +9 -0
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +59 -29
- package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +4 -4
- package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/textures/ColorTexture.d.ts +2 -2
- package/dist/src/core/textures/ColorTexture.js +1 -2
- package/dist/src/core/textures/ColorTexture.js.map +1 -1
- package/dist/src/core/textures/ImageTexture.d.ts +7 -1
- package/dist/src/core/textures/ImageTexture.js +55 -39
- package/dist/src/core/textures/ImageTexture.js.map +1 -1
- package/dist/src/core/textures/NoiseTexture.js +1 -1
- package/dist/src/core/textures/NoiseTexture.js.map +1 -1
- package/dist/src/core/textures/RenderTexture.js +1 -1
- package/dist/src/core/textures/RenderTexture.js.map +1 -1
- package/dist/src/core/textures/SubTexture.d.ts +1 -2
- package/dist/src/core/textures/SubTexture.js +11 -29
- package/dist/src/core/textures/SubTexture.js.map +1 -1
- package/dist/src/core/textures/Texture.d.ts +51 -7
- package/dist/src/core/textures/Texture.js +127 -15
- package/dist/src/core/textures/Texture.js.map +1 -1
- package/dist/src/main-api/Inspector.d.ts +3 -0
- package/dist/src/main-api/Inspector.js +156 -0
- package/dist/src/main-api/Inspector.js.map +1 -1
- package/dist/src/main-api/Renderer.d.ts +1 -3
- package/dist/src/main-api/Renderer.js +2 -4
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/CoreNode.ts +8 -4
- package/src/core/CoreTextureManager.ts +59 -65
- package/src/core/Stage.ts +10 -16
- package/src/core/TextureMemoryManager.ts +118 -131
- package/src/core/lib/WebGlContextWrapper.ts +38 -0
- package/src/core/lib/textureCompression.ts +18 -7
- package/src/core/platform.ts +5 -1
- package/src/core/renderers/CoreContextTexture.ts +1 -0
- package/src/core/renderers/canvas/CanvasCoreTexture.ts +5 -3
- package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +78 -40
- package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +10 -4
- package/src/core/textures/ColorTexture.ts +4 -7
- package/src/core/textures/ImageTexture.ts +66 -51
- package/src/core/textures/NoiseTexture.ts +1 -1
- package/src/core/textures/RenderTexture.ts +1 -1
- package/src/core/textures/SubTexture.ts +14 -31
- package/src/core/textures/Texture.ts +150 -21
- package/src/main-api/Inspector.ts +203 -0
- package/src/main-api/Renderer.ts +2 -4
|
@@ -412,7 +412,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
412
412
|
this.setStatus(state, 'failed', new Error(msg));
|
|
413
413
|
return;
|
|
414
414
|
}
|
|
415
|
-
trFontFace.texture.setRenderableOwner(state, true);
|
|
415
|
+
trFontFace.texture.setRenderableOwner(state.props.fontFamily, true);
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
// If the font hasn't been loaded yet, stop here.
|
|
@@ -787,13 +787,16 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
787
787
|
renderable: boolean,
|
|
788
788
|
): void {
|
|
789
789
|
super.setIsRenderable(state, renderable);
|
|
790
|
-
state.trFontFace?.texture.setRenderableOwner(
|
|
790
|
+
state.trFontFace?.texture.setRenderableOwner(
|
|
791
|
+
state.props.fontFamily,
|
|
792
|
+
renderable,
|
|
793
|
+
);
|
|
791
794
|
}
|
|
792
795
|
|
|
793
796
|
override destroyState(state: SdfTextRendererState): void {
|
|
794
797
|
super.destroyState(state);
|
|
795
798
|
// If there's a Font Face assigned we must free the owner relation to its texture
|
|
796
|
-
state.trFontFace?.texture.setRenderableOwner(state, false);
|
|
799
|
+
state.trFontFace?.texture.setRenderableOwner(state.props.fontFamily, false);
|
|
797
800
|
}
|
|
798
801
|
//#endregion Overrides
|
|
799
802
|
|
|
@@ -813,7 +816,10 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
|
|
|
813
816
|
protected releaseFontFace(state: SdfTextRendererState) {
|
|
814
817
|
state.resLineHeight = undefined;
|
|
815
818
|
if (state.trFontFace) {
|
|
816
|
-
state.trFontFace.texture.setRenderableOwner(
|
|
819
|
+
state.trFontFace.texture.setRenderableOwner(
|
|
820
|
+
state.props.fontFamily,
|
|
821
|
+
false,
|
|
822
|
+
);
|
|
817
823
|
state.trFontFace = undefined;
|
|
818
824
|
}
|
|
819
825
|
}
|
|
@@ -45,13 +45,12 @@ export interface ColorTextureProps {
|
|
|
45
45
|
* a Node are.
|
|
46
46
|
*/
|
|
47
47
|
export class ColorTexture extends Texture {
|
|
48
|
-
|
|
48
|
+
override readonly type = TextureType.color as const;
|
|
49
|
+
public props: Required<ColorTextureProps>;
|
|
49
50
|
|
|
50
|
-
props:
|
|
51
|
-
|
|
52
|
-
constructor(txManager: CoreTextureManager, props?: ColorTextureProps) {
|
|
51
|
+
constructor(txManager: CoreTextureManager, props: ColorTextureProps) {
|
|
53
52
|
super(txManager);
|
|
54
|
-
this.props = ColorTexture.resolveDefaults(props
|
|
53
|
+
this.props = ColorTexture.resolveDefaults(props);
|
|
55
54
|
}
|
|
56
55
|
|
|
57
56
|
get color() {
|
|
@@ -77,8 +76,6 @@ export class ColorTexture extends Texture {
|
|
|
77
76
|
pixelData[3] = (this.color >>> 24) & 0xff; // Alpha
|
|
78
77
|
}
|
|
79
78
|
|
|
80
|
-
this.setState('fetched', { width: 1, height: 1 });
|
|
81
|
-
|
|
82
79
|
return {
|
|
83
80
|
data: pixelData,
|
|
84
81
|
premultiplyAlpha: true,
|
|
@@ -107,6 +107,13 @@ export interface ImageTextureProps {
|
|
|
107
107
|
* @default null
|
|
108
108
|
*/
|
|
109
109
|
sy?: number | null;
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Maximum number of times to retry loading the image if it fails.
|
|
113
|
+
*
|
|
114
|
+
* @default 5
|
|
115
|
+
*/
|
|
116
|
+
maxRetryCount?: number | null;
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
/**
|
|
@@ -124,13 +131,14 @@ export interface ImageTextureProps {
|
|
|
124
131
|
* {@link ImageTextureProps.premultiplyAlpha} prop to `false`.
|
|
125
132
|
*/
|
|
126
133
|
export class ImageTexture extends Texture {
|
|
134
|
+
override readonly type = TextureType.image as const;
|
|
127
135
|
public props: Required<ImageTextureProps>;
|
|
128
136
|
|
|
129
|
-
public override type: TextureType = TextureType.image;
|
|
130
|
-
|
|
131
137
|
constructor(txManager: CoreTextureManager, props: ImageTextureProps) {
|
|
138
|
+
const resolvedProps = ImageTexture.resolveDefaults(props);
|
|
132
139
|
super(txManager);
|
|
133
|
-
this.props =
|
|
140
|
+
this.props = resolvedProps;
|
|
141
|
+
this.maxRetryCount = props.maxRetryCount as number;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
hasAlphaChannel(mimeType: string) {
|
|
@@ -147,14 +155,19 @@ export class ImageTexture extends Texture {
|
|
|
147
155
|
return new Promise<{
|
|
148
156
|
data: HTMLImageElement | null;
|
|
149
157
|
premultiplyAlpha: boolean;
|
|
150
|
-
}>((resolve) => {
|
|
158
|
+
}>((resolve, reject) => {
|
|
151
159
|
img.onload = () => {
|
|
152
160
|
resolve({ data: img, premultiplyAlpha: hasAlpha });
|
|
153
161
|
};
|
|
154
162
|
|
|
155
|
-
img.onerror = () => {
|
|
156
|
-
|
|
157
|
-
|
|
163
|
+
img.onerror = (err) => {
|
|
164
|
+
const errorMessage =
|
|
165
|
+
err instanceof Error
|
|
166
|
+
? err.message
|
|
167
|
+
: err instanceof Event
|
|
168
|
+
? `Image loading failed for ${img.src}`
|
|
169
|
+
: 'Unknown image loading error';
|
|
170
|
+
reject(new Error(`Image loading failed: ${errorMessage}`));
|
|
158
171
|
};
|
|
159
172
|
|
|
160
173
|
if (src instanceof Blob) {
|
|
@@ -181,28 +194,40 @@ export class ImageTexture extends Texture {
|
|
|
181
194
|
|
|
182
195
|
if (imageBitmapSupported.full === true && sw !== null && sh !== null) {
|
|
183
196
|
// createImageBitmap with crop
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
197
|
+
try {
|
|
198
|
+
const bitmap = await createImageBitmap(blob, sx || 0, sy || 0, sw, sh, {
|
|
199
|
+
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
|
|
200
|
+
colorSpaceConversion: 'none',
|
|
201
|
+
imageOrientation: 'none',
|
|
202
|
+
});
|
|
203
|
+
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
|
|
204
|
+
} catch (error) {
|
|
205
|
+
throw new Error(`Failed to create image bitmap with crop: ${error}`);
|
|
206
|
+
}
|
|
190
207
|
} else if (imageBitmapSupported.basic === true) {
|
|
191
208
|
// basic createImageBitmap without options or crop
|
|
192
209
|
// this is supported for Chrome v50 to v52/54 that doesn't support options
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
210
|
+
try {
|
|
211
|
+
return {
|
|
212
|
+
data: await createImageBitmap(blob),
|
|
213
|
+
premultiplyAlpha: hasAlphaChannel,
|
|
214
|
+
};
|
|
215
|
+
} catch (error) {
|
|
216
|
+
throw new Error(`Failed to create basic image bitmap: ${error}`);
|
|
217
|
+
}
|
|
197
218
|
}
|
|
198
219
|
|
|
199
220
|
// default createImageBitmap without crop but with options
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
221
|
+
try {
|
|
222
|
+
const bitmap = await createImageBitmap(blob, {
|
|
223
|
+
premultiplyAlpha: hasAlphaChannel ? 'premultiply' : 'none',
|
|
224
|
+
colorSpaceConversion: 'none',
|
|
225
|
+
imageOrientation: 'none',
|
|
226
|
+
});
|
|
227
|
+
return { data: bitmap, premultiplyAlpha: hasAlphaChannel };
|
|
228
|
+
} catch (error) {
|
|
229
|
+
throw new Error(`Failed to create image bitmap with options: ${error}`);
|
|
230
|
+
}
|
|
206
231
|
}
|
|
207
232
|
|
|
208
233
|
async loadImage(src: string) {
|
|
@@ -214,14 +239,18 @@ export class ImageTexture extends Texture {
|
|
|
214
239
|
this.txManager.hasWorker === true &&
|
|
215
240
|
this.txManager.imageWorkerManager !== null
|
|
216
241
|
) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
242
|
+
try {
|
|
243
|
+
return this.txManager.imageWorkerManager.getImage(
|
|
244
|
+
src,
|
|
245
|
+
premultiplyAlpha,
|
|
246
|
+
sx,
|
|
247
|
+
sy,
|
|
248
|
+
sw,
|
|
249
|
+
sh,
|
|
250
|
+
);
|
|
251
|
+
} catch (error) {
|
|
252
|
+
throw new Error(`Failed to load image via worker: ${error}`);
|
|
253
|
+
}
|
|
225
254
|
}
|
|
226
255
|
|
|
227
256
|
let blob;
|
|
@@ -229,9 +258,11 @@ export class ImageTexture extends Texture {
|
|
|
229
258
|
if (isBase64Image(src) === true) {
|
|
230
259
|
blob = dataURIToBlob(src);
|
|
231
260
|
} else {
|
|
232
|
-
|
|
233
|
-
(
|
|
234
|
-
)
|
|
261
|
+
try {
|
|
262
|
+
blob = (await fetchJson(src, 'blob')) as Blob;
|
|
263
|
+
} catch (error) {
|
|
264
|
+
throw new Error(`Failed to fetch image blob from ${src}: ${error}`);
|
|
265
|
+
}
|
|
235
266
|
}
|
|
236
267
|
|
|
237
268
|
return this.createImageBitmap(blob, premultiplyAlpha, sx, sy, sw, sh);
|
|
@@ -258,23 +289,6 @@ export class ImageTexture extends Texture {
|
|
|
258
289
|
};
|
|
259
290
|
}
|
|
260
291
|
|
|
261
|
-
let width, height;
|
|
262
|
-
// check if resp.data is typeof Uint8ClampedArray else
|
|
263
|
-
// use resp.data.width and resp.data.height
|
|
264
|
-
if (resp.data instanceof Uint8Array) {
|
|
265
|
-
width = this.props.width ?? 0;
|
|
266
|
-
height = this.props.height ?? 0;
|
|
267
|
-
} else {
|
|
268
|
-
width = resp.data?.width ?? (this.props.width || 0);
|
|
269
|
-
height = resp.data?.height ?? (this.props.height || 0);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// we're loaded!
|
|
273
|
-
this.setState('fetched', {
|
|
274
|
-
width,
|
|
275
|
-
height,
|
|
276
|
-
});
|
|
277
|
-
|
|
278
292
|
return {
|
|
279
293
|
data: resp.data,
|
|
280
294
|
premultiplyAlpha: this.props.premultiplyAlpha ?? true,
|
|
@@ -393,6 +407,7 @@ export class ImageTexture extends Texture {
|
|
|
393
407
|
sy: props.sy ?? null,
|
|
394
408
|
sw: props.sw ?? null,
|
|
395
409
|
sh: props.sh ?? null,
|
|
410
|
+
maxRetryCount: props.maxRetryCount ?? 5,
|
|
396
411
|
};
|
|
397
412
|
}
|
|
398
413
|
|
|
@@ -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,6 +85,7 @@ 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
90
|
constructor(txManager: CoreTextureManager, props: SubTextureProps) {
|
|
88
91
|
super(txManager);
|
|
@@ -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.
|
|
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) {
|
|
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) {
|
|
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);
|
|
@@ -143,17 +140,6 @@ export class SubTexture extends Texture {
|
|
|
143
140
|
this.forwardParentTxState('failed', error);
|
|
144
141
|
};
|
|
145
142
|
|
|
146
|
-
private onParentTxFetched = () => {
|
|
147
|
-
this.forwardParentTxState('fetched', {
|
|
148
|
-
width: this.props.width,
|
|
149
|
-
height: this.props.height,
|
|
150
|
-
});
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
private onParentTxFetching = () => {
|
|
154
|
-
this.forwardParentTxState('fetching');
|
|
155
|
-
};
|
|
156
|
-
|
|
157
143
|
private onParentTxLoading = () => {
|
|
158
144
|
this.forwardParentTxState('loading');
|
|
159
145
|
};
|
|
@@ -171,17 +157,14 @@ export class SubTexture extends Texture {
|
|
|
171
157
|
|
|
172
158
|
override onChangeIsRenderable(isRenderable: boolean): void {
|
|
173
159
|
// Propagate the renderable owner change to the parent texture
|
|
174
|
-
this.parentTexture.setRenderableOwner(this, isRenderable);
|
|
160
|
+
this.parentTexture.setRenderableOwner(this.subtextureId, isRenderable);
|
|
175
161
|
}
|
|
176
162
|
|
|
177
163
|
override async getTextureSource(): Promise<TextureData> {
|
|
178
|
-
//
|
|
179
|
-
return
|
|
180
|
-
this.
|
|
181
|
-
|
|
182
|
-
data: this.props,
|
|
183
|
-
});
|
|
184
|
-
});
|
|
164
|
+
// SubTexture data ready - dimensions will be set during upload
|
|
165
|
+
return {
|
|
166
|
+
data: this.props,
|
|
167
|
+
};
|
|
185
168
|
}
|
|
186
169
|
|
|
187
170
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
@@ -103,9 +103,7 @@ export interface TextureData {
|
|
|
103
103
|
|
|
104
104
|
export type TextureState =
|
|
105
105
|
| 'initial' // Before anything is loaded
|
|
106
|
-
| '
|
|
107
|
-
| 'fetched' // Texture source is ready
|
|
108
|
-
| 'loading' // Uploading to GPU
|
|
106
|
+
| 'loading' // Loading texture data and uploading to GPU
|
|
109
107
|
| 'loaded' // Fully loaded and usable
|
|
110
108
|
| 'failed' // Failed to load
|
|
111
109
|
| 'freed'; // Released and must be reloaded
|
|
@@ -139,16 +137,10 @@ export abstract class Texture extends EventEmitter {
|
|
|
139
137
|
private _dimensions: Dimensions | null = null;
|
|
140
138
|
private _error: Error | null = null;
|
|
141
139
|
|
|
142
|
-
/**
|
|
143
|
-
* Texture states that are considered transitional and should be skipped during cleanup
|
|
144
|
-
*/
|
|
145
|
-
public static readonly TRANSITIONAL_TEXTURE_STATES: readonly TextureState[] =
|
|
146
|
-
['fetching', 'fetched', 'loading'];
|
|
147
|
-
|
|
148
140
|
// aggregate state
|
|
149
141
|
public state: TextureState = 'initial';
|
|
150
142
|
|
|
151
|
-
readonly renderableOwners =
|
|
143
|
+
readonly renderableOwners: any[] = [];
|
|
152
144
|
|
|
153
145
|
readonly renderable: boolean = false;
|
|
154
146
|
|
|
@@ -160,6 +152,34 @@ export abstract class Texture extends EventEmitter {
|
|
|
160
152
|
|
|
161
153
|
public textureData: TextureData | null = null;
|
|
162
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Memory used by this texture in bytes
|
|
157
|
+
*
|
|
158
|
+
* @remarks
|
|
159
|
+
* This is tracked by the TextureMemoryManager and updated when the texture
|
|
160
|
+
* is loaded/freed. Set to 0 when texture is not loaded.
|
|
161
|
+
*/
|
|
162
|
+
public memUsed = 0;
|
|
163
|
+
|
|
164
|
+
public retryCount = 0;
|
|
165
|
+
public maxRetryCount: number | null = null;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Timestamp when texture was created (for startup grace period)
|
|
169
|
+
*/
|
|
170
|
+
private createdAt: number = Date.now();
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Flag to track if grace period has expired to avoid repeated Date.now() calls
|
|
174
|
+
*/
|
|
175
|
+
private gracePeriodExpired: boolean = false;
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Grace period in milliseconds to prevent premature cleanup during app startup
|
|
179
|
+
* This helps prevent race conditions when bounds calculation is delayed
|
|
180
|
+
*/
|
|
181
|
+
private static readonly STARTUP_GRACE_PERIOD = 2000; // 2 seconds
|
|
182
|
+
|
|
163
183
|
constructor(protected txManager: CoreTextureManager) {
|
|
164
184
|
super();
|
|
165
185
|
}
|
|
@@ -172,6 +192,59 @@ export abstract class Texture extends EventEmitter {
|
|
|
172
192
|
return this._error;
|
|
173
193
|
}
|
|
174
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Checks if the texture is within the startup grace period.
|
|
197
|
+
* During this period, textures are protected from cleanup to prevent
|
|
198
|
+
* race conditions during app initialization.
|
|
199
|
+
*/
|
|
200
|
+
isWithinStartupGracePeriod(): boolean {
|
|
201
|
+
// If grace period already expired, return false immediately
|
|
202
|
+
if (this.gracePeriodExpired) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Check if grace period has expired now
|
|
207
|
+
const hasExpired =
|
|
208
|
+
Date.now() - this.createdAt >= Texture.STARTUP_GRACE_PERIOD;
|
|
209
|
+
|
|
210
|
+
if (hasExpired) {
|
|
211
|
+
// Cache the result to avoid future Date.now() calls
|
|
212
|
+
this.gracePeriodExpired = true;
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Checks if the texture can be safely cleaned up.
|
|
221
|
+
* Considers the renderable state, startup grace period, and renderable owners.
|
|
222
|
+
*/
|
|
223
|
+
canBeCleanedUp(): boolean {
|
|
224
|
+
// Never cleanup if explicitly prevented
|
|
225
|
+
if (this.preventCleanup) {
|
|
226
|
+
return false;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Don't cleanup if still within startup grace period
|
|
230
|
+
if (this.isWithinStartupGracePeriod()) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Don't cleanup if not renderable
|
|
235
|
+
if (this.renderable === true) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Don't cleanup if there are still renderable owners
|
|
240
|
+
if (this.renderableOwners.length > 0) {
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Safe to cleanup
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
175
248
|
/**
|
|
176
249
|
* Add/remove an owner to/from the Texture based on its renderability.
|
|
177
250
|
*
|
|
@@ -186,33 +259,44 @@ export abstract class Texture extends EventEmitter {
|
|
|
186
259
|
* @param owner
|
|
187
260
|
* @param renderable
|
|
188
261
|
*/
|
|
189
|
-
setRenderableOwner(owner:
|
|
190
|
-
const oldSize = this.renderableOwners.
|
|
262
|
+
setRenderableOwner(owner: string | number, renderable: boolean): void {
|
|
263
|
+
const oldSize = this.renderableOwners.length;
|
|
264
|
+
const hasOwnerIndex = this.renderableOwners.indexOf(owner);
|
|
191
265
|
|
|
192
266
|
if (renderable === true) {
|
|
193
|
-
if (
|
|
267
|
+
if (hasOwnerIndex === -1) {
|
|
194
268
|
// Add the owner to the set
|
|
195
|
-
this.renderableOwners.
|
|
269
|
+
this.renderableOwners.push(owner);
|
|
196
270
|
}
|
|
197
271
|
|
|
198
|
-
const newSize = this.renderableOwners.
|
|
199
|
-
if (
|
|
272
|
+
const newSize = this.renderableOwners.length;
|
|
273
|
+
if (oldSize !== newSize && newSize === 1) {
|
|
200
274
|
(this.renderable as boolean) = true;
|
|
201
275
|
this.onChangeIsRenderable?.(true);
|
|
202
276
|
this.load();
|
|
203
277
|
}
|
|
204
278
|
} else {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
279
|
+
if (hasOwnerIndex !== -1) {
|
|
280
|
+
this.renderableOwners.splice(hasOwnerIndex, 1);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const newSize = this.renderableOwners.length;
|
|
284
|
+
if (oldSize !== newSize && newSize === 0) {
|
|
208
285
|
(this.renderable as boolean) = false;
|
|
209
286
|
this.onChangeIsRenderable?.(false);
|
|
210
|
-
|
|
287
|
+
|
|
288
|
+
// note, not doing a cleanup here, cleanup is managed by the Stage/TextureMemoryManager
|
|
289
|
+
// when it deems appropriate based on memory pressure
|
|
211
290
|
}
|
|
212
291
|
}
|
|
213
292
|
}
|
|
214
293
|
|
|
215
294
|
load(): void {
|
|
295
|
+
if (this.maxRetryCount !== null && this.retryCount > this.maxRetryCount) {
|
|
296
|
+
// We've exceeded the max retry count, do not attempt to load again
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
216
300
|
this.txManager.loadTexture(this);
|
|
217
301
|
}
|
|
218
302
|
|
|
@@ -251,6 +335,18 @@ export abstract class Texture extends EventEmitter {
|
|
|
251
335
|
this.ctxTexture?.free();
|
|
252
336
|
}
|
|
253
337
|
|
|
338
|
+
/**
|
|
339
|
+
* Release the texture data and core context texture for this Texture without changing state.
|
|
340
|
+
*
|
|
341
|
+
* @remarks
|
|
342
|
+
* The ctxTexture is created by the renderer and lives on the GPU.
|
|
343
|
+
*/
|
|
344
|
+
release(): void {
|
|
345
|
+
this.ctxTexture?.release();
|
|
346
|
+
this.ctxTexture = undefined;
|
|
347
|
+
this.freeTextureData();
|
|
348
|
+
}
|
|
349
|
+
|
|
254
350
|
/**
|
|
255
351
|
* Destroy the texture.
|
|
256
352
|
*
|
|
@@ -276,7 +372,9 @@ export abstract class Texture extends EventEmitter {
|
|
|
276
372
|
* e.g. ImageData that is downloaded from a URL.
|
|
277
373
|
*/
|
|
278
374
|
freeTextureData(): void {
|
|
279
|
-
|
|
375
|
+
queueMicrotask(() => {
|
|
376
|
+
this.textureData = null;
|
|
377
|
+
});
|
|
280
378
|
}
|
|
281
379
|
|
|
282
380
|
public setState(
|
|
@@ -289,6 +387,9 @@ export abstract class Texture extends EventEmitter {
|
|
|
289
387
|
|
|
290
388
|
let payload: Error | Dimensions | null = null;
|
|
291
389
|
if (state === 'loaded') {
|
|
390
|
+
// Clear any previous error when successfully loading
|
|
391
|
+
this._error = null;
|
|
392
|
+
|
|
292
393
|
if (
|
|
293
394
|
errorOrDimensions !== undefined &&
|
|
294
395
|
'width' in errorOrDimensions === true &&
|
|
@@ -303,6 +404,22 @@ export abstract class Texture extends EventEmitter {
|
|
|
303
404
|
} else if (state === 'failed') {
|
|
304
405
|
this._error = errorOrDimensions as Error;
|
|
305
406
|
payload = this._error;
|
|
407
|
+
|
|
408
|
+
// increment the retry count for the texture
|
|
409
|
+
// this is used to compare against maxRetryCount, if set
|
|
410
|
+
// to determine if we should try loading again
|
|
411
|
+
this.retryCount += 1;
|
|
412
|
+
|
|
413
|
+
queueMicrotask(() => {
|
|
414
|
+
this.release();
|
|
415
|
+
});
|
|
416
|
+
} else if (state === 'loading') {
|
|
417
|
+
// Clear error and reset dimensions when starting to load
|
|
418
|
+
// This ensures stale dimensions from previous loads don't persist
|
|
419
|
+
this._error = null;
|
|
420
|
+
this._dimensions = null;
|
|
421
|
+
} else {
|
|
422
|
+
this._error = null;
|
|
306
423
|
}
|
|
307
424
|
|
|
308
425
|
// emit the new state
|
|
@@ -373,4 +490,16 @@ export abstract class Texture extends EventEmitter {
|
|
|
373
490
|
): Record<string, unknown> {
|
|
374
491
|
return {};
|
|
375
492
|
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Retry the texture by resetting retryCount and setting state to 'initial'.
|
|
496
|
+
*
|
|
497
|
+
* @remarks
|
|
498
|
+
* This allows the texture to be loaded again.
|
|
499
|
+
*/
|
|
500
|
+
public retry(): void {
|
|
501
|
+
this.release();
|
|
502
|
+
this.retryCount = 0;
|
|
503
|
+
this.load();
|
|
504
|
+
}
|
|
376
505
|
}
|