// GPU frustum-cull + LOD filter + stream-compaction.
//
// One invocation per way. Visible ways' vertices are claimed atomically
// from compact[] and copied from geom[]. The caller resets draw_count to 0
// (by writing [0, 1, 0, 0] to the indirect_buf) before dispatching.
//
// Vertex layout: 6 × u32 = 24 bytes (pos.xy as f32 bits, col.rgba as f32 bits).
// Using raw u32 arrays avoids WGSL struct-alignment padding (vec4 needs align 16).
struct WayMeta {
bbox_min: vec2<f32>, // offset 0
bbox_max: vec2<f32>, // offset 8
vert_start: u32, // offset 16 — index into geom[]
vert_count: u32, // offset 20
lod: u32, // offset 24 (0=country 1=region 2=city)
_pad: u32, // offset 28
} // stride 32
struct ViewportCull {
min: vec2<f32>, // offset 0
max: vec2<f32>, // offset 8
lod: u32, // offset 16 — show ways with lod <= this
_p0: u32, // offset 20
_p1: u32, // offset 24
_p2: u32, // offset 28
} // size 32
@group(0) @binding(0) var<storage, read> way_meta: array<WayMeta>;
@group(0) @binding(1) var<storage, read> geom: array<u32>;
@group(0) @binding(2) var<storage, read_write> compact: array<u32>;
@group(0) @binding(3) var<storage, read_write> draw_count: atomic<u32>;
@group(0) @binding(4) var<uniform> vp: ViewportCull;
const VPV: u32 = 6u; // u32 words per vertex
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3<u32>) {
let i = id.x;
if i >= arrayLength(&way_meta) { return; }
let m = way_meta[i];
// Frustum cull — reject if bbox completely outside viewport
if m.bbox_max.x < vp.min.x || m.bbox_min.x > vp.max.x { return; }
if m.bbox_max.y < vp.min.y || m.bbox_min.y > vp.max.y { return; }
// Cumulative LOD: country ways visible at all zoom levels, city only at high zoom
if m.lod > vp.lod { return; }
// Atomically claim vert_count vertex slots in compact[]
let dst = atomicAdd(&draw_count, m.vert_count);
let cap = arrayLength(&compact) / VPV;
if dst + m.vert_count > cap { return; }
// Copy 24-byte vertices word-by-word (avoids struct-alignment issues)
for (var v = 0u; v < m.vert_count; v++) {
let s = (m.vert_start + v) * VPV;
let d = (dst + v) * VPV;
compact[d ] = geom[s ];
compact[d + 1] = geom[s + 1];
compact[d + 2] = geom[s + 2];
compact[d + 3] = geom[s + 3];
compact[d + 4] = geom[s + 4];
compact[d + 5] = geom[s + 5];
}
}