// **MSDF glyph rendering** (feature `wgpu`) — crisp text/labels at any zoom from a
// multi-channel signed-distance-field atlas. Instanced quads (one per glyph) sample
// the atlas; the fragment takes the **median** of the RGB distance (the MSDF trick
// that preserves sharp corners a single-channel SDF rounds off) and converts it to
// coverage via the per-glyph **screen pixel range**, so the edge stays ~1px wide no
// matter how far the label is zoomed.
struct MsdfU {
// x,y = viewport size px ; z = (bool) gamma flag ; w unused
vp: vec4<f32>,
};
@group(0) @binding(0) var<uniform> U: MsdfU;
@group(1) @binding(0) var atlas_tex: texture_2d<f32>;
@group(1) @binding(1) var atlas_smp: sampler;
struct VOut {
@builtin(position) clip: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) color: vec4<f32>,
@location(2) px_range: f32,
};
struct GlyphIn {
@location(0) rect_min: vec2<f32>,
@location(1) rect_max: vec2<f32>,
@location(2) uv_min: vec2<f32>,
@location(3) uv_max: vec2<f32>,
@location(4) color: vec4<f32>,
@location(5) px_range: f32,
};
@vertex
fn msdf_vs(@builtin(vertex_index) vi: u32, g: GlyphIn) -> VOut {
var corners = array<vec2<f32>, 6>(
vec2<f32>(0.0, 0.0), vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0),
vec2<f32>(0.0, 1.0), vec2<f32>(1.0, 0.0), vec2<f32>(1.0, 1.0),
);
let c = corners[vi];
let px = mix(g.rect_min, g.rect_max, c);
let uv = mix(g.uv_min, g.uv_max, c);
// pixel rect → NDC (y down in pixel space)
let ndc = vec2<f32>(px.x / U.vp.x * 2.0 - 1.0, 1.0 - px.y / U.vp.y * 2.0);
var out: VOut;
out.clip = vec4<f32>(ndc, 0.0, 1.0);
out.uv = uv;
out.color = g.color;
out.px_range = g.px_range;
return out;
}
fn median3(a: f32, b: f32, c: f32) -> f32 {
return max(min(a, b), min(max(a, b), c));
}
@fragment
fn msdf_fs(in: VOut) -> @location(0) vec4<f32> {
let s = textureSample(atlas_tex, atlas_smp, in.uv).rgb;
let sd = median3(s.r, s.g, s.b) - 0.5;
// Coverage over ~1px regardless of zoom — the crispness guarantee.
let screen_px_dist = in.px_range * sd;
let alpha = clamp(screen_px_dist + 0.5, 0.0, 1.0);
// Premultiplied source-over output.
return vec4<f32>(in.color.rgb * (alpha * in.color.a), alpha * in.color.a);
}