bevy_pf_vector 0.1.1

GPU vector rendering for bevy_pf: paths tessellated once at asset load and redrawn as instanced geometry, for the mostly-static topology of game HUD content.
Documentation
// Signed-distance primitives: rect, rounded rect, circle and line, all from
// ONE quad per instance and one shader.
//
// Why this exists alongside the tessellated path: tessellation is keyed by
// geometry CONTENT, so a shape whose SIZE changes every frame mints new
// geometry every frame and re-tessellates. That is fine for HUD chrome whose
// dimensions are fixed and whose transform/colour animate; it is pathological
// for bars, meters and blips that resize continuously. Here `size` is an
// INSTANCE parameter, so resizing costs one instance write and zero
// tessellation.
//
// One primitive covers four shapes because a rounded rect degenerates:
//   radius == 0            -> rect
//   radius == min(size)/2  -> circle/capsule
//   thin size.y + rotation -> line with round caps
// Fill and stroke are the same code path: a band test on the distance, with
// `thickness <= 0` meaning filled. Technique follows bevy_vector_shapes'
// shapes/rect.wgsl (read, not guessed) — see CLAUDE.md.

struct VectorView {
    clip_from_world: mat4x4<f32>,
    // (origin.xy, size.zw) — width is .z, NOT .x. See CLAUDE.md.
    viewport: vec4<f32>,
}

@group(0) @binding(0) var<uniform> view: VectorView;
@group(0) @binding(1) var<storage, read> clips_raw: array<vec4<f32>>;

fn clip_one(world: vec2<f32>, index: u32) -> f32 {
    let base = index * 3u;
    let v0 = clips_raw[base];
    let v1 = clips_raw[base + 1u];
    let v2 = clips_raw[base + 2u];
    let local = vec2<f32>(
        v0.x * world.x + v0.z * world.y + v1.x,
        v0.y * world.x + v0.w * world.y + v1.y,
    );
    let half_extents = vec2<f32>(v1.z, v1.w);
    let radius = v2.x;
    let q = abs(local) - half_extents;
    let dist = length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - radius;
    let px_world = 2.0 / (view.clip_from_world[0][0] * view.viewport.z);
    let aa = max(px_world * length(v0.xy), 1.0e-5);
    return clamp(0.5 - dist / aa, 0.0, 1.0);
}

fn clip_coverage(world: vec2<f32>, pack: u32) -> f32 {
    let count = pack % 8u;
    let index = pack / 8u;
    var cov = 1.0;
    if (count > 0u) { cov = cov * clip_one(world, index); }
    if (count > 1u) { cov = cov * clip_one(world, index + 1u); }
    if (count > 2u) { cov = cov * clip_one(world, index + 2u); }
    if (count > 3u) { cov = cov * clip_one(world, index + 3u); }
    return cov;
}

struct VertexIn {
    // Unit quad corner in [-1, 1].
    @location(0) position: vec2<f32>,
    @location(1) unused_normal: vec2<f32>,
    @location(2) unused_coverage: f32,
    @location(3) i_linear: vec4<f32>,
    @location(4) i_translation_z: vec4<f32>,
    @location(5) i_color: vec4<f32>,
    // [size.x, size.y, corner_radius, thickness]
    @location(6) i_params: vec4<f32>,
}

struct VertexOut {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color: vec4<f32>,
    // Position within the shape, in local units, for the distance field.
    @location(1) local_xy: vec2<f32>,
    @location(2) world_xy: vec2<f32>,
    @location(3) clip_pack_f: f32,
    @location(4) half_size: vec2<f32>,
    @location(5) radius: f32,
    @location(6) thickness: f32,
}

@vertex
fn vertex(in: VertexIn) -> VertexOut {
    let size = in.i_params.xy;
    let half_size = size * 0.5;
    // Corner radius cannot exceed half the shortest side, or the SDF inverts.
    let radius = clamp(in.i_params.z, 0.0, min(half_size.x, half_size.y));
    let thickness = in.i_params.w;

    // Pad the quad so the antialiased edge — and, for a stroke, the outer
    // half of the band — has somewhere to live. Two pixels matches the
    // reference and covers the derivative footprint at any scale.
    let px_world = 2.0 / (view.clip_from_world[0][0] * view.viewport.z);
    let pad = px_world * 2.0;

    let local = in.position * (half_size + vec2<f32>(pad));
    let world_xy = vec2<f32>(
        in.i_linear.x * local.x + in.i_linear.z * local.y + in.i_translation_z.x,
        in.i_linear.y * local.x + in.i_linear.w * local.y + in.i_translation_z.y,
    );

    var out: VertexOut;
    out.clip_position =
        view.clip_from_world * vec4<f32>(world_xy, in.i_translation_z.z, 1.0);
    out.color = in.i_color;
    out.local_xy = local;
    out.world_xy = world_xy;
    out.clip_pack_f = in.i_translation_z.w;
    out.half_size = half_size;
    out.radius = radius;
    out.thickness = thickness;
    return out;
}

/// Distance from `p` to a box of half-extents `b`. Negative inside.
fn box_sdf(p: vec2<f32>, b: vec2<f32>) -> f32 {
    let q = abs(p) - b;
    return length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0);
}

@fragment
fn fragment(in: VertexOut) -> @location(0) vec4<f32> {
    // Rounded rect: shrink the box by the radius, then subtract it back.
    let dist = box_sdf(in.local_xy, in.half_size - vec2<f32>(in.radius)) - in.radius;

    // Antialias from the SCREEN-SPACE derivative of the distance. This is why
    // the primitive is resolution- and scale-independent without any geometry
    // work: the pixel footprint is measured per fragment rather than baked
    // into an extruded fringe.
    let aa = max(fwidth(dist), 1.0e-5);

    // Outer edge for both fill and stroke.
    var coverage = 1.0 - smoothstep(-aa, aa, dist);
    if (in.thickness > 0.0) {
        // Stroke: knock out everything further inside than `thickness`, so
        // the surviving band straddles the authored edge.
        coverage = coverage * smoothstep(-aa, aa, dist + in.thickness);
    }

    let alpha = in.color.a * coverage
        * clip_coverage(in.world_xy, u32(in.clip_pack_f + 0.5));
    if (alpha < 0.0001) {
        discard;
    }
    return vec4<f32>(in.color.rgb, alpha);
}