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    GPUIQuad quad;
 38};
 39
 40vertex QuadFragmentInput quad_vertex(
 41    uint unit_vertex_id [[vertex_id]],
 42    uint quad_id [[instance_id]],
 43    constant float2 *unit_vertices [[buffer(GPUIQuadInputIndexVertices)]],
 44    constant GPUIQuad *quads [[buffer(GPUIQuadInputIndexQuads)]],
 45    constant GPUIUniforms *uniforms [[buffer(GPUIQuadInputIndexUniforms)]]
 46) {
 47    float2 unit_vertex = unit_vertices[unit_vertex_id];
 48    GPUIQuad quad = quads[quad_id];
 49    float2 position = unit_vertex * quad.size + quad.origin;
 50    float4 device_position = to_device_position(position, uniforms->viewport_size);
 51
 52    return QuadFragmentInput {
 53        device_position,
 54        quad,
 55    };
 56}
 57
 58fragment float4 quad_fragment(
 59    QuadFragmentInput input [[stage_in]]
 60) {
 61    float2 half_size = input.quad.size / 2.;
 62    float2 center = input.quad.origin + half_size;
 63    float2 center_to_point = input.position.xy - center;
 64    float2 edge_to_point = abs(center_to_point) - half_size;
 65    float2 rounded_edge_to_point = abs(center_to_point) - half_size + input.quad.corner_radius;
 66    float distance = length(max(0., rounded_edge_to_point)) + min(0., max(rounded_edge_to_point.x, rounded_edge_to_point.y)) - input.quad.corner_radius;
 67
 68    float border_width = 0.;
 69    if (edge_to_point.x > edge_to_point.y) {
 70        border_width = center_to_point.x <= 0. ? input.quad.border_left : input.quad.border_right;
 71    } else {
 72        border_width = center_to_point.y <= 0. ? input.quad.border_top : input.quad.border_bottom;
 73    }
 74
 75    float4 color;
 76    if (border_width == 0.) {
 77        color = coloru_to_colorf(input.quad.background_color);
 78    } else {
 79        float inset_distance = distance + border_width;
 80        color = mix(
 81            coloru_to_colorf(input.quad.border_color),
 82            coloru_to_colorf(input.quad.background_color),
 83            saturate(0.5 - inset_distance)
 84        );
 85    }
 86
 87    float4 coverage = float4(1., 1., 1., saturate(0.5 - distance));
 88    return coverage * color;
 89}
 90
 91struct ShadowFragmentInput {
 92    float4 position [[position]];
 93    GPUIShadow shadow;
 94};
 95
 96vertex ShadowFragmentInput shadow_vertex(
 97    uint unit_vertex_id [[vertex_id]],
 98    uint shadow_id [[instance_id]],
 99    constant float2 *unit_vertices [[buffer(GPUIShadowInputIndexVertices)]],
100    constant GPUIShadow *shadows [[buffer(GPUIShadowInputIndexShadows)]],
101    constant GPUIUniforms *uniforms [[buffer(GPUIShadowInputIndexUniforms)]]
102) {
103    float2 unit_vertex = unit_vertices[unit_vertex_id];
104    GPUIShadow shadow = shadows[shadow_id];
105
106    float margin = 3. * shadow.sigma;
107    float2 position = unit_vertex * (shadow.size + 2. * margin) + shadow.origin - margin;
108    float4 device_position = to_device_position(position, uniforms->viewport_size);
109
110    return ShadowFragmentInput {
111        device_position,
112        shadow,
113    };
114}
115
116fragment float4 shadow_fragment(
117    ShadowFragmentInput input [[stage_in]]
118) {
119    float sigma = input.shadow.sigma;
120    float corner_radius = input.shadow.corner_radius;
121    float2 half_size = input.shadow.size / 2.;
122    float2 center = input.shadow.origin + half_size;
123    float2 point = input.position.xy - center;
124
125    // The signal is only non-zero in a limited range, so don't waste samples
126    float low = point.y - half_size.y;
127    float high = point.y + half_size.y;
128    float start = clamp(-3. * sigma, low, high);
129    float end = clamp(3. * sigma, low, high);
130
131    // Accumulate samples (we can get away with surprisingly few samples)
132    float step = (end - start) / 4.;
133    float y = start + step * 0.5;
134    float alpha = 0.;
135    for (int i = 0; i < 4; i++) {
136        alpha += blur_along_x(point.x, point.y - y, sigma, corner_radius, half_size) * gaussian(y, sigma) * step;
137        y += step;
138    }
139
140    return float4(1., 1., 1., alpha) * coloru_to_colorf(input.shadow.color);
141}
142
143struct SpriteFragmentInput {
144    float4 position [[position]];
145    float2 atlas_position;
146    float4 color [[flat]];
147};
148
149vertex SpriteFragmentInput sprite_vertex(
150    uint unit_vertex_id [[vertex_id]],
151    uint sprite_id [[instance_id]],
152    constant float2 *unit_vertices [[buffer(GPUISpriteVertexInputIndexVertices)]],
153    constant GPUISprite *sprites [[buffer(GPUISpriteVertexInputIndexSprites)]],
154    constant float2 *viewport_size [[buffer(GPUISpriteVertexInputIndexViewportSize)]],
155    constant float2 *atlas_size [[buffer(GPUISpriteVertexInputIndexAtlasSize)]]
156) {
157    float2 unit_vertex = unit_vertices[unit_vertex_id];
158    GPUISprite sprite = sprites[sprite_id];
159    float2 position = unit_vertex * sprite.size + sprite.origin;
160    float4 device_position = to_device_position(position, *viewport_size);
161    float2 atlas_position = (unit_vertex * sprite.size + sprite.atlas_origin) / *atlas_size;
162
163    return SpriteFragmentInput {
164        device_position,
165        atlas_position,
166        coloru_to_colorf(sprite.color),
167    };
168}
169
170fragment float4 sprite_fragment(
171    SpriteFragmentInput input [[stage_in]],
172    texture2d<float> atlas [[ texture(GPUISpriteFragmentInputIndexAtlas) ]]
173) {
174    constexpr sampler atlas_sampler(mag_filter::linear, min_filter::linear);
175    float4 color = input.color;
176    float4 mask = atlas.sample(atlas_sampler, input.atlas_position);
177    color.a *= mask.a;
178    return color;
179}