shaders.metal

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