Pajama / Gaussian Lab

RUST / WGPU · MEASURED IMPLEMENTATION

Move the work. Measure the whole frame.

The GPU now decides which Gaussians to draw, sorts their depths, and supplies the draw count. The CPU still manages streaming, the camera and command encoding.

Open the live CPU / GPU comparison →

What the measurements say

Same checkpoint, 1280 × 960, three calibrated cameras and four times including both endpoints. Two warmup rounds, seven measured rounds, alternating backend order; 84 frames per backend per scene. CPU baseline is the previously optimized v9 path.

Loading measured evidence…

Frame time here is serialized CPU preparation → GPU completion, with a fixed small timestamp copy. It excludes correctness readbacks, networking, display refresh and frame pipelining. It is not browser FPS. GPU total includes all culling and sorting passes, not just rasterization.

Native summary JSON · Browser A/B and playback evidence

Follow one Gaussian through the GPU

  1. Evaluate and cull. Read the resident 128-byte STG record. Evaluate cubic motion and temporal opacity at t. Apply the same 1/255 opacity cutoff and center-frustum margin as the CPU. Invisible records receive a sentinel key.
  2. Sort a full 32-bit depth key. Eight stable 4-bit radix passes: count each digit per workgroup, prefix-scan the counts, then scatter records to their destinations. Shared bit masks preserve input order within each bucket. Equal computed depths retain ascending source IDs.
  3. Compact by sorting, then draw indirectly. Sentinels move behind all visible entries. A GPU atomic counter supplies instanceCount; draw_indirect consumes it without a CPU round trip. The final sort pass writes the packed 4-byte source-ID vertex stream.
  4. Keep the same rasterizer. The existing vertex shader projects each Gaussian to an ellipse; the fragment shader evaluates its falloff and blends it. Source replacement invalidates GPU preparation. Unchanged frames reuse the prepared order.

What is preserved, and what is not promised

Use it in your engine

let mut splats = StgPass::new_gpu(&device, &ply, format, depth)?;
splats.prepare(&queue, t, view, projection, viewport)?;
splats.encode_prepare(&queue, &mut encoder, None);
// Begin the host render pass AFTER compute preparation.
splats.draw(&mut pass); // GPU-generated instance count
// Submit before preparing the next frame with these buffers.

Rust orchestration · Culling kernel · Stable radix kernels · Reproduce the A/B