UNPKG

playcanvas

Version:

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

345 lines (344 loc) 14.1 kB
/** * 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';