facett-graphview 0.1.12

facett — a vello-backed, domain-agnostic scalable 2D graph render engine. Runtime-selects vello (GPU/wgpu) when a usable GPU exists, vello_cpu (multithreaded SIMD) as the no-GPU fallback. The eventual home for every graph surface (dep/arch/release dashboards, korp, graph-DB browsing).
Documentation
// **GPU-compute force-directed layout** (feature `gpu`) — the VRAM n-body layout.
// One compute entry `cs_layout` advances every node one Fruchterman-Reingold step,
// reading `src`, writing `dst` (ping-pong, no read/write hazard). It MIRRORS
// `gpu_layout::step_cpu` line-for-line (same loop order, same math) so the CPU
// fallback and the GPU path are the same deterministic function (FC-7 parity).
//
//   node  : a = (pos.x, pos.y, vel.x, vel.y),  b = (weight, _, _, _)
//   forces: inverse-square repulsion over ALL nodes (O(n²)) + Hooke springs along
//           the streamed CSR neighbours + centring gravity, velocity-integrated
//           with mass = weight (heavy nodes move slower).

struct LNode {
    a: vec4<f32>,
    b: vec4<f32>,
};

struct U {
    // x = n, y = repulsion, z = attraction, w = ideal_len
    a: vec4<f32>,
    // x = gravity, y = damping, z = dt, w = min_dist
    b: vec4<f32>,
};

@group(0) @binding(0) var<uniform> P: U;
@group(0) @binding(1) var<storage, read> src: array<LNode>;
@group(0) @binding(2) var<storage, read_write> dst: array<LNode>;
@group(0) @binding(3) var<storage, read> offsets: array<u32>;
@group(0) @binding(4) var<storage, read> targets: array<u32>;

@compute @workgroup_size(64)
fn cs_layout(@builtin(global_invocation_id) gid: vec3<u32>) {
    let i = gid.x;
    let n = u32(P.a.x);
    if (i >= n) { return; }

    let rep = P.a.y;
    let attr = P.a.z;
    let ideal = P.a.w;
    let grav = P.b.x;
    let damp = P.b.y;
    let dt = P.b.z;
    let mind = P.b.w;
    let min2 = mind * mind;

    let me = src[i];
    let pos_i = me.a.xy;
    let wi = me.b.x;
    var force = vec2<f32>(0.0, 0.0);

    // ── all-pairs inverse-square repulsion ──
    for (var j = 0u; j < n; j = j + 1u) {
        if (j == i) { continue; }
        let oj = src[j];
        let d = pos_i - oj.a.xy;
        let dist2 = max(dot(d, d), min2);
        force = force + d * (rep * wi * oj.b.x / dist2);
    }

    // ── spring attraction along CSR neighbours ──
    let start = offsets[i];
    let end = offsets[i + 1u];
    for (var k = start; k < end; k = k + 1u) {
        let t = targets[k];
        let d = src[t].a.xy - pos_i;
        let dist = max(length(d), 1e-4);
        force = force + d * (attr * (dist - ideal) / dist);
    }

    // ── centring gravity ──
    force = force - pos_i * grav;

    // ── integrate (mass = wi) ──
    let inv_m = 1.0 / max(wi, 1e-4);
    let vel = (me.a.zw + force * inv_m * dt) * damp;
    let pos = pos_i + vel * dt;

    dst[i].a = vec4<f32>(pos.x, pos.y, vel.x, vel.y);
    dst[i].b = me.b;
}