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;
 47};
 48
 49float4 quad_sdf(QuadFragmentInput input) {
 50    float2 half_size = input.size / 2.;
 51    float2 center = input.origin + half_size;
 52    float2 center_to_point = input.position.xy - center;
 53    float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.corner_radius;
 54    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.corner_radius;
 55
 56    float vertical_border = center_to_point.x <= 0. ? input.border_left : input.border_right;
 57    float horizontal_border = center_to_point.y <= 0. ? input.border_top : input.border_bottom;
 58    float2 inset_size = half_size - input.corner_radius - float2(vertical_border, horizontal_border);
 59    float2 point_to_inset_corner = abs(center_to_point) - inset_size;
 60    float border_width;
 61    if (point_to_inset_corner.x < 0. && point_to_inset_corner.y < 0.) {
 62        border_width = 0.;
 63    } else if (point_to_inset_corner.y > point_to_inset_corner.x) {
 64        border_width = horizontal_border;
 65    } else {
 66        border_width = vertical_border;
 67    }
 68
 69    float4 color;
 70    if (border_width == 0.) {
 71        color = input.background_color;
 72    } else {
 73        float inset_distance = distance + border_width;
 74        float4 border_color = float4(
 75            mix(input.background_color.rgb, input.border_color.rgb, input.border_color.a),
 76            saturate(input.background_color.a + input.border_color.a)
 77        );
 78        color = mix(border_color, input.background_color, saturate(0.5 - inset_distance));
 79    }
 80
 81    return color * float4(1., 1., 1., saturate(0.5 - distance));
 82}
 83
 84vertex QuadFragmentInput quad_vertex(
 85    uint unit_vertex_id [[vertex_id]],
 86    uint quad_id [[instance_id]],
 87    constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
 88    constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
 89    constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
 90) {
 91    float2 unit_vertex = unit_vertices[unit_vertex_id];
 92    GPUIQuad quad = quads[quad_id];
 93    float2 position = unit_vertex * quad.size + quad.origin;
 94    float4 device_position = to_device_position(position, uniforms->viewport_size);
 95
 96    return QuadFragmentInput {
 97        device_position,
 98        float2(0., 0.),
 99        quad.origin,
100        quad.size,
101        coloru_to_colorf(quad.background_color),
102        quad.border_top,
103        quad.border_right,
104        quad.border_bottom,
105        quad.border_left,
106        coloru_to_colorf(quad.border_color),
107        quad.corner_radius,
108    };
109}
110
111fragment float4 quad_fragment(
112    QuadFragmentInput input [[stage_in]]
113) {
114    return quad_sdf(input);
115}
116
117struct ShadowFragmentInput {
118    float4 position [[position]];
119    vector_float2 origin;
120    vector_float2 size;
121    float corner_radius;
122    float sigma;
123    vector_uchar4 color;
124};
125
126vertex ShadowFragmentInput shadow_vertex(
127    uint unit_vertex_id [[vertex_id]],
128    uint shadow_id [[instance_id]],
129    constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
130    constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
131    constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
132) {
133    float2 unit_vertex = unit_vertices[unit_vertex_id];
134    GPUIShadow shadow = shadows[shadow_id];
135
136    float margin = 3. * shadow.sigma;
137    float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
138    float4 device_position = to_device_position(position, uniforms->viewport_size);
139
140    return ShadowFragmentInput {
141        device_position,
142        shadow.origin,
143        shadow.size,
144        shadow.corner_radius,
145        shadow.sigma,
146        shadow.color,
147    };
148}
149
150fragment float4 shadow_fragment(
151    ShadowFragmentInput input [[stage_in]]
152) {
153    float sigma = input.sigma;
154    float corner_radius = input.corner_radius;
155    float2 half_size = input.size / 2.;
156    float2 center = input.origin + half_size;
157    float2 point = input.position.xy - center;
158
159    // The signal is only non-zero in a limited range, so don't waste samples
160    float low = point.y - half_size.y;
161    float high = point.y + half_size.y;
162    float start = clamp(-3. * sigma, low, high);
163    float end = clamp(3. * sigma, low, high);
164
165    // Accumulate samples (we can get away with surprisingly few samples)
166    float step = (end - start) / 4.;
167    float y = start + step * 0.5;
168    float alpha = 0.;
169    for (int i = 0; i < 4; i++) {
170        alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
171        y += step;
172    }
173
174    return float4(1., 1., 1., alpha) * coloru_to_colorf(input.color);
175}
176
177struct SpriteFragmentInput {
178    float4 position [[position]];
179    float2 atlas_position;
180    float4 color [[flat]];
181    uchar compute_winding [[flat]];
182};
183
184vertex SpriteFragmentInput sprite_vertex(
185    uint unit_vertex_id [[vertex_id]],
186    uint sprite_id [[instance_id]],
187    constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
188    constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
189    constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
190    constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
191) {
192    float2 unit_vertex = unit_vertices[unit_vertex_id];
193    GPUISprite sprite = sprites[sprite_id];
194    float2 position = unit_vertex * sprite.target_size + sprite.origin;
195    float4 device_position = to_device_position(position, *viewport_size);
196    float2 atlas_position = (unit_vertex * sprite.source_size + sprite.atlas_origin) / *atlas_size;
197
198    return SpriteFragmentInput {
199        device_position,
200        atlas_position,
201        coloru_to_colorf(sprite.color),
202        sprite.compute_winding
203    };
204}
205
206fragment float4 sprite_fragment(
207    SpriteFragmentInput input [[stage_in]],
208    texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
209) {
210    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
211    float4 color = input.color;
212    float4 sample = atlas.sample(atlas_sampler, input.atlas_position);
213    float mask;
214    if (input.compute_winding) {
215        mask = 1. - abs(1. - fmod(sample.r, 2.));
216    } else {
217        mask = sample.a;
218    }
219    color.a *= mask;
220    return color;
221}
222
223vertex QuadFragmentInput image_vertex(
224    uint unit_vertex_id [[vertex_id]],
225    uint image_id [[instance_id]],
226    constant float2 *unit_vertices [[buffer(GPUIImageVertexInputIndexVertices)]],
227    constant GPUIImage *images [[buffer(GPUIImageVertexInputIndexImages)]],
228    constant float2 *viewport_size [[buffer(GPUIImageVertexInputIndexViewportSize)]],
229    constant float2 *atlas_size [[buffer(GPUIImageVertexInputIndexAtlasSize)]]
230) {
231    float2 unit_vertex = unit_vertices[unit_vertex_id];
232    GPUIImage image = images[image_id];
233    float2 position = unit_vertex * image.target_size + image.origin;
234    float4 device_position = to_device_position(position, *viewport_size);
235    float2 atlas_position = (unit_vertex * image.source_size + image.atlas_origin) / *atlas_size;
236
237    return QuadFragmentInput {
238        device_position,
239        atlas_position,
240        image.origin,
241        image.target_size,
242        float4(0.),
243        image.border_top,
244        image.border_right,
245        image.border_bottom,
246        image.border_left,
247        coloru_to_colorf(image.border_color),
248        image.corner_radius,
249    };
250}
251
252fragment float4 image_fragment(
253    QuadFragmentInput input [[stage_in]],
254    texture2d<float> atlas [[ texture(GPUIImageFragmentInputIndexAtlas) ]]
255) {
256    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
257    input.background_color = atlas.sample(atlas_sampler, input.atlas_position);
258    return quad_sdf(input);
259}
260
261struct PathAtlasVertexOutput {
262    float4 position [[position]];
263    float2 st_position;
264    float clip_rect_distance [[clip_distance]] [4];
265};
266
267struct PathAtlasFragmentInput {
268    float4 position [[position]];
269    float2 st_position;
270};
271
272vertex PathAtlasVertexOutput path_atlas_vertex(
273    uint vertex_id [[vertex_id]],
274    constant GPUIPathVertex *vertices [[buffer(GPUIPathAtlasVertexInputIndexVertices)]],
275    constant float2 *atlas_size [[buffer(GPUIPathAtlasVertexInputIndexAtlasSize)]]
276) {
277    GPUIPathVertex v = vertices[vertex_id];
278    float4 device_position = to_device_position(v.xy_position, *atlas_size);
279    return PathAtlasVertexOutput {
280        device_position,
281        v.st_position,
282        {
283            v.xy_position.x - v.clip_rect_origin.x,
284            v.clip_rect_origin.x + v.clip_rect_size.x - v.xy_position.x,
285            v.xy_position.y - v.clip_rect_origin.y,
286            v.clip_rect_origin.y + v.clip_rect_size.y - v.xy_position.y
287        }
288    };
289}
290
291fragment float4 path_atlas_fragment(
292    PathAtlasFragmentInput input [[stage_in]]
293) {
294    float2 dx = dfdx(input.st_position);
295    float2 dy = dfdy(input.st_position);
296    float2 gradient = float2(
297        (2. * input.st_position.x) * dx.x - dx.y,
298        (2. * input.st_position.x) * dy.x - dy.y
299    );
300    float f = (input.st_position.x * input.st_position.x) - input.st_position.y;
301    float distance = f / length(gradient);
302    float alpha = saturate(0.5 - distance);
303    return float4(alpha, 0., 0., 1.);
304}
305
306struct UnderlineFragmentInput {
307    float4 position [[position]];
308    float2 origin;
309    float2 size;
310    float thickness;
311    float4 color;
312    bool squiggly;
313};
314
315vertex UnderlineFragmentInput underline_vertex(
316    uint unit_vertex_id [[vertex_id]],
317    uint underline_id [[instance_id]],
318    constant float2 *unit_vertices [[buffer(GPUIUnderlineInputIndexVertices)]],
319    constant GPUIUnderline *underlines [[buffer(GPUIUnderlineInputIndexUnderlines)]],
320    constant GPUIUniforms *uniforms [[buffer(GPUIUnderlineInputIndexUniforms)]]
321) {
322    float2 unit_vertex = unit_vertices[unit_vertex_id];
323    GPUIUnderline underline = underlines[underline_id];
324    float2 position = unit_vertex * underline.size + underline.origin;
325    float4 device_position = to_device_position(position, uniforms->viewport_size);
326
327    return UnderlineFragmentInput {
328        device_position,
329        underline.origin,
330        underline.size,
331        underline.thickness,
332        coloru_to_colorf(underline.color),
333        underline.squiggly != 0,
334    };
335}
336
337fragment float4 underline_fragment(
338    UnderlineFragmentInput input [[stage_in]]
339) {
340    if (input.squiggly) {
341        float half_thickness = input.thickness * 0.5;
342        float2 st = ((input.position.xy - input.origin) / input.size.y) - float2(0., 0.5);
343        float frequency = (M_PI_F * (3. * input.thickness)) / 8.;
344        float amplitude = 1. / (2. * input.thickness);
345        float sine = sin(st.x * frequency) * amplitude;
346        float dSine = cos(st.x * frequency) * amplitude * frequency;
347        float distance = (st.y - sine) / sqrt(1. + dSine * dSine);
348        float distance_in_pixels = distance * input.size.y;
349        float distance_from_top_border = distance_in_pixels - half_thickness;
350        float distance_from_bottom_border = distance_in_pixels + half_thickness;
351        float alpha = saturate(0.5 - max(-distance_from_bottom_border, distance_from_top_border));
352        return input.color * float4(1., 1., 1., alpha);
353    } else {
354        return input.color;
355    }
356}