1#include <metal_stdlib>
2#include "shaders.h"
3
4using namespace metal;
5
6float4 coloru_to_colorf(uchar4 coloru) {
7 return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
8}
9
10float4 to_device_position(float2 pixel_position, float2 viewport_size) {
11 return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
12}
13
14// A standard gaussian function, used for weighting samples
15float gaussian(float x, float sigma) {
16 return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
17}
18
19// This approximates the error function, needed for the gaussian integral
20float2 erf(float2 x) {
21 float2 s = sign(x);
22 float2 a = abs(x);
23 x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
24 x *= x;
25 return s - s / (x * x);
26}
27
28float blur_along_x(float x, float y, float sigma, float corner, float2 halfSize) {
29 float delta = min(halfSize.y - corner - abs(y), 0.);
30 float curved = halfSize.x - corner + sqrt(max(0., corner * corner - delta * delta));
31 float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
32 return integral.y - integral.x;
33}
34
35struct QuadFragmentInput {
36 float4 position [[position]];
37 vector_float2 origin;
38 vector_float2 size;
39 vector_uchar4 background_color;
40 float border_top;
41 float border_right;
42 float border_bottom;
43 float border_left;
44 vector_uchar4 border_color;
45 float corner_radius;
46};
47
48vertex QuadFragmentInput quad_vertex(
49 uint unit_vertex_id [[vertex_id]],
50 uint quad_id [[instance_id]],
51 constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
52 constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
53 constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
54) {
55 float2 unit_vertex = unit_vertices[unit_vertex_id];
56 GPUIQuad quad = quads[quad_id];
57 float2 position = unit_vertex * quad.size + quad.origin;
58 float4 device_position = to_device_position(position, uniforms->viewport_size);
59
60 return QuadFragmentInput {
61 device_position,
62 quad.origin,
63 quad.size,
64 quad.background_color,
65 quad.border_top,
66 quad.border_right,
67 quad.border_bottom,
68 quad.border_left,
69 quad.border_color,
70 quad.corner_radius,
71 };
72}
73
74fragment float4 quad_fragment(
75 QuadFragmentInput input [[stage_in]]
76) {
77 float2 half_size = input.size / 2.;
78 float2 center = input.origin + half_size;
79 float2 center_to_point = input.position.xy - center;
80 float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
81 float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.corner_radius;
82
83 float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
84 float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
85 float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
86 float2 point_to_inset_corner = abs(center_to_point) - inset_size;
87 float border_width;
88 if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
89 border_width = 0.;
90 } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
91 border_width = horizontal_border;
92 } else {
93 border_width = vertical_border;
94 }
95
96 float4 color;
97 if (border_width == 0.) {
98 color = coloru_to_colorf(input.background_color);
99 } else {
100 float inset_distance = distance + border_width;
101 color = mix(
102 coloru_to_colorf(input.border_color),
103 coloru_to_colorf(input.background_color),
104 saturate(0.5 - inset_distance)
105 );
106 }
107
108 float4 coverage = float4(1., 1., 1., saturate(0.5 - distance));
109 return coverage * color;
110}
111
112struct ShadowFragmentInput {
113 float4 position [[position]];
114 vector_float2 origin;
115 vector_float2 size;
116 float corner_radius;
117 float sigma;
118 vector_uchar4 color;
119};
120
121vertex ShadowFragmentInput shadow_vertex(
122 uint unit_vertex_id [[vertex_id]],
123 uint shadow_id [[instance_id]],
124 constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
125 constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
126 constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
127) {
128 float2 unit_vertex = unit_vertices[unit_vertex_id];
129 GPUIShadow shadow = shadows[shadow_id];
130
131 float margin = 3. * shadow.sigma;
132 float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
133 float4 device_position = to_device_position(position, uniforms->viewport_size);
134
135 return ShadowFragmentInput {
136 device_position,
137 shadow.origin,
138 shadow.size,
139 shadow.corner_radius,
140 shadow.sigma,
141 shadow.color,
142 };
143}
144
145fragment float4 shadow_fragment(
146 ShadowFragmentInput input [[stage_in]]
147) {
148 float sigma = input.sigma;
149 float corner_radius = input.corner_radius;
150 float2 half_size = input.size / 2.;
151 float2 center = input.origin + half_size;
152 float2 point = input.position.xy - center;
153
154 // The signal is only non-zero in a limited range, so don't waste samples
155 float low = point.y - half_size.y;
156 float high = point.y + half_size.y;
157 float start = clamp(-3. * sigma, low, high);
158 float end = clamp(3. * sigma, low, high);
159
160 // Accumulate samples (we can get away with surprisingly few samples)
161 float step = (end - start) / 4.;
162 float y = start + step * 0.5;
163 float alpha = 0.;
164 for (int i = 0; i < 4; i++) {
165 alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
166 y += step;
167 }
168
169 return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
170}
171
172struct SpriteFragmentInput {
173 float4 position [[position]];
174 float2 atlas_position;
175 float4 color [[flat]];
176 uchar compute_winding [[flat]];
177};
178
179vertex SpriteFragmentInput sprite_vertex(
180 uint unit_vertex_id [[vertex_id]],
181 uint sprite_id [[instance_id]],
182 constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
183 constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
184 constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
185 constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
186) {
187 float2 unit_vertex = unit_vertices[unit_vertex_id];
188 GPUISprite sprite = sprites[sprite_id];
189 float2 position = unit_vertex * sprite.target_size + sprite.origin;
190 float4 device_position = to_device_position(position, *viewport_size);
191 float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
192
193 return SpriteFragmentInput {
194 device_position,
195 atlas_position,
196 coloru_to_colorf(sprite.color),
197 sprite.compute_winding
198 };
199}
200
201#define MAX_WINDINGS 32.
202
203fragment float4 sprite_fragment(
204 SpriteFragmentInput input [[stage_in]],
205 texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
206) {
207 constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
208 float4 color = input.color;
209 float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
210 float mask;
211 if (input.compute_winding) {
212 mask = 1. - abs(1. - fmod(sample.r * MAX_WINDINGS, 2.));
213 } else {
214 mask = sample.a;
215 }
216 color.a *= mask;
217 return color;
218}
219
220struct PathAtlasVertexOutput {
221 float4 position [[position]];
222 float2 st_position;
223 float clip_rect_distance [[clip_distance]] [4];
224};
225
226struct PathAtlasFragmentInput {
227 float4 position [[position]];
228 float2 st_position;
229};
230
231vertex PathAtlasVertexOutput path_atlas_vertex(
232 uint vertex_id [[vertex_id]],
233 constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
234 constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
235) {
236 GPUIPathVertex v = vertices[vertex_id];
237 float4 device_position = to_device_position(v.xy_position, *atlas_size);
238 return PathAtlasVertexOutput {
239 device_position,
240 v.st_position,
241 {
242 v.xy_position.x - v.clip_rect_origin.x,
243 v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
244 v.xy_position.y - v.clip_rect_origin.y,
245 v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
246 }
247 };
248}
249
250fragment float4 path_atlas_fragment(
251 PathAtlasFragmentInput input [[stage_in]]
252) {
253 float2 dx = dfdx(input.st_position);
254 float2 dy = dfdy(input.st_position);
255 float2 gradient = float2(
256 (2. * input.st_position.x) * dx.x - dx.y,
257 (2. * input.st_position.x) * dy.x - dy.y
258 );
259 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
260 float distance = f / length(gradient);
261 float alpha = saturate(0.5 - distance) / MAX_WINDINGS;
262 return float4(alpha, 0., 0., 1.);
263}