@lightningjs/renderer 0.7.6 → 0.8.0

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 (102) 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 +63 -6
  4. package/dist/src/core/CoreNode.js +97 -20
  5. package/dist/src/core/CoreNode.js.map +1 -1
  6. package/dist/src/core/CoreTextNode.d.ts +5 -0
  7. package/dist/src/core/CoreTextNode.js +15 -10
  8. package/dist/src/core/CoreTextNode.js.map +1 -1
  9. package/dist/src/core/CoreTextureManager.js +2 -0
  10. package/dist/src/core/CoreTextureManager.js.map +1 -1
  11. package/dist/src/core/Stage.d.ts +4 -0
  12. package/dist/src/core/Stage.js +8 -1
  13. package/dist/src/core/Stage.js.map +1 -1
  14. package/dist/src/core/TextureMemoryManager.d.ts +12 -0
  15. package/dist/src/core/TextureMemoryManager.js +42 -0
  16. package/dist/src/core/TextureMemoryManager.js.map +1 -0
  17. package/dist/src/core/platform.js +8 -0
  18. package/dist/src/core/platform.js.map +1 -1
  19. package/dist/src/core/renderers/CoreContextTexture.d.ts +5 -1
  20. package/dist/src/core/renderers/CoreContextTexture.js +3 -1
  21. package/dist/src/core/renderers/CoreContextTexture.js.map +1 -1
  22. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.d.ts +2 -1
  23. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js +2 -2
  24. package/dist/src/core/renderers/webgl/WebGlCoreCtxSubTexture.js.map +1 -1
  25. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.d.ts +3 -1
  26. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js +22 -5
  27. package/dist/src/core/renderers/webgl/WebGlCoreCtxTexture.js.map +1 -1
  28. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.d.ts +3 -0
  29. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js +4 -2
  30. package/dist/src/core/renderers/webgl/WebGlCoreRenderer.js.map +1 -1
  31. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js +17 -30
  32. package/dist/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.js.map +1 -1
  33. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.d.ts +1 -0
  34. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js +24 -30
  35. package/dist/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.js.map +1 -1
  36. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.d.ts +2 -0
  37. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js +18 -0
  38. package/dist/src/core/text-rendering/renderers/CanvasTextRenderer.js.map +1 -1
  39. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.d.ts +8 -0
  40. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js +26 -4
  41. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.js.map +1 -1
  42. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js +1 -3
  43. package/dist/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.js.map +1 -1
  44. package/dist/src/core/text-rendering/renderers/TextRenderer.d.ts +19 -0
  45. package/dist/src/core/text-rendering/renderers/TextRenderer.js +26 -0
  46. package/dist/src/core/text-rendering/renderers/TextRenderer.js.map +1 -1
  47. package/dist/src/core/textures/Texture.d.ts +26 -1
  48. package/dist/src/core/textures/Texture.js +30 -1
  49. package/dist/src/core/textures/Texture.js.map +1 -1
  50. package/dist/src/main-api/ICoreDriver.d.ts +1 -0
  51. package/dist/src/main-api/Inspector.js +2 -1
  52. package/dist/src/main-api/Inspector.js.map +1 -1
  53. package/dist/src/main-api/RendererMain.d.ts +10 -1
  54. package/dist/src/main-api/RendererMain.js +6 -1
  55. package/dist/src/main-api/RendererMain.js.map +1 -1
  56. package/dist/src/render-drivers/main/MainCoreDriver.d.ts +1 -0
  57. package/dist/src/render-drivers/main/MainCoreDriver.js +7 -0
  58. package/dist/src/render-drivers/main/MainCoreDriver.js.map +1 -1
  59. package/dist/src/render-drivers/main/MainOnlyNode.d.ts +1 -0
  60. package/dist/src/render-drivers/main/MainOnlyNode.js +10 -6
  61. package/dist/src/render-drivers/main/MainOnlyNode.js.map +1 -1
  62. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js +1 -0
  63. package/dist/src/render-drivers/threadx/ThreadXCoreDriver.js.map +1 -1
  64. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.d.ts +1 -0
  65. package/dist/src/render-drivers/threadx/ThreadXRendererMessage.js.map +1 -1
  66. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js +3 -0
  67. package/dist/src/render-drivers/threadx/worker/ThreadXRendererNode.js.map +1 -1
  68. package/dist/src/render-drivers/threadx/worker/renderer.js +1 -0
  69. package/dist/src/render-drivers/threadx/worker/renderer.js.map +1 -1
  70. package/dist/src/utils.d.ts +6 -0
  71. package/dist/src/utils.js +9 -1
  72. package/dist/src/utils.js.map +1 -1
  73. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  74. package/package.json +1 -1
  75. package/src/common/CommonTypes.ts +7 -0
  76. package/src/core/CoreNode.ts +105 -20
  77. package/src/core/CoreTextNode.ts +44 -43
  78. package/src/core/CoreTextureManager.ts +2 -0
  79. package/src/core/Stage.ts +10 -0
  80. package/src/core/TextureMemoryManager.ts +66 -0
  81. package/src/core/platform.ts +8 -0
  82. package/src/core/renderers/CoreContextTexture.ts +6 -1
  83. package/src/core/renderers/webgl/WebGlCoreCtxSubTexture.ts +7 -2
  84. package/src/core/renderers/webgl/WebGlCoreCtxTexture.ts +34 -6
  85. package/src/core/renderers/webgl/WebGlCoreRenderer.ts +10 -2
  86. package/src/core/renderers/webgl/shaders/effects/LinearGradientEffect.ts +16 -32
  87. package/src/core/renderers/webgl/shaders/effects/RadialGradientEffect.ts +26 -32
  88. package/src/core/text-rendering/renderers/CanvasTextRenderer.ts +23 -0
  89. package/src/core/text-rendering/renderers/SdfTextRenderer/SdfTextRenderer.ts +32 -4
  90. package/src/core/text-rendering/renderers/SdfTextRenderer/internal/layoutText.ts +1 -3
  91. package/src/core/text-rendering/renderers/TextRenderer.ts +32 -0
  92. package/src/core/textures/Texture.ts +39 -2
  93. package/src/main-api/ICoreDriver.ts +2 -0
  94. package/src/main-api/Inspector.ts +2 -1
  95. package/src/main-api/RendererMain.ts +19 -2
  96. package/src/render-drivers/main/MainCoreDriver.ts +9 -0
  97. package/src/render-drivers/main/MainOnlyNode.ts +12 -6
  98. package/src/render-drivers/threadx/ThreadXCoreDriver.ts +1 -0
  99. package/src/render-drivers/threadx/ThreadXRendererMessage.ts +1 -0
  100. package/src/render-drivers/threadx/worker/ThreadXRendererNode.ts +7 -0
  101. package/src/render-drivers/threadx/worker/renderer.ts +1 -0
  102. package/src/utils.ts +10 -1
