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