fidget-wgpu 0.5.0

WGPU backend for Fidget
Documentation
struct MergeConfig {
    /// Image size, in pixels
    image_size: vec2u,

    /// Whether or not to denoise when merging (bool)
    denoise: u32,

    /// Offset applied to indices when merging
    index_base: u32,

    /// Number of valid image buffers (0-7)
    image_count: u32,

    // Explicit padding to the nearest multiple of 8
    _pad: u32,
}

@group(0) @binding(0) var<uniform> config: MergeConfig;

@group(0) @binding(1) var<storage, read> image0: array<GeometryPixel>;
@group(0) @binding(2) var<storage, read> image1: array<GeometryPixel>;
@group(0) @binding(3) var<storage, read> image2: array<GeometryPixel>;
@group(0) @binding(4) var<storage, read> image3: array<GeometryPixel>;
@group(0) @binding(5) var<storage, read> image4: array<GeometryPixel>;
@group(0) @binding(6) var<storage, read> image5: array<GeometryPixel>;
@group(0) @binding(7) var<storage, read> image6: array<GeometryPixel>;
@group(0) @binding(8) var<storage, read_write> out: array<PackedVoxel>;


@compute @workgroup_size(8, 8)
fn merge_main(
    @builtin(global_invocation_id) global_id: vec3u
) {
    // Clamp to image size
    if global_id.x >= config.image_size.x ||
       global_id.y >= config.image_size.y ||
       config.image_count == 0
    {
        return;
    }

    let pos = global_id.xy;
    let i = config.image_size.x * pos.y + pos.x;

    var p = PackedVoxel(0, 0); // dummy value
    let b = pack_at(0, pos);
    if config.index_base == 0 {
        p = b;
    } else {
        p = merge_pixel(out[i], b);
    }

    if config.image_count > 1 {
        p = merge_pixel(p, pack_at(1, pos));
    }
    if config.image_count > 2 {
        p = merge_pixel(p, pack_at(2, pos));
    }
    if config.image_count > 3 {
        p = merge_pixel(p, pack_at(3, pos));
    }
    if config.image_count > 4 {
        p = merge_pixel(p, pack_at(4, pos));
    }
    if config.image_count > 5 {
        p = merge_pixel(p, pack_at(5, pos));
    }
    if config.image_count > 6 {
        p = merge_pixel(p, pack_at(6, pos));
    }

    out[i] = p;
}

fn pack_at(image_index: u32, pos: vec2u) -> PackedVoxel {
    let i = config.image_size.x * pos.y + pos.x;
    let p = maybe_denoise(image_index, pos);
    return pack(TaggedGeometryPixel(p, image_index + config.index_base));
}

fn maybe_denoise(image_index: u32, pos: vec2u) -> GeometryPixel {
    let pixel = read_pixel(image_index, pos.x + pos.y * config.image_size.x);
    if config.denoise != 0 {
        if pixel.depth > 0 {
            if pixel.normal.z > 0.0 {
                return pixel;
            } else {
                let normal = denoise_at(image_index, pos, pixel);
                return GeometryPixel(normal, pixel.depth);
            }
        } else {
            return GeometryPixel(
                vec3f(0.0, 0.0, 0.0),
                0,
            );
        }
    } else {
        return pixel;
    }
}

fn denoise_at(image_index: u32, pos: vec2u, pixel: GeometryPixel) -> vec3f {
    let empty = GeometryPixel(vec3f(0.0), 0);
    var data = array<array<GeometryPixel, 3>, 3>(
        array<GeometryPixel, 3>(empty, empty, empty),
        array<GeometryPixel, 3>(empty, pixel, empty),
        array<GeometryPixel, 3>(empty, empty, empty),
    );
    // Populate a 3x3 grid of normals.
    for (var i = -1; i <= 1; i += 1) {
        for (var j = -1; j <= 1; j += 1) {
            let new_pos = vec2i(pos) + vec2i(i, j);
            if (i == 0 && j == 0) ||
                new_pos.x < 0 ||
                new_pos.y < 0 ||
                u32(new_pos.x) >= config.image_size.x ||
                u32(new_pos.y) >= config.image_size.y
            {
                continue;
            }
            data[i + 1][j + 1] = read_pixel(
                image_index,
                u32(new_pos.x) + u32(new_pos.y) * config.image_size.x
            );
        }
    }

    // Iterate over four 2x2 pixel regions, picking the one that's most
    // consistent (most normals agree with mean)
    var scores = array<vec4f, 4>(
        vec4f(0.0),
        vec4f(0.0),
        vec4f(0.0),
        vec4f(0.0),
    );
    for (var i = -1; i <= 0; i += 1) {
        for (var j = -1; j <= 0; j += 1) {
            var sum = vec3f(0.0);
            var count = 0;
            for (var dx = 0; dx <= 1; dx += 1) {
                for (var dy = 0; dy <= 1; dy += 1) {
                    let p = data[i + 1 + dx][j + 1 + dy];
                    if p.depth != 0 && p.normal.z > 0.0 {
                        sum += data[i + 1 + dx][j + 1 + dy].normal;
                        count += 1;
                    }
                }
            }
            if count == 0 {
                continue; // leave score as 0
            }
            var score = 0.0;
            let mean = sum / f32(count);
            for (var dx = 0; dx <= 1; dx += 1) {
                for (var dy = 0; dy <= 1; dy += 1) {
                    if data[i + 1 + dx][j + 1 + dy].depth != 0 {
                        score += dot(mean, data[i + 1 + dx][j + 1 + dy].normal);
                    }
                }
            }
            scores[(i + 1) + (j + 1) * 2] = vec4f(mean, score);
        }
    }

    var best = scores[0];
    for (var i = 0; i < 3; i += 1) {
        if scores[i].w > best.w {
            best = scores[i];
        }
    }
    // Preserve the back-facing normal if we didn't get any valid quadrants
    if best.w == 0.0 {
        return pixel.normal;
    }
    return best.xyz;
}

fn read_pixel(image_index: u32, pixel_index: u32) -> GeometryPixel {
    switch image_index {
        case 0: { return image0[pixel_index]; }
        case 1: { return image1[pixel_index]; }
        case 2: { return image2[pixel_index]; }
        case 3: { return image3[pixel_index]; }
        case 4: { return image4[pixel_index]; }
        case 5: { return image5[pixel_index]; }
        case 6: { return image6[pixel_index]; }
        default: { return GeometryPixel(vec3f(0.0), 0); }
    }
}

fn merge_pixel(a: PackedVoxel, b: PackedVoxel) -> PackedVoxel {
    if a.depth >= b.depth {
        return a;
    } else {
        return b;
    }
}