UNPKG

playcanvas

Version:

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

378 lines (377 loc) 12.7 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../core/debug.js"; import { BoundingBox } from "../../core/shape/bounding-box.js"; import { GSPLATDATA_COMPACT } from "../constants.js"; import { Mesh } from "../mesh.js"; import { ShaderMaterial } from "../materials/shader-material.js"; import { WorkBufferRenderInfo } from "../gsplat-unified/gsplat-work-buffer.js"; import { GSplatStreams } from "./gsplat-streams.js"; import { GSplatResourceCleanup } from "./gsplat-resource-cleanup.js"; let id = 0; const tempMap = /* @__PURE__ */ new Map(); class GSplatResourceBase { /** * @param {GraphicsDevice} device - The graphics device. * @param {object} gsplatData - Data source with getCenters(), calcAabb(), numSplats, etc. * @param {object} [options] - Construction options. * @param {boolean} [options.prepareCenters] - When omitted or true, calls gsplatData.getCenters() * and stores the result. When false, {@link GSplatResourceBase#centers} stays null until set or * materialized by a subclass (e.g. lazy allocation in GSplatContainer). */ constructor(device, gsplatData, options = {}) { /** * @type {GraphicsDevice} * @ignore */ __publicField(this, "device"); /** * @type {GSplatData | GSplatCompressedData | GSplatSogData} * @ignore */ __publicField(this, "gsplatData"); /** * @type {Float32Array|null} * @protected */ __publicField(this, "_centers", null); /** * Version counter for centers array changes. Remains 0 for static resources. * Only GSplatContainer increments this via its update() method. * * @ignore */ __publicField(this, "centersVersion", 0); /** @type {BoundingBox} */ __publicField(this, "aabb"); /** * @type {Mesh|null} * @ignore */ __publicField(this, "mesh", null); /** * @type {number} * @ignore */ __publicField(this, "id", id++); /** * Cache for work buffer render materials/shaders. Keyed by configuration hash. * Stored per-resource because materials depend on resource-specific configuration * (SH bands, textures, defines). Cleaned up when resource is destroyed. * * @type {Map<string, WorkBufferRenderInfo>} * @ignore */ __publicField(this, "workBufferRenderInfos", /* @__PURE__ */ new Map()); /** * Format descriptor for this resource. Assigned by derived classes. * * @type {GSplatFormat} * @protected */ __publicField(this, "_format", null); /** * Manages textures for this resource based on format streams. * * @type {GSplatStreams} * @ignore */ __publicField(this, "streams"); /** * Non-texture uniform parameters required by this resource's format. * This is the single source of truth for format-specific uniforms (e.g., dequantization * parameters) used by both material configuration and processing. * * @type {Map<string, any>} * @ignore */ __publicField(this, "parameters", /* @__PURE__ */ new Map()); /** @private */ __publicField(this, "_refCount", 0); /** @private */ __publicField(this, "_meshRefCount", 0); this.device = device; this.gsplatData = gsplatData; this.streams = new GSplatStreams(device); if (options.prepareCenters !== false) { this._centers = gsplatData.getCenters(); } else { this._centers = null; } this.aabb = new BoundingBox(); gsplatData.calcAabb(this.aabb); } /** * CPU-side splat center positions (xyz per splat), or null when not built for this resource. * * @type {Float32Array|null} */ set centers(value) { this._centers = value; } get centers() { return this._centers; } /** * True when a centers buffer has been allocated ({@link GSplatResourceBase#centers} is non-null). * Reads internal storage only so checks do not trigger lazy allocation in {@link GSplatContainer}. * * @type {boolean} */ get hasCenters() { return this._centers != null; } /** * Destroys this resource. If the resource is still in use by the sorter, destruction is * automatically deferred until it's safe. */ destroy() { if (this.refCount > 0) { GSplatResourceCleanup.queueDestroy(this.device, this); return; } this._actualDestroy(); } /** * Actually destroys this resource and releases all GPU resources. * Derived classes should override this method instead of destroy(). * * @protected */ _actualDestroy() { this.streams.destroy(); this.mesh?.destroy(); this.workBufferRenderInfos.forEach((info) => info.destroy()); this.workBufferRenderInfos.clear(); } /** * Increments the reference count. * * @ignore */ incRefCount() { this._refCount++; } /** * Decrements the reference count. * * @ignore */ decRefCount() { this._refCount--; } /** * Gets the current reference count. This represents how many times this resource is currently * being used internally by the engine. For {@link GSplatComponent#asset|assets} assigned to * {@link GSplatComponent#unified|unified} gsplat components, this tracks active usage during * rendering and sorting operations. * * Resources should not be unloaded while the reference count is non-zero, as they are still * in use by the rendering pipeline. * * @type {number} * @ignore */ get refCount() { return this._refCount; } /** * Ensures mesh and instanceIndices exist. Creates them lazily on first call. Must be paired * with a call to releaseMesh() when done. * * @ignore */ ensureMesh() { if (!this.mesh) { this.mesh = GSplatResourceBase.createMesh(this.device); this.mesh.aabb.copy(this.aabb); } this._meshRefCount++; } /** * Releases reference to mesh. When all references are released, cleans up instanceIndices. * The mesh itself is destroyed by MeshInstance when its internal refCount reaches zero. * * @ignore */ releaseMesh() { this._meshRefCount--; if (this._meshRefCount < 1) { this.mesh = null; } } /** * True when this resource's color-only work buffer updates (spherical harmonics refresh) can * source geometry from the work buffer itself instead of re-reading the source textures. The * resource format's read chunk must compile out getCenter/getRotation/getScale when * GSPLAT_WORKBUFFER_GEOMETRY is defined (see gsplatWorkBufferGeometryPS chunk). * * @type {boolean} * @ignore */ get supportsWorkBufferGeometry() { return false; } /** * Get or create a QuadRender for rendering to work buffer. * * @param {boolean} colorOnly - Whether to render only color (not full MRT). * @param {{ code: string, hash: number }|null} workBufferModifier - Optional custom modifier (object with code and pre-computed hash). * @param {number} formatHash - Captured format hash for shader caching. * @param {string} formatDeclarations - Captured format declarations for shader compilation. * @param {GSplatFormat} workBufferFormat - The work buffer format descriptor. * @returns {WorkBufferRenderInfo} The WorkBufferRenderInfo instance. * @ignore */ getWorkBufferRenderInfo(colorOnly, workBufferModifier, formatHash, formatDeclarations, workBufferFormat) { this.configureMaterialDefines(tempMap); tempMap.set("GSPLAT_LOD", ""); if (colorOnly) { tempMap.set("GSPLAT_COLOR_ONLY", ""); if (this.supportsWorkBufferGeometry) { tempMap.set("GSPLAT_WORKBUFFER_GEOMETRY", ""); if (workBufferFormat.dataFormat === GSPLATDATA_COMPACT) { tempMap.set("GSPLAT_WORKBUFFER_COMPACT", ""); } } } let definesKey = ""; for (const [k, v] of tempMap) { if (definesKey) definesKey += ";"; definesKey += `${k}=${v}`; } const key = `${formatHash};${workBufferFormat.hash};${workBufferModifier?.hash ?? 0};${definesKey}`; let info = this.workBufferRenderInfos.get(key); if (!info) { const material = new ShaderMaterial(); this.configureMaterial(material, workBufferModifier, formatDeclarations); const chunks = this.device.isWebGPU ? material.shaderChunks.wgsl : material.shaderChunks.glsl; const outputStreams = colorOnly ? [workBufferFormat.getStream("dataColor")] : [...workBufferFormat.streams, ...workBufferFormat.extraStreams]; let outputCode = workBufferFormat.getOutputDeclarations(outputStreams); if (colorOnly && workBufferFormat.extraStreams.length > 0) { outputCode += ` ${workBufferFormat.getOutputStubs(workBufferFormat.extraStreams)}`; } chunks.set("gsplatWorkBufferOutputVS", outputCode); const writeCode = workBufferFormat.getWriteCode(); if (writeCode) { chunks.set("gsplatWriteVS", writeCode); } tempMap.forEach((v, k) => material.setDefine(k, v)); info = new WorkBufferRenderInfo(this.device, key, material, colorOnly, workBufferFormat); this.workBufferRenderInfos.set(key, info); } tempMap.clear(); return info; } static createMesh(device) { const splatInstanceSize = GSplatResourceBase.instanceSize; const meshPositions = new Float32Array(12 * splatInstanceSize); const meshIndices = new Uint32Array(6 * splatInstanceSize); for (let i = 0; i < splatInstanceSize; ++i) { meshPositions.set([ -1, -1, i, 1, -1, i, 1, 1, i, -1, 1, i ], i * 12); const b = i * 4; meshIndices.set([ 0 + b, 1 + b, 2 + b, 0 + b, 2 + b, 3 + b ], i * 6); } const mesh = new Mesh(device); mesh.setPositions(meshPositions, 3); mesh.setIndices(meshIndices); mesh.update(); return mesh; } static get instanceSize() { return 128; } get numSplats() { return this.gsplatData.numSplats; } /** * Gets the format descriptor for this resource. The format defines texture streams and * shader code for reading splat data. Use this to add extra streams. * * @type {GSplatFormat} */ get format() { return this._format; } /** * Gets a texture by name. * * @param {string} name - The name of the texture. * @returns {Texture|null} The texture, or null if not found. */ getTexture(name) { return this.streams.getTexture(name) ?? null; } /** * Gets the texture dimensions (width and height) used by this resource's data textures. * * @type {Vec2} */ get textureDimensions() { return this.streams.textureDimensions; } /** * Configures a material to use this resource's data. Base implementation injects format's * shader chunks and binds textures from the streams. * * @param {ShaderMaterial} material - The material to configure. * @param {{ code: string, hash: number }|null} workBufferModifier - Optional custom modifier (object with code and pre-computed hash). * @param {string} formatDeclarations - Captured format declarations for shader compilation. * @ignore */ configureMaterial(material, workBufferModifier, formatDeclarations) { this.configureMaterialDefines(material.defines); this.streams.syncWithFormat(this.format); const chunks = this.device.isWebGPU ? material.shaderChunks.wgsl : material.shaderChunks.glsl; chunks.set("gsplatDeclarationsVS", formatDeclarations); chunks.set("gsplatReadVS", this.format.getReadCode()); if (workBufferModifier?.code) { chunks.set("gsplatModifyVS", workBufferModifier.code); } for (const [name, texture] of this.streams.textures) { material.setParameter(name, texture); } for (const [name, value] of this.parameters) { material.setParameter(name, value); } if (this.textureDimensions.x > 0) { material.setParameter("splatTextureSize", this.textureDimensions.x); } } /** * Configures material defines for this resource. Derived classes should override this. * * @param {Map<string, string|number|boolean>} defines - The defines map to configure. * @ignore */ configureMaterialDefines(defines) { } instantiate() { Debug.removed("GSplatResource.instantiate is removed. Use gsplat component instead"); } } export { GSplatResourceBase };