@@ -55,35 +55,32 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
55
55
  super(stage, props);
56
56
  this._textRendererOverride = props.textRendererOverride;
57
57
  const { resolvedTextRenderer, textRendererState } =
58
- this.resolveTextRendererAndState(
59
- {
60
- x: this.absX,
61
- y: this.absY,
62
- width: props.width,
63
- height: props.height,
64
- textAlign: props.textAlign,
65
- color: props.color,
66
- zIndex: props.zIndex,
67
- contain: props.contain,
68
- scrollable: props.scrollable,
69
- scrollY: props.scrollY,
70
- offsetY: props.offsetY,
71
- letterSpacing: props.letterSpacing,
72
- debug: props.debug,
73
- fontFamily: props.fontFamily,
74
- fontSize: props.fontSize,
75
- fontStretch: props.fontStretch,
76
- fontStyle: props.fontStyle,
77
- fontWeight: props.fontWeight,
78
- text: props.text,
79
- lineHeight: props.lineHeight,
80
- maxLines: props.maxLines,
81
- textBaseline: props.textBaseline,
82
- verticalAlign: props.verticalAlign,
83
- overflowSuffix: props.overflowSuffix,
84
- },
85
- undefined,
86
- );
58
+ this.resolveTextRendererAndState({
59
+ x: this.absX,
60
+ y: this.absY,
61
+ width: props.width,
62
+ height: props.height,
63
+ textAlign: props.textAlign,
64
+ color: props.color,
65
+ zIndex: props.zIndex,
66
+ contain: props.contain,
67
+ scrollable: props.scrollable,
68
+ scrollY: props.scrollY,
69
+ offsetY: props.offsetY,
70
+ letterSpacing: props.letterSpacing,
71
+ debug: props.debug,
72
+ fontFamily: props.fontFamily,
73
+ fontSize: props.fontSize,
74
+ fontStretch: props.fontStretch,
75
+ fontStyle: props.fontStyle,
76
+ fontWeight: props.fontWeight,
77
+ text: props.text,
78
+ lineHeight: props.lineHeight,
79
+ maxLines: props.maxLines,
80
+ textBaseline: props.textBaseline,
81
+ verticalAlign: props.verticalAlign,
82
+ overflowSuffix: props.overflowSuffix,
83
+ });
87
84
  this.textRenderer = resolvedTextRenderer;
