oxiui-render-wgpu 0.1.0

wgpu GPU render surface for OxiUI
Documentation
// Gradient fill shader for the headless wgpu backend.
//
// Supports linear and radial gradient fills over a rectangular region.
// Gradient geometry is provided as screen-space quads; the actual colour ramp
// is defined in a per-draw GradientUniforms uniform buffer.
//
// Uniform layout (GradientUniforms, 288 bytes):
//   offset   0 : p0            (vec2<f32>) — linear start / radial centre
//   offset   8 : p1            (vec2<f32>) — linear end / (unused)
//   offset  16 : radius        (f32)       — radial outer radius (0 for linear)
//   offset  20 : gradient_type (u32)       — 0=linear, 1=radial
//   offset  24 : stop_count    (u32)       — number of active stops (1–8)
//   offset  28 : _pad          (u32)
//   offset  32 : stop_offsets  (8 × vec4<f32>) — x = offset in [0,1]
//   offset 160 : stop_colors   (8 × vec4<f32>) — rgba in [0,1]

// Vertex input.
struct VsIn {
    @location(0) position: vec2<f32>,
    @location(1) local:    vec2<f32>,
}

struct VsOut {
    @builtin(position) clip_position: vec4<f32>,
    @location(0) local: vec2<f32>,
}

struct Globals {
    viewport: vec2<f32>,
    _pad:     vec2<f32>,
}

struct GradientUniforms {
    p0:            vec2<f32>,
    p1:            vec2<f32>,
    radius:        f32,
    gradient_type: u32,
    stop_count:    u32,
    _pad:          u32,
    stop_offsets:  array<vec4<f32>, 8>,
    stop_colors:   array<vec4<f32>, 8>,
}

@group(0) @binding(0) var<uniform> globals:  Globals;
@group(0) @binding(1) var<uniform> gradient: GradientUniforms;

fn pixel_to_ndc(p: vec2<f32>) -> vec2<f32> {
    return vec2<f32>(
        p.x / globals.viewport.x * 2.0 - 1.0,
        1.0 - p.y / globals.viewport.y * 2.0,
    );
}

@vertex
fn vs_main(in: VsIn) -> VsOut {
    var out: VsOut;
    out.clip_position = vec4<f32>(pixel_to_ndc(in.position), 0.0, 1.0);
    out.local         = in.local;
    return out;
}

// ── Gradient colour sampling ─────────────────────────────────────────────────

// Interpolate gradient colour at normalised position t in [0, 1].
fn sample_gradient(t: f32) -> vec4<f32> {
    let tc = clamp(t, 0.0, 1.0);
    let n  = i32(gradient.stop_count);

    // Single-stop degenerate case.
    if n <= 1 { return gradient.stop_colors[0]; }

    // Find the two surrounding stops.
    for (var i = 0; i < 7; i++) {
        let a_off = gradient.stop_offsets[i].x;
        let b_off = gradient.stop_offsets[i + 1].x;
        if tc <= b_off || i == n - 2 {
            if tc <= a_off { return gradient.stop_colors[i]; }
            let seg = b_off - a_off;
            var frac: f32;
            if seg < 1e-6 {
                frac = 0.0;
            } else {
                frac = (tc - a_off) / seg;
            }
            return mix(gradient.stop_colors[i], gradient.stop_colors[i + 1], frac);
        }
    }

    // Past the last stop.
    return gradient.stop_colors[n - 1];
}

// ── Fragment shader ───────────────────────────────────────────────────────────

@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
    var t: f32;

    if gradient.gradient_type == 0u {
        // Linear gradient: project local onto the gradient axis.
        let axis = gradient.p1 - gradient.p0;
        let len2 = dot(axis, axis);
        if len2 < 1e-12 {
            t = 0.0;
        } else {
            t = dot(in.local - gradient.p0, axis) / len2;
        }
    } else {
        // Radial gradient: distance from centre divided by radius.
        let d = length(in.local - gradient.p0);
        if gradient.radius < 1e-6 {
            t = 1.0;
        } else {
            t = d / gradient.radius;
        }
    }

    return sample_gradient(t);
}