wgpu-font-renderer 0.1.0

GPU-Centered Font Rendering crate
Documentation
struct Params {
    screen_resolution: vec2<f32>,
    _pad: vec2<f32>,
    transform: mat4x4<f32>,
}

struct VertexInput {
    @location(0) v_pos: vec2<f32>,
    @location(1) pos: vec2<f32>,
    @location(2) left_side_bearing: f32,
    @location(3) font_size: f32,
    @location(4) size: vec2<f32>,
    @location(5) atlas_pos: vec2<f32>,
    @location(6) atlas_size: u32,
    @location(7) units_per_em: f32,
    @location(8) layer: i32,
    @location(9) color: vec4<f32>,
}

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) pos: vec2<f32>,
    @location(2) left_side_bearing: f32,
    @location(3) font_size: f32,
    @location(4) size: vec2<f32>,
    @location(5) atlas_pos: vec2<f32>,
    @location(6) atlas_size: i32,
    @location(7) units_per_em: f32,
    @location(8) layer: f32,
    @location(9) color: vec4<f32>,
}

@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var atlas_sampler: sampler;
@group(1) @binding(0) var atlas_texture: texture_2d_array<f32>;

@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
    var output: VertexOutput;

    output.uv = vec2<f32>(input.v_pos);
    output.layer = f32(input.layer);

    var transform = mat4x4<f32>(
        vec4<f32>(input.size.x, 0.,           0., 0.),
        vec4<f32>(0.,           input.size.y, 0., 0.),
        vec4<f32>(0.,           0.,           1., 0.),
        vec4<f32>(input.pos,                  0., 1.),
    );

    output.position = params.transform * transform * vec4<f32>(input.v_pos * 1., 0., 1.);
    output.pos = input.pos;
    output.font_size = input.font_size;
    output.size = input.size;
    output.layer = f32(input.layer);
    output.atlas_size = i32(input.atlas_size);
    output.atlas_pos = input.atlas_pos;
    output.left_side_bearing = input.left_side_bearing;
    output.units_per_em = input.units_per_em;
    output.color = input.color;

    return output;
}

fn test_cross(a: vec2<f32>, b: vec2<f32>, p: vec2<f32>) -> f32 {
    return sign((b.y - a.y) * (p.x - a.x) - (b.x - a.x) * (p.y - a.y));
}

fn sign_bezier(A: vec2<f32>, B: vec2<f32>, C: vec2<f32>, p: vec2<f32>) -> f32 {
    let a: vec2<f32> = C - A;
    let b: vec2<f32> = B - A;
    let c: vec2<f32> = p - A;

    let r: f32 = (a.x * b.y - b.x * a.y);

    if abs(r) < 0.001 {
        return test_cross(A, B, p);
    }

    let bary = vec2<f32>(c.x * b.y - b.x * c.y, a.x * c.y - c.x * a.y) / r;
    let d = vec2<f32>(bary.y * .5, 0.) + 1. - bary.x - bary.y;
    return mix(
        sign(d.x * d.x - d.y), 
        mix(
            -1.,
            1.,
            step(
                test_cross(A, B, p) * test_cross(B, C, p),
                0.
            )
        ),
        step(
            (d.x - d.y),
            0.
        )
    ) * test_cross(A, C, B);
}

fn remap(value: f32, from1: f32, to1: f32, from2: f32, to2: f32) -> f32 {
    return (value - from1) / (to1 - from1) * (to2 - from2) + from2;
}

fn solve_cubic(a: f32, b: f32, c: f32) -> vec3<f32> {
    var p = b - a * a / 3.;
    var p3 = p * p * p;
    var q = a * (2. * a * a - 9. * b) / 27. + c;
    var d = q * q + 4. * p3 / 27.;
    var offset = -a / 3.;

    if d >= 0. {
        var z = sqrt(d);
        var x = (vec2(z, -z) -q) / 2.;
        var uv = sign(x) * pow(abs(x), vec2(1./3.));
        return vec3<f32>(offset + uv.x + uv.y);
    }

    var v = acos(-sqrt(-27. / p3) * q / 2.) / 3.;
    var m = cos(v);
    var n = sin(v) * 1.732050808;
    return vec3<f32>(m + m, -n - m, n - m) * sqrt(-p / 3.) + offset;
}