88
85
  this.trState = textRendererState;
89
86
  }
@@ -179,8 +176,10 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
179
176
  set textRendererOverride(value: CoreTextNodeProps['textRendererOverride']) {
180
177
  this._textRendererOverride = value;
181
178
 
179
+ this.textRenderer.destroyState(this.trState);
180
+
182
181
  const { resolvedTextRenderer, textRendererState } =
183
- this.resolveTextRendererAndState(this.trState.props, this.trState);
182
+ this.resolveTextRendererAndState(this.trState.props);
184
183
  this.textRenderer = resolvedTextRenderer;
185
184
  this.trState = textRendererState;
186
185
  }
@@ -348,6 +347,11 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
348
347
  return super.checkRenderProps();
349
348
  }
350
349
 
350
+ override onChangeIsRenderable(isRenderable: boolean) {
351
+ super.onChangeIsRenderable(isRenderable);
352
+ this.textRenderer.setIsRenderable(this.trState, isRenderable);
353
+ }
354
+
351
355
  override renderQuads(renderer: CoreRenderer) {
352
356
  assertTruthy(this.globalTransform);
353
357
  this.textRenderer.renderQuads(
@@ -358,15 +362,21 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
358
362
  );
359
363
  }
360
364
 
365
+ /**
366
+ * Destroy the node and cleanup all resources
367
+ */
368
+ override destroy(): void {
369
+ super.destroy();
370
+
371
+ this.textRenderer.destroyState(this.trState);
372
+ }
373
+
361
374
  /**
362
375
  * Resolve a text renderer and a new state based on the current text renderer props provided
363
376
  * @param props
364
377
  * @returns
365
378
  */
366
- private resolveTextRendererAndState(
367
- props: TrProps,
368
- prevState?: TextRendererState,
369
- ): {
379
+ private resolveTextRendererAndState(props: TrProps): {
370
380
  resolvedTextRenderer: TextRenderer;
371
381
  textRendererState: TextRendererState;
372
382
  } {
@@ -377,15 +387,6 @@ export class CoreTextNode extends CoreNode implements ICoreTextNode {
377
387
 
378
388
  const textRendererState = resolvedTextRenderer.createState(props);
379
389
 
380
- const stateEvents = ['loading', 'loaded', 'failed'];
381
-
382
- if (prevState) {
383
- // Remove the old event listeners from previous state obj there was one
384
- stateEvents.forEach((eventName) => {
385
- prevState.emitter.off(eventName);
386
- });
387
- }
388
-
389
390
  textRendererState.emitter.on('loaded', this.onTextLoaded);
390
391
  textRendererState.emitter.on('failed', this.onTextFailed);
391
392
 
@@ -288,6 +288,8 @@ export class CoreTextureManager {
288
288
  }
289
289
  }
290
290
  }
291
+ // Free the ctx texture if it exists.
292
+ this.ctxTextureCache.get(texture)?.free();
291
293
  }
