From a full data-transport audit at b0f8780. Three related client costs that one design change removes:
- Per-frame re-tonemap + full texture re-upload during exposure animation —
lodStepNorm (js/src/45_lod.ts:111-132) calls lodWriteGridTexture (45_lod.ts:50-70) every animation frame: an O(w*h) log1p loop over the retained f32 grid plus a full R8 texImage2D upload (~1-2 ms per frame for a plot-sized grid, x2 when crossfading). The stored texel is 255*log1p(c)/log1p(norm), so a norm change is a pure scalar rescale — animate a u_normScale uniform in DENSITY_FS (js/src/40_gl.ts:386) against a texture baked once.
- log-u8 wire grid decoded u8→f32 then re-encoded f32→u8 for upload —
lodDecodeLogU8 (45_lod.ts:32-42), consumed at 45_lod.ts:497-515 / 50_chartview.ts:2049-2062. At apply time normMax === d.max, so the wire u8 bytes could go straight into texImage2D: two avoidable full-grid copies + 4x transient memory per density update (fires per zoom gesture).
- CPU f32 grid retained per cached density window —
lodRememberDensity (45_lod.ts:198-220, maxCached=8) keeps grid (wh4 B) only to re-encode on norm change: ~13 MB per density trace at cap. With shader-side normalization, drop the CPU grid from cached entries entirely.
Investigate: confirm the rescale identity holds across the crossfade path and pyramid-composed grids; check standalone kernel-less re-bin path uses the same upload helper.
From a full data-transport audit at b0f8780. Three related client costs that one design change removes:
lodStepNorm(js/src/45_lod.ts:111-132) callslodWriteGridTexture(45_lod.ts:50-70) every animation frame: an O(w*h)log1ploop over the retained f32 grid plus a full R8texImage2Dupload (~1-2 ms per frame for a plot-sized grid, x2 when crossfading). The stored texel is255*log1p(c)/log1p(norm), so a norm change is a pure scalar rescale — animate au_normScaleuniform inDENSITY_FS(js/src/40_gl.ts:386) against a texture baked once.lodDecodeLogU8(45_lod.ts:32-42), consumed at45_lod.ts:497-515/50_chartview.ts:2049-2062. At apply timenormMax === d.max, so the wire u8 bytes could go straight intotexImage2D: two avoidable full-grid copies + 4x transient memory per density update (fires per zoom gesture).lodRememberDensity(45_lod.ts:198-220, maxCached=8) keepsgrid(wh4 B) only to re-encode on norm change: ~13 MB per density trace at cap. With shader-side normalization, drop the CPU grid from cached entries entirely.Investigate: confirm the rescale identity holds across the crossfade path and pyramid-composed grids; check standalone kernel-less re-bin path uses the same upload helper.