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 fill_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_fill_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_fill_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 = fill_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 float alpha;
231 if (shadow.blur_radius == 0.) {
232 float distance = quad_sdf(input.position.xy, shadow.bounds, shadow.corner_radii);
233 alpha = saturate(0.5 - distance);
234 } else {
235 // The signal is only non-zero in a limited range, so don't waste samples
236 float low = point.y - half_size.y;
237 float high = point.y + half_size.y;
238 float start = clamp(-3. * shadow.blur_radius, low, high);
239 float end = clamp(3. * shadow.blur_radius, low, high);
240
241 // Accumulate samples (we can get away with surprisingly few samples)
242 float step = (end - start) / 4.;
243 float y = start + step * 0.5;
244 alpha = 0.;
245 for (int i = 0; i < 4; i++) {
246 alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
247 corner_radius, half_size) *
248 gaussian(y, shadow.blur_radius) * step;
249 y += step;
250 }
251 }
252
253 return input.color * float4(1., 1., 1., alpha);
254}
255
256struct UnderlineVertexOutput {
257 float4 position [[position]];
258 float4 color [[flat]];
259 uint underline_id [[flat]];
260 float clip_distance [[clip_distance]][4];
261};
262
263struct UnderlineFragmentInput {
264 float4 position [[position]];
265 float4 color [[flat]];
266 uint underline_id [[flat]];
267};
268
269vertex UnderlineVertexOutput underline_vertex(
270 uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
271 constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
272 constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
273 constant Size_DevicePixels *viewport_size
274 [[buffer(ShadowInputIndex_ViewportSize)]]) {
275 float2 unit_vertex = unit_vertices[unit_vertex_id];
276 Underline underline = underlines[underline_id];
277 float4 device_position =
278 to_device_position(unit_vertex, underline.bounds, viewport_size);
279 float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
280 underline.content_mask.bounds);
281 float4 color = hsla_to_rgba(underline.color);
282 return UnderlineVertexOutput{
283 device_position,
284 color,
285 underline_id,
286 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
287}
288
289fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
290 constant Underline *underlines
291 [[buffer(UnderlineInputIndex_Underlines)]]) {
292 Underline underline = underlines[input.underline_id];
293 if (underline.wavy) {
294 float half_thickness = underline.thickness * 0.5;
295 float2 origin =
296 float2(underline.bounds.origin.x, underline.bounds.origin.y);
297 float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
298 float2(0., 0.5);
299 float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
300 float amplitude = 1. / (2. * underline.thickness);
301 float sine = sin(st.x * frequency) * amplitude;
302 float dSine = cos(st.x * frequency) * amplitude * frequency;
303 float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
304 float distance_in_pixels = distance * underline.bounds.size.height;
305 float distance_from_top_border = distance_in_pixels - half_thickness;
306 float distance_from_bottom_border = distance_in_pixels + half_thickness;
307 float alpha = saturate(
308 0.5 - max(-distance_from_bottom_border, distance_from_top_border));
309 return input.color * float4(1., 1., 1., alpha);
310 } else {
311 return input.color;
312 }
313}
314
315struct MonochromeSpriteVertexOutput {
316 float4 position [[position]];
317 float2 tile_position;
318 float4 color [[flat]];
319 float clip_distance [[clip_distance]][4];
320};
321
322struct MonochromeSpriteFragmentInput {
323 float4 position [[position]];
324 float2 tile_position;
325 float4 color [[flat]];
326};
327
328vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
329 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
330 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
331 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
332 constant Size_DevicePixels *viewport_size
333 [[buffer(SpriteInputIndex_ViewportSize)]],
334 constant Size_DevicePixels *atlas_size
335 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
336 float2 unit_vertex = unit_vertices[unit_vertex_id];
337 MonochromeSprite sprite = sprites[sprite_id];
338 float4 device_position =
339 to_device_position_transformed(unit_vertex, sprite.bounds, sprite.transformation, viewport_size);
340 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
341 sprite.content_mask.bounds);
342 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
343 float4 color = hsla_to_rgba(sprite.color);
344 return MonochromeSpriteVertexOutput{
345 device_position,
346 tile_position,
347 color,
348 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
349}
350
351fragment float4 monochrome_sprite_fragment(
352 MonochromeSpriteFragmentInput input [[stage_in]],
353 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
354 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
355 constexpr sampler atlas_texture_sampler(mag_filter::linear,
356 min_filter::linear);
357 float4 sample =
358 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
359 float4 color = input.color;
360 color.a *= sample.a;
361 return color;
362}
363
364struct PolychromeSpriteVertexOutput {
365 float4 position [[position]];
366 float2 tile_position;
367 uint sprite_id [[flat]];
368 float clip_distance [[clip_distance]][4];
369};
370
371struct PolychromeSpriteFragmentInput {
372 float4 position [[position]];
373 float2 tile_position;
374 uint sprite_id [[flat]];
375};
376
377vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
378 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
379 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
380 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
381 constant Size_DevicePixels *viewport_size
382 [[buffer(SpriteInputIndex_ViewportSize)]],
383 constant Size_DevicePixels *atlas_size
384 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
385
386 float2 unit_vertex = unit_vertices[unit_vertex_id];
387 PolychromeSprite sprite = sprites[sprite_id];
388 float4 device_position =
389 to_device_position(unit_vertex, sprite.bounds, viewport_size);
390 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
391 sprite.content_mask.bounds);
392 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
393 return PolychromeSpriteVertexOutput{
394 device_position,
395 tile_position,
396 sprite_id,
397 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
398}
399
400fragment float4 polychrome_sprite_fragment(
401 PolychromeSpriteFragmentInput input [[stage_in]],
402 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
403 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
404 PolychromeSprite sprite = sprites[input.sprite_id];
405 constexpr sampler atlas_texture_sampler(mag_filter::linear,
406 min_filter::linear);
407 float4 sample =
408 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
409 float distance =
410 quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
411
412 float4 color = sample;
413 if (sprite.grayscale) {
414 float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
415 color.r = grayscale;
416 color.g = grayscale;
417 color.b = grayscale;
418 }
419 color.a *= sprite.opacity * saturate(0.5 - distance);
420 return color;
421}
422
423struct PathRasterizationVertexOutput {
424 float4 position [[position]];
425 float2 st_position;
426 float clip_rect_distance [[clip_distance]][4];
427};
428
429struct PathRasterizationFragmentInput {
430 float4 position [[position]];
431 float2 st_position;
432};
433
434vertex PathRasterizationVertexOutput path_rasterization_vertex(
435 uint vertex_id [[vertex_id]],
436 constant PathVertex_ScaledPixels *vertices
437 [[buffer(PathRasterizationInputIndex_Vertices)]],
438 constant Size_DevicePixels *atlas_size
439 [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
440 PathVertex_ScaledPixels v = vertices[vertex_id];
441 float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
442 float2 viewport_size = float2(atlas_size->width, atlas_size->height);
443 return PathRasterizationVertexOutput{
444 float4(vertex_position / viewport_size * float2(2., -2.) +
445 float2(-1., 1.),
446 0., 1.),
447 float2(v.st_position.x, v.st_position.y),
448 {v.xy_position.x - v.content_mask.bounds.origin.x,
449 v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
450 v.xy_position.x,
451 v.xy_position.y - v.content_mask.bounds.origin.y,
452 v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
453 v.xy_position.y}};
454}
455
456fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
457 [[stage_in]]) {
458 float2 dx = dfdx(input.st_position);
459 float2 dy = dfdy(input.st_position);
460 float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
461 (2. * input.st_position.x) * dy.x - dy.y);
462 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
463 float distance = f / length(gradient);
464 float alpha = saturate(0.5 - distance);
465 return float4(alpha, 0., 0., 1.);
466}
467
468struct PathSpriteVertexOutput {
469 float4 position [[position]];
470 float2 tile_position;
471 uint sprite_id [[flat]];
472 float4 solid_color [[flat]];
473 float4 color0 [[flat]];
474 float4 color1 [[flat]];
475};
476
477vertex PathSpriteVertexOutput path_sprite_vertex(
478 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
479 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
480 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
481 constant Size_DevicePixels *viewport_size
482 [[buffer(SpriteInputIndex_ViewportSize)]],
483 constant Size_DevicePixels *atlas_size
484 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
485
486 float2 unit_vertex = unit_vertices[unit_vertex_id];
487 PathSprite sprite = sprites[sprite_id];
488 // Don't apply content mask because it was already accounted for when
489 // rasterizing the path.
490 float4 device_position =
491 to_device_position(unit_vertex, sprite.bounds, viewport_size);
492 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
493
494 GradientColor gradient = prepare_fill_color(
495 sprite.color.tag,
496 sprite.color.color_space,
497 sprite.color.solid,
498 sprite.color.colors[0].color,
499 sprite.color.colors[1].color
500 );
501
502 return PathSpriteVertexOutput{
503 device_position,
504 tile_position,
505 sprite_id,
506 gradient.solid,
507 gradient.color0,
508 gradient.color1
509 };
510}
511
512fragment float4 path_sprite_fragment(
513 PathSpriteVertexOutput input [[stage_in]],
514 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
515 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
516 constexpr sampler atlas_texture_sampler(mag_filter::linear,
517 min_filter::linear);
518 float4 sample =
519 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
520 float mask = 1. - abs(1. - fmod(sample.r, 2.));
521 PathSprite sprite = sprites[input.sprite_id];
522 Background background = sprite.color;
523 float4 color = fill_color(background, input.position.xy, sprite.bounds,
524 input.solid_color, input.color0, input.color1);
525 color.a *= mask;
526 return color;
527}
528
529struct SurfaceVertexOutput {
530 float4 position [[position]];
531 float2 texture_position;
532 float clip_distance [[clip_distance]][4];
533};
534
535struct SurfaceFragmentInput {
536 float4 position [[position]];
537 float2 texture_position;
538};
539
540vertex SurfaceVertexOutput surface_vertex(
541 uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
542 constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
543 constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
544 constant Size_DevicePixels *viewport_size
545 [[buffer(SurfaceInputIndex_ViewportSize)]],
546 constant Size_DevicePixels *texture_size
547 [[buffer(SurfaceInputIndex_TextureSize)]]) {
548 float2 unit_vertex = unit_vertices[unit_vertex_id];
549 SurfaceBounds surface = surfaces[surface_id];
550 float4 device_position =
551 to_device_position(unit_vertex, surface.bounds, viewport_size);
552 float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
553 surface.content_mask.bounds);
554 // We are going to copy the whole texture, so the texture position corresponds
555 // to the current vertex of the unit triangle.
556 float2 texture_position = unit_vertex;
557 return SurfaceVertexOutput{
558 device_position,
559 texture_position,
560 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
561}
562
563fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
564 texture2d<float> y_texture
565 [[texture(SurfaceInputIndex_YTexture)]],
566 texture2d<float> cb_cr_texture
567 [[texture(SurfaceInputIndex_CbCrTexture)]]) {
568 constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
569 const float4x4 ycbcrToRGBTransform =
570 float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
571 float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
572 float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
573 float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
574 float4 ycbcr = float4(
575 y_texture.sample(texture_sampler, input.texture_position).r,
576 cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
577
578 return ycbcrToRGBTransform * ycbcr;
579}
580
581float4 hsla_to_rgba(Hsla hsla) {
582 float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
583 float s = hsla.s;
584 float l = hsla.l;
585 float a = hsla.a;
586
587 float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
588 float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
589 float m = l - c / 2.0;
590
591 float r = 0.0;
592 float g = 0.0;
593 float b = 0.0;
594
595 if (h >= 0.0 && h < 1.0) {
596 r = c;
597 g = x;
598 b = 0.0;
599 } else if (h >= 1.0 && h < 2.0) {
600 r = x;
601 g = c;
602 b = 0.0;
603 } else if (h >= 2.0 && h < 3.0) {
604 r = 0.0;
605 g = c;
606 b = x;
607 } else if (h >= 3.0 && h < 4.0) {
608 r = 0.0;
609 g = x;
610 b = c;
611 } else if (h >= 4.0 && h < 5.0) {
612 r = x;
613 g = 0.0;
614 b = c;
615 } else {
616 r = c;
617 g = 0.0;
618 b = x;
619 }
620
621 float4 rgba;
622 rgba.x = (r + m);
623 rgba.y = (g + m);
624 rgba.z = (b + m);
625 rgba.w = a;
626 return rgba;
627}
628
629float3 srgb_to_linear(float3 color) {
630 return pow(color, float3(2.2));
631}
632
633float3 linear_to_srgb(float3 color) {
634 return pow(color, float3(1.0 / 2.2));
635}
636
637// Converts a sRGB color to the Oklab color space.
638// Reference: https://bottosson.github.io/posts/oklab/#converting-from-linear-srgb-to-oklab
639float4 srgb_to_oklab(float4 color) {
640 // Convert non-linear sRGB to linear sRGB
641 color = float4(srgb_to_linear(color.rgb), color.a);
642
643 float l = 0.4122214708 * color.r + 0.5363325363 * color.g + 0.0514459929 * color.b;
644 float m = 0.2119034982 * color.r + 0.6806995451 * color.g + 0.1073969566 * color.b;
645 float s = 0.0883024619 * color.r + 0.2817188376 * color.g + 0.6299787005 * color.b;
646
647 float l_ = pow(l, 1.0/3.0);
648 float m_ = pow(m, 1.0/3.0);
649 float s_ = pow(s, 1.0/3.0);
650
651 return float4(
652 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
653 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
654 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
655 color.a
656 );
657}
658
659// Converts an Oklab color to the sRGB color space.
660float4 oklab_to_srgb(float4 color) {
661 float l_ = color.r + 0.3963377774 * color.g + 0.2158037573 * color.b;
662 float m_ = color.r - 0.1055613458 * color.g - 0.0638541728 * color.b;
663 float s_ = color.r - 0.0894841775 * color.g - 1.2914855480 * color.b;
664
665 float l = l_ * l_ * l_;
666 float m = m_ * m_ * m_;
667 float s = s_ * s_ * s_;
668
669 float3 linear_rgb = float3(
670 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
671 -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
672 -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s
673 );
674
675 // Convert linear sRGB to non-linear sRGB
676 return float4(linear_to_srgb(linear_rgb), color.a);
677}
678
679float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
680 constant Size_DevicePixels *input_viewport_size) {
681 float2 position =
682 unit_vertex * float2(bounds.size.width, bounds.size.height) +
683 float2(bounds.origin.x, bounds.origin.y);
684 float2 viewport_size = float2((float)input_viewport_size->width,
685 (float)input_viewport_size->height);
686 float2 device_position =
687 position / viewport_size * float2(2., -2.) + float2(-1., 1.);
688 return float4(device_position, 0., 1.);
689}
690
691float4 to_device_position_transformed(float2 unit_vertex, Bounds_ScaledPixels bounds,
692 TransformationMatrix transformation,
693 constant Size_DevicePixels *input_viewport_size) {
694 float2 position =
695 unit_vertex * float2(bounds.size.width, bounds.size.height) +
696 float2(bounds.origin.x, bounds.origin.y);
697
698 // Apply the transformation matrix to the position via matrix multiplication.
699 float2 transformed_position = float2(0, 0);
700 transformed_position[0] = position[0] * transformation.rotation_scale[0][0] + position[1] * transformation.rotation_scale[0][1];
701 transformed_position[1] = position[0] * transformation.rotation_scale[1][0] + position[1] * transformation.rotation_scale[1][1];
702
703 // Add in the translation component of the transformation matrix.
704 transformed_position[0] += transformation.translation[0];
705 transformed_position[1] += transformation.translation[1];
706
707 float2 viewport_size = float2((float)input_viewport_size->width,
708 (float)input_viewport_size->height);
709 float2 device_position =
710 transformed_position / viewport_size * float2(2., -2.) + float2(-1., 1.);
711 return float4(device_position, 0., 1.);
712}
713
714
715float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
716 constant Size_DevicePixels *atlas_size) {
717 float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
718 float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
719 return (tile_origin + unit_vertex * tile_size) /
720 float2((float)atlas_size->width, (float)atlas_size->height);
721}
722
723float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
724 Corners_ScaledPixels corner_radii) {
725 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
726 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
727 float2 center_to_point = point - center;
728 float corner_radius;
729 if (center_to_point.x < 0.) {
730 if (center_to_point.y < 0.) {
731 corner_radius = corner_radii.top_left;
732 } else {
733 corner_radius = corner_radii.bottom_left;
734 }
735 } else {
736 if (center_to_point.y < 0.) {
737 corner_radius = corner_radii.top_right;
738 } else {
739 corner_radius = corner_radii.bottom_right;
740 }
741 }
742
743 float2 rounded_edge_to_point =
744 abs(center_to_point) - half_size + corner_radius;
745 float distance =
746 length(max(0., rounded_edge_to_point)) +
747 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
748 corner_radius;
749
750 return distance;
751}
752
753// A standard gaussian function, used for weighting samples
754float gaussian(float x, float sigma) {
755 return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
756}
757
758// This approximates the error function, needed for the gaussian integral
759float2 erf(float2 x) {
760 float2 s = sign(x);
761 float2 a = abs(x);
762 float2 r1 = 1. + (0.278393 + (0.230389 + (0.000972 + 0.078108 * a) * a) * a) * a;
763 float2 r2 = r1 * r1;
764 return s - s / (r2 * r2);
765}
766
767float blur_along_x(float x, float y, float sigma, float corner,
768 float2 half_size) {
769 float delta = min(half_size.y - corner - abs(y), 0.);
770 float curved =
771 half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
772 float2 integral =
773 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
774 return integral.y - integral.x;
775}
776
777float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
778 Bounds_ScaledPixels clip_bounds) {
779 float2 position =
780 unit_vertex * float2(bounds.size.width, bounds.size.height) +
781 float2(bounds.origin.x, bounds.origin.y);
782 return float4(position.x - clip_bounds.origin.x,
783 clip_bounds.origin.x + clip_bounds.size.width - position.x,
784 position.y - clip_bounds.origin.y,
785 clip_bounds.origin.y + clip_bounds.size.height - position.y);
786}
787
788float4 over(float4 below, float4 above) {
789 float4 result;
790 float alpha = above.a + below.a * (1.0 - above.a);
791 result.rgb =
792 (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
793 result.a = alpha;
794 return result;
795}
796
797GradientColor prepare_fill_color(uint tag, uint color_space, Hsla solid,
798 Hsla color0, Hsla color1) {
799 GradientColor out;
800 if (tag == 0 || tag == 2) {
801 out.solid = hsla_to_rgba(solid);
802 } else if (tag == 1) {
803 out.color0 = hsla_to_rgba(color0);
804 out.color1 = hsla_to_rgba(color1);
805
806 // Prepare color space in vertex for avoid conversion
807 // in fragment shader for performance reasons
808 if (color_space == 1) {
809 // Oklab
810 out.color0 = srgb_to_oklab(out.color0);
811 out.color1 = srgb_to_oklab(out.color1);
812 }
813 }
814
815 return out;
816}
817
818float2x2 rotate2d(float angle) {
819 float s = sin(angle);
820 float c = cos(angle);
821 return float2x2(c, -s, s, c);
822}
823
824float4 fill_color(Background background,
825 float2 position,
826 Bounds_ScaledPixels bounds,
827 float4 solid_color, float4 color0, float4 color1) {
828 float4 color;
829
830 switch (background.tag) {
831 case 0:
832 color = solid_color;
833 break;
834 case 1: {
835 // -90 degrees to match the CSS gradient angle.
836 float gradient_angle = background.gradient_angle_or_pattern_height;
837 float radians = (fmod(gradient_angle, 360.0) - 90.0) * (M_PI_F / 180.0);
838 float2 direction = float2(cos(radians), sin(radians));
839
840 // Expand the short side to be the same as the long side
841 if (bounds.size.width > bounds.size.height) {
842 direction.y *= bounds.size.height / bounds.size.width;
843 } else {
844 direction.x *= bounds.size.width / bounds.size.height;
845 }
846
847 // Get the t value for the linear gradient with the color stop percentages.
848 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
849 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
850 float2 center_to_point = position - center;
851 float t = dot(center_to_point, direction) / length(direction);
852 // Check the direction to determine whether to use x or y
853 if (abs(direction.x) > abs(direction.y)) {
854 t = (t + half_size.x) / bounds.size.width;
855 } else {
856 t = (t + half_size.y) / bounds.size.height;
857 }
858
859 // Adjust t based on the stop percentages
860 t = (t - background.colors[0].percentage)
861 / (background.colors[1].percentage
862 - background.colors[0].percentage);
863 t = clamp(t, 0.0, 1.0);
864
865 switch (background.color_space) {
866 case 0:
867 color = mix(color0, color1, t);
868 break;
869 case 1: {
870 float4 oklab_color = mix(color0, color1, t);
871 color = oklab_to_srgb(oklab_color);
872 break;
873 }
874 }
875 break;
876 }
877 case 2: {
878 float gradient_angle_or_pattern_height = background.gradient_angle_or_pattern_height;
879 float pattern_width = (gradient_angle_or_pattern_height / 65535.0f) / 255.0f;
880 float pattern_interval = fmod(gradient_angle_or_pattern_height, 65535.0f) / 255.0f;
881 float pattern_height = pattern_width + pattern_interval;
882 float stripe_angle = M_PI_F / 4.0;
883 float pattern_period = pattern_height * sin(stripe_angle);
884 float2x2 rotation = rotate2d(stripe_angle);
885 float2 relative_position = position - float2(bounds.origin.x, bounds.origin.y);
886 float2 rotated_point = rotation * relative_position;
887 float pattern = fmod(rotated_point.x, pattern_period);
888 float distance = min(pattern, pattern_period - pattern) - pattern_period * (pattern_width / pattern_height) / 2.0f;
889 color = solid_color;
890 color.a *= saturate(0.5 - distance);
891 break;
892 }
893 }
894
895 return color;
896}