@solidtv/renderer 1.5.6 → 1.6.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 (106) hide show
  1. package/dist/src/core/CoreNode.js +2 -2
  2. package/dist/src/core/CoreNode.js.map +1 -1
  3. package/dist/src/core/CoreTextureManager.d.ts +31 -0
  4. package/dist/src/core/CoreTextureManager.js +87 -6
  5. package/dist/src/core/CoreTextureManager.js.map +1 -1
  6. package/dist/src/core/Stage.js +11 -2
  7. package/dist/src/core/Stage.js.map +1 -1
  8. package/dist/src/core/lib/WebGlContextWrapper.d.ts +17 -1
  9. package/dist/src/core/lib/WebGlContextWrapper.js +21 -0
  10. package/dist/src/core/lib/WebGlContextWrapper.js.map +1 -1
  11. package/dist/src/core/lib/colorParser.d.ts +4 -1
  12. package/dist/src/core/lib/colorParser.js +16 -6
  13. package/dist/src/core/lib/colorParser.js.map +1 -1
  14. package/dist/src/core/platforms/web/WebPlatform.js +60 -50
  15. package/dist/src/core/platforms/web/WebPlatform.js.map +1 -1
  16. package/dist/src/core/renderers/canvas/CanvasRenderer.d.ts +4 -0
  17. package/dist/src/core/renderers/canvas/CanvasRenderer.js +28 -13
  18. package/dist/src/core/renderers/canvas/CanvasRenderer.js.map +1 -1
  19. package/dist/src/core/renderers/canvas/CanvasShaderNode.js +1 -0
  20. package/dist/src/core/renderers/canvas/CanvasShaderNode.js.map +1 -1
  21. package/dist/src/core/renderers/webgl/SdfRenderOp.js +2 -2
  22. package/dist/src/core/renderers/webgl/SdfRenderOp.js.map +1 -1
  23. package/dist/src/core/renderers/webgl/WebGlRenderer.d.ts +43 -0
  24. package/dist/src/core/renderers/webgl/WebGlRenderer.js +109 -10
  25. package/dist/src/core/renderers/webgl/WebGlRenderer.js.map +1 -1
  26. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js +21 -13
  27. package/dist/src/core/renderers/webgl/WebGlShaderProgram.js.map +1 -1
  28. package/dist/src/core/renderers/webgl/internal/ShaderUtils.d.ts +8 -3
  29. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js +12 -11
  30. package/dist/src/core/renderers/webgl/internal/ShaderUtils.js.map +1 -1
  31. package/dist/src/core/shaders/canvas/Rounded.js +3 -3
  32. package/dist/src/core/shaders/canvas/Rounded.js.map +1 -1
  33. package/dist/src/core/shaders/canvas/RoundedWithShadow.js +3 -3
  34. package/dist/src/core/shaders/canvas/RoundedWithShadow.js.map +1 -1
  35. package/dist/src/core/shaders/canvas/utils/render.js +3 -3
  36. package/dist/src/core/shaders/canvas/utils/render.js.map +1 -1
  37. package/dist/src/core/shaders/webgl/Border.js +12 -11
  38. package/dist/src/core/shaders/webgl/Border.js.map +1 -1
  39. package/dist/src/core/shaders/webgl/HolePunch.js +12 -5
  40. package/dist/src/core/shaders/webgl/HolePunch.js.map +1 -1
  41. package/dist/src/core/shaders/webgl/LinearGradient.js +3 -19
  42. package/dist/src/core/shaders/webgl/LinearGradient.js.map +1 -1
  43. package/dist/src/core/shaders/webgl/RadialGradient.js +3 -20
  44. package/dist/src/core/shaders/webgl/RadialGradient.js.map +1 -1
  45. package/dist/src/core/shaders/webgl/RadialProgress.js +15 -23
  46. package/dist/src/core/shaders/webgl/RadialProgress.js.map +1 -1
  47. package/dist/src/core/shaders/webgl/RoundedWithBorder.js +35 -32
  48. package/dist/src/core/shaders/webgl/RoundedWithBorder.js.map +1 -1
  49. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js +58 -45
  50. package/dist/src/core/shaders/webgl/RoundedWithBorderAndShadow.js.map +1 -1
  51. package/dist/src/core/shaders/webgl/RoundedWithShadow.js +15 -13
  52. package/dist/src/core/shaders/webgl/RoundedWithShadow.js.map +1 -1
  53. package/dist/src/core/shaders/webgl/Shadow.js +4 -5
  54. package/dist/src/core/shaders/webgl/Shadow.js.map +1 -1
  55. package/dist/src/core/text-rendering/SdfTextRenderer.js +23 -10
  56. package/dist/src/core/text-rendering/SdfTextRenderer.js.map +1 -1
  57. package/dist/src/core/text-rendering/TextLayoutEngine.js +14 -2
  58. package/dist/src/core/text-rendering/TextLayoutEngine.js.map +1 -1
  59. package/dist/src/main-api/Renderer.d.ts +28 -5
  60. package/dist/src/main-api/Renderer.js +5 -1
  61. package/dist/src/main-api/Renderer.js.map +1 -1
  62. package/dist/src/utils.js +1 -1
  63. package/dist/src/utils.js.map +1 -1
  64. package/dist/tsconfig.dist.tsbuildinfo +1 -1
  65. package/package.json +1 -1
  66. package/src/core/CoreNode.ts +2 -2
  67. package/src/core/CoreTextureManager.test.ts +147 -0
  68. package/src/core/CoreTextureManager.ts +97 -8
  69. package/src/core/Stage.renderListUpdate.test.ts +73 -0
  70. package/src/core/Stage.ts +13 -1
  71. package/src/core/lib/WebGlContextWrapper.test.ts +23 -1
  72. package/src/core/lib/WebGlContextWrapper.ts +23 -1
  73. package/src/core/lib/colorParser.test.ts +46 -0
  74. package/src/core/lib/colorParser.ts +17 -6
  75. package/src/core/platforms/web/WebPlatform.ts +63 -52
  76. package/src/core/renderers/canvas/CanvasRenderer.test.ts +230 -0
  77. package/src/core/renderers/canvas/CanvasRenderer.ts +29 -15
  78. package/src/core/renderers/canvas/CanvasShaderNode.test.ts +73 -0
  79. package/src/core/renderers/canvas/CanvasShaderNode.ts +1 -0
  80. package/src/core/renderers/webgl/SdfRenderOp.ts +2 -2
  81. package/src/core/renderers/webgl/WebGlRenderer.reuseRenderOp.test.ts +122 -0
  82. package/src/core/renderers/webgl/WebGlRenderer.sdfBuffer.test.ts +302 -0
  83. package/src/core/renderers/webgl/WebGlRenderer.ts +151 -11
  84. package/src/core/renderers/webgl/WebGlShaderProgram.reuseRenderOp.test.ts +150 -0
  85. package/src/core/renderers/webgl/WebGlShaderProgram.ts +27 -15
  86. package/src/core/renderers/webgl/WebGlShaderProgram.uniformDedup.test.ts +4 -2
  87. package/src/core/renderers/webgl/internal/ShaderUtils.test.ts +124 -0
  88. package/src/core/renderers/webgl/internal/ShaderUtils.ts +14 -13
  89. package/src/core/shaders/canvas/Rounded.ts +3 -3
  90. package/src/core/shaders/canvas/RoundedWithShadow.ts +3 -3
  91. package/src/core/shaders/canvas/utils/render.ts +3 -3
  92. package/src/core/shaders/webgl/Border.ts +12 -11
  93. package/src/core/shaders/webgl/HolePunch.ts +12 -5
  94. package/src/core/shaders/webgl/LinearGradient.ts +3 -19
  95. package/src/core/shaders/webgl/RadialGradient.ts +3 -20
  96. package/src/core/shaders/webgl/RadialProgress.ts +15 -23
  97. package/src/core/shaders/webgl/RoundedWithBorder.ts +35 -32
  98. package/src/core/shaders/webgl/RoundedWithBorderAndShadow.ts +58 -45
  99. package/src/core/shaders/webgl/RoundedWithShadow.ts +15 -13
  100. package/src/core/shaders/webgl/Shadow.ts +4 -5
  101. package/src/core/text-rendering/SdfTextRenderer.test.ts +177 -0
  102. package/src/core/text-rendering/SdfTextRenderer.ts +38 -10
  103. package/src/core/text-rendering/TextLayoutEngine.ts +15 -2
  104. package/src/core/text-rendering/tests/TextLayoutEngine.test.ts +99 -0
  105. package/src/main-api/Renderer.ts +35 -6
  106. package/src/utils.ts +1 -1
