@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
|
@@ -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;
|
|
@@ -302,11 +302,14 @@ export const loadFont = async (
|
|
|
302
302
|
const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []);
|
|
303
303
|
// Create loading promise
|
|
304
304
|
const loadPromise = (async (): Promise<void> => {
|
|
305
|
-
// Load font JSON data
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
// Load font JSON data
|
|
306
|
+
const response = await fetch(atlasDataUrl);
|
|
307
|
+
if (!response.ok) {
|
|
308
|
+
throw new Error(`Failed to load font data: ${response.statusText}`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const fontData = (await response.json()) as SdfFontData;
|
|
312
|
+
if (!fontData || !fontData.chars) {
|
|
310
313
|
throw new Error('Invalid SDF font data format');
|
|
311
314
|
}
|
|
312
315
|
|
|
@@ -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;
|
|
@@ -254,14 +254,6 @@ export interface TrProps extends TrFontProps {
|
|
|
254
254
|
* Glyph layout information for WebGL rendering
|
|
255
255
|
*/
|
|
256
256
|
export interface GlyphLayout {
|
|
257
|
-
/**
|
|
258
|
-
* Unicode codepoint
|
|
259
|
-
*/
|
|
260
|
-
codepoint: number;
|
|
261
|
-
/**
|
|
262
|
-
* Glyph ID in the font atlas
|
|
263
|
-
*/
|
|
264
|
-
glyphId: number;
|
|
265
257
|
/**
|
|
266
258
|
* X position relative to text origin
|
|
267
259
|
*/
|
|
@@ -278,14 +270,6 @@ export interface GlyphLayout {
|
|
|
278
270
|
* Height of glyph in font units
|
|
279
271
|
*/
|
|
280
272
|
height: number;
|
|
281
|
-
/**
|
|
282
|
-
* X offset for glyph positioning
|
|
283
|
-
*/
|
|
284
|
-
xOffset: number;
|
|
285
|
-
/**
|
|
286
|
-
* Y offset for glyph positioning
|
|
287
|
-
*/
|
|
288
|
-
yOffset: number;
|
|
289
273
|
/**
|
|
290
274
|
* Atlas texture coordinates (normalized 0-1)
|
|
291
275
|
*/
|
|
@@ -300,9 +284,13 @@ export interface GlyphLayout {
|
|
|
300
284
|
*/
|
|
301
285
|
export interface TextLayout {
|
|
302
286
|
/**
|
|
303
|
-
*
|
|
287
|
+
* vertices for rendering quads in WebGL
|
|
304
288
|
*/
|
|
305
|
-
|
|
289
|
+
vertexBuffer: Float32Array;
|
|
290
|
+
/**
|
|
291
|
+
* glyph count in layout
|
|
292
|
+
*/
|
|
293
|
+
glyphCount: number;
|
|
306
294
|
/**
|
|
307
295
|
* Total text width
|
|
308
296
|
*/
|
|
@@ -327,6 +315,14 @@ export interface TextLayout {
|
|
|
327
315
|
* distanceRange used
|
|
328
316
|
*/
|
|
329
317
|
distanceRange: number;
|
|
318
|
+
/**
|
|
319
|
+
* number of lines that exceeded maxHeight and were truncated
|
|
320
|
+
*/
|
|
321
|
+
remainingLines: number;
|
|
322
|
+
/**
|
|
323
|
+
* Whether there is remaining text that exceeded maxHeight and was truncated
|
|
324
|
+
*/
|
|
325
|
+
hasRemainingText: boolean;
|
|
330
326
|
}
|
|
331
327
|
|
|
332
328
|
export interface FontLoadOptions {
|
|
@@ -379,6 +375,14 @@ export interface TextRenderProps {
|
|
|
379
375
|
parentHasRenderTexture: boolean;
|
|
380
376
|
framebufferDimensions: unknown;
|
|
381
377
|
stage: Stage;
|
|
378
|
+
/**
|
|
379
|
+
* Mutable wrapper ref used by the SDF renderer to cache the underlying
|
|
380
|
+
* WebGLBuffer across frames. The SDF renderer reads and writes the
|
|
381
|
+
* `.current` property so the node's ref box is updated in-place.
|
|
382
|
+
* CoreTextNode owns the ref and is responsible for calling
|
|
383
|
+
* deleteBuffer when the buffer is no longer needed.
|
|
384
|
+
*/
|
|
385
|
+
glBufferRef: { current: WebGLBuffer | null };
|
|
382
386
|
}
|
|
383
387
|
|
|
384
388
|
export interface TextRenderInfo {
|
|
@@ -394,15 +398,13 @@ export interface TextRenderer {
|
|
|
394
398
|
type: 'canvas' | 'sdf';
|
|
395
399
|
font: FontHandler;
|
|
396
400
|
renderText: (props: CoreTextNodeProps) => TextRenderInfo;
|
|
397
|
-
// Updated to accept layout data and return vertex buffer for performance
|
|
398
|
-
addQuads: (layout?: TextLayout) => Float32Array | null;
|
|
399
401
|
renderQuads: (
|
|
400
402
|
renderer: CoreRenderer,
|
|
401
403
|
layout: TextLayout,
|
|
402
|
-
vertexBuffer: Float32Array,
|
|
403
404
|
renderProps: TextRenderProps,
|
|
404
405
|
) => void;
|
|
405
406
|
init: (stage: Stage) => void;
|
|
407
|
+
clearCache: () => void;
|
|
406
408
|
}
|
|
407
409
|
|
|
408
410
|
/**
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* limitations under the License.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type {
|
|
20
|
+
import type { CoreTextNodeProps } from '../CoreTextNode.js';
|
|
21
21
|
|
|
22
22
|
const invisibleChars = /[\u200B\u200C\u200D\uFEFF\u00AD\u2060]/g;
|
|
23
23
|
|
|
@@ -97,3 +97,7 @@ export function tokenizeString(tokenRegex: RegExp, text: string): string[] {
|
|
|
97
97
|
final.pop();
|
|
98
98
|
return final.filter((word) => word != '');
|
|
99
99
|
}
|
|
100
|
+
|
|
101
|
+
export function getLayoutCacheKey(props: CoreTextNodeProps): string {
|
|
102
|
+
return `${props.text}-${props.fontFamily}-${props.fontSize}-${props.letterSpacing}-${props.lineHeight}-${props.maxHeight}-${props.maxWidth}-${props.textAlign}-${props.wordBreak}-${props.maxLines}-${props.overflowSuffix}`;
|
|
103
|
+
}
|