// Line-draw shader for OSM ways.
// Vertices are in **origin-local** Mercator space: `pos = mercator_f64(lonlat) −
// origin`, where `origin` is the dataset's Mercator bbox-min computed once in f64
// on the CPU. Baking the vertices relative to a data-local origin keeps every
// coordinate the VS touches SMALL, so the projection no longer suffers f32
// catastrophic cancellation at high zoom (the old `mercator*zoom + offset` added
// two ~4e7 f32 whose true pixel was their tiny difference → multi-pixel drift).
//
// The transform mirrors the CPU `to_px` (subtract-before-scale):
// screen = (pos − ref) * zoom + screen_center
// with `ref = camera_center − origin` (small) and `screen_center` the pixel the
// camera centre lands on. `zoom` is per-axis so the GPU line lane matches a
// non-uniform CPU fit exactly.
//
// `px_to_ndc` comes from the shared prelude `common.wgsl`, prepended by
// `render::gpu::wgsl()`. Do not re-declare it here.
struct Uniforms {
zoom_x: f32,
zoom_y: f32,
screen_center_x: f32,
screen_center_y: f32,
ref_x: f32,
ref_y: f32,
viewport_w: f32,
viewport_h: f32,
// The 2D ground frame (bearing rotation + cos(tilt) foreshorten) — see
// `DrawUniforms` and `project_ground_px` in the shared prelude. Identity is
// rot = (1,0,0,1), foreshorten = 1.
rot: vec4<f32>,
foreshorten: f32,
// Frame-uniform RGB multiplier on the vertex colour (alpha untouched).
// 1.0 changes nothing; the map's fill draw passes the pitched-horizon fade here
// so the baked colours can stay static in VRAM under a moving tilt slider.
tint: f32,
_pad0: f32,
_pad1: f32,
}
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VertexOut {
@builtin(position) pos: vec4<f32>,
@location(0) col: vec4<f32>,
}
@vertex
fn vs_main(
@location(0) pos: vec2<f32>,
@location(1) color: vec4<f32>,
) -> VertexOut {
// Origin-local Mercator → screen pixel (logical), through the shared prelude's
// ONE projection writer: subtract-before-scale (so the (pos − ref) difference is
// formed on two SMALL f32 and stays precise at huge zoom), then the ground frame,
// then the pane centre.
let s = project_ground_px(
pos,
vec2<f32>(u.ref_x, u.ref_y),
vec2<f32>(u.zoom_x, u.zoom_y),
u.rot,
u.foreshorten,
vec2<f32>(u.screen_center_x, u.screen_center_y),
);
// Screen pixel → clip space [-1, 1]; y is flipped (screen y=0 is top).
let ndc = px_to_ndc(s, vec2<f32>(u.viewport_w, u.viewport_h));
return VertexOut(vec4<f32>(ndc, 0.0, 1.0), color);
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// `tint` is 1.0 for every stream but the map's fills, where it carries the
// tilt fade. Alpha is deliberately untouched — the fade darkens, it does not
// dissolve, which is exactly what the CPU painter's `layer::fade` does.
return vec4<f32>(in.col.rgb * u.tint, in.col.a);
}