@lightningjs/renderer 0.7.6 → 0.8.1

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 (110) hide show
  1. package/README.md +4 -0
  2. package/dist/src/common/CommonTypes.d.ts +6 -0
  3. package/dist/src/core/CoreNode.d.ts +65 -8
  4. package/dist/src/core/CoreNode.js +104 -28
  5. package/dist/src/core/CoreNode.js.map +1 -1
  6. package/dist/src/core/CoreShaderManager.d.ts +2 -0
  7. package/dist/src/core/CoreShaderManager.js +2 -0
  8. package/dist/src/core/CoreShaderManager.js.map +1 -1
  9. package/dist/src/core/CoreTextNode.d.ts +5 -0
  10. package/dist/src/core/CoreTextNode.js +15 -10
  11. package/dist/src/core/CoreTextNode.js.map +1 -1
  12. package/dist/src/core/CoreTextureManager.js +2 -0
  13. package/dist/src/core/CoreTextureManager.js.map +1 -1
  14. package/dist/src/core/Stage.d.ts +4 -0
  15. package/dist/src/core/Stage.js +8 -1
  16. package/dist/src/core/Stage.js.map +1 -1
  17. package/dist/src/core/TextureMemoryManager.d.ts +12 -0
  18. package/dist/src/core/TextureMemoryManager.js +42 -0
  19. package/dist/src/core/TextureMemoryManager.js.map +1 -0
  20. package/dist/src/core/platform.js +8 -0
  21. package/dist/src/core/platform.js.map +1 -1
  22. package/dist/src/core/renderers/CoreContextTexture.d.ts +5 -1
  23. package/dist/src/core/renderers/CoreContextTexture.js +3 -1
  24. package/dist/src/core/renderers/CoreContextTexture.js.map +1 -1
  25. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.d.ts +2 -1
  26. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js +2 -2
  27. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js.map +1 -1
  28. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +3 -1
  29. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +22 -5
  30. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  31. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +3 -0
  32. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +4 -2
  33. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  34. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.d.ts +58 -0
  35. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js +112 -0
  36. package/dist/src/core/renderers/webgl/shaders/effects/HolePunchEffect.js.map +1 -0
  37. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +17 -30
  38. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js.map +1 -1
  39. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.d.ts +1 -0
  40. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +24 -30
  41. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js.map +1 -1
  42. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +2 -0
  43. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +18 -0
  44. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  45. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +8 -0
  46. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +26 -4
  47. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  48. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +4 -3
  49. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  50. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +19 -0
  51. package/dist/src/core/text-rendering/renderers/TextRenderer.js +26 -0
  52. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  53. package/dist/src/core/textures/Texture.d.ts +26 -1
  54. package/dist/src/core/textures/Texture.js +30 -1
  55. package/dist/src/core/textures/Texture.js.map +1 -1
  56. package/dist/src/main-api/ICoreDriver.d.ts +1 -0
  57. package/dist/src/main-api/Inspector.js +2 -1
  58. package/dist/src/main-api/Inspector.js.map +1 -1
  59. package/dist/src/main-api/RendererMain.d.ts +10 -1
  60. package/dist/src/main-api/RendererMain.js +6 -1
  61. package/dist/src/main-api/RendererMain.js.map +1 -1
  62. package/dist/src/render-drivers/main/MainCoreDriver.d.ts +1 -0
  63. package/dist/src/render-drivers/main/MainCoreDriver.js +7 -0
  64. package/dist/src/render-drivers/main/MainCoreDriver.js.map +1 -1
  65. package/dist/src/render-drivers/main/MainOnlyNode.d.ts +1 -0
  66. package/dist/src/render-drivers/main/MainOnlyNode.js +10 -6
  67. package/dist/src/render-drivers/main/MainOnlyNode.js.map +1 -1
  68. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js +1 -0
  69. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +1 -1
  70. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +1 -0
  71. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +1 -1
  72. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js +3 -0
  73. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js.map +1 -1
  74. package/dist/src/render-drivers/threadx/worker/renderer.js +1 -0
  75. package/dist/src/render-drivers/threadx/worker/renderer.js.map +1 -1
  76. package/dist/src/utils.d.ts +6 -0
  77. package/dist/src/utils.js +9 -1
  78. package/dist/src/utils.js.map +1 -1
  79. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  80. package/package.json +1 -1
  81. package/src/common/CommonTypes.ts +7 -0
  82. package/src/core/CoreNode.ts +118 -34
  83. package/src/core/CoreShaderManager.ts +3 -0
  84. package/src/core/CoreTextNode.ts +44 -43
  85. package/src/core/CoreTextureManager.ts +2 -0
  86. package/src/core/Stage.ts +10 -0
  87. package/src/core/TextureMemoryManager.ts +66 -0
  88. package/src/core/platform.ts +8 -0
  89. package/src/core/renderers/CoreContextTexture.ts +6 -1
  90. package/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts +7 -2
  91. package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +34 -6
  92. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +10 -2
  93. package/src/core/renderers/webgl/shaders/effects/HolePunchEffect.ts +166 -0
  94. package/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts +16 -32
  95. package/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts +26 -32
  96. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +23 -0
  97. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +32 -4
  98. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +4 -3
  99. package/src/core/text-rendering/renderers/TextRenderer.ts +32 -0
  100. package/src/core/textures/Texture.ts +39 -2
  101. package/src/main-api/ICoreDriver.ts +2 -0
  102. package/src/main-api/Inspector.ts +2 -1
  103. package/src/main-api/RendererMain.ts +19 -2
  104. package/src/render-drivers/main/MainCoreDriver.ts +9 -0
  105. package/src/render-drivers/main/MainOnlyNode.ts +12 -6
  106. package/src/render-drivers/threadx/ThreadXCoreDriver.ts +1 -0
  107. package/src/render-drivers/threadx/ThreadXRendererMessage.ts +1 -0
  108. package/src/render-drivers/threadx/worker/ThreadXRendererNode.ts +7 -0
  109. package/src/render-drivers/threadx/worker/renderer.ts +1 -0
  110. package/src/utils.ts +10 -1
