import * as THREE from 'three/webgpu'; import type { Renderer, StorageBufferNode, UniformNode } from 'three/webgpu'; import { SplatSourceResource } from './SplatSourceResource.cjs'; import type { ExpandedSplatGPUResources, ExpandedSplatSourceData, SplatSourceReadNodes } from './SplatSourceResource.cjs'; import type { SplatVideoAtlasLayout } from './SplatVideoFrames.cjs'; import type { SplatVideoBounds, SplatVideoPayload } from './SplatVideoManifest.cjs'; /** Construction options for the full-GPU video attribute source. */ export interface SplatVideoTextureSourceOptions { video?: SplatVideoPayload | undefined; /** Exact, unpadded range used to dequantize position planes. */ positionBounds?: SplatVideoBounds | undefined; /** @deprecated Legacy alias for positionBounds. */ bounds?: SplatVideoBounds | undefined; capacity?: number | undefined; /** Number of authored frames eligible for the resident transport cache. */ frameCount?: number | undefined; /** Resident geometry + R8 appearance-cache budget. Defaults to 192 MiB; 0 disables the cache. */ cacheBudgetBytes?: number | undefined; } /** Default fixed budget from the transport-v2 rollout plan. */ export declare const SPLAT_VIDEO_CACHE_BUDGET_BYTES: number; /** Allocation decision made before any resident GPU texture is created. */ export interface SplatVideoCachePlan { readonly enabled: boolean; readonly frameBytes: number; readonly requiredBytes: number; readonly budgetBytes: number; readonly frameCount: number; readonly reason: 'fits' | 'disabled' | 'invalid-size' | 'budget-exceeded'; } /** Point-in-time resident transport-cache diagnostics. */ export interface SplatVideoCacheFacts extends SplatVideoCachePlan { readonly state: 'disabled' | 'warming' | 'resident' | 'disposed'; readonly resident: number; readonly residentBytes: number; readonly bytes: number; readonly hits: number; readonly misses: number; /** Decode/inflate work used by the last published frame: 1 on lap one, 0 on a cache hit. */ readonly decodeWork: 0 | 1; readonly totalDecodeWork: number; /** GPU textures owned by the cache, including its GPU-only luma upload resources. */ readonly ownedTextures: number; /** Packed-v3 input storage buffers owned by the source. */ readonly ownedBuffers: number; readonly rejection: string | null; } export type SplatVideoUploadMode = 'none' | 'cache-hit' | 'staging-ring'; export type SplatVideoUnpackMode = 'unbuilt' | 'plane-compat' | 'subgroup-f16'; /** Per-frame upload/unpack diagnostics used by readStats and the M4 release gate. */ export interface SplatVideoPipelineFacts { readonly uploadMilliseconds: number; readonly uploadMode: SplatVideoUploadMode; readonly stagingSlots: number; readonly stagedUploads: number; readonly unpackMode: SplatVideoUnpackMode; readonly unpackDispatches: number; } /** * Plan a full resident geometry + R8 appearance cache without allocating it. * * @param {Object} layout - Atlas dimensions shared with the unpack kernel. * @param {number} frameCount - Authored frame count. * @param {number} [budgetBytes=192MiB] - Maximum resident plane bytes. */ export declare function planSplatVideoCache(layout: SplatVideoAtlasLayout, frameCount: number, budgetBytes?: number, geometryFrameBytes?: number): SplatVideoCachePlan; /** Narrow GaussianSplats surface consumed by the unpack kernel. */ export interface SplatVideoTextureMesh { buffers: (ExpandedSplatGPUResources & { chunkBounds?: StorageBufferNode<'float'> | null | undefined; }) | null; _chunkBoundsMaxStdDev?: number | undefined; _needsUpdate?: boolean | undefined; } /** * Capacity-sized conservative cull bounds: every chunk = the motion-dilated clip AABB. They * carry a fresh `video`-clip mesh only until the first unpack dispatch rewrites the buffer * per chunk ON the GPU (tight bounds for live chunks, far-away sentinels for empty ones). * * @param {number} capacity - Pinned splat capacity (`video.maxSplatCount`). * @param {Object} bounds - Clip bounds `{ min:[x,y,z], max:[x,y,z] }`. * @returns {Float32Array} `ceil(capacity/256) × 6` floats (min xyz, max xyz per chunk). */ export declare function buildVideoClipChunkBounds(capacity: number, bounds: SplatVideoBounds): Float32Array; /** * Raw-byte carrier for decoded `VideoFrame`s bound to data (not color) consumers. * * Deliberately a plain `THREE.Texture` rather than `THREE.VideoFrameTexture`: the WebGPU * backend routes ANY VideoFrame image through `copyExternalImageToTexture` (one GPU-side blit, * no CPU readback), but `isVideoTexture` would (a) warn that data textures should be sRGB and * (b) risk an `rgba8unorm-srgb` allocation whose `textureLoad` returns LINEARIZED floats — * destroying the byte-exact luma the geometry track depends on. As a plain texture with * `NoColorSpace` the GPU format stays `rgba8unorm` and `textureLoad(...).r * 255` recovers the * encoded byte exactly (full-range luma with neutral chroma converts 1:1 to R). * * @class SplatVideoPlaneTexture * @augments THREE.Texture * @short Byte-exact VideoFrame texture for compute-side plane unpacking. * @category GaussianSplatting */ export declare class SplatVideoPlaneTexture extends THREE.Texture { readonly isSplatVideoPlaneTexture: true; constructor(); /** * Hand a decoded frame to the GPU. The caller keeps ownership of the `VideoFrame` and must * keep it un-closed until after the next texture upload (the backend blits lazily, when the * texture is next bound by a compute or render pass). * * @param {VideoFrame} frame - The decoded frame to upload on next use. */ setFrame(frame: VideoFrame): void; } /** Byte-exact R8 upload target for an inflated geometry member. */ export declare class SplatVideoGeometryTexture extends THREE.DataTexture { constructor(width: number, height: number); setBytes(bytes: Uint8Array): void; } /** * GPU source for USV `video` clips (GS4D-5 v2): packed geometry lands in a storage buffer, * decoded appearance lands through `copyExternalImageToTexture`, and one unpack compute pass * expands the transport into the pinned splat buffers. Legacy plane layouts remain readable. * * The kernel is one workgroup per 256-splat cull chunk. Each thread decodes one splat * (17 `textureLoad`s across the two atlases) and writes the `[position,scale][rotation,color]` * pairs; the workgroup then min/max-reduces positions in shared memory and lane 0 rewrites the * chunk's cull AABB (padded by `exp(maxLogScale)·maxStdDev`, `computeChunkBounds` convention). * Chunks with no live splat this frame get far-away sentinel bounds and frustum-cull out, so * projection cost tracks the frame's actual splat count. `buildReadNodes()` is exactly the * expanded static source's — consumers bind nothing new (the resolve-pass lesson). * * Byte-plane decode mirrors `decodeFramePlanes` exactly (the CPU twin for goldens): * position = hi·256+lo → unorm16 lerp in clip bounds; rotation = 4 bytes → the USC * smallest-three word; scale = unorm8 lerp in `scaleRange` (LOG domain — the shader `exp()`s * at projection, like every other source); color = unorm8 RGBA. `shape-lossless` reads scale * and alpha from geometry, while the legacy layout reads them from appearance. * * @class SplatVideoTextureSource * @augments SplatSourceResource * @short GPU texture-unpack source for USV `video` clips. * @category GaussianSplatting */ export declare class SplatVideoTextureSource extends SplatSourceResource { layout: SplatVideoAtlasLayout; geometryTexture: SplatVideoGeometryTexture; appearanceTexture: SplatVideoPlaneTexture; uCount: UniformNode<'uint', number>; unpackDirty: boolean; private _inner; private _uBoundsMin; private _uBoundsSpan; private _uScaleMin; private _uScaleSpan; private _uCacheLayer; private _unpackCompute; private _cachePlan; private _cacheInitialized; private _cacheRejected; private _cacheGeometry; private _cacheAppearance; private _cacheFrameLayers; private _cacheGeometryRegion; private _packedGeometry; private _pendingPackedGeometry; private _packedWordsPerFrame; private _packedUploadRing; private _packedUploadCursor; private _stagedUploads; private _uploadError; private _uploadMode; private _unpackMode; private _unpackDispatches; private _renderer; private _currentFrame; private _cacheHits; private _cacheMisses; private _decodeWork; private _totalDecodeWork; private _disposed; /** * @param {Object} data - The pinned mesh's setData payload (arrays unused — transport bytes are the data). * @param {number} count - Capacity count (`video.maxSplatCount`). * @param {Object} options - Source options. * @param {Object} options.video - Normalized `manifest.video` (maxSplatCount, atlasWidth, scaleRange, counts). * @param {Object} options.positionBounds - Exact unpadded position dequantization range. * @param {number} [options.capacity=count] - Resident buffer capacity. */ constructor(data: ExpandedSplatSourceData, count: number, options?: SplatVideoTextureSourceOptions); createGPUResources(): ExpandedSplatGPUResources; upload(): void; buildReadNodes(buffers: ExpandedSplatGPUResources): SplatSourceReadNodes; rangeSignature(): number[]; /** * CPU mirrors are not resident on the GPU path (that is the point). Helpers that need CPU * positions must decode the frame explicitly via `decodeFramePlanes` (SplatVideoFrames). */ readPositionsCPU(): null; readScalesCPU(): null; readRotationsCPU(): null; readColorsCPU(): null; /** Stage one newly decoded/inflated pair for its first GPU publication. */ setDecodedFrame(frame: number, geometry: Uint8Array, appearance: VideoFrame): this; /** Select a fully resident frame in O(1), without asking either transport decoder to work. */ useResidentFrame(frame: number): boolean; /** Whether every authored frame has a paired resident layer. */ get cacheComplete(): boolean; /** Immutable cache/transport counters suitable for devtools and release gates. */ get cacheFacts(): Readonly; /** Upload-ring and unpack-kernel facts suitable for release telemetry. */ get pipelineFacts(): Readonly; /** Mark the resident textures as holding a new frame pair (dispatch on next mesh update). */ markDirty(): this; /** * Dispatch the unpack pass when a new frame pair arrived since the last dispatch. Builds the * compute node lazily on first use (buffers exist by then — call from the mesh's * `beforeupdate`, the same slot the spacetime resolve pass rides). * * @param {THREE.WebGPURenderer} renderer - Active renderer. * @param {GaussianSplats} splats - The mesh whose buffers this source populated. * @returns {boolean} Whether a dispatch happened. */ updateUnpack(renderer: Renderer, splats: SplatVideoTextureMesh): boolean; private _initializeCache; private _copyCurrentFrameToCache; private _uploadPackedGeometry; private _buildUnpack; dispose(): void; } export declare const VIDEO_UNPACK_PLANE_COUNTS: { geometry: 10; appearance: 7; }; /** Shape-safe plane counts mirrored by the same unpack kernel. */ export declare const VIDEO_SHAPE_LOSSLESS_UNPACK_PLANE_COUNTS: { geometry: 14; appearance: 3; };