nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
struct GlobalUniforms {
    projection: mat4x4<f32>,
}

struct RectInstance {
    position_size: vec4<f32>,
    color: vec4<f32>,
    border_color: vec4<f32>,
    clip_rect: vec4<f32>,
    params: vec4<f32>,
}

@group(0) @binding(0) var<uniform> globals: GlobalUniforms;
@group(1) @binding(0) var<storage, read> instances: array<RectInstance>;

struct VertexInput {
    @location(0) position: vec2<f32>,
    @builtin(instance_index) instance_index: u32,
}

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

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

    let inst = instances[vertex.instance_index];
    let rect_pos = inst.position_size.xy;
    let rect_size = inst.position_size.zw;

    let local = vertex.position * rect_size;

    let rotation = inst.params.w;
    let center = rect_size * 0.5;
    let centered = local - center;
    let cos_r = cos(rotation);
    let sin_r = sin(rotation);
    let rotated = vec2<f32>(
        centered.x * cos_r - centered.y * sin_r,
        centered.x * sin_r + centered.y * cos_r,
    );
    let world_pos = rect_pos + center + rotated;

    output.position = globals.projection * vec4<f32>(world_pos, 0.0, 1.0);
    output.position.z = inst.params.z;
    output.local_pos = local;
    output.rect_size = rect_size;
    output.screen_pos = world_pos;
    output.instance_index = vertex.instance_index;

    return output;
}

fn rounded_rect_sdf(pos: vec2<f32>, size: vec2<f32>, radius: f32) -> f32 {
    let half_size = size * 0.5;
    let center_pos = pos - half_size;
    let clamped_radius = min(radius, min(half_size.x, half_size.y));
    let q = abs(center_pos) - half_size + clamped_radius;
    return length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - clamped_radius;
}

@fragment
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
    let inst = instances[input.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 corner_radius = inst.params.x;
    let border_width = inst.params.y;

    let dist = rounded_rect_sdf(input.local_pos, input.rect_size, corner_radius);

    let edge_softness = 1.0;
    var alpha = 1.0 - smoothstep(-edge_softness, 0.0, dist);

    var final_color = inst.color;

    if border_width > 0.0 {
        let inner_dist = dist + border_width;
        let border_alpha = 1.0 - smoothstep(-edge_softness, 0.0, inner_dist);
        let is_border = alpha - border_alpha;
        final_color = mix(final_color, inst.border_color, is_border);
    }

    if alpha < 0.01 {
        discard;
    }

    return vec4<f32>(final_color.rgb, final_color.a * alpha);
}