292
294
 
293
295
  /**
package/src/core/Stage.ts CHANGED
@@ -37,11 +37,13 @@ import type {
37
37
  FpsUpdatePayload,
38
38
  FrameTickPayload,
39
39
  } from '../common/CommonTypes.js';
40
+ import { TextureMemoryManager } from './TextureMemoryManager.js';
40
41
 
41
42
  export interface StageOptions {
42
43
  rootId: number;
43
44
  appWidth: number;
44
45
  appHeight: number;
46
+ txMemByteThreshold: number;
45
47
  boundsMargin: number | [number, number, number, number];
46
48
  deviceLogicalPixelRatio: number;
47
49
  devicePhysicalPixelRatio: number;
@@ -73,6 +75,7 @@ export class Stage extends EventEmitter {
73
75
  /// Module Instances
74
76
  public readonly animationManager: AnimationManager;
75
77
  public readonly txManager: CoreTextureManager;
78
+ public readonly txMemManager: TextureMemoryManager;
76
79
  public readonly fontManager: TrFontManager;
77
80
  public readonly textRenderers: Partial<TextRendererMap>;
78
81
  public readonly shManager: CoreShaderManager;
@@ -106,9 +109,11 @@ export class Stage extends EventEmitter {
106
109
  boundsMargin,
107
110
  enableContextSpy,
108
111
  numImageWorkers,
112
+ txMemByteThreshold,
109
113
  } = options;
110
114
 
111
115
  this.txManager = new CoreTextureManager(numImageWorkers);
116
+ this.txMemManager = new TextureMemoryManager(txMemByteThreshold);
112
117
  this.shManager = new CoreShaderManager();
113
118
  this.animationManager = new AnimationManager();
114
119
  this.contextSpy = enableContextSpy ? new ContextSpy() : null;
@@ -138,6 +143,7 @@ export class Stage extends EventEmitter {
138
143
  clearColor: clearColor ?? 0xff000000,
139
144
  bufferMemory,
140
145
  txManager: this.txManager,
146
+ txMemManager: this.txMemManager,
141
147
  shManager: this.shManager,
142
148
  contextSpy: this.contextSpy,
143
149
  });
@@ -243,11 +249,15 @@ export class Stage extends EventEmitter {
243
249
 
244
250
  renderer?.render();
245
251
 
252
+ this.calculateFps();
253
+
246
254
  // Reset renderRequested flag if it was set
247
255
  if (renderRequested) {
248
256
  this.renderRequested = false;
249
257
  }
258
+ }
250
259
 
260
+ calculateFps() {
251
261
  // If there's an FPS update interval, emit the FPS update event
252
262
  // when the specified interval has elapsed.
253
263
  const { fpsUpdateInterval } = this.options;
@@ -0,0 +1,66 @@
1
+ /*
2
+ * If not stated otherwise in this file or this component's LICENSE file the
3
+ * following copyright and licenses apply:
4
+ *
5
+ * Copyright 2024 Comcast Cable Communications Management, LLC.
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the License);
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ */
19
+ import type { CoreContextTexture } from './renderers/CoreContextTexture.js';
20
+
21
+ export class TextureMemoryManager {
22
+ private memUsed = 0;
23
+ private textures: Map<CoreContextTexture, number> = new Map();
24
+ private threshold: number;
25
+
26
+ /**
27
+ * @param byteThreshold Number of texture bytes to trigger garbage collection
28
+ */
29
+ constructor(byteThreshold: number) {
30
+ this.threshold = byteThreshold;
31
+
32
+ // If the threshold is 0, we disable the memory manager by replacing the
33
+ // setTextureMemUse method with a no-op function.
34
+ if (byteThreshold === 0) {
35
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
36
+ this.setTextureMemUse = () => {};
37
+ }
38
+ }
39
+
40
+ setTextureMemUse(ctxTexture: CoreContextTexture, byteSize: number) {
41
+ if (this.textures.has(ctxTexture)) {
42
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
43
+ this.memUsed -= this.textures.get(ctxTexture)!;
44
+ }
45
+
46
+ if (byteSize === 0) {
47
+ this.textures.delete(ctxTexture);
48
+ return;
49
+ } else {
50
+ this.memUsed += byteSize;
51
+ this.textures.set(ctxTexture, byteSize);
52
+ }
53
+
54
+ if (this.memUsed > this.threshold) {
55
+ this.gc();
56
+ }
57
+ }
58
+
59
+ gc() {
60
+ this.textures.forEach((byteSize, ctxTexture) => {
61
+ if (!ctxTexture.renderable) {
62
+ ctxTexture.free();
63
+ }
64
+ });
65
+ }
66
+ }
@@ -23,14 +23,22 @@ import type { Stage } from './Stage.js';
23
23
  * Platform render loop initiator