@@ -57,9 +57,9 @@ export function roundedRectWithBorder(
57
57
 
58
58
  //draw node content first
59
59
  ctx.save();
60
- const path = new Path2D();
61
- roundRect(path, x, y, width, height, radius);
62
- ctx.clip(path);
60
+ ctx.beginPath();
61
+ roundRect(ctx, x, y, width, height, radius);
62
+ ctx.clip();
63
63
  renderContext();
64
64
  ctx.restore();
65
65
 
@@ -135,24 +135,25 @@ export const Border: WebGlShaderType<BorderProps> = {
135
135
  float outerDist = box(boxUv + v_outerBorderUv, v_outerSize - edgeWidth);
136
136
  float innerDist = box(boxUv + v_innerBorderUv, v_innerSize - edgeWidth);
137
137
 
138
- if(u_borderGap == 0.0) {
139
- float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
140
- float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
141
- resultColor = mix(resultColor, u_borderColor, outerAlpha * u_borderColor.a);
142
- resultColor = mix(resultColor, color, innerAlpha);
143
- gl_FragColor = resultColor * u_alpha;
144
- return;
145
- }
138
+ float outerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, outerDist);
139
+ float innerAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, innerDist);
146
140
 
147
141
  float nodeDist = box(boxUv, v_halfDimensions - edgeWidth);
