mirage-engine 0.1.1

Mirage, an immediate-mode 3D engine for simple games on desktop and the browser
Documentation
// What a post effect's code is passed and what it may read. Two seams:
// one for an effect's own values and code, one for the scene depth
// binding, whose
// type follows the sample count. The depth resolve is drawn from this
// shader with the first seam filled by a `draw` that returns the pixel it
// was passed.

struct Pixel {
    // What the chain holds at this pixel.
    color: vec4<f32>,
    // Across the frame, zero at its top left.
    uv: vec2<f32>,
    // Physical pixels, the center of each at a half.
    position: vec2<f32>,
    // The frame, in physical pixels.
    size: vec2<f32>,
}

// The view the frame was drawn under: its size in physical pixels, the
// near and far clip in meters, and whether the lens foreshortens.
struct Frame {
    size: vec2<f32>,
    near: f32,
    far: f32,
    // 1 where the lens foreshortens, 0 otherwise.
    perspective: u32,
}

const FORESHORTENED: u32 = 1u;

@group(1) @binding(0) var source: texture_2d<f32>;
@group(1) @binding(1) var source_sampler: sampler;
@group(1) @binding(2) var resolved: texture_2d<f32>;

// mirage-engine:depth
@group(2) @binding(1) var<uniform> frame: Frame;

struct Fragment {
    @builtin(position) position: vec4<f32>,
    @location(0) uv: vec2<f32>,
}

// One triangle large enough to cover the target, so no vertex buffer is bound.
@vertex
fn fullscreen(@builtin(vertex_index) index: u32) -> Fragment {
    let corner = vec2<f32>(f32((index << 1u) & 2u), f32(index & 2u));

    var fragment: Fragment;
    fragment.position = vec4<f32>(corner * 2.0 - 1.0, 0.0, 1.0);
    fragment.uv = vec2<f32>(corner.x, 1.0 - corner.y);
    return fragment;
}

// What the chain holds anywhere, held at the edges past the frame.
fn color_at(uv: vec2<f32>) -> vec4<f32> {
    return textureSample(source, source_sampler, uv);
}

// The view depth in meters the scene was drawn at, held at the edges past
// the frame, and the far clip where nothing was drawn.
fn depth_at(uv: vec2<f32>) -> f32 {
    let held = clamp(uv * frame.size, vec2<f32>(0.0), frame.size - 1.0);
    return textureLoad(resolved, vec2<i32>(held), 0).r;
}

// mirage-engine:effect

// The seam's own `draw` over every pixel of the frame. Named so that it
// cannot meet `effect`, which is what a post effect's values are bound as.
@fragment
fn effect_fragment(fragment: Fragment) -> @location(0) vec4<f32> {
    var pixel: Pixel;
    pixel.color = textureSample(source, source_sampler, fragment.uv);
    pixel.uv = fragment.uv;
    pixel.position = fragment.position.xy;
    pixel.size = frame.size;
    return draw(pixel);
}

// The scene's own depth, taken to one sample per pixel and to meters along
// the view.
@fragment
fn resolve(fragment: Fragment) -> @location(0) vec4<f32> {
    let at = vec2<i32>(fragment.position.xy);
    let written = textureLoad(scene, at, 0);
    let span = frame.far - frame.near;

    var meters = frame.near + written * span;
    if frame.perspective == FORESHORTENED {
        meters = frame.near * frame.far / (frame.far - written * span);
    }
    return vec4<f32>(meters, 0.0, 0.0, 1.0);
}