@@ -19,6 +19,7 @@
19
19
 
20
20
  import type { Dimensions } from '../../../common/CommonTypes.js';
21
21
  import { assertTruthy } from '../../../utils.js';
22
+ import type { TextureMemoryManager } from '../../TextureMemoryManager.js';
22
23
  import type { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
23
24
  import type { Texture } from '../../textures/Texture.js';
24
25
  import { isPowerOfTwo } from '../../utils.js';
@@ -44,8 +45,12 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
44
45
  private _w = 0;
45
46
  private _h = 0;
46
47
 
47
- constructor(protected glw: WebGlContextWrapper, textureSource: Texture) {
48
- super(textureSource);
48
+ constructor(
49
+ protected glw: WebGlContextWrapper,
50
+ memManager: TextureMemoryManager,
51
+ textureSource: Texture,
52
+ ) {
53
+ super(memManager, textureSource);
49
54
  }
50
55
 
51
56
  get ctxTexture(): WebGLTexture {
@@ -56,6 +61,10 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
56
61
  return this._nativeCtxTexture;
57
62
  }
58
63
 
64
+ get renderable(): boolean {
65
+ return this.textureSource.renderable;
66
+ }
67
+
59
68
  get w() {
60
69
  return this._w;
61
70
  }
@@ -80,8 +89,12 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
80
89
  }
81
90
  this._state = 'loading';
82
91
  this.textureSource.setState('loading');
92
+ this._nativeCtxTexture = this.createNativeCtxTexture();
83
93
  this.onLoadRequest()
84
94
  .then(({ width, height }) => {
95
+ if (this._state === 'freed') {
96
+ return;
97
+ }
85
98
  this._state = 'loaded';
86
99
  this._w = width;
87
100
  this._h = height;
@@ -100,8 +113,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
100
113
  * Called when the texture data needs to be loaded and uploaded to a texture
101
114
  */
102
115
  async onLoadRequest(): Promise<Dimensions> {
103
- this._nativeCtxTexture = this.createNativeCtxTexture();
104
- const { glw } = this;
116
+ const { glw, memManager } = this;
105
117
 
106
118
  // On initial load request, create a 1x1 transparent texture to use until
107
119
  // the texture data is finally loaded.
@@ -126,10 +138,17 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
126
138
  glw.UNSIGNED_BYTE,
127
139
  TRANSPARENT_TEXTURE_DATA,
128
140
  );
141
+ memManager.setTextureMemUse(this, TRANSPARENT_TEXTURE_DATA.byteLength);
129
142
 
130
143
  const textureData = await this.textureSource?.getTextureData();
144
+ // If the texture has been freed while loading, return early.
145
+ if (!this._nativeCtxTexture) {
146
+ assertTruthy(this._state === 'freed');
147
+ return { width: 0, height: 0 };
148
+ }
131
149
  let width = 0;
132
150
  let height = 0;
151
+
133
152
  assertTruthy(this._nativeCtxTexture);
134
153
  glw.activeTexture(0);
135
154
  // If textureData is null, the texture is empty (0, 0) and we don't need to
@@ -150,6 +169,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
150
169
  );
151
170
 
152
171
  glw.texImage2D(0, glw.RGBA, glw.RGBA, glw.UNSIGNED_BYTE, data);
172
+ memManager.setTextureMemUse(this, width * height * 4);
153
173
 
154
174
  // generate mipmaps for power-of-2 textures or in WebGL2RenderingContext
155
175
  if (glw.isWebGl2() || (isPowerOfTwo(width) && isPowerOfTwo(height))) {
@@ -160,6 +180,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
160
180
  height = 0;
161
181
  // Reset to a 1x1 transparent texture
162
182
  glw.bindTexture(this._nativeCtxTexture);
183
+
163
184
  glw.texImage2D(
164
185
  0,
165
186
  glw.RGBA,
@@ -170,6 +191,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
170
191
  glw.UNSIGNED_BYTE,
171
192
  TRANSPARENT_TEXTURE_DATA,
172
193
  );
194
+ memManager.setTextureMemUse(this, TRANSPARENT_TEXTURE_DATA.byteLength);
173
195
  } else if ('mipmaps' in textureData.data && textureData.data.mipmaps) {
174
196
  const {
175
197
  mipmaps,
@@ -184,12 +206,14 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
184
206
  : (mipmaps[0] as unknown as ArrayBufferView);
185
207
 
186
208
  glw.bindTexture(this._nativeCtxTexture);
187
- glw.compressedTexImage2D(0, glInternalFormat, width, height, 0, view);
188
209
 
210
+ glw.compressedTexImage2D(0, glInternalFormat, width, height, 0, view);
189
211
  glw.texParameteri(glw.TEXTURE_WRAP_S, glw.CLAMP_TO_EDGE);
190
212
  glw.texParameteri(glw.TEXTURE_WRAP_T, glw.CLAMP_TO_EDGE);
191
213
  glw.texParameteri(glw.TEXTURE_MAG_FILTER, glw.LINEAR);
192
214
  glw.texParameteri(glw.TEXTURE_MIN_FILTER, glw.LINEAR);
215
+
216
+ memManager.setTextureMemUse(this, view.byteLength);
193
217
  } else {
194
218
  console.error(
195
219
  `WebGlCoreCtxTexture.onLoadRequest: Unexpected textureData returned`,
@@ -202,6 +226,7 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
202
226
  height,
203
227
  };
204
228
  }
229
+
205
230
  /**
206
231
  * Free the WebGLTexture from the GPU
207
232
  *
@@ -212,13 +237,16 @@ export class WebGlCoreCtxTexture extends CoreContextTexture {
212
237
  return;
213
238
  }
214
239
  this._state = 'freed';
240
+ this.textureSource.setState('freed');
215
241
  this._w = 0;
216
242
  this._h = 0;
217
243
  if (!this._nativeCtxTexture) {
218
244
  return;
219
245
  }
220
- const { glw } = this;
246
+ const { glw, memManager } = this;
247
+
221
248
  glw.deleteTexture(this._nativeCtxTexture);
249
+ memManager.setTextureMemUse(this, 0);
222
250
  this._nativeCtxTexture = null;
223
251
  }
224
252
 
@@ -57,6 +57,7 @@ import { WebGlCoreShader } from './WebGlCoreShader.js';
57
57
  import { RoundedRectangle } from './shaders/RoundedRectangle.js';
58
58
  import { ContextSpy } from '../../lib/ContextSpy.js';
59
59
  import { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
60
+ import type { TextureMemoryManager } from '../../TextureMemoryManager.js';
60
61
 
61
62
  const WORDS_PER_QUAD = 24;
62
63
  const BYTES_PER_QUAD = WORDS_PER_QUAD * 4;
@@ -66,6 +67,7 @@ export interface WebGlCoreRendererOptions {
66
67
  canvas: HTMLCanvasElement | OffscreenCanvas;
67
68
  pixelRatio: number;
68
69
  txManager: CoreTextureManager;
70
+ txMemManager: TextureMemoryManager;
69
71
  shManager: CoreShaderManager;
70
72
  clearColor: number;
71
73
  bufferMemory: number;
@@ -84,6 +86,7 @@ export class WebGlCoreRenderer extends CoreRenderer {
84
86
 
85
87
  //// Core Managers
86
88
  txManager: CoreTextureManager;
89
+ txMemManager: TextureMemoryManager;
87
90
  shManager: CoreShaderManager;
88
91
 
89
92
  //// Options
@@ -114,6 +117,7 @@ export class WebGlCoreRenderer extends CoreRenderer {
114
117
  const { canvas, clearColor, bufferMemory } = options;
115
118
  this.options = options;
116
119
  this.txManager = options.txManager;
120
+ this.txMemManager = options.txMemManager;
117
121
  this.shManager = options.shManager;
118
122
  this.defaultTexture = new ColorTexture(this.txManager);
119
123
  // When the default texture is loaded, request a render in case the
@@ -198,9 +202,13 @@ export class WebGlCoreRenderer extends CoreRenderer {
198
202
 
199
203
  override createCtxTexture(textureSource: Texture): CoreContextTexture {
200
204
  if (textureSource instanceof SubTexture) {
201
- return new WebGlCoreCtxSubTexture(this.glw, textureSource);
205
+ return new WebGlCoreCtxSubTexture(
206
+ this.glw,
207
+ this.txMemManager,
208
+ textureSource,
209
+ );
202
210
  }
203
- return new WebGlCoreCtxTexture(this.glw, textureSource);
211
+ return new WebGlCoreCtxTexture(this.glw, this.txMemManager, textureSource);
204
212
  }
205
213
 
206
214
  /**
@@ -0,0 +1,166 @@
1
+ /*
2
+ * If not stated otherwise in this file or this component's LICENSE file the
3
+ * following copyright and licenses apply:
4
+ *
5
+ * Copyright 2023 Comcast Cable Communications Management, LLC.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the License);
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ import {
20
+ ShaderEffect,
21
+ type DefaultEffectProps,
22
+ type ShaderEffectUniforms,
23
+ } from './ShaderEffect.js';
24
+
25
+ /**
26
+ * Properties of the {@link RadiusEffect} shader
27
+ */
28
+ export interface HolePunchEffectProps extends DefaultEffectProps {
29
+ /**
30
+ * X position where the hole punch starts
31
+ */
32
+ x?: number;
33
+ /**
34
+ * Y position where the hole punch starts
35
+ */
36
+ y?: number;
37
+ /**
38
+ * Width of the hole punch
39
+ */
40
+ width?: number;
41
+ /**
42
+ * height of the hole punch
43
+ *
44
+ * @remarks if not defined uses the width value
45
+ */
46
+ height?: number;
47
+ /**
48
+ * Corner radius in pixels, to cut out of the corners of the hole punch
49
+ *
50
+ * @remarks
51
+ * You can input an array with a length of up to four or a number.
52
+ *
53
+ * array length 4:
54
+ * [topLeft, topRight, bottomRight, bottomLeft]
55
+ *
56
+ * array length 2:
57
+ * [20, 40] -> [20(topLeft), 40(topRight), 20(bottomRight), 40(bottomLeft)]
58
+ *
59
+ * array length 3:
60
+ * [20, 40, 60] -> [20(topLeft), 40(topRight), 60(bottomRight), 20(bottomLeft)]
61
+ *
62
+ * number:
63
+ * 30 -> [30, 30, 30, 30]
64
+ *
65
+ * @default 0
66
+ */
67
+ radius?: number | number[];
68
+ }
69
+
70
+ /**
71
+ * Masks the current maskcolor a holepunch effect with rounded corners similar to {@link RoundedRectangle}
72
+ */
73
+ export class HolePunchEffect extends ShaderEffect {
74
+ static z$__type__Props: HolePunchEffectProps;
75
+ override readonly name = 'holePunch';
76
+
77
+ static override getEffectKey(): string {
78
+ return `holePunch`;
79
+ }
80
+
81
+ static override uniforms: ShaderEffectUniforms = {
82
+ x: {
83
+ value: 0,
84
+ method: 'uniform1f',
85
+ type: 'float',
86
+ },
87
+ y: {
88
+ value: 0,
89
+ method: 'uniform1f',
90
+ type: 'float',
91
+ },
92
+ width: {
93
+ value: 0,
94
+ method: 'uniform1f',
95
+ type: 'float',
96
+ },
97
+ height: {
98
+ value: 0,
99
+ method: 'uniform1f',
100
+ type: 'float',
101
+ },
102
+ radius: {
103
+ value: 0,
104
+ method: 'uniform4fv',
105
+ type: 'vec4',
106
+ validator: (value: number | number[]) => {
107
+ let r = value;
108
+ if (Array.isArray(r)) {
109
+ if (r.length === 2) {
110
+ r = [r[0], r[1], r[0], r[1]] as number[];
111
+ } else if (r.length === 3) {
112
+ r = [r[0], r[1], r[2], r[0]] as number[];
113
+ } else if (r.length !== 4) {
114
+ r = [r[0], r[0], r[0], r[0]] as number[];
115
+ }
116
+ } else if (typeof r === 'number') {
117
+ r = [r, r, r, r];
118
+ }
119
+ return r;
120
+ },
121
+ },
122
+ };
123
+
124
+ static override resolveDefaults(
125
+ props: HolePunchEffectProps,
126
+ ): Required<HolePunchEffectProps> {
127
+ return {
128
+ x: props.x || 0,
129
+ y: props.y || 0,
130
+ width: props.width || 50,
131
+ height: props.height || 50,
132
+ radius: props.radius ?? 0,
133
+ };
134
+ }
135
+
136
+ static override methods: Record<string, string> = {
137
+ fillMask: `
138
+ float function(float dist) {
139
+ return clamp(-dist, 0.0, 1.0);
140
+ }
141
+ `,
142
+ boxDist: `
143
+ float function(vec2 p, vec2 size, float radius) {
144
+ size -= vec2(radius);
145
+ vec2 d = abs(p) - size;
146
+ return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - radius;
147
+ }
148
+ `,
149
+ };
150
+
151
+ static override onShaderMask = `
152
+ vec2 halfDimensions = u_dimensions * 0.5;
153
+ vec2 size = vec2(width, height) * 0.5;
154
+ vec2 basePos = v_textureCoordinate.xy * u_dimensions.xy - vec2(x, y);
155
+ vec2 pos = basePos - size;
156
+ float r = radius[0] * step(pos.x, 0.5) * step(pos.y, 0.5);
157
+ r = r + radius[1] * step(0.5, pos.x) * step(pos.y, 0.5);
158
+ r = r + radius[2] * step(0.5, pos.x) * step(0.5, pos.y);
159
+ r = r + radius[3] * step(pos.x, 0.5) * step(0.5, pos.y);
160
+ return $boxDist(pos, size, r);
161
+ `;
162
+
163
+ static override onEffectMask = `
164
+ return mix(maskColor, vec4(0.0), $fillMask(shaderMask));
165
+ `;
166
+ }
@@ -61,13 +61,22 @@ export class LinearGradientEffect extends ShaderEffect {
61
61
  ): Required<LinearGradientEffectProps> {
62
62
  const colors = props.colors ?? [0xff000000, 0xffffffff];
63
63
 
64
- let stops = props.stops;
65
- if (!stops) {
66
- stops = [];
67
- const calc = colors.length - 1;
68
- for (let i = 0; i < colors.length; i++) {
69
- stops.push(i * (1 / calc));
64
+ let stops = props.stops || [];
65
+ if (stops.length === 0 || stops.length !== colors.length) {
66
+ const colorsL = colors.length;
67
+ let i = 0;
68
+ const tmp = stops;
69
+ for (; i < colorsL; i++) {
70
+ if (stops[i]) {
71
+ tmp[i] = stops[i]!;
72
+ if (stops[i - 1] === undefined && tmp[i - 2] !== undefined) {
73
+ tmp[i - 1] = tmp[i - 2]! + (stops[i]! - tmp[i - 2]!) / 2;
74
+ }
75
+ } else {
76
+ tmp[i] = i * (1 / (colors.length - 1));
77
+ }
70
78
  }
79
+ stops = tmp;
71
80
  }
72
81
  return {
73
82
  colors,
@@ -94,28 +103,6 @@ export class LinearGradientEffect extends ShaderEffect {
94
103
  },
95
104
  stops: {
96
105
  value: [],
97
- validator: (
98
- value: number[],
99
- props: LinearGradientEffectProps,
100
- ): number[] => {
101
- const colors = props.colors ?? [];
102
- let stops = value;
103
- const tmp: number[] = value;
104
- if (stops.length === 0 || (stops && stops.length !== colors.length)) {
105
- for (let i = 0; i < colors.length; i++) {
106
- if (stops[i]) {
107
- tmp[i] = stops[i]!;
108
- if (stops[i - 1] === undefined && tmp[i - 2] !== undefined) {
109
- tmp[i - 1] = tmp[i - 2]! + (stops[i]! - tmp[i - 2]!) / 2;
110
- }
111
- } else {
112
- tmp[i] = i * (1 / (colors.length - 1));
113
- }
114
- }
115
- stops = tmp;
116
- }
117
- return tmp;
118
- },
119
106
  size: (props: LinearGradientEffectProps) => props.colors!.length,
120
107
  method: 'uniform1fv',
121
108
  type: 'float',
@@ -166,10 +153,7 @@ export class LinearGradientEffect extends ShaderEffect {
166
153
 
167
154
  float stopCalc = (dist - stops[0]) / (stops[1] - stops[0]);
168
155
  vec4 colorOut = $fromLinear(mix($toLinear(colors[0]), $toLinear(colors[1]), stopCalc));
169
- for(int i = 1; i < ${colors}-1; i++) {
170
- stopCalc = (dist - stops[i]) / (stops[i + 1] - stops[i]);
171
- colorOut = mix(colorOut, colors[i + 1], clamp(stopCalc, 0.0, 1.0));
172
- }
156
+ ${this.ColorLoop(colors)}
173
157
  return mix(maskColor, colorOut, clamp(colorOut.a, 0.0, 1.0));
174
158
  `;
175
159
  };
@@ -66,13 +66,22 @@ export class RadialGradientEffect extends ShaderEffect {
66
66
  ): Required<RadialGradientEffectProps> {
67
67
  const colors = props.colors ?? [0xff000000, 0xffffffff];
68
68
 
69
- let stops = props.stops;
70
- if (!stops) {
71
- stops = [];
72
- const calc = colors.length - 1;
73
- for (let i = 0; i < colors.length; i++) {
74
- stops.push(i * (1 / calc));
69
+ let stops = props.stops || [];
70
+ if (stops.length === 0 || stops.length !== colors.length) {
71
+ const colorsL = colors.length;
72
+ let i = 0;
73
+ const tmp = stops;
74
+ for (; i < colorsL; i++) {
75
+ if (stops[i]) {
76
+ tmp[i] = stops[i]!;
77
+ if (stops[i - 1] === undefined && tmp[i - 2] !== undefined) {
78
+ tmp[i - 1] = tmp[i - 2]! + (stops[i]! - tmp[i - 2]!) / 2;
79
+ }
80
+ } else {
81
+ tmp[i] = i * (1 / (colors.length - 1));
82
+ }
75
83
  }
84
+ stops = tmp;
76
85
  }
77
86
  return {
78
87
  colors,
@@ -111,34 +120,22 @@ export class RadialGradientEffect extends ShaderEffect {
111
120
  },
112
121
  stops: {
113
122
  value: [],
114
- validator: (
115
- value: number[],
116
- props: RadialGradientEffectProps,
117
- ): number[] => {
118
- const colors = props.colors ?? [];
119
- let stops = value;
120
- const tmp: number[] = value;
121
- if (stops.length === 0 || (stops && stops.length !== colors.length)) {
122
- for (let i = 0; i < colors.length; i++) {
123
- if (stops[i]) {
124
- tmp[i] = stops[i]!;
125
- if (stops[i - 1] === undefined && tmp[i - 2] !== undefined) {
126
- tmp[i - 1] = tmp[i - 2]! + (stops[i]! - tmp[i - 2]!) / 2;
127
- }
128
- } else {
129
- tmp[i] = i * (1 / (colors.length - 1));
130
- }
131
- }
132
- stops = tmp;
133
- }
134
- return tmp;
135
- },
136
123
  size: (props: RadialGradientEffectProps) => props.colors!.length,
137
124
  method: 'uniform1fv',
138
125
  type: 'float',
139
126
  },
140
127
  };
141
128
 
129
+ static ColorLoop = (amount: number): string => {
130
+ let loop = '';
131
+ for (let i = 2; i < amount; i++) {
132
+ loop += `colorOut = mix(colorOut, colors[${i}], clamp((dist - stops[${
133
+ i - 1
134
+ }]) / (stops[${i}] - stops[${i - 1}]), 0.0, 1.0));`;
135
+ }
136
+ return loop;
137
+ };
138
+
142
139
  static override onColorize = (props: RadialGradientEffectProps) => {
143
140
  const colors = props.colors!.length || 1;
144
141
  return `
@@ -149,10 +146,7 @@ export class RadialGradientEffect extends ShaderEffect {
149
146
 
150
147
  float stopCalc = (dist - stops[0]) / (stops[1] - stops[0]);
151
148
  vec4 colorOut = mix(colors[0], colors[1], stopCalc);
152
- for(int i = 1; i < ${colors}-1; i++) {
153
- stopCalc = (dist - stops[i]) / (stops[i + 1] - stops[i]);
154
- colorOut = mix(colorOut, colors[i + 1], clamp(stopCalc, 0.0, 1.0));
155
- }
149
+ ${this.ColorLoop(colors)}
156
150
  return mix(maskColor, colorOut, clamp(colorOut.a, 0.0, 1.0));
157
151
  `;
158
152
  };
@@ -290,6 +290,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
290
290
  textH: 0,
291
291
  fontInfo: undefined,
292
292
  fontFaceLoadedHandler: undefined,
293
+ isRenderable: false,
293
294
  debugData: {
294
295
  updateCount: 0,
295
296
  layoutCount: 0,
@@ -500,9 +501,11 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
500
501
  for (const pageInfo of canvasPages) {
501
502
  if (pageInfo.valid) continue;
502
503
  if (pageInfo.lineNumStart < 0) {
504
+ pageInfo.texture?.setRenderableOwner(state, false);
503
505
  pageInfo.texture = this.stage.txManager.loadTexture('ImageTexture', {
504
506
  src: '',
505
507
  });
508
+ pageInfo.texture.setRenderableOwner(state, state.isRenderable);
506
509
  pageInfo.valid = true;
507
510
  continue;
508
511
  }
@@ -517,6 +520,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
517
520
  ),
518
521
  });
519
522
  if (!(this.canvas.width === 0 || this.canvas.height === 0)) {
523
+ pageInfo.texture?.setRenderableOwner(state, false);
520
524
  pageInfo.texture = this.stage.txManager.loadTexture(
521
525
  'ImageTexture',
522
526
  {
@@ -531,6 +535,7 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
531
535
  preload: true,
532
536
  },
533
537
  );
538
+ pageInfo.texture.setRenderableOwner(state, state.isRenderable);
534
539
  }
535
540
  pageInfo.valid = true;
536
541
  }
@@ -694,6 +699,24 @@ export class CanvasTextRenderer extends TextRenderer<CanvasTextRendererState> {
694
699
  // );
695
700
  // }
696
701
  }
702
+
703
+ override setIsRenderable(
704
+ state: CanvasTextRendererState,
705
+ renderable: boolean,
706
+ ): void {
707
+ super.setIsRenderable(state, renderable);
708
+ // Set state object owner from any canvas page textures
709
+ state.canvasPages?.forEach((pageInfo) => {
710
+ pageInfo.texture?.setRenderableOwner(state, renderable);
711
+ });
712
+ }
713
+
714
+ override destroyState(state: CanvasTextRendererState): void {
715
+ // Remove state object owner from any canvas page textures
716
+ state.canvasPages?.forEach((pageInfo) => {
717
+ pageInfo.texture?.setRenderableOwner(state, false);
718
+ });
719
+ }
697
720
  //#endregion Overrides
698
721
 
699
722
  /**
@@ -150,22 +150,22 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
150
150
  return {
151
151
  fontFamily: (state, value) => {
152
152
  state.props.fontFamily = value;
153
- state.trFontFace = undefined;
153
+ this.releaseFontFace(state);
154
154
  this.invalidateLayoutCache(state);
155
155
  },
156
156
  fontWeight: (state, value) => {
157
157
  state.props.fontWeight = value;
158
- state.trFontFace = undefined;
158
+ this.releaseFontFace(state);
159
159
  this.invalidateLayoutCache(state);
160
160
  },
161
161
  fontStyle: (state, value) => {
162
162
  state.props.fontStyle = value;
163
- state.trFontFace = undefined;
163
+ this.releaseFontFace(state);
164
164
  this.invalidateLayoutCache(state);
165
165
  },
166
166
  fontStretch: (state, value) => {
167
167
  state.props.fontStretch = value;
168
- state.trFontFace = undefined;
168
+ this.releaseFontFace(state);
169
169
  this.invalidateLayoutCache(state);
170
170
  },
171
171
  fontSize: (state, value) => {
@@ -360,6 +360,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
360
360
  textW: undefined,
361
361
  distanceRange: 0,
362
362
  trFontFace: undefined,
363
+ isRenderable: false,
363
364
  debugData: {
364
365
  updateCount: 0,
365
366
  layoutCount: 0,
@@ -392,6 +393,7 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
392
393
  this.setStatus(state, 'failed', new Error(msg));
393
394
  return;
394
395
  }
396
+ trFontFace.texture.setRenderableOwner(state, state.isRenderable);
395
397
  }
396
398
 
397
399
  // If the font hasn't been loaded yet, stop here.
@@ -741,6 +743,20 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
741
743
  // debugData.drawCount++;
742
744
  // }
743
745
  }
746
+
747
+ override setIsRenderable(
748
+ state: SdfTextRendererState,
749
+ renderable: boolean,
750
+ ): void {
751
+ super.setIsRenderable(state, renderable);
752
+ state.trFontFace?.texture.setRenderableOwner(state, renderable);
753
+ }
754
+
755
+ override destroyState(state: SdfTextRendererState): void {
756
+ super.destroyState(state);
757
+ // If there's a Font Face assigned we must free the owner relation to its texture
758
+ state.trFontFace?.texture.setRenderableOwner(state, false);
759
+ }
744
760
  //#endregion Overrides
745
761
 
746
762
  public resolveFontFace(props: TrFontProps): SdfTrFontFace | undefined {
@@ -749,6 +765,18 @@ export class SdfTextRenderer extends TextRenderer<SdfTextRendererState> {
749
765
  | undefined;
750
766
  }
751
767
 
768
+ /**
769
+ * Release the loaded SDF font face
770
+ *
771
+ * @param state
772
+ */
773
+ protected releaseFontFace(state: SdfTextRendererState) {
774
+ if (state.trFontFace) {
775
+ state.trFontFace.texture.setRenderableOwner(state, false);
776
+ state.trFontFace = undefined;
777
+ }
778
+ }
779
+
752
780
  /**
753
781
  * Invalidate the layout cache stored in the state. This will cause the text
754
782
  * to be re-layed out on the next update.
@@ -212,8 +212,6 @@ export function layoutText(
212
212
  charEndX >= lineVertexW &&
213
213
  // There is a last word that we can break to the next line
214
214
  lastWord.codepointIndex !== -1 &&
215
- // We have advanced at least one character since the last word started
216
- lastWord.codepointIndex < glyph.cluster &&
217
215
  // Prevents infinite loop when a single word is longer than the width
218
216
  lastWord.xStart > 0
219
217
  ) {
@@ -237,6 +235,9 @@ export function layoutText(
237
235
  );
238
236
  curX = lastWord.xStart;
239
237
  bufferOffset = lastWord.bufferOffset;
238
+ // HACK: For the rest of the line when inserting the overflow suffix,
239
+ // set contain = 'none' to prevent an infinite loop.
240
+ contain = 'none';
240
241
  }
241
242
  } else {
242
243
  // This glyph fits, so we can add it to the buffer
@@ -286,8 +287,8 @@ export function layoutText(
286
287
  }
287
288
 
288
289
  maxY = Math.max(maxY, quadY + glyph.height);
290
+ maxX = Math.max(maxX, quadX + glyph.width);
289
291
  curX += glyph.xAdvance;
290
- maxX = Math.max(maxX, curX);
291
292
  }
292
293
  } else {
293
294
  // Unmapped character