repose-render-wgpu 0.25.2

Repose wgpu handler
Documentation
// CPU-computed Y′CbCr → R′G′B′ affine transform.
// Folds range expansion + matrix coefficients into a single M/b.
struct YuvTransform {
    row0: vec4<f32>, // m[0][0], m[0][1], m[0][2], 0
    row1: vec4<f32>, // m[1][0], m[1][1], m[1][2], 0
    row2: vec4<f32>, // m[2][0], m[2][1], m[2][2], 0
    b:    vec4<f32>, // b[0], b[1], b[2], 0
}

@group(1) @binding(0) var tex_y:  texture_2d<f32>;
@group(1) @binding(1) var tex_uv: texture_2d<f32>;
@group(1) @binding(2) var samp:   sampler;
@group(1) @binding(3) var<uniform> yuv_transform: YuvTransform;

struct VSOut {
    @builtin(position) pos: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) tint: vec4<f32>,
    @location(2) uv_x_offset: f32,
};

@vertex
fn vs_main(
    @location(0) xywh: vec4<f32>,
    @location(1) uv_rect: vec4<f32>,
    @location(2) tint: vec4<f32>,
    @location(3) uv_x_offset: f32,
    @location(4) sin_cos: vec2<f32>,
    @builtin(vertex_index) v: u32
) -> VSOut {
    var positions = array<vec2<f32>, 6>(
        vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
        vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
    );
    var uvs = array<vec2<f32>, 6>(
        vec2(0.0, 0.0), vec2(1.0, 0.0), vec2(1.0, 1.0),
        vec2(0.0, 0.0), vec2(1.0, 1.0), vec2(0.0, 1.0)
    );
    let p = positions[v];
    let uv_lerp = uvs[v];
    let half = 0.5 * xywh.zw;
    let corner = (p * 2.0 - 1.0) * half;
    let rotated = vec2(corner.x * sin_cos.x - corner.y * sin_cos.y, corner.x * sin_cos.y + corner.y * sin_cos.x);
    var o: VSOut;
    o.pos = vec4(xywh.xy + rotated, 0.0, 1.0);
    o.uv = mix(uv_rect.xy, uv_rect.zw, uv_lerp);
    o.tint = tint;
    o.uv_x_offset = uv_x_offset;
    return o;
}

fn apply_yuv(t: YuvTransform, yuv: vec3<f32>) -> vec3<f32> {
    return vec3(
        dot(t.row0.xyz, yuv) + t.b.x,
        dot(t.row1.xyz, yuv) + t.b.y,
        dot(t.row2.xyz, yuv) + t.b.z,
    );
}

fn srgb_eotf(c: f32) -> f32 {
    if c <= 0.04045 {
        return c / 12.92;
    }
    return pow((c + 0.055) / 1.055, 2.4);
}

fn catmull_rom_weights(t: f32) -> vec4<f32> {
    let t2 = t * t;
    let t3 = t2 * t;
    return vec4(
        -0.5 * t3 + t2 - 0.5 * t,
         1.5 * t3 - 2.5 * t2 + 1.0,
        -1.5 * t3 + 2.0 * t2 + 0.5 * t,
         0.5 * t3 - 0.5 * t2,
    );
}

fn sample_bicubic_uv(uv: vec2<f32>) -> vec2<f32> {
    let uv_size = vec2<f32>(textureDimensions(tex_uv));
    let coord = uv * uv_size - 0.5;
    let px = floor(coord);
    let f = coord - px;

    let wx = catmull_rom_weights(f.x);
    let wy = catmull_rom_weights(f.y);

    var result = vec4(0.0);
    for (var row = 0; row < 4; row++) {
        let wy_r = wy[row];
        for (var col = 0; col < 4; col++) {
            let p = vec2(f32(col) - 1.0, f32(row) - 1.0);
            let tap_uv = (px + p + 0.5) / uv_size;
            let s = textureSampleLevel(tex_uv, samp, tap_uv, 0.0);
            result += s * wx[col] * wy_r;
        }
    }
    return result.rg;
}

fn srgb_eotf_vec3(c: vec3<f32>) -> vec3<f32> {
    return vec3(srgb_eotf(c.r), srgb_eotf(c.g), srgb_eotf(c.b));
}

@fragment
fn fs_main(i: VSOut) -> @location(0) vec4<f32> {
    let uv_off = vec2(i.uv_x_offset, 0.0);
    let y = textureSample(tex_y, samp, i.uv).r;
    let uv = sample_bicubic_uv(i.uv + uv_off);
    let rgb_gamma = clamp(apply_yuv(yuv_transform, vec3(y, uv.r, uv.g)), vec3(0.0), vec3(1.0));
    let rgb_lin = srgb_eotf_vec3(rgb_gamma) * i.tint.rgb;
    let a = i.tint.a;
    return vec4(rgb_lin * a, a);
}