Area-weight pyramid compose to remove interim zoom banding (#153)#217
Conversation
The tile-pyramid compose path served interim density frames with a visible vertical beat when zooming a dense cloud. compose picks the coarsest level that still meets the render resolution, so the window holds 1-2 source cells per output bin; assigning each source cell whole to the bin under its center then handed adjacent bins 1 vs 2 cells apiece, a moire against the output grid. Area-weight each source cell across the bins its extent overlaps instead. A snap tolerance collapses near-aligned edges to one bin, so cell-aligned windows stay bit-exact against bin_2d.
…nosis-b59345 # Conflicts: # spec/design/lod-architecture.md # src/tiles.rs
Merging this PR will degrade performance by 39.54%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ❌ | test_pyramid_compose |
5.9 ms | 9.8 ms | -39.54% |
Tip
Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.
Comparing claude/issue-153-diagnosis-b59345 (ca3b04f) with main (ad29127)
Footnotes
-
1 benchmark was skipped, so the baseline result was used instead. If it was deleted from the codebase, click here and archive it to remove it from the performance reports. ↩
Greptile SummaryThis PR removes tile-pyramid zoom banding with area-weighted resampling. The main changes are:
Confidence Score: 4/5Mixed-axis composition can still skip area weighting on the downsampled axis.
src/tiles.rs
What T-Rex did
Important Files Changed
Reviews (3): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile |
Re: the CodSpeed
|
| version | wall-clock |
|---|---|
main (nearest) baseline |
0.919 ms |
| one-pass 2D area-weight scatter (first cut) | 2.22 ms |
| separable pull-based (current HEAD, e73487b) | 0.950 ms |
e73487b makes the area-weighted compose essentially free relative to the nearest-neighbor baseline it "regressed" from (@alekpetuskey measured 0.53 ms vs 0.63 ms on his machine — faster than baseline). All 10 tiles tests pass and the uniform-field column deviation is 0.0 (banding gone).
Recommendation: acknowledge on CodSpeed. Instruction count is genuinely higher by design; there's no way to make area weighting as cheap as nearest without reintroducing the banding, and on-hardware latency is unchanged.
e73487b to
ca3b04f
Compare
Fixes #153.
Diagnosis
The vertical banding is a resampling beat (moiré) artifact in the tile-pyramid
composepath — visible in the interim density frames served while zooming a dense point cloud.The client requests a density grid at ~screen resolution (
w×h), andcomposepicks the coarsest pyramid level that still meets that resolution. By construction that level packs betweenwand2wsource cells across the window (source→output ratio in[1, 2)). The old code assigned each source cell whole to the single output bin under its center:At a ratio like 1.33, adjacent output bins receive 1 vs 2 source cells apiece — a ~2:1 brightness beat that renders as the regular vertical stripes in the issue screenshot (dominant on X because the canvas is wider than tall, so the X ratio lands in the beat zone).
Fix
Replace the center-only assignment with area-weighted resampling (a box downsample): each source cell splits its count across the output bins its extent overlaps, in proportion to overlap. A uniform field now composes flat instead of beating. A new
axis_weightshelper precomputes the per-axis split (hoisted, socomposestays O(visible cells)).Exactness preserved: weights within
COMPOSE_SNAP_EPS(1e-6) of a bin edge collapse to a single bin, so cell-aligned windows (ratio exactly 1) remain bit-exact againstbin_2d— the documented contract and existing tests hold.Verification
tilesRust tests pass, including the two exact-alignment tests and conservation.compose_unaligned_ratio_has_no_banding— confirmed it fails under the old nearest-neighbor logic (column = 128 vs mean 85.3) and passes with the fix.cargo test(103 tests), release build, ABI smoke (121 checks), clippy, fmt all clean.Files changed
src/tiles.rs— area-weightedcompose+axis_weightshelper + regression test + module doc.spec/design/lod-architecture.md— disclosed the resampling method (§28 "no silent decisions").