24
24
  */
25
25
  export const startLoop = (stage: Stage) => {
26
+ let isIdle = false;
26
27
  const runLoop = () => {
27
28
  stage.updateAnimations();
28
29
 
29
30
  if (!stage.hasSceneUpdates()) {
31
+ // We still need to calculate the fps else it looks like the app is frozen
32
+ stage.calculateFps();
30
33
  setTimeout(runLoop, 16.666666666666668);
34
+ if (!isIdle) {
35
+ stage.emit('idle');
36
+ isIdle = true;
37
+ }
31
38
  return;
32
39
  }
33
40
 
41
+ isIdle = false;
34
42
  stage.drawFrame();
35
43
  requestAnimationFrame(runLoop);
36
44
  };
@@ -17,14 +17,19 @@
17
17
  * limitations under the License.
18
18
  */
19
19
 
20
+ import type { TextureMemoryManager } from '../TextureMemoryManager.js';
20
21
  import type { Texture } from '../textures/Texture.js';
21
22
 
22
23
  export abstract class CoreContextTexture {
24
+ readonly memManager: TextureMemoryManager;
23
25
  readonly textureSource: Texture;
24
26
 
25
- constructor(textureSource: Texture) {
27
+ constructor(memManager: TextureMemoryManager, textureSource: Texture) {
28
+ this.memManager = memManager;
26
29
  this.textureSource = textureSource;
27
30
  }
28
31
 
29
32
  abstract load(): void;
33
+ abstract free(): void;
34
+ abstract get renderable(): boolean;
30
35
  }
@@ -18,13 +18,18 @@
18
18
  */
19
19
 
20
20
  import type { Dimensions } from '../../../common/CommonTypes.js';
21
+ import type { TextureMemoryManager } from '../../TextureMemoryManager.js';
21
22
  import type { WebGlContextWrapper } from '../../lib/WebGlContextWrapper.js';
22
23
  import type { SubTexture } from '../../textures/SubTexture.js';
23
24
  import { WebGlCoreCtxTexture } from './WebGlCoreCtxTexture.js';
24
25
 
25
26
  export class WebGlCoreCtxSubTexture extends WebGlCoreCtxTexture {
26
- constructor(glw: WebGlContextWrapper, textureSource: SubTexture) {
27
- super(glw, textureSource);
27
+ constructor(
28
+ glw: WebGlContextWrapper,
29
+ memManager: TextureMemoryManager,
30
+ textureSource: SubTexture,
31
+ ) {
32
+ super(glw, memManager, textureSource);
28
33
  }
29
34
 
30
35
  override async onLoadRequest(): Promise<Dimensions> {
@@ -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
  /**
@@ -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
  };