/** * GaussianSplatSorter - Wait-Free Hierarchical Radix Sort for WebGPU Gaussian Splatting * * Orchestrates the full GPU sort pipeline for Gaussian splat rendering: * 1. Compress: raw splats -> RGBA8 colors, f16 covariance, depth keys * 2. Sort: 4-pass 8-bit radix sort using Blelloch scan (no global atomics) * 3. Render: sorted indices feed the splat renderer for correct back-to-front compositing * * Performance characteristics: * - O(n) radix sort (4 passes for 32-bit keys) * - Wait-free: no global atomics, no cross-workgroup synchronization * - 50% memory reduction via compression (32 bytes/splat vs 64 bytes) * - Cross-browser: Chrome, Safari, Firefox, mobile (no subgroup ops, no shader-f16) * * References: * - HoloScript W.035: Radix sort outperforms bitonic sort for N > 64K splats * - HoloScript G.030.01: Safari requires explicit bind group recreation after buffer swap * * @module gpu/GaussianSplatSorter * @version 1.0.0 */ /** * Minimal device provider — the sorter only needs `getDevice()`. A full `WebGPUContext` * structurally satisfies this interface, so existing callers are unaffected. `fromDevice()` * uses it to drive the sorter from a bare GPUDevice (a headless Dawn test device, or a device * shared with the character renderer) without constructing a WebGPUContext. */ export interface SplatDeviceProvider { getDevice(): GPUDevice; } export interface GaussianSplatSorterOptions { /** Maximum number of splats (determines buffer allocation) */ maxSplats: number; /** Workgroup size for compute shaders (default: 256, must be power of 2) */ workgroupSize?: number; /** Elements processed per thread in sort passes (default: 4) */ elementsPerThread?: number; /** Enable debug timing via timestamp queries (default: false) */ enableTimestamps?: boolean; /** Canvas width for projection calculations */ canvasWidth: number; /** Canvas height for projection calculations */ canvasHeight: number; /** * Color-attachment format the render pipeline targets. Defaults to the preferred canvas format * (browser) or `bgra8unorm` (headless). Set to match the offscreen texture when rendering to a * render-to-texture target (e.g. `rgba8unorm` for the headless readback verification floor). */ renderFormat?: GPUTextureFormat; } export interface SplatSortStats { /** Number of splats currently being sorted */ splatCount: number; /** Number of workgroup blocks for sort */ blockCount: number; /** GPU time for compression pass (ms, if timestamps enabled) */ compressTimeMs?: number; /** GPU time for sort passes (ms, if timestamps enabled) */ sortTimeMs?: number; /** GPU time for render (ms, if timestamps enabled) */ renderTimeMs?: number; /** Total GPU time (ms) */ totalTimeMs?: number; /** Memory usage in bytes */ memoryUsageBytes: number; } export interface CameraState { /** View matrix (column-major, 16 floats) */ viewMatrix: Float32Array; /** Projection matrix (column-major, 16 floats) */ projMatrix: Float32Array; /** View-projection matrix (column-major, 16 floats) */ viewProjectionMatrix: Float32Array; /** Camera position in world space */ cameraPosition: [number, number, number]; /** Focal length X in pixels */ focalX: number; /** Focal length Y in pixels */ focalY: number; } export declare class GaussianSplatSorter { private context; private device; private options; private sortShaderModule; private compressShaderModule; private renderShaderModule; private compressPipeline; private histogramPipeline; private blellochScanPipeline; private globalPrefixPipeline; private scatterPipeline; private sortBindGroupLayout; private renderPipeline; private rawSplatBuffer; private compressedSplatBuffer; private sortKeysA; private sortKeysB; private sortValuesA; private sortValuesB; private blockHistogramsBuffer; private globalPrefixesBuffer; private compressUniformBuffer; private sortUniformBuffer; private renderUniformBuffer; private compressBindGroup; private splatCount; private blockCount; private initialized; constructor(context: SplatDeviceProvider, options: GaussianSplatSorterOptions); /** * Build a sorter from a bare GPUDevice (no WebGPUContext). For headless render-to-texture * (the Dawn verification floor) and for sharing one device with the character renderer so a * skinned mesh and a Gaussian-splat variant can composite into the same frame. Call * `await sorter.initialize()` before use, exactly as with the constructor. */ static fromDevice(device: GPUDevice, options: GaussianSplatSorterOptions): GaussianSplatSorter; /** * Initialize all GPU resources: shaders, pipelines, buffers. * * Must be called before any sort or render operations. */ initialize(): Promise; /** * Create and validate shader modules with cross-browser error reporting. */ private createShaderModules; /** * Create all compute pipelines for the sort. */ private createComputePipelines; /** * Create render pipeline for sorted splat rendering. */ private createRenderPipeline; /** * Allocate all GPU buffers. */ private createBuffers; private createSortBuffer; /** * Upload raw splat data to the GPU. * * Layout per splat = WGSL `SplatRaw` std-layout (64 bytes). vec3 has 16-byte * alignment, so each vec3 is followed by 4 bytes of implicit padding — the * fields are NOT tightly packed: * position: vec3 bytes 0..11 (+4 pad) * scale: vec3 bytes 16..27 (+4 pad) - LINEAR scale (exp of log-scale) * rotation: vec4 bytes 32..47 - quaternion (w, x, y, z) * color: vec4 bytes 48..63 - RGBA [0..1], a = opacity * As float32 indices per splat: pos@0, scale@4, rot@8, color@12. * * @param data Raw splat data as Float32Array (16 floats / 64 bytes per splat) * @param count Number of splats (not bytes) */ uploadSplatData(data: Float32Array, count: number): void; /** * Execute the full sort pipeline: compress -> 4-pass radix sort. * * Should be called each frame before rendering when the camera moves. * Uses a single command encoder for all passes to minimize CPU overhead. * * @param camera Current camera state for depth computation * @param commandEncoder Optional encoder to chain with other passes * @returns Command encoder with all sort passes recorded */ sort(camera: CameraState, commandEncoder?: GPUCommandEncoder): GPUCommandEncoder; /** * Record compression compute pass. */ private recordCompressPass; /** * Record one radix sort pass (histogram + scan + scatter). */ private recordSortPass; /** * Record render pass for sorted Gaussian splats. * * @param encoder Command encoder to record into * @param camera Camera state for rendering * @param colorView Color attachment view * @param depthView Depth attachment view * @param clearColor Optional clear color (default: transparent black) */ recordRenderPass(encoder: GPUCommandEncoder, camera: CameraState, colorView: GPUTextureView, depthView: GPUTextureView, clearColor?: GPUColor): void; /** * Execute full frame: sort + render in a single command submission. * * This is the main per-frame method for most use cases. * * @param camera Current camera state * @param colorView Color attachment view * @param depthView Depth attachment view * @param clearColor Optional clear color */ frame(camera: CameraState, colorView: GPUTextureView, depthView: GPUTextureView, clearColor?: GPUColor): void; /** * Get current sort statistics. */ getStats(): SplatSortStats; /** * Calculate total GPU memory usage in bytes. */ getMemoryUsage(): number; /** * Get the sorted index buffer (for external rendering integration). * * After sort(), the sorted indices are in sortValuesA (for even pass count). */ getSortedIndicesBuffer(): GPUBuffer; /** * Get the compressed splat buffer (for external rendering integration). */ getCompressedSplatBuffer(): GPUBuffer; /** * Update canvas dimensions (e.g., on resize). */ updateDimensions(width: number, height: number): void; /** * Destroy all GPU resources. */ destroy(): void; } /** * Create and initialize a GaussianSplatSorter. * * @example * ```typescript * const sorter = await createGaussianSplatSorter({ * maxSplats: 500_000, * canvasWidth: 1920, * canvasHeight: 1080, * }); * * // Upload splat data * sorter.uploadSplatData(splatData, splatCount); * * // Each frame: * sorter.frame(camera, colorView, depthView, { r: 0, g: 0, b: 0, a: 1 }); * ``` */ export declare function createGaussianSplatSorter(options: GaussianSplatSorterOptions & { contextOptions?: any; }): Promise; //# sourceMappingURL=GaussianSplatSorter.d.ts.map