148
142
  float nodeAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, nodeDist);
149
143
 
150
144
  float borderDist = max(-innerDist, outerDist);
151
145
  float borderAlpha = 1.0 - smoothstep(-0.5 * edgeWidth, 0.5 * edgeWidth, borderDist);
152
- resultColor = mix(resultColor, color, nodeAlpha);
153
- resultColor = mix(resultColor, u_borderColor, borderAlpha * u_borderColor.a);
154
146
 
155
- gl_FragColor = resultColor * u_alpha;
147
+ // Branchless gap select -- Mali 400 serializes uniform branches, so both
148
+ // composites are computed and mix()-selected on hasGap instead.
149
+ vec4 resNoGap = mix(resultColor, u_borderColor, outerAlpha * u_borderColor.a);
150
+ resNoGap = mix(resNoGap, color, innerAlpha);
151
+
152
+ vec4 resGap = mix(resultColor, color, nodeAlpha);
153
+ resGap = mix(resGap, u_borderColor, borderAlpha * u_borderColor.a);
154
+
155
+ float hasGap = step(0.0001, abs(u_borderGap));
156
+ gl_FragColor = mix(resNoGap, resGap, hasGap) * u_alpha;
156
157
  }
157
158
  `,
158
159
  };
@@ -46,11 +46,18 @@ export const HolePunch: WebGlShaderType<HolePunchProps> = {
46
46
  void main() {
47
47
  vec4 color = texture2D(u_texture, v_textureCoords) * v_color;
48
48
  vec2 p = (v_textureCoords.xy * u_dimensions.xy - u_pos) - u_size;
49
- vec4 r = u_radius;
50
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
51
- r.x = (p.y > 0.0) ? r.y : r.x;
52
- p = abs(p) - u_size + r.x;
53
- float dist = min(max(p.x, p.y), 0.0) + length(max(p, 0.0)) - r.x + 2.0;
49
+
50
+ // Branchless radius selection based on quadrant
51
+ // x: TL, y: TR, z: BR, w: BL
52
+ vec2 stepVal = step(vec2(0.0), p);
53
+ float r = mix(
54
+ mix(u_radius.x, u_radius.y, stepVal.x),
55
+ mix(u_radius.w, u_radius.z, stepVal.x),
56
+ stepVal.y
57
+ );
58
+
59
+ p = abs(p) - u_size + r;
60
+ float dist = min(max(p.x, p.y), 0.0) + length(max(p, 0.0)) - r + 2.0;
54
61
  float roundedAlpha = 1.0 - smoothstep(0.0, u_pixelRatio, dist);
55
62
  gl_FragColor = mix(color, vec4(0.0), min(color.a, roundedAlpha));
56
63
  }
@@ -6,6 +6,7 @@ import {
6
6
  } from '../templates/LinearGradientTemplate.js';
7
7
  import type { WebGlRenderer } from '../../renderers/webgl/WebGlRenderer.js';
8
8
  import type { WebGlShaderType } from '../../renderers/webgl/WebGlShaderNode.js';
9
+ import { genGradientColors } from '../../renderers/webgl/internal/ShaderUtils.js';
9
10
 
10
11
  export const LinearGradient: WebGlShaderType<LinearGradientProps> = {
11
12
  props: LinearGradientTemplate.props,
@@ -56,7 +57,6 @@ export const LinearGradient: WebGlShaderType<LinearGradientProps> = {
56
57
  # endif
57
58
 
58
59
  #define MAX_STOPS ${props.colors.length}
59
- #define LAST_STOP ${props.colors.length - 1}
60
60
 
61
61
  uniform float u_alpha;
62
62
 
@@ -72,24 +72,8 @@ export const LinearGradient: WebGlShaderType<LinearGradientProps> = {
72
72
 
73
73
  vec4 getGradientColor(float dist) {
74
74
  dist = clamp(dist, 0.0, 1.0);
75
-
76
- if(dist <= u_stops[0]) {
77
- return u_colors[0];
78
- }
79
-
80
- if(dist >= u_stops[LAST_STOP]) {
81
- return u_colors[LAST_STOP];
82
- }
83
-
84
- for(int i = 0; i < LAST_STOP; i++) {
85
- float left = u_stops[i];
86
- float right = u_stops[i + 1];
87
- if(dist >= left && dist <= right) {
88
- float lDist = smoothstep(left, right, dist);
89
- return mix(u_colors[i], u_colors[i + 1], lDist);
90
- }
91
- }
92
- return u_colors[LAST_STOP];
75
+ ${genGradientColors(props.colors.length)}
76
+ return colorOut;
93
77
  }
94
78
 
95
79
  void main() {
@@ -6,6 +6,7 @@ import {
6
6
  } from '../templates/RadialGradientTemplate.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 RadialGradient: WebGlShaderType<RadialGradientProps> = {
11
12
  props: RadialGradientTemplate.props,
@@ -37,7 +38,6 @@ export const RadialGradient: WebGlShaderType<RadialGradientProps> = {
37
38
  # endif
38
39
 
39
40
  #define MAX_STOPS ${props.colors.length}
40
- #define LAST_STOP ${props.colors.length - 1}
41
41
 
42
42
  uniform float u_alpha;
43
43
  uniform vec2 u_dimensions;
@@ -56,25 +56,8 @@ export const RadialGradient: WebGlShaderType<RadialGradientProps> = {
56
56
 
57
57
  vec4 getGradientColor(float dist) {
58
58
  dist = clamp(dist, 0.0, 1.0);
59
-
60
- if(dist <= u_stops[0]) {
61
- return u_colors[0];
62
- }
63
-
64
- if(dist >= u_stops[LAST_STOP]) {
65
- return u_colors[LAST_STOP];
66
- }
67
-
68
- for(int i = 0; i < LAST_STOP; i++) {
69
- float left = u_stops[i];
70
- float right = u_stops[i + 1];
71
- if(dist >= left && dist <= right) {
72
- float lDist = smoothstep(left, right, dist);
73
- return mix(u_colors[i], u_colors[i + 1], lDist);
74
- }
75
- }
76
-
77
- return u_colors[LAST_STOP];
59
+ ${genGradientColors(props.colors.length)}
60
+ return colorOut;
78
61
  }
79
62
 
80
63
  void main() {
@@ -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
- if (dist <= u_stops[0]) {
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
- float cyclePos = u_duration > 0.0 ? fract(u_time / u_duration) : 0.0;
120
- float animProgress = u_countdown > 0.5 ? 1.0 - cyclePos : cyclePos;
121
- float progress = u_duration > 0.0 ? animProgress : u_progress;
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 = progress >= 1.0 ? 1.0 : step(t, progress);
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
- float gradT = progress > 0.0 ? clamp(t / progress, 0.0, 1.0) : 0.0;
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
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
164
- r.x = (p.y > 0.0) ? r.y : r.x;
165
- vec2 q = abs(p) - s + r.x;
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
- resultColor = (color * nodeAlpha) + (u_fillColor * gapAlpha);
207
- resultColor = mix(resultColor, u_borderColor, borderAlpha * u_borderColor.a);
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
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
171
- r.x = (p.y > 0.0) ? r.y : r.x;
172
- vec2 q = abs(p) - s + r.x;
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
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
178
- r.x = (p.y > 0.0) ? r.y : r.x;
179
- vec2 q = abs(p) - s + r.x;
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
- if(v_outerSize.x > v_halfDimensions.x || v_outerSize.y > v_halfDimensions.y) {
204
- shadowAlpha = shadowBox(boxUv + v_outerBorderUv - u_shadow.xy, v_outerSize + u_shadow.w - edgeWidth, v_outerBorderRadius + u_shadow.z);
205
- }
206
- else {
207
- shadowAlpha = shadowBox(boxUv - u_shadow.xy, v_halfDimensions + u_shadow.w - edgeWidth, u_radius + u_shadow.z);
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
- resultColor = mix(resultColor, u_shadowColor, shadowAlpha);
228
- resultColor = mix(resultColor, color, nodeAlpha);
229
- resultColor = mix(resultColor, u_borderColor, borderAlpha * u_borderColor.a);
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
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
103
- r.x = (p.y > 0.0) ? r.y : r.x;
104
- vec2 q = abs(p) - s + r.x;
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
- r.xy = (p.x > 0.0) ? r.yz : r.xw;
110
- r.x = (p.y > 0.0) ? r.y : r.x;
111
- vec2 q = abs(p) - s + r.x;
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
- vec4 resColor = vec4(0.0);
123
- if (u_shadow_color.a > 0.0) {
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
- vec4 resColor = vec4(0.0);
110
- if (u_color.a > 0.0) {
111
- float shadowDist = shadowBox(v_shadowBox, v_shadowSize, v_shadowRadius);
112
- resColor = u_color * shadowDist;
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;