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>,
    @location(1) normal: vec2<f32>,
    @location(2) kind: u32,
    @location(3) shape_index: u32,
};

struct InstanceInput {
    @location(4) position: vec2<f32>,
    @location(5) fill_color: vec4<f32>,
    @location(6) stroke_color: vec4<f32>,
    @location(7) stroke_width: f32,
    @location(8) size: f32,
    @location(9) angle: f32,
    @location(10) shape_index: u32,
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,

    // if 1.0, draw the fragment, otherwise discard
    @location(0) draw_shape: f32,

    // If 1.0, the vertex is part of the bounding box of a circle symbol
    // otherwise, vertex is part of a geometric (path-based) symbol
    @location(1) is_circle: f32,

    // Color of vertex when drawing geometric symbol based on a path
    @location(2) geom_color: vec4<f32>,

    // Position and size of the shape
    @location(3) center: vec2<f32>,
    @location(4) radius: f32,

    // Properties of circle symbol. Circles are drawn in the fragment shader,
    // so more info must be passed through
    @location(5) circle_fill_color: vec4<f32>,
    @location(6) circle_stroke_color: vec4<f32>,
    @location(7) circle_stroke_width: f32,
};

const PI = 3.14159265359;

@vertex
fn vs_main(
    model: VertexInput,
    instance: InstanceInput,
) -> VertexOutput {
    var out: VertexOutput;

    if (instance.shape_index != model.shape_index) {
        out.draw_shape = 0.0;
        return out;
    } else {
        out.draw_shape = 1.0;
    }

    let size_scale = sqrt(instance.size);

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

    // Compute scenegraph x and y coordinates
    let angle_rad = PI * instance.angle / 180.0;
    let rot = mat2x2(cos(angle_rad), -sin(angle_rad), sin(angle_rad), cos(angle_rad));
    let rotated_pos = rot * model.position;
    let sg_x = rotated_pos[0] * size_scale + position[0];
    let sg_y = rotated_pos[1] * size_scale + (chart_uniforms.size[1] - position[1]);
    let pos = vec2(sg_x, sg_y);

    if (model.kind == 0u) {
        // fill vertex
        out.geom_color = instance.fill_color;

        let normalized_pos = 2.0 * pos / chart_uniforms.size - 1.0;
        out.clip_position = vec4<f32>(normalized_pos, 0.0, 1.0);
        out.is_circle = 0.0;
    } else if (model.kind == 1u) {
        // stroke vertex
        out.geom_color = instance.stroke_color;

        // Compute scaled stroke width.
        // The 0.1 here is the width that lyon used to compute the stroke tesselation
        let scaled_stroke_width = 0.1 * size_scale;

        // Adjust vertex along normal to achieve desired line width
        // The factor of 2.0 here is because the normal vector that lyon
        // returns has length such that moving all stroke vertices by the length
        // of the "normal" vector will increase the line width by 2.
        let normal = rot * model.normal;
        var diff = scaled_stroke_width - instance.stroke_width;
        let adjusted_pos = pos - diff * normal / 2.0;

        let normalized_pos = 2.0 * adjusted_pos / chart_uniforms.size - 1.0;
        out.clip_position = vec4<f32>(normalized_pos, 0.0, 1.0);
        out.is_circle = 0.0;
    } else if (model.kind == 2u) {
        // circle symbol. Circles are drawn in the fragment shader, so
        // we compute the center and radius and pass through the stroke and
        // fill specifications.
        out.is_circle = 1.0;

        // Pass through colors and stroke_width
        out.circle_fill_color = instance.fill_color;
        out.circle_stroke_color = instance.stroke_color;
        out.circle_stroke_width = instance.stroke_width;

        // Compute normalized position
        let normalized_pos = 2.0 * pos / chart_uniforms.size - 1.0;
        out.clip_position = vec4<f32>(normalized_pos, 0.0, 1.0);
    }

    // Compute circle center in fragment shader coordinates
    out.center = vec2<f32>(
        position[0] * chart_uniforms.scale,
        position[1] * chart_uniforms.scale,
    );

    // Compute radius in fragment shader coordinates
    out.radius = size_scale * chart_uniforms.scale / 2.0;

    return out;
}

// Fragment shader
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    if (in.draw_shape != 1.0) {
        discard;
    }

    let top_left = in.center - in.radius;
    let bottom_right = in.center + in.radius;
    if (in.is_circle == 1.0) {
        // Draw anti-aliased circle
        let circle_stroke_color = lookup_color(
            in.circle_stroke_color, in.clip_position, top_left, bottom_right
        );
        let circle_fill_color = lookup_color(
            in.circle_fill_color, in.clip_position, top_left, bottom_right
        );

        let buffer = 0.5 * chart_uniforms.scale;
        let dist = length(in.center - vec2<f32>(in.clip_position[0], in.clip_position[1]));

        if (in.circle_stroke_width > 0.0) {
            let inner_radius = in.radius - in.circle_stroke_width * chart_uniforms.scale / 2.0;
            let outer_radius = in.radius + in.circle_stroke_width * chart_uniforms.scale / 2.0;
            if (dist > outer_radius + buffer * 2.0) {
                discard;
            } else {
                let outer_factor = 1.0 - smoothstep(outer_radius - buffer, outer_radius + buffer, dist);
                let inner_factor = 1.0 - smoothstep(inner_radius - buffer, inner_radius + buffer, dist);
                var mixed_color: vec4<f32>;
                if (circle_fill_color[3] == 0.0) {
                    // No fill, so use opacity to fade out stroke rather than interpolate color
                    mixed_color = circle_stroke_color;
                    mixed_color[3] *= outer_factor * (1.0 - inner_factor);
                } else {
                    // Has fill, interpolate opacity outside of circle and interpolate color inside
                    mixed_color = mix(circle_stroke_color, circle_fill_color, inner_factor);
                    mixed_color[3] *= outer_factor;
                }

                return mixed_color;
            }
        } else {
            let alpha_factor = 1.0 - smoothstep(in.radius - buffer, in.radius + buffer, dist);
            var mixed_color: vec4<f32> = circle_fill_color;
            mixed_color[3] *= alpha_factor;
            if (dist > in.radius + buffer) {
                discard;
            } else {
                return mixed_color;
            }
        }
    } else {
        return lookup_color(
           in.geom_color, in.clip_position, top_left, bottom_right
        );
    }
}