// L0 thick-AA line shader — instanced oriented quads, a capsule (stadium) SDF in
// the fragment shader. The coverage math matches `render/cpu/sdf.rs::line_coverage`
// so a GPU line matches the CPU raster.
const CAP_BUTT: u32 = 0u;
const CAP_ROUND: u32 = 1u;
struct Uniforms {
viewport: vec2<f32>,
_pad: vec2<f32>,
};
@group(0) @binding(0) var<uniform> U: Uniforms;
// One LineInstance (matches `prim::LineInstance`, 48 bytes).
struct Instance {
@location(0) a: vec2<f32>,
@location(1) b: vec2<f32>,
@location(2) half_width: f32,
@location(3) aa: f32,
@location(4) cap: u32,
@location(5) color: vec4<f32>,
};
struct VsOut {
@builtin(position) clip: vec4<f32>,
@location(0) p: vec2<f32>, // pixel position of this fragment
@location(1) @interpolate(flat) a: vec2<f32>,
@location(2) @interpolate(flat) b: vec2<f32>,
@location(3) @interpolate(flat) half_width: f32,
@location(4) @interpolate(flat) aa: f32,
@location(5) @interpolate(flat) cap: u32,
@location(6) color: vec4<f32>,
};
@vertex
fn vs_main(@builtin(vertex_index) vi: u32, inst: Instance) -> VsOut {
// Oriented quad around the segment, inflated by (half_width + aa) on each side
// and past each cap.
let dir_raw = inst.b - inst.a;
let len = max(length(dir_raw), 1e-6);
let dir = dir_raw / len;
let nrm = vec2<f32>(-dir.y, dir.x);
let r = inst.half_width + inst.aa;
// Corner selection: u along the axis (−r .. len+r), v across (−r .. r).
var us = array<f32, 6>(-1.0, 1.0, -1.0, 1.0, 1.0, -1.0);
var vs = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
let along = select(0.0, len, us[vi] > 0.0) + us[vi] * r;
let across = vs[vi] * r;
let px = inst.a + dir * along + nrm * across;
let ndc = vec2<f32>(px.x / U.viewport.x * 2.0 - 1.0, 1.0 - px.y / U.viewport.y * 2.0);
var out: VsOut;
out.clip = vec4<f32>(ndc, 0.0, 1.0);
out.p = px;
out.a = inst.a;
out.b = inst.b;
out.half_width = inst.half_width;
out.aa = inst.aa;
out.cap = inst.cap;
out.color = inst.color;
return out;
}
fn coverage_from_sd(d: f32, aa: f32) -> f32 {
if (aa <= 0.0) {
return select(0.0, 1.0, d <= 0.0);
}
return 1.0 - smoothstep(-aa, aa, d);
}
@fragment
fn fs_main(in: VsOut) -> @location(0) vec4<f32> {
let ab = in.b - in.a;
let len2 = max(dot(ab, ab), 1e-12);
let t = clamp(dot(in.p - in.a, ab) / len2, 0.0, 1.0);
let closest = in.a + ab * t;
let dist = length(in.p - closest);
var cov = coverage_from_sd(dist - in.half_width, in.aa);
if (in.cap == CAP_BUTT) {
let len = sqrt(len2);
let u = ab / len;
let s = dot(in.p - in.a, u);
let axis_sd = max(-s, s - len);
cov = cov * coverage_from_sd(axis_sd, in.aa);
}
if (cov <= 0.0) {
discard;
}
let a = in.color.a * cov;
return vec4<f32>(in.color.rgb * a, a);
}