fn sd_bezier(A: vec2<f32>, B: vec2<f32>, C: vec2<f32>, p: vec2<f32>) -> f32 {
    var new_B = mix(B + vec2<f32>(1e-4), B, abs(sign(B * 2. - A - C)));
    var a = new_B - A;
    var b = A - new_B * 2. + C;
    var c = a * 2.;
    var d = A - p;
    var k = vec3<f32>(3. * dot(a, b), 2. * dot(a, a) + dot(d, b), dot(d, a)) / dot(b, b);
    var t = clamp(solve_cubic(k.x, k.y, k.z), vec3<f32>(0.), vec3<f32>(1.));
    var pos = A + (c + b * t.x) * t.x;
    var dis = length(pos - p);
    pos = A + (c + b * t.y) * t.y;
    dis = min(dis, length(pos - p));
    pos = A + (c + b * t.z) * t.z;
    dis = min(dis, length(pos- p));
    return dis * sign_bezier(A, B, C, p);
}

fn sdf_triplet_alpha(sdf: vec3<f32>, horz_scale: f32, vert_scale: f32, vgrad: f32, doffset: f32) -> vec3<f32> {
    let hdoffset = mix(doffset * horz_scale, doffset * vert_scale, vgrad);
    let rdoffset = mix(doffset, hdoffset, 0.);
    var alpha = smoothstep(vec3(.5 - rdoffset), vec3(.5 + rdoffset), sdf);
    alpha = pow(alpha, vec3(1. + .2 * vgrad * 0.));
    return alpha;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let font_size = input.font_size;

    var uv = input.uv;

    uv.x = remap(uv.x, 0., 1., 0., input.size.x * input.units_per_em / font_size);
    uv.x += input.left_side_bearing;
    uv.y = remap(uv.y, 0., 1., 0., input.size.y * input.units_per_em / font_size);

    var curve_points_count = input.atlas_size;
    var sideR = 0.;
    var sideG = 0.;
    var sideB = 0.;

    var distR = 0.;
    var distG = 0.;
    var distB = 0.;

    {
        var y_offset = input.atlas_pos.y;
        var x_offset = input.atlas_pos.x;
        var i = 0;
        loop {
            if !(i < curve_points_count) {
                break;
            }

            var atlas_x_offset = x_offset + f32(i);

            var pixels_before_eol = 2048. - atlas_x_offset;

            if pixels_before_eol == 0. {
                y_offset = y_offset + 1.;
                x_offset = 0.;
                curve_points_count = curve_points_count - i;
            }

            let ax = textureSample(atlas_texture, atlas_sampler, vec2<f32>(atlas_x_offset / 2048., y_offset / 2048.), i32(0)).x;
            let ay = textureSample(atlas_texture, atlas_sampler, vec2<f32>((atlas_x_offset + 1.) / 2048., y_offset / 2048.), i32(0)).x;
            let az = textureSample(atlas_texture, atlas_sampler, vec2<f32>((atlas_x_offset + 2.) / 2048., y_offset / 2048.), i32(0)).x;
            let aw = textureSample(atlas_texture, atlas_sampler, vec2<f32>((atlas_x_offset + 3.) / 2048., y_offset / 2048.), i32(0)).x;
            let bx = textureSample(atlas_texture, atlas_sampler, vec2<f32>((atlas_x_offset + 4.) / 2048., y_offset / 2048.), i32(0)).x;
            let by = textureSample(atlas_texture, atlas_sampler, vec2<f32>((atlas_x_offset + 5.) / 2048., y_offset / 2048.), i32(0)).x;

            if ((uv.y > ay && uv.y < by) || (uv.y > by && uv.y < ay)) {
                let snR = sign_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv - vec2(1./3., 0.));
                let snG = sign_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv);
                let snB = sign_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv + vec2(1./3., 0.));
                sideR += snR;
                sideG += snG;
                sideB += snB;
            }

            let x = abs(sd_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv));
            if distG == 0. || x < distG {
                distR = abs(sd_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv - vec2(1./3., 0.)));
                distG = x;
                distB = abs(sd_bezier(vec2<f32>(ax, ay), vec2<f32>(az, aw), vec2<f32>(bx, by), uv + vec2(1./3., 0.)));
            }

            continuing {
                if pixels_before_eol == 0. {
                    i = 0;
                } else {
                    i = i + 8;
                }
            }
        }
    }

    var vgrad = abs(dpdy(distG));

    let horz_scale = .5;
    let vert_scale = .6;

    var triplet_alpha = sdf_triplet_alpha(vec3(distR, distG, distB), horz_scale, vert_scale, vgrad, 26. - 0.16 * font_size);

    if sideG == -2. {
        triplet_alpha.r = 1. - triplet_alpha.r;
    }

    return vec4(input.color.rgb, 1 - triplet_alpha.r);
}