import * as THREE from 'three/webgpu'; import { ComputePrefixSum } from '../Compute/ComputePrefixSum.cjs'; import { DeferredSurfaceNodeMaterial } from '../Materials/DeferredSurfaceNodeMaterial.cjs'; import type { GaussianSplats } from './GaussianSplats.cjs'; import type { SplatRenderViewContext } from './SplatSceneCoordinator.cjs'; import type { TSLFloatNode, TSLNode, TSLStorageNode, TSLUniformNode, TSLVec3Node, TSLVec4Node } from '../types/tsl.cjs'; export type SplatComputeTileLightingMode = 'custom' | 'scene'; type SplatComputeTileOutputMode = 'color' | 'color-depth' | 'surface'; export interface SplatComputeTileRendererOptions { tileSize?: number | undefined; maxTilesPerSplat?: number | undefined; entryMultiplier?: number | undefined; transmittanceThreshold?: number | undefined; surfaceAlphaThreshold?: number | undefined; readbackInterval?: number | undefined; allowRasterFallback?: boolean | undefined; lightingMode?: SplatComputeTileLightingMode | undefined; normalFilter?: boolean | undefined; } export type SplatComputeTileNormalMode = 'hybrid' | 'geometric' | 'splat' | number; export type SplatComputeTileDebugMode = 'lit' | 'normal' | 'geometric' | 'splat' | 'albedo' | 'validity' | 'shadow' | 'ao' | number; export interface SplatComputeTileDirectionalLighting { direction?: THREE.Vector3 | undefined; color?: THREE.ColorRepresentation | undefined; intensity?: number | undefined; shadowStrength?: number | undefined; normalBias?: number | undefined; light?: THREE.DirectionalLight | null | undefined; shadowNode?: TSLNode | null | undefined; } export interface SplatComputeTilePointLighting { position?: THREE.Vector3 | undefined; color?: THREE.ColorRepresentation | undefined; intensity?: number | undefined; range?: number | undefined; } /** Reconstructed shading point handed to the surface-node callbacks. */ export interface SplatComputeTileSurfaceContext { /** World-space position of the reconstructed splat surface. */ worldPosition: TSLVec3Node; /** Surface normal selected by the active normal mode. */ normal: TSLVec3Node; /** Composited coverage at this pixel; near zero outside the splats. */ alpha: TSLFloatNode; } /** Ambient-occlusion input: a fixed node, or one built from the reconstructed surface. */ export type SplatComputeTileAONode = TSLFloatNode | ((surface: SplatComputeTileSurfaceContext) => TSLFloatNode); /** Normal override built from the reconstructed surface. */ export type SplatComputeTileNormalNode = (surface: SplatComputeTileSurfaceContext) => TSLVec3Node; /** * Replacement for the shadow value a light samples from its map, given that value and the * reconstructed surface. Splats cast a stochastic clip-mode shadow that they cannot exclude * themselves from, so a splat that receives its own map shadow acnes; returning a constant * drops the map term, and returning a surface-derived term substitutes a usable one. */ export type SplatComputeTileReceivedShadowNode = (mapShadow: TSLNode, surface: SplatComputeTileSurfaceContext) => TSLNode; export interface SplatComputeTileLightingOptions { mode?: SplatComputeTileLightingMode | undefined; aoNode?: SplatComputeTileAONode | null | undefined; normalNode?: SplatComputeTileNormalNode | null | undefined; envNode?: TSLNode | null | undefined; lightsNode?: THREE.LightsNode | null | undefined; receivedShadowNode?: SplatComputeTileReceivedShadowNode | null | undefined; enabled?: boolean | undefined; strength?: number | undefined; contrastLimit?: number | undefined; normalMode?: SplatComputeTileNormalMode | undefined; normalBlend?: number | undefined; normalRadius?: number | undefined; normalMaxDistance?: number | undefined; normalFilterBlend?: number | undefined; normalFilter?: boolean | undefined; normalConfidenceFloor?: number | undefined; debugMode?: SplatComputeTileDebugMode | undefined; cameraPosition?: THREE.Vector3 | undefined; roughness?: number | undefined; metalness?: number | undefined; specularIntensity?: number | undefined; diffuseWrap?: number | undefined; twoSided?: boolean | undefined; ambientColor?: THREE.ColorRepresentation | undefined; ambientIntensity?: number | undefined; directional?: SplatComputeTileDirectionalLighting | undefined; points?: readonly SplatComputeTilePointLighting[] | undefined; } interface SplatComputeTileSceneSurfaceNodes { aoNode: SplatComputeTileAONode | null; normalNode: SplatComputeTileNormalNode | null; envNode: TSLNode | null; lightsNode: THREE.LightsNode | null; receivedShadowNode: SplatComputeTileReceivedShadowNode | null; } interface SplatComputeTileLightingUniforms { enabled: TSLUniformNode<'float', number>; strength: TSLUniformNode<'float', number>; contrastLimit: TSLUniformNode<'float', number>; normalMode: TSLUniformNode<'float', number>; normalBlend: TSLUniformNode<'float', number>; normalRadius: TSLUniformNode<'int', number>; normalMaxDistance: TSLUniformNode<'float', number>; normalFilterBlend: TSLUniformNode<'float', number>; normalConfidenceFloor: TSLUniformNode<'float', number>; debugMode: TSLUniformNode<'float', number>; cameraPosition: TSLUniformNode<'vec3', THREE.Vector3>; roughness: TSLUniformNode<'float', number>; metalness: TSLUniformNode<'float', number>; specularIntensity: TSLUniformNode<'float', number>; diffuseWrap: TSLUniformNode<'float', number>; twoSided: TSLUniformNode<'float', number>; ambientColor: TSLUniformNode<'color', THREE.Color>; ambientIntensity: TSLUniformNode<'float', number>; directionalDirection: TSLUniformNode<'vec3', THREE.Vector3>; directionalColor: TSLUniformNode<'color', THREE.Color>; directionalIntensity: TSLUniformNode<'float', number>; directionalShadowStrength: TSLUniformNode<'float', number>; shadowNormalBias: TSLUniformNode<'float', number>; pointPositions: [TSLUniformNode<'vec3', THREE.Vector3>, TSLUniformNode<'vec3', THREE.Vector3>]; pointColors: [TSLUniformNode<'color', THREE.Color>, TSLUniformNode<'color', THREE.Color>]; pointIntensities: [TSLUniformNode<'float', number>, TSLUniformNode<'float', number>]; pointRanges: [TSLUniformNode<'float', number>, TSLUniformNode<'float', number>]; } interface SplatIndirectBuffer { attribute: THREE.IndirectStorageBufferAttribute; node: TSLStorageNode<'uint'>; } interface SplatCompositeSurface { alpha: TSLFloatNode; ao: TSLFloatNode; context: SplatComputeTileSurfaceContext; albedo: TSLVec3Node; worldPosition: TSLVec3Node; viewDirection: TSLVec3Node; normal: TSLVec3Node; normalConfidence: TSLFloatNode; geometricNormal: TSLVec3Node; geometricValid: TSLFloatNode; splatNormal: TSLVec3Node; splatNormalDebug: TSLVec3Node; surfaceDepthDebug: TSLFloatNode; } export interface SplatComputeTileStats { readonly rendererMode: 'compute-tiles' | 'raster-fallback' | 'compute-tiles-pending'; readonly allowRasterFallback: boolean; readonly tileSize: number; readonly tiles: number; readonly maxEntriesPerTile: null; readonly tileSort: 'workgroup-bitonic+buckets'; readonly rasterWorkgroup: '8x4-pixel-indirect'; readonly surfaceAlphaThreshold: number; readonly tileEntryCapacity: number; readonly entryCount: number | null; readonly largeSplatCount: number | null; readonly smallTileCount: number | null; readonly mediumTileCount: number | null; readonly heavyTileCount: number | null; readonly rasterTileCount: number | null; readonly compositeTileCount: number | null; readonly compositeLightingMode: SplatComputeTileLightingMode; readonly chunkCount: number | null; readonly globalSortBypassed: boolean; readonly normalFilter: boolean; readonly gpuBytes: number; readonly overflowCount: number | null; readonly oversizedSplats: number | null; readonly rasterDeferred: boolean; readonly fallbackReason: string | null; } export type SplatComputeBatch = THREE.ComputeNode[] & { id: string; name: string; isComputeNode: true; }; /** * Optional adaptive tiled-compute Gaussian splat renderer. * * The renderer reuses compacted projected splats, bins exact ellipse/tile intersections * into a prefix-summed entry buffer, sorts small tiles in shared memory, bucket-sorts * heavy tiles in local chunks, and rasterizes non-empty tiles indirectly. Oversized * splats are counted and scattered cooperatively. If the contiguous entry buffer is * temporarily undersized, asynchronous overflow readback grows it while keeping the * raster fallback active. * * @class SplatComputeTileRenderer * @short Opt-in WebGPU tiled compute rasterizer with safe raster fallback. * @category GaussianSplatting */ export declare class SplatComputeTileRenderer { splats: GaussianSplats; tileSize: number; maxTilesPerSplat: number; entryMultiplier: number; transmittanceThreshold: number; surfaceAlphaThreshold: number; readbackInterval: number; allowRasterFallback: boolean; active: boolean; supported: boolean; fallbackReason: string | null; autoDriven: boolean; _promoteStage: number; overflowCount: number | null; entryCount: number | null; largeSplatCount: number | null; smallTileCount: number | null; mediumTileCount: number | null; heavyTileCount: number | null; rasterTileCount: number | null; compositeTileCount: number | null; chunkCount: number | null; oversizedSplats: number | undefined; _frame: number; _readbackPending: boolean; _disposed: boolean; _capacityValidated: boolean; _lastSubmittedRasterized: boolean; rasterDeferred: boolean; _width: number; _height: number; _tilesX: number; _tilesY: number; _tileCount: number; _opaqueDepthTexture: THREE.Texture | null; _entryCapacity: number; _chunkCapacity: number; _allocTilesX: number; _allocTilesY: number; _allocTileCount: number; _allocEntryCapacity: number; _allocChunkCapacity: number; _buffersNeedResize: boolean; directionalShadowLight: THREE.DirectionalLight | null; directionalShadowNode: TSLNode | null; compositeLightingMode: SplatComputeTileLightingMode; sceneSurfaceNodes: SplatComputeTileSceneSurfaceNodes; _prefixSum: ComputePrefixSum | null; _prefixBatch: SplatComputeBatch | null; _prepareFrameBatch: SplatComputeBatch | null; _rasterFrameBatch: SplatComputeBatch | null; timestampContext: SplatComputeBatch | null; outputTexture: THREE.StorageTexture; outputNormalTexture: THREE.StorageTexture; outputNormalFilteredTexture: THREE.StorageTexture; outputSurfaceDepthTexture: THREE.StorageTexture; outputDepthTexture: THREE.StorageTexture; screenSize: TSLUniformNode<'vec2', THREE.Vector2>; _uWidth: TSLUniformNode<'uint', number>; _uHeight: TSLUniformNode<'uint', number>; _uTilesX: TSLUniformNode<'uint', number>; _uTilesY: TSLUniformNode<'uint', number>; _uTileCount: TSLUniformNode<'uint', number>; _uEntryCapacity: TSLUniformNode<'uint', number>; _uChunkCapacity: TSLUniformNode<'uint', number>; lighting: SplatComputeTileLightingUniforms; normalFilterEnabled: boolean; customCompositeMaterial: THREE.NodeMaterial; sceneCompositeMaterial: DeferredSurfaceNodeMaterial; compositeMaterial: THREE.NodeMaterial; compositeMesh: THREE.Mesh; tileCounts: TSLStorageNode<'uint'> | null; tileOffsets: TSLStorageNode<'uint'> | null; tileEntries: TSLStorageNode<'uint'> | null; tileScratch: TSLStorageNode<'uint'> | null; tileData: TSLStorageNode<'uvec4'> | null; counters: TSLStorageNode<'uint'> | null; largeSplatIds: TSLStorageNode<'uint'> | null; tileLists: TSLStorageNode<'uint'> | null; bucketCounts: TSLStorageNode<'uint'> | null; bucketOffsets: TSLStorageNode<'uint'> | null; chunkList: TSLStorageNode<'uvec4'> | null; _largeDispatch: SplatIndirectBuffer | null; _smallDispatch: SplatIndirectBuffer | null; _mediumDispatch: SplatIndirectBuffer | null; _heavyDispatch: SplatIndirectBuffer | null; _chunkDispatch: SplatIndirectBuffer | null; _filterDispatch: SplatIndirectBuffer | null; _rasterDispatch: SplatIndirectBuffer | null; _rasterDraw: SplatIndirectBuffer | null; _prepareBatch: SplatComputeBatch; _rasterPrepareBatch: SplatComputeBatch; _smallBatch: SplatComputeBatch; _mediumBatch: SplatComputeBatch; _heavyBucketBatch: SplatComputeBatch; _heavyRasterBatch: SplatComputeBatch; _clear: THREE.ComputeNode; _stageKeys: THREE.ComputeNode; _count: THREE.ComputeNode; _prepareLargeDispatch: THREE.ComputeNode; _copyCounts: THREE.ComputeNode; _scatter: THREE.ComputeNode; _scatterLarge: THREE.ComputeNode; _clearLists: THREE.ComputeNode; _classify: THREE.ComputeNode; _countLarge: THREE.ComputeNode; _sortSmall: THREE.ComputeNode; _sortMedium: THREE.ComputeNode; _bucketCount: THREE.ComputeNode; _bucketPrefix: THREE.ComputeNode; _bucketScatter: THREE.ComputeNode; _sortChunks: THREE.ComputeNode; _prepareChunkDispatch: THREE.ComputeNode; _copyHeavy: THREE.ComputeNode; _raster: THREE.ComputeNode; _prepareRasterDispatch: THREE.ComputeNode; _prepareFilterDispatch: THREE.ComputeNode | null; _filterNormals: THREE.ComputeNode | null; _renderer: THREE.Renderer; constructor(splats: GaussianSplats, options?: SplatComputeTileRendererOptions); /** * Reconstruct the world-space camera ray for a pixel center: worldPos = cameraPos + d·ray, * with d the LINEAR view depth. Built from the splat pipeline's own focal/screen/inverse-view * uniforms — the exact projection model the compute pass used to write the depths — so the * round trip is bit-consistent and works in any pass (no reliance on camera node state). * @private */ _pixelRay(pixel: TSLNode<'ivec2'>): TSLVec3Node; _buildCompositeSurface(accumulated: TSLVec4Node, normalAccumulated: TSLVec4Node, surfaceDepthAccumulated: TSLFloatNode, screenPixel: TSLNode<'ivec2'>): SplatCompositeSurface; _buildCompositeColor(accumulated: TSLVec4Node, normalAccumulated: TSLVec4Node, surfaceDepthAccumulated: TSLFloatNode, screenPixel: TSLNode<'ivec2'>): TSLVec4Node; _usesSceneLighting(): boolean; get _outputMode(): SplatComputeTileOutputMode; _configureCompositeMaterial(accumulated: TSLVec4Node, normalAccumulated: TSLVec4Node, surfaceDepthAccumulated: TSLFloatNode, depth: TSLFloatNode, screenPixel: TSLNode<'ivec2'>): void; _createOutputTexture(name: string, depth?: boolean): THREE.StorageTexture; _createCompositeMesh(): void; _configureCompositeMesh(): void; _refreshCompositeMaterial(): void; /** * Normal source for the composite: the denoised texture when the filter pass is active, * the raw accumulation otherwise. Same layout (xyz = Σw·n, w = Σw). * @private */ get _activeNormalTexture(): THREE.StorageTexture; setOpaqueDepthTexture(textureValue: THREE.Texture | null): void; setLighting(options?: SplatComputeTileLightingOptions): void; _isSupported(): string | null; _sortKeyLayoutIsHashed(): boolean; get bypassesGlobalSort(): boolean; init(renderer: THREE.Renderer): boolean; invalidate(): void; resize(renderer: THREE.Renderer, force?: boolean, viewContext?: SplatRenderViewContext | null): boolean; _updateDispatchCounts(): void; _tileAllocSize(renderer: THREE.Renderer): THREE.Vector2; _reallocate(renderer: THREE.Renderer, tileCount: number, entryCapacity: number): void; _createDispatchBuffer(name: string): SplatIndirectBuffer; _createDrawBuffer(name: string): SplatIndirectBuffer; _buildCompute(): void; update(renderer: THREE.Renderer, viewContext?: SplatRenderViewContext | null): boolean; _requestOverflowReadback(renderer: THREE.Renderer): Promise; /** * Deactivate the composite and drop capacity validation (idle-hold demote): the next * promotion revalidates against its exact pose. Visibility flips only — no pipeline * or buffer churn. */ suspend(reason: string): void; /** * One promotion step of the idle-hold path: the full tile pipeline is submitted as * five ordered stages across consecutive still frames (bin/classify, small sort, * medium sort, heavy bucket+sort, merge+raster), bounding the per-frame cost far * below the single-shot pipeline's spike. Data is stable across the stages by * construction — any camera or data change demotes and restarts the sequence. * After the last stage the standard overflow readback validates the exact pose and * activates the composite. */ promoteStep(renderer: THREE.Renderer, viewContext?: SplatRenderViewContext | null): void; _setFallback(reason: string): void; _syncVisibility(): void; get stats(): SplatComputeTileStats; _disposeBuffers(): void; dispose(): void; } export {};