nightshade 0.8.0

A cross-platform data-oriented game engine.
Documentation
struct TextInstanceData {
    color: vec4<f32>,
    outline_color: vec4<f32>,
    clip_rect: vec4<f32>,
    params: vec4<f32>,
}

@group(0) @binding(0) var<storage, read> text_instances: array<TextInstanceData>;
@group(0) @binding(1) var<storage, read> character_colors: array<vec4<f32>>;
@group(1) @binding(0) var font_texture: texture_2d<f32>;
@group(1) @binding(1) var font_sampler: sampler;

struct GlobalUniforms {
    projection: mat4x4<f32>,
}

@group(2) @binding(0) var<uniform> globals: GlobalUniforms;

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) tex_coords: vec2<f32>,
    @location(2) character_index: u32,
    @location(3) text_instance_index: u32,
}

struct VertexOutput {
    @builtin(position) position: vec4<f32>,
    @location(0) tex_coords: vec2<f32>,
    @location(1) screen_pos: vec2<f32>,
    @location(2) @interpolate(flat) character_index: u32,
    @location(3) @interpolate(flat) text_instance_index: u32,
}

fn median(r: f32, g: f32, b: f32) -> f32 {
    return max(min(r, g), min(max(r, g), b));
}

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

    let inst = text_instances[vertex.text_instance_index];
    let depth = inst.params.y;

    output.position = globals.projection * vec4<f32>(vertex.position.xy, 0.0, 1.0);
    output.position.z = depth;
    output.tex_coords = vertex.tex_coords;
    output.screen_pos = vertex.position.xy;
    output.character_index = vertex.character_index;
    output.text_instance_index = vertex.text_instance_index;

    return output;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let inst = text_instances[input.text_instance_index];

    let clip = inst.clip_rect;
    if clip.z > clip.x {
        let frag_pos = input.screen_pos;
        if frag_pos.x < clip.x || frag_pos.x > clip.z ||
           frag_pos.y < clip.y || frag_pos.y > clip.w {
            discard;
        }
    }

    let msd = textureSample(font_texture, font_sampler, input.tex_coords);
    let sd = median(msd.r, msd.g, msd.b);

    let px_range = 8.0;
    let tex_dims = vec2<f32>(textureDimensions(font_texture));
    let unit_range = vec2<f32>(px_range) / tex_dims;
    let screen_tex_size = vec2<f32>(1.0) / fwidth(input.tex_coords);
    let screen_px_range = max(0.5 * dot(unit_range, screen_tex_size), 1.0);
    let screen_px_distance = screen_px_range * (sd - 0.5);
    let alpha_base = clamp(screen_px_distance + 0.5, 0.0, 1.0);

    let char_color = character_colors[input.character_index];
    let use_override = char_color.a > 0.0;
    let base_color = select(inst.color, char_color, use_override);

    let outline_width = inst.params.x;
    if outline_width > 0.0 {
        let outline_threshold = 0.5 - outline_width;
        let outline_screen_dist = screen_px_range * (sd - outline_threshold);
        let alpha_outline = clamp(outline_screen_dist + 0.5, 0.0, 1.0);

        let final_color = mix(inst.outline_color, base_color, alpha_base / max(alpha_outline, 0.001));
        return vec4<f32>(final_color.rgb, final_color.a * alpha_outline);
    } else {
        return vec4<f32>(base_color.rgb, base_color.a * alpha_base);
    }
}