Repository navigation
Space gsplat sort keys by log distance so stray far splats keep near precision - #9899
Merged
Merged
Conversation
…near precision Sort keys were spread linearly over the distance range, in camera-weighted bins out to the farthest splat. A few stray splats far away stretched the range, so splats near the camera shared keys and drew out of order (superspl.at scenes 0bb9b075 and 0839ceea). Radial keys, and the depth keys of a perspective camera, now come from the bit pattern of the distance as a float, which follows log2: every doubling of distance gets the same number of keys. They start ten doublings inside the near clip, since the splats closer than it are drawn too, with their depth clamped. The GPU projector and the CPU worker compute them the same way. Orthographic and fisheye depth sorts keep the bins, since they can draw splats at or behind the camera. The CPU worker writes the distances as floats first and converts them in a second pass, and caps log keys at 16 bits so the counting sort's table stays in cache. Its depth keys give the splats behind the camera the last sixteenth of the keys, spaced the same way, so a shader that moves them into view finds them in the order of their signed depth (the CPU sort reads the centers before any modifier). Log depth keys are measured from the camera, so a directional CPU sort that uses them now re-sorts when the camera moves, not only when it turns. A switch of projection or sort mode re-sorts too, even with the camera still. And a sort the worker turns away while one is in flight is no longer dropped: the request stays until it is sent, and the camera pose is recorded only then. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mvaligursky
approved these changes
Nov 2, 2026
mvaligursky
left a comment
Contributor
There was a problem hiding this comment.
All checks out, and the example with cpu sorting don't see any objectionable sorting issue from 16bit key, so approving!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Gsplat sort keys were spread linearly over the distance range, in camera-weighted bins out to the farthest splat. A few stray splats far outside a scene stretch that range, so splats near the camera share keys and draw out of order. On superspl.at scene 0839ceea the CPU sort flickers across the whole scene. The GPU projector spaced its keys the same way.
Radial keys, and the depth keys of a perspective camera, now come from the bit pattern of the distance as a float, which follows log2. Every doubling of distance gets the same number of keys, so far splats cost no precision near the camera. Radial keys use the squared distance, and depth keys the depth in front of the camera. Keys start ten doublings inside the near clip, because splats closer than the near clip are still drawn, with their depth clamped. The GPU projector and the CPU worker compute the keys the same way. Orthographic and fisheye depth sorts keep the bins, since they can draw splats at or behind the camera.
Because log keys are spread over every doubling of distance, a few keys are not enough: a small scene far from the camera would have its splats share keys. So the GPU sort now takes at least 16 bits, where scenes under about 23k splats had 12. Larger scenes are unchanged, and the smaller ones get one more radix pass.
In the CPU worker:
In the manager:
GSplatSortBinWeights.computeCameraBinloses its unusedradialparameter.Trade-off
The CPU path uses 16-bit keys, while the GPU sort uses up to 20. In 0839ceea a slight flicker remains on very fine detail (a bike) on the CPU path, and it goes away with 20-bit CPU keys. CPU keys stay at 16 bits for speed: at 20 bits the counting table is 4 MB.
Performance
CPU sort time for 3M splats, upstream → this PR, as medians of alternating runs. The layouts are:
Coherent means the splats are stored in depth order, which is upstream's best case. Random means shuffled.
Pixel 7 Pro, Chrome, in a web worker:
Apple M4 Max, Node 22:
Upstream is quicker with stray splats because its bins collapse: most splats land in a few bins, which is the precision loss this PR fixes.
On 0839ceea itself, on the Pixel 7 Pro in WebGL during the default camera animation:
On the GPU path, 0839ceea on the Pixel 7 Pro in WebGPU takes 32.4 → 30.8 ms of GPU time per frame with radial sorting, and 32.9 → 30.9 ms with depth sorting (medians of 300 frames over three alternating rounds).
Testing
GSplatUnifiedSorter#setSortParamsreporting whether it sent the sort.Checklist