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