@lightningjs/renderer 3.0.2 → 3.0.4
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.d.ts +2 -1
- package/dist/src/core/CoreNode.js +31 -7
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextNode.d.ts +26 -6
- package/dist/src/core/CoreTextNode.js +163 -60
- 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/fps.d.ts +15 -0
- package/dist/src/core/lib/fps.js +62 -0
- package/dist/src/core/lib/fps.js.map +1 -0
- 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 +4 -2
- 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/renderers/webgl/WebGlShaderProgram.js +2 -3
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.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/CanvasFontHandler.d.ts +1 -1
- package/dist/src/core/text-rendering/CanvasFontHandler.js +1 -1
- package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
- package/dist/src/core/text-rendering/CanvasTextRenderer.d.ts +3 -5
- package/dist/src/core/text-rendering/CanvasTextRenderer.js +16 -22
- 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/SdfTextRenderer.d.ts +4 -6
- package/dist/src/core/text-rendering/SdfTextRenderer.js +87 -168
- 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/TextLayoutEngine.js +43 -12
- package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
- package/dist/src/core/text-rendering/TextRenderer.d.ts +41 -27
- 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/Inspector.d.ts +1 -1
- package/dist/src/main-api/Inspector.js +25 -20
- package/dist/src/main-api/Inspector.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.test.ts +1 -1
- package/src/core/CoreNode.ts +37 -8
- package/src/core/CoreTextNode.test.ts +350 -0
- package/src/core/CoreTextNode.ts +201 -74
- 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 +40 -31
- package/src/core/renderers/webgl/WebGlShaderProgram.test.ts +274 -0
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +7 -7
- package/src/core/text-rendering/CanvasFontHandler.ts +2 -2
- package/src/core/text-rendering/CanvasTextRenderer.ts +24 -45
- package/src/core/text-rendering/SdfTextRenderer.ts +106 -215
- package/src/core/text-rendering/TextLayoutEngine.ts +61 -28
- package/src/core/text-rendering/TextRenderer.ts +42 -33
- package/src/core/text-rendering/Utils.ts +5 -1
- package/src/core/text-rendering/tests/TextLayoutEngine.test.ts +20 -0
- package/src/main-api/Inspector.ts +25 -25
- 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/renderers/webgl/SdfRenderOp.ts +0 -106
package/src/core/CoreTextNode.ts
CHANGED
|
@@ -21,7 +21,6 @@ import type {
|
|
|
21
21
|
FontHandler,
|
|
22
22
|
TextRenderer,
|
|
23
23
|
TrProps,
|
|
24
|
-
TextLayout,
|
|
25
24
|
TextRenderInfo,
|
|
26
25
|
} from './text-rendering/TextRenderer.js';
|
|
27
26
|
import {
|
|
@@ -40,6 +39,11 @@ import type { RectWithValid } from './lib/utils.js';
|
|
|
40
39
|
import type { CoreRenderer } from './renderers/CoreRenderer.js';
|
|
41
40
|
import type { TextureLoadedEventHandler } from './textures/Texture.js';
|
|
42
41
|
import { Matrix3d } from './lib/Matrix3d.js';
|
|
42
|
+
import { BufferCollection } from './renderers/webgl/internal/BufferCollection.js';
|
|
43
|
+
import type { SdfShaderProps } from './shaders/webgl/SdfShader.js';
|
|
44
|
+
import type { WebGlRenderer } from './renderers/webgl/WebGlRenderer.js';
|
|
45
|
+
import type { WebGlCtxTexture } from './renderers/webgl/WebGlCtxTexture.js';
|
|
46
|
+
import { mergeColorAlpha } from '../utils.js';
|
|
43
47
|
export interface CoreTextNodeProps extends CoreNodeProps, TrProps {
|
|
44
48
|
/**
|
|
45
49
|
* Force Text Node to use a specific Text Renderer
|
|
@@ -63,19 +67,14 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
63
67
|
private _waitingForFont = false;
|
|
64
68
|
private _containType: TextConstraint = TextConstraint.none;
|
|
65
69
|
|
|
66
|
-
|
|
67
|
-
private
|
|
68
|
-
private
|
|
70
|
+
private _sdfBuffer: WebGLBuffer | null = null;
|
|
71
|
+
private _sdfQuadCollection: BufferCollection | null = null;
|
|
72
|
+
private _sdfShaderProps: Partial<SdfShaderProps> | null = null;
|
|
69
73
|
|
|
70
74
|
// Text renderer properties - stored directly on the node
|
|
71
|
-
|
|
75
|
+
textProps: CoreTextNodeProps;
|
|
72
76
|
|
|
73
|
-
private _renderInfo: TextRenderInfo =
|
|
74
|
-
width: 0,
|
|
75
|
-
height: 0,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
private _type: 'sdf' | 'canvas' = 'sdf'; // Default to SDF renderer
|
|
77
|
+
private _renderInfo: TextRenderInfo | null = null;
|
|
79
78
|
|
|
80
79
|
constructor(
|
|
81
80
|
stage: Stage,
|
|
@@ -85,7 +84,6 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
85
84
|
super(stage, props);
|
|
86
85
|
this.textRenderer = textRenderer;
|
|
87
86
|
this.fontHandler = textRenderer.font;
|
|
88
|
-
this._type = textRenderer.type;
|
|
89
87
|
|
|
90
88
|
// Initialize text properties from props
|
|
91
89
|
// Props are guaranteed to have all defaults resolved by Stage.createTextNode
|
|
@@ -113,6 +111,19 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
113
111
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
114
112
|
};
|
|
115
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Delete the cached WebGLBuffer held by the SDF renderer ref and reset the
|
|
116
|
+
* ref so the next renderQuads call allocates a fresh one.
|
|
117
|
+
* Safe to call from destroy() or on text change.
|
|
118
|
+
*/
|
|
119
|
+
private releaseSdfBuffer(): void {
|
|
120
|
+
const buf = this._sdfBuffer;
|
|
121
|
+
if (buf === null) return;
|
|
122
|
+
this.stage.renderer.deleteBuffer(buf);
|
|
123
|
+
this._sdfBuffer = null;
|
|
124
|
+
this._sdfQuadCollection = null;
|
|
125
|
+
}
|
|
126
|
+
|
|
116
127
|
allowTextGeneration() {
|
|
117
128
|
const p = this.props.parent;
|
|
118
129
|
if (p === null) {
|
|
@@ -196,7 +207,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
196
207
|
* Override CoreNode's update method to handle text-specific updates
|
|
197
208
|
*/
|
|
198
209
|
override update(delta: number, parentClippingRect: RectWithValid): void {
|
|
199
|
-
const hasValidText =
|
|
210
|
+
const hasValidText =
|
|
211
|
+
typeof this.textProps.text === 'string' && this.textProps.text.length > 0;
|
|
200
212
|
|
|
201
213
|
if (
|
|
202
214
|
hasValidText === true &&
|
|
@@ -206,8 +218,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
206
218
|
) {
|
|
207
219
|
if (this.fontHandler.isFontLoaded(this.textProps.fontFamily) === true) {
|
|
208
220
|
this._waitingForFont = false;
|
|
209
|
-
this.
|
|
210
|
-
this.
|
|
221
|
+
this._renderInfo = null; // Clear any previous render info before generating new layout
|
|
222
|
+
this.releaseSdfBuffer(); // Free the cached WebGLBuffer
|
|
211
223
|
const resp = this.textRenderer.renderText(this.textProps);
|
|
212
224
|
this.handleRenderResult(resp);
|
|
213
225
|
this._layoutGenerated = true;
|
|
@@ -216,11 +228,13 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
216
228
|
this._waitingForFont = true;
|
|
217
229
|
}
|
|
218
230
|
} else if (hasValidText === false) {
|
|
231
|
+
this.props.w = 0;
|
|
232
|
+
this.props.h = 0;
|
|
219
233
|
// If text is invalid, ensure node is not renderable
|
|
220
234
|
this.setRenderable(false);
|
|
221
235
|
this._layoutGenerated = false;
|
|
222
|
-
this.
|
|
223
|
-
this.
|
|
236
|
+
this._renderInfo = null;
|
|
237
|
+
this.releaseSdfBuffer(); // Free the cached WebGLBuffer
|
|
224
238
|
}
|
|
225
239
|
|
|
226
240
|
// First run the standard CoreNode update
|
|
@@ -232,20 +246,21 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
232
246
|
*/
|
|
233
247
|
override updateIsRenderable(): void {
|
|
234
248
|
// Guard: Text nodes are never renderable without valid text
|
|
235
|
-
const hasValidText =
|
|
236
|
-
|
|
249
|
+
const hasValidText =
|
|
250
|
+
typeof this.textProps.text === 'string' && this.textProps.text.length > 0;
|
|
251
|
+
|
|
252
|
+
const renderInfo = this._renderInfo;
|
|
253
|
+
if (hasValidText === false || renderInfo === null) {
|
|
237
254
|
this.setRenderable(false);
|
|
238
255
|
return;
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
// SDF text nodes are always renderable if they have a valid layout
|
|
242
|
-
if (
|
|
259
|
+
if (renderInfo.type === 'canvas') {
|
|
243
260
|
super.updateIsRenderable();
|
|
244
261
|
return;
|
|
245
262
|
}
|
|
246
|
-
|
|
247
|
-
// For SDF, check if we have a cached layout
|
|
248
|
-
this.setRenderable(this._cachedLayout !== null);
|
|
263
|
+
this.setRenderable(true);
|
|
249
264
|
}
|
|
250
265
|
|
|
251
266
|
/**
|
|
@@ -253,10 +268,19 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
253
268
|
*/
|
|
254
269
|
private handleRenderResult(result: TextRenderInfo): void {
|
|
255
270
|
// Host paths on top
|
|
256
|
-
const textRendererType =
|
|
271
|
+
const textRendererType = result.type;
|
|
257
272
|
let width = result.width;
|
|
258
273
|
let height = result.height;
|
|
259
274
|
|
|
275
|
+
// Handle zero-dimension case (can happen with certain text inputs or font issues)
|
|
276
|
+
if (width === 0 || height === 0) {
|
|
277
|
+
this.emit('failed', {
|
|
278
|
+
type: 'text',
|
|
279
|
+
error: new Error('Text rendering failed, width or height zero'),
|
|
280
|
+
} satisfies NodeTextFailedPayload);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
260
284
|
// Handle Canvas renderer (uses ImageData)
|
|
261
285
|
if (textRendererType === 'canvas') {
|
|
262
286
|
if (result.imageData === undefined) {
|
|
@@ -273,6 +297,9 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
273
297
|
premultiplyAlpha: true,
|
|
274
298
|
src: result.imageData as ImageData,
|
|
275
299
|
});
|
|
300
|
+
|
|
301
|
+
this.props.w = width;
|
|
302
|
+
this.props.h = height;
|
|
276
303
|
// It isn't renderable until the texture is loaded we have to set it to false here to avoid it
|
|
277
304
|
// being detected as a renderable default color node in the next frame
|
|
278
305
|
// it will be corrected once the texture is loaded
|
|
@@ -282,25 +309,31 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
282
309
|
// We do want the texture to load immediately
|
|
283
310
|
this.texture.setRenderableOwner(this._id, true);
|
|
284
311
|
}
|
|
285
|
-
}
|
|
312
|
+
} else {
|
|
313
|
+
const layout = result.layout;
|
|
314
|
+
// For SDF, we rely on the presence of a valid layout to determine renderability
|
|
315
|
+
if (layout === undefined) {
|
|
316
|
+
this.emit('failed', {
|
|
317
|
+
type: 'text',
|
|
318
|
+
error: new Error(
|
|
319
|
+
'SDF text rendering failed, no layout data returned',
|
|
320
|
+
),
|
|
321
|
+
} satisfies NodeTextFailedPayload);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
286
324
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
this.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
} satisfies NodeTextFailedPayload);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
325
|
+
this.props.w = width;
|
|
326
|
+
this.props.h = height;
|
|
327
|
+
this.setUpdateType(UpdateType.Local);
|
|
328
|
+
this.setRenderable(true);
|
|
329
|
+
this.numQuads = layout.glyphCount;
|
|
295
330
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
331
|
+
this._sdfShaderProps = {
|
|
332
|
+
size: layout.fontScale,
|
|
333
|
+
distanceRange: layout.distanceRange,
|
|
334
|
+
};
|
|
299
335
|
|
|
300
|
-
|
|
301
|
-
if (textRendererType === 'sdf') {
|
|
302
|
-
this.setRenderable(true);
|
|
303
|
-
this.setUpdateType(UpdateType.Local);
|
|
336
|
+
this.renderOpTextures = [result.atlasTexture as WebGlCtxTexture];
|
|
304
337
|
}
|
|
305
338
|
|
|
306
339
|
this._renderInfo = result;
|
|
@@ -309,6 +342,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
309
342
|
|
|
310
343
|
// Reusable bound method for emitting loaded event
|
|
311
344
|
private emitTextLoadedEvent = () => {
|
|
345
|
+
if (this._renderInfo === null) return; // Guard against unexpected null
|
|
346
|
+
|
|
312
347
|
this.emit('loaded', {
|
|
313
348
|
type: 'text',
|
|
314
349
|
dimensions: {
|
|
@@ -328,44 +363,72 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
328
363
|
return;
|
|
329
364
|
}
|
|
330
365
|
|
|
331
|
-
//
|
|
332
|
-
if (this.
|
|
333
|
-
super.renderQuads(renderer);
|
|
366
|
+
// Early return if no renderInfo
|
|
367
|
+
if (this._renderInfo === null) {
|
|
334
368
|
return;
|
|
335
369
|
}
|
|
336
370
|
|
|
337
|
-
//
|
|
338
|
-
if (
|
|
371
|
+
// Canvas renderer: use standard texture rendering via CoreNode
|
|
372
|
+
if (this._renderInfo.type === 'canvas') {
|
|
373
|
+
super.renderQuads(renderer);
|
|
339
374
|
return;
|
|
340
375
|
}
|
|
341
376
|
|
|
342
|
-
if (this.
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
377
|
+
if (this._sdfBuffer === null) {
|
|
378
|
+
const glw = (this.stage.renderer as WebGlRenderer).glw;
|
|
379
|
+
this._sdfBuffer = glw.createBuffer();
|
|
380
|
+
if (this._sdfBuffer === null) {
|
|
381
|
+
console.error('Failed to create WebGL buffer for SDF text rendering');
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
glw.arrayBufferData(
|
|
385
|
+
this._sdfBuffer,
|
|
386
|
+
this._renderInfo.layout.vertexBuffer,
|
|
387
|
+
glw.STATIC_DRAW,
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
this._sdfQuadCollection = new BufferCollection([
|
|
391
|
+
{
|
|
392
|
+
buffer: this._sdfBuffer,
|
|
393
|
+
attributes: {
|
|
394
|
+
a_position: {
|
|
395
|
+
name: 'a_position',
|
|
396
|
+
size: 2,
|
|
397
|
+
type: glw.FLOAT as number,
|
|
398
|
+
normalized: false,
|
|
399
|
+
stride: 4 * Float32Array.BYTES_PER_ELEMENT,
|
|
400
|
+
offset: 0,
|
|
401
|
+
},
|
|
402
|
+
a_textureCoords: {
|
|
403
|
+
name: 'a_textureCoords',
|
|
404
|
+
size: 2,
|
|
405
|
+
type: glw.FLOAT as number,
|
|
406
|
+
normalized: false,
|
|
407
|
+
stride: 4 * Float32Array.BYTES_PER_ELEMENT,
|
|
408
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT,
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
]);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
this.sdfShaderProps!.transform = this.globalTransform!.getFloatArr();
|
|
416
|
+
this.sdfShaderProps!.color = mergeColorAlpha(
|
|
417
|
+
this.props.color,
|
|
418
|
+
this.worldAlpha,
|
|
368
419
|
);
|
|
420
|
+
|
|
421
|
+
this.textRenderer.renderQuads(this);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
override updateRenderState(renderState: CoreNodeRenderState): void {
|
|
425
|
+
super.updateRenderState(renderState);
|
|
426
|
+
if (
|
|
427
|
+
this._renderInfo !== null &&
|
|
428
|
+
renderState === CoreNodeRenderState.OutOfBounds
|
|
429
|
+
) {
|
|
430
|
+
this.releaseSdfBuffer();
|
|
431
|
+
}
|
|
369
432
|
}
|
|
370
433
|
|
|
371
434
|
override destroy(): void {
|
|
@@ -374,8 +437,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
374
437
|
}
|
|
375
438
|
|
|
376
439
|
// Clear cached layout and vertex buffer
|
|
377
|
-
this.
|
|
378
|
-
this.
|
|
440
|
+
this._renderInfo = null;
|
|
441
|
+
this.releaseSdfBuffer(); // Delete the cached WebGLBuffer before losing stage ref
|
|
379
442
|
|
|
380
443
|
this.fontHandler = null!; // Clear reference to avoid memory leaks
|
|
381
444
|
this.textRenderer = null!; // Clear reference to avoid memory leaks
|
|
@@ -383,6 +446,70 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
383
446
|
super.destroy();
|
|
384
447
|
}
|
|
385
448
|
|
|
449
|
+
/**
|
|
450
|
+
* used in webgl SDF shader to get the quad buffer collection for rendering text quads
|
|
451
|
+
*/
|
|
452
|
+
override get quadBufferCollection(): BufferCollection {
|
|
453
|
+
return this._sdfQuadCollection || super.quadBufferCollection;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* used in webgl SDF shader to get the SDF shader props for rendering text quads
|
|
458
|
+
*/
|
|
459
|
+
get sdfShaderProps(): SdfShaderProps {
|
|
460
|
+
return this._sdfShaderProps as SdfShaderProps;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
override get isSdfRenderOp(): boolean {
|
|
464
|
+
return this.textRenderer.type === 'sdf';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
override draw(renderer: WebGlRenderer) {
|
|
468
|
+
if (this.textRenderer.type === 'canvas') {
|
|
469
|
+
super.draw(renderer);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const { glw, stage } = renderer;
|
|
474
|
+
const canvas = stage.platform!.canvas!;
|
|
475
|
+
const shader = this.props.shader as any;
|
|
476
|
+
|
|
477
|
+
stage.shManager.useShader(shader.program);
|
|
478
|
+
shader.program.bindRenderOp(this);
|
|
479
|
+
|
|
480
|
+
const clippingRect = this.clippingRect;
|
|
481
|
+
|
|
482
|
+
// Clipping
|
|
483
|
+
if (clippingRect.valid === true) {
|
|
484
|
+
const pixelRatio = this.parentHasRenderTexture ? 1 : stage.pixelRatio;
|
|
485
|
+
|
|
486
|
+
const clipX = Math.round(clippingRect.x * pixelRatio);
|
|
487
|
+
const clipWidth = Math.round(clippingRect.w * pixelRatio);
|
|
488
|
+
const clipHeight = Math.round(clippingRect.h * pixelRatio);
|
|
489
|
+
let clipY = Math.round(
|
|
490
|
+
canvas.height - clipHeight - clippingRect.y * pixelRatio,
|
|
491
|
+
);
|
|
492
|
+
// if parent has render texture, we need to adjust the scissor rect
|
|
493
|
+
// to be relative to the parent's framebuffer
|
|
494
|
+
if (this.parentHasRenderTexture) {
|
|
495
|
+
const parentFramebufferDimensions = this.parentFramebufferDimensions;
|
|
496
|
+
clipY =
|
|
497
|
+
parentFramebufferDimensions !== null
|
|
498
|
+
? parentFramebufferDimensions.h - this.props.h
|
|
499
|
+
: 0;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
glw.setScissorTest(true);
|
|
503
|
+
glw.scissor(clipX, clipY, clipWidth, clipHeight);
|
|
504
|
+
} else {
|
|
505
|
+
glw.setScissorTest(false);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// SDF rendering uses drawArrays with explicit triangle vertices (6 vertices per quad)
|
|
509
|
+
// Note: buffers should be bound by bindRenderOp -> bindBufferCollection
|
|
510
|
+
glw.drawArrays(glw.TRIANGLES, 0, 6 * this.numQuads);
|
|
511
|
+
}
|
|
512
|
+
|
|
386
513
|
override set w(value: number) {
|
|
387
514
|
this.maxWidth = value;
|
|
388
515
|
}
|
|
@@ -601,7 +728,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
601
728
|
}
|
|
602
729
|
}
|
|
603
730
|
|
|
604
|
-
get renderInfo(): TextRenderInfo {
|
|
731
|
+
get renderInfo(): TextRenderInfo | null {
|
|
605
732
|
return this._renderInfo;
|
|
606
733
|
}
|
|
607
734
|
}
|
|
@@ -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
|
}
|