viewport-lib 0.19.0

3D viewport rendering library
Documentation
// Reproject last frame's scene depth into this frame's camera, producing the
// depth that mip 0 of the HiZ pyramid is built from. The cull runs before this
// frame's scene pass, so there is no current depth yet; reprojecting the prior
// frame's depth is the cheapest source that tracks camera motion.
//
// Forward scatter: each source texel is unprojected to world space with the
// previous view-projection, reprojected with the current one, and its depth is
// written to the destination pixel with an atomic min (the nearest surface that
// lands on a pixel wins). The destination starts at the far value, so texels no
// source reaches (revealed this frame) stay far and read as "no occluder",
// which keeps the cull from dropping anything that just became visible.

struct ReprojUniform {
    // Inverse of the previous frame's view-projection (NDC -> world).
    inv_prev_vp: mat4x4<f32>,
    // Current frame's view-projection (world -> NDC).
    cur_vp:      mat4x4<f32>,
    dims:        vec2<u32>,
    _pad:        vec2<u32>,
}

@group(0) @binding(0) var<uniform>             u:          ReprojUniform;
@group(0) @binding(1) var                      prev_depth: texture_2d<f32>;
@group(0) @binding(2) var<storage, read_write> dst:        array<atomic<u32>>;

// Bit pattern of 1.0f. For depths in [0,1] the float bit pattern is monotonic,
// so atomicMin over the bits is atomicMin over the depth.
const FAR_BITS: u32 = 0x3f800000u;

// 2D dispatch (not 1D over w*h) so neither workgroup dimension exceeds the
// 65535 limit at large or supersampled resolutions.
@compute @workgroup_size(8, 8)
fn init(@builtin(global_invocation_id) gid: vec3<u32>) {
    let dim = u.dims;
    if gid.x >= dim.x || gid.y >= dim.y {
        return;
    }
    atomicStore(&dst[gid.y * dim.x + gid.x], FAR_BITS);
}

@compute @workgroup_size(8, 8)
fn scatter(@builtin(global_invocation_id) gid: vec3<u32>) {
    let dim = u.dims;
    if gid.x >= dim.x || gid.y >= dim.y {
        return;
    }

    let d = textureLoad(prev_depth, vec2<i32>(gid.xy), 0).r;
    // Background carries no occluder to reproject.
    if d >= 1.0 {
        return;
    }

    let dimf = vec2<f32>(dim);
    let uv = (vec2<f32>(gid.xy) + 0.5) / dimf;
    let ndc_prev = vec3<f32>(uv.x * 2.0 - 1.0, (1.0 - uv.y) * 2.0 - 1.0, d);

    let world_h = u.inv_prev_vp * vec4<f32>(ndc_prev, 1.0);
    if world_h.w == 0.0 {
        return;
    }
    let world = world_h.xyz / world_h.w;

    let clip = u.cur_vp * vec4<f32>(world, 1.0);
    if clip.w <= 0.0 {
        return;
    }
    let ndc = clip.xyz / clip.w;
    if ndc.x < -1.0 || ndc.x > 1.0 || ndc.y < -1.0 || ndc.y > 1.0 || ndc.z < 0.0 || ndc.z > 1.0 {
        return;
    }

    let du = vec2<f32>(ndc.x * 0.5 + 0.5, 1.0 - (ndc.y * 0.5 + 0.5));
    let px = vec2<u32>(du * dimf);
    let cx = min(px.x, dim.x - 1u);
    let cy = min(px.y, dim.y - 1u);
    let idx = cy * dim.x + cx;
    atomicMin(&dst[idx], bitcast<u32>(ndc.z));
}