darkly 0.5.0

A GPU-native paint engine on wgpu: brushes, layers, blend modes, masks, selections, and undo.
Documentation
// Final blit from accumulator to surface with view transform.

struct VertexOutput {
    @builtin(position) position: vec4f,
    @location(0) uv: vec2f,
}

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

struct ViewTransform {
    row0: vec4f,
    row1: vec4f,
    row2: vec4f,
    bg: vec4f,
    // flags.x = pixel filter mode (0=linear, 1=nearest, 2=auto)
    flags: vec4f,
}

@group(0) @binding(0) var t_source: texture_2d<f32>;
@group(0) @binding(1) var t_sampler: sampler;
@group(0) @binding(2) var<uniform> view: ViewTransform;

@fragment fn fs_present(in: VertexOutput) -> @location(0) vec4f {
    // Transform screen pixel -> window-local canvas pixel using the inverse view matrix
    let screen_pos = in.position.xy;
    let canvas_x = view.row0.x * screen_pos.x + view.row1.x * screen_pos.y + view.row2.x;
    let canvas_y = view.row0.y * screen_pos.x + view.row1.y * screen_pos.y + view.row2.y;

    // Sample using the padded texture size so texels map 1:1 to window-local canvas pixels.
    let tex_dims = vec2f(textureDimensions(t_source));
    let uv = vec2f(canvas_x, canvas_y) / tex_dims;
    let clamped_uv = clamp(uv, vec2f(0.0), vec2f(1.0));

    // Pixel filter selection:
    //   mode 0 = linear: sample as-is.
    //   mode 1 = nearest: snap UV to texel center so the bound linear
    //           sampler returns the unfiltered texel value.
    //   mode 2 = auto:   nearest when zoomed in past 1:1, otherwise linear.
    //
    // The inverse view matrix scales screen→canvas by 1/zoom, so the
    // magnitude of `row0.xy` (which equals `inv_zoom * (cos, sin)`) is
    // `inv_zoom`. inv_zoom < 1 means zoom > 1 (zoomed in).
    let mode = u32(view.flags.x + 0.5);
    let inv_zoom = length(vec2f(view.row0.x, view.row0.y));
    let use_nearest = mode == 1u || (mode == 2u && inv_zoom < 1.0);
    let snapped_uv = (floor(clamped_uv * tex_dims) + vec2f(0.5)) / tex_dims;
    let sample_uv = select(clamped_uv, snapped_uv, use_nearest);
    let color = textureSample(t_source, t_sampler, sample_uv);

    // OOB check uses actual canvas dimensions (unpadded) so the tile
    // padding area shows as workspace background, not black.
    let canvas_dims = vec2f(view.row0.z, view.row1.z);
    let oob = canvas_x < 0.0 || canvas_x > canvas_dims.x
           || canvas_y < 0.0 || canvas_y > canvas_dims.y;

    // Composite the canvas over a screen-space checker so any transparency
    // in the final composite reads as transparency, not as darkened-by-
    // discarded-alpha. The composite cache is straight-alpha (composite.wgsl's
    // Porter-Duff divides rgb by out_a), so the source-over here multiplies
    // rgb by alpha rather than treating it as premultiplied. Gray values
    // match the layer-panel thumbnails (102/255, 153/255).
    let cell = floor(screen_pos / 8.0);
    let parity = (i32(cell.x) + i32(cell.y)) & 1;
    let checker = vec3f(select(0.6, 0.4, parity == 0));
    let composed = color.rgb * color.a + checker * (1.0 - color.a);
    return select(vec4f(composed, 1.0), view.bg, oob);
}