1#include <metal_stdlib>
2#include <simd/simd.h>
3
4using namespace metal;
5
6float4 hsla_to_rgba(Hsla hsla);
7float3 srgb_to_linear(float3 color);
8float3 linear_to_srgb(float3 color);
9float4 srgb_to_oklab(float4 color);
10float4 oklab_to_srgb(float4 color);
11float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
12 constant Size_DevicePixels *viewport_size);
13float4 to_device_position_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
14 TransformationMatrix transformation,
15 constant Size_DevicePixels *input_viewport_size);
16
17float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
18 constant Size_DevicePixels *atlas_size);
19float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
20 Bounds_ScaledPixels clip_bounds);
21float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
22 Corners_ScaledPixels corner_radii);
23float gaussian(float x, float sigma);
24float2 erf(float2 x);
25float blur_along_x(float x, float y, float sigma, float corner,
26 float2 half_size);
27float4 over(float4 below, float4 above);
28float radians(float degrees);
29float4 gradient_color(Background background, float2 position, Bounds_ScaledPixels bounds,
30 float4 solid_color, float4 color0, float4 color1);
31
32struct GradientColor {
33 float4 solid;
34 float4 color0;
35 float4 color1;
36};
37GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid, Hsla color0, Hsla color1);
38
39struct QuadVertexOutput {
40 uint quad_id [[flat]];
41 float4 position [[position]];
42 float4 border_color [[flat]];
43 float4 background_solid [[flat]];
44 float4 background_color0 [[flat]];
45 float4 background_color1 [[flat]];
46 float clip_distance [[clip_distance]][4];
47};
48
49struct QuadFragmentInput {
50 uint quad_id [[flat]];
51 float4 position [[position]];
52 float4 border_color [[flat]];
53 float4 background_solid [[flat]];
54 float4 background_color0 [[flat]];
55 float4 background_color1 [[flat]];
56};
57
58vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
59 uint quad_id [[instance_id]],
60 constant float2 *unit_vertices
61 [[buffer(QuadInputIndex_Vertices)]],
62 constant Quad *quads
63 [[buffer(QuadInputIndex_Quads)]],
64 constant Size_DevicePixels *viewport_size
65 [[buffer(QuadInputIndex_ViewportSize)]]) {
66 float2 unit_vertex = unit_vertices[unit_vertex_id];
67 Quad quad = quads[quad_id];
68 float4 device_position =
69 to_device_position(unit_vertex, quad.bounds, viewport_size);
70 float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
71 quad.content_mask.bounds);
72 float4 border_color = hsla_to_rgba(quad.border_color);
73
74 GradientColor gradient = prepare_gradient_color(
75 quad.background.tag,
76 quad.background.color_space,
77 quad.background.solid,
78 quad.background.colors[0].color,
79 quad.background.colors[1].color
80 );
81
82 return QuadVertexOutput{
83 quad_id,
84 device_position,
85 border_color,
86 gradient.solid,
87 gradient.color0,
88 gradient.color1,
89 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
90}
91
92fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
93 constant Quad *quads
94 [[buffer(QuadInputIndex_Quads)]]) {
95 Quad quad = quads[input.quad_id];
96 float2 half_size = float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
97 float2 center = float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
98 float2 center_to_point = input.position.xy - center;
99 float4 color = gradient_color(quad.background, input.position.xy, quad.bounds,
100 input.background_solid, input.background_color0, input.background_color1);
101
102 // Fast path when the quad is not rounded and doesn't have any border.
103 if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
104 quad.corner_radii.top_right == 0. &&
105 quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
106 quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
107 quad.border_widths.bottom == 0.) {
108 return color;
109 }
110
111 float corner_radius;
112 if (center_to_point.x < 0.) {
113 if (center_to_point.y < 0.) {
114 corner_radius = quad.corner_radii.top_left;
115 } else {
116 corner_radius = quad.corner_radii.bottom_left;
117 }
118 } else {
119 if (center_to_point.y < 0.) {
120 corner_radius = quad.corner_radii.top_right;
121 } else {
122 corner_radius = quad.corner_radii.bottom_right;
123 }
124 }
125
126 float2 rounded_edge_to_point =
127 fabs(center_to_point) - half_size + corner_radius;
128 float distance =
129 length(max(0., rounded_edge_to_point)) +
130 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
131 corner_radius;
132
133 float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
134 : quad.border_widths.right;
135 float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
136 : quad.border_widths.bottom;
137 float2 inset_size =
138 half_size - corner_radius - float2(vertical_border, horizontal_border);
139 float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
140 float border_width;
141 if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
142 border_width = 0.;
143 } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
144 border_width = horizontal_border;
145 } else {
146 border_width = vertical_border;
147 }
148
149 if (border_width != 0.) {
150 float inset_distance = distance + border_width;
151 // Blend the border on top of the background and then linearly interpolate
152 // between the two as we slide inside the background.
153 float4 blended_border = over(color, input.border_color);
154 color = mix(blended_border, color,
155 saturate(0.5 - inset_distance));
156 }
157
158 return color * float4(1., 1., 1., saturate(0.5 - distance));
159}
160
161struct ShadowVertexOutput {
162 float4 position [[position]];
163 float4 color [[flat]];
164 uint shadow_id [[flat]];
165 float clip_distance [[clip_distance]][4];
166};
167
168struct ShadowFragmentInput {
169 float4 position [[position]];
170 float4 color [[flat]];
171 uint shadow_id [[flat]];
172};
173
174vertex ShadowVertexOutput shadow_vertex(
175 uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
176 constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
177 constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
178 constant Size_DevicePixels *viewport_size
179 [[buffer(ShadowInputIndex_ViewportSize)]]) {
180 float2 unit_vertex = unit_vertices[unit_vertex_id];
181 Shadow shadow = shadows[shadow_id];
182
183 float margin = 3. * shadow.blur_radius;
184 // Set the bounds of the shadow and adjust its size based on the shadow's
185 // spread radius to achieve the spreading effect
186 Bounds_ScaledPixels bounds = shadow.bounds;
187 bounds.origin.x -= margin;
188 bounds.origin.y -= margin;
189 bounds.size.width += 2. * margin;
190 bounds.size.height += 2. * margin;
191
192 float4 device_position =
193 to_device_position(unit_vertex, bounds, viewport_size);
194 float4 clip_distance =
195 distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
196 float4 color = hsla_to_rgba(shadow.color);
197
198 return ShadowVertexOutput{
199 device_position,
200 color,
201 shadow_id,
202 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
203}
204
205fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
206 constant Shadow *shadows
207 [[buffer(ShadowInputIndex_Shadows)]]) {
208 Shadow shadow = shadows[input.shadow_id];
209
210 float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
211 float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
212 float2 half_size = size / 2.;
213 float2 center = origin + half_size;
214 float2 point = input.position.xy - center;
215 float corner_radius;
216 if (point.x < 0.) {
217 if (point.y < 0.) {
218 corner_radius = shadow.corner_radii.top_left;
219 } else {
220 corner_radius = shadow.corner_radii.bottom_left;
221 }
222 } else {
223 if (point.y < 0.) {
224 corner_radius = shadow.corner_radii.top_right;
225 } else {
226 corner_radius = shadow.corner_radii.bottom_right;
227 }
228 }
229
230 // The signal is only non-zero in a limited range, so don't waste samples
231 float low = point.y - half_size.y;
232 float high = point.y + half_size.y;
233 float start = clamp(-3. * shadow.blur_radius, low, high);
234 float end = clamp(3. * shadow.blur_radius, low, high);
235
236 // Accumulate samples (we can get away with surprisingly few samples)
237 float step = (end - start) / 4.;
238 float y = start + step * 0.5;
239 float alpha = 0.;
240 for (int i = 0; i < 4; i++) {
241 alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
242 corner_radius, half_size) *
243 gaussian(y, shadow.blur_radius) * step;
244 y += step;
245 }
246
247 return input.color * float4(1., 1., 1., alpha);
248}
249
250struct UnderlineVertexOutput {
251 float4 position [[position]];
252 float4 color [[flat]];
253 uint underline_id [[flat]];
254 float clip_distance [[clip_distance]][4];
255};
256
257struct UnderlineFragmentInput {
258 float4 position [[position]];
259 float4 color [[flat]];
260 uint underline_id [[flat]];
261};
262
263vertex UnderlineVertexOutput underline_vertex(
264 uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
265 constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
266 constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
267 constant Size_DevicePixels *viewport_size
268 [[buffer(ShadowInputIndex_ViewportSize)]]) {
269 float2 unit_vertex = unit_vertices[unit_vertex_id];
270 Underline underline = underlines[underline_id];
271 float4 device_position =
272 to_device_position(unit_vertex, underline.bounds, viewport_size);
273 float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
274 underline.content_mask.bounds);
275 float4 color = hsla_to_rgba(underline.color);
276 return UnderlineVertexOutput{
277 device_position,
278 color,
279 underline_id,
280 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
281}
282
283fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
284 constant Underline *underlines
285 [[buffer(UnderlineInputIndex_Underlines)]]) {
286 Underline underline = underlines[input.underline_id];
287 if (underline.wavy) {
288 float half_thickness = underline.thickness * 0.5;
289 float2 origin =
290 float2(underline.bounds.origin.x, underline.bounds.origin.y);
291 float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
292 float2(0., 0.5);
293 float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
294 float amplitude = 1. / (2. * underline.thickness);
295 float sine = sin(st.x * frequency) * amplitude;
296 float dSine = cos(st.x * frequency) * amplitude * frequency;
297 float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
298 float distance_in_pixels = distance * underline.bounds.size.height;
299 float distance_from_top_border = distance_in_pixels - half_thickness;
300 float distance_from_bottom_border = distance_in_pixels + half_thickness;
301 float alpha = saturate(
302 0.5 - max(-distance_from_bottom_border, distance_from_top_border));
303 return input.color * float4(1., 1., 1., alpha);
304 } else {
305 return input.color;
306 }
307}
308
309struct MonochromeSpriteVertexOutput {
310 float4 position [[position]];
311 float2 tile_position;
312 float4 color [[flat]];
313 float clip_distance [[clip_distance]][4];
314};
315
316struct MonochromeSpriteFragmentInput {
317 float4 position [[position]];
318 float2 tile_position;
319 float4 color [[flat]];
320};
321
322vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
323 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
324 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
325 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
326 constant Size_DevicePixels *viewport_size
327 [[buffer(SpriteInputIndex_ViewportSize)]],
328 constant Size_DevicePixels *atlas_size
329 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
330 float2 unit_vertex = unit_vertices[unit_vertex_id];
331 MonochromeSprite sprite = sprites[sprite_id];
332 float4 device_position =
333 to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
334 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
335 sprite.content_mask.bounds);
336 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
337 float4 color = hsla_to_rgba(sprite.color);
338 return MonochromeSpriteVertexOutput{
339 device_position,
340 tile_position,
341 color,
342 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
343}
344
345fragment float4 monochrome_sprite_fragment(
346 MonochromeSpriteFragmentInput input [[stage_in]],
347 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
348 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
349 constexpr sampler atlas_texture_sampler(mag_filter::linear,
350 min_filter::linear);
351 float4 sample =
352 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
353 float4 color = input.color;
354 color.a *= sample.a;
355 return color;
356}
357
358struct PolychromeSpriteVertexOutput {
359 float4 position [[position]];
360 float2 tile_position;
361 uint sprite_id [[flat]];
362 float clip_distance [[clip_distance]][4];
363};
364
365struct PolychromeSpriteFragmentInput {
366 float4 position [[position]];
367 float2 tile_position;
368 uint sprite_id [[flat]];
369};
370
371vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
372 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
373 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
374 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
375 constant Size_DevicePixels *viewport_size
376 [[buffer(SpriteInputIndex_ViewportSize)]],
377 constant Size_DevicePixels *atlas_size
378 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
379
380 float2 unit_vertex = unit_vertices[unit_vertex_id];
381 PolychromeSprite sprite = sprites[sprite_id];
382 float4 device_position =
383 to_device_position(unit_vertex, sprite.bounds, viewport_size);
384 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
385 sprite.content_mask.bounds);
386 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
387 return PolychromeSpriteVertexOutput{
388 device_position,
389 tile_position,
390 sprite_id,
391 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
392}
393
394fragment float4 polychrome_sprite_fragment(
395 PolychromeSpriteFragmentInput input [[stage_in]],
396 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
397 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
398 PolychromeSprite sprite = sprites[input.sprite_id];
399 constexpr sampler atlas_texture_sampler(mag_filter::linear,
400 min_filter::linear);
401 float4 sample =
402 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
403 float distance =
404 quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
405
406 float4 color = sample;
407 if (sprite.grayscale) {
408 float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
409 color.r = grayscale;
410 color.g = grayscale;
411 color.b = grayscale;
412 }
413 color.a *= sprite.opacity * saturate(0.5 - distance);
414 return color;
415}
416
417struct PathRasterizationVertexOutput {
418 float4 position [[position]];
419 float2 st_position;
420 float clip_rect_distance [[clip_distance]][4];
421};
422
423struct PathRasterizationFragmentInput {
424 float4 position [[position]];
425 float2 st_position;
426};
427
428vertex PathRasterizationVertexOutput path_rasterization_vertex(
429 uint vertex_id [[vertex_id]],
430 constant PathVertex_ScaledPixels *vertices
431 [[buffer(PathRasterizationInputIndex_Vertices)]],
432 constant Size_DevicePixels *atlas_size
433 [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
434 PathVertex_ScaledPixels v = vertices[vertex_id];
435 float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
436 float2 viewport_size = float2(atlas_size->width, atlas_size->height);
437 return PathRasterizationVertexOutput{
438 float4(vertex_position / viewport_size * float2(2., -2.) +
439 float2(-1., 1.),
440 0., 1.),
441 float2(v.st_position.x, v.st_position.y),
442 {v.xy_position.x - v.content_mask.bounds.origin.x,
443 v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
444 v.xy_position.x,
445 v.xy_position.y - v.content_mask.bounds.origin.y,
446 v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
447 v.xy_position.y}};
448}
449
450fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
451 [[stage_in]]) {
452 float2 dx = dfdx(input.st_position);
453 float2 dy = dfdy(input.st_position);
454 float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
455 (2. * input.st_position.x) * dy.x - dy.y);
456 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
457 float distance = f / length(gradient);
458 float alpha = saturate(0.5 - distance);
459 return float4(alpha, 0., 0., 1.);
460}
461
462struct PathSpriteVertexOutput {
463 float4 position [[position]];
464 float2 tile_position;
465 uint sprite_id [[flat]];
466 float4 solid_color [[flat]];
467 float4 color0 [[flat]];
468 float4 color1 [[flat]];
469};
470
471vertex PathSpriteVertexOutput path_sprite_vertex(
472 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
473 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
474 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
475 constant Size_DevicePixels *viewport_size
476 [[buffer(SpriteInputIndex_ViewportSize)]],
477 constant Size_DevicePixels *atlas_size
478 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
479
480 float2 unit_vertex = unit_vertices[unit_vertex_id];
481 PathSprite sprite = sprites[sprite_id];
482 // Don't apply content mask because it was already accounted for when
483 // rasterizing the path.
484 float4 device_position =
485 to_device_position(unit_vertex, sprite.bounds, viewport_size);
486 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
487
488 GradientColor gradient = prepare_gradient_color(
489 sprite.color.tag,
490 sprite.color.color_space,
491 sprite.color.solid,
492 sprite.color.colors[0].color,
493 sprite.color.colors[1].color
494 );
495
496 return PathSpriteVertexOutput{
497 device_position,
498 tile_position,
499 sprite_id,
500 gradient.solid,
501 gradient.color0,
502 gradient.color1
503 };
504}
505
506fragment float4 path_sprite_fragment(
507 PathSpriteVertexOutput input [[stage_in]],
508 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
509 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
510 constexpr sampler atlas_texture_sampler(mag_filter::linear,
511 min_filter::linear);
512 float4 sample =
513 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
514 float mask = 1. - abs(1. - fmod(sample.r, 2.));
515 PathSprite sprite = sprites[input.sprite_id];
516 Background background = sprite.color;
517 float4 color = gradient_color(background, input.position.xy, sprite.bounds,
518 input.solid_color, input.color0, input.color1);
519 color.a *= mask;
520 return color;
521}
522
523struct SurfaceVertexOutput {
524 float4 position [[position]];
525 float2 texture_position;
526 float clip_distance [[clip_distance]][4];
527};
528
529struct SurfaceFragmentInput {
530 float4 position [[position]];
531 float2 texture_position;
532};
533
534vertex SurfaceVertexOutput surface_vertex(
535 uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
536 constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
537 constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
538 constant Size_DevicePixels *viewport_size
539 [[buffer(SurfaceInputIndex_ViewportSize)]],
540 constant Size_DevicePixels *texture_size
541 [[buffer(SurfaceInputIndex_TextureSize)]]) {
542 float2 unit_vertex = unit_vertices[unit_vertex_id];
543 SurfaceBounds surface = surfaces[surface_id];
544 float4 device_position =
545 to_device_position(unit_vertex, surface.bounds, viewport_size);
546 float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
547 surface.content_mask.bounds);
548 // We are going to copy the whole texture, so the texture position corresponds
549 // to the current vertex of the unit triangle.
550 float2 texture_position = unit_vertex;
551 return SurfaceVertexOutput{
552 device_position,
553 texture_position,
554 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
555}
556
557fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
558 texture2d<float> y_texture
559 [[texture(SurfaceInputIndex_YTexture)]],
560 texture2d<float> cb_cr_texture
561 [[texture(SurfaceInputIndex_CbCrTexture)]]) {
562 constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
563 const float4x4 ycbcrToRGBTransform =
564 float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
565 float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
566 float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
567 float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
568 float4 ycbcr = float4(
569 y_texture.sample(texture_sampler, input.texture_position).r,
570 cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
571
572 return ycbcrToRGBTransform * ycbcr;
573}
574
575float4 hsla_to_rgba(Hsla hsla) {
576 float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
577 float s = hsla.s;
578 float l = hsla.l;
579 float a = hsla.a;
580
581 float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
582 float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
583 float m = l - c / 2.0;
584
585 float r = 0.0;
586 float g = 0.0;
587 float b = 0.0;
588
589 if (h >= 0.0 && h < 1.0) {
590 r = c;
591 g = x;
592 b = 0.0;
593 } else if (h >= 1.0 && h < 2.0) {
594 r = x;
595 g = c;
596 b = 0.0;
597 } else if (h >= 2.0 && h < 3.0) {
598 r = 0.0;
599 g = c;
600 b = x;
601 } else if (h >= 3.0 && h < 4.0) {
602 r = 0.0;
603 g = x;
604 b = c;
605 } else if (h >= 4.0 && h < 5.0) {
606 r = x;
607 g = 0.0;
608 b = c;
609 } else {
610 r = c;
611 g = 0.0;
612 b = x;
613 }
614
615 float4 rgba;
616 rgba.x = (r + m);
617 rgba.y = (g + m);
618 rgba.z = (b + m);
619 rgba.w = a;
620 return rgba;
621}
622
623float3 srgb_to_linear(float3 color) {
624 return pow(color, float3(2.2));
625}
626
627float3 linear_to_srgb(float3 color) {
628 return pow(color, float3(1.0 / 2.2));
629}
630
631// Converts a sRGB color to the Oklab color space.
632// Reference: https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
633float4 srgb_to_oklab(float4 color) {
634 // Convert non-linear sRGB to linear sRGB
635 color = float4(srgb_to_linear(color.rgb), color.a);
636
637 float l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b;
638 float m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b;
639 float s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b;
640
641 float l_ = pow(l, 1.0/3.0);
642 float m_ = pow(m, 1.0/3.0);
643 float s_ = pow(s, 1.0/3.0);
644
645 return float4(
646 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
647 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
648 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
649 color.a
650 );
651}
652
653// Converts an Oklab color to the sRGB color space.
654float4 oklab_to_srgb(float4 color) {
655 float l_ = color.r + 0.3963377774 * color.g + 0.2158037573 * color.b;
656 float m_ = color.r - 0.1055613458 * color.g - 0.0638541728 * color.b;
657 float s_ = color.r - 0.0894841775 * color.g - 1.2914855480 * color.b;
658
659 float l = l_ * l_ * l_;
660 float m = m_ * m_ * m_;
661 float s = s_ * s_ * s_;
662
663 float3 linear_rgb = float3(
664 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
665 -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
666 -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
667 );
668
669 // Convert linear sRGB to non-linear sRGB
670 return float4(linear_to_srgb(linear_rgb), color.a);
671}
672
673float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
674 constant Size_DevicePixels *input_viewport_size) {
675 float2 position =
676 unit_vertex * float2(bounds.size.width, bounds.size.height) +
677 float2(bounds.origin.x, bounds.origin.y);
678 float2 viewport_size = float2((float)input_viewport_size->width,
679 (float)input_viewport_size->height);
680 float2 device_position =
681 position / viewport_size * float2(2., -2.) + float2(-1., 1.);
682 return float4(device_position, 0., 1.);
683}
684
685float4 to_device_position_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
686 TransformationMatrix transformation,
687 constant Size_DevicePixels *input_viewport_size) {
688 float2 position =
689 unit_vertex * float2(bounds.size.width, bounds.size.height) +
690 float2(bounds.origin.x, bounds.origin.y);
691
692 // Apply the transformation matrix to the position via matrix multiplication.
693 float2 transformed_position = float2(0, 0);
694 transformed_position[0] = position[0] * transformation.rotation_scale[0][0] + position[1] * transformation.rotation_scale[0][1];
695 transformed_position[1] = position[0] * transformation.rotation_scale[1][0] + position[1] * transformation.rotation_scale[1][1];
696
697 // Add in the translation component of the transformation matrix.
698 transformed_position[0] += transformation.translation[0];
699 transformed_position[1] += transformation.translation[1];
700
701 float2 viewport_size = float2((float)input_viewport_size->width,
702 (float)input_viewport_size->height);
703 float2 device_position =
704 transformed_position / viewport_size * float2(2., -2.) + float2(-1., 1.);
705 return float4(device_position, 0., 1.);
706}
707
708
709float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
710 constant Size_DevicePixels *atlas_size) {
711 float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
712 float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
713 return (tile_origin + unit_vertex * tile_size) /
714 float2((float)atlas_size->width, (float)atlas_size->height);
715}
716
717float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
718 Corners_ScaledPixels corner_radii) {
719 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
720 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
721 float2 center_to_point = point - center;
722 float corner_radius;
723 if (center_to_point.x < 0.) {
724 if (center_to_point.y < 0.) {
725 corner_radius = corner_radii.top_left;
726 } else {
727 corner_radius = corner_radii.bottom_left;
728 }
729 } else {
730 if (center_to_point.y < 0.) {
731 corner_radius = corner_radii.top_right;
732 } else {
733 corner_radius = corner_radii.bottom_right;
734 }
735 }
736
737 float2 rounded_edge_to_point =
738 abs(center_to_point) - half_size + corner_radius;
739 float distance =
740 length(max(0., rounded_edge_to_point)) +
741 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
742 corner_radius;
743
744 return distance;
745}
746
747// A standard gaussian function, used for weighting samples
748float gaussian(float x, float sigma) {
749 return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
750}
751
752// This approximates the error function, needed for the gaussian integral
753float2 erf(float2 x) {
754 float2 s = sign(x);
755 float2 a = abs(x);
756 float2 r1 = 1. + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a;
757 float2 r2 = r1 * r1;
758 return s - s / (r2 * r2);
759}
760
761float blur_along_x(float x, float y, float sigma, float corner,
762 float2 half_size) {
763 float delta = min(half_size.y - corner - abs(y), 0.);
764 float curved =
765 half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
766 float2 integral =
767 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
768 return integral.y - integral.x;
769}
770
771float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
772 Bounds_ScaledPixels clip_bounds) {
773 float2 position =
774 unit_vertex * float2(bounds.size.width, bounds.size.height) +
775 float2(bounds.origin.x, bounds.origin.y);
776 return float4(position.x - clip_bounds.origin.x,
777 clip_bounds.origin.x + clip_bounds.size.width - position.x,
778 position.y - clip_bounds.origin.y,
779 clip_bounds.origin.y + clip_bounds.size.height - position.y);
780}
781
782float4 over(float4 below, float4 above) {
783 float4 result;
784 float alpha = above.a + below.a * (1.0 - above.a);
785 result.rgb =
786 (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
787 result.a = alpha;
788 return result;
789}
790
791GradientColor prepare_gradient_color(uint tag, uint color_space, Hsla solid,
792 Hsla color0, Hsla color1) {
793 GradientColor out;
794 if (tag == 0) {
795 out.solid = hsla_to_rgba(solid);
796 } else if (tag == 1) {
797 out.color0 = hsla_to_rgba(color0);
798 out.color1 = hsla_to_rgba(color1);
799
800 // Prepare color space in vertex for avoid conversion
801 // in fragment shader for performance reasons
802 if (color_space == 1) {
803 // Oklab
804 out.color0 = srgb_to_oklab(out.color0);
805 out.color1 = srgb_to_oklab(out.color1);
806 }
807 }
808
809 return out;
810}
811
812float4 gradient_color(Background background,
813 float2 position,
814 Bounds_ScaledPixels bounds,
815 float4 solid_color, float4 color0, float4 color1) {
816 float4 color;
817
818 switch (background.tag) {
819 case 0:
820 color = solid_color;
821 break;
822 case 1: {
823 // -90 degrees to match the CSS gradient angle.
824 float radians = (fmod(background.angle, 360.0) - 90.0) * (M_PI_F / 180.0);
825 float2 direction = float2(cos(radians), sin(radians));
826
827 // Expand the short side to be the same as the long side
828 if (bounds.size.width > bounds.size.height) {
829 direction.y *= bounds.size.height / bounds.size.width;
830 } else {
831 direction.x *= bounds.size.width / bounds.size.height;
832 }
833
834 // Get the t value for the linear gradient with the color stop percentages.
835 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
836 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
837 float2 center_to_point = position - center;
838 float t = dot(center_to_point, direction) / length(direction);
839 // Check the direct to determine the use x or y
840 if (abs(direction.x) > abs(direction.y)) {
841 t = (t + half_size.x) / bounds.size.width;
842 } else {
843 t = (t + half_size.y) / bounds.size.height;
844 }
845
846 // Adjust t based on the stop percentages
847 t = (t - background.colors[0].percentage)
848 / (background.colors[1].percentage
849 - background.colors[0].percentage);
850 t = clamp(t, 0.0, 1.0);
851
852 switch (background.color_space) {
853 case 0:
854 color = mix(color0, color1, t);
855 break;
856 case 1: {
857 float4 oklab_color = mix(color0, color1, t);
858 color = oklab_to_srgb(oklab_color);
859 break;
860 }
861 }
862 break;
863 }
864 }
865
866 return color;
867}