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
// Scene blit — presents the compute pass's framebuffer STORAGE BUFFER
// (packed `rgba8unorm` per pixel, row stride = `dims.size.x`) into the
// swapchain via a fullscreen triangle. A buffer (not a storage texture)
// is used because Chrome's Dawn tiles write storage textures in a way
// the sampled read disagrees with; a linear buffer is unambiguous on
// every backend. Pixel-exact (nearest) by construction.

struct VsOut {
    @builtin(position) clip: vec4<f32>,
};

@vertex
fn vs_main(@builtin(vertex_index) vid: u32) -> VsOut {
    // Single triangle covering NDC [-1, 3]²; clipped to the viewport.
    let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;
    let y = 1.0 - f32(vid & 2u) * 2.0;
    var out: VsOut;
    out.clip = vec4<f32>(x, y, 0.0, 1.0);
    return out;
}

struct Dims {
    size: vec2<u32>,
    flip_x: u32,
    _pad: u32,
};

@group(0) @binding(0) var<storage, read> fb: array<u32>;
@group(0) @binding(1) var<uniform> dims: Dims;

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    // `in.clip.xy` is the framebuffer pixel centre (px + 0.5). The
    // compute pass wrote `fb[py * width + px]`, so read the same texel —
    // mirrored to `width-1-px` when the horizontal flip is on.
    var px = u32(in.clip.x);
    let py = u32(in.clip.y);
    if dims.flip_x != 0u {
        px = dims.size.x - 1u - px;
    }
    let idx = py * dims.size.x + px;
    return unpack4x8unorm(fb[idx]);
}