vtsampler 0.1.1

Cross-platform GPU video format conversion and scaling (wgpu compute)
Documentation
{# Conversion + scale compute shader (generated per format pair). #}
{% for i in range(input_plane_count) %}
@group(0) @binding({{ i }}) var input_{{ i }}: texture_2d<f32>;
{% endfor %}
{% for i in range(output_plane_count) %}
@group(0) @binding({{ input_plane_count + i }}) var output_{{ i }}: texture_storage_2d<{{ output_storage_formats[i] }}8unorm, write>;
{% endfor %}

{% if need_scale %}
{% if scale_filter == "bilinear" %}
fn sample_bilinear(tex: texture_2d<f32>, coord: vec2<f32>, size: vec2<u32>) -> vec4<f32> {
    let c = clamp(coord, vec2<f32>(0.0), vec2<f32>(size) - vec2<f32>(1.0));
    let tl = textureLoad(tex, vec2<i32>(c), 0);
    let tr = textureLoad(tex, vec2<i32>(c + vec2<f32>(1.0, 0.0)), 0);
    let bl = textureLoad(tex, vec2<i32>(c + vec2<f32>(0.0, 1.0)), 0);
    let br = textureLoad(tex, vec2<i32>(c + vec2<f32>(1.0, 1.0)), 0);
    let f = fract(c);
    return mix(mix(tl, tr, f.x), mix(bl, br, f.x), f.y);
}
{% endif %}
fn map_coords(coords: vec2<i32>, input_size: vec2<u32>, output_size: vec2<u32>) -> vec2<i32> {
{% if scale_filter == "bilinear" %}
    let fc = vec2<f32>(coords) * (vec2<f32>(input_size) / vec2<f32>(output_size));
    return vec2<i32>(fc);
{% else %}
    return vec2<i32>(
        clamp(i32(round(f32(coords.x) * (f32(input_size.x) / f32(output_size.x)))), 0, i32(input_size.x) - 1),
        clamp(i32(round(f32(coords.y) * (f32(input_size.y) / f32(output_size.y)))), 0, i32(input_size.y) - 1)
    );
{% endif %}
}
{% endif %}

@compute @workgroup_size(16, 16)
fn main(@builtin(global_invocation_id) position: vec3<u32>) {
    let max_position = textureDimensions(output_0);
    if (position.x >= max_position.x || position.y >= max_position.y) {
        return;
    }

    let coords = vec2<i32>(position.xy);

    {% include "sample_input.wgsl.jinja" %}
    {% include "color_convert.wgsl.jinja" %}
    {% include "store_output.wgsl.jinja" %}
}