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 float2 atlas_position; // only used in the image shader
38 float2 origin;
39 float2 size;
40 float4 background_color;
41 float border_top;
42 float border_right;
43 float border_bottom;
44 float border_left;
45 float4 border_color;
46 float corner_radius;
47};
48
49float4 quad_sdf(QuadFragmentInput input) {
50 float2 half_size = input.size / 2.;
51 float2 center = input.origin + half_size;
52 float2 center_to_point = input.position.xy - center;
53 float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
54 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;
55
56 float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
57 float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
58 float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
59 float2 point_to_inset_corner = abs(center_to_point) - inset_size;
60 float border_width;
61 if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
62 border_width = 0.;
63 } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
64 border_width = horizontal_border;
65 } else {
66 border_width = vertical_border;
67 }
68
69 float4 color;
70 if (border_width == 0.) {
71 color = input.background_color;
72 } else {
73 float inset_distance = distance + border_width;
74
75 // Decrease border's opacity as we move inside the background.
76 input.border_color.a *= 1. - saturate(0.5 - inset_distance);
77
78 // Alpha-blend the border and the background.
79 float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
80 float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
81 float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
82 float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
83 color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
84 }
85
86 return color * float4(1., 1., 1., saturate(0.5 - distance));
87}
88
89vertex QuadFragmentInput quad_vertex(
90 uint unit_vertex_id [[vertex_id]],
91 uint quad_id [[instance_id]],
92 constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
93 constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
94 constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
95) {
96 float2 unit_vertex = unit_vertices[unit_vertex_id];
97 GPUIQuad quad = quads[quad_id];
98 float2 position = unit_vertex * quad.size + quad.origin;
99 float4 device_position = to_device_position(position, uniforms->viewport_size);
100
101 return QuadFragmentInput {
102 device_position,
103 float2(0., 0.),
104 quad.origin,
105 quad.size,
106 coloru_to_colorf(quad.background_color),
107 quad.border_top,
108 quad.border_right,
109 quad.border_bottom,
110 quad.border_left,
111 coloru_to_colorf(quad.border_color),
112 quad.corner_radius,
113 };
114}
115
116fragment float4 quad_fragment(
117 QuadFragmentInput input [[stage_in]]
118) {
119 return quad_sdf(input);
120}
121
122struct ShadowFragmentInput {
123 float4 position [[position]];
124 vector_float2 origin;
125 vector_float2 size;
126 float corner_radius;
127 float sigma;
128 vector_uchar4 color;
129};
130
131vertex ShadowFragmentInput shadow_vertex(
132 uint unit_vertex_id [[vertex_id]],
133 uint shadow_id [[instance_id]],
134 constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
135 constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
136 constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
137) {
138 float2 unit_vertex = unit_vertices[unit_vertex_id];
139 GPUIShadow shadow = shadows[shadow_id];
140
141 float margin = 3. * shadow.sigma;
142 float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
143 float4 device_position = to_device_position(position, uniforms->viewport_size);
144
145 return ShadowFragmentInput {
146 device_position,
147 shadow.origin,
148 shadow.size,
149 shadow.corner_radius,
150 shadow.sigma,
151 shadow.color,
152 };
153}
154
155fragment float4 shadow_fragment(
156 ShadowFragmentInput input [[stage_in]]
157) {
158 float sigma = input.sigma;
159 float corner_radius = input.corner_radius;
160 float2 half_size = input.size / 2.;
161 float2 center = input.origin + half_size;
162 float2 point = input.position.xy - center;
163
164 // The signal is only non-zero in a limited range, so don't waste samples
165 float low = point.y - half_size.y;
166 float high = point.y + half_size.y;
167 float start = clamp(-3. * sigma, low, high);
168 float end = clamp(3. * sigma, low, high);
169
170 // Accumulate samples (we can get away with surprisingly few samples)
171 float step = (end - start) / 4.;
172 float y = start + step * 0.5;
173 float alpha = 0.;
174 for (int i = 0; i < 4; i++) {
175 alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
176 y += step;
177 }
178
179 return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
180}
181
182struct SpriteFragmentInput {
183 float4 position [[position]];
184 float2 atlas_position;
185 float4 color [[flat]];
186 uchar compute_winding [[flat]];
187};
188
189vertex SpriteFragmentInput sprite_vertex(
190 uint unit_vertex_id [[vertex_id]],
191 uint sprite_id [[instance_id]],
192 constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
193 constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
194 constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
195 constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
196) {
197 float2 unit_vertex = unit_vertices[unit_vertex_id];
198 GPUISprite sprite = sprites[sprite_id];
199 float2 position = unit_vertex * sprite.target_size + sprite.origin;
200 float4 device_position = to_device_position(position, *viewport_size);
201 float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
202
203 return SpriteFragmentInput {
204 device_position,
205 atlas_position,
206 coloru_to_colorf(sprite.color),
207 sprite.compute_winding
208 };
209}
210
211fragment float4 sprite_fragment(
212 SpriteFragmentInput input [[stage_in]],
213 texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
214) {
215 constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
216 float4 color = input.color;
217 float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
218 float mask;
219 if (input.compute_winding) {
220 mask = 1. - abs(1. - fmod(sample.r, 2.));
221 } else {
222 mask = sample.a;
223 }
224 color.a *= mask;
225 return color;
226}
227
228vertex QuadFragmentInput image_vertex(
229 uint unit_vertex_id [[vertex_id]],
230 uint image_id [[instance_id]],
231 constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
232 constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
233 constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
234 constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
235) {
236 float2 unit_vertex = unit_vertices[unit_vertex_id];
237 GPUIImage image = images[image_id];
238 float2 position = unit_vertex * image.target_size + image.origin;
239 float4 device_position = to_device_position(position, *viewport_size);
240 float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
241
242 return QuadFragmentInput {
243 device_position,
244 atlas_position,
245 image.origin,
246 image.target_size,
247 float4(0.),
248 image.border_top,
249 image.border_right,
250 image.border_bottom,
251 image.border_left,
252 coloru_to_colorf(image.border_color),
253 image.corner_radius,
254 };
255}
256
257fragment float4 image_fragment(
258 QuadFragmentInput input [[stage_in]],
259 texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
260) {
261 constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
262 input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
263 return quad_sdf(input);
264}
265
266struct PathAtlasVertexOutput {
267 float4 position [[position]];
268 float2 st_position;
269 float clip_rect_distance [[clip_distance]] [4];
270};
271
272struct PathAtlasFragmentInput {
273 float4 position [[position]];
274 float2 st_position;
275};
276
277vertex PathAtlasVertexOutput path_atlas_vertex(
278 uint vertex_id [[vertex_id]],
279 constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
280 constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
281) {
282 GPUIPathVertex v = vertices[vertex_id];
283 float4 device_position = to_device_position(v.xy_position, *atlas_size);
284 return PathAtlasVertexOutput {
285 device_position,
286 v.st_position,
287 {
288 v.xy_position.x - v.clip_rect_origin.x,
289 v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
290 v.xy_position.y - v.clip_rect_origin.y,
291 v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
292 }
293 };
294}
295
296fragment float4 path_atlas_fragment(
297 PathAtlasFragmentInput input [[stage_in]]
298) {
299 float2 dx = dfdx(input.st_position);
300 float2 dy = dfdy(input.st_position);
301 float2 gradient = float2(
302 (2. * input.st_position.x) * dx.x - dx.y,
303 (2. * input.st_position.x) * dy.x - dy.y
304 );
305 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
306 float distance = f / length(gradient);
307 float alpha = saturate(0.5 - distance);
308 return float4(alpha, 0., 0., 1.);
309}
310
311struct UnderlineFragmentInput {
312 float4 position [[position]];
313 float2 origin;
314 float2 size;
315 float thickness;
316 float4 color;
317 bool squiggly;
318};
319
320vertex UnderlineFragmentInput underline_vertex(
321 uint unit_vertex_id [[vertex_id]],
322 uint underline_id [[instance_id]],
323 constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
324 constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
325 constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
326) {
327 float2 unit_vertex = unit_vertices[unit_vertex_id];
328 GPUIUnderline underline = underlines[underline_id];
329 float2 position = unit_vertex * underline.size + underline.origin;
330 float4 device_position = to_device_position(position, uniforms->viewport_size);
331
332 return UnderlineFragmentInput {
333 device_position,
334 underline.origin,
335 underline.size,
336 underline.thickness,
337 coloru_to_colorf(underline.color),
338 underline.squiggly != 0,
339 };
340}
341
342fragment float4 underline_fragment(
343 UnderlineFragmentInput input [[stage_in]]
344) {
345 if (input.squiggly) {
346 float half_thickness = input.thickness * 0.5;
347 float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
348 float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
349 float amplitude = 1. / (2. * input.thickness);
350 float sine = sin(st.x * frequency) * amplitude;
351 float dSine = cos(st.x * frequency) * amplitude * frequency;
352 float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
353 float distance_in_pixels = distance * input.size.y;
354 float distance_from_top_border = distance_in_pixels - half_thickness;
355 float distance_from_bottom_border = distance_in_pixels + half_thickness;
356 float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
357 return input.color * float4(1., 1., 1., alpha);
358 } else {
359 return input.color;
360 }
361}