backdrop-blur-wgpu 0.1.0

wgpu backend for backdrop-blur: a safe, WGSL implementation of the frosted-glass seam (separable Gaussian now; dual-Kawase to follow).
Documentation
// Dual-Kawase (dual-filter) upsample — the second half of the production blur (KWin, picom;
// Bjørge/ARM SIGGRAPH 2015). One pass doubles resolution. 8 taps: 4 cardinals×1 (at ±2·halfpixel)
// + 4 diagonals×2 (at ±halfpixel), ÷12 (energy-preserving). Linear scratch only.
//
// `halfpixel = 0.5 / size(sampled texture)` — the smaller level being read this pass (KWin's
// convention). Matches the downsample so the round trip is symmetric. The per-pass spread is fixed
// at one half-texel (no continuous `offset` scalar), so radius is log2-quantized — see the
// downsample header and `cache::resolve_kawase_levels`.

struct KawaseParams {
    halfpixel: vec2<f32>,
    _pad: vec2<f32>,
};

@group(0) @binding(0) var src: texture_2d<f32>;
@group(0) @binding(1) var samp: sampler;
@group(0) @binding(2) var<uniform> params: KawaseParams;

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

@vertex
fn vs_main(@builtin(vertex_index) vi: u32) -> VsOut {
    var out: VsOut;
    let x = f32((vi << 1u) & 2u);
    let y = f32(vi & 2u);
    out.uv = vec2<f32>(x, y);
    out.pos = vec4<f32>(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0);
    return out;
}

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    let uv = in.uv;
    let hp = params.halfpixel;
    var sum = vec4<f32>(0.0);
    // 4 cardinals, weight 1, at ±2·halfpixel.
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(-hp.x * 2.0, 0.0), 0.0);
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(hp.x * 2.0, 0.0), 0.0);
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(0.0, -hp.y * 2.0), 0.0);
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(0.0, hp.y * 2.0), 0.0);
    // 4 diagonals, weight 2, at ±halfpixel.
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(-hp.x, -hp.y), 0.0) * 2.0;
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(hp.x, -hp.y), 0.0) * 2.0;
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(-hp.x, hp.y), 0.0) * 2.0;
    sum += textureSampleLevel(src, samp, uv + vec2<f32>(hp.x, hp.y), 0.0) * 2.0;
    return sum / 12.0;
}