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