latkit 0.2.1

Columnar data drawn as vertices, edges, lines, and areas on the GPU.
Documentation
// A shot's panels composed into the bytes a consumer takes, so no pixel is touched on the CPU: RGBA
// rows with straight alpha for an image, or NV12 planes (BT.709, limited range) for an encoder. One
// dispatch per panel writes its panel of the picture in whole words; a fill writes the background
// where no panel draws.

import package::lib::types::Compose;

@group(2) @binding(0) var panel: texture_2d<f32>;
@group(2) @binding(1) var<storage, read_write> bytes: array<u32>;
@group(2) @binding(2) var<uniform> compose: Compose;

// BT.709 luma weights of gamma-encoded color, and the scales of its two chroma differences.
const LUMA: vec3f = vec3f(0.2126, 0.7152, 0.0722);
const BLUE: f32 = 1.8556;
const RED: f32 = 1.5748;

// A panel's premultiplied color as the picture keeps it: straight with its alpha in a transparent
// image, else over black.
fn kept(c: vec4f) -> vec4f {
    if compose.alpha == 0u {
        return vec4f(c.rgb, 1.0);
    }
    if c.a <= 0.0 {
        return vec4f(0.0);
    }
    return vec4f(c.rgb / c.a, c.a);
}

// Writes the 4×2 block of NV12 at `at` from its eight colors, rows of four.
fn write_block(at: vec2u, rgb: array<vec3f, 8>) {
    var chroma = vec4f(0.0);
    for (var row = 0u; row < 2u; row++) {
        var luma = vec4f(0.0);
        for (var column = 0u; column < 4u; column++) {
            let c = rgb[row * 4u + column];
            let y = dot(c, LUMA);
            luma[column] = y;
            // Cb and Cr of each 2×2, averaged.
            let pair = column / 2u * 2u;
            chroma[pair] += (c.b - y) / BLUE * 0.25;
            chroma[pair + 1u] += (c.r - y) / RED * 0.25;
        }
        bytes[((at.y + row) * compose.picture.x + at.x) / 4u] = pack4x8unorm((16.0 + 219.0 * luma) / 255.0);
    }
    let words = compose.chroma + (at.y / 2u * compose.picture.x + at.x) / 4u;
    bytes[words] = pack4x8unorm((128.0 + 224.0 * chroma) / 255.0);
}

// One pixel of a panel as an RGBA word.
@compute @workgroup_size(16, 16)
fn rgba(@builtin(global_invocation_id) id: vec3u) {
    if any(id.xy >= compose.size) {
        return;
    }
    let at = compose.origin + id.xy;
    bytes[at.y * compose.picture.x + at.x] = pack4x8unorm(kept(textureLoad(panel, id.xy, 0)));
}

// One pixel of the background as an RGBA word.
@compute @workgroup_size(16, 16)
fn fill_rgba(@builtin(global_invocation_id) id: vec3u) {
    if any(id.xy >= compose.picture) {
        return;
    }
    bytes[id.y * compose.picture.x + id.x] = pack4x8unorm(compose.background);
}

// One 4×2 block of a panel as NV12: two words of luma and one of chroma.
@compute @workgroup_size(16, 16)
fn nv12(@builtin(global_invocation_id) id: vec3u) {
    let block = id.xy * vec2u(4u, 2u);
    if any(block >= compose.size) {
        return;
    }
    var rgb: array<vec3f, 8>;
    for (var i = 0u; i < 8u; i++) {
        rgb[i] = textureLoad(panel, block + vec2u(i % 4u, i / 4u), 0).rgb;
    }
    write_block(compose.origin + block, rgb);
}

// One 4×2 block of the background as NV12.
@compute @workgroup_size(16, 16)
fn fill_nv12(@builtin(global_invocation_id) id: vec3u) {
    let block = id.xy * vec2u(4u, 2u);
    if any(block >= compose.picture) {
        return;
    }
    let c = compose.background.rgb * compose.background.a;
    write_block(block, array<vec3f, 8>(c, c, c, c, c, c, c, c));
}