shaders.metal

  1#include <metal_stdlib>
  2#include "shaders.h"
  3
  4using namespace metal;
  5
  6float4 coloru_to_colorf(uchar4 coloru) {
  7    return float4(coloru) / float4(0xff, 0xff, 0xff, 0xff);
  8}
  9
 10float4 to_device_position(float2 pixel_position, float2 viewport_size) {
 11    return float4(pixel_position / viewport_size * float2(2., -2.) + float2(-1., 1.), 0., 1.);
 12}
 13
 14// A standard gaussian function, used for weighting samples
 15float gaussian(float x, float sigma) {
 16    return exp(-(x * x) / (2. * sigma * sigma)) / (sqrt(2. * M_PI_F) * sigma);
 17}
 18
 19// This approximates the error function, needed for the gaussian integral
 20float2 erf(float2 x) {
 21    float2 s = sign(x);
 22    float2 a = abs(x);
 23    x = 1. + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
 24    x *= x;
 25    return s - s / (x * x);
 26}
 27
 28float blur_along_x(float x, float y, float sigma, float corner, float2 halfSize) {
 29    float delta = min(halfSize.y - corner - abs(y), 0.);
 30    float curved = halfSize.x - corner + sqrt(max(0., corner * corner - delta * delta));
 31    float2 integral = 0.5 + 0.5 * erf((x + float2(-curved, curved)) * (sqrt(0.5) / sigma));
 32    return integral.y - integral.x;
 33}
 34
 35struct QuadFragmentInput {
 36    float4 position [[position]];
 37    float2 atlas_position; // only used in the image shader
 38    float2 origin;
 39    float2 size;
 40    float4 background_color;
 41    float border_top;
 42    float border_right;
 43    float border_bottom;
 44    float border_left;
 45    float4 border_color;
 46    float corner_radius_top_left;
 47    float corner_radius_top_right;
 48    float corner_radius_bottom_right;
 49    float corner_radius_bottom_left;
 50    uchar grayscale; // only used in image shader
 51};
 52
 53float4 quad_sdf(QuadFragmentInput input) {
 54    float2 half_size = input.size / 2.;
 55    float2 center = input.origin + half_size;
 56    float2 center_to_point = input.position.xy - center;
 57    float corner_radius;
 58    if (center_to_point.x < 0.) {
 59        if (center_to_point.y < 0.) {
 60            corner_radius = input.corner_radius_top_left;
 61        } else {
 62            corner_radius = input.corner_radius_bottom_left;
 63        }
 64    } else {
 65        if (center_to_point.y < 0.) {
 66            corner_radius = input.corner_radius_top_right;
 67        } else {
 68            corner_radius = input.corner_radius_bottom_right;
 69        }
 70    }
 71
 72    float2 rounded_edge_to_point = abs(center_to_point) - half_size + corner_radius;
 73    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - corner_radius;
 74
 75    float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
 76    float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
 77    float2 inset_size = half_size - corner_radius - float2(vertical_border, horizontal_border);
 78    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
 79    float border_width;
 80    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
 81        border_width = 0.;
 82    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
 83        border_width = horizontal_border;
 84    } else {
 85        border_width = vertical_border;
 86    }
 87
 88    float4 color;
 89    if (border_width == 0.) {
 90        color = input.background_color;
 91    } else {
 92        float inset_distance = distance + border_width;
 93
 94        // Decrease border's opacity as we move inside the background.
 95        input.border_color.a *= 1. - saturate(0.5 - inset_distance);
 96
 97        // Alpha-blend the border and the background.
 98        float output_alpha = input.border_color.a + input.background_color.a * (1. - input.border_color.a);
 99        float3 premultiplied_border_rgb = input.border_color.rgb * input.border_color.a;
100        float3 premultiplied_background_rgb = input.background_color.rgb * input.background_color.a;
101        float3 premultiplied_output_rgb = premultiplied_border_rgb + premultiplied_background_rgb * (1. - input.border_color.a);
102        color = float4(premultiplied_output_rgb / output_alpha, output_alpha);
103    }
104
105    return color * float4(1., 1., 1., saturate(0.5 - distance));
106}
107
108vertex QuadFragmentInput quad_vertex(
109    uint unit_vertex_id [[vertex_id]],
110    uint quad_id [[instance_id]],
111    constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
112    constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
113    constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
114) {
115    float2 unit_vertex = unit_vertices[unit_vertex_id];
116    GPUIQuad quad = quads[quad_id];
117    float2 position = unit_vertex * quad.size + quad.origin;
118    float4 device_position = to_device_position(position, uniforms->viewport_size);
119
120    return QuadFragmentInput {
121        device_position,
122        float2(0., 0.),
123        quad.origin,
124        quad.size,
125        coloru_to_colorf(quad.background_color),
126        quad.border_top,
127        quad.border_right,
128        quad.border_bottom,
129        quad.border_left,
130        coloru_to_colorf(quad.border_color),
131        quad.corner_radius_top_left,
132        quad.corner_radius_top_right,
133        quad.corner_radius_bottom_right,
134        quad.corner_radius_bottom_left,
135        0,
136    };
137}
138
139fragment float4 quad_fragment(
140    QuadFragmentInput input [[stage_in]]
141) {
142    return quad_sdf(input);
143}
144
145struct ShadowFragmentInput {
146    float4 position [[position]];
147    vector_float2 origin;
148    vector_float2 size;
149    float corner_radius_top_left;
150    float corner_radius_top_right;
151    float corner_radius_bottom_right;
152    float corner_radius_bottom_left;
153    float sigma;
154    vector_uchar4 color;
155};
156
157vertex ShadowFragmentInput shadow_vertex(
158    uint unit_vertex_id [[vertex_id]],
159    uint shadow_id [[instance_id]],
160    constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
161    constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
162    constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
163) {
164    float2 unit_vertex = unit_vertices[unit_vertex_id];
165    GPUIShadow shadow = shadows[shadow_id];
166
167    float margin = 3. * shadow.sigma;
168    float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
169    float4 device_position = to_device_position(position, uniforms->viewport_size);
170
171    return ShadowFragmentInput {
172        device_position,
173        shadow.origin,
174        shadow.size,
175        shadow.corner_radius_top_left,
176        shadow.corner_radius_top_right,
177        shadow.corner_radius_bottom_right,
178        shadow.corner_radius_bottom_left,
179        shadow.sigma,
180        shadow.color,
181    };
182}
183
184fragment float4 shadow_fragment(
185    ShadowFragmentInput input [[stage_in]]
186) {
187    float sigma = input.sigma;
188    float2 half_size = input.size / 2.;
189    float2 center = input.origin + half_size;
190    float2 point = input.position.xy - center;
191    float2 center_to_point = input.position.xy - center;
192    float corner_radius;
193    if (center_to_point.x < 0.) {
194        if (center_to_point.y < 0.) {
195            corner_radius = input.corner_radius_top_left;
196        } else {
197            corner_radius = input.corner_radius_bottom_left;
198        }
199    } else {
200        if (center_to_point.y < 0.) {
201            corner_radius = input.corner_radius_top_right;
202        } else {
203            corner_radius = input.corner_radius_bottom_right;
204        }
205    }
206
207    // The signal is only non-zero in a limited range, so don't waste samples
208    float low = point.y - half_size.y;
209    float high = point.y + half_size.y;
210    float start = clamp(-3. * sigma, low, high);
211    float end = clamp(3. * sigma, low, high);
212
213    // Accumulate samples (we can get away with surprisingly few samples)
214    float step = (end - start) / 4.;
215    float y = start + step * 0.5;
216    float alpha = 0.;
217    for (int i = 0; i < 4; i++) {
218        alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
219        y += step;
220    }
221
222    return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
223}
224
225struct SpriteFragmentInput {
226    float4 position [[position]];
227    float2 atlas_position;
228    float4 color [[flat]];
229    uchar compute_winding [[flat]];
230};
231
232vertex SpriteFragmentInput sprite_vertex(
233    uint unit_vertex_id [[vertex_id]],
234    uint sprite_id [[instance_id]],
235    constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
236    constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
237    constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
238    constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
239) {
240    float2 unit_vertex = unit_vertices[unit_vertex_id];
241    GPUISprite sprite = sprites[sprite_id];
242    float2 position = unit_vertex * sprite.target_size + sprite.origin;
243    float4 device_position = to_device_position(position, *viewport_size);
244    float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
245
246    return SpriteFragmentInput {
247        device_position,
248        atlas_position,
249        coloru_to_colorf(sprite.color),
250        sprite.compute_winding
251    };
252}
253
254fragment float4 sprite_fragment(
255    SpriteFragmentInput input [[stage_in]],
256    texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
257) {
258    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
259    float4 color = input.color;
260    float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
261    float mask;
262    if (input.compute_winding) {
263        mask = 1. - abs(1. - fmod(sample.r, 2.));
264    } else {
265        mask = sample.a;
266    }
267    color.a *= mask;
268    return color;
269}
270
271vertex QuadFragmentInput image_vertex(
272    uint unit_vertex_id [[vertex_id]],
273    uint image_id [[instance_id]],
274    constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
275    constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
276    constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
277    constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
278) {
279    float2 unit_vertex = unit_vertices[unit_vertex_id];
280    GPUIImage image = images[image_id];
281    float2 position = unit_vertex * image.target_size + image.origin;
282    float4 device_position = to_device_position(position, *viewport_size);
283    float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
284
285    return QuadFragmentInput {
286        device_position,
287        atlas_position,
288        image.origin,
289        image.target_size,
290        float4(0.),
291        image.border_top,
292        image.border_right,
293        image.border_bottom,
294        image.border_left,
295        coloru_to_colorf(image.border_color),
296        image.corner_radius_top_left,
297        image.corner_radius_top_right,
298        image.corner_radius_bottom_right,
299        image.corner_radius_bottom_left,
300        image.grayscale,
301    };
302}
303
304fragment float4 image_fragment(
305    QuadFragmentInput input [[stage_in]],
306    texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
307) {
308    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
309    input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
310    if (input.grayscale) {
311        float grayscale =
312            0.2126 * input.background_color.r +
313            0.7152 * input.background_color.g +
314            0.0722 * input.background_color.b;
315        input.background_color = float4(grayscale, grayscale, grayscale, input.background_color.a);
316    }
317    return quad_sdf(input);
318}
319
320vertex QuadFragmentInput surface_vertex(
321    uint unit_vertex_id [[vertex_id]],
322    uint image_id [[instance_id]],
323    constant float2 *unit_vertices [[buffer(GPUISurfaceVertexInputIndexVertices)]],
324    constant GPUISurface *images [[buffer(GPUISurfaceVertexInputIndexSurfaces)]],
325    constant float2 *viewport_size [[buffer(GPUISurfaceVertexInputIndexViewportSize)]],
326    constant float2 *atlas_size [[buffer(GPUISurfaceVertexInputIndexAtlasSize)]]
327) {
328    float2 unit_vertex = unit_vertices[unit_vertex_id];
329    GPUISurface image = images[image_id];
330    float2 position = unit_vertex * image.target_size + image.origin;
331    float4 device_position = to_device_position(position, *viewport_size);
332    float2 atlas_position = (unit_vertex * image.source_size) / *atlas_size;
333
334    return QuadFragmentInput {
335        device_position,
336        atlas_position,
337        image.origin,
338        image.target_size,
339        float4(0.),
340        0.,
341        0.,
342        0.,
343        0.,
344        float4(0.),
345        0.,
346        0,
347    };
348}
349
350fragment float4 surface_fragment(
351    QuadFragmentInput input [[stage_in]],
352    texture2d<float> y_atlas [[ texture(GPUISurfaceFragmentInputIndexYAtlas) ]],
353    texture2d<float> cb_cr_atlas [[ texture(GPUISurfaceFragmentInputIndexCbCrAtlas) ]]
354) {
355    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
356    const float4x4 ycbcrToRGBTransform = float4x4(
357        float4(+1.0000f, +1.0000f, +1.0000f, +0.0000f),
358        float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
359        float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
360        float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
361    );
362    float4 ycbcr = float4(y_atlas.sample(atlas_sampler, input.atlas_position).r,
363                          cb_cr_atlas.sample(atlas_sampler, input.atlas_position).rg, 1.0);
364
365    input.background_color = ycbcrToRGBTransform * ycbcr;
366    return quad_sdf(input);
367}
368
369struct PathAtlasVertexOutput {
370    float4 position [[position]];
371    float2 st_position;
372    float clip_rect_distance [[clip_distance]] [4];
373};
374
375struct PathAtlasFragmentInput {
376    float4 position [[position]];
377    float2 st_position;
378};
379
380vertex PathAtlasVertexOutput path_atlas_vertex(
381    uint vertex_id [[vertex_id]],
382    constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
383    constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
384) {
385    GPUIPathVertex v = vertices[vertex_id];
386    float4 device_position = to_device_position(v.xy_position, *atlas_size);
387    return PathAtlasVertexOutput {
388        device_position,
389        v.st_position,
390        {
391            v.xy_position.x - v.clip_rect_origin.x,
392            v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
393            v.xy_position.y - v.clip_rect_origin.y,
394            v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
395        }
396    };
397}
398
399fragment float4 path_atlas_fragment(
400    PathAtlasFragmentInput input [[stage_in]]
401) {
402    float2 dx = dfdx(input.st_position);
403    float2 dy = dfdy(input.st_position);
404    float2 gradient = float2(
405        (2. * input.st_position.x) * dx.x - dx.y,
406        (2. * input.st_position.x) * dy.x - dy.y
407    );
408    float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
409    float distance = f / length(gradient);
410    float alpha = saturate(0.5 - distance);
411    return float4(alpha, 0., 0., 1.);
412}
413
414struct UnderlineFragmentInput {
415    float4 position [[position]];
416    float2 origin;
417    float2 size;
418    float thickness;
419    float4 color;
420    bool squiggly;
421};
422
423vertex UnderlineFragmentInput underline_vertex(
424    uint unit_vertex_id [[vertex_id]],
425    uint underline_id [[instance_id]],
426    constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
427    constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
428    constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
429) {
430    float2 unit_vertex = unit_vertices[unit_vertex_id];
431    GPUIUnderline underline = underlines[underline_id];
432    float2 position = unit_vertex * underline.size + underline.origin;
433    float4 device_position = to_device_position(position, uniforms->viewport_size);
434
435    return UnderlineFragmentInput {
436        device_position,
437        underline.origin,
438        underline.size,
439        underline.thickness,
440        coloru_to_colorf(underline.color),
441        underline.squiggly != 0,
442    };
443}
444
445fragment float4 underline_fragment(
446    UnderlineFragmentInput input [[stage_in]]
447) {
448    if (input.squiggly) {
449        float half_thickness = input.thickness * 0.5;
450        float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
451        float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
452        float amplitude = 1. / (2. * input.thickness);
453        float sine = sin(st.x * frequency) * amplitude;
454        float dSine = cos(st.x * frequency) * amplitude * frequency;
455        float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
456        float distance_in_pixels = distance * input.size.y;
457        float distance_from_top_border = distance_in_pixels - half_thickness;
458        float distance_from_bottom_border = distance_in_pixels + half_thickness;
459        float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
460        return input.color * float4(1., 1., 1., alpha);
461    } else {
462        return input.color;
463    }
464}