@lightningjs/renderer 3.0.3 → 3.0.5

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.
Files changed (240) hide show
  1. package/dist/src/common/EventEmitter.d.ts +7 -4
  2. package/dist/src/common/EventEmitter.js +1 -1
  3. package/dist/src/common/EventEmitter.js.map +1 -1
  4. package/dist/src/core/AutosizeManager.d.ts +29 -0
  5. package/dist/src/core/AutosizeManager.js +169 -0
  6. package/dist/src/core/AutosizeManager.js.map +1 -0
  7. package/dist/src/core/CoreNode.d.ts +2 -1
  8. package/dist/src/core/CoreNode.js +6 -3
  9. package/dist/src/core/CoreNode.js.map +1 -1
  10. package/dist/src/core/CoreTextNode.d.ts +18 -5
  11. package/dist/src/core/CoreTextNode.js +141 -59
  12. package/dist/src/core/CoreTextNode.js.map +1 -1
  13. package/dist/src/core/CoreTextNodeCanvas.d.ts +215 -0
  14. package/dist/src/core/CoreTextNodeCanvas.js +236 -0
  15. package/dist/src/core/CoreTextNodeCanvas.js.map +1 -0
  16. package/dist/src/core/Stage.js +10 -0
  17. package/dist/src/core/Stage.js.map +1 -1
  18. package/dist/src/core/TextureMemoryManager.js +4 -3
  19. package/dist/src/core/TextureMemoryManager.js.map +1 -1
  20. package/dist/src/core/animations/Animation.d.ts +21 -0
  21. package/dist/src/core/animations/Animation.js +194 -0
  22. package/dist/src/core/animations/Animation.js.map +1 -0
  23. package/dist/src/core/animations/AnimationManager.d.ts +1 -1
  24. package/dist/src/core/animations/AnimationManager.js +5 -5
  25. package/dist/src/core/animations/AnimationManager.js.map +1 -1
  26. package/dist/src/core/animations/CoreAnimation.d.ts +11 -2
  27. package/dist/src/core/animations/CoreAnimation.js +44 -38
  28. package/dist/src/core/animations/CoreAnimation.js.map +1 -1
  29. package/dist/src/core/animations/CoreAnimationController.d.ts +6 -0
  30. package/dist/src/core/animations/CoreAnimationController.js +16 -3
  31. package/dist/src/core/animations/CoreAnimationController.js.map +1 -1
  32. package/dist/src/core/animations/Playback.d.ts +64 -0
  33. package/dist/src/core/animations/Playback.js +169 -0
  34. package/dist/src/core/animations/Playback.js.map +1 -0
  35. package/dist/src/core/animations/Ticker.d.ts +71 -0
  36. package/dist/src/core/animations/Ticker.js +89 -0
  37. package/dist/src/core/animations/Ticker.js.map +1 -0
  38. package/dist/src/core/animations/Transition.d.ts +38 -0
  39. package/dist/src/core/animations/Transition.js +35 -0
  40. package/dist/src/core/animations/Transition.js.map +1 -0
  41. package/dist/src/core/animations/TransitionsController.d.ts +10 -0
  42. package/dist/src/core/animations/TransitionsController.js +39 -0
  43. package/dist/src/core/animations/TransitionsController.js.map +1 -0
  44. package/dist/src/core/animations/utils.d.ts +2 -0
  45. package/dist/src/core/animations/utils.js +136 -0
  46. package/dist/src/core/animations/utils.js.map +1 -0
  47. package/dist/src/core/lib/ImageWorker.d.ts +2 -2
  48. package/dist/src/core/lib/ImageWorker.js +11 -30
  49. package/dist/src/core/lib/ImageWorker.js.map +1 -1
  50. package/dist/src/core/lib/WebGlContextWrapper.d.ts +5 -16
  51. package/dist/src/core/lib/WebGlContextWrapper.js +1 -35
  52. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  53. package/dist/src/core/lib/textureCompression.d.ts +2 -14
  54. package/dist/src/core/lib/textureCompression.js +67 -320
  55. package/dist/src/core/lib/textureCompression.js.map +1 -1
  56. package/dist/src/core/platform.js +9 -38
  57. package/dist/src/core/platform.js.map +1 -1
  58. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.d.ts +0 -1
  59. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js +3 -11
  60. package/dist/src/core/renderers/canvas/CanvasCoreRenderer.js.map +1 -1
  61. package/dist/src/core/renderers/canvas/CanvasCoreTexture.d.ts +1 -2
  62. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js +11 -16
  63. package/dist/src/core/renderers/canvas/CanvasCoreTexture.js.map +1 -1
  64. package/dist/src/core/renderers/canvas/CanvasRenderer.js +14 -1
  65. package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
  66. package/dist/src/core/renderers/canvas/CanvasTexture.d.ts +1 -1
  67. package/dist/src/core/renderers/canvas/CanvasTexture.js +16 -6
  68. package/dist/src/core/renderers/canvas/CanvasTexture.js.map +1 -1
  69. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.d.ts +0 -13
  70. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js +192 -113
  71. package/dist/src/core/renderers/canvas/internal/C2DShaderUtils.js.map +1 -1
  72. package/dist/src/core/renderers/canvas/internal/ColorUtils.d.ts +2 -0
  73. package/dist/src/core/renderers/canvas/internal/ColorUtils.js +14 -0
  74. package/dist/src/core/renderers/canvas/internal/ColorUtils.js.map +1 -1
  75. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.d.ts +1 -2
  76. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js +5 -12
  77. package/dist/src/core/renderers/webgl/WebGlCoreCtxRenderTexture.js.map +1 -1
  78. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +6 -18
  79. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +60 -102
  80. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  81. package/dist/src/core/renderers/webgl/WebGlCoreRenderOp.js +1 -1
  82. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +1 -3
  83. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +31 -74
  84. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  85. package/dist/src/core/renderers/webgl/WebGlCoreShader.d.ts +7 -2
  86. package/dist/src/core/renderers/webgl/WebGlCoreShader.js +50 -21
  87. package/dist/src/core/renderers/webgl/WebGlCoreShader.js.map +1 -1
  88. package/dist/src/core/renderers/webgl/WebGlRenderOp.d.ts +2 -3
  89. package/dist/src/core/renderers/webgl/WebGlRenderOp.js +1 -3
  90. package/dist/src/core/renderers/webgl/WebGlRenderOp.js.map +1 -1
  91. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +2 -2
  92. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  93. package/dist/src/core/renderers/webgl/WebGlShaderNode.d.ts +1 -0
  94. package/dist/src/core/renderers/webgl/WebGlShaderNode.js +23 -0
  95. package/dist/src/core/renderers/webgl/WebGlShaderNode.js.map +1 -1
  96. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +4 -5
  97. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
  98. package/dist/src/core/renderers/webgl/internal/ShaderUtils.d.ts +1 -0
  99. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js.map +1 -1
  100. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js +6 -3
  101. package/dist/src/core/renderers/webgl/shaders/DefaultShader.js.map +1 -1
  102. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js +11 -0
  103. package/dist/src/core/renderers/webgl/shaders/DefaultShaderBatched.js.map +1 -1
  104. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js +10 -5
  105. package/dist/src/core/renderers/webgl/shaders/DynamicShader.js.map +1 -1
  106. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js +10 -5
  107. package/dist/src/core/renderers/webgl/shaders/RoundedRectangle.js.map +1 -1
  108. package/dist/src/core/renderers/webgl/shaders/SdfShader.js +12 -0
  109. package/dist/src/core/renderers/webgl/shaders/SdfShader.js.map +1 -1
  110. package/dist/src/core/renderers/webgl/shaders/effects/BorderBottomEffect.js +1 -1
  111. package/dist/src/core/renderers/webgl/shaders/effects/BorderLeftEffect.js +1 -1
  112. package/dist/src/core/renderers/webgl/shaders/effects/BorderRightEffect.js +1 -1
  113. package/dist/src/core/renderers/webgl/shaders/effects/BorderTopEffect.js +1 -1
  114. package/dist/src/core/renderers/webgl/shaders/effects/EffectUtils.js +2 -2
  115. package/dist/src/core/renderers/webgl/shaders/effects/FadeOutEffect.js +5 -5
  116. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +1 -1
  117. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.d.ts +1 -0
  118. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +30 -14
  119. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js.map +1 -1
  120. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.d.ts +0 -1
  121. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +3 -13
  122. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js.map +1 -1
  123. package/dist/src/core/renderers/webgl/shaders/effects/RadialProgressEffect.js +1 -1
  124. package/dist/src/core/renderers/webgl/shaders/effects/RadiusEffect.js +5 -5
  125. package/dist/src/core/shaders/webgl/SdfShadowShader.d.ts +9 -0
  126. package/dist/src/core/shaders/webgl/SdfShadowShader.js +100 -0
  127. package/dist/src/core/shaders/webgl/SdfShadowShader.js.map +1 -0
  128. package/dist/src/core/text-rendering/CanvasFont.d.ts +1 -1
  129. package/dist/src/core/text-rendering/CanvasFont.js +7 -16
  130. package/dist/src/core/text-rendering/CanvasFont.js.map +1 -1
  131. package/dist/src/core/text-rendering/CanvasFontHandler.d.ts +1 -1
  132. package/dist/src/core/text-rendering/CanvasFontHandler.js +1 -1
  133. package/dist/src/core/text-rendering/CanvasFontHandler.js.map +1 -1
  134. package/dist/src/core/text-rendering/CanvasTextRenderer.d.ts +2 -3
  135. package/dist/src/core/text-rendering/CanvasTextRenderer.js +7 -5
  136. package/dist/src/core/text-rendering/CanvasTextRenderer.js.map +1 -1
  137. package/dist/src/core/text-rendering/CoreFont.d.ts +1 -1
  138. package/dist/src/core/text-rendering/CoreFont.js +1 -1
  139. package/dist/src/core/text-rendering/CoreFont.js.map +1 -1
  140. package/dist/src/core/text-rendering/FontManager.js +2 -1
  141. package/dist/src/core/text-rendering/FontManager.js.map +1 -1
  142. package/dist/src/core/text-rendering/SdfTextRenderer.d.ts +3 -5
  143. package/dist/src/core/text-rendering/SdfTextRenderer.js +19 -109
  144. package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
  145. package/dist/src/core/text-rendering/TextLayoutEngine.js +43 -12
  146. package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
  147. package/dist/src/core/text-rendering/TextRenderer.d.ts +16 -8
  148. package/dist/src/core/text-rendering/canvas/Settings.d.ts +64 -0
  149. package/dist/src/core/text-rendering/canvas/Settings.js +20 -0
  150. package/dist/src/core/text-rendering/canvas/Settings.js.map +1 -0
  151. package/dist/src/core/text-rendering/canvas/Utils.d.ts +20 -0
  152. package/dist/src/core/text-rendering/canvas/Utils.js +144 -0
  153. package/dist/src/core/text-rendering/canvas/Utils.js.map +1 -0
  154. package/dist/src/core/text-rendering/canvas/calculateRenderInfo.d.ts +60 -0
  155. package/dist/src/core/text-rendering/canvas/calculateRenderInfo.js +183 -0
  156. package/dist/src/core/text-rendering/canvas/calculateRenderInfo.js.map +1 -0
  157. package/dist/src/core/text-rendering/canvas/draw.d.ts +5 -0
  158. package/dist/src/core/text-rendering/canvas/draw.js +132 -0
  159. package/dist/src/core/text-rendering/canvas/draw.js.map +1 -0
  160. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js +2 -2
  161. package/dist/src/core/text-rendering/font-face-types/SdfTrFontFace/SdfTrFontFace.js.map +1 -1
  162. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +3 -4
  163. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +46 -98
  164. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  165. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/getStartConditions.d.ts +1 -1
  166. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.d.ts +2 -2
  167. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +66 -8
  168. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  169. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/setRenderWindow.d.ts +1 -1
  170. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +1 -1
  171. package/dist/src/core/text-rendering/renderers/canvas/CanvasTextRenderer.d.ts +26 -0
  172. package/dist/src/core/text-rendering/renderers/canvas/CanvasTextRenderer.js +158 -0
  173. package/dist/src/core/text-rendering/renderers/canvas/CanvasTextRenderer.js.map +1 -0
  174. package/dist/src/core/text-rendering/renderers/canvas/internal/draw.d.ts +5 -0
  175. package/dist/src/core/text-rendering/renderers/canvas/internal/draw.js +127 -0
  176. package/dist/src/core/text-rendering/renderers/canvas/internal/draw.js.map +1 -0
  177. package/dist/src/core/text-rendering/renderers/canvas/internal/mergeDefaults.d.ts +2 -0
  178. package/dist/src/core/text-rendering/renderers/canvas/internal/mergeDefaults.js +50 -0
  179. package/dist/src/core/text-rendering/renderers/canvas/internal/mergeDefaults.js.map +1 -0
  180. package/dist/src/core/text-rendering/renderers/canvas/internal/renderInfo.d.ts +33 -0
  181. package/dist/src/core/text-rendering/renderers/canvas/internal/renderInfo.js +192 -0
  182. package/dist/src/core/text-rendering/renderers/canvas/internal/renderInfo.js.map +1 -0
  183. package/dist/src/core/text-rendering/renderers/canvas/internal/types.d.ts +66 -0
  184. package/dist/src/core/text-rendering/renderers/canvas/internal/types.js +2 -0
  185. package/dist/src/core/text-rendering/renderers/canvas/internal/types.js.map +1 -0
  186. package/dist/src/core/text-rendering/renderers/canvas/internal/utils.d.ts +91 -0
  187. package/dist/src/core/text-rendering/renderers/canvas/internal/utils.js +282 -0
  188. package/dist/src/core/text-rendering/renderers/canvas/internal/utils.js.map +1 -0
  189. package/dist/src/core/text-rendering/sdf/PeekableGenerator.d.ts +12 -0
  190. package/dist/src/core/text-rendering/sdf/PeekableGenerator.js +61 -0
  191. package/dist/src/core/text-rendering/sdf/PeekableGenerator.js.map +1 -0
  192. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.d.ts +45 -0
  193. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js +69 -0
  194. package/dist/src/core/text-rendering/sdf/SimpleFontShaper.js.map +1 -0
  195. package/dist/src/core/text-rendering/sdf/Utils.d.ts +26 -0
  196. package/dist/src/core/text-rendering/sdf/Utils.js +301 -0
  197. package/dist/src/core/text-rendering/sdf/Utils.js.map +1 -0
  198. package/dist/src/core/text-rendering/sdf/index.d.ts +1 -0
  199. package/dist/src/core/text-rendering/sdf/index.js +20 -0
  200. package/dist/src/core/text-rendering/sdf/index.js.map +1 -0
  201. package/dist/src/core/textures/ImageTexture.js +11 -0
  202. package/dist/src/core/textures/ImageTexture.js.map +1 -1
  203. package/dist/src/core/textures/Texture.js +5 -0
  204. package/dist/src/core/textures/Texture.js.map +1 -1
  205. package/dist/src/main-api/Inspector.d.ts +1 -1
  206. package/dist/src/main-api/Inspector.js +25 -20
  207. package/dist/src/main-api/Inspector.js.map +1 -1
  208. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  209. package/dist/tsconfig.tsbuildinfo +1 -0
  210. package/package.json +1 -1
  211. package/src/common/EventEmitter.ts +6 -8
  212. package/src/core/CoreNode.test.ts +1 -1
  213. package/src/core/CoreNode.ts +8 -4
  214. package/src/core/CoreTextNode.test.ts +138 -25
  215. package/src/core/CoreTextNode.ts +174 -67
  216. package/src/core/Stage.ts +11 -0
  217. package/src/core/TextureMemoryManager.ts +4 -3
  218. package/src/core/animations/AnimationManager.ts +5 -5
  219. package/src/core/animations/CoreAnimation.ts +61 -45
  220. package/src/core/animations/CoreAnimationController.ts +16 -7
  221. package/src/core/renderers/canvas/CanvasRenderer.ts +19 -1
  222. package/src/core/renderers/canvas/CanvasTexture.ts +23 -8
  223. package/src/core/renderers/webgl/WebGlRenderer.ts +2 -3
  224. package/src/core/renderers/webgl/WebGlShaderNode.ts +24 -0
  225. package/src/core/renderers/webgl/WebGlShaderProgram.test.ts +274 -0
  226. package/src/core/renderers/webgl/WebGlShaderProgram.ts +10 -10
  227. package/src/core/renderers/webgl/internal/ShaderUtils.ts +1 -0
  228. package/src/core/text-rendering/CanvasFontHandler.ts +2 -2
  229. package/src/core/text-rendering/CanvasTextRenderer.ts +14 -7
  230. package/src/core/text-rendering/SdfTextRenderer.ts +28 -140
  231. package/src/core/text-rendering/TextLayoutEngine.ts +61 -28
  232. package/src/core/text-rendering/TextRenderer.ts +19 -12
  233. package/src/core/text-rendering/tests/TextLayoutEngine.test.ts +20 -0
  234. package/src/core/textures/ImageTexture.ts +18 -0
  235. package/src/core/textures/Texture.ts +6 -0
  236. package/src/main-api/Inspector.ts +25 -25
  237. package/dist/src/core/text-rendering/TextGenerator.d.ts +0 -10
  238. package/dist/src/core/text-rendering/TextGenerator.js +0 -36
  239. package/dist/src/core/text-rendering/TextGenerator.js.map +0 -1
  240. package/src/core/renderers/webgl/SdfRenderOp.ts +0 -106
