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
// bevy_pf_vector — instanced vector geometry with analytic edge AA.
//
// Vertices are pre-tessellated path meshes in local space, carrying an
// outward silhouette normal and a coverage value. Fringe vertices
// (coverage 0) are displaced exactly one screen pixel outward in the vertex
// shader, so antialiasing is resolution- and zoom-independent while the
// geometry stays static. Interior vertices have zero normal — no movement.
//
// This removes the need for MSAA: interiors render on the opaque early-z
// pipeline, fringes alpha-blend, edges resolve analytically.
//
// Instances are 36 bytes: 2x2 affine + translation + depth + RGBA8 color.

struct VectorView {
    clip_from_world: mat4x4<f32>,
    // (origin.xy, size.zw) in physical pixels.
    viewport: vec4<f32>,
}

@group(0) @binding(0) var<uniform> view: VectorView;
struct ClipEntry {
    inv_linear: vec4<f32>,
    inv_translation: vec2<f32>,
    half_extents: vec2<f32>,
    radius: f32,
    _pad0: f32,
    _pad1: f32,
    _pad2: f32,
}

// Raw vec4 view: reading through the typed struct misreads on this naga
// version, so entries are reconstructed from 3 vec4s per 48-byte entry.
@group(0) @binding(1) var<storage, read> clips_raw: array<vec4<f32>>;
@group(0) @binding(2) var gradient_tex: texture_2d<f32>;
@group(0) @binding(3) var gradient_sampler: sampler;

// Analytic nested clipping: multiply coverage by an antialiased
// rounded-rect/circle SDF per chain entry. AA width is derived from the
// view scale (no derivatives — safe in any control flow).
fn clip_one(world: vec2<f32>, e: u32) -> f32 {
    let v0 = clips_raw[e * 3u];
    let v1 = clips_raw[e * 3u + 1u];
    let v2 = clips_raw[e * 3u + 2u];
    let p = vec2<f32>(
        v0.x * world.x + v0.z * world.y + v1.x,
        v0.y * world.x + v0.w * world.y + v1.y,
    );
    let q = abs(p) - v1.zw;
    let d = length(max(q, vec2<f32>(0.0))) + min(max(q.x, q.y), 0.0) - v2.x;
    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 - d / aa, 0.0, 1.0);
}

// Straight-line nested-clip evaluation (a dynamic loop miscompiles on some
// naga/driver combinations); up to 4 chain entries.
fn clip_coverage(world: vec2<f32>, pack: u32) -> f32 {
    let count = pack & 7u;
    let index = pack >> 3u;
    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 {
    @location(0) position: vec2<f32>,
    @location(1) normal: vec2<f32>,
    @location(2) coverage: f32,
    // [m00, m01, m10, m11] — columns of the 2x2 linear part.
    @location(3) i_linear: vec4<f32>,
    // [tx, ty, z, unused]
    @location(4) i_translation_z: vec4<f32>,
    // Unorm8x4 — hardware-expanded to 0..1 floats for free.
    @location(5) i_color: vec4<f32>,
    // Gradient geometry in local space: linear = (start, end); radial =
    // (center, radius, _).
    @location(6) i_brush_params: vec4<f32>,
    // atlas_row * 4 + kind (0 solid / 1 linear / 2 radial).
    @location(7) i_brush_meta: f32,
}

struct VertexOut {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) color: vec4<f32>,
    @location(1) coverage: f32,
    @location(2) world_xy: vec2<f32>,
    @location(3) clip_pack_f: f32,
    @location(4) local_xy: vec2<f32>,
    @location(5) brush_params: vec4<f32>,
    @location(6) brush_meta: f32,
}

@vertex
fn vertex(in: VertexIn) -> VertexOut {
    var world_xy = vec2<f32>(
        in.i_linear.x * in.position.x + in.i_linear.z * in.position.y + in.i_translation_z.x,
        in.i_linear.y * in.position.x + in.i_linear.w * in.position.y + in.i_translation_z.y,
    );

    // Antialiasing band, straddling the true edge. Boundary vertices carry
    // the outward silhouette normal; the displacement is HALF a screen pixel
    // scaled by (0.5 - coverage), so the full-coverage vertex moves half a
    // pixel INWARD and its coverage-0 twin half a pixel OUTWARD. The ramp is
    // therefore centered on the authored edge: coverage 0.5 exactly on it,
    // and the shape's covered area is preserved.
    //
    // Extruding a full pixel outward without insetting (the naive version)
    // inflates every shape by a pixel and gives thin strokes about twice
    // their authored weight — visible immediately when stacking translucent
    // 1px strokes, which is exactly what HUD chrome does.
    //
    // The normal is rotated/scaled by the instance's linear part then
    // renormalized, so instance scale never changes band width.
    // clip_from_world[0][0] == 2 / world_width for an unrotated 2D camera,
    // so one pixel is 2 / (clip00 * viewport_width) world units.
    let world_normal = vec2<f32>(
        in.i_linear.x * in.normal.x + in.i_linear.z * in.normal.y,
        in.i_linear.y * in.normal.x + in.i_linear.w * in.normal.y,
    );
    let len = length(world_normal);
    if (len > 1.0e-6) {
        let px_world = 2.0 / (view.clip_from_world[0][0] * view.viewport.z);
        world_xy += (world_normal / len) * px_world * (0.5 - in.coverage);
    }

    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.coverage = in.coverage;
    out.world_xy = world_xy;
    out.clip_pack_f = in.i_translation_z.w;
    out.local_xy = in.position;
    out.brush_params = in.i_brush_params;
    out.brush_meta = in.i_brush_meta;
    return out;
}

@fragment
fn fragment(in: VertexOut) -> @location(0) vec4<f32> {
    var base = in.color;
    let bmeta = u32(in.brush_meta + 0.5);
    let kind = bmeta & 3u;
    if (kind != 0u) {
        var t = 0.0;
        if (kind == 1u) {
            let d = in.brush_params.zw - in.brush_params.xy;
            t = dot(in.local_xy - in.brush_params.xy, d) / max(dot(d, d), 1.0e-6);
        } else {
            t = length(in.local_xy - in.brush_params.xy) / max(in.brush_params.z, 1.0e-6);
        }
        let row = f32(bmeta >> 2u);
        let uv = vec2<f32>(
            (clamp(t, 0.0, 1.0) * 255.0 + 0.5) / 256.0,
            (row + 0.5) / 1024.0,
        );
        base = textureSampleLevel(gradient_tex, gradient_sampler, uv, 0.0) * in.color;
    }
    return vec4<f32>(base.rgb, base.a * in.coverage * clip_coverage(in.world_xy, u32(in.clip_pack_f + 0.5)));
}