playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
224 lines (223 loc) • 10.7 kB
TypeScript
/**
* 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';