@@ -319,8 +319,30 @@ describe('CoreTextNode', () => {
319
319
  });
320
320
  }
321
321
 
322
+ function createSdfRenderInfo() {
323
+ return {
324
+ type: 'sdf' as const,
325
+ width: 100,
326
+ height: 20,
327
+ atlasTexture: {} as any,
328
+ layout: {
329
+ glyphCount: 1,
330
+ width: 100,
331
+ height: 20,
332
+ fontScale: 1,
333
+ lineHeight: 20,
334
+ fontFamily: 'Arial',
335
+ distanceRange: 4,
336
+ vertexBuffer: new Float32Array([0, 0, 0, 0, 10, 0, 1, 0]),
337
+ truncatedTextLines: 0,
338
+ },
339
+ hasRemainingText: false,
340
+ remainingLines: 0,
341
+ };
342
+ }
343
+
322
344
  describe('updateRenderState – SDF buffer release on OutOfBounds', () => {
323
- it('should call renderer.deleteBuffer and clear _sdfBufferRef when transitioning to OutOfBounds', () => {
345
+ it('should call renderer.deleteBuffer and clear _sdfBuffer when transitioning to OutOfBounds', () => {
324
346
  const deleteBuffer = vi.fn();
325
347
  const node = new CoreTextNode(
326
348
  makeStageWithDeleteBuffer(deleteBuffer),
@@ -328,17 +350,17 @@ describe('CoreTextNode', () => {
328
350
  mockTextRenderer,
329
351
  );
330
352
 
331
- // Simulate a live WebGLBuffer sitting in the ref
332
353
  const fakeBuffer = {};
333
- (node as any)._sdfBufferRef.current = fakeBuffer;
354
+ (node as any)._sdfBuffer = fakeBuffer;
355
+ (node as any)._renderInfo = createSdfRenderInfo();
334
356
 
335
357
  node.updateRenderState(CoreNodeRenderState.OutOfBounds);
336
358
 
337
359
  expect(deleteBuffer).toHaveBeenCalledWith(fakeBuffer);
338
- expect((node as any)._sdfBufferRef.current).toBeNull();
360
+ expect((node as any)._sdfBuffer).toBeNull();
339
361
  });
340
362
 
341
- it('should not call renderer.deleteBuffer when _sdfBufferRef is already null', () => {
363
+ it('should not call renderer.deleteBuffer when _sdfBuffer is already null', () => {
342
364
  const deleteBuffer = vi.fn();
343
365
  const node = new CoreTextNode(
344
366
  makeStageWithDeleteBuffer(deleteBuffer),
@@ -346,7 +368,7 @@ describe('CoreTextNode', () => {
346
368
  mockTextRenderer,
347
369
  );
348
370
 
349
- // _sdfBufferRef.current is null by default
371
+ (node as any)._renderInfo = createSdfRenderInfo();
350
372
  node.updateRenderState(CoreNodeRenderState.OutOfBounds);
351
373
 
352
374
  expect(deleteBuffer).not.toHaveBeenCalled();
@@ -361,12 +383,13 @@ describe('CoreTextNode', () => {
361
383
  );
362
384
 
363
385
  const fakeBuffer = {};
364
- (node as any)._sdfBufferRef.current = fakeBuffer;
386
+ (node as any)._sdfBuffer = fakeBuffer;
387
+ (node as any)._renderInfo = createSdfRenderInfo();
365
388
 
366
389
  node.updateRenderState(CoreNodeRenderState.InBounds);
367
390
 
368
391
  expect(deleteBuffer).not.toHaveBeenCalled();
369
- expect((node as any)._sdfBufferRef.current).toBe(fakeBuffer);
392
+ expect((node as any)._sdfBuffer).toBe(fakeBuffer);
370
393
  });
371
394
 
372
395
  it('should not release the buffer for a canvas-type text node', () => {
@@ -382,7 +405,14 @@ describe('CoreTextNode', () => {
382
405
  canvasTextRenderer,
383
406
  );
384
407
 
385
- (node as any)._sdfBufferRef.current = {};
408
+ (node as any)._renderInfo = {
409
+ type: 'canvas',
410
+ width: 100,
411
+ height: 20,
412
+ imageData: {} as ImageData,
413
+ hasRemainingText: false,
414
+ remainingLines: 0,
415
+ };
386
416
 
387
417
  node.updateRenderState(CoreNodeRenderState.OutOfBounds);
388
418
 
@@ -401,12 +431,12 @@ describe('CoreTextNode', () => {
401
431
  );
402
432
 
403
433
  const fakeBuffer = {} as WebGLBuffer;
404
- (node as any)._sdfBufferRef.current = fakeBuffer;
434
+ (node as any)._sdfBuffer = fakeBuffer;
405
435
 
406
436
  node.update(16, clippingRect);
407
437
 
408
438
  expect(deleteBuffer).toHaveBeenCalledWith(fakeBuffer);
409
- expect((node as any)._sdfBufferRef.current).toBeNull();
439
+ expect((node as any)._sdfBuffer).toBeNull();
410
440
  });
411
441
 
412
442
  it('should call renderer.deleteBuffer again on each subsequent layout regeneration', () => {
@@ -424,12 +454,12 @@ describe('CoreTextNode', () => {
424
454
  // Trigger a second layout pass by invalidating the layout
425
455
  node.fontSize = 24;
426
456
  const secondBuffer = {} as WebGLBuffer;
427
- (node as any)._sdfBufferRef.current = secondBuffer;
457
+ (node as any)._sdfBuffer = secondBuffer;
428
458
 
429
459
  node.update(16, clippingRect);
430
460
 
431
461
  expect(deleteBuffer).toHaveBeenCalledWith(secondBuffer);
432
- expect((node as any)._sdfBufferRef.current).toBeNull();
462
+ expect((node as any)._sdfBuffer).toBeNull();
433
463
  });
434
464
 
435
465
  it('should not call renderer.deleteBuffer when buffer is already null at regeneration time', () => {
@@ -441,7 +471,7 @@ describe('CoreTextNode', () => {
441
471
  mockTextRenderer,
442
472
  );
443
473
 
444
- // _sdfBufferRef.current is null by default
474
+ // _sdfBuffer is null by default
445
475
  node.update(16, clippingRect);
446
476
 
447
477
  expect(deleteBuffer).not.toHaveBeenCalled();
@@ -460,14 +490,14 @@ describe('CoreTextNode', () => {
460
490
 
461
491
  // Prime the node with a cached buffer
462
492
  const fakeBuffer = {} as WebGLBuffer;
463
- (node as any)._sdfBufferRef.current = fakeBuffer;
493
+ (node as any)._sdfBuffer = fakeBuffer;
464
494
  (node as any)._layoutGenerated = true;
465
495
 
466
496
  node.text = '';
467
497
  node.update(16, clippingRect);
468
498
 
469
499
  expect(deleteBuffer).toHaveBeenCalledWith(fakeBuffer);
470
- expect((node as any)._sdfBufferRef.current).toBeNull();
500
+ expect((node as any)._sdfBuffer).toBeNull();
471
501
  });
472
502
 
473
503
  it('should not call renderer.deleteBuffer when text is invalid and buffer is already null', () => {
@@ -484,7 +514,7 @@ describe('CoreTextNode', () => {
484
514
  expect(deleteBuffer).not.toHaveBeenCalled();
485
515
  });
486
516
 
487
- it('should also clear _cachedLayout when text becomes invalid', () => {
517
+ it('should also clear _renderInfo when text becomes invalid', () => {
488
518
  const deleteBuffer = vi.fn();
489
519
  const props = { ...defaultTextProps, text: 'Hello', forceLoad: true };
490
520
  const node = new CoreTextNode(
@@ -493,13 +523,12 @@ describe('CoreTextNode', () => {
493
523
  mockTextRenderer,
494
524
  );
495
525
 
496
- (node as any)._cachedLayout = {};
497
- (node as any)._layoutGenerated = true;
526
+ (node as any)._renderInfo = createSdfRenderInfo();
498
527
 
499
528
  node.text = '';
500
529
  node.update(16, clippingRect);
501
530
 
502
- expect((node as any)._cachedLayout).toBeNull();
531
+ expect((node as any)._renderInfo).toBeNull();
503
532
  });
504
533
  });
505
534
 
@@ -513,12 +542,12 @@ describe('CoreTextNode', () => {
513
542
  );
514
543
 
515
544
  const fakeBuffer = {} as WebGLBuffer;
516
- (node as any)._sdfBufferRef.current = fakeBuffer;
545
+ (node as any)._sdfBuffer = fakeBuffer;
517
546
 
518
547
  node.destroy();
519
548
 
520
549
  expect(deleteBuffer).toHaveBeenCalledWith(fakeBuffer);
521
- expect((node as any)._sdfBufferRef.current).toBeNull();
550
+ expect((node as any)._sdfBuffer).toBeNull();
522
551
  });
523
552
 
524
553
  it('should not call renderer.deleteBuffer on destroy when buffer is already null', () => {
@@ -535,14 +564,98 @@ describe('CoreTextNode', () => {
535
564
  expect(deleteBuffer).not.toHaveBeenCalled();
536
565
  });
537
566
 
538
- it('should clear _cachedLayout on destroy', () => {
567
+ it('should clear _renderInfo on destroy', () => {
539
568
  const node = new CoreTextNode(stage, defaultTextProps, mockTextRenderer);
540
569
 
541
- (node as any)._cachedLayout = {};
570
+ (node as any)._renderInfo = createSdfRenderInfo();
542
571
 
543
572
  node.destroy();
544
573
 
545
- expect((node as any)._cachedLayout).toBeNull();
574
+ expect((node as any)._renderInfo).toBeNull();
575
+ });
576
+ });
577
+
578
+ describe('SDF render path', () => {
579
+ it('reuses the uploaded SDF buffer across renderQuads calls', () => {
580
+ const createBuffer = vi.fn().mockReturnValue({ label: 'sdf-buffer' });
581
+ const arrayBufferData = vi.fn();
582
+ const glw = {
583
+ createBuffer,
584
+ arrayBufferData,
585
+ STATIC_DRAW: 0x88e4,
586
+ FLOAT: 0x1406,
587
+ };
588
+ const sdfStage = mock<Stage>({
589
+ strictBound: createBound(0, 0, 1920, 1080),
590
+ preloadBound: createBound(0, 0, 1920, 1080),
591
+ defaultTexture: { state: 'loaded' },
592
+ renderer: { glw } as unknown as CoreRenderer,
593
+ });
594
+ const node = new CoreTextNode(
595
+ sdfStage,
596
+ defaultTextProps,
597
+ mockTextRenderer,
598
+ );
599
+ const transform = new Float32Array([1, 0, 0, 1, 0, 0]);
600
+
601
+ (node as any).handleRenderResult(createSdfRenderInfo());
602
+ (node as any).globalTransform = {
603
+ getFloatArr: vi.fn().mockReturnValue(transform),
604
+ };
605
+
606
+ node.renderQuads(sdfStage.renderer);
607
+ node.renderQuads(sdfStage.renderer);
608
+
609
+ expect(createBuffer).toHaveBeenCalledTimes(1);
610
+ expect(arrayBufferData).toHaveBeenCalledTimes(1);
611
+ expect(mockTextRenderer.renderQuads).toHaveBeenCalledTimes(2);
612
+ expect((node as any)._sdfBuffer).toEqual({ label: 'sdf-buffer' });
613
+ expect(node.sdfShaderProps.transform).toBe(transform);
614
+ });
615
+
616
+ it('uses framebuffer-relative scissor coordinates for RTT draws', () => {
617
+ const bindRenderOp = vi.fn();
618
+ const useShader = vi.fn();
619
+ const setScissorTest = vi.fn();
620
+ const scissor = vi.fn();
621
+ const drawArrays = vi.fn();
622
+ const sdfStage = mock<Stage>({
623
+ strictBound: createBound(0, 0, 1920, 1080),
624
+ preloadBound: createBound(0, 0, 1920, 1080),
625
+ defaultTexture: { state: 'loaded' },
626
+ pixelRatio: 2,
627
+ platform: { canvas: { width: 1920, height: 1080 } } as any,
628
+ shManager: { useShader } as any,
629
+ });
630
+ const node = new CoreTextNode(
631
+ sdfStage,
632
+ defaultTextProps,
633
+ mockTextRenderer,
634
+ );
635
+ const shader = { program: { bindRenderOp } };
636
+
637
+ node.props.shader = shader as any;
638
+ node.numQuads = 2;
639
+ node.clippingRect = { x: 10, y: 20, w: 30, h: 40, valid: true };
640
+ node.parentHasRenderTexture = true;
641
+ node.rttParent = { framebufferDimensions: { w: 320, h: 180 } } as any;
642
+ node.props.h = 25;
643
+
644
+ node.draw({
645
+ glw: {
646
+ TRIANGLES: 4,
647
+ setScissorTest,
648
+ scissor,
649
+ drawArrays,
650
+ },
651
+ stage: sdfStage,
652
+ } as any);
653
+
654
+ expect(useShader).toHaveBeenCalledWith(shader.program);
655
+ expect(bindRenderOp).toHaveBeenCalledWith(node);
656
+ expect(setScissorTest).toHaveBeenCalledWith(true);
657
+ expect(scissor).toHaveBeenCalledWith(10, 155, 30, 40);
658
+ expect(drawArrays).toHaveBeenCalledWith(4, 0, 12);
546
659
  });
547
660
  });
548
661
  });
@@ -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,21 +67,14 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
63
67
  private _waitingForFont = false;
64
68
  private _containType: TextConstraint = TextConstraint.none;
65
69
 
66
- // SDF layout caching for performance
67
- private _cachedLayout: TextLayout | null = null;
68
- // Mutable ref box shared with SdfTextRenderer so the renderer can write the
69
- // created WebGLBuffer back into it, allowing reuse across frames.
70
- private _sdfBufferRef: { current: WebGLBuffer | null } = { current: null };
70
+ private _sdfBuffer: WebGLBuffer | null = null;
71
+ private _sdfQuadCollection: BufferCollection | null = null;
72
+ private _sdfShaderProps: Partial<SdfShaderProps> | null = null;
71
73
 
72
74
  // Text renderer properties - stored directly on the node
73
- private textProps: CoreTextNodeProps;
75
+ textProps: CoreTextNodeProps;
74
76
 
75
- private _renderInfo: TextRenderInfo = {
76
- width: 0,
77
- height: 0,
78
- };
79
-
80
- private _type: 'sdf' | 'canvas' = 'sdf'; // Default to SDF renderer
77
+ private _renderInfo: TextRenderInfo | null = null;
81
78
 
82
79
  constructor(
83
80
  stage: Stage,
@@ -87,7 +84,6 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
87
84
  super(stage, props);
88
85
  this.textRenderer = textRenderer;
89
86
  this.fontHandler = textRenderer.font;
90
- this._type = textRenderer.type;
91
87
 
92
88
  // Initialize text properties from props
93
89
  // Props are guaranteed to have all defaults resolved by Stage.createTextNode
@@ -121,10 +117,11 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
121
117
  * Safe to call from destroy() or on text change.
122
118
  */
123
119
  private releaseSdfBuffer(): void {
124
- const buf = this._sdfBufferRef.current;
120
+ const buf = this._sdfBuffer;
125
121
  if (buf === null) return;
126
122
  this.stage.renderer.deleteBuffer(buf);
127
- this._sdfBufferRef.current = null;
123
+ this._sdfBuffer = null;
124
+ this._sdfQuadCollection = null;
128
125
  }
129
126
 
130
127
  allowTextGeneration() {
@@ -221,7 +218,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
221
218
  ) {
222
219
  if (this.fontHandler.isFontLoaded(this.textProps.fontFamily) === true) {
223
220
  this._waitingForFont = false;
224
- this._cachedLayout = null; // Invalidate cached layout
221
+ this._renderInfo = null; // Clear any previous render info before generating new layout
225
222
  this.releaseSdfBuffer(); // Free the cached WebGLBuffer
226
223
  const resp = this.textRenderer.renderText(this.textProps);
227
224
  this.handleRenderResult(resp);
@@ -236,7 +233,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
236
233
  // If text is invalid, ensure node is not renderable
237
234
  this.setRenderable(false);
238
235
  this._layoutGenerated = false;
239
- this._cachedLayout = null;
236
+ this._renderInfo = null;
240
237
  this.releaseSdfBuffer(); // Free the cached WebGLBuffer
241
238
  }
242
239
 
@@ -251,19 +248,19 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
251
248
  // Guard: Text nodes are never renderable without valid text
252
249
  const hasValidText =
253
250
  typeof this.textProps.text === 'string' && this.textProps.text.length > 0;
254
- if (hasValidText === false) {
251
+
252
+ const renderInfo = this._renderInfo;
253
+ if (hasValidText === false || renderInfo === null) {
255
254
  this.setRenderable(false);
256
255
  return;
257
256
  }
258
257
 
259
258
  // SDF text nodes are always renderable if they have a valid layout
260
- if (this._type === 'canvas') {
259
+ if (renderInfo.type === 'canvas') {
261
260
  super.updateIsRenderable();
262
261
  return;
263
262
  }
264
-
265
- // For SDF, check if we have a cached layout
266
- this.setRenderable(this._cachedLayout !== null);
263
+ this.setRenderable(true);
267
264
  }
268
265
 
269
266
  /**
@@ -271,10 +268,19 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
271
268
  */
272
269
  private handleRenderResult(result: TextRenderInfo): void {
273
270
  // Host paths on top
274
- const textRendererType = this._type;
271
+ const textRendererType = result.type;
275
272
  let width = result.width;
276
273
  let height = result.height;
277
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
+
278
284
  // Handle Canvas renderer (uses ImageData)
279
285
  if (textRendererType === 'canvas') {
280
286
  if (result.imageData === undefined) {
@@ -291,6 +297,9 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
291
297
  premultiplyAlpha: true,
292
298
  src: result.imageData as ImageData,
293
299
  });
300
+
301
+ this.props.w = width;
302
+ this.props.h = height;
294
303
  // It isn't renderable until the texture is loaded we have to set it to false here to avoid it
295
304
  // being detected as a renderable default color node in the next frame
296
305
  // it will be corrected once the texture is loaded
@@ -300,25 +309,31 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
300
309
  // We do want the texture to load immediately
301
310
  this.texture.setRenderableOwner(this._id, true);
302
311
  }
303
- }
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
+ }
304
324
 
305
- // Handle zero-dimension case (can happen with certain text inputs or font issues)
306
- if (width === 0 || height === 0) {
307
- this.emit('failed', {
308
- type: 'text',
309
- error: new Error('Text rendering failed, width or height zero'),
310
- } satisfies NodeTextFailedPayload);
311
- return;
312
- }
325
+ this.props.w = width;
326
+ this.props.h = height;
327
+ this.setUpdateType(UpdateType.Local);
328
+ this.setRenderable(true);
329
+ this.numQuads = layout.glyphCount;
313
330
 
314
- this._cachedLayout = result.layout || null;
315
- this.props.w = width;
316
- this.props.h = height;
331
+ this._sdfShaderProps = {
332
+ size: layout.fontScale,
333
+ distanceRange: layout.distanceRange,
334
+ };
317
335
 
318
- // Handle SDF renderer (uses layout caching)
319
- if (textRendererType === 'sdf') {
320
- this.setRenderable(true);
321
- this.setUpdateType(UpdateType.Local);
336
+ this.renderOpTextures = [result.atlasTexture as WebGlCtxTexture];
322
337
  }
323
338
 
324
339
  this._renderInfo = result;
@@ -327,6 +342,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
327
342
 
328
343
  // Reusable bound method for emitting loaded event
329
344
  private emitTextLoadedEvent = () => {
345
+ if (this._renderInfo === null) return; // Guard against unexpected null
346
+
330
347
  this.emit('loaded', {
331
348
  type: 'text',
332
349
  dimensions: {
@@ -346,42 +363,68 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
346
363
  return;
347
364
  }
348
365
 
349
- // Canvas renderer: use standard texture rendering via CoreNode
350
- if (this._type === 'canvas') {
351
- super.renderQuads(renderer);
366
+ // Early return if no renderInfo
367
+ if (this._renderInfo === null) {
352
368
  return;
353
369
  }
354
370
 
355
- // Early return if no cached data
356
- if (this._cachedLayout === null) {
371
+ // Canvas renderer: use standard texture rendering via CoreNode
372
+ if (this._renderInfo.type === 'canvas') {
373
+ super.renderQuads(renderer);
357
374
  return;
358
375
  }
359
376
 
360
- const props = this.textProps;
361
- this.textRenderer.renderQuads(renderer, this._cachedLayout as TextLayout, {
362
- fontFamily: this.textProps.fontFamily,
363
- fontSize: props.fontSize,
364
- color: this.props.color || 0xffffffff,
365
- offsetY: props.offsetY,
366
- worldAlpha: this.worldAlpha,
367
- globalTransform: this.globalTransform!.getFloatArr(),
368
- clippingRect: this.clippingRect,
369
- width: this.props.w,
370
- height: this.props.h,
371
- parentHasRenderTexture: this.parentHasRenderTexture,
372
- framebufferDimensions:
373
- this.parentHasRenderTexture === true
374
- ? this.parentFramebufferDimensions
375
- : null,
376
- stage: this.stage,
377
- glBufferRef: this._sdfBufferRef,
378
- });
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,
419
+ );
420
+
421
+ this.textRenderer.renderQuads(this);
379
422
  }
380
423
 
381
424
  override updateRenderState(renderState: CoreNodeRenderState): void {
382
425
  super.updateRenderState(renderState);
383
426
  if (
384
- this._type === 'sdf' &&
427
+ this._renderInfo !== null &&
385
428
  renderState === CoreNodeRenderState.OutOfBounds
386
429
  ) {
387
430
  this.releaseSdfBuffer();
@@ -394,7 +437,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
394
437
  }
395
438
 
396
439
  // Clear cached layout and vertex buffer
397
- this._cachedLayout = null;
440
+ this._renderInfo = null;
398
441
  this.releaseSdfBuffer(); // Delete the cached WebGLBuffer before losing stage ref
399
442
 
400
443
  this.fontHandler = null!; // Clear reference to avoid memory leaks
@@ -403,6 +446,70 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
403
446
  super.destroy();
404
447
  }
405
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
+
406
513
  override set w(value: number) {
407
514
  this.maxWidth = value;
408
515
  }
@@ -621,7 +728,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
621
728
  }
622
729
  }
623
730
 
624
- get renderInfo(): TextRenderInfo {
731
+ get renderInfo(): TextRenderInfo | null {
625
732
  return this._renderInfo;
626
733
  }
627
734
  }
package/src/core/Stage.ts CHANGED
@@ -224,6 +224,17 @@ export class Stage {
224
224
 
225
225
  const renderMode = this.renderer.mode || 'webgl';
226
226
 
227
+ // Canvas2D textures are plain JS heap objects managed by the browser GC.
228
+ // Threshold-based upload blocking makes no sense for JS heap — disable it
229
+ // by setting criticalThreshold to 0 while keeping the eviction machinery.
230
+ if (renderMode === 'canvas') {
231
+ this.txMemManager.updateSettings({
232
+ ...textureMemory,
233
+ criticalThreshold: 0,
234
+ doNotExceedCriticalThreshold: false,
235
+ });
236
+ }
237
+
227
238
  this.createDefaultTexture();
228
239
  setPremultiplyMode(renderMode);
229
240