darkly 0.5.0

A GPU-native paint engine on wgpu: brushes, layers, blend modes, masks, selections, and undo.
Documentation
// Orthogonal (90°-multiple) transform of a texture: exact pixel permutation.
//
// Flips and 90° rotations relabel texels without resampling, so every fetch is
// a `textureLoad` at an integer source index — no sampler, no filtering, no
// premultiply. Output is bit-identical to the input up to the permutation,
// which is the whole point (cf. rescale.wgsl, which *does* resample).
//
// Two entry points share the same inverse index map:
//   fs_remap         — whole-surface transform; dest may be a rotated (swapped)
//                      size. dest texel ← src[inv_map(dest)]. Used by canvas
//                      flip/rotate (per node) and the selection mask.
//   fs_mirror_masked — in-place H/V mirror of a region about its own centre,
//                      gated by an R8 mask: selected texels take the mirror,
//                      unselected texels pass through unchanged. Used by layer/
//                      selection flip so a non-rectangular selection clips
//                      correctly. (Mirror only — dest size == src size.)
//
// xform codes (match the Rust `OrthoXform` discriminants):
//   0 FlipH   1 FlipV   2 Rot180   3 Rot90Cw   4 Rot90Ccw

struct Params {
    // [xform, src_w, src_h, has_mask]
    p: vec4u,
};

@group(0) @binding(0) var t_src: texture_2d<f32>;
@group(0) @binding(1) var<uniform> u: Params;

struct VsOut { @builtin(position) pos: vec4f };

@vertex
fn vs_main(@builtin(vertex_index) idx: u32) -> VsOut {
    // Fullscreen triangle.
    let uv = vec2f(f32((idx << 1u) & 2u), f32(idx & 2u));
    var out: VsOut;
    out.pos = vec4f(uv * 2.0 - 1.0, 0.0, 1.0);
    return out;
}

// Inverse map: given a destination texel index and the source dimensions,
// return the source texel index that lands on it. Derived by inverting the
// forward local-coordinate map in `OrthoXform::map_local`.
fn inv_map(dst: vec2i, sw: i32, sh: i32) -> vec2i {
    let xform = u.p.x;
    switch xform {
        case 0u: { return vec2i(sw - 1 - dst.x, dst.y); }          // FlipH
        case 1u: { return vec2i(dst.x, sh - 1 - dst.y); }          // FlipV
        case 2u: { return vec2i(sw - 1 - dst.x, sh - 1 - dst.y); } // Rot180
        case 3u: { return vec2i(dst.y, sh - 1 - dst.x); }          // Rot90Cw  (dest is sh×sw)
        default: { return vec2i(sw - 1 - dst.y, dst.x); }          // Rot90Ccw (dest is sh×sw)
    }
}

@fragment
fn fs_remap(in: VsOut) -> @location(0) vec4f {
    let sw = i32(u.p.y);
    let sh = i32(u.p.z);
    let dst = vec2i(floor(in.pos.xy));
    return textureLoad(t_src, inv_map(dst, sw, sh), 0);
}

@group(0) @binding(2) var t_mask: texture_2d<f32>;

@fragment
fn fs_mirror_masked(in: VsOut) -> @location(0) vec4f {
    let sw = i32(u.p.y);
    let sh = i32(u.p.z);
    let dst = vec2i(floor(in.pos.xy));
    // fs_mirror_masked is only invoked with FlipH/FlipV, so dest size == src.
    let mirror = inv_map(dst, sw, sh);
    let selected = textureLoad(t_mask, dst, 0).r > 0.5;
    let src = select(dst, mirror, selected);
    return textureLoad(t_src, src, 0);
}