@lightningjs/renderer 3.0.2 → 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/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/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
|
@@ -474,28 +474,27 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
474
474
|
private insertRTTNodeInOrder(node: CoreNode) {
|
|
475
475
|
let insertIndex = this.rttNodes.length; // Default to the end of the array
|
|
476
476
|
|
|
477
|
+
// Build a one-shot index map so all lookups below are O(1) instead of O(n).
|
|
478
|
+
const rttNodes = this.rttNodes;
|
|
479
|
+
const indexMap = new Map<number, number>();
|
|
480
|
+
for (let i = 0; i < rttNodes.length; i++) {
|
|
481
|
+
indexMap.set(rttNodes[i]!.id, i);
|
|
482
|
+
}
|
|
483
|
+
|
|
477
484
|
// 1. Traverse upwards to ensure the node is placed before its RTT parent (if any).
|
|
478
485
|
let currentNode: CoreNode = node;
|
|
479
|
-
while (currentNode) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const parentIndex = this.rttNodes.indexOf(currentNode.parent);
|
|
485
|
-
if (parentIndex !== -1) {
|
|
486
|
-
// Found an RTT parent in the list; set insertIndex to place node before the parent
|
|
486
|
+
while (currentNode.parent !== null) {
|
|
487
|
+
const parentIndex = indexMap.get(currentNode.parent.id);
|
|
488
|
+
if (parentIndex !== undefined) {
|
|
487
489
|
insertIndex = parentIndex;
|
|
488
490
|
break;
|
|
489
491
|
}
|
|
490
|
-
|
|
491
492
|
currentNode = currentNode.parent;
|
|
492
493
|
}
|
|
493
494
|
|
|
494
495
|
// 2. Traverse downwards to ensure the node is placed after any RTT children.
|
|
495
|
-
|
|
496
|
-
const maxChildIndex = this.findMaxChildRTTIndex(node);
|
|
496
|
+
const maxChildIndex = this.findMaxChildRTTIndex(node, indexMap);
|
|
497
497
|
if (maxChildIndex !== -1) {
|
|
498
|
-
// Adjust insertIndex to be after the last child RTT node
|
|
499
498
|
insertIndex = Math.max(insertIndex, maxChildIndex + 1);
|
|
500
499
|
}
|
|
501
500
|
|
|
@@ -503,25 +502,25 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
503
502
|
this.rttNodes.splice(insertIndex, 0, node);
|
|
504
503
|
}
|
|
505
504
|
|
|
506
|
-
//
|
|
507
|
-
private findMaxChildRTTIndex(
|
|
505
|
+
// Iterative DFS to find the highest rttNodes index among all RTT descendants of node.
|
|
506
|
+
private findMaxChildRTTIndex(
|
|
507
|
+
node: CoreNode,
|
|
508
|
+
indexMap: Map<number, number>,
|
|
509
|
+
): number {
|
|
508
510
|
let maxIndex = -1;
|
|
509
|
-
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
511
|
+
// Explicit stack avoids recursive arrow function allocation and call-stack growth.
|
|
512
|
+
const stack: CoreNode[] = [node];
|
|
513
|
+
while (stack.length !== 0) {
|
|
514
|
+
const current = stack.pop()!;
|
|
515
|
+
const idx = indexMap.get(current.id);
|
|
516
|
+
if (idx !== undefined && idx > maxIndex) {
|
|
517
|
+
maxIndex = idx;
|
|
514
518
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
traverseChildren(child);
|
|
519
|
+
const children = current.children;
|
|
520
|
+
for (let i = 0; i < children.length; i++) {
|
|
521
|
+
stack.push(children[i]!);
|
|
519
522
|
}
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Start traversal directly with the provided node
|
|
523
|
-
traverseChildren(node);
|
|
524
|
-
|
|
523
|
+
}
|
|
525
524
|
return maxIndex;
|
|
526
525
|
}
|
|
527
526
|
|
|
@@ -724,4 +723,15 @@ export class WebGlRenderer extends CoreRenderer {
|
|
|
724
723
|
normalized: normalizedColor,
|
|
725
724
|
};
|
|
726
725
|
}
|
|
726
|
+
|
|
727
|
+
override destroy(): void {
|
|
728
|
+
const loseCtx = this.glw.getExtension(
|
|
729
|
+
'WEBGL_lose_context',
|
|
730
|
+
) as WEBGL_lose_context | null;
|
|
731
|
+
loseCtx?.loseContext();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
override deleteBuffer(buffer: WebGLBuffer): void {
|
|
735
|
+
this.glw.deleteBuffer(buffer);
|
|
736
|
+
}
|
|
727
737
|
}
|
|
@@ -22,7 +22,7 @@ import type { Stage } from '../Stage.js';
|
|
|
22
22
|
import type { TextLineStruct, TextRenderInfo } from './TextRenderer.js';
|
|
23
23
|
import * as CanvasFontHandler from './CanvasFontHandler.js';
|
|
24
24
|
import type { CoreTextNodeProps } from '../CoreTextNode.js';
|
|
25
|
-
import { hasZeroWidthSpace } from './Utils.js';
|
|
25
|
+
import { getLayoutCacheKey, hasZeroWidthSpace } from './Utils.js';
|
|
26
26
|
import { mapTextLayout } from './TextLayoutEngine.js';
|
|
27
27
|
|
|
28
28
|
const MAX_TEXTURE_DIMENSION = 4096;
|
|
@@ -43,16 +43,7 @@ let measureContext:
|
|
|
43
43
|
| null = null;
|
|
44
44
|
|
|
45
45
|
// Cache for text layout calculations
|
|
46
|
-
const layoutCache = new Map<
|
|
47
|
-
string,
|
|
48
|
-
{
|
|
49
|
-
lines: string[];
|
|
50
|
-
lineWidths: number[];
|
|
51
|
-
maxLineWidth: number;
|
|
52
|
-
remainingText: string;
|
|
53
|
-
moreTextLines: boolean;
|
|
54
|
-
}
|
|
55
|
-
>();
|
|
46
|
+
const layoutCache = new Map<string, TextRenderInfo>();
|
|
56
47
|
|
|
57
48
|
// Initialize the Text Renderer
|
|
58
49
|
const init = (stage: Stage): void => {
|
|
@@ -96,6 +87,12 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
96
87
|
assertTruthy(canvas, 'Canvas is not initialized');
|
|
97
88
|
assertTruthy(context, 'Canvas context is not available');
|
|
98
89
|
assertTruthy(measureContext, 'Canvas measureContext is not available');
|
|
90
|
+
const cacheKey = getLayoutCacheKey(props);
|
|
91
|
+
|
|
92
|
+
let layout = layoutCache.get(cacheKey);
|
|
93
|
+
if (layout !== undefined) {
|
|
94
|
+
return layout;
|
|
95
|
+
}
|
|
99
96
|
// Extract already normalized properties
|
|
100
97
|
const {
|
|
101
98
|
text,
|
|
@@ -189,48 +186,24 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
189
186
|
if (canvas.width > 0 && canvas.height > 0) {
|
|
190
187
|
imageData = context.getImageData(0, 0, canvasW, canvasH);
|
|
191
188
|
}
|
|
192
|
-
|
|
189
|
+
const renderInfo = {
|
|
193
190
|
imageData,
|
|
194
191
|
width: effectiveWidth,
|
|
195
192
|
height: effectiveHeight,
|
|
196
193
|
remainingLines,
|
|
197
194
|
hasRemainingText,
|
|
198
195
|
};
|
|
196
|
+
layoutCache.set(cacheKey, renderInfo);
|
|
197
|
+
return renderInfo;
|
|
199
198
|
};
|
|
200
199
|
|
|
201
|
-
/**
|
|
202
|
-
* Generate a cache key for text layout calculations
|
|
203
|
-
*/
|
|
204
|
-
function generateLayoutCacheKey(
|
|
205
|
-
text: string,
|
|
206
|
-
fontFamily: string,
|
|
207
|
-
fontSize: number,
|
|
208
|
-
fontStyle: string,
|
|
209
|
-
wordWrap: boolean,
|
|
210
|
-
wordWrapWidth: number,
|
|
211
|
-
letterSpacing: number,
|
|
212
|
-
maxLines: number,
|
|
213
|
-
overflowSuffix: string,
|
|
214
|
-
): string {
|
|
215
|
-
return `${text}-${fontFamily}-${fontSize}-${fontStyle}-${wordWrap}-${wordWrapWidth}-${letterSpacing}-${maxLines}-${overflowSuffix}`;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
200
|
/**
|
|
219
201
|
* Clear layout cache for memory management
|
|
220
202
|
*/
|
|
221
|
-
const
|
|
203
|
+
const clearCache = (): void => {
|
|
222
204
|
layoutCache.clear();
|
|
223
205
|
};
|
|
224
206
|
|
|
225
|
-
/**
|
|
226
|
-
* Add quads for rendering (Canvas doesn't use quads)
|
|
227
|
-
*/
|
|
228
|
-
const addQuads = (): Float32Array | null => {
|
|
229
|
-
// Canvas renderer doesn't use quad-based rendering
|
|
230
|
-
// Return null for interface compatibility
|
|
231
|
-
return null;
|
|
232
|
-
};
|
|
233
|
-
|
|
234
207
|
/**
|
|
235
208
|
* Render quads for Canvas renderer (Canvas doesn't use quad-based rendering)
|
|
236
209
|
*/
|
|
@@ -246,10 +219,9 @@ const CanvasTextRenderer = {
|
|
|
246
219
|
type,
|
|
247
220
|
font: CanvasFontHandler,
|
|
248
221
|
renderText,
|
|
249
|
-
addQuads,
|
|
250
222
|
renderQuads,
|
|
251
223
|
init,
|
|
252
|
-
|
|
224
|
+
clearCache,
|
|
253
225
|
};
|
|
254
226
|
|
|
255
227
|
export default CanvasTextRenderer;
|
|
@@ -25,7 +25,7 @@ import type {
|
|
|
25
25
|
TextRenderProps,
|
|
26
26
|
} from './TextRenderer.js';
|
|
27
27
|
import type { CoreTextNodeProps } from '../CoreTextNode.js';
|
|
28
|
-
import { hasZeroWidthSpace } from './Utils.js';
|
|
28
|
+
import { getLayoutCacheKey, hasZeroWidthSpace } from './Utils.js';
|
|
29
29
|
import * as SdfFontHandler from './SdfFontHandler.js';
|
|
30
30
|
import type { CoreRenderer } from '../renderers/CoreRenderer.js';
|
|
31
31
|
import { WebGlRenderer } from '../renderers/webgl/WebGlRenderer.js';
|
|
@@ -35,7 +35,7 @@ import { BufferCollection } from '../renderers/webgl/internal/BufferCollection.j
|
|
|
35
35
|
import type { WebGlCtxTexture } from '../renderers/webgl/WebGlCtxTexture.js';
|
|
36
36
|
import type { WebGlShaderNode } from '../renderers/webgl/WebGlShaderNode.js';
|
|
37
37
|
import { mergeColorAlpha } from '../../utils.js';
|
|
38
|
-
import type { TextLayout
|
|
38
|
+
import type { TextLayout } from './TextRenderer.js';
|
|
39
39
|
import { mapTextLayout } from './TextLayoutEngine.js';
|
|
40
40
|
|
|
41
41
|
// Each glyph requires 6 vertices (2 triangles) with 4 floats each (x, y, u, v)
|
|
@@ -57,6 +57,7 @@ const init = (stage: Stage): void => {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
const font: FontHandler = SdfFontHandler;
|
|
60
|
+
const layoutCache = new Map<string, TextLayout>();
|
|
60
61
|
|
|
61
62
|
/**
|
|
62
63
|
* SDF text renderer using MSDF/SDF fonts with WebGL
|
|
@@ -74,6 +75,19 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
77
|
|
|
78
|
+
const cacheKey = getLayoutCacheKey(props);
|
|
79
|
+
|
|
80
|
+
let layout = layoutCache.get(cacheKey);
|
|
81
|
+
if (layout !== undefined) {
|
|
82
|
+
return {
|
|
83
|
+
width: layout.width,
|
|
84
|
+
height: layout.height,
|
|
85
|
+
remainingLines: layout.remainingLines,
|
|
86
|
+
hasRemainingText: layout.hasRemainingText,
|
|
87
|
+
layout, // Cache layout for addQuads
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
// Get font cache for this font family
|
|
78
92
|
const fontData = SdfFontHandler.getFontData(props.fontFamily);
|
|
79
93
|
if (fontData === undefined) {
|
|
@@ -85,99 +99,19 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => {
|
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
// Calculate text layout and generate glyph data for caching
|
|
88
|
-
|
|
102
|
+
layout = generateTextLayout(props, fontData);
|
|
103
|
+
layoutCache.set(cacheKey, layout);
|
|
89
104
|
|
|
90
105
|
// For SDF renderer, ImageData is null since we render via WebGL
|
|
91
106
|
return {
|
|
92
|
-
remainingLines: 0,
|
|
93
|
-
hasRemainingText: false,
|
|
94
107
|
width: layout.width,
|
|
95
108
|
height: layout.height,
|
|
109
|
+
remainingLines: layout.remainingLines,
|
|
110
|
+
hasRemainingText: layout.hasRemainingText,
|
|
96
111
|
layout, // Cache layout for addQuads
|
|
97
112
|
};
|
|
98
113
|
};
|
|
99
114
|
|
|
100
|
-
/**
|
|
101
|
-
* Add quads for rendering using cached layout data
|
|
102
|
-
*/
|
|
103
|
-
const addQuads = (layout?: TextLayout): Float32Array | null => {
|
|
104
|
-
if (layout === undefined) {
|
|
105
|
-
return null; // No layout data available
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const glyphs = layout.glyphs;
|
|
109
|
-
const glyphsLength = glyphs.length;
|
|
110
|
-
|
|
111
|
-
if (glyphsLength === 0) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const vertexBuffer = new Float32Array(
|
|
116
|
-
glyphsLength * VERTICES_PER_GLYPH * FLOATS_PER_VERTEX,
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
let bufferIndex = 0;
|
|
120
|
-
let glyphIndex = 0;
|
|
121
|
-
|
|
122
|
-
while (glyphIndex < glyphsLength) {
|
|
123
|
-
const glyph = glyphs[glyphIndex];
|
|
124
|
-
glyphIndex++;
|
|
125
|
-
if (glyph === undefined) {
|
|
126
|
-
continue;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const x1 = glyph.x;
|
|
130
|
-
const y1 = glyph.y;
|
|
131
|
-
const x2 = x1 + glyph.width;
|
|
132
|
-
const y2 = y1 + glyph.height;
|
|
133
|
-
|
|
134
|
-
const u1 = glyph.atlasX;
|
|
135
|
-
const v1 = glyph.atlasY;
|
|
136
|
-
const u2 = u1 + glyph.atlasWidth;
|
|
137
|
-
const v2 = v1 + glyph.atlasHeight;
|
|
138
|
-
|
|
139
|
-
// Triangle 1: Top-left, top-right, bottom-left
|
|
140
|
-
// Vertex 1: Top-left
|
|
141
|
-
vertexBuffer[bufferIndex++] = x1;
|
|
142
|
-
vertexBuffer[bufferIndex++] = y1;
|
|
143
|
-
vertexBuffer[bufferIndex++] = u1;
|
|
144
|
-
vertexBuffer[bufferIndex++] = v1;
|
|
145
|
-
|
|
146
|
-
// Vertex 2: Top-right
|
|
147
|
-
vertexBuffer[bufferIndex++] = x2;
|
|
148
|
-
vertexBuffer[bufferIndex++] = y1;
|
|
149
|
-
vertexBuffer[bufferIndex++] = u2;
|
|
150
|
-
vertexBuffer[bufferIndex++] = v1;
|
|
151
|
-
|
|
152
|
-
// Vertex 3: Bottom-left
|
|
153
|
-
vertexBuffer[bufferIndex++] = x1;
|
|
154
|
-
vertexBuffer[bufferIndex++] = y2;
|
|
155
|
-
vertexBuffer[bufferIndex++] = u1;
|
|
156
|
-
vertexBuffer[bufferIndex++] = v2;
|
|
157
|
-
|
|
158
|
-
// Triangle 2: Top-right, bottom-right, bottom-left
|
|
159
|
-
// Vertex 4: Top-right (duplicate)
|
|
160
|
-
vertexBuffer[bufferIndex++] = x2;
|
|
161
|
-
vertexBuffer[bufferIndex++] = y1;
|
|
162
|
-
vertexBuffer[bufferIndex++] = u2;
|
|
163
|
-
vertexBuffer[bufferIndex++] = v1;
|
|
164
|
-
|
|
165
|
-
// Vertex 5: Bottom-right
|
|
166
|
-
vertexBuffer[bufferIndex++] = x2;
|
|
167
|
-
vertexBuffer[bufferIndex++] = y2;
|
|
168
|
-
vertexBuffer[bufferIndex++] = u2;
|
|
169
|
-
vertexBuffer[bufferIndex++] = v2;
|
|
170
|
-
|
|
171
|
-
// Vertex 6: Bottom-left (duplicate)
|
|
172
|
-
vertexBuffer[bufferIndex++] = x1;
|
|
173
|
-
vertexBuffer[bufferIndex++] = y2;
|
|
174
|
-
vertexBuffer[bufferIndex++] = u1;
|
|
175
|
-
vertexBuffer[bufferIndex++] = v2;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return vertexBuffer;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
115
|
/**
|
|
182
116
|
* Create and submit WebGL render operations for SDF text
|
|
183
117
|
* This is called from CoreTextNode during rendering to add SDF text to the render pipeline
|
|
@@ -185,13 +119,13 @@ const addQuads = (layout?: TextLayout): Float32Array | null => {
|
|
|
185
119
|
const renderQuads = (
|
|
186
120
|
renderer: CoreRenderer,
|
|
187
121
|
layout: TextLayout,
|
|
188
|
-
vertexBuffer: Float32Array,
|
|
189
122
|
renderProps: TextRenderProps,
|
|
190
123
|
): void => {
|
|
191
124
|
const fontFamily = renderProps.fontFamily;
|
|
192
125
|
const color = renderProps.color;
|
|
193
126
|
const worldAlpha = renderProps.worldAlpha;
|
|
194
127
|
const globalTransform = renderProps.globalTransform;
|
|
128
|
+
const vertexBuffer = layout.vertexBuffer;
|
|
195
129
|
|
|
196
130
|
const atlasTexture = SdfFontHandler.getAtlas(fontFamily);
|
|
197
131
|
if (atlasTexture === null) {
|
|
@@ -202,68 +136,82 @@ const renderQuads = (
|
|
|
202
136
|
// We can safely assume this is a WebGL renderer else this wouldn't be called
|
|
203
137
|
const glw = (renderer as WebGlRenderer).glw;
|
|
204
138
|
const stride = 4 * Float32Array.BYTES_PER_ELEMENT;
|
|
205
|
-
const webGlBuffer = glw.createBuffer();
|
|
206
|
-
|
|
207
|
-
if (!webGlBuffer) {
|
|
208
|
-
console.warn('Failed to create WebGL buffer for SDF text');
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
139
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Wraps a WebGLBuffer in a BufferCollection with the standard SDF vertex
|
|
142
|
+
* layout, creates and submits an SdfRenderOp. Called by both the cache-miss
|
|
143
|
+
* and cache-hit paths so attribute/op setup only exists in one place.
|
|
144
|
+
*/
|
|
145
|
+
const buildAndSubmitRenderOp = (gpuBuffer: WebGLBuffer): void => {
|
|
146
|
+
const webGlBuffers = new BufferCollection([
|
|
147
|
+
{
|
|
148
|
+
buffer: gpuBuffer,
|
|
149
|
+
attributes: {
|
|
150
|
+
a_position: {
|
|
151
|
+
name: 'a_position',
|
|
152
|
+
size: 2,
|
|
153
|
+
type: glw.FLOAT as number,
|
|
154
|
+
normalized: false,
|
|
155
|
+
stride,
|
|
156
|
+
offset: 0,
|
|
157
|
+
},
|
|
158
|
+
a_textureCoords: {
|
|
159
|
+
name: 'a_textureCoords',
|
|
160
|
+
size: 2,
|
|
161
|
+
type: glw.FLOAT as number,
|
|
162
|
+
normalized: false,
|
|
163
|
+
stride,
|
|
164
|
+
offset: 2 * Float32Array.BYTES_PER_ELEMENT,
|
|
165
|
+
},
|
|
231
166
|
},
|
|
232
167
|
},
|
|
233
|
-
|
|
234
|
-
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
const renderOp = new SdfRenderOp(
|
|
171
|
+
renderer as WebGlRenderer,
|
|
172
|
+
sdfShader!,
|
|
173
|
+
{
|
|
174
|
+
transform: globalTransform,
|
|
175
|
+
color: mergeColorAlpha(color, worldAlpha),
|
|
176
|
+
size: layout.fontScale,
|
|
177
|
+
distanceRange: layout.distanceRange,
|
|
178
|
+
} satisfies SdfShaderProps,
|
|
179
|
+
webGlBuffers,
|
|
180
|
+
worldAlpha,
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
182
|
+
renderProps.clippingRect as any,
|
|
183
|
+
layout.width,
|
|
184
|
+
layout.height,
|
|
185
|
+
false,
|
|
186
|
+
renderProps.parentHasRenderTexture,
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
188
|
+
renderProps.framebufferDimensions as any,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
renderOp.addTexture(atlasTexture.ctxTexture as WebGlCtxTexture);
|
|
192
|
+
renderOp.numQuads = layout.glyphCount;
|
|
193
|
+
(renderer as WebGlRenderer).addRenderOp(renderOp);
|
|
194
|
+
};
|
|
235
195
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
}
|
|
196
|
+
// Reuse the cached WebGLBuffer if one was provided — avoids a createBuffer
|
|
197
|
+
// call every frame on nodes whose text has not changed.
|
|
198
|
+
const glBufferRefBox = renderProps.glBufferRef;
|
|
240
199
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
{
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
renderProps.clippingRect as any,
|
|
254
|
-
layout.width,
|
|
255
|
-
layout.height,
|
|
256
|
-
false,
|
|
257
|
-
renderProps.parentHasRenderTexture,
|
|
258
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
259
|
-
renderProps.framebufferDimensions as any,
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
// Add atlas texture and set quad count
|
|
263
|
-
renderOp.addTexture(atlasTexture.ctxTexture as WebGlCtxTexture);
|
|
264
|
-
renderOp.numQuads = layout.glyphs.length;
|
|
200
|
+
if (glBufferRefBox.current === null) {
|
|
201
|
+
// Cache miss: allocate a new buffer, upload vertex data, then cache it.
|
|
202
|
+
const newBuffer = glw.createBuffer() as WebGLBuffer | null;
|
|
203
|
+
if (newBuffer === null) {
|
|
204
|
+
console.warn('Failed to create WebGL buffer for SDF text');
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// Upload vertex data directly into the new buffer.
|
|
208
|
+
glw.arrayBufferData(newBuffer, vertexBuffer, glw.STATIC_DRAW as number);
|
|
209
|
+
// Write back into the ref box so the owning node can reuse it next frame.
|
|
210
|
+
glBufferRefBox.current = newBuffer;
|
|
211
|
+
}
|
|
265
212
|
|
|
266
|
-
(
|
|
213
|
+
// Cache hit (or freshly allocated): build the render op and submit.
|
|
214
|
+
buildAndSubmitRenderOp(glBufferRefBox.current);
|
|
267
215
|
};
|
|
268
216
|
|
|
269
217
|
/**
|
|
@@ -315,8 +263,27 @@ const generateTextLayout = (
|
|
|
315
263
|
);
|
|
316
264
|
|
|
317
265
|
const lineAmount = lines.length;
|
|
266
|
+
let bufferIndex = 0;
|
|
267
|
+
let glyphCount = 0;
|
|
268
|
+
// Count total glyphs (excluding spaces) for buffer allocation
|
|
269
|
+
for (let i = 0; i < lineAmount; i++) {
|
|
270
|
+
const textLine = (lines[i] as TextLineStruct)[0];
|
|
271
|
+
for (const char of textLine) {
|
|
272
|
+
if (hasZeroWidthSpace(char) === true) {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const codepoint = char.codePointAt(0);
|
|
276
|
+
if (codepoint === undefined) {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
glyphCount++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const vertexBuffer = new Float32Array(
|
|
284
|
+
glyphCount * VERTICES_PER_GLYPH * FLOATS_PER_VERTEX,
|
|
285
|
+
);
|
|
318
286
|
|
|
319
|
-
const glyphs: GlyphLayout[] = [];
|
|
320
287
|
let currentX = 0;
|
|
321
288
|
let currentY = 0;
|
|
322
289
|
for (let i = 0; i < lineAmount; i++) {
|
|
@@ -351,23 +318,52 @@ const generateTextLayout = (
|
|
|
351
318
|
// Apply pair kerning before placing this glyph.
|
|
352
319
|
currentX += kerning;
|
|
353
320
|
|
|
354
|
-
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
321
|
+
const x1 = currentX + glyph.xoffset;
|
|
322
|
+
const y1 = currentY + glyph.yoffset;
|
|
323
|
+
const x2 = x1 + glyph.width;
|
|
324
|
+
const y2 = y1 + glyph.height;
|
|
325
|
+
const u1 = glyph.x / atlasWidth;
|
|
326
|
+
const v1 = glyph.y / atlasHeight;
|
|
327
|
+
const u2 = u1 + glyph.width / atlasWidth;
|
|
328
|
+
const v2 = v1 + glyph.height / atlasHeight;
|
|
329
|
+
|
|
330
|
+
// Triangle 1: Top-left, top-right, bottom-left
|
|
331
|
+
// Vertex 1: Top-left
|
|
332
|
+
vertexBuffer[bufferIndex++] = x1;
|
|
333
|
+
vertexBuffer[bufferIndex++] = y1;
|
|
334
|
+
vertexBuffer[bufferIndex++] = u1;
|
|
335
|
+
vertexBuffer[bufferIndex++] = v1;
|
|
336
|
+
|
|
337
|
+
// Vertex 2: Top-right
|
|
338
|
+
vertexBuffer[bufferIndex++] = x2;
|
|
339
|
+
vertexBuffer[bufferIndex++] = y1;
|
|
340
|
+
vertexBuffer[bufferIndex++] = u2;
|
|
341
|
+
vertexBuffer[bufferIndex++] = v1;
|
|
342
|
+
|
|
343
|
+
// Vertex 3: Bottom-left
|
|
344
|
+
vertexBuffer[bufferIndex++] = x1;
|
|
345
|
+
vertexBuffer[bufferIndex++] = y2;
|
|
346
|
+
vertexBuffer[bufferIndex++] = u1;
|
|
347
|
+
vertexBuffer[bufferIndex++] = v2;
|
|
348
|
+
|
|
349
|
+
// Triangle 2: Top-right, bottom-right, bottom-left
|
|
350
|
+
// Vertex 4: Top-right (duplicate)
|
|
351
|
+
vertexBuffer[bufferIndex++] = x2;
|
|
352
|
+
vertexBuffer[bufferIndex++] = y1;
|
|
353
|
+
vertexBuffer[bufferIndex++] = u2;
|
|
354
|
+
vertexBuffer[bufferIndex++] = v1;
|
|
355
|
+
|
|
356
|
+
// Vertex 5: Bottom-right
|
|
357
|
+
vertexBuffer[bufferIndex++] = x2;
|
|
358
|
+
vertexBuffer[bufferIndex++] = y2;
|
|
359
|
+
vertexBuffer[bufferIndex++] = u2;
|
|
360
|
+
vertexBuffer[bufferIndex++] = v2;
|
|
361
|
+
|
|
362
|
+
// Vertex 6: Bottom-left (duplicate)
|
|
363
|
+
vertexBuffer[bufferIndex++] = x1;
|
|
364
|
+
vertexBuffer[bufferIndex++] = y2;
|
|
365
|
+
vertexBuffer[bufferIndex++] = u1;
|
|
366
|
+
vertexBuffer[bufferIndex++] = v2;
|
|
371
367
|
|
|
372
368
|
// Advance position with letter spacing (in design units)
|
|
373
369
|
currentX += glyph.xadvance + letterSpacing;
|
|
@@ -378,16 +374,23 @@ const generateTextLayout = (
|
|
|
378
374
|
|
|
379
375
|
// Convert final dimensions to pixel space for the layout
|
|
380
376
|
return {
|
|
381
|
-
|
|
377
|
+
vertexBuffer,
|
|
378
|
+
glyphCount,
|
|
382
379
|
distanceRange: fontScale * fontData.distanceField.distanceRange,
|
|
383
380
|
width: effectiveWidth * fontScale,
|
|
384
381
|
height: effectiveHeight,
|
|
385
382
|
fontScale: fontScale,
|
|
386
383
|
lineHeight: lineHeightPx,
|
|
387
384
|
fontFamily,
|
|
385
|
+
remainingLines,
|
|
386
|
+
hasRemainingText,
|
|
388
387
|
};
|
|
389
388
|
};
|
|
390
389
|
|
|
390
|
+
const clearCache = (): void => {
|
|
391
|
+
layoutCache.clear();
|
|
392
|
+
};
|
|
393
|
+
|
|
391
394
|
/**
|
|
392
395
|
* SDF Text Renderer - implements TextRenderer interface
|
|
393
396
|
*/
|
|
@@ -395,9 +398,9 @@ const SdfTextRenderer = {
|
|
|
395
398
|
type,
|
|
396
399
|
font,
|
|
397
400
|
renderText,
|
|
398
|
-
addQuads,
|
|
399
401
|
renderQuads,
|
|
400
402
|
init,
|
|
403
|
+
clearCache,
|
|
401
404
|
};
|
|
402
405
|
|
|
403
406
|
export default SdfTextRenderer;
|