UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

224 lines (223 loc) 10.7 kB
/** * Owns the per-splat compute pass for the hybrid GSplat renderer (Pass B in the pipeline): * * project + screen-space cull + sort key generation + compaction * * The pass reads the post-frustum-culled `compactedSplatIds` produced by * {@link GSplatIntervalCompaction} and writes a pre-projected raster-friendly * cache (see {@link CACHE_STRIDE}) plus a parallel array of depth-based sort * keys. A workgroup-local atomic compaction pattern (one global atomicAdd per * workgroup of 256 threads) keeps post-cull entries dense in the output buffers. * * Designed to live alongside the existing renderers without affecting them. The * cache layout is duplicated in `gsplat-projector-constants.js` (JS) and the * projector / hybrid VS WGSL chunks via the `{CACHE_STRIDE}` cdefine. * * @ignore */ export class GSplatProjector { /** * @param {GraphicsDevice} device - The graphics device (must support compute). */ constructor(device: GraphicsDevice); /** @type {GraphicsDevice} */ device: GraphicsDevice; /** * 32 B per splat (8 u32 slots), sized to the work-buffer capacity (not the * post-cull count) to avoid undersizing on transient cull-rate drops. * * @type {StorageBuffer|null} */ projCache: StorageBuffer | null; /** @type {StorageBuffer|null} */ sortKeys: StorageBuffer | null; /** * Single-element atomic counter; reset to 0 every frame and incremented by * the projector pass via per-workgroup atomicAdd. * * @type {StorageBuffer|null} */ renderCounter: StorageBuffer | null; /** * Storage buffer holding interleaved bin weights {base, divider} consumed by * the projector for camera-relative sort key precision. * * @type {StorageBuffer|null} */ binWeightsBuffer: StorageBuffer | null; /** @type {GSplatSortBinWeights} */ binWeightsUtil: GSplatSortBinWeights; /** * Lazily-created projector compute variants keyed by `radial|pick|fisheye|aa` * combination (see `_projectorKey`). AA is mutually exclusive with pick, so up to * 12 variants exist; only the combinations actually needed by the running app are * compiled. * * @type {Map<string, Compute>} */ _projectorComputes: Map<string, Compute>; /** @type {BindGroupFormat|null} */ _projectorBindGroupFormat: BindGroupFormat | null; /** * Uniform buffer format for the non-fisheye projector variant. * * @type {UniformBufferFormat|null} */ _projectorUniformBufferFormat: UniformBufferFormat | null; /** * Uniform buffer format for the fisheye projector variant. Adds 4 fisheye * scalar uniforms after the shared fields. * * @type {UniformBufferFormat|null} */ _projectorUniformBufferFormatFisheye: UniformBufferFormat | null; /** @type {Compute|null} */ _writeIndirectArgsCompute: Compute | null; /** @type {BindGroupFormat|null} */ _writeArgsBindGroupFormat: BindGroupFormat | null; /** @type {UniformBufferFormat|null} */ _writeArgsUniformBufferFormat: UniformBufferFormat | null; /** * Cached work-buffer format version; changes invalidate the projector * compute (its bind group format is derived from the work-buffer format). */ _formatVersion: number; /** @type {number} */ _allocatedCacheCount: number; /** @type {Float32Array} */ cameraPositionData: Float32Array; /** @type {Float32Array} */ cameraDirectionData: Float32Array; destroy(): void; /** @private */ private _createUniformBufferFormats; /** @private */ private _createWriteIndirectArgsCompute; /** * Destroys the projector compute pipelines so they are lazily rebuilt against the * current work-buffer format. * * @private */ private _destroyProjectorComputes; /** * Builds the cache key for a projector variant combination. * * @param {boolean} radialSort - Radial vs linear sort. * @param {boolean} pickMode - Pick output mode. * @param {boolean} fisheyeMode - Fisheye projection mode. * @param {boolean} antiAlias - Anti-aliasing opacity compensation mode. * @returns {string} The cache key. * @private */ private _projectorKey; /** * Builds the projector Compute (one variant per sort/pick/fisheye combination). * * @param {GSplatWorkBuffer} workBuffer - The current work buffer (provides format). * @param {boolean} radialSort - Whether to compile the RADIAL_SORT variant. * @param {boolean} pickMode - Whether to write pcId into the cache for picking. * @param {boolean} fisheyeMode - Whether to compile the GSPLAT_FISHEYE variant. * @param {boolean} antiAlias - Whether to compile the GSPLAT_AA variant (bakes the * anti-aliasing opacity compensation into the cached alpha). * @returns {Compute} The created compute instance. * @private */ private _createProjectorCompute; /** * Returns the projector Compute for the requested sort/pick/fisheye combination, * lazily creating it. Recreates all compute instances when the work-buffer format * version changes. * * @param {GSplatWorkBuffer} workBuffer - The current work buffer. * @param {boolean} radialSort - Whether to use the radial sort variant. * @param {boolean} [pickMode] - Whether to use the pick-output variant. * @param {boolean} [fisheyeMode] - Whether to use the fisheye projection variant. * @param {boolean} [antiAlias] - Whether to use the anti-aliasing variant. * @returns {Compute} The projector compute instance. * @private */ private _getProjectorCompute; /** * Ensures projCache and sortKeys storage buffers are sized to at least `capacity` splats. * Both buffers are sized to the work-buffer capacity (passed in by the caller) rather than * post-cull counts, mirroring `compactedSplatIds` allocation in interval compaction. * * @param {number} capacity - Required splat capacity (work-buffer total active splats). * @private */ private _ensureCapacity; /** * Runs the project + cull + key-gen + compact compute pass. * * @param {object} params - Dispatch parameters. * @param {GSplatWorkBuffer} params.workBuffer - The current work buffer. * @param {GraphNode} params.cameraNode - The camera node (with attached CameraComponent). * @param {StorageBuffer} params.compactedSplatIds - Output of interval compaction. * @param {StorageBuffer} params.sortElementCountBuffer - GPU-written visible count from * interval compaction (single u32 element); the projector reads this to early-out * threads beyond the post-frustum-cull range. * @param {number} params.totalCapacity - Work-buffer capacity used to size projCache / * sortKeys (typically `worldState.totalActiveSplats`). * @param {boolean} params.radialSort - Whether to use the radial sort key variant. * @param {number} params.numBits - Sort key bit count (defines bucket count = 1 << numBits). * @param {number} params.minDist - Minimum distance for sort key normalisation. * @param {number} params.maxDist - Maximum distance for sort key normalisation. * @param {number} params.alphaClip - Alpha cull threshold. * @param {number} params.minPixelSize - Minimum on-screen pixel size before culling. * @param {number} params.minContribution - Minimum total contribution before culling. * @param {number} params.viewportWidth - Render viewport width in pixels. * @param {number} params.viewportHeight - Render viewport height in pixels. * @param {boolean} params.flipY - Whether the active render target uses `flipY` (must match * {@link Renderer#setCameraUniforms}). * @param {boolean} [params.pickMode] - Whether to write picking IDs into the cache. * @param {import('../graphics/fisheye-projection.js').FisheyeProjection} [params.fisheyeProj] - * Fisheye projection state. When `fisheyeProj.enabled` is true the projector picks the * GSPLAT_FISHEYE variant and writes NDC-style clip data; otherwise the linear path runs. * @param {boolean} [params.antiAlias] - Whether to bake anti-aliasing opacity * compensation into the cached alpha. Ignored in pick mode (picking only gates on a * binary opacity threshold), which keeps the projector variant count down. */ dispatch(params: { workBuffer: GSplatWorkBuffer; cameraNode: GraphNode; compactedSplatIds: StorageBuffer; sortElementCountBuffer: StorageBuffer; totalCapacity: number; radialSort: boolean; numBits: number; minDist: number; maxDist: number; alphaClip: number; minPixelSize: number; minContribution: number; viewportWidth: number; viewportHeight: number; flipY: boolean; pickMode?: boolean; fisheyeProj?: import("../graphics/fisheye-projection.js").FisheyeProjection; antiAlias?: boolean; }): void; /** * Writes per-frame indirect draw / dispatch arguments derived from `renderCounter[0]`. * * @param {number} drawSlot - Slot index in `device.indirectDrawBuffer`. * @param {number} sortSlotBase - Base slot index in `device.indirectDispatchBuffer`. The * radix sort backend uses `sortIndirectInfo[0]` consecutive slots starting here. * @param {StorageBuffer} numSplatsBuffer - Storage buffer the vertex shader reads for the * post-cull splat count (single u32 element). * @param {StorageBuffer} sortElementCountBuffer - Storage buffer the radix sort reads for * its element count (single u32 element). * @param {Uint32Array} sortIndirectInfo - Sorter-owned 4-element Uint32 array returned by * `ComputeRadixSort.prepareIndirect()`, used as a `vec4<u32>` uniform by the shader. */ writeIndirectArgs(drawSlot: number, sortSlotBase: number, numSplatsBuffer: StorageBuffer, sortElementCountBuffer: StorageBuffer, sortIndirectInfo: Uint32Array): void; } import type { GraphicsDevice } from '../../platform/graphics/graphics-device.js'; import { StorageBuffer } from '../../platform/graphics/storage-buffer.js'; import { GSplatSortBinWeights } from './gsplat-sort-bin-weights.js'; import { Compute } from '../../platform/graphics/compute.js'; import { BindGroupFormat } from '../../platform/graphics/bind-group-format.js'; import { UniformBufferFormat } from '../../platform/graphics/uniform-buffer-format.js'; import type { GSplatWorkBuffer } from './gsplat-work-buffer.js'; import type { GraphNode } from '../graph-node.js';