1#include <metal_stdlib>
2#include <simd/simd.h>
3
4using namespace metal;
5
6float4 hsla_to_rgba(Hsla hsla);
7float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
8 constant Size_DevicePixels *viewport_size);
9float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
10 constant Size_DevicePixels *atlas_size);
11float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
12 Bounds_ScaledPixels clip_bounds);
13float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
14 Corners_ScaledPixels corner_radii);
15float gaussian(float x, float sigma);
16float2 erf(float2 x);
17float blur_along_x(float x, float y, float sigma, float corner,
18 float2 half_size);
19float4 over(float4 below, float4 above);
20
21struct QuadVertexOutput {
22 float4 position [[position]];
23 float4 background_color [[flat]];
24 float4 border_color [[flat]];
25 uint quad_id [[flat]];
26 float clip_distance [[clip_distance]][4];
27};
28
29struct QuadFragmentInput {
30 float4 position [[position]];
31 float4 background_color [[flat]];
32 float4 border_color [[flat]];
33 uint quad_id [[flat]];
34};
35
36vertex QuadVertexOutput quad_vertex(uint unit_vertex_id [[vertex_id]],
37 uint quad_id [[instance_id]],
38 constant float2 *unit_vertices
39 [[buffer(QuadInputIndex_Vertices)]],
40 constant Quad *quads
41 [[buffer(QuadInputIndex_Quads)]],
42 constant Size_DevicePixels *viewport_size
43 [[buffer(QuadInputIndex_ViewportSize)]]) {
44 float2 unit_vertex = unit_vertices[unit_vertex_id];
45 Quad quad = quads[quad_id];
46 float4 device_position =
47 to_device_position(unit_vertex, quad.bounds, viewport_size);
48 float4 clip_distance = distance_from_clip_rect(unit_vertex, quad.bounds,
49 quad.content_mask.bounds);
50 float4 background_color = hsla_to_rgba(quad.background);
51 float4 border_color = hsla_to_rgba(quad.border_color);
52 return QuadVertexOutput{
53 device_position,
54 background_color,
55 border_color,
56 quad_id,
57 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
58}
59
60fragment float4 quad_fragment(QuadFragmentInput input [[stage_in]],
61 constant Quad *quads
62 [[buffer(QuadInputIndex_Quads)]]) {
63 Quad quad = quads[input.quad_id];
64
65 // Fast path when the quad is not rounded and doesn't have any border.
66 if (quad.corner_radii.top_left == 0. && quad.corner_radii.bottom_left == 0. &&
67 quad.corner_radii.top_right == 0. &&
68 quad.corner_radii.bottom_right == 0. && quad.border_widths.top == 0. &&
69 quad.border_widths.left == 0. && quad.border_widths.right == 0. &&
70 quad.border_widths.bottom == 0.) {
71 return input.background_color;
72 }
73
74 float2 half_size =
75 float2(quad.bounds.size.width, quad.bounds.size.height) / 2.;
76 float2 center =
77 float2(quad.bounds.origin.x, quad.bounds.origin.y) + half_size;
78 float2 center_to_point = input.position.xy - center;
79 float corner_radius;
80 if (center_to_point.x < 0.) {
81 if (center_to_point.y < 0.) {
82 corner_radius = quad.corner_radii.top_left;
83 } else {
84 corner_radius = quad.corner_radii.bottom_left;
85 }
86 } else {
87 if (center_to_point.y < 0.) {
88 corner_radius = quad.corner_radii.top_right;
89 } else {
90 corner_radius = quad.corner_radii.bottom_right;
91 }
92 }
93
94 float2 rounded_edge_to_point =
95 fabs(center_to_point) - half_size + corner_radius;
96 float distance =
97 length(max(0., rounded_edge_to_point)) +
98 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
99 corner_radius;
100
101 float vertical_border = center_to_point.x <= 0. ? quad.border_widths.left
102 : quad.border_widths.right;
103 float horizontal_border = center_to_point.y <= 0. ? quad.border_widths.top
104 : quad.border_widths.bottom;
105 float2 inset_size =
106 half_size - corner_radius - float2(vertical_border, horizontal_border);
107 float2 point_to_inset_corner = fabs(center_to_point) - inset_size;
108 float border_width;
109 if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
110 border_width = 0.;
111 } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
112 border_width = horizontal_border;
113 } else {
114 border_width = vertical_border;
115 }
116
117 float4 color;
118 if (border_width == 0.) {
119 color = input.background_color;
120 } else {
121 float inset_distance = distance + border_width;
122 // Blend the border on top of the background and then linearly interpolate
123 // between the two as we slide inside the background.
124 float4 blended_border = over(input.background_color, input.border_color);
125 color = mix(blended_border, input.background_color,
126 saturate(0.5 - inset_distance));
127 }
128
129 return color * float4(1., 1., 1., saturate(0.5 - distance));
130}
131
132struct ShadowVertexOutput {
133 float4 position [[position]];
134 float4 color [[flat]];
135 uint shadow_id [[flat]];
136 float clip_distance [[clip_distance]][4];
137};
138
139struct ShadowFragmentInput {
140 float4 position [[position]];
141 float4 color [[flat]];
142 uint shadow_id [[flat]];
143};
144
145vertex ShadowVertexOutput shadow_vertex(
146 uint unit_vertex_id [[vertex_id]], uint shadow_id [[instance_id]],
147 constant float2 *unit_vertices [[buffer(ShadowInputIndex_Vertices)]],
148 constant Shadow *shadows [[buffer(ShadowInputIndex_Shadows)]],
149 constant Size_DevicePixels *viewport_size
150 [[buffer(ShadowInputIndex_ViewportSize)]]) {
151 float2 unit_vertex = unit_vertices[unit_vertex_id];
152 Shadow shadow = shadows[shadow_id];
153
154 float margin = 3. * shadow.blur_radius;
155 // Set the bounds of the shadow and adjust its size based on the shadow's
156 // spread radius to achieve the spreading effect
157 Bounds_ScaledPixels bounds = shadow.bounds;
158 bounds.origin.x -= margin;
159 bounds.origin.y -= margin;
160 bounds.size.width += 2. * margin;
161 bounds.size.height += 2. * margin;
162
163 float4 device_position =
164 to_device_position(unit_vertex, bounds, viewport_size);
165 float4 clip_distance =
166 distance_from_clip_rect(unit_vertex, bounds, shadow.content_mask.bounds);
167 float4 color = hsla_to_rgba(shadow.color);
168
169 return ShadowVertexOutput{
170 device_position,
171 color,
172 shadow_id,
173 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
174}
175
176fragment float4 shadow_fragment(ShadowFragmentInput input [[stage_in]],
177 constant Shadow *shadows
178 [[buffer(ShadowInputIndex_Shadows)]]) {
179 Shadow shadow = shadows[input.shadow_id];
180
181 float2 origin = float2(shadow.bounds.origin.x, shadow.bounds.origin.y);
182 float2 size = float2(shadow.bounds.size.width, shadow.bounds.size.height);
183 float2 half_size = size / 2.;
184 float2 center = origin + half_size;
185 float2 point = input.position.xy - center;
186 float corner_radius;
187 if (point.x < 0.) {
188 if (point.y < 0.) {
189 corner_radius = shadow.corner_radii.top_left;
190 } else {
191 corner_radius = shadow.corner_radii.bottom_left;
192 }
193 } else {
194 if (point.y < 0.) {
195 corner_radius = shadow.corner_radii.top_right;
196 } else {
197 corner_radius = shadow.corner_radii.bottom_right;
198 }
199 }
200
201 // The signal is only non-zero in a limited range, so don't waste samples
202 float low = point.y - half_size.y;
203 float high = point.y + half_size.y;
204 float start = clamp(-3. * shadow.blur_radius, low, high);
205 float end = clamp(3. * shadow.blur_radius, low, high);
206
207 // Accumulate samples (we can get away with surprisingly few samples)
208 float step = (end - start) / 4.;
209 float y = start + step * 0.5;
210 float alpha = 0.;
211 for (int i = 0; i < 4; i++) {
212 alpha += blur_along_x(point.x, point.y - y, shadow.blur_radius,
213 corner_radius, half_size) *
214 gaussian(y, shadow.blur_radius) * step;
215 y += step;
216 }
217
218 return input.color * float4(1., 1., 1., alpha);
219}
220
221struct UnderlineVertexOutput {
222 float4 position [[position]];
223 float4 color [[flat]];
224 uint underline_id [[flat]];
225 float clip_distance [[clip_distance]][4];
226};
227
228struct UnderlineFragmentInput {
229 float4 position [[position]];
230 float4 color [[flat]];
231 uint underline_id [[flat]];
232};
233
234vertex UnderlineVertexOutput underline_vertex(
235 uint unit_vertex_id [[vertex_id]], uint underline_id [[instance_id]],
236 constant float2 *unit_vertices [[buffer(UnderlineInputIndex_Vertices)]],
237 constant Underline *underlines [[buffer(UnderlineInputIndex_Underlines)]],
238 constant Size_DevicePixels *viewport_size
239 [[buffer(ShadowInputIndex_ViewportSize)]]) {
240 float2 unit_vertex = unit_vertices[unit_vertex_id];
241 Underline underline = underlines[underline_id];
242 float4 device_position =
243 to_device_position(unit_vertex, underline.bounds, viewport_size);
244 float4 clip_distance = distance_from_clip_rect(unit_vertex, underline.bounds,
245 underline.content_mask.bounds);
246 float4 color = hsla_to_rgba(underline.color);
247 return UnderlineVertexOutput{
248 device_position,
249 color,
250 underline_id,
251 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
252}
253
254fragment float4 underline_fragment(UnderlineFragmentInput input [[stage_in]],
255 constant Underline *underlines
256 [[buffer(UnderlineInputIndex_Underlines)]]) {
257 Underline underline = underlines[input.underline_id];
258 if (underline.wavy) {
259 float half_thickness = underline.thickness * 0.5;
260 float2 origin =
261 float2(underline.bounds.origin.x, underline.bounds.origin.y);
262 float2 st = ((input.position.xy - origin) / underline.bounds.size.height) -
263 float2(0., 0.5);
264 float frequency = (M_PI_F * (3. * underline.thickness)) / 8.;
265 float amplitude = 1. / (2. * underline.thickness);
266 float sine = sin(st.x * frequency) * amplitude;
267 float dSine = cos(st.x * frequency) * amplitude * frequency;
268 float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
269 float distance_in_pixels = distance * underline.bounds.size.height;
270 float distance_from_top_border = distance_in_pixels - half_thickness;
271 float distance_from_bottom_border = distance_in_pixels + half_thickness;
272 float alpha = saturate(
273 0.5 - max(-distance_from_bottom_border, distance_from_top_border));
274 return input.color * float4(1., 1., 1., alpha);
275 } else {
276 return input.color;
277 }
278}
279
280struct MonochromeSpriteVertexOutput {
281 float4 position [[position]];
282 float2 tile_position;
283 float4 color [[flat]];
284 float clip_distance [[clip_distance]][4];
285};
286
287struct MonochromeSpriteFragmentInput {
288 float4 position [[position]];
289 float2 tile_position;
290 float4 color [[flat]];
291};
292
293vertex MonochromeSpriteVertexOutput monochrome_sprite_vertex(
294 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
295 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
296 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
297 constant Size_DevicePixels *viewport_size
298 [[buffer(SpriteInputIndex_ViewportSize)]],
299 constant Size_DevicePixels *atlas_size
300 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
301 float2 unit_vertex = unit_vertices[unit_vertex_id];
302 MonochromeSprite sprite = sprites[sprite_id];
303 float4 device_position =
304 to_device_position(unit_vertex, sprite.bounds, viewport_size);
305 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
306 sprite.content_mask.bounds);
307 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
308 float4 color = hsla_to_rgba(sprite.color);
309 return MonochromeSpriteVertexOutput{
310 device_position,
311 tile_position,
312 color,
313 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
314}
315
316fragment float4 monochrome_sprite_fragment(
317 MonochromeSpriteFragmentInput input [[stage_in]],
318 constant MonochromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
319 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
320 constexpr sampler atlas_texture_sampler(mag_filter::linear,
321 min_filter::linear);
322 float4 sample =
323 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
324 float4 color = input.color;
325 color.a *= sample.a;
326 return color;
327}
328
329struct PolychromeSpriteVertexOutput {
330 float4 position [[position]];
331 float2 tile_position;
332 uint sprite_id [[flat]];
333 float clip_distance [[clip_distance]][4];
334};
335
336struct PolychromeSpriteFragmentInput {
337 float4 position [[position]];
338 float2 tile_position;
339 uint sprite_id [[flat]];
340};
341
342vertex PolychromeSpriteVertexOutput polychrome_sprite_vertex(
343 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
344 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
345 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
346 constant Size_DevicePixels *viewport_size
347 [[buffer(SpriteInputIndex_ViewportSize)]],
348 constant Size_DevicePixels *atlas_size
349 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
350
351 float2 unit_vertex = unit_vertices[unit_vertex_id];
352 PolychromeSprite sprite = sprites[sprite_id];
353 float4 device_position =
354 to_device_position(unit_vertex, sprite.bounds, viewport_size);
355 float4 clip_distance = distance_from_clip_rect(unit_vertex, sprite.bounds,
356 sprite.content_mask.bounds);
357 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
358 return PolychromeSpriteVertexOutput{
359 device_position,
360 tile_position,
361 sprite_id,
362 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
363}
364
365fragment float4 polychrome_sprite_fragment(
366 PolychromeSpriteFragmentInput input [[stage_in]],
367 constant PolychromeSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
368 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
369 PolychromeSprite sprite = sprites[input.sprite_id];
370 constexpr sampler atlas_texture_sampler(mag_filter::linear,
371 min_filter::linear);
372 float4 sample =
373 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
374 float distance =
375 quad_sdf(input.position.xy, sprite.bounds, sprite.corner_radii);
376
377 float4 color = sample;
378 if (sprite.grayscale) {
379 float grayscale = 0.2126 * color.r + 0.7152 * color.g + 0.0722 * color.b;
380 color.r = grayscale;
381 color.g = grayscale;
382 color.b = grayscale;
383 }
384 color.a *= saturate(0.5 - distance);
385 return color;
386}
387
388struct PathRasterizationVertexOutput {
389 float4 position [[position]];
390 float2 st_position;
391 float clip_rect_distance [[clip_distance]][4];
392};
393
394struct PathRasterizationFragmentInput {
395 float4 position [[position]];
396 float2 st_position;
397};
398
399vertex PathRasterizationVertexOutput path_rasterization_vertex(
400 uint vertex_id [[vertex_id]],
401 constant PathVertex_ScaledPixels *vertices
402 [[buffer(PathRasterizationInputIndex_Vertices)]],
403 constant Size_DevicePixels *atlas_size
404 [[buffer(PathRasterizationInputIndex_AtlasTextureSize)]]) {
405 PathVertex_ScaledPixels v = vertices[vertex_id];
406 float2 vertex_position = float2(v.xy_position.x, v.xy_position.y);
407 float2 viewport_size = float2(atlas_size->width, atlas_size->height);
408 return PathRasterizationVertexOutput{
409 float4(vertex_position / viewport_size * float2(2., -2.) +
410 float2(-1., 1.),
411 0., 1.),
412 float2(v.st_position.x, v.st_position.y),
413 {v.xy_position.x - v.content_mask.bounds.origin.x,
414 v.content_mask.bounds.origin.x + v.content_mask.bounds.size.width -
415 v.xy_position.x,
416 v.xy_position.y - v.content_mask.bounds.origin.y,
417 v.content_mask.bounds.origin.y + v.content_mask.bounds.size.height -
418 v.xy_position.y}};
419}
420
421fragment float4 path_rasterization_fragment(PathRasterizationFragmentInput input
422 [[stage_in]]) {
423 float2 dx = dfdx(input.st_position);
424 float2 dy = dfdy(input.st_position);
425 float2 gradient = float2((2. * input.st_position.x) * dx.x - dx.y,
426 (2. * input.st_position.x) * dy.x - dy.y);
427 float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
428 float distance = f / length(gradient);
429 float alpha = saturate(0.5 - distance);
430 return float4(alpha, 0., 0., 1.);
431}
432
433struct PathSpriteVertexOutput {
434 float4 position [[position]];
435 float2 tile_position;
436 float4 color [[flat]];
437};
438
439vertex PathSpriteVertexOutput path_sprite_vertex(
440 uint unit_vertex_id [[vertex_id]], uint sprite_id [[instance_id]],
441 constant float2 *unit_vertices [[buffer(SpriteInputIndex_Vertices)]],
442 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
443 constant Size_DevicePixels *viewport_size
444 [[buffer(SpriteInputIndex_ViewportSize)]],
445 constant Size_DevicePixels *atlas_size
446 [[buffer(SpriteInputIndex_AtlasTextureSize)]]) {
447
448 float2 unit_vertex = unit_vertices[unit_vertex_id];
449 PathSprite sprite = sprites[sprite_id];
450 // Don't apply content mask because it was already accounted for when
451 // rasterizing the path.
452 float4 device_position =
453 to_device_position(unit_vertex, sprite.bounds, viewport_size);
454 float2 tile_position = to_tile_position(unit_vertex, sprite.tile, atlas_size);
455 float4 color = hsla_to_rgba(sprite.color);
456 return PathSpriteVertexOutput{device_position, tile_position, color};
457}
458
459fragment float4 path_sprite_fragment(
460 PathSpriteVertexOutput input [[stage_in]],
461 constant PathSprite *sprites [[buffer(SpriteInputIndex_Sprites)]],
462 texture2d<float> atlas_texture [[texture(SpriteInputIndex_AtlasTexture)]]) {
463 constexpr sampler atlas_texture_sampler(mag_filter::linear,
464 min_filter::linear);
465 float4 sample =
466 atlas_texture.sample(atlas_texture_sampler, input.tile_position);
467 float mask = 1. - abs(1. - fmod(sample.r, 2.));
468 float4 color = input.color;
469 color.a *= mask;
470 return color;
471}
472
473struct SurfaceVertexOutput {
474 float4 position [[position]];
475 float2 texture_position;
476 float clip_distance [[clip_distance]][4];
477};
478
479struct SurfaceFragmentInput {
480 float4 position [[position]];
481 float2 texture_position;
482};
483
484vertex SurfaceVertexOutput surface_vertex(
485 uint unit_vertex_id [[vertex_id]], uint surface_id [[instance_id]],
486 constant float2 *unit_vertices [[buffer(SurfaceInputIndex_Vertices)]],
487 constant SurfaceBounds *surfaces [[buffer(SurfaceInputIndex_Surfaces)]],
488 constant Size_DevicePixels *viewport_size
489 [[buffer(SurfaceInputIndex_ViewportSize)]],
490 constant Size_DevicePixels *texture_size
491 [[buffer(SurfaceInputIndex_TextureSize)]]) {
492 float2 unit_vertex = unit_vertices[unit_vertex_id];
493 SurfaceBounds surface = surfaces[surface_id];
494 float4 device_position =
495 to_device_position(unit_vertex, surface.bounds, viewport_size);
496 float4 clip_distance = distance_from_clip_rect(unit_vertex, surface.bounds,
497 surface.content_mask.bounds);
498 // We are going to copy the whole texture, so the texture position corresponds
499 // to the current vertex of the unit triangle.
500 float2 texture_position = unit_vertex;
501 return SurfaceVertexOutput{
502 device_position,
503 texture_position,
504 {clip_distance.x, clip_distance.y, clip_distance.z, clip_distance.w}};
505}
506
507fragment float4 surface_fragment(SurfaceFragmentInput input [[stage_in]],
508 texture2d<float> y_texture
509 [[texture(SurfaceInputIndex_YTexture)]],
510 texture2d<float> cb_cr_texture
511 [[texture(SurfaceInputIndex_CbCrTexture)]]) {
512 constexpr sampler texture_sampler(mag_filter::linear, min_filter::linear);
513 const float4x4 ycbcrToRGBTransform =
514 float4x4(float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
515 float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
516 float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
517 float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f));
518 float4 ycbcr = float4(
519 y_texture.sample(texture_sampler, input.texture_position).r,
520 cb_cr_texture.sample(texture_sampler, input.texture_position).rg, 1.0);
521
522 return ycbcrToRGBTransform * ycbcr;
523}
524
525float4 hsla_to_rgba(Hsla hsla) {
526 float h = hsla.h * 6.0; // Now, it's an angle but scaled in [0, 6) range
527 float s = hsla.s;
528 float l = hsla.l;
529 float a = hsla.a;
530
531 float c = (1.0 - fabs(2.0 * l - 1.0)) * s;
532 float x = c * (1.0 - fabs(fmod(h, 2.0) - 1.0));
533 float m = l - c / 2.0;
534
535 float r = 0.0;
536 float g = 0.0;
537 float b = 0.0;
538
539 if (h >= 0.0 && h < 1.0) {
540 r = c;
541 g = x;
542 b = 0.0;
543 } else if (h >= 1.0 && h < 2.0) {
544 r = x;
545 g = c;
546 b = 0.0;
547 } else if (h >= 2.0 && h < 3.0) {
548 r = 0.0;
549 g = c;
550 b = x;
551 } else if (h >= 3.0 && h < 4.0) {
552 r = 0.0;
553 g = x;
554 b = c;
555 } else if (h >= 4.0 && h < 5.0) {
556 r = x;
557 g = 0.0;
558 b = c;
559 } else {
560 r = c;
561 g = 0.0;
562 b = x;
563 }
564
565 float4 rgba;
566 rgba.x = (r + m);
567 rgba.y = (g + m);
568 rgba.z = (b + m);
569 rgba.w = a;
570 return rgba;
571}
572
573float4 to_device_position(float2 unit_vertex, Bounds_ScaledPixels bounds,
574 constant Size_DevicePixels *input_viewport_size) {
575 float2 position =
576 unit_vertex * float2(bounds.size.width, bounds.size.height) +
577 float2(bounds.origin.x, bounds.origin.y);
578 float2 viewport_size = float2((float)input_viewport_size->width,
579 (float)input_viewport_size->height);
580 float2 device_position =
581 position / viewport_size * float2(2., -2.) + float2(-1., 1.);
582 return float4(device_position, 0., 1.);
583}
584
585float2 to_tile_position(float2 unit_vertex, AtlasTile tile,
586 constant Size_DevicePixels *atlas_size) {
587 float2 tile_origin = float2(tile.bounds.origin.x, tile.bounds.origin.y);
588 float2 tile_size = float2(tile.bounds.size.width, tile.bounds.size.height);
589 return (tile_origin + unit_vertex * tile_size) /
590 float2((float)atlas_size->width, (float)atlas_size->height);
591}
592
593float quad_sdf(float2 point, Bounds_ScaledPixels bounds,
594 Corners_ScaledPixels corner_radii) {
595 float2 half_size = float2(bounds.size.width, bounds.size.height) / 2.;
596 float2 center = float2(bounds.origin.x, bounds.origin.y) + half_size;
597 float2 center_to_point = point - center;
598 float corner_radius;
599 if (center_to_point.x < 0.) {
600 if (center_to_point.y < 0.) {
601 corner_radius = corner_radii.top_left;
602 } else {
603 corner_radius = corner_radii.bottom_left;
604 }
605 } else {
606 if (center_to_point.y < 0.) {
607 corner_radius = corner_radii.top_right;
608 } else {
609 corner_radius = corner_radii.bottom_right;
610 }
611 }
612
613 float2 rounded_edge_to_point =
614 abs(center_to_point) - half_size + corner_radius;
615 float distance =
616 length(max(0., rounded_edge_to_point)) +
617 min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) -
618 corner_radius;
619
620 return distance;
621}
622
623// A standard gaussian function, used for weighting samples
624float gaussian(float x, float sigma) {
625 return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
626}
627
628// This approximates the error function, needed for the gaussian integral
629float2 erf(float2 x) {
630 float2 s = sign(x);
631 float2 a = abs(x);
632 x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
633 x *= x;
634 return s - s / (x * x);
635}
636
637float blur_along_x(float x, float y, float sigma, float corner,
638 float2 half_size) {
639 float delta = min(half_size.y - corner - abs(y), 0.);
640 float curved =
641 half_size.x - corner + sqrt(max(0., corner * corner - delta * delta));
642 float2 integral =
643 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
644 return integral.y - integral.x;
645}
646
647float4 distance_from_clip_rect(float2 unit_vertex, Bounds_ScaledPixels bounds,
648 Bounds_ScaledPixels clip_bounds) {
649 float2 position =
650 unit_vertex * float2(bounds.size.width, bounds.size.height) +
651 float2(bounds.origin.x, bounds.origin.y);
652 return float4(position.x - clip_bounds.origin.x,
653 clip_bounds.origin.x + clip_bounds.size.width - position.x,
654 position.y - clip_bounds.origin.y,
655 clip_bounds.origin.y + clip_bounds.size.height - position.y);
656}
657
658float4 over(float4 below, float4 above) {
659 float4 result;
660 float alpha = above.a + below.a * (1.0 - above.a);
661 result.rgb =
662 (above.rgb * above.a + below.rgb * below.a * (1.0 - above.a)) / alpha;
663 result.a = alpha;
664 return result;
665}