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}