@solidtv/renderer 1.5.5 → 1.6.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.
- package/dist/src/core/CoreNode.js +2 -2
- package/dist/src/core/CoreNode.js.map +1 -1
- package/dist/src/core/Stage.js +9 -1
- package/dist/src/core/Stage.js.map +1 -1
- package/dist/src/core/lib/WebGlContextWrapper.d.ts +17 -1
- package/dist/src/core/lib/WebGlContextWrapper.js +21 -0
- package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
- package/dist/src/core/lib/colorParser.d.ts +4 -1
- package/dist/src/core/lib/colorParser.js +16 -6
- package/dist/src/core/lib/colorParser.js.map +1 -1
- package/dist/src/core/lib/validateImageBitmap.js +7 -0
- package/dist/src/core/lib/validateImageBitmap.js.map +1 -1
- package/dist/src/core/platforms/web/WebPlatform.js +39 -2
- package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasRenderer.d.ts +4 -0
- package/dist/src/core/renderers/canvas/CanvasRenderer.js +28 -13
- package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
- package/dist/src/core/renderers/canvas/CanvasShaderNode.js +1 -0
- package/dist/src/core/renderers/canvas/CanvasShaderNode.js.map +1 -1
- package/dist/src/core/renderers/webgl/SdfRenderOp.js +2 -2
- package/dist/src/core/renderers/webgl/SdfRenderOp.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +43 -0
- package/dist/src/core/renderers/webgl/WebGlRenderer.js +109 -10
- package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +21 -13
- package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.d.ts +8 -3
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +12 -11
- package/dist/src/core/renderers/webgl/internal/ShaderUtils.js.map +1 -1
- package/dist/src/core/shaders/canvas/Rounded.js +3 -3
- package/dist/src/core/shaders/canvas/Rounded.js.map +1 -1
- package/dist/src/core/shaders/canvas/RoundedWithShadow.js +3 -3
- package/dist/src/core/shaders/canvas/RoundedWithShadow.js.map +1 -1
- package/dist/src/core/shaders/canvas/utils/render.js +3 -3
- package/dist/src/core/shaders/canvas/utils/render.js.map +1 -1
- package/dist/src/core/shaders/webgl/Border.js +12 -11
- package/dist/src/core/shaders/webgl/Border.js.map +1 -1
- package/dist/src/core/shaders/webgl/HolePunch.js +12 -5
- package/dist/src/core/shaders/webgl/HolePunch.js.map +1 -1
- package/dist/src/core/shaders/webgl/LinearGradient.js +3 -19
- package/dist/src/core/shaders/webgl/LinearGradient.js.map +1 -1
- package/dist/src/core/shaders/webgl/RadialGradient.js +3 -20
- package/dist/src/core/shaders/webgl/RadialGradient.js.map +1 -1
- package/dist/src/core/shaders/webgl/RadialProgress.js +15 -23
- package/dist/src/core/shaders/webgl/RadialProgress.js.map +1 -1
- package/dist/src/core/shaders/webgl/RoundedWithBorder.js +35 -32
- package/dist/src/core/shaders/webgl/RoundedWithBorder.js.map +1 -1
- package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +58 -45
- package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js.map +1 -1
- package/dist/src/core/shaders/webgl/RoundedWithShadow.js +15 -13
- package/dist/src/core/shaders/webgl/RoundedWithShadow.js.map +1 -1
- package/dist/src/core/shaders/webgl/Shadow.js +4 -5
- package/dist/src/core/shaders/webgl/Shadow.js.map +1 -1
- package/dist/src/core/text-rendering/SdfTextRenderer.js +23 -10
- package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
- package/dist/src/core/text-rendering/TextLayoutEngine.js +14 -2
- package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
- package/dist/src/main-api/Renderer.d.ts +20 -0
- package/dist/src/main-api/Renderer.js +6 -0
- package/dist/src/main-api/Renderer.js.map +1 -1
- package/dist/src/utils.js +1 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/tsconfig.dist.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/core/CoreNode.ts +2 -2
- package/src/core/Stage.renderListUpdate.test.ts +73 -0
- package/src/core/Stage.ts +11 -1
- package/src/core/lib/WebGlContextWrapper.test.ts +23 -1
- package/src/core/lib/WebGlContextWrapper.ts +23 -1
- package/src/core/lib/colorParser.test.ts +46 -0
- package/src/core/lib/colorParser.ts +17 -6
- package/src/core/lib/validateImageBitmap.test.ts +15 -0
- package/src/core/lib/validateImageBitmap.ts +8 -0
- package/src/core/platforms/web/WebPlatform.loopError.test.ts +145 -0
- package/src/core/platforms/web/WebPlatform.ts +39 -2
- package/src/core/renderers/canvas/CanvasRenderer.test.ts +230 -0
- package/src/core/renderers/canvas/CanvasRenderer.ts +29 -15
- package/src/core/renderers/canvas/CanvasShaderNode.test.ts +73 -0
- package/src/core/renderers/canvas/CanvasShaderNode.ts +1 -0
- package/src/core/renderers/webgl/SdfRenderOp.ts +2 -2
- package/src/core/renderers/webgl/WebGlRenderer.reuseRenderOp.test.ts +122 -0
- package/src/core/renderers/webgl/WebGlRenderer.sdfBuffer.test.ts +302 -0
- package/src/core/renderers/webgl/WebGlRenderer.ts +151 -11
- package/src/core/renderers/webgl/WebGlShaderProgram.reuseRenderOp.test.ts +150 -0
- package/src/core/renderers/webgl/WebGlShaderProgram.ts +27 -15
- package/src/core/renderers/webgl/WebGlShaderProgram.uniformDedup.test.ts +4 -2
- package/src/core/renderers/webgl/internal/ShaderUtils.test.ts +124 -0
- package/src/core/renderers/webgl/internal/ShaderUtils.ts +14 -13
- package/src/core/shaders/canvas/Rounded.ts +3 -3
- package/src/core/shaders/canvas/RoundedWithShadow.ts +3 -3
- package/src/core/shaders/canvas/utils/render.ts +3 -3
- package/src/core/shaders/webgl/Border.ts +12 -11
- package/src/core/shaders/webgl/HolePunch.ts +12 -5
- package/src/core/shaders/webgl/LinearGradient.ts +3 -19
- package/src/core/shaders/webgl/RadialGradient.ts +3 -20
- package/src/core/shaders/webgl/RadialProgress.ts +15 -23
- package/src/core/shaders/webgl/RoundedWithBorder.ts +35 -32
- package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +58 -45
- package/src/core/shaders/webgl/RoundedWithShadow.ts +15 -13
- package/src/core/shaders/webgl/Shadow.ts +4 -5
- package/src/core/text-rendering/SdfTextRenderer.test.ts +177 -0
- package/src/core/text-rendering/SdfTextRenderer.ts +38 -10
- package/src/core/text-rendering/TextLayoutEngine.ts +15 -2
- package/src/core/text-rendering/tests/TextLayoutEngine.test.ts +99 -0
- package/src/main-api/Renderer.ts +28 -0
- package/src/utils.ts +1 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
} from '../templates/RadialProgressTemplate.js';
|
|
7
7
|
import type { WebGlShaderType } from '../../renderers/webgl/WebGlShaderNode.js';
|
|
8
8
|
import type { WebGlRenderer } from '../../renderers/webgl/WebGlRenderer.js';
|
|
9
|
+
import { genGradientColors } from '../../renderers/webgl/internal/ShaderUtils.js';
|
|
9
10
|
|
|
10
11
|
export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
11
12
|
props: RadialProgressTemplate.props,
|
|
@@ -57,7 +58,6 @@ export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
|
57
58
|
# endif
|
|
58
59
|
|
|
59
60
|
#define MAX_STOPS ${maxStops}
|
|
60
|
-
#define LAST_STOP ${maxStops - 1}
|
|
61
61
|
#define CAP_ROUND ${props.cap}
|
|
62
62
|
#define HAS_TRACK ${props.trackColor !== 0 ? 1 : 0}
|
|
63
63
|
|
|
@@ -87,22 +87,8 @@ export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
|
87
87
|
|
|
88
88
|
vec4 getGradientColor(float dist) {
|
|
89
89
|
dist = clamp(dist, 0.0, 1.0);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return u_colors[0];
|
|
93
|
-
}
|
|
94
|
-
if (dist >= u_stops[LAST_STOP]) {
|
|
95
|
-
return u_colors[LAST_STOP];
|
|
96
|
-
}
|
|
97
|
-
for (int i = 0; i < LAST_STOP; i++) {
|
|
98
|
-
float left = u_stops[i];
|
|
99
|
-
float right = u_stops[i + 1];
|
|
100
|
-
if (dist >= left && dist <= right) {
|
|
101
|
-
float lDist = smoothstep(left, right, dist);
|
|
102
|
-
return mix(u_colors[i], u_colors[i + 1], lDist);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return u_colors[LAST_STOP];
|
|
90
|
+
${genGradientColors(maxStops)}
|
|
91
|
+
return colorOut;
|
|
106
92
|
}
|
|
107
93
|
|
|
108
94
|
// Coverage of a disc centered at \`c\` with radius \`r\` at pixel \`p\` (with 1px AA)
|
|
@@ -116,9 +102,13 @@ export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
|
116
102
|
// Effective progress: when u_duration > 0 the shader self-animates from
|
|
117
103
|
// u_time, otherwise we use the static u_progress prop. countdown == 1
|
|
118
104
|
// drains (1 -> 0), countdown == 0 fills (0 -> 1).
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
105
|
+
// Branchless (Mali 400 serializes uniform branches): the mix()-guarded
|
|
106
|
+
// denominator keeps the division finite when u_duration == 0 -- a
|
|
107
|
+
// max()-clamped epsilon would overflow fp16 mediump to Inf/NaN.
|
|
108
|
+
float hasDuration = step(0.0001, u_duration);
|
|
109
|
+
float cyclePos = fract(u_time / mix(1.0, u_duration, hasDuration)) * hasDuration;
|
|
110
|
+
float animProgress = mix(cyclePos, 1.0 - cyclePos, step(0.5, u_countdown));
|
|
111
|
+
float progress = mix(u_progress, animProgress, hasDuration);
|
|
122
112
|
|
|
123
113
|
vec2 p = v_nodeCoords.xy * u_dimensions - u_center;
|
|
124
114
|
float dist = length(p);
|
|
@@ -135,7 +125,7 @@ export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
|
135
125
|
|
|
136
126
|
// Filled arc coverage (1 if in filled arc, else 0). When progress >= 1 the
|
|
137
127
|
// whole ring is filled regardless of \`t\` -- guards against the mod() seam.
|
|
138
|
-
float arcCoverage =
|
|
128
|
+
float arcCoverage = max(step(1.0, progress), step(t, progress));
|
|
139
129
|
float fillCoverage = ringCoverage * arcCoverage;
|
|
140
130
|
|
|
141
131
|
#if CAP_ROUND
|
|
@@ -151,8 +141,10 @@ export const RadialProgress: WebGlShaderType<RadialProgressProps> = {
|
|
|
151
141
|
#endif
|
|
152
142
|
|
|
153
143
|
// Sample gradient. Normalize \`t\` to the *filled* portion so the gradient
|
|
154
|
-
// spans the visible arc end-to-end regardless of progress.
|
|
155
|
-
|
|
144
|
+
// spans the visible arc end-to-end regardless of progress. mix()-guarded
|
|
145
|
+
// denominator: safe at progress == 0 without fp16 overflow.
|
|
146
|
+
float hasProgress = step(0.000001, progress);
|
|
147
|
+
float gradT = clamp(t / mix(1.0, progress, hasProgress), 0.0, 1.0) * hasProgress;
|
|
156
148
|
vec4 fillCol = getGradientColor(gradT);
|
|
157
149
|
|
|
158
150
|
// Composite: track under fill (if track enabled), both gated by ringCoverage.
|
|
@@ -64,6 +64,15 @@ export const RoundedWithBorder: WebGlShaderType<RoundedWithBorderProps> = {
|
|
|
64
64
|
v_innerSize = vec2(0.0);
|
|
65
65
|
v_outerSize = vec2(0.0);
|
|
66
66
|
|
|
67
|
+
// Defaults for the zero-border case: the fragment shader is branchless
|
|
68
|
+
// and always evaluates the border SDFs, so every varying must carry a
|
|
69
|
+
// finite value (an unwritten varying is undefined and can poison the
|
|
70
|
+
// final mix() with NaN).
|
|
71
|
+
v_outerBorderUv = vec2(0.0);
|
|
72
|
+
v_innerBorderUv = vec2(0.0);
|
|
73
|
+
v_outerBorderRadius = u_radius;
|
|
74
|
+
v_innerBorderRadius = u_radius;
|
|
75
|
+
|
|
67
76
|
if(borderZero == 0.0) {
|
|
68
77
|
vec4 adjustedBorderWidth = u_borderWidth - 1.0 + clamp(u_borderWidth, -1.0, 1.0);
|
|
69
78
|
|
|
@@ -72,9 +81,6 @@ export const RoundedWithBorder: WebGlShaderType<RoundedWithBorderProps> = {
|
|
|
72
81
|
float borderBottom = adjustedBorderWidth.z;
|
|
73
82
|
float borderLeft = adjustedBorderWidth.w;
|
|
74
83
|
|
|
75
|
-
v_outerBorderUv = vec2(0.0);
|
|
76
|
-
v_innerBorderUv = vec2(0.0);
|
|
77
|
-
|
|
78
84
|
vec2 borderSize = vec2(borderRight + borderLeft, borderTop + borderBottom);
|
|
79
85
|
vec2 extraSize = borderSize * u_borderAlign;
|
|
80
86
|
float gapLeft = step(0.001, borderLeft) * u_borderGap;
|
|
@@ -159,52 +165,49 @@ export const RoundedWithBorder: WebGlShaderType<RoundedWithBorderProps> = {
|
|
|
159
165
|
varying vec4 v_outerBorderRadius;
|
|
160
166
|
varying vec2 v_halfDimensions;
|
|
161
167
|
|
|
168
|
+
// Branchless quadrant radius select (r: x TL, y TR, z BR, w BL) --
|
|
169
|
+
// Mali 400-class fragment pipelines serialize any branch, ternaries included.
|
|
170
|
+
float quadRadius(vec2 p, vec4 r) {
|
|
171
|
+
vec2 stepVal = step(vec2(0.0), p);
|
|
172
|
+
return mix(mix(r.x, r.y, stepVal.x), mix(r.w, r.z, stepVal.x), stepVal.y);
|
|
173
|
+
}
|
|
174
|
+
|
|
162
175
|
float roundedBox(vec2 p, vec2 s, vec4 r) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - r.x;
|
|
176
|
+
float rad = quadRadius(p, r);
|
|
177
|
+
vec2 q = abs(p) - s + rad;
|
|
178
|
+
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - rad;
|
|
167
179
|
}
|
|
168
180
|
|
|
169
181
|
void main() {
|
|
170
182
|
vec4 color = texture2D(u_texture, v_textureCoords) * v_color;
|
|
171
|
-
vec4 resultColor = vec4(0.0);
|
|
172
183
|
vec2 boxUv = v_nodeCoords.xy * u_dimensions - v_halfDimensions;
|
|
173
184
|
float borderZero = 1.0 - step(0.001, dot(abs(u_borderWidth), vec4(1.0)));
|
|
174
185
|
float edgeWidth = 1.0 / u_pixelRatio;
|
|
175
186
|
|
|
176
|
-
float nodeDist;
|
|
177
|
-
float nodeAlpha;
|
|
178
|
-
|
|
179
|
-
if(borderZero == 1.0) {
|
|
180
|
-
nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
181
|
-
nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
182
|
-
gl_FragColor = (color * nodeAlpha) * u_alpha;
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
187
|
+
float nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
188
|
+
float nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
185
189
|
|
|
186
190
|
float outerDist = roundedBox(boxUv + v_outerBorderUv, v_outerSize - edgeWidth, v_outerBorderRadius);
|
|
187
191
|
float innerDist = roundedBox(boxUv + v_innerBorderUv, v_innerSize - edgeWidth, v_innerBorderRadius);
|
|
188
|
-
|
|
189
|
-
if(u_borderGap == 0.0) {
|
|
190
|
-
float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
|
|
191
|
-
float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
|
|
192
|
-
resultColor = mix(resultColor, u_borderColor, outerAlpha * u_borderColor.a);
|
|
193
|
-
resultColor = mix(resultColor, color, innerAlpha);
|
|
194
|
-
gl_FragColor = resultColor * u_alpha;
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
199
|
-
nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
192
|
+
float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
|
|
200
193
|
float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
|
|
201
|
-
float gapAlpha = max(0.0, innerAlpha - nodeAlpha);
|
|
202
194
|
|
|
195
|
+
float gapAlpha = max(0.0, innerAlpha - nodeAlpha);
|
|
203
196
|
float borderDist = max(-innerDist, outerDist);
|
|
204
197
|
float borderAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, borderDist);
|
|
205
198
|
|
|
206
|
-
|
|
207
|
-
|
|
199
|
+
// Branchless path select -- Mali 400 serializes uniform branches, so all
|
|
200
|
+
// three composites (no gap / gap / zero border) are computed and
|
|
201
|
+
// mix()-selected instead of if/else'd.
|
|
202
|
+
vec4 resNoGap = mix(vec4(0.0), u_borderColor, outerAlpha * u_borderColor.a);
|
|
203
|
+
resNoGap = mix(resNoGap, color, innerAlpha);
|
|
204
|
+
|
|
205
|
+
vec4 resGap = (color * nodeAlpha) + (u_fillColor * gapAlpha);
|
|
206
|
+
resGap = mix(resGap, u_borderColor, borderAlpha * u_borderColor.a);
|
|
207
|
+
|
|
208
|
+
float hasGap = step(0.0001, abs(u_borderGap));
|
|
209
|
+
vec4 resultColor = mix(resNoGap, resGap, hasGap);
|
|
210
|
+
resultColor = mix(resultColor, color * nodeAlpha, borderZero);
|
|
208
211
|
gl_FragColor = resultColor * u_alpha;
|
|
209
212
|
}
|
|
210
213
|
`,
|
|
@@ -71,6 +71,15 @@ export const RoundedWithBorderAndShadow: WebGlShaderType<RoundedWithBorderAndSha
|
|
|
71
71
|
v_innerSize = vec2(0.0);
|
|
72
72
|
v_outerSize = vec2(0.0);
|
|
73
73
|
|
|
74
|
+
// Defaults for the zero-border case: the fragment shader is branchless
|
|
75
|
+
// and always evaluates the border SDFs, so every varying must carry a
|
|
76
|
+
// finite value (an unwritten varying is undefined and can poison the
|
|
77
|
+
// final mix() with NaN).
|
|
78
|
+
v_outerBorderUv = vec2(0.0);
|
|
79
|
+
v_innerBorderUv = vec2(0.0);
|
|
80
|
+
v_outerBorderRadius = u_radius;
|
|
81
|
+
v_innerBorderRadius = u_radius;
|
|
82
|
+
|
|
74
83
|
if(borderZero == 0.0) {
|
|
75
84
|
vec4 adjustedBorderWidth = u_borderWidth - 1.0 + clamp(u_borderWidth, -1.0, 1.0);
|
|
76
85
|
|
|
@@ -79,9 +88,6 @@ export const RoundedWithBorderAndShadow: WebGlShaderType<RoundedWithBorderAndSha
|
|
|
79
88
|
float borderBottom = adjustedBorderWidth.z;
|
|
80
89
|
float borderLeft = adjustedBorderWidth.w;
|
|
81
90
|
|
|
82
|
-
v_outerBorderUv = vec2(0.0);
|
|
83
|
-
v_innerBorderUv = vec2(0.0);
|
|
84
|
-
|
|
85
91
|
vec2 borderSize = vec2(borderRight + borderLeft, borderTop + borderBottom);
|
|
86
92
|
vec2 extraSize = borderSize * u_borderAlign;
|
|
87
93
|
float gapLeft = step(0.001, borderLeft) * u_borderGap;
|
|
@@ -166,67 +172,74 @@ export const RoundedWithBorderAndShadow: WebGlShaderType<RoundedWithBorderAndSha
|
|
|
166
172
|
varying vec4 v_outerBorderRadius;
|
|
167
173
|
varying vec2 v_halfDimensions;
|
|
168
174
|
|
|
175
|
+
// Branchless quadrant radius select (r: x TL, y TR, z BR, w BL) --
|
|
176
|
+
// Mali 400-class fragment pipelines serialize any branch, ternaries included.
|
|
177
|
+
float quadRadius(vec2 p, vec4 r) {
|
|
178
|
+
vec2 stepVal = step(vec2(0.0), p);
|
|
179
|
+
return mix(mix(r.x, r.y, stepVal.x), mix(r.w, r.z, stepVal.x), stepVal.y);
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
float roundedBox(vec2 p, vec2 s, vec4 r) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - r.x;
|
|
183
|
+
float rad = quadRadius(p, r);
|
|
184
|
+
vec2 q = abs(p) - s + rad;
|
|
185
|
+
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - rad;
|
|
174
186
|
}
|
|
175
187
|
|
|
176
188
|
float shadowBox(vec2 p, vec2 s, vec4 r) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
float dist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
|
|
189
|
+
float rad = quadRadius(p, r);
|
|
190
|
+
vec2 q = abs(p) - s + rad;
|
|
191
|
+
float dist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rad;
|
|
181
192
|
return 1.0 - smoothstep(-u_shadow.w, u_shadow.w + u_shadow.z, dist);
|
|
182
193
|
}
|
|
183
194
|
|
|
184
195
|
void main() {
|
|
185
196
|
vec4 color = texture2D(u_texture, v_textureCoords) * v_color;
|
|
186
|
-
vec4 resultColor = vec4(0.0);
|
|
187
197
|
vec2 boxUv = v_nodeCoords.xy * u_dimensions - v_halfDimensions;
|
|
188
198
|
float borderZero = 1.0 - step(0.001, dot(abs(u_borderWidth), vec4(1.0)));
|
|
189
199
|
float edgeWidth = 1.0 / u_pixelRatio;
|
|
190
|
-
float nodeDist;
|
|
191
|
-
float nodeAlpha;
|
|
192
|
-
float shadowAlpha;
|
|
193
|
-
|
|
194
|
-
if(borderZero == 1.0) {
|
|
195
|
-
nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
196
|
-
nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
197
|
-
shadowAlpha = shadowBox(boxUv - u_shadow.xy, v_halfDimensions + u_shadow.w - edgeWidth, u_radius + u_shadow.z);
|
|
198
|
-
resultColor = mix(resultColor, u_shadowColor, shadowAlpha);
|
|
199
|
-
gl_FragColor = mix(resultColor, color, nodeAlpha) * u_alpha;
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
200
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
201
|
+
float nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
202
|
+
float nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
203
|
+
|
|
204
|
+
// Shadow geometry select, branchless: when the border grows the quad
|
|
205
|
+
// beyond the node (outer size exceeds half dimensions on either axis)
|
|
206
|
+
// the shadow hugs the outer border box, otherwise the node box. This
|
|
207
|
+
// replaces a varying-driven if/else (worst offender: diverges per
|
|
208
|
+
// fragment) with a step()/mix() parameter select into ONE shadowBox call.
|
|
209
|
+
// When borderZero, v_outerSize is vec2(0.0) so outerSel is 0 and the
|
|
210
|
+
// node-box parameters win, matching the old zero-border path.
|
|
211
|
+
float outerSel = max(
|
|
212
|
+
step(v_halfDimensions.x, v_outerSize.x),
|
|
213
|
+
step(v_halfDimensions.y, v_outerSize.y)
|
|
214
|
+
);
|
|
215
|
+
float shadowAlpha = shadowBox(
|
|
216
|
+
boxUv + v_outerBorderUv * outerSel - u_shadow.xy,
|
|
217
|
+
mix(v_halfDimensions, v_outerSize, outerSel) + u_shadow.w - edgeWidth,
|
|
218
|
+
mix(u_radius, v_outerBorderRadius, outerSel) + u_shadow.z
|
|
219
|
+
);
|
|
209
220
|
|
|
210
221
|
float outerDist = roundedBox(boxUv + v_outerBorderUv, v_outerSize - edgeWidth, v_outerBorderRadius);
|
|
211
222
|
float innerDist = roundedBox(boxUv + v_innerBorderUv, v_innerSize - edgeWidth, v_innerBorderRadius);
|
|
223
|
+
float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
|
|
224
|
+
float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
|
|
212
225
|
|
|
213
|
-
if(u_borderGap == 0.0) {
|
|
214
|
-
float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
|
|
215
|
-
float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
|
|
216
|
-
resultColor = mix(resultColor, u_shadowColor, shadowAlpha);
|
|
217
|
-
resultColor = mix(resultColor, u_borderColor, outerAlpha * u_borderColor.a);
|
|
218
|
-
resultColor = mix(resultColor, color, innerAlpha);
|
|
219
|
-
gl_FragColor = resultColor * u_alpha;
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
nodeDist = roundedBox(boxUv, v_halfDimensions - edgeWidth, u_radius);
|
|
224
|
-
nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
|
|
225
226
|
float borderDist = max(-innerDist, outerDist);
|
|
226
227
|
float borderAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, borderDist);
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
228
|
+
|
|
229
|
+
// Branchless path select -- Mali 400 serializes uniform branches, so all
|
|
230
|
+
// three composites (no gap / gap / zero border) are computed and
|
|
231
|
+
// mix()-selected instead of if/else'd.
|
|
232
|
+
vec4 shadowBase = u_shadowColor * shadowAlpha;
|
|
233
|
+
|
|
234
|
+
vec4 resNoGap = mix(shadowBase, u_borderColor, outerAlpha * u_borderColor.a);
|
|
235
|
+
resNoGap = mix(resNoGap, color, innerAlpha);
|
|
236
|
+
|
|
237
|
+
vec4 resFill = mix(shadowBase, color, nodeAlpha);
|
|
238
|
+
vec4 resGap = mix(resFill, u_borderColor, borderAlpha * u_borderColor.a);
|
|
239
|
+
|
|
240
|
+
float hasGap = step(0.0001, abs(u_borderGap));
|
|
241
|
+
vec4 resultColor = mix(resNoGap, resGap, hasGap);
|
|
242
|
+
resultColor = mix(resultColor, resFill, borderZero);
|
|
230
243
|
gl_FragColor = resultColor * u_alpha;
|
|
231
244
|
}
|
|
232
245
|
`,
|
|
@@ -98,18 +98,23 @@ export const RoundedWithShadow: WebGlShaderType<RoundedWithShadowProps> = {
|
|
|
98
98
|
varying vec4 v_shadowRadius;
|
|
99
99
|
varying vec2 v_shadowSmooth;
|
|
100
100
|
|
|
101
|
+
// Branchless quadrant radius select (r: x TL, y TR, z BR, w BL) --
|
|
102
|
+
// Mali 400-class fragment pipelines serialize any branch, ternaries included.
|
|
103
|
+
float quadRadius(vec2 p, vec4 r) {
|
|
104
|
+
vec2 stepVal = step(vec2(0.0), p);
|
|
105
|
+
return mix(mix(r.x, r.y, stepVal.x), mix(r.w, r.z, stepVal.x), stepVal.y);
|
|
106
|
+
}
|
|
107
|
+
|
|
101
108
|
float roundedBox(vec2 p, vec2 s, vec4 r) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - r.x;
|
|
109
|
+
float rad = quadRadius(p, r);
|
|
110
|
+
vec2 q = abs(p) - s + rad;
|
|
111
|
+
return (min(max(q.x, q.y), 0.0) + length(max(q, 0.0))) - rad;
|
|
106
112
|
}
|
|
107
113
|
|
|
108
114
|
float shadowBox(vec2 p, vec2 s, vec4 r) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
float dist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
|
|
115
|
+
float rad = quadRadius(p, r);
|
|
116
|
+
vec2 q = abs(p) - s + rad;
|
|
117
|
+
float dist = min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - rad;
|
|
113
118
|
return 1.0 - smoothstep(v_shadowSmooth.x, v_shadowSmooth.y, dist);
|
|
114
119
|
}
|
|
115
120
|
|
|
@@ -119,11 +124,8 @@ export const RoundedWithShadow: WebGlShaderType<RoundedWithShadowProps> = {
|
|
|
119
124
|
float boxDist = roundedBox(v_boxUv, v_boxSize, u_radius);
|
|
120
125
|
float roundedAlpha = 1.0 - smoothstep(v_boxSmooth.x, v_boxSmooth.y, boxDist);
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
float shadowAlpha = shadowBox(v_shadowBox, v_shadowSize, v_shadowRadius);
|
|
125
|
-
resColor = u_shadow_color * shadowAlpha;
|
|
126
|
-
}
|
|
127
|
+
float shadowAlpha = shadowBox(v_shadowBox, v_shadowSize, v_shadowRadius);
|
|
128
|
+
vec4 resColor = u_shadow_color * (shadowAlpha * step(0.0001, u_shadow_color.a));
|
|
127
129
|
|
|
128
130
|
resColor = mix(resColor, color, min(color.a, roundedAlpha));
|
|
129
131
|
gl_FragColor = resColor * u_alpha;
|
|
@@ -106,11 +106,10 @@ export const Shadow: WebGlShaderType<ShadowProps> = {
|
|
|
106
106
|
float boxDist = box(v_boxUv, v_boxSize);
|
|
107
107
|
float boxAlpha = 1.0 - smoothstep(v_boxSmooth.x, v_boxSmooth.y, boxDist);
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
109
|
+
// Branchless alpha gate -- Mali 400 serializes uniform branches; the
|
|
110
|
+
// step() zeroes the shadow term exactly when u_color.a == 0.
|
|
111
|
+
float shadowDist = shadowBox(v_shadowBox, v_shadowSize, v_shadowRadius);
|
|
112
|
+
vec4 resColor = u_color * (shadowDist * step(0.0001, u_color.a));
|
|
114
113
|
|
|
115
114
|
resColor = mix(resColor, color, min(color.a, boxAlpha));
|
|
116
115
|
gl_FragColor = resColor * u_alpha;
|
|
@@ -133,6 +133,183 @@ describe('SdfTextRenderer layout cache', () => {
|
|
|
133
133
|
});
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
+
describe('SdfTextRenderer renderQuads cache dispatch', () => {
|
|
137
|
+
type FakeRenderer = {
|
|
138
|
+
stage: unknown;
|
|
139
|
+
sdfBufferIdx: number;
|
|
140
|
+
fSdfBuffer: Float32Array;
|
|
141
|
+
addSdfQuads: ReturnType<typeof vi.fn>;
|
|
142
|
+
addSdfCachedQuads: ReturnType<typeof vi.fn>;
|
|
143
|
+
addSdfTranslatedQuads: ReturnType<typeof vi.fn>;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const GLYPHS = 2;
|
|
147
|
+
|
|
148
|
+
const makeRenderer = (): FakeRenderer => {
|
|
149
|
+
const renderer: FakeRenderer = {
|
|
150
|
+
stage: {
|
|
151
|
+
options: { textLayoutCacheSize: 10 },
|
|
152
|
+
shManager: {
|
|
153
|
+
registerShaderType: vi.fn(),
|
|
154
|
+
createShader: vi.fn(() => ({})),
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
sdfBufferIdx: 0,
|
|
158
|
+
fSdfBuffer: new Float32Array(1024),
|
|
159
|
+
// The miss path snapshots [startIdx, endIdx) of fSdfBuffer, so the
|
|
160
|
+
// fake must advance sdfBufferIdx like the real writer does.
|
|
161
|
+
addSdfQuads: vi.fn(() => {
|
|
162
|
+
renderer.sdfBufferIdx += GLYPHS * 24;
|
|
163
|
+
}),
|
|
164
|
+
addSdfCachedQuads: vi.fn(),
|
|
165
|
+
addSdfTranslatedQuads: vi.fn(),
|
|
166
|
+
};
|
|
167
|
+
return renderer;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const makeLayout = () => ({
|
|
171
|
+
glyphs: new Float32Array(GLYPHS * 8),
|
|
172
|
+
glyphCount: GLYPHS,
|
|
173
|
+
fontScale: 1,
|
|
174
|
+
distanceRange: 4,
|
|
175
|
+
width: 100,
|
|
176
|
+
height: 50,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const makeCache = () => ({
|
|
180
|
+
vertices: null as Float32Array | null,
|
|
181
|
+
glyphCount: 0,
|
|
182
|
+
color: 0,
|
|
183
|
+
alpha: -1,
|
|
184
|
+
transform: new Float32Array(6),
|
|
185
|
+
layoutRef: null as unknown,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const makeRenderProps = (
|
|
189
|
+
cache: ReturnType<typeof makeCache>,
|
|
190
|
+
transform: number[],
|
|
191
|
+
) => ({
|
|
192
|
+
fontFamily: 'Test',
|
|
193
|
+
fontSize: 42,
|
|
194
|
+
color: 0xffffffff,
|
|
195
|
+
offsetY: 0,
|
|
196
|
+
worldAlpha: 1,
|
|
197
|
+
globalTransform: new Float32Array(transform),
|
|
198
|
+
clippingRect: { x: 0, y: 0, w: 0, h: 0, valid: false },
|
|
199
|
+
width: 100,
|
|
200
|
+
height: 50,
|
|
201
|
+
parentHasRenderTexture: false,
|
|
202
|
+
framebufferDimensions: null,
|
|
203
|
+
sdfCache: cache,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const IDENTITY = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
207
|
+
const TRANSLATED = [1, 0, 0, 0, 1, 0, 120, -40, 1];
|
|
208
|
+
const SCALED = [2, 0, 0, 0, 2, 0, 0, 0, 1];
|
|
209
|
+
|
|
210
|
+
const renderQuads = (
|
|
211
|
+
renderer: FakeRenderer,
|
|
212
|
+
layout: ReturnType<typeof makeLayout>,
|
|
213
|
+
renderProps: ReturnType<typeof makeRenderProps>,
|
|
214
|
+
) =>
|
|
215
|
+
SdfTextRenderer.renderQuads(
|
|
216
|
+
renderer as never,
|
|
217
|
+
layout as never,
|
|
218
|
+
null as never,
|
|
219
|
+
renderProps as never,
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const setupWithAtlas = () => {
|
|
223
|
+
initRenderer(10);
|
|
224
|
+
vi.mocked(SdfFontHandler.getAtlas).mockReturnValue({
|
|
225
|
+
ctxTexture: {},
|
|
226
|
+
} as never);
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
it('takes the miss path first and snapshots the cache', () => {
|
|
230
|
+
setupWithAtlas();
|
|
231
|
+
const renderer = makeRenderer();
|
|
232
|
+
const layout = makeLayout();
|
|
233
|
+
const cache = makeCache();
|
|
234
|
+
|
|
235
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
236
|
+
|
|
237
|
+
expect(renderer.addSdfQuads.mock.calls.length).toBe(1);
|
|
238
|
+
expect(cache.vertices).not.toBeNull();
|
|
239
|
+
expect(cache.layoutRef).toBe(layout);
|
|
240
|
+
expect(cache.glyphCount).toBe(GLYPHS);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('takes the mem-copy path when nothing changed', () => {
|
|
244
|
+
setupWithAtlas();
|
|
245
|
+
const renderer = makeRenderer();
|
|
246
|
+
const layout = makeLayout();
|
|
247
|
+
const cache = makeCache();
|
|
248
|
+
|
|
249
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
250
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
251
|
+
|
|
252
|
+
expect(renderer.addSdfQuads.mock.calls.length).toBe(1);
|
|
253
|
+
expect(renderer.addSdfCachedQuads.mock.calls.length).toBe(1);
|
|
254
|
+
expect(renderer.addSdfTranslatedQuads.mock.calls.length).toBe(0);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('takes the translation path when only tx/ty changed, keeping the cache base', () => {
|
|
258
|
+
setupWithAtlas();
|
|
259
|
+
const renderer = makeRenderer();
|
|
260
|
+
const layout = makeLayout();
|
|
261
|
+
const cache = makeCache();
|
|
262
|
+
|
|
263
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
264
|
+
const baseVertices = cache.vertices;
|
|
265
|
+
|
|
266
|
+
renderQuads(renderer, layout, makeRenderProps(cache, TRANSLATED));
|
|
267
|
+
|
|
268
|
+
expect(renderer.addSdfQuads.mock.calls.length).toBe(1);
|
|
269
|
+
expect(renderer.addSdfCachedQuads.mock.calls.length).toBe(0);
|
|
270
|
+
const calls = renderer.addSdfTranslatedQuads.mock.calls;
|
|
271
|
+
expect(calls.length).toBe(1);
|
|
272
|
+
// (cachedVertices, glyphCount, dx, dy, ...)
|
|
273
|
+
expect(calls[0]![0]).toBe(baseVertices);
|
|
274
|
+
expect(calls[0]![1]).toBe(GLYPHS);
|
|
275
|
+
expect(calls[0]![2]).toBe(120);
|
|
276
|
+
expect(calls[0]![3]).toBe(-40);
|
|
277
|
+
// The scroll path must not re-snapshot: base transform and vertices stay
|
|
278
|
+
expect(cache.vertices).toBe(baseVertices);
|
|
279
|
+
expect(cache.transform[4]).toBe(0);
|
|
280
|
+
expect(cache.transform[5]).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('falls back to the miss path when scale or rotation changed', () => {
|
|
284
|
+
setupWithAtlas();
|
|
285
|
+
const renderer = makeRenderer();
|
|
286
|
+
const layout = makeLayout();
|
|
287
|
+
const cache = makeCache();
|
|
288
|
+
|
|
289
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
290
|
+
renderQuads(renderer, layout, makeRenderProps(cache, SCALED));
|
|
291
|
+
|
|
292
|
+
expect(renderer.addSdfQuads.mock.calls.length).toBe(2);
|
|
293
|
+
expect(renderer.addSdfTranslatedQuads.mock.calls.length).toBe(0);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
it('falls back to the miss path when color or alpha changed', () => {
|
|
297
|
+
setupWithAtlas();
|
|
298
|
+
const renderer = makeRenderer();
|
|
299
|
+
const layout = makeLayout();
|
|
300
|
+
const cache = makeCache();
|
|
301
|
+
|
|
302
|
+
renderQuads(renderer, layout, makeRenderProps(cache, IDENTITY));
|
|
303
|
+
|
|
304
|
+
const faded = makeRenderProps(cache, TRANSLATED);
|
|
305
|
+
faded.worldAlpha = 0.5;
|
|
306
|
+
renderQuads(renderer, layout, faded);
|
|
307
|
+
|
|
308
|
+
expect(renderer.addSdfQuads.mock.calls.length).toBe(2);
|
|
309
|
+
expect(renderer.addSdfTranslatedQuads.mock.calls.length).toBe(0);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
|
|
136
313
|
describe('SdfTextRenderer lazy shader compile', () => {
|
|
137
314
|
it('registers the SDF shader at init but defers compilation', () => {
|
|
138
315
|
const shManager = {
|
|
@@ -150,11 +150,14 @@ const addQuads = (_layout?: TextLayout): Float32Array | null => {
|
|
|
150
150
|
/**
|
|
151
151
|
* Submit SDF glyphs to the renderer's shared batched buffer.
|
|
152
152
|
*
|
|
153
|
-
*
|
|
154
|
-
* 1. **
|
|
155
|
-
* The cached pre-transformed Float32Array is mem-copied directly
|
|
156
|
-
* shared SDF buffer (no per-glyph matrix math).
|
|
157
|
-
* 2. **
|
|
153
|
+
* Three paths:
|
|
154
|
+
* 1. **Exact cache hit** — layout, transform, color, and alpha haven't
|
|
155
|
+
* changed. The cached pre-transformed Float32Array is mem-copied directly
|
|
156
|
+
* into the shared SDF buffer (no per-glyph matrix math).
|
|
157
|
+
* 2. **Translation hit** — only tx/ty changed (the scroll path). The cached
|
|
158
|
+
* vertices are copied with the position delta applied; the cache keeps its
|
|
159
|
+
* original base so nothing is re-snapshotted.
|
|
160
|
+
* 3. **Cache miss** — re-computes per-glyph world-space vertices via
|
|
158
161
|
* `addSdfQuads`, then snapshots the result into the cache.
|
|
159
162
|
*/
|
|
160
163
|
const renderQuads = (
|
|
@@ -176,7 +179,7 @@ const renderQuads = (
|
|
|
176
179
|
// Compiles on the first real SDF draw; cheap memoized lookup thereafter.
|
|
177
180
|
const shader = getSdfShader(webGlRenderer.stage);
|
|
178
181
|
|
|
179
|
-
// --- Cache-hit fast
|
|
182
|
+
// --- Cache-hit fast paths -----------------------------------------------
|
|
180
183
|
if (cache !== undefined && cache.vertices !== null) {
|
|
181
184
|
const ct = cache.transform;
|
|
182
185
|
const t = renderProps.globalTransform;
|
|
@@ -187,13 +190,38 @@ const renderQuads = (
|
|
|
187
190
|
ct[0] === t[0] &&
|
|
188
191
|
ct[1] === t[1] &&
|
|
189
192
|
ct[2] === t[3] &&
|
|
190
|
-
ct[3] === t[4]
|
|
191
|
-
ct[4] === t[6] &&
|
|
192
|
-
ct[5] === t[7]
|
|
193
|
+
ct[3] === t[4]
|
|
193
194
|
) {
|
|
194
|
-
|
|
195
|
+
const dx = t[6]! - ct[4]!;
|
|
196
|
+
const dy = t[7]! - ct[5]!;
|
|
197
|
+
|
|
198
|
+
if (dx === 0 && dy === 0) {
|
|
199
|
+
// Fully static: mem-copy the cached vertices as-is.
|
|
200
|
+
webGlRenderer.addSdfCachedQuads(
|
|
201
|
+
cache.vertices,
|
|
202
|
+
cache.glyphCount,
|
|
203
|
+
ctxTexture,
|
|
204
|
+
renderProps.clippingRect,
|
|
205
|
+
renderProps.worldAlpha,
|
|
206
|
+
layout.width,
|
|
207
|
+
layout.height,
|
|
208
|
+
renderProps.parentHasRenderTexture,
|
|
209
|
+
renderProps.framebufferDimensions,
|
|
210
|
+
shader,
|
|
211
|
+
);
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Pure translation (the scroll path): same glyphs, same scale/rotation,
|
|
216
|
+
// only tx/ty moved. Copy the cached vertices shifted by the delta from
|
|
217
|
+
// the cached base transform. The cache keeps its original base, so
|
|
218
|
+
// every frame recomputes from the same reference — no drift and no
|
|
219
|
+
// per-frame re-snapshot.
|
|
220
|
+
webGlRenderer.addSdfTranslatedQuads(
|
|
195
221
|
cache.vertices,
|
|
196
222
|
cache.glyphCount,
|
|
223
|
+
dx,
|
|
224
|
+
dy,
|
|
197
225
|
ctxTexture,
|
|
198
226
|
renderProps.clippingRect,
|
|
199
227
|
renderProps.worldAlpha,
|