The naive version of point cloud rendering is a loop that uploads a vertex buffer every frame and issues one draw call. It works, it is easy to reason about, and it falls over somewhere around two hundred thousand points on a laptop. Here is what the path to eight million looked like.
Stop re-uploading
The first and largest win had nothing to do with rendering. Most frames in a recording do not change most of their data. A lidar sweep logged at t=4.2s is still the same bytes at t=4.3s if nothing overwrote it.
We were re-uploading it anyway, because the renderer asked the store "what is visible now?" and got back a fresh array each time. Caching the GPU buffer against the chunk it came from — and invalidating only when that chunk is superseded — removed most of the per-frame bandwidth. On a static scene the upload cost goes to zero.
Chunk layout is a rendering concern
Points arrive grouped by when they were logged, which is convenient for the store and useless for the camera. Two points logged in the same millisecond can be a hundred metres apart.
So we sort within a chunk by spatial locality before it is sealed. That gives each chunk a tight bounding box instead of one that spans the whole scene, which in turn makes frustum culling meaningful — you can reject an entire chunk with one box test rather than discovering, per point, that it was off screen.
The general shape: make the unit you can cheaply reject the same as the unit you cheaply store.
Instancing, finally
Each point is a camera-facing quad. Rather than four vertices per point in a buffer, we issue one instanced draw over a unit quad and read position, colour and radius from storage buffers indexed by instance. That cuts vertex bandwidth by four and lets the vertex stage do the billboarding arithmetic instead of the CPU.
// One draw per chunk that survived culling, not one per point.
for chunk in visible_chunks {
pass.set_bind_group(1, &chunk.bind_group, &[]);
pass.draw(0..4, 0..chunk.point_count);
}
What did not work
We tried level-of-detail by subsampling distant chunks. It was fast and it was wrong: for inspection work, a point disappearing because it was far away is indistinguishable from a point that was never logged. People debug with this tool. Silently dropping data is a bug, not an optimisation. We reverted it.
Depth-sorted alpha blending also went in and came back out. Correct transparency for eight million overlapping quads costs more than it buys when almost every real cloud is opaque.
Where the frame goes now
On an M-series laptop at eight million points, roughly: 28% ingest and decode, 11% query, 19% GPU upload on the frames where anything changed, 34% render, 8% UI. The interesting property is that the upload slice is near zero in the steady state — it only spikes when new data lands.