@solidtv/renderer 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/common/EventEmitter.d.ts +8 -0
- package/dist/src/common/EventEmitter.js +20 -0
- package/dist/src/common/EventEmitter.js.map +1 -1
- package/dist/src/core/Autosizer.js +3 -1
- package/dist/src/core/Autosizer.js.map +1 -1
- package/dist/src/core/CoreNode.d.ts +39 -0
- package/dist/src/core/CoreNode.js +115 -25
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/CoreTextNode.js +11 -4
- package/dist/src/core/CoreTextNode.js.map +1 -1
- package/dist/src/core/Stage.d.ts +6 -0
- package/dist/src/core/Stage.js +9 -0
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/TextureMemoryManager.d.ts +39 -2
- package/dist/src/core/TextureMemoryManager.js +53 -1
- package/dist/src/core/TextureMemoryManager.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasRenderer.js +5 -1
- package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlRenderer.js +5 -1
- package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.d.ts +28 -0
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +70 -10
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
- package/dist/src/main-api/Renderer.d.ts +23 -0
- package/dist/src/main-api/Renderer.js +2 -0
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/common/EventEmitter.ts +21 -0
- package/src/core/Autosizer.ts +3 -1
- package/src/core/CoreNode.test.ts +425 -0
- package/src/core/CoreNode.ts +146 -26
- package/src/core/CoreTextNode.test.ts +62 -0
- package/src/core/CoreTextNode.ts +13 -4
- package/src/core/Stage.ts +9 -0
- package/src/core/TextureMemoryManager.test.ts +272 -5
- package/src/core/TextureMemoryManager.ts +59 -2
- package/src/core/renderers/canvas/CanvasRenderer.ts +7 -1
- package/src/core/renderers/webgl/WebGlRenderer.ts +6 -1
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +78 -15
- package/src/core/renderers/webgl/WebGlShaderProgram.uniformDedup.test.ts +233 -0
- package/src/main-api/Renderer.ts +26 -0
package/src/core/CoreNode.ts
CHANGED
|
@@ -392,6 +392,23 @@ export interface CoreNodeProps {
|
|
|
392
392
|
* rendering.
|
|
393
393
|
*/
|
|
394
394
|
colorBl: number;
|
|
395
|
+
/**
|
|
396
|
+
* Placeholder color shown while the Node's texture is not yet loaded.
|
|
397
|
+
*
|
|
398
|
+
* @remarks
|
|
399
|
+
* When set to a non-zero color and the Node has a texture (e.g. {@link src}),
|
|
400
|
+
* the Node renders a solid rectangle of this color until the texture
|
|
401
|
+
* finishes loading, instead of rendering nothing. The placeholder renders
|
|
402
|
+
* through the Node's shader, so rounded corners and borders apply to it.
|
|
403
|
+
* It also shows again if the texture is freed under memory pressure (until
|
|
404
|
+
* the reload completes) and remains if the texture permanently fails.
|
|
405
|
+
*
|
|
406
|
+
* The color value is a number in the format 0xRRGGBBAA. Set to `0` to
|
|
407
|
+
* disable (default). Has no effect on Nodes without a texture.
|
|
408
|
+
*
|
|
409
|
+
* @default `0`
|
|
410
|
+
*/
|
|
411
|
+
placeholderColor: number;
|
|
395
412
|
/**
|
|
396
413
|
* The Node's parent Node.
|
|
397
414
|
*
|
|
@@ -793,6 +810,15 @@ export class CoreNode extends EventEmitter {
|
|
|
793
810
|
private hasColorProps = false;
|
|
794
811
|
public textureLoaded = false;
|
|
795
812
|
|
|
813
|
+
/**
|
|
814
|
+
* True while this node should render its `placeholderColor` instead of its
|
|
815
|
+
* texture: `placeholderColor` is non-zero, a texture is set, and that
|
|
816
|
+
* texture is not loaded. Read by the renderers' quad path to substitute the
|
|
817
|
+
* stage's default (1x1 white) texture. Maintained by
|
|
818
|
+
* {@link updatePlaceholderActive} — never written elsewhere.
|
|
819
|
+
*/
|
|
820
|
+
public placeholderActive = false;
|
|
821
|
+
|
|
796
822
|
public updateType = UpdateType.All;
|
|
797
823
|
public childUpdateType = UpdateType.None;
|
|
798
824
|
|
|
@@ -953,6 +979,31 @@ export class CoreNode extends EventEmitter {
|
|
|
953
979
|
}
|
|
954
980
|
|
|
955
981
|
//#region Textures
|
|
982
|
+
/**
|
|
983
|
+
* Recompute {@link placeholderActive} after any of its inputs changed
|
|
984
|
+
* (placeholderColor, texture, textureLoaded).
|
|
985
|
+
*
|
|
986
|
+
* @remarks
|
|
987
|
+
* On a toggle this raises `PremultipliedColors` (the quad's vertex colors
|
|
988
|
+
* switch between the placeholder color and the regular color props — this
|
|
989
|
+
* also marks the quad dirty) and `IsRenderable` (a loading texture with a
|
|
990
|
+
* placeholder is renderable). Both are processed in the same frame's update
|
|
991
|
+
* pass, before quads are submitted.
|
|
992
|
+
*/
|
|
993
|
+
private updatePlaceholderActive(): void {
|
|
994
|
+
const active =
|
|
995
|
+
this.props.placeholderColor !== 0 &&
|
|
996
|
+
this.props.texture !== null &&
|
|
997
|
+
this.textureLoaded === false;
|
|
998
|
+
|
|
999
|
+
if (active !== this.placeholderActive) {
|
|
1000
|
+
this.placeholderActive = active;
|
|
1001
|
+
this.setUpdateType(
|
|
1002
|
+
UpdateType.PremultipliedColors | UpdateType.IsRenderable,
|
|
1003
|
+
);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
956
1007
|
loadTexture(): void {
|
|
957
1008
|
if (this.props.texture === null) {
|
|
958
1009
|
return;
|
|
@@ -1023,6 +1074,7 @@ export class CoreNode extends EventEmitter {
|
|
|
1023
1074
|
}
|
|
1024
1075
|
|
|
1025
1076
|
this.textureLoaded = true;
|
|
1077
|
+
this.updatePlaceholderActive();
|
|
1026
1078
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
1027
1079
|
|
|
1028
1080
|
// Texture was loaded. In case the RAF loop has already stopped, we request
|
|
@@ -1057,9 +1109,12 @@ export class CoreNode extends EventEmitter {
|
|
|
1057
1109
|
|
|
1058
1110
|
private onTextureFailed: TextureFailedEventHandler = (_, error) => {
|
|
1059
1111
|
// immediately set isRenderable to false, so that we handle the error
|
|
1060
|
-
// without waiting for the next frame loop
|
|
1112
|
+
// without waiting for the next frame loop. With a placeholder set, the
|
|
1113
|
+
// same frame's update pass recomputes this to true and renders the
|
|
1114
|
+
// placeholder instead.
|
|
1061
1115
|
this.textureLoaded = false;
|
|
1062
1116
|
this.isRenderable = false;
|
|
1117
|
+
this.updatePlaceholderActive();
|
|
1063
1118
|
this.updateTextureOwnership(false);
|
|
1064
1119
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
1065
1120
|
|
|
@@ -1081,9 +1136,12 @@ export class CoreNode extends EventEmitter {
|
|
|
1081
1136
|
|
|
1082
1137
|
private onTextureFreed: TextureFreedEventHandler = () => {
|
|
1083
1138
|
// immediately set isRenderable to false, so that we handle the error
|
|
1084
|
-
// without waiting for the next frame loop
|
|
1139
|
+
// without waiting for the next frame loop. With a placeholder set, the
|
|
1140
|
+
// same frame's update pass recomputes this to true and renders the
|
|
1141
|
+
// placeholder while the texture reloads.
|
|
1085
1142
|
this.textureLoaded = false;
|
|
1086
1143
|
this.isRenderable = false;
|
|
1144
|
+
this.updatePlaceholderActive();
|
|
1087
1145
|
this.updateTextureOwnership(false);
|
|
1088
1146
|
this.setUpdateType(UpdateType.IsRenderable);
|
|
1089
1147
|
|
|
@@ -1355,7 +1413,13 @@ export class CoreNode extends EventEmitter {
|
|
|
1355
1413
|
this.calculateRenderCoords();
|
|
1356
1414
|
this.updateBoundingRect();
|
|
1357
1415
|
|
|
1358
|
-
|
|
1416
|
+
// RecalcUniforms is intentionally NOT set here: shader uniforms are a
|
|
1417
|
+
// function of resolvedProps + w/h only (that is exactly the shader
|
|
1418
|
+
// value-key cache key), so pure transform changes (translate, scale,
|
|
1419
|
+
// rotate) cannot affect them. The flag is raised where w/h actually
|
|
1420
|
+
// change: the w/h setters, Autosizer.applyDimensions, text layout
|
|
1421
|
+
// application, and the shader setter itself.
|
|
1422
|
+
updateType |= UpdateType.RenderState;
|
|
1359
1423
|
|
|
1360
1424
|
//only propagate children updates if not autosizing
|
|
1361
1425
|
if ((updateType & UpdateType.Autosize) === 0) {
|
|
@@ -1420,27 +1484,39 @@ export class CoreNode extends EventEmitter {
|
|
|
1420
1484
|
if (updateType & UpdateType.PremultipliedColors) {
|
|
1421
1485
|
const alpha = this.worldAlpha;
|
|
1422
1486
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
const merged = premultiplyColorABGR(tl, alpha);
|
|
1432
|
-
|
|
1433
|
-
this.premultipliedColorTl = merged;
|
|
1434
|
-
|
|
1435
|
-
if (same === true) {
|
|
1436
|
-
this.premultipliedColorTr =
|
|
1487
|
+
if (this.placeholderActive === true) {
|
|
1488
|
+
// Placeholder rendering: all four corners take the placeholder color.
|
|
1489
|
+
// The quad samples the stage's default 1x1 white texture, so this is
|
|
1490
|
+
// exactly the color-rect path.
|
|
1491
|
+
const merged = premultiplyColorABGR(props.placeholderColor, alpha);
|
|
1492
|
+
this.premultipliedColorTl =
|
|
1493
|
+
this.premultipliedColorTr =
|
|
1437
1494
|
this.premultipliedColorBl =
|
|
1438
1495
|
this.premultipliedColorBr =
|
|
1439
1496
|
merged;
|
|
1440
1497
|
} else {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1498
|
+
const tl = props.colorTl;
|
|
1499
|
+
const tr = props.colorTr;
|
|
1500
|
+
const bl = props.colorBl;
|
|
1501
|
+
const br = props.colorBr;
|
|
1502
|
+
|
|
1503
|
+
// Fast equality check (covers all 4 corners)
|
|
1504
|
+
const same = tl === tr && tl === bl && tl === br;
|
|
1505
|
+
|
|
1506
|
+
const merged = premultiplyColorABGR(tl, alpha);
|
|
1507
|
+
|
|
1508
|
+
this.premultipliedColorTl = merged;
|
|
1509
|
+
|
|
1510
|
+
if (same === true) {
|
|
1511
|
+
this.premultipliedColorTr =
|
|
1512
|
+
this.premultipliedColorBl =
|
|
1513
|
+
this.premultipliedColorBr =
|
|
1514
|
+
merged;
|
|
1515
|
+
} else {
|
|
1516
|
+
this.premultipliedColorTr = premultiplyColorABGR(tr, alpha);
|
|
1517
|
+
this.premultipliedColorBl = premultiplyColorABGR(bl, alpha);
|
|
1518
|
+
this.premultipliedColorBr = premultiplyColorABGR(br, alpha);
|
|
1519
|
+
}
|
|
1444
1520
|
}
|
|
1445
1521
|
}
|
|
1446
1522
|
|
|
@@ -1754,15 +1830,22 @@ export class CoreNode extends EventEmitter {
|
|
|
1754
1830
|
// preemptive check for failed textures this will mark the current node as non-renderable
|
|
1755
1831
|
// and will prevent further checks until the texture is reloaded or retry is reset on the texture
|
|
1756
1832
|
if (this.texture.retryCount > this.texture.maxRetryCount) {
|
|
1757
|
-
// texture has failed to load, we cannot render
|
|
1833
|
+
// texture has failed to load, we cannot render the texture itself —
|
|
1834
|
+
// but a placeholder color still renders in its place
|
|
1758
1835
|
this.updateTextureOwnership(false);
|
|
1759
|
-
this.setRenderable(
|
|
1836
|
+
this.setRenderable(
|
|
1837
|
+
this.placeholderActive === true &&
|
|
1838
|
+
(this.stage.renderOnlyInViewport === false ||
|
|
1839
|
+
this.renderState === CoreNodeRenderState.InViewport),
|
|
1840
|
+
);
|
|
1760
1841
|
return;
|
|
1761
1842
|
}
|
|
1762
1843
|
|
|
1763
1844
|
needsTextureOwnership = true;
|
|
1764
|
-
// Use cached boolean instead of string comparison
|
|
1765
|
-
|
|
1845
|
+
// Use cached boolean instead of string comparison; a placeholder
|
|
1846
|
+
// renders while the texture is loading
|
|
1847
|
+
newIsRenderable =
|
|
1848
|
+
this.textureLoaded === true || this.placeholderActive === true;
|
|
1766
1849
|
} else if (
|
|
1767
1850
|
// check shader
|
|
1768
1851
|
(this.props.shader !== this.stage.renderer.getDefaultShaderNode() ||
|
|
@@ -1774,6 +1857,17 @@ export class CoreNode extends EventEmitter {
|
|
|
1774
1857
|
newIsRenderable = true;
|
|
1775
1858
|
}
|
|
1776
1859
|
|
|
1860
|
+
// renderOnlyInViewport: nodes in the preload margin keep texture
|
|
1861
|
+
// ownership above (so loading proceeds) but stay out of the render list
|
|
1862
|
+
// until they actually intersect the viewport.
|
|
1863
|
+
if (
|
|
1864
|
+
newIsRenderable === true &&
|
|
1865
|
+
this.stage.renderOnlyInViewport === true &&
|
|
1866
|
+
this.renderState !== CoreNodeRenderState.InViewport
|
|
1867
|
+
) {
|
|
1868
|
+
newIsRenderable = false;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1777
1871
|
this.updateTextureOwnership(needsTextureOwnership);
|
|
1778
1872
|
this.setRenderable(newIsRenderable);
|
|
1779
1873
|
}
|
|
@@ -2049,6 +2143,9 @@ export class CoreNode extends EventEmitter {
|
|
|
2049
2143
|
}
|
|
2050
2144
|
|
|
2051
2145
|
get renderTexture(): Texture | null {
|
|
2146
|
+
if (this.placeholderActive === true) {
|
|
2147
|
+
return this.stage.defaultTexture;
|
|
2148
|
+
}
|
|
2052
2149
|
return this.props.texture || this.stage.defaultTexture;
|
|
2053
2150
|
}
|
|
2054
2151
|
|
|
@@ -2232,7 +2329,9 @@ export class CoreNode extends EventEmitter {
|
|
|
2232
2329
|
const props = this.props;
|
|
2233
2330
|
if (props.w !== value) {
|
|
2234
2331
|
props.w = value;
|
|
2235
|
-
|
|
2332
|
+
// Dimensions feed shader uniforms (e.g. factored corner radius), so a
|
|
2333
|
+
// resize must recompute them; see the Global-update branch in update().
|
|
2334
|
+
let updateType = UpdateType.Local | UpdateType.RecalcUniforms;
|
|
2236
2335
|
|
|
2237
2336
|
if (
|
|
2238
2337
|
props.texture !== null &&
|
|
@@ -2262,7 +2361,9 @@ export class CoreNode extends EventEmitter {
|
|
|
2262
2361
|
const props = this.props;
|
|
2263
2362
|
if (props.h !== value) {
|
|
2264
2363
|
props.h = value;
|
|
2265
|
-
|
|
2364
|
+
// Dimensions feed shader uniforms (e.g. factored corner radius), so a
|
|
2365
|
+
// resize must recompute them; see the Global-update branch in update().
|
|
2366
|
+
let updateType = UpdateType.Local | UpdateType.RecalcUniforms;
|
|
2266
2367
|
|
|
2267
2368
|
if (
|
|
2268
2369
|
props.texture !== null &&
|
|
@@ -2514,6 +2615,24 @@ export class CoreNode extends EventEmitter {
|
|
|
2514
2615
|
this.setUpdateType(UpdateType.PremultipliedColors);
|
|
2515
2616
|
}
|
|
2516
2617
|
|
|
2618
|
+
get placeholderColor(): number {
|
|
2619
|
+
return this.props.placeholderColor;
|
|
2620
|
+
}
|
|
2621
|
+
|
|
2622
|
+
set placeholderColor(value: number) {
|
|
2623
|
+
const p = this.props;
|
|
2624
|
+
if (p.placeholderColor === value) return;
|
|
2625
|
+
|
|
2626
|
+
p.placeholderColor = value;
|
|
2627
|
+
this.updatePlaceholderActive();
|
|
2628
|
+
|
|
2629
|
+
// If the placeholder is (still) showing, the new color must reach the
|
|
2630
|
+
// quad buffer even though the active state did not toggle.
|
|
2631
|
+
if (this.placeholderActive === true) {
|
|
2632
|
+
this.setUpdateType(UpdateType.PremultipliedColors);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2517
2636
|
get colorTop(): number {
|
|
2518
2637
|
return this.props.colorTop;
|
|
2519
2638
|
}
|
|
@@ -2934,6 +3053,7 @@ export class CoreNode extends EventEmitter {
|
|
|
2934
3053
|
this.textureCoords = undefined;
|
|
2935
3054
|
this.props.texture = value;
|
|
2936
3055
|
this.textureLoaded = value !== null && value.state === 'loaded';
|
|
3056
|
+
this.updatePlaceholderActive();
|
|
2937
3057
|
|
|
2938
3058
|
if (value !== null) {
|
|
2939
3059
|
if (this.autosizer !== null) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
2
|
import { mock } from 'vitest-mock-extended';
|
|
3
3
|
import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js';
|
|
4
|
+
import { CoreNodeRenderState } from './CoreNode.js';
|
|
4
5
|
import { Stage } from './Stage.js';
|
|
5
6
|
import { CoreRenderer } from './renderers/CoreRenderer.js';
|
|
6
7
|
import { createBound } from './lib/utils.js';
|
|
@@ -28,6 +29,7 @@ const defaultProps = (
|
|
|
28
29
|
colorTl: 0xffffffff,
|
|
29
30
|
colorTop: 0xffffffff,
|
|
30
31
|
colorTr: 0xffffffff,
|
|
32
|
+
placeholderColor: 0,
|
|
31
33
|
h: 0,
|
|
32
34
|
mount: 0,
|
|
33
35
|
mountX: 0,
|
|
@@ -143,3 +145,63 @@ describe('CoreTextNode (canvas) clearing text', () => {
|
|
|
143
145
|
expect(node.isRenderable).toBe(false);
|
|
144
146
|
});
|
|
145
147
|
});
|
|
148
|
+
|
|
149
|
+
describe('CoreTextNode (sdf) renderOnlyInViewport', () => {
|
|
150
|
+
const makeSdfRenderer = (): TextRenderer => {
|
|
151
|
+
const font = mock<FontHandler>({ type: 'sdf' });
|
|
152
|
+
return {
|
|
153
|
+
type: 'sdf',
|
|
154
|
+
font,
|
|
155
|
+
renderText: vi.fn(),
|
|
156
|
+
addQuads: vi.fn(),
|
|
157
|
+
renderQuads: vi.fn(),
|
|
158
|
+
init: vi.fn(),
|
|
159
|
+
} as unknown as TextRenderer;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
function sdfNodeWithLayout(renderOnlyInViewport: boolean): CoreTextNode {
|
|
163
|
+
const stage = mock<Stage>({
|
|
164
|
+
strictBound: createBound(0, 0, 200, 200),
|
|
165
|
+
preloadBound: createBound(0, 0, 400, 200),
|
|
166
|
+
defaultTexture: { state: 'loaded' } as never,
|
|
167
|
+
defShaderNode: null as never,
|
|
168
|
+
renderer: mock<CoreRenderer>() as CoreRenderer,
|
|
169
|
+
renderOnlyInViewport,
|
|
170
|
+
});
|
|
171
|
+
const node = new CoreTextNode(
|
|
172
|
+
stage,
|
|
173
|
+
defaultProps({ text: 'Hello' }),
|
|
174
|
+
makeSdfRenderer(),
|
|
175
|
+
);
|
|
176
|
+
(node as unknown as { _cachedLayout: object })._cachedLayout = {};
|
|
177
|
+
node.worldAlpha = 1;
|
|
178
|
+
return node;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
it('off: margin-ring SDF text is renderable (legacy behavior)', () => {
|
|
182
|
+
const node = sdfNodeWithLayout(false);
|
|
183
|
+
node.renderState = CoreNodeRenderState.InBounds;
|
|
184
|
+
|
|
185
|
+
node.updateIsRenderable();
|
|
186
|
+
|
|
187
|
+
expect(node.isRenderable).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('on: margin-ring SDF text stays out of the render list', () => {
|
|
191
|
+
const node = sdfNodeWithLayout(true);
|
|
192
|
+
node.renderState = CoreNodeRenderState.InBounds;
|
|
193
|
+
|
|
194
|
+
node.updateIsRenderable();
|
|
195
|
+
|
|
196
|
+
expect(node.isRenderable).toBe(false);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('on: viewport SDF text is renderable', () => {
|
|
200
|
+
const node = sdfNodeWithLayout(true);
|
|
201
|
+
node.renderState = CoreNodeRenderState.InViewport;
|
|
202
|
+
|
|
203
|
+
node.updateIsRenderable();
|
|
204
|
+
|
|
205
|
+
expect(node.isRenderable).toBe(true);
|
|
206
|
+
});
|
|
207
|
+
});
|
package/src/core/CoreTextNode.ts
CHANGED
|
@@ -227,9 +227,14 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
-
// For SDF, check if we have a cached layout
|
|
230
|
+
// For SDF, check if we have a cached layout. renderOnlyInViewport gates
|
|
231
|
+
// SDF text the same way CoreNode gates quads: margin-ring text stays out
|
|
232
|
+
// of the render list until it intersects the viewport.
|
|
231
233
|
this.setRenderable(
|
|
232
|
-
this.checkBasicRenderability() === true &&
|
|
234
|
+
this.checkBasicRenderability() === true &&
|
|
235
|
+
this._cachedLayout !== null &&
|
|
236
|
+
(this.stage.renderOnlyInViewport === false ||
|
|
237
|
+
this.renderState === CoreNodeRenderState.InViewport),
|
|
233
238
|
);
|
|
234
239
|
}
|
|
235
240
|
|
|
@@ -271,8 +276,12 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps {
|
|
|
271
276
|
this.props.w = width;
|
|
272
277
|
this.props.h = height;
|
|
273
278
|
|
|
274
|
-
// Text dimensions may have changed, recalculate transforms and bounds
|
|
275
|
-
|
|
279
|
+
// Text dimensions may have changed, recalculate transforms and bounds.
|
|
280
|
+
// RecalcUniforms because the direct props.w/h write above bypasses the
|
|
281
|
+
// w/h setters and dimensions feed shader uniforms.
|
|
282
|
+
this.setUpdateType(
|
|
283
|
+
UpdateType.Local | UpdateType.RenderBounds | UpdateType.RecalcUniforms,
|
|
284
|
+
);
|
|
276
285
|
|
|
277
286
|
// Handle SDF renderer (uses layout caching)
|
|
278
287
|
if (textRendererType === 'sdf') {
|
package/src/core/Stage.ts
CHANGED
|
@@ -97,6 +97,12 @@ export class Stage {
|
|
|
97
97
|
public readonly renderer: CoreRenderer;
|
|
98
98
|
public readonly root: CoreNode;
|
|
99
99
|
public boundsMargin: [number, number, number, number];
|
|
100
|
+
/**
|
|
101
|
+
* When true, nodes inside the bounds margin but outside the viewport keep
|
|
102
|
+
* loading textures yet stay out of the render list. Read by
|
|
103
|
+
* `CoreNode.updateIsRenderable` on the scroll path.
|
|
104
|
+
*/
|
|
105
|
+
public readonly renderOnlyInViewport: boolean;
|
|
100
106
|
public readonly defShaderNode: CoreShaderNode | null = null;
|
|
101
107
|
public strictBound: Bound;
|
|
102
108
|
public preloadBound: Bound;
|
|
@@ -208,6 +214,7 @@ export class Stage {
|
|
|
208
214
|
setBaselineMode(options.textBaselineMode);
|
|
209
215
|
|
|
210
216
|
this.platform = platform;
|
|
217
|
+
this.renderOnlyInViewport = options.renderOnlyInViewport !== false;
|
|
211
218
|
|
|
212
219
|
this.startTime = platform.getTimeStamp();
|
|
213
220
|
|
|
@@ -382,6 +389,7 @@ export class Stage {
|
|
|
382
389
|
colorRight: 0x00000000,
|
|
383
390
|
colorTl: 0x00000000,
|
|
384
391
|
colorTr: 0x00000000,
|
|
392
|
+
placeholderColor: 0x00000000,
|
|
385
393
|
colorBl: 0x00000000,
|
|
386
394
|
colorBr: 0x00000000,
|
|
387
395
|
zIndex: 0,
|
|
@@ -1043,6 +1051,7 @@ export class Stage {
|
|
|
1043
1051
|
colorTr,
|
|
1044
1052
|
colorBl,
|
|
1045
1053
|
colorBr,
|
|
1054
|
+
placeholderColor: props.placeholderColor ?? 0,
|
|
1046
1055
|
zIndex: props.zIndex ?? 0,
|
|
1047
1056
|
parent: props.parent ?? null,
|
|
1048
1057
|
texture: props.texture ?? null,
|