@lightningjs/renderer 3.0.1 → 3.0.3
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/README.md +56 -196
- package/dist/src/core/CoreNode.js +25 -4
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextNode.d.ts +9 -2
- package/dist/src/core/CoreTextNode.js +32 -11
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/CoreTextureManager.d.ts +8 -0
- package/dist/src/core/CoreTextureManager.js +13 -1
- package/dist/src/core/CoreTextureManager.js.map +1 -1
- package/dist/src/core/Stage.d.ts +8 -0
- package/dist/src/core/Stage.js +23 -0
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.d.ts +8 -13
- package/dist/src/core/TextureMemoryManager.js +22 -27
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/lib/ImageWorker.d.ts +2 -2
- package/dist/src/core/lib/ImageWorker.js +31 -12
- package/dist/src/core/lib/ImageWorker.js.map +1 -1
- package/dist/src/core/lib/WebGlContextWrapper.d.ts +105 -56
- package/dist/src/core/lib/WebGlContextWrapper.js +164 -158
- package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
- package/dist/src/core/lib/textureCompression.js +19 -10
- package/dist/src/core/lib/textureCompression.js.map +1 -1
- package/dist/src/core/lib/validateImageBitmap.d.ts +2 -1
- package/dist/src/core/lib/validateImageBitmap.js +4 -4
- package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
- package/dist/src/core/platform.js +2 -2
- package/dist/src/core/platform.js.map +1 -1
- package/dist/src/core/platforms/Platform.d.ts +4 -0
- package/dist/src/core/platforms/Platform.js.map +1 -1
- package/dist/src/core/platforms/web/WebPlatform.d.ts +2 -0
- package/dist/src/core/platforms/web/WebPlatform.js +13 -0
- package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
- package/dist/src/core/renderers/CoreRenderer.d.ts +6 -0
- package/dist/src/core/renderers/CoreRenderer.js +8 -0
- package/dist/src/core/renderers/CoreRenderer.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasRenderer.d.ts +1 -0
- package/dist/src/core/renderers/canvas/CanvasRenderer.js +5 -0
- package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlRenderOp.d.ts +45 -0
- package/dist/src/core/renderers/webgl/WebGlRenderOp.js +127 -0
- package/dist/src/core/renderers/webgl/WebGlRenderOp.js.map +1 -0
- package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +2 -0
- package/dist/src/core/renderers/webgl/WebGlRenderer.js +30 -22
- package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/CanvasFont.d.ts +14 -0
- package/dist/src/core/text-rendering/CanvasFont.js +120 -0
- package/dist/src/core/text-rendering/CanvasFont.js.map +1 -0
- package/dist/src/core/text-rendering/CanvasTextRenderer.d.ts +1 -2
- package/dist/src/core/text-rendering/CanvasTextRenderer.js +11 -19
- package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/CoreFont.d.ts +33 -0
- package/dist/src/core/text-rendering/CoreFont.js +48 -0
- package/dist/src/core/text-rendering/CoreFont.js.map +1 -0
- package/dist/src/core/text-rendering/FontManager.d.ts +11 -0
- package/dist/src/core/text-rendering/FontManager.js +41 -0
- package/dist/src/core/text-rendering/FontManager.js.map +1 -0
- package/dist/src/core/text-rendering/SdfFont.d.ts +29 -0
- package/dist/src/core/text-rendering/SdfFont.js +142 -0
- package/dist/src/core/text-rendering/SdfFont.js.map +1 -0
- package/dist/src/core/text-rendering/SdfFontHandler.js +7 -5
- package/dist/src/core/text-rendering/SdfFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/SdfTextRenderer.d.ts +2 -2
- package/dist/src/core/text-rendering/SdfTextRenderer.js +141 -132
- package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/TextGenerator.d.ts +10 -0
- package/dist/src/core/text-rendering/TextGenerator.js +36 -0
- package/dist/src/core/text-rendering/TextGenerator.js.map +1 -0
- package/dist/src/core/text-rendering/TextRenderer.d.ts +26 -20
- package/dist/src/core/text-rendering/Utils.d.ts +2 -0
- package/dist/src/core/text-rendering/Utils.js +3 -0
- package/dist/src/core/text-rendering/Utils.js.map +1 -1
- package/dist/src/main-api/Renderer.d.ts +14 -0
- package/dist/src/main-api/Renderer.js +29 -3
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +2 -1
- package/src/core/CoreNode.ts +29 -4
- package/src/core/CoreTextNode.test.ts +237 -0
- package/src/core/CoreTextNode.ts +53 -33
- package/src/core/CoreTextureManager.ts +14 -2
- package/src/core/Stage.ts +29 -0
- package/src/core/TextureMemoryManager.test.ts +134 -0
- package/src/core/TextureMemoryManager.ts +23 -30
- package/src/core/platforms/Platform.ts +5 -0
- package/src/core/platforms/web/WebPlatform.ts +13 -0
- package/src/core/renderers/CoreRenderer.ts +10 -0
- package/src/core/renderers/canvas/CanvasRenderer.ts +6 -0
- package/src/core/renderers/webgl/WebGlRenderer.rtt.test.ts +551 -0
- package/src/core/renderers/webgl/WebGlRenderer.ts +38 -28
- package/src/core/text-rendering/CanvasTextRenderer.ts +13 -41
- package/src/core/text-rendering/SdfFontHandler.ts +8 -5
- package/src/core/text-rendering/SdfTextRenderer.ts +166 -163
- package/src/core/text-rendering/TextRenderer.ts +23 -21
- package/src/core/text-rendering/Utils.ts +5 -1
- package/src/main-api/Renderer.test.ts +153 -0
- package/src/main-api/Renderer.ts +33 -3
- package/dist/src/core/renderers/webgl/WebGlCoreShader.destroy.d.ts +0 -1
- package/dist/src/core/renderers/webgl/WebGlCoreShader.destroy.js +0 -2
- package/dist/src/core/renderers/webgl/WebGlCoreShader.destroy.js.map +0 -1
package/src/core/CoreTextNode.ts
CHANGED
|
@@ -65,7 +65,9 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
65
65
|
|
|
66
66
|
// SDF layout caching for performance
|
|
67
67
|
private _cachedLayout: TextLayout | null = null;
|
|
68
|
-
|
|
68
|
+
// Mutable ref box shared with SdfTextRenderer so the renderer can write the
|
|
69
|
+
// created WebGLBuffer back into it, allowing reuse across frames.
|
|
70
|
+
private _sdfBufferRef: { current: WebGLBuffer | null } = { current: null };
|
|
69
71
|
|
|
70
72
|
// Text renderer properties - stored directly on the node
|
|
71
73
|
private textProps: CoreTextNodeProps;
|
|
@@ -113,6 +115,18 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
113
115
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
114
116
|
};
|
|
115
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Delete the cached WebGLBuffer held by the SDF renderer ref and reset the
|
|
120
|
+
* ref so the next renderQuads call allocates a fresh one.
|
|
121
|
+
* Safe to call from destroy() or on text change.
|
|
122
|
+
*/
|
|
123
|
+
private releaseSdfBuffer(): void {
|
|
124
|
+
const buf = this._sdfBufferRef.current;
|
|
125
|
+
if (buf === null) return;
|
|
126
|
+
this.stage.renderer.deleteBuffer(buf);
|
|
127
|
+
this._sdfBufferRef.current = null;
|
|
128
|
+
}
|
|
129
|
+
|
|
116
130
|
allowTextGeneration() {
|
|
117
131
|
const p = this.props.parent;
|
|
118
132
|
if (p === null) {
|
|
@@ -196,7 +210,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
196
210
|
* Override CoreNode's update method to handle text-specific updates
|
|
197
211
|
*/
|
|
198
212
|
override update(delta: number, parentClippingRect: RectWithValid): void {
|
|
199
|
-
const hasValidText =
|
|
213
|
+
const hasValidText =
|
|
214
|
+
typeof this.textProps.text === 'string' && this.textProps.text.length > 0;
|
|
200
215
|
|
|
201
216
|
if (
|
|
202
217
|
hasValidText === true &&
|
|
@@ -207,7 +222,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
207
222
|
if (this.fontHandler.isFontLoaded(this.textProps.fontFamily) === true) {
|
|
208
223
|
this._waitingForFont = false;
|
|
209
224
|
this._cachedLayout = null; // Invalidate cached layout
|
|
210
|
-
this.
|
|
225
|
+
this.releaseSdfBuffer(); // Free the cached WebGLBuffer
|
|
211
226
|
const resp = this.textRenderer.renderText(this.textProps);
|
|
212
227
|
this.handleRenderResult(resp);
|
|
213
228
|
this._layoutGenerated = true;
|
|
@@ -216,11 +231,13 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
216
231
|
this._waitingForFont = true;
|
|
217
232
|
}
|
|
218
233
|
} else if (hasValidText === false) {
|
|
234
|
+
this.props.w = 0;
|
|
235
|
+
this.props.h = 0;
|
|
219
236
|
// If text is invalid, ensure node is not renderable
|
|
220
237
|
this.setRenderable(false);
|
|
221
238
|
this._layoutGenerated = false;
|
|
222
239
|
this._cachedLayout = null;
|
|
223
|
-
this.
|
|
240
|
+
this.releaseSdfBuffer(); // Free the cached WebGLBuffer
|
|
224
241
|
}
|
|
225
242
|
|
|
226
243
|
// First run the standard CoreNode update
|
|
@@ -232,7 +249,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
232
249
|
*/
|
|
233
250
|
override updateIsRenderable(): void {
|
|
234
251
|
// Guard: Text nodes are never renderable without valid text
|
|
235
|
-
const hasValidText =
|
|
252
|
+
const hasValidText =
|
|
253
|
+
typeof this.textProps.text === 'string' && this.textProps.text.length > 0;
|
|
236
254
|
if (hasValidText === false) {
|
|
237
255
|
this.setRenderable(false);
|
|
238
256
|
return;
|
|
@@ -335,37 +353,39 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
335
353
|
}
|
|
336
354
|
|
|
337
355
|
// Early return if no cached data
|
|
338
|
-
if (
|
|
356
|
+
if (this._cachedLayout === null) {
|
|
339
357
|
return;
|
|
340
358
|
}
|
|
341
359
|
|
|
342
|
-
if (this._lastVertexBuffer === null) {
|
|
343
|
-
this._lastVertexBuffer = this.textRenderer.addQuads(this._cachedLayout);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
360
|
const props = this.textProps;
|
|
347
|
-
this.textRenderer.renderQuads(
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
);
|
|
361
|
+
this.textRenderer.renderQuads(renderer, this._cachedLayout as TextLayout, {
|
|
362
|
+
fontFamily: this.textProps.fontFamily,
|
|
363
|
+
fontSize: props.fontSize,
|
|
364
|
+
color: this.props.color || 0xffffffff,
|
|
365
|
+
offsetY: props.offsetY,
|
|
366
|
+
worldAlpha: this.worldAlpha,
|
|
367
|
+
globalTransform: this.globalTransform!.getFloatArr(),
|
|
368
|
+
clippingRect: this.clippingRect,
|
|
369
|
+
width: this.props.w,
|
|
370
|
+
height: this.props.h,
|
|
371
|
+
parentHasRenderTexture: this.parentHasRenderTexture,
|
|
372
|
+
framebufferDimensions:
|
|
373
|
+
this.parentHasRenderTexture === true
|
|
374
|
+
? this.parentFramebufferDimensions
|
|
375
|
+
: null,
|
|
376
|
+
stage: this.stage,
|
|
377
|
+
glBufferRef: this._sdfBufferRef,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
override updateRenderState(renderState: CoreNodeRenderState): void {
|
|
382
|
+
super.updateRenderState(renderState);
|
|
383
|
+
if (
|
|
384
|
+
this._type === 'sdf' &&
|
|
385
|
+
renderState === CoreNodeRenderState.OutOfBounds
|
|
386
|
+
) {
|
|
387
|
+
this.releaseSdfBuffer();
|
|
388
|
+
}
|
|
369
389
|
}
|
|
370
390
|
|
|
371
391
|
override destroy(): void {
|
|
@@ -375,7 +395,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
375
395
|
|
|
376
396
|
// Clear cached layout and vertex buffer
|
|
377
397
|
this._cachedLayout = null;
|
|
378
|
-
this.
|
|
398
|
+
this.releaseSdfBuffer(); // Delete the cached WebGLBuffer before losing stage ref
|
|
379
399
|
|
|
380
400
|
this.fontHandler = null!; // Clear reference to avoid memory leaks
|
|
381
401
|
this.textRenderer = null!; // Clear reference to avoid memory leaks
|
|
@@ -289,8 +289,6 @@ export class CoreTextureManager extends EventEmitter {
|
|
|
289
289
|
* @param immediate - Whether to prioritize the texture for immediate loading
|
|
290
290
|
*/
|
|
291
291
|
async loadTexture(texture: Texture, priority?: boolean): Promise<void> {
|
|
292
|
-
this.stage.txMemManager.removeFromOrphanedTextures(texture);
|
|
293
|
-
|
|
294
292
|
if (texture.type === TextureType.subTexture) {
|
|
295
293
|
// ignore subtextures - they get loaded through their parent
|
|
296
294
|
return;
|
|
@@ -464,6 +462,20 @@ export class CoreTextureManager extends EventEmitter {
|
|
|
464
462
|
}
|
|
465
463
|
}
|
|
466
464
|
|
|
465
|
+
/**
|
|
466
|
+
* Destroy the CoreTextureManager and release all internal references.
|
|
467
|
+
*
|
|
468
|
+
* @remarks
|
|
469
|
+
* Clears the upload queue and key/inverse-key caches so that queued
|
|
470
|
+
* textures can be garbage-collected after a renderer teardown.
|
|
471
|
+
*/
|
|
472
|
+
destroy(): void {
|
|
473
|
+
this.uploadTextureQueue = [];
|
|
474
|
+
this.keyCache.clear();
|
|
475
|
+
// inverseKeyCache is a WeakMap – entries will be GC'd automatically once
|
|
476
|
+
// the texture objects themselves are no longer referenced.
|
|
477
|
+
}
|
|
478
|
+
|
|
467
479
|
/**
|
|
468
480
|
* Resolve a parent texture from the cache or fallback to the provided texture.
|
|
469
481
|
*
|
package/src/core/Stage.ts
CHANGED
|
@@ -882,6 +882,35 @@ export class Stage {
|
|
|
882
882
|
this.txMemManager.cleanup(full);
|
|
883
883
|
}
|
|
884
884
|
|
|
885
|
+
/**
|
|
886
|
+
* Destroy the stage and release all resources.
|
|
887
|
+
*
|
|
888
|
+
* @remarks
|
|
889
|
+
* This method stops the render loop, destroys all nodes, releases all
|
|
890
|
+
* textures and GPU resources, and terminates any background workers.
|
|
891
|
+
*/
|
|
892
|
+
destroy(): void {
|
|
893
|
+
// Stop the render loop and terminate workers
|
|
894
|
+
this.platform.stopLoop();
|
|
895
|
+
|
|
896
|
+
// Recursively destroy all nodes (includes CoreTextNode font/text cleanup)
|
|
897
|
+
this.root.destroy();
|
|
898
|
+
|
|
899
|
+
// Free all GPU-side textures and clear internal tracking arrays/caches
|
|
900
|
+
this.txMemManager.destroy();
|
|
901
|
+
|
|
902
|
+
// Clear the texture upload queue and key caches
|
|
903
|
+
this.txManager.destroy();
|
|
904
|
+
|
|
905
|
+
// Release the GPU context (WebGL) or canvas resources
|
|
906
|
+
this.renderer.destroy();
|
|
907
|
+
|
|
908
|
+
// Clear text renderer caches
|
|
909
|
+
for (const key in this.textRenderers) {
|
|
910
|
+
this.textRenderers[key]!.clearCache();
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
885
914
|
set clearColor(value: number) {
|
|
886
915
|
this.renderer.updateClearColor(value);
|
|
887
916
|
this.renderRequested = true;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* If not stated otherwise in this file or this component's LICENSE file the
|
|
3
|
+
* following copyright and licenses apply:
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC.
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the License);
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { describe, it, expect } from 'vitest';
|
|
21
|
+
import { mock } from 'vitest-mock-extended';
|
|
22
|
+
import {
|
|
23
|
+
TextureMemoryManager,
|
|
24
|
+
type TextureMemoryManagerSettings,
|
|
25
|
+
} from './TextureMemoryManager.js';
|
|
26
|
+
import type { Stage } from './Stage.js';
|
|
27
|
+
import type { Texture } from './textures/Texture.js';
|
|
28
|
+
import { TextureType } from './textures/Texture.js';
|
|
29
|
+
|
|
30
|
+
function makeSettings(
|
|
31
|
+
overrides?: Partial<TextureMemoryManagerSettings>,
|
|
32
|
+
): TextureMemoryManagerSettings {
|
|
33
|
+
return {
|
|
34
|
+
criticalThreshold: 124e6,
|
|
35
|
+
targetThresholdLevel: 0.5,
|
|
36
|
+
cleanupInterval: 5000,
|
|
37
|
+
debugLogging: false,
|
|
38
|
+
baselineMemoryAllocation: 25e6,
|
|
39
|
+
doNotExceedCriticalThreshold: false,
|
|
40
|
+
...overrides,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makeTexture(memUsed = 1024): Texture {
|
|
45
|
+
const texture = mock<Texture>();
|
|
46
|
+
texture.memUsed = memUsed;
|
|
47
|
+
texture.type = TextureType.image;
|
|
48
|
+
texture.state = 'loaded';
|
|
49
|
+
(texture as { renderable: boolean }).renderable = false;
|
|
50
|
+
texture.preventCleanup = false;
|
|
51
|
+
texture.canBeCleanedUp.mockReturnValue(true);
|
|
52
|
+
return texture;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function makeStage() {
|
|
56
|
+
const stage = mock<Stage>();
|
|
57
|
+
const txManager = mock<Stage['txManager']>();
|
|
58
|
+
(stage as { txManager: Stage['txManager'] }).txManager = txManager;
|
|
59
|
+
return stage;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe('TextureMemoryManager', () => {
|
|
63
|
+
describe('destroy()', () => {
|
|
64
|
+
it('calls destroyTexture for every loaded texture', () => {
|
|
65
|
+
const stage = makeStage();
|
|
66
|
+
const manager = new TextureMemoryManager(stage, makeSettings());
|
|
67
|
+
|
|
68
|
+
const textures = [makeTexture(512), makeTexture(1024), makeTexture(2048)];
|
|
69
|
+
|
|
70
|
+
for (const texture of textures) {
|
|
71
|
+
manager.setTextureMemUse(texture, texture.memUsed);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
manager.destroy();
|
|
75
|
+
|
|
76
|
+
for (const texture of textures) {
|
|
77
|
+
expect(texture.destroy).toHaveBeenCalledOnce();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('clears internal tracking so memUsed is 0 after destroy', () => {
|
|
82
|
+
const stage = makeStage();
|
|
83
|
+
const manager = new TextureMemoryManager(stage, makeSettings());
|
|
84
|
+
|
|
85
|
+
const texture = makeTexture(4096);
|
|
86
|
+
manager.setTextureMemUse(texture, texture.memUsed);
|
|
87
|
+
|
|
88
|
+
manager.destroy();
|
|
89
|
+
|
|
90
|
+
expect(manager.getMemoryInfo().memUsed).toBe(0);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('reports no loaded textures after destroy', () => {
|
|
94
|
+
const stage = makeStage();
|
|
95
|
+
const manager = new TextureMemoryManager(stage, makeSettings());
|
|
96
|
+
|
|
97
|
+
manager.setTextureMemUse(makeTexture(512), 512);
|
|
98
|
+
manager.setTextureMemUse(makeTexture(1024), 1024);
|
|
99
|
+
|
|
100
|
+
manager.destroy();
|
|
101
|
+
|
|
102
|
+
expect(manager.getMemoryInfo().loadedTextures).toBe(0);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('removes each texture from the cache via txManager', () => {
|
|
106
|
+
const stage = makeStage();
|
|
107
|
+
const manager = new TextureMemoryManager(stage, makeSettings());
|
|
108
|
+
|
|
109
|
+
const textures = [makeTexture(256), makeTexture(512)];
|
|
110
|
+
for (const texture of textures) {
|
|
111
|
+
manager.setTextureMemUse(texture, texture.memUsed);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
manager.destroy();
|
|
115
|
+
|
|
116
|
+
expect(stage.txManager.removeTextureFromCache).toHaveBeenCalledTimes(
|
|
117
|
+
textures.length,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
for (const texture of textures) {
|
|
121
|
+
expect(stage.txManager.removeTextureFromCache).toHaveBeenCalledWith(
|
|
122
|
+
texture,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('handles an empty loadedTextures list without throwing', () => {
|
|
128
|
+
const stage = makeStage();
|
|
129
|
+
const manager = new TextureMemoryManager(stage, makeSettings());
|
|
130
|
+
|
|
131
|
+
expect(() => manager.destroy()).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -116,7 +116,6 @@ export interface MemoryInfo {
|
|
|
116
116
|
export class TextureMemoryManager {
|
|
117
117
|
private memUsed = 0;
|
|
118
118
|
private loadedTextures: (Texture | null)[] = [];
|
|
119
|
-
private orphanedTextures: Texture[] = [];
|
|
120
119
|
private criticalThreshold: number = 124e6;
|
|
121
120
|
private targetThreshold: number = 0.5;
|
|
122
121
|
private cleanupInterval: number = 5000;
|
|
@@ -145,35 +144,6 @@ export class TextureMemoryManager {
|
|
|
145
144
|
this.updateSettings(settings);
|
|
146
145
|
}
|
|
147
146
|
|
|
148
|
-
/**
|
|
149
|
-
* Add a texture to the orphaned textures list
|
|
150
|
-
*
|
|
151
|
-
* @param texture - The texture to add to the orphaned textures list
|
|
152
|
-
*/
|
|
153
|
-
addToOrphanedTextures(texture: Texture) {
|
|
154
|
-
// if the texture is already in the orphaned textures list add it at the end
|
|
155
|
-
if (this.orphanedTextures.includes(texture)) {
|
|
156
|
-
this.removeFromOrphanedTextures(texture);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// If the texture can be cleaned up, add it to the orphaned textures list
|
|
160
|
-
if (texture.preventCleanup === false) {
|
|
161
|
-
this.orphanedTextures.push(texture);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Remove a texture from the orphaned textures list
|
|
167
|
-
*
|
|
168
|
-
* @param texture - The texture to remove from the orphaned textures list
|
|
169
|
-
*/
|
|
170
|
-
removeFromOrphanedTextures(texture: Texture) {
|
|
171
|
-
const index = this.orphanedTextures.indexOf(texture);
|
|
172
|
-
if (index !== -1) {
|
|
173
|
-
this.orphanedTextures.splice(index, 1);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
147
|
/**
|
|
178
148
|
* Set the memory usage of a texture
|
|
179
149
|
*
|
|
@@ -415,4 +385,27 @@ export class TextureMemoryManager {
|
|
|
415
385
|
this.setTextureMemUse = () => {};
|
|
416
386
|
}
|
|
417
387
|
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Destroy the TextureMemoryManager and release all internal references.
|
|
391
|
+
*
|
|
392
|
+
* @remarks
|
|
393
|
+
* Clears the debug-logging interval (if active) and empties all internal
|
|
394
|
+
* texture tracking arrays so that held textures can be garbage-collected.
|
|
395
|
+
*/
|
|
396
|
+
destroy(): void {
|
|
397
|
+
if (this.loggingID) {
|
|
398
|
+
clearInterval(this.loggingID);
|
|
399
|
+
this.loggingID = 0 as unknown as ReturnType<typeof setInterval>;
|
|
400
|
+
}
|
|
401
|
+
// Free GPU resources for every loaded texture before clearing the array
|
|
402
|
+
for (let i = 0; i < this.loadedTextures.length; i++) {
|
|
403
|
+
const texture = this.loadedTextures[i];
|
|
404
|
+
if (texture !== null && texture !== undefined) {
|
|
405
|
+
this.destroyTexture(texture);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
this.loadedTextures = [];
|
|
409
|
+
this.memUsed = 0;
|
|
410
|
+
}
|
|
418
411
|
}
|
|
@@ -89,6 +89,11 @@ export abstract class Platform {
|
|
|
89
89
|
*/
|
|
90
90
|
abstract startLoop(stage: Stage): void;
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Stops the main rendering loop and releases platform resources.
|
|
94
|
+
*/
|
|
95
|
+
abstract stopLoop(): void;
|
|
96
|
+
|
|
92
97
|
/**
|
|
93
98
|
* Fetches a resource from the network.
|
|
94
99
|
* @param url - The URL of the resource to fetch.
|
|
@@ -48,6 +48,7 @@ export class WebPlatform extends Platform {
|
|
|
48
48
|
private useImageWorker: boolean;
|
|
49
49
|
private imageWorkerManager: ImageWorkerManager | null = null;
|
|
50
50
|
private hasWorker = !!self.Worker;
|
|
51
|
+
private stopped = false;
|
|
51
52
|
|
|
52
53
|
constructor(settings: PlatformSettings = {}) {
|
|
53
54
|
super(settings);
|
|
@@ -100,10 +101,12 @@ export class WebPlatform extends Platform {
|
|
|
100
101
|
////////////////////////
|
|
101
102
|
|
|
102
103
|
override startLoop(stage: Stage): void {
|
|
104
|
+
this.stopped = false;
|
|
103
105
|
let isIdle = false;
|
|
104
106
|
let lastFrameTime = 0;
|
|
105
107
|
|
|
106
108
|
const runLoop = (currentTime: number = 0) => {
|
|
109
|
+
if (this.stopped) return;
|
|
107
110
|
const targetFrameTime = stage.targetFrameTime;
|
|
108
111
|
|
|
109
112
|
// Check if we should throttle this frame
|
|
@@ -173,6 +176,16 @@ export class WebPlatform extends Platform {
|
|
|
173
176
|
requestAnimationFrame(runLoop);
|
|
174
177
|
}
|
|
175
178
|
|
|
179
|
+
override stopLoop(): void {
|
|
180
|
+
this.stopped = true;
|
|
181
|
+
if (this.imageWorkerManager !== null) {
|
|
182
|
+
for (const worker of this.imageWorkerManager.workers) {
|
|
183
|
+
worker.terminate();
|
|
184
|
+
}
|
|
185
|
+
this.imageWorkerManager = null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
176
189
|
////////////////////////
|
|
177
190
|
// Image handling
|
|
178
191
|
////////////////////////
|
|
@@ -67,5 +67,15 @@ export abstract class CoreRenderer {
|
|
|
67
67
|
abstract getQuadCount(): number | null;
|
|
68
68
|
abstract updateViewport(): void;
|
|
69
69
|
abstract updateClearColor(color: number): void;
|
|
70
|
+
abstract destroy(): void;
|
|
70
71
|
getTextureCoords?(node: CoreNode): TextureCoords | undefined;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Delete a GPU buffer previously allocated by this renderer.
|
|
75
|
+
* No-op for renderers that do not use WebGL buffers (e.g. Canvas).
|
|
76
|
+
*/
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
78
|
+
deleteBuffer(_buffer: WebGLBuffer): void {
|
|
79
|
+
// no-op default — overridden by WebGlRenderer
|
|
80
|
+
}
|
|
71
81
|
}
|
|
@@ -255,4 +255,10 @@ export class CanvasRenderer extends CoreRenderer {
|
|
|
255
255
|
getDefaultShaderNode() {
|
|
256
256
|
return null;
|
|
257
257
|
}
|
|
258
|
+
|
|
259
|
+
override destroy(): void {
|
|
260
|
+
// Release canvas 2D context by resizing canvas to 0
|
|
261
|
+
this.canvas.width = 0;
|
|
262
|
+
this.canvas.height = 0;
|
|
263
|
+
}
|
|
258
264
|
}
|