avenger-wgpu 0.0.1-a1

WGPU rendering engine for the Avenger visualization framework
Documentation
// Vertex shader
struct ChartUniform {
    size: vec2<f32>,
    origin: vec2<f32>,
    group_size: vec2<f32>,
    scale: f32,
    clip: f32,
};

@group(0) @binding(0)
var<uniform> chart_uniforms: ChartUniform;

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

struct InstanceInput {
    @location(1) position: vec2<f32>,
    @location(2) fill: vec4<f32>,
    @location(3) width: f32,
    @location(4) height: f32,
    @location(5) stroke: vec4<f32>,
    @location(6) stroke_width: f32,
    @location(7) corner_radius: f32,
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) fill: vec4<f32>,
    @location(1) stroke: vec4<f32>,
    @location(2) stroke_width: f32,
    @location(3) corner_radius: f32,

    // Outer points are outside of stroke
    @location(4) outer_top_left: vec2<f32>,
    @location(5) outer_bottom_right: vec2<f32>,

    // Inner points are centers of the corner radius
    @location(6) inner_top_left: vec2<f32>,
    @location(7) inner_bottom_right: vec2<f32>,
};

@vertex
fn vs_main(
    model: VertexInput,
    instance: InstanceInput,
) -> VertexOutput {
    var out: VertexOutput;
    // Pass through values
    out.fill = instance.fill;
    out.stroke = instance.stroke;
    out.stroke_width = instance.stroke_width;

    // corner_radius may not be less than half the rect height
    let corner_radius = min(instance.corner_radius, instance.height / 2.0);
    out.corner_radius = corner_radius;

    // Compute absolute position
    let position = instance.position + chart_uniforms.origin;

    // Compute corner points in fragment shader coordinates
    let half_stroke = instance.stroke_width / 2.0;
    let width_height = vec2<f32>(instance.width, instance.height);
    out.outer_top_left = (position - half_stroke) * chart_uniforms.scale;
    out.outer_bottom_right = (position + width_height + half_stroke) * chart_uniforms.scale;

    // Compute corner radius center points in fragment shader coordinates
    out.inner_top_left = (position + corner_radius) * chart_uniforms.scale;
    out.inner_bottom_right = (position + width_height - corner_radius) * chart_uniforms.scale;

    // Compute vertex coordinates
    let x = 2.0 * (model.position[0] * (instance.width + instance.stroke_width) + position[0] - half_stroke) / chart_uniforms.size[0] - 1.0;
    let y = 2.0 * (model.position[1] * (instance.height + instance.stroke_width) + (chart_uniforms.size[1] - position[1] - instance.height - half_stroke)) / chart_uniforms.size[1] - 1.0;
    out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
    return out;
}

// Fragment shader
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let scaled_radius = in.corner_radius * chart_uniforms.scale;
    let scaled_stroke_width = in.stroke_width * chart_uniforms.scale;
    let frag_xy = vec2<f32>(in.clip_position[0], in.clip_position[1]);

    // Compute fill and stroke, potentially based on gradient
    let fill = lookup_color(in.fill, in.clip_position, in.outer_top_left, in.outer_bottom_right);
    let stroke = lookup_color(in.stroke, in.clip_position, in.outer_top_left, in.outer_bottom_right);

    if (scaled_radius > 0.0) {
        // has rounded corners
        let inner_bottom_left = vec2<f32>(in.inner_top_left[0], in.inner_bottom_right[1]);
        let inner_top_right = vec2<f32>(in.inner_bottom_right[0], in.inner_top_left[1]);

        let in_top_left = in.clip_position[0] < in.inner_top_left[0] && in.clip_position[1] < in.inner_top_left[1];
        let in_top_right = inner_top_right[0] < in.clip_position[0] && in.clip_position[1] < inner_top_right[1];
        let in_bottom_right = in.inner_bottom_right[0] < in.clip_position[0] && in.inner_bottom_right[1] < in.clip_position[1];
        let in_bottom_left = in.clip_position[0] < inner_bottom_left[0] && inner_bottom_left[1] < in.clip_position[1];

        let buffer = 0.5 * chart_uniforms.scale;
        if (scaled_stroke_width > 0.0) {
            var dist: f32 = scaled_radius;
            if (in_top_left) {
                dist = distance(in.inner_top_left, frag_xy);
            } else if (in_bottom_right) {
                dist = distance(in.inner_bottom_right, frag_xy);
            } else if (in_bottom_left) {
                dist = distance(inner_bottom_left, frag_xy);
            } else if (in_top_right) {
                dist = distance(inner_top_right, frag_xy);
            } else {
                let right_dist = frag_xy[0] - inner_top_right[0];
                let left_dist = in.inner_top_left[0] - frag_xy[0];
                let top_dist = in.inner_top_left[1] - frag_xy[1];
                let bottom_dist = frag_xy[1] - in.inner_bottom_right[1];
                dist = max(max(right_dist, left_dist), max(bottom_dist, top_dist));
            }

            let stroke_radius = scaled_radius + scaled_stroke_width / 2.0;
            let outer_factor = 1.0 - smoothstep(stroke_radius - buffer, stroke_radius + buffer, dist);

            let inner_radius = scaled_radius - scaled_stroke_width / 2.0;
            let inner_factor = 1.0 - smoothstep(inner_radius - buffer, inner_radius + buffer, dist);

            var mixed_color: vec4<f32>;

            if (fill[3] == 0.0) {
                mixed_color = stroke;
                mixed_color[3] *= outer_factor * (1.0 - inner_factor);
            } else {
                mixed_color = mix(stroke, fill, inner_factor);
                mixed_color[3] *= outer_factor;
            }

            return mixed_color;
        } else {
            var dist: f32 = scaled_radius;
            if (in_top_left) {
                dist = distance(in.inner_top_left, frag_xy);
            } else if (in_bottom_right) {
                dist = distance(in.inner_bottom_right, frag_xy);
            } else if (in_bottom_left) {
                dist = distance(inner_bottom_left, frag_xy);
            } else if (in_top_right) {
                dist = distance(inner_top_right, frag_xy);
            } else {
                // skip anit-aliasing when not in a corner
                return fill;
            }

            let alpha_factor = 1.0 - smoothstep(scaled_radius - buffer, scaled_radius + buffer, dist);
            var color: vec4<f32> = fill;
            color[3] *= alpha_factor;
            return color;
        }
    } else {
        // no rounded corners
        if (scaled_stroke_width > 0.0) {
            // has stroke
            let in_left_stroke = in.clip_position[0] - in.outer_top_left[0] < scaled_stroke_width;
            let in_right_stroke = in.outer_bottom_right[0] - in.clip_position[0]  < scaled_stroke_width;
            let in_top_stroke = in.clip_position[1] - in.outer_top_left[1] < scaled_stroke_width;
            let in_bottom_stroke = in.outer_bottom_right[1] - in.clip_position[1] < scaled_stroke_width;

            let in_stroke = in_left_stroke || in_right_stroke || in_bottom_stroke || in_top_stroke;
            if (in_stroke) {
                return stroke;
            } else {
                return fill;
            }
        } else {
            // no stroke
            return fill;
        }
    }
}