playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
345 lines (344 loc) • 14.1 kB
TypeScript
/**
* GSplatManager manages the rendering of splats using a work buffer, where all active splats are
* stored and rendered from. It owns the {@link GSplatWorld} (work buffer, world-state versions,
* streaming, bake), the world version lifecycle ({@link GSplatWorld#markSorted}/`onSorted`), and —
* for the CPU-sort path ({@link GSplatQuadRenderer}) — a web-worker sorter. Each frame it bakes the
* render-ready world state and then delegates per-view work to the active renderer:
*
* - CPU sort (WebGPU + WebGL): the manager sends camera + centers to the worker, which returns a
* sorted order; the quad renderer's vertex shader reads `orderBuffer[vertexId] → splatId`. No
* GPU culling.
* - GPU sort ({@link GSplatHybridRenderer}, WebGPU only): the renderer owns the interval cull +
* compaction, projector, and radix sort; the manager just marks the version sorted and calls
* {@link GSplatRenderer#prepareRenderView}.
*
* @ignore
*/
export class GSplatManager {
/**
* @param {GraphicsDevice} device - The graphics device.
* @param {GSplatDirector} director - The director.
* @param {Layer} layer - The layer.
* @param {GraphNode} cameraNode - The camera node.
*/
constructor(device: GraphicsDevice, director: GSplatDirector, layer: Layer, cameraNode: GraphNode);
/** @type {GraphicsDevice} */
device: GraphicsDevice;
/** @type {GraphNode} */
node: GraphNode;
/**
* Owns the work buffer, versioned world states, allocation, octree/LOD evaluation, streaming,
* budget, and the work-buffer bake. Created 1:1 per manager (no sharing yet).
*
* @type {GSplatWorld}
*/
world: GSplatWorld;
/** @type {GSplatRenderer} */
renderer: GSplatRenderer;
/**
* Casts directional shadows for the GPU-sort (hybrid) forward renderer, which cannot self-cast.
* Null when the forward renderer is CPU-sort (the quad renderer self-casts) or when this
* manager has no shadow render mode. Shares this manager's {@link GSplatWorld}.
*
* @type {GSplatShadowRenderer|null}
*/
shadowRenderer: GSplatShadowRenderer | null;
/**
* Shared GPU scratch for the GPU-sort (hybrid) path, created while a GPU-sort renderer is in use
* and injected into both the forward {@link GSplatHybridRenderer} and the
* {@link GSplatShadowRenderer} so they share the compaction candidate buffer. Null for the
* CPU-sort (quad) renderer.
*
* @type {GSplatHybridRendererScratch|null}
* @private
*/
private _hybridScratch;
/**
* The currently active renderer mode. Starts as undefined so the first
* prepareRendererMode() call always creates the appropriate resources.
*
* @type {number|undefined}
*/
activeRenderer: number | undefined;
/**
* CPU-based sorter (used by the quad renderer; the hybrid renderer owns its own GPU sort).
*
* @type {GSplatUnifiedSorter|null}
*/
cpuSorter: GSplatUnifiedSorter | null;
/**
* Tracks last seen centersVersion per resource ID for detecting centers updates. Used to feed
* the CPU sorter when the world creates a new world-state version.
*
* @type {Map<number, number>}
* @private
*/
private _centersVersions;
/** @type {Vec3} */
lastSortCameraPos: Vec3;
/** @type {Vec3} */
lastSortCameraFwd: Vec3;
/** @type {boolean} */
sortNeeded: boolean;
/**
* Event handle for the graphics device restored event.
*
* @type {EventHandle|null}
* @private
*/
private _deviceRestoredEvent;
/** @type {GraphNode} */
cameraNode: GraphNode;
/** @type {Scene} */
scene: Scene;
/**
* Bitmask flags controlling which render passes this manager participates in.
*
* @type {number|undefined}
*/
renderMode: number | undefined;
/**
* Persistent result objects written (out-param) by the {@link GSplatWorld} APIs to avoid
* per-frame allocation. Consumed synchronously by the manager after each call.
*
* @private
*/
private _updateResult;
/** @private */
private _bakeResult;
/** @private */
private _markResult;
/** @private */
private _formatResult;
/**
* Frame token of the last {@link updateStreaming} run, for once-per-frame dedup between the
* component-system streaming tick and the render path.
*
* @type {number}
* @private
*/
private _lastStreamToken;
/**
* Whether the most recent streaming pass produced new data a render would show (new world-state
* version or work-buffer recreation). Used by the director to decide whether to fire frame:request.
*
* @type {boolean}
* @private
*/
private _streamAdvanced;
/**
* Reused per-call parameter bag passed to the renderer's forward {@link GSplatRenderer#prepareRenderView}.
* Avoids per-frame allocation and keeps the renderer free of a back-reference to the manager/scene.
*
* @type {GSplatRenderViewParams}
* @private
*/
private _renderViewParams;
/**
* Reused per-call parameter bag passed to the renderer's {@link GSplatRenderer#preparePickingView}.
* Separate from {@link _renderViewParams} so mid-frame picking can't corrupt the forward params.
*
* @type {GSplatRenderViewParams}
* @private
*/
private _pickParams;
director: GSplatDirector;
layer: Layer;
destroy(): void;
_destroyed: boolean;
/**
* Handles a graphics context restore: the work buffer render target is recreated empty, so
* force a full rebuild and re-sort to re-materialize the splats from the (auto-restored) source
* textures.
*
* Skipped when the world has streaming octree instances: those destroy and asynchronously
* reload their source resources from URL via their own device-lost handling, and rebuilding the
* work buffer here would render from textures that have been destroyed (and not yet reloaded).
*
* @private
*/
private _onDeviceRestored;
/**
* Destroys CPU sorting resources (worker-based sorter).
*
* @private
*/
private destroyCpuSorting;
/**
* Creates the CPU sorter and prepares it for the current world state. Disables any
* GPU-side indirect draw and hides the mesh until the first sort result arrives.
*
* @private
*/
private initCpuSorting;
get material(): import("../materials/shader-material.js").ShaderMaterial;
/**
* Number of work-buffer blocks uploaded this frame (forwarded from the world for stats).
*
* @type {number}
*/
get bufferCopyUploaded(): number;
/**
* Total number of work-buffer blocks this frame (forwarded from the world for stats).
*
* @type {number}
*/
get bufferCopyTotal(): number;
/**
* True when the CPU sorter has a completed sort result waiting to be applied by a render. Used
* by the director to request a render so the pending result is applied.
*
* @type {boolean}
*/
get hasPendingSort(): boolean;
/**
* Dispatches a renderer-specific pick pipeline and returns the configured pick mesh instance.
* The hybrid renderer refreshes its shared projector/sort buffers for the picker camera and
* returns a transient pick mesh.
*
* @param {object} camera - The camera.
* @param {number} width - Pick target width.
* @param {number} height - Pick target height.
* @returns {import('../mesh-instance.js').MeshInstance|null} The pick mesh instance, or null.
*/
prepareForPicking(camera: object, width: number, height: number): import("../mesh-instance.js").MeshInstance | null;
/**
* Writes the current scene gsplat params into a renderer per-view parameter bag. Lets the
* renderer run its GPU pipeline without a back-reference to the manager or scene.
*
* @param {GSplatRenderViewParams} p - The parameter bag to populate.
* @private
*/
private _writeGsplatParams;
/**
* Fills and returns the reused forward-view parameter bag for the manager's camera.
*
* @returns {GSplatRenderViewParams} The populated {@link _renderViewParams}.
* @private
*/
private _fillRenderViewParams;
/**
* Fills and returns the reused picking parameter bag for the picker camera.
*
* @param {object} camera - The picker camera.
* @param {number} width - Pick target width.
* @param {number} height - Pick target height.
* @returns {GSplatRenderViewParams} The populated {@link _pickParams}.
* @private
*/
private _fillPickParams;
/**
* Creates the CPU sorter (Web Worker based).
*
* @returns {GSplatUnifiedSorter} The created sorter.
*/
createSorter(): GSplatUnifiedSorter;
/**
* Sets the render mode for this manager and its renderer.
*
* @param {number} renderMode - Bitmask flags controlling render passes (GSPLAT_FORWARD, GSPLAT_SHADOW, or both).
* @ignore
*/
setRenderMode(renderMode: number): void;
/**
* Creates or destroys {@link shadowRenderer} to match the current render mode and forward
* renderer. The GPU-sort (hybrid) renderer cannot self-cast shadows, so when shadow rendering
* is requested and the forward renderer uses GPU sort, a dedicated {@link GSplatShadowRenderer}
* casts on its behalf (sharing this manager's world). The CPU-sort quad renderer self-casts, so
* no shadow renderer is created for it.
*
* @private
*/
private _syncShadowRenderer;
/**
* Creates the renderer and sort resources for the given mode. Used at init time.
*
* @param {number} mode - The GSPLAT_RENDERER_* constant.
* @private
*/
private _createRenderer;
/**
* Checks whether the resolved renderer mode has changed and transitions to the new mode
* (CPU raster quad <-> hybrid GPU sort).
*
* @private
*/
private prepareRendererMode;
/**
* Supply the manager with the placements to use. This is used to update the manager when the
* layer's placements have changed, called infrequently.
*
* @param {GSplatPlacement[]} placements - The placements to reconcile with.
*/
reconcile(placements: GSplatPlacement[]): void;
onSorted(count: any, version: any, orderData: any): void;
/**
* On the first sort of a world-state version, advances the render-ready version (cleanup +
* first-sort work-buffer rebuild) and applies the renderer rebuild reaction. The world version
* lifecycle is owned by the manager; the GPU pipeline (in the renderer) assumes a baked,
* render-ready work buffer. This is the synchronous GPU-sort counterpart of {@link onSorted}
* (the async CPU path). No-op once the version has been sorted before.
*
* @param {GSplatWorldState} worldState - The world state about to be sorted.
* @private
*/
private _markSortedIfNeeded;
/**
* Tests if the camera has moved enough to require re-sorting.
* - For radial sorting: only position matters (rotation doesn't affect sort order)
* - For directional sorting: only forward direction matters (position doesn't affect sort order)
*
* @returns {boolean} True if camera moved enough to require re-sorting, otherwise false.
*/
testCameraMovedForSort(): boolean;
/**
* Fires the frame:ready event with current sorting and loading state.
*/
fireFrameReadyEvent(): void;
/**
* CPU streaming pass: work-buffer format sync, renderer-mode transition, and the world's LOD /
* octree streaming / world-state creation. Runs every frame from the component system's
* framerender tick (even when rendering is skipped), and once from {@link update} on the render
* path. Deduped via `token` so it runs at most once per frame. Performs no render-pass / draw
* work — only CPU/IO and GPU resource creation.
*
* @param {number} token - Per-frame token; a repeated token is a no-op (returns the cached result).
* @returns {boolean} True if new data was produced that a render would show (new world-state
* version or work-buffer recreation).
*/
updateStreaming(token: number): boolean;
update(): number;
/**
* Post-cull shadow pass. Called from the director after cullComposition has fitted each
* directional light's shadow-camera frustum, and before the frame graph renders the shadow
* maps. Dispatches the per-light gsplat shadow cull and binds the results. No-op unless this
* manager has a {@link shadowRenderer} (GPU-sort forward path with a shadow render mode).
*/
updateShadows(): void;
/**
* Feeds the CPU sorter the centers for the splats in the latest world-state version. Called
* after the world creates a new version (the version-change detection lives here because the
* CPU sorter is manager-owned).
*
* @private
*/
private _feedCpuSorterCenters;
/**
* Sorts the splats using CPU worker (asynchronous).
*
* @param {GSplatWorldState} lastState - The last world state.
*/
sortCpu(lastState: GSplatWorldState): void;
}
import type { GraphicsDevice } from '../../platform/graphics/graphics-device.js';
import { GraphNode } from '../graph-node.js';
import { GSplatWorld } from './gsplat-world.js';
import type { GSplatRenderer } from './gsplat-renderer.js';
import { GSplatShadowRenderer } from './gsplat-shadow-renderer.js';
import { GSplatUnifiedSorter } from './gsplat-unified-sorter.js';
import { Vec3 } from '../../core/math/vec3.js';
import type { Scene } from '../scene.js';
import type { GSplatDirector } from './gsplat-director.js';
import type { Layer } from '../layer.js';
import type { GSplatPlacement } from './gsplat-placement.js';
import type { GSplatWorldState } from './gsplat-world-state.js';