teksilo-render 0.9.1

wgpu renderer for Teksilo — rect, SDF and path pipelines with a glyph atlas.
Documentation
// SPDX-License-Identifier: MPL-2.0
// SPDX-FileCopyrightText: 2026 FernTech

// Shader-driven animated quads — sprite-atlas kinds.
//
// Same vertex shape and uniform buffer as anim_procedural.wgsl, with
// an additional bind group (group 1) carrying the sprite atlas
// texture + sampler. The fragment shader decodes the current frame
// index from `AnimParams.phase`, maps `uv` to the atlas cell, and
// samples.

const MAX_ANIM_SLOTS: u32 = 128u;

// Must match `teksilo_canvas::AnimParams` (shared with anim_procedural).
struct AnimParams {
    kind: u32,
    phase: f32,
    sweep_ratio: f32,
    _pad0: f32,
    color0: vec4<f32>,
    color1: vec4<f32>,
    atlas_cols: f32,
    atlas_rows: f32,
    _pad1: vec2<f32>,
};

struct AnimUniforms {
    params: array<AnimParams, 128>,
};

@group(0) @binding(0) var<uniform> anim: AnimUniforms;
@group(1) @binding(0) var atlas_texture: texture_2d<f32>;
@group(1) @binding(1) var atlas_sampler: sampler;

struct VertexInput {
    @location(0) position: vec2<f32>,
    @location(1) uv: vec2<f32>,
    @location(2) slot: u32,
};

struct VertexOutput {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) uv: vec2<f32>,
    @location(1) @interpolate(flat) slot: u32,
};

@vertex
fn vs_main(in: VertexInput) -> VertexOutput {
    var out: VertexOutput;
    out.clip_position = vec4<f32>(in.position, 0.0, 1.0);
    out.uv = in.uv;
    out.slot = in.slot;
    return out;
}

@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
    let p = anim.params[in.slot];
    // Sprite path: `phase` encodes the integer frame index as f32
    // (wrote by `AnimatedQuadRegistry::compute_params`). Map uv to
    // the frame's cell in the atlas grid.
    let frame_idx = u32(p.phase);
    let cols = u32(max(p.atlas_cols, 1.0));
    let rows = u32(max(p.atlas_rows, 1.0));
    let clamped = min(frame_idx, cols * rows - 1u);
    let col = clamped % cols;
    let row = clamped / cols;
    let cell_uv = vec2<f32>(
        (f32(col) + in.uv.x) / f32(cols),
        (f32(row) + in.uv.y) / f32(rows),
    );
    let sampled = textureSample(atlas_texture, atlas_sampler, cell_uv);
    // `color1` is the tint: alpha=0 means no tint, leave sprite as-is;
    // alpha>0 means multiply sprite RGBA by the tint (premultiplied
    // against the tint's alpha for opacity).
    if (p.color1.a > 0.0) {
        return vec4<f32>(
            sampled.rgb * p.color1.rgb,
            sampled.a * p.color1.a,
        );
    }
    return sampled;
}