roxlap-gpu 0.13.0

GPU compute-shader renderer for the roxlap voxel engine (WGPU + WGSL DDA marcher). Sibling to roxlap-core's CPU opticast.
Documentation
// roxlap-gpu line.wgsl — depth-tested 3D debug lines (L3.2).
//
// Editor gizmos / debug overlays (bounding boxes, floor grids, axes)
// drawn over the marched frame. Geometry is expanded to screen-space
// quads CPU-side (`build_line_vertices`); each vertex carries:
//   - NDC position (the quad corner),
//   - the euclidean scene-depth of its source endpoint (= the marcher's
//     `best_t` metric: world distance from the camera),
//   - an alpha-blended colour, and
//   - a per-vertex depth-test flag.
//
// The fragment shader compares the interpolated euclidean depth against
// the scene-DDA depth buffer (`best_t`, smaller = closer; `T_INF` for
// sky) so terrain in front occludes a line behind it. `depth_test = 0`
// lines (e.g. a hover highlight) skip the test and always draw on top.
// Alpha blending is the pipeline's job (src-alpha over the loaded frame).

struct Params {
    screen_w: u32,
    screen_h: u32,
    depth_bias: f32,
    // 1 = no scene depth buffer bound (sprite-only / empty scene) →
    // skip the test so the dummy 1-word buffer is never indexed.
    no_depth: u32,
    // 1 = viewport flip on. The depth buffer is stored unflipped (the blit
    // mirrors at read time), but our vertices carry the flipped NDC X, so
    // the fragment must mirror its lookup back to the unflipped column.
    flip_x: u32,
};

@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read> depth_buf: array<u32>;

struct VsIn {
    @location(0) pos: vec2<f32>,
    @location(1) depth: f32,
    @location(2) depth_test: f32,
    @location(3) color: vec4<f32>,
};

struct VsOut {
    @builtin(position) clip: vec4<f32>,
    @location(0) color: vec4<f32>,
    @location(1) depth: f32,
    @location(2) depth_test: f32,
};

@vertex
fn vs_main(in: VsIn) -> VsOut {
    var o: VsOut;
    o.clip = vec4<f32>(in.pos, 0.0, 1.0);
    o.color = in.color;
    o.depth = in.depth;
    o.depth_test = in.depth_test;
    return o;
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    if (in.depth_test > 0.5 && params.no_depth == 0u) {
        // `clip` is the framebuffer pixel coordinate in the fragment
        // stage (origin top-left) — matches the depth buffer's row-major
        // `gid.y * screen_w + gid.x` layout.
        var px = u32(in.clip.x);
        let py = u32(in.clip.y);
        if (px < params.screen_w && py < params.screen_h) {
            // Mirror back to the unflipped column the marcher wrote.
            if (params.flip_x != 0u) {
                px = params.screen_w - 1u - px;
            }
            let scene_t = bitcast<f32>(depth_buf[py * params.screen_w + px]);
            if (in.depth > scene_t + params.depth_bias) {
                discard;
            }
        }
    }
    return in.color;
}