@lightningjs/renderer 2.17.0 → 2.18.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/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 +130 -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 +154 -21
- package/src/main-api/Inspector.ts +203 -0
- package/src/main-api/Renderer.ts +2 -4
|
@@ -17,9 +17,8 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
import { isProductionEnvironment } from '../utils.js';
|
|
20
|
-
import { getTimeStamp } from './platform.js';
|
|
21
20
|
import type { Stage } from './Stage.js';
|
|
22
|
-
import { Texture, TextureType
|
|
21
|
+
import { Texture, TextureType } from './textures/Texture.js';
|
|
23
22
|
import { bytesToMb } from './utils.js';
|
|
24
23
|
|
|
25
24
|
export interface TextureMemoryManagerSettings {
|
|
@@ -116,14 +115,14 @@ export interface MemoryInfo {
|
|
|
116
115
|
*/
|
|
117
116
|
export class TextureMemoryManager {
|
|
118
117
|
private memUsed = 0;
|
|
119
|
-
private loadedTextures:
|
|
120
|
-
private orphanedTextures: Texture[] = [];
|
|
118
|
+
private loadedTextures: (Texture | null)[] = [];
|
|
121
119
|
private criticalThreshold: number;
|
|
122
120
|
private targetThreshold: number;
|
|
123
121
|
private cleanupInterval: number;
|
|
124
122
|
private debugLogging: boolean;
|
|
125
123
|
private lastCleanupTime = 0;
|
|
126
124
|
private baselineMemoryAllocation: number;
|
|
125
|
+
private needsDefrag = false;
|
|
127
126
|
|
|
128
127
|
public criticalCleanupRequested = false;
|
|
129
128
|
public doNotExceedCriticalThreshold: boolean;
|
|
@@ -181,35 +180,6 @@ export class TextureMemoryManager {
|
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
|
|
184
|
-
/**
|
|
185
|
-
* Add a texture to the orphaned textures list
|
|
186
|
-
*
|
|
187
|
-
* @param texture - The texture to add to the orphaned textures list
|
|
188
|
-
*/
|
|
189
|
-
addToOrphanedTextures(texture: Texture) {
|
|
190
|
-
// if the texture is already in the orphaned textures list add it at the end
|
|
191
|
-
if (this.orphanedTextures.includes(texture)) {
|
|
192
|
-
this.removeFromOrphanedTextures(texture);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// If the texture can be cleaned up, add it to the orphaned textures list
|
|
196
|
-
if (texture.preventCleanup === false) {
|
|
197
|
-
this.orphanedTextures.push(texture);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Remove a texture from the orphaned textures list
|
|
203
|
-
*
|
|
204
|
-
* @param texture - The texture to remove from the orphaned textures list
|
|
205
|
-
*/
|
|
206
|
-
removeFromOrphanedTextures(texture: Texture) {
|
|
207
|
-
const index = this.orphanedTextures.indexOf(texture);
|
|
208
|
-
if (index !== -1) {
|
|
209
|
-
this.orphanedTextures.splice(index, 1);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
183
|
/**
|
|
214
184
|
* Set the memory usage of a texture
|
|
215
185
|
*
|
|
@@ -217,17 +187,25 @@ export class TextureMemoryManager {
|
|
|
217
187
|
* @param byteSize - The size of the texture in bytes
|
|
218
188
|
*/
|
|
219
189
|
setTextureMemUse(texture: Texture, byteSize: number) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.memUsed -= this.loadedTextures.get(texture)!;
|
|
223
|
-
}
|
|
190
|
+
// Update global memory counter by subtracting old value
|
|
191
|
+
this.memUsed -= texture.memUsed;
|
|
224
192
|
|
|
225
193
|
if (byteSize === 0) {
|
|
226
|
-
|
|
194
|
+
// PERFORMANCE: Mark for deletion instead of splice (zero overhead)
|
|
195
|
+
const index = this.loadedTextures.indexOf(texture);
|
|
196
|
+
if (index !== -1) {
|
|
197
|
+
this.loadedTextures[index] = null;
|
|
198
|
+
this.needsDefrag = true;
|
|
199
|
+
}
|
|
200
|
+
texture.memUsed = 0;
|
|
227
201
|
return;
|
|
228
202
|
} else {
|
|
203
|
+
// Update texture memory and add to tracking if not already present
|
|
204
|
+
texture.memUsed = byteSize;
|
|
229
205
|
this.memUsed += byteSize;
|
|
230
|
-
this.loadedTextures.
|
|
206
|
+
if (this.loadedTextures.indexOf(texture) === -1) {
|
|
207
|
+
this.loadedTextures.push(texture);
|
|
208
|
+
}
|
|
231
209
|
}
|
|
232
210
|
|
|
233
211
|
if (this.memUsed > this.criticalThreshold) {
|
|
@@ -247,42 +225,21 @@ export class TextureMemoryManager {
|
|
|
247
225
|
return this.memUsed > this.criticalThreshold;
|
|
248
226
|
}
|
|
249
227
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
if (texture === undefined) {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (texture.renderable === true) {
|
|
267
|
-
// If the texture is renderable, we can't free it up
|
|
268
|
-
continue;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// Skip textures that are in transitional states - we only want to clean up
|
|
272
|
-
// textures that are in a stable state (loaded, failed, or freed)
|
|
273
|
-
if (
|
|
274
|
-
texture.state === 'initial' ||
|
|
275
|
-
Texture.TRANSITIONAL_TEXTURE_STATES.includes(texture.state)
|
|
276
|
-
) {
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
this.destroyTexture(texture);
|
|
281
|
-
}
|
|
228
|
+
/**
|
|
229
|
+
* Check if defragmentation is needed
|
|
230
|
+
*
|
|
231
|
+
* @remarks
|
|
232
|
+
* Returns true if the loadedTextures array has null entries that need
|
|
233
|
+
* to be compacted. Called by platform during idle periods.
|
|
234
|
+
*
|
|
235
|
+
* @returns true if defragmentation should be performed
|
|
236
|
+
*/
|
|
237
|
+
checkDefrag() {
|
|
238
|
+
return this.needsDefrag;
|
|
282
239
|
}
|
|
283
240
|
|
|
284
241
|
/**
|
|
285
|
-
* Destroy a texture and
|
|
242
|
+
* Destroy a texture and null out its array position
|
|
286
243
|
*
|
|
287
244
|
* @param texture - The texture to destroy
|
|
288
245
|
*/
|
|
@@ -293,46 +250,25 @@ export class TextureMemoryManager {
|
|
|
293
250
|
);
|
|
294
251
|
}
|
|
295
252
|
|
|
253
|
+
// PERFORMANCE: Null out array position instead of splice (zero overhead)
|
|
254
|
+
const index = this.loadedTextures.indexOf(texture);
|
|
255
|
+
if (index !== -1) {
|
|
256
|
+
this.loadedTextures[index] = null;
|
|
257
|
+
this.needsDefrag = true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Destroy texture and update memory counters
|
|
296
261
|
const txManager = this.stage.txManager;
|
|
297
|
-
txManager.removeTextureFromQueue(texture);
|
|
298
262
|
txManager.removeTextureFromCache(texture);
|
|
299
263
|
|
|
300
264
|
texture.destroy();
|
|
301
265
|
|
|
302
|
-
|
|
303
|
-
this.
|
|
304
|
-
|
|
305
|
-
cleanupDeep(critical: boolean) {
|
|
306
|
-
// Free non-renderable textures until we reach the target threshold
|
|
307
|
-
const memTarget = critical ? this.criticalThreshold : this.targetThreshold;
|
|
308
|
-
|
|
309
|
-
// Filter for textures that are candidates for cleanup
|
|
310
|
-
// note: This is an expensive operation, so we only do it in deep cleanup
|
|
311
|
-
const cleanupCandidates = [...this.loadedTextures.keys()].filter(
|
|
312
|
-
(texture) => {
|
|
313
|
-
return (
|
|
314
|
-
(texture.type === TextureType.image ||
|
|
315
|
-
texture.type === TextureType.noise ||
|
|
316
|
-
texture.type === TextureType.renderToTexture) &&
|
|
317
|
-
texture.renderable === false &&
|
|
318
|
-
texture.preventCleanup === false &&
|
|
319
|
-
texture.state !== 'initial' &&
|
|
320
|
-
!Texture.TRANSITIONAL_TEXTURE_STATES.includes(texture.state)
|
|
321
|
-
);
|
|
322
|
-
},
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
while (this.memUsed >= memTarget && cleanupCandidates.length > 0) {
|
|
326
|
-
const texture = cleanupCandidates.shift();
|
|
327
|
-
if (texture === undefined) {
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
this.destroyTexture(texture);
|
|
332
|
-
}
|
|
266
|
+
// Update memory counters
|
|
267
|
+
this.memUsed -= texture.memUsed;
|
|
268
|
+
texture.memUsed = 0;
|
|
333
269
|
}
|
|
334
270
|
|
|
335
|
-
cleanup(
|
|
271
|
+
cleanup() {
|
|
336
272
|
const critical = this.criticalCleanupRequested;
|
|
337
273
|
this.lastCleanupTime = this.frameTime;
|
|
338
274
|
|
|
@@ -345,25 +281,41 @@ export class TextureMemoryManager {
|
|
|
345
281
|
|
|
346
282
|
if (this.debugLogging === true) {
|
|
347
283
|
console.log(
|
|
348
|
-
`[TextureMemoryManager] Cleaning up textures. Critical: ${critical}
|
|
284
|
+
`[TextureMemoryManager] Cleaning up textures. Critical: ${critical}.`,
|
|
349
285
|
);
|
|
350
286
|
}
|
|
351
287
|
|
|
352
|
-
//
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
288
|
+
// Free non-renderable textures until we reach the target threshold
|
|
289
|
+
const memTarget = critical ? this.criticalThreshold : this.targetThreshold;
|
|
290
|
+
|
|
291
|
+
// PERFORMANCE: Zero-overhead cleanup with null marking
|
|
292
|
+
// Skip null entries, mark cleaned textures as null for later defrag
|
|
293
|
+
let currentMemUsed = this.memUsed;
|
|
294
|
+
|
|
295
|
+
for (let i = 0; i < this.loadedTextures.length; i++) {
|
|
296
|
+
// Early exit: target memory reached
|
|
297
|
+
if (currentMemUsed < memTarget) {
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const texture = this.loadedTextures[i];
|
|
302
|
+
if (!texture) continue; // Skip null entries from previous deletions
|
|
303
|
+
|
|
304
|
+
// Fast type check for cleanable textures
|
|
305
|
+
const isCleanableType =
|
|
306
|
+
texture.type === TextureType.image ||
|
|
307
|
+
texture.type === TextureType.noise ||
|
|
308
|
+
texture.type === TextureType.renderToTexture;
|
|
309
|
+
|
|
310
|
+
// Immediate cleanup if eligible
|
|
311
|
+
if (isCleanableType && texture.canBeCleanedUp() === true) {
|
|
312
|
+
// Get memory before destroying
|
|
313
|
+
const textureMemory = texture.memUsed;
|
|
314
|
+
|
|
315
|
+
// Destroy texture (which will null out the array position)
|
|
316
|
+
this.destroyTexture(texture);
|
|
317
|
+
currentMemUsed -= textureMemory;
|
|
318
|
+
}
|
|
367
319
|
}
|
|
368
320
|
|
|
369
321
|
if (this.memUsed >= this.criticalThreshold) {
|
|
@@ -382,6 +334,37 @@ export class TextureMemoryManager {
|
|
|
382
334
|
}
|
|
383
335
|
}
|
|
384
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Defragment the loadedTextures array by removing null entries
|
|
339
|
+
*
|
|
340
|
+
* @remarks
|
|
341
|
+
* This should be called during idle periods to compact the array
|
|
342
|
+
* after null-marking deletions. Zero overhead during critical cleanup.
|
|
343
|
+
*/
|
|
344
|
+
defragment() {
|
|
345
|
+
if (!this.needsDefrag) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// PERFORMANCE: Single-pass compaction
|
|
350
|
+
let writeIndex = 0;
|
|
351
|
+
for (
|
|
352
|
+
let readIndex = 0;
|
|
353
|
+
readIndex < this.loadedTextures.length;
|
|
354
|
+
readIndex++
|
|
355
|
+
) {
|
|
356
|
+
const texture = this.loadedTextures[readIndex];
|
|
357
|
+
if (texture !== null && texture !== undefined) {
|
|
358
|
+
this.loadedTextures[writeIndex] = texture;
|
|
359
|
+
writeIndex++;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Trim array to new size
|
|
364
|
+
this.loadedTextures.length = writeIndex;
|
|
365
|
+
this.needsDefrag = false;
|
|
366
|
+
}
|
|
367
|
+
|
|
385
368
|
/**
|
|
386
369
|
* Get the current texture memory usage information
|
|
387
370
|
*
|
|
@@ -391,15 +374,19 @@ export class TextureMemoryManager {
|
|
|
391
374
|
*/
|
|
392
375
|
getMemoryInfo(): MemoryInfo {
|
|
393
376
|
let renderableTexturesLoaded = 0;
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
377
|
+
let renderableMemUsed = this.baselineMemoryAllocation;
|
|
378
|
+
|
|
379
|
+
for (const texture of this.loadedTextures) {
|
|
380
|
+
if (texture && texture.renderable) {
|
|
381
|
+
renderableTexturesLoaded += 1;
|
|
382
|
+
renderableMemUsed += texture.memUsed;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Count non-null entries for accurate loaded texture count
|
|
387
|
+
const actualLoadedTextures = this.loadedTextures.filter(
|
|
388
|
+
(t) => t !== null,
|
|
389
|
+
).length;
|
|
403
390
|
|
|
404
391
|
return {
|
|
405
392
|
criticalThreshold: this.criticalThreshold,
|
|
@@ -407,7 +394,7 @@ export class TextureMemoryManager {
|
|
|
407
394
|
renderableMemUsed,
|
|
408
395
|
memUsed: this.memUsed,
|
|
409
396
|
renderableTexturesLoaded,
|
|
410
|
-
loadedTextures:
|
|
397
|
+
loadedTextures: actualLoadedTextures,
|
|
411
398
|
baselineMemoryAllocation: this.baselineMemoryAllocation,
|
|
412
399
|
};
|
|
413
400
|
}
|
|
@@ -1277,6 +1277,44 @@ export class WebGlContextWrapper {
|
|
|
1277
1277
|
const { gl } = this;
|
|
1278
1278
|
gl.deleteShader(shader);
|
|
1279
1279
|
}
|
|
1280
|
+
|
|
1281
|
+
/**
|
|
1282
|
+
* Check for WebGL errors and return error information
|
|
1283
|
+
* @param operation Description of the operation for error reporting
|
|
1284
|
+
* @returns Object with error information or null if no error
|
|
1285
|
+
*/
|
|
1286
|
+
checkError(
|
|
1287
|
+
operation: string,
|
|
1288
|
+
): { error: number; errorName: string; message: string } | null {
|
|
1289
|
+
const error = this.getError();
|
|
1290
|
+
if (error !== 0) {
|
|
1291
|
+
// 0 is GL_NO_ERROR
|
|
1292
|
+
let errorName = 'UNKNOWN_ERROR';
|
|
1293
|
+
switch (error) {
|
|
1294
|
+
case this.INVALID_ENUM:
|
|
1295
|
+
errorName = 'INVALID_ENUM';
|
|
1296
|
+
break;
|
|
1297
|
+
case 0x0501: // GL_INVALID_VALUE
|
|
1298
|
+
errorName = 'INVALID_VALUE';
|
|
1299
|
+
break;
|
|
1300
|
+
case this.INVALID_OPERATION:
|
|
1301
|
+
errorName = 'INVALID_OPERATION';
|
|
1302
|
+
break;
|
|
1303
|
+
case 0x0505: // GL_OUT_OF_MEMORY
|
|
1304
|
+
errorName = 'OUT_OF_MEMORY';
|
|
1305
|
+
break;
|
|
1306
|
+
case 0x9242: // GL_CONTEXT_LOST_WEBGL
|
|
1307
|
+
errorName = 'CONTEXT_LOST_WEBGL';
|
|
1308
|
+
break;
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const message = `WebGL ${errorName} (0x${error.toString(
|
|
1312
|
+
16,
|
|
1313
|
+
)}) during ${operation}`;
|
|
1314
|
+
return { error, errorName, message };
|
|
1315
|
+
}
|
|
1316
|
+
return null;
|
|
1317
|
+
}
|
|
1280
1318
|
}
|
|
1281
1319
|
|
|
1282
1320
|
// prettier-ignore
|
|
@@ -39,14 +39,25 @@ export function isCompressedTextureContainer(url: string): boolean {
|
|
|
39
39
|
export const loadCompressedTexture = async (
|
|
40
40
|
url: string,
|
|
41
41
|
): Promise<TextureData> => {
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(url);
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
if (!response.ok) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Failed to fetch compressed texture: ${response.status} ${response.statusText}`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
52
|
+
|
|
53
|
+
if (url.indexOf('.ktx') !== -1) {
|
|
54
|
+
return loadKTXData(arrayBuffer);
|
|
55
|
+
}
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
return loadPVRData(arrayBuffer);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(`Failed to load compressed texture from ${url}: ${error}`);
|
|
60
|
+
}
|
|
50
61
|
};
|
|
51
62
|
|
|
52
63
|
/**
|
|
@@ -111,7 +122,7 @@ const loadPVRData = async (buffer: ArrayBuffer): Promise<TextureData> => {
|
|
|
111
122
|
const header = new Int32Array(arrayBuffer, 0, pvrHeaderLength);
|
|
112
123
|
|
|
113
124
|
// @ts-expect-error Object possibly undefined
|
|
114
|
-
|
|
125
|
+
|
|
115
126
|
const dataOffset = header[pvrMetadata] + 52;
|
|
116
127
|
const pvrtcData = new Uint8Array(arrayBuffer, dataOffset);
|
|
117
128
|
const mipmaps = [];
|
package/src/core/platform.ts
CHANGED
|
@@ -63,7 +63,11 @@ export const startLoop = (stage: Stage) => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (stage.txMemManager.checkCleanup() === true) {
|
|
66
|
-
stage.txMemManager.cleanup(
|
|
66
|
+
stage.txMemManager.cleanup();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (stage.txMemManager.checkDefrag() === true) {
|
|
70
|
+
stage.txMemManager.defragment();
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
stage.flushFrameEvents();
|
|
@@ -41,18 +41,20 @@ export class CanvasCoreTexture extends CoreContextTexture {
|
|
|
41
41
|
try {
|
|
42
42
|
const size = await this.onLoadRequest();
|
|
43
43
|
this.textureSource.setState('loaded', size);
|
|
44
|
-
this.textureSource.freeTextureData();
|
|
45
44
|
this.updateMemSize();
|
|
46
45
|
} catch (err) {
|
|
47
46
|
this.textureSource.setState('failed', err as Error);
|
|
48
|
-
this.textureSource.freeTextureData();
|
|
49
47
|
throw err;
|
|
50
48
|
}
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
51
|
+
release(): void {
|
|
54
52
|
this.image = undefined;
|
|
55
53
|
this.tintCache = undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
free(): void {
|
|
57
|
+
this.release();
|
|
56
58
|
this.textureSource.setState('freed');
|
|
57
59
|
this.setTextureMemUse(0);
|
|
58
60
|
this.textureSource.freeTextureData();
|
|
@@ -22,7 +22,6 @@ import { assertTruthy } from '../../../utils.js';
|
|
|
22
22
|
import type { TextureMemoryManager } from '../../TextureMemoryManager.js';
|
|
23
23
|
import type { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
|
|
24
24
|
import type { Texture } from '../../textures/Texture.js';
|
|
25
|
-
import { isPowerOfTwo } from '../../utils.js';
|
|
26
25
|
import { CoreContextTexture } from '../CoreContextTexture.js';
|
|
27
26
|
import { isHTMLImageElement } from './internal/RendererUtils.js';
|
|
28
27
|
|
|
@@ -52,6 +51,25 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
52
51
|
super(memManager, textureSource);
|
|
53
52
|
}
|
|
54
53
|
|
|
54
|
+
/**
|
|
55
|
+
* GL error check with direct state marking
|
|
56
|
+
* Uses cached error result to minimize function calls
|
|
57
|
+
*/
|
|
58
|
+
private checkGLError(): boolean {
|
|
59
|
+
// Skip if already failed to prevent double-processing
|
|
60
|
+
if (this.state === 'failed') {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const error = this.glw.getError();
|
|
65
|
+
if (error !== 0) {
|
|
66
|
+
this.state = 'failed';
|
|
67
|
+
this.textureSource.setState('failed', new Error(`WebGL Error: ${error}`));
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
|
|
55
73
|
get ctxTexture(): WebGLTexture | null {
|
|
56
74
|
if (this.state === 'freed') {
|
|
57
75
|
this.load();
|
|
@@ -92,10 +110,11 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
92
110
|
|
|
93
111
|
if (this._nativeCtxTexture === null) {
|
|
94
112
|
this.state = 'failed';
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
this.textureSource.setState(
|
|
114
|
+
'failed',
|
|
115
|
+
new Error('WebGL Texture creation failed'),
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
99
118
|
}
|
|
100
119
|
|
|
101
120
|
try {
|
|
@@ -113,16 +132,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
113
132
|
// Update the texture source's width and height so that it can be used
|
|
114
133
|
// for rendering.
|
|
115
134
|
this.textureSource.setState('loaded', { width, height });
|
|
116
|
-
|
|
117
|
-
// cleanup source texture data next tick
|
|
118
|
-
// This is done using queueMicrotask to ensure it runs after the current
|
|
119
|
-
// event loop tick, allowing the texture to be fully loaded and bound
|
|
120
|
-
// to the GL context before freeing the source data.
|
|
121
|
-
// This is important to avoid issues with the texture data being
|
|
122
|
-
// freed while the texture is still being loaded or used.
|
|
123
|
-
queueMicrotask(() => {
|
|
124
|
-
this.textureSource.freeTextureData();
|
|
125
|
-
});
|
|
135
|
+
this.textureSource.freeTextureData();
|
|
126
136
|
} catch (err: unknown) {
|
|
127
137
|
// If the texture has been freed while loading, return early.
|
|
128
138
|
// Type assertion needed because state could change during async operations
|
|
@@ -130,12 +140,9 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
130
140
|
return;
|
|
131
141
|
}
|
|
132
142
|
|
|
143
|
+
// Ensure texture is marked as failed
|
|
133
144
|
this.state = 'failed';
|
|
134
|
-
|
|
135
|
-
this.textureSource.setState('failed', error);
|
|
136
|
-
this.textureSource.freeTextureData();
|
|
137
|
-
console.error(err);
|
|
138
|
-
throw error; // Re-throw to propagate the error
|
|
145
|
+
this.textureSource.setState('failed');
|
|
139
146
|
}
|
|
140
147
|
}
|
|
141
148
|
|
|
@@ -145,10 +152,19 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
145
152
|
async onLoadRequest(): Promise<Dimensions> {
|
|
146
153
|
const { glw } = this;
|
|
147
154
|
const textureData = this.textureSource.textureData;
|
|
155
|
+
|
|
156
|
+
// Early return if texture is already failed
|
|
157
|
+
if (this.state === 'failed') {
|
|
158
|
+
return { width: 0, height: 0 };
|
|
159
|
+
}
|
|
160
|
+
|
|
148
161
|
if (textureData === null || this._nativeCtxTexture === null) {
|
|
149
|
-
|
|
150
|
-
|
|
162
|
+
this.state = 'failed';
|
|
163
|
+
this.textureSource.setState(
|
|
164
|
+
'failed',
|
|
165
|
+
new Error('No texture data available'),
|
|
151
166
|
);
|
|
167
|
+
return { width: 0, height: 0 };
|
|
152
168
|
}
|
|
153
169
|
|
|
154
170
|
// Set to a 1x1 transparent texture
|
|
@@ -160,6 +176,11 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
160
176
|
|
|
161
177
|
glw.activeTexture(0);
|
|
162
178
|
|
|
179
|
+
// High-performance error check - single call, direct state marking
|
|
180
|
+
if (this.checkGLError() === true) {
|
|
181
|
+
return { width: 0, height: 0 };
|
|
182
|
+
}
|
|
183
|
+
|
|
163
184
|
const tdata = textureData.data;
|
|
164
185
|
const format = glw.RGBA;
|
|
165
186
|
const formatBytes = 4;
|
|
@@ -183,25 +204,13 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
183
204
|
|
|
184
205
|
glw.texImage2D(0, format, format, glw.UNSIGNED_BYTE, tdata);
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Reset to a 1x1 transparent texture
|
|
191
|
-
glw.bindTexture(this._nativeCtxTexture);
|
|
207
|
+
// Check for errors after image upload operations
|
|
208
|
+
if (this.checkGLError() === true) {
|
|
209
|
+
return { width: 0, height: 0 };
|
|
210
|
+
}
|
|
192
211
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
format,
|
|
196
|
-
1,
|
|
197
|
-
1,
|
|
198
|
-
0,
|
|
199
|
-
format,
|
|
200
|
-
glw.UNSIGNED_BYTE,
|
|
201
|
-
TRANSPARENT_TEXTURE_DATA,
|
|
202
|
-
);
|
|
203
|
-
this.setTextureMemUse(TRANSPARENT_TEXTURE_DATA.byteLength);
|
|
204
|
-
} else if ('mipmaps' in tdata && tdata.mipmaps) {
|
|
212
|
+
this.setTextureMemUse(height * width * formatBytes * memoryPadding);
|
|
213
|
+
} else if (tdata && 'mipmaps' in tdata && tdata.mipmaps) {
|
|
205
214
|
const { mipmaps, width = 0, height = 0, type, glInternalFormat } = tdata;
|
|
206
215
|
const view =
|
|
207
216
|
type === 'ktx'
|
|
@@ -216,6 +225,11 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
216
225
|
glw.texParameteri(glw.TEXTURE_MAG_FILTER, glw.LINEAR);
|
|
217
226
|
glw.texParameteri(glw.TEXTURE_MIN_FILTER, glw.LINEAR);
|
|
218
227
|
|
|
228
|
+
// Check for errors after compressed texture operations
|
|
229
|
+
if (this.checkGLError() === true) {
|
|
230
|
+
return { width: 0, height: 0 };
|
|
231
|
+
}
|
|
232
|
+
|
|
219
233
|
this.setTextureMemUse(view.byteLength);
|
|
220
234
|
} else if (tdata && tdata instanceof Uint8Array) {
|
|
221
235
|
// Color Texture
|
|
@@ -239,12 +253,24 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
239
253
|
tdata,
|
|
240
254
|
);
|
|
241
255
|
|
|
256
|
+
// Check for errors after color texture operations
|
|
257
|
+
if (this.checkGLError() === true) {
|
|
258
|
+
return { width: 0, height: 0 };
|
|
259
|
+
}
|
|
260
|
+
|
|
242
261
|
this.setTextureMemUse(width * height * formatBytes);
|
|
243
262
|
} else {
|
|
244
263
|
console.error(
|
|
245
264
|
`WebGlCoreCtxTexture.onLoadRequest: Unexpected textureData returned`,
|
|
246
265
|
textureData,
|
|
247
266
|
);
|
|
267
|
+
|
|
268
|
+
this.state = 'failed';
|
|
269
|
+
this.textureSource.setState(
|
|
270
|
+
'failed',
|
|
271
|
+
new Error('Unexpected texture data'),
|
|
272
|
+
);
|
|
273
|
+
return { width: 0, height: 0 };
|
|
248
274
|
}
|
|
249
275
|
|
|
250
276
|
return {
|
|
@@ -265,6 +291,13 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
265
291
|
|
|
266
292
|
this.state = 'freed';
|
|
267
293
|
this.textureSource.setState('freed');
|
|
294
|
+
this.release();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Release the WebGLTexture from the GPU without changing state
|
|
299
|
+
*/
|
|
300
|
+
release(): void {
|
|
268
301
|
this._w = 0;
|
|
269
302
|
this._h = 0;
|
|
270
303
|
|
|
@@ -308,6 +341,11 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
|
|
|
308
341
|
glw.texParameteri(glw.TEXTURE_WRAP_S, glw.CLAMP_TO_EDGE);
|
|
309
342
|
glw.texParameteri(glw.TEXTURE_WRAP_T, glw.CLAMP_TO_EDGE);
|
|
310
343
|
|
|
344
|
+
const error = glw.getError();
|
|
345
|
+
if (error !== 0) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
|
|
311
349
|
return nativeTexture;
|
|
312
350
|
}
|
|
313
351
|
}
|