UNPKG

playcanvas

Version:

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

328 lines (327 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 { hashCode } from "../../core/hash.js"; import { SEMANTIC_POSITION, getGlslShaderType } from "../../platform/graphics/constants.js"; import { BlendState } from "../../platform/graphics/blend-state.js"; import { RenderTarget } from "../../platform/graphics/render-target.js"; import { QuadRender } from "../../scene/graphics/quad-render.js"; import { RenderPassShaderQuad } from "../../scene/graphics/render-pass-shader-quad.js"; import { ShaderUtils } from "../../scene/shader-lib/shader-utils.js"; import { GSPLAT_STREAM_INSTANCE } from "../../scene/constants.js"; import glslGsplatProcess from "../../scene/shader-lib/glsl/chunks/gsplat/frag/gsplatProcess.js"; import wgslGsplatProcess from "../../scene/shader-lib/wgsl/chunks/gsplat/frag/gsplatProcess.js"; class GSplatProcessor { /** * Creates a new GSplatProcessor instance. * * @param {GraphicsDevice} device - The graphics device. * @param {GSplatProcessorBinding} source - Source configuration specifying where to read from. * Can specify resource directly or component (for instance textures). * @param {GSplatProcessorBinding} destination - Destination configuration specifying where to write. * Can specify resource directly or component (for instance textures). * @param {object} options - Shader options for the processing logic. * @param {string} [options.processGLSL] - GLSL code at module scope. Must define a `void process()` * function that implements the processing logic. Can include uniform declarations and helper functions. * @param {string} [options.processWGSL] - WGSL code at module scope. Must define a `fn process()` * function that implements the processing logic. Can include uniform declarations and helper functions. */ constructor(device, source, destination, options) { /** * @type {GraphicsDevice} * @private */ __publicField(this, "_device"); /** * Source binding configuration. * * @type {GSplatProcessorBinding} * @private */ __publicField(this, "_source"); /** * Destination binding configuration. * * @type {GSplatProcessorBinding} * @private */ __publicField(this, "_destination"); /** * Source resource (resolved from binding). * * @type {GSplatResourceBase} * @private */ __publicField(this, "_srcResource"); /** * Destination resource (resolved from binding). * * @type {GSplatResourceBase} * @private */ __publicField(this, "_dstResource"); /** * @type {GSplatStreamDescriptor[]} * @private */ __publicField(this, "_dstStreamDescriptors"); /** * Set of destination stream names for quick lookup. * * @type {Set<string>} * @private */ __publicField(this, "_dstStreamNames"); /** * Whether to use all input streams (no specific source streams requested). * * @type {boolean} * @private */ __publicField(this, "_useAllInputStreams"); /** * Pre-resolved source textures to bind during process(). * * @type {Array<{name: string, texture: TextureType}>} * @private */ __publicField(this, "_srcTextures", []); /** * @type {RenderTarget|null} * @private */ __publicField(this, "_renderTarget", null); /** * @type {QuadRender|null} * @private */ __publicField(this, "_quadRender", null); /** * @type {RenderPassShaderQuad|null} * @private */ __publicField(this, "_renderPass", null); /** * Shader parameters set by the user. * * @type {Map<string, { scopeId: object, data: number|number[]|ArrayBufferView|TextureType|StorageBuffer }>} * @private */ __publicField(this, "_parameters", /* @__PURE__ */ new Map()); /** * The blend state to use when processing. Allows accumulation of results * (e.g., additive blending for painting). Defaults to no blending. * * @type {BlendState} */ __publicField(this, "blendState", BlendState.NOBLEND); this._device = device; this._source = source; this._destination = destination; this._srcResource = source.resource ?? source.component?.resource; this._dstResource = destination.resource ?? destination.component?.resource; Debug.assert(this._srcResource, "GSplatProcessor: Source resource not found. Provide resource or component."); Debug.assert(this._dstResource, "GSplatProcessor: Destination resource not found. Provide resource or component."); this._dstStreamDescriptors = []; this._dstStreamNames = /* @__PURE__ */ new Set(); for (const streamName of destination.streams) { const stream = this._dstResource.format.getStream(streamName); Debug.assert(stream, `GSplatProcessor: Destination stream '${streamName}' not found in resource format.`); if (stream) { this._dstStreamDescriptors.push(stream); this._dstStreamNames.add(stream.name); } } this._useAllInputStreams = !source.streams?.length; const srcFormat = this._srcResource.format; const srcStreams = this._useAllInputStreams ? [...srcFormat.streams, ...srcFormat.extraStreams] : source.streams.map((name) => ({ name })); for (const stream of srcStreams) { const texture = this._resolveTexture(source, stream.name, this._srcResource); Debug.assert(texture, `GSplatProcessor: Texture '${stream.name}' not found`); this._srcTextures.push({ name: stream.name, texture }); } this._createRenderTarget(); this._createShader(options); this._renderPass = new RenderPassShaderQuad(device); this._renderPass.quadRender = this._quadRender; this._renderPass.init(this._renderTarget); this._renderPass.colorOps.clear = false; this._renderPass.depthStencilOps.clearDepth = false; } /** * Destroys this processor and releases all resources. */ destroy() { this._renderTarget?.destroy(); this._renderTarget = null; this._quadRender?.destroy(); this._quadRender = null; this._renderPass?.destroy(); this._renderPass = null; this._parameters.clear(); } /** * Resolves a texture for the given stream name from a binding configuration. * * Resolution order: * 1. Component instance texture (if component provided and stream is instance-level) * 2. Resource texture * * @param {GSplatProcessorBinding} binding - The binding configuration. * @param {string} name - The stream name. * @param {GSplatResourceBase} resource - The resolved resource. * @returns {TextureType|null} The resolved texture, or null if not found. * @private */ _resolveTexture(binding, name, resource) { if (binding.component) { const stream = resource.format.getStream(name); if (stream?.storage === GSPLAT_STREAM_INSTANCE) { const texture2 = binding.component.getInstanceTexture(name); if (texture2) { return texture2; } } } const texture = resource.getTexture(name); if (!texture) { Debug.error(`GSplatProcessor: Texture '${name}' not found`); } return texture; } /** * Creates the MRT render target for destination streams. * * @private */ _createRenderTarget() { const colorBuffers = []; Debug.assert(this._dstStreamDescriptors.length > 0, "GSplatProcessor: No destination streams specified."); for (const stream of this._dstStreamDescriptors) { const texture = this._resolveTexture(this._destination, stream.name, this._dstResource); if (texture) { colorBuffers.push(texture); } else { Debug.error(`GSplatProcessor: Destination texture stream '${stream.name}' not found.`); } } if (colorBuffers.length > 0) { this._renderTarget = new RenderTarget({ name: "GSplatProcessor-MRT", colorBuffers, depth: false, flipY: true }); } } /** * Creates the shader and QuadRender for processing. * * @param {object} options - Shader options. * @private */ _createShader(options) { const { processGLSL = "", processWGSL = "" } = options; const device = this._device; const srcFormat = this._srcResource.format; const dstFormat = this._dstResource.format; let inputDeclarations = ""; let readCode = ""; if (this._useAllInputStreams) { const allStreams = [...srcFormat.streams, ...srcFormat.extraStreams]; const sameResource = this._srcResource === this._dstResource; const inputStreamNames = allStreams.filter((s) => !sameResource || !this._dstStreamNames.has(s.name)).map((s) => s.name); inputDeclarations = srcFormat.getInputDeclarations(inputStreamNames); readCode = srcFormat.getReadCode(); } else { inputDeclarations = srcFormat.getInputDeclarations(this._source.streams); } const outputDeclarations = dstFormat.getOutputDeclarations(this._dstStreamDescriptors); const fragmentOutputTypes = this._dstStreamDescriptors.map((stream) => { const info = getGlslShaderType(stream.format); return info.returnType; }); const defines = /* @__PURE__ */ new Map(); this._srcResource.configureMaterialDefines(defines); defines.set("SH_BANDS", "0"); const isWebGPU = device.isWebGPU; const includes = /* @__PURE__ */ new Map(); includes.set("gsplatProcessInputVS", inputDeclarations); includes.set("gsplatProcessOutputVS", outputDeclarations); includes.set("gsplatProcessReadVS", readCode); includes.set("gsplatProcessChunk", isWebGPU ? processWGSL : processGLSL); const hash = hashCode([ isWebGPU ? processWGSL : processGLSL, this._useAllInputStreams ? "1" : "0" ].join("|")); const outputStreams = this._dstStreamDescriptors.map((s) => s.name).join(","); const shader = ShaderUtils.createShader(device, { uniqueName: `GSplatProcessor:${srcFormat.hash};${hash};out=${outputStreams}`, attributes: { vertex_position: SEMANTIC_POSITION }, vertexDefines: defines, fragmentDefines: defines, vertexChunk: "fullscreenQuadVS", fragmentGLSL: glslGsplatProcess, fragmentWGSL: wgslGsplatProcess, fragmentIncludes: includes, fragmentOutputTypes }); this._quadRender = new QuadRender(shader); } /** * Sets a shader parameter for this processor. Parameters are applied during processing. * * @param {string} name - The name of the parameter (uniform name in shader). * @param {number|number[]|ArrayBufferView|TextureType|StorageBuffer} data - The value for the parameter. */ setParameter(name, data) { const scopeId = this._device.scope.resolve(name); this._parameters.set(name, { scopeId, data }); } /** * Gets a shader parameter value previously set with {@link setParameter}. * * @param {string} name - The name of the parameter. * @returns {number|number[]|ArrayBufferView|TextureType|StorageBuffer|undefined} The parameter value, or undefined if not set. */ getParameter(name) { return this._parameters.get(name)?.data; } /** * Removes a shader parameter. * * @param {string} name - The name of the parameter to remove. */ deleteParameter(name) { this._parameters.delete(name); } /** * Executes the processing, reading from source streams and writing to destination streams. */ process() { if (!this._renderPass) { Debug.warn("GSplatProcessor: Cannot process - not initialized."); return; } const device = this._device; for (const { name, texture } of this._srcTextures) { device.scope.resolve(name).setValue(texture); } device.scope.resolve("splatTextureSize").setValue(this._srcResource.textureDimensions.x); device.scope.resolve("dstTextureSize").setValue(this._dstResource.textureDimensions.x); device.scope.resolve("srcNumSplats").setValue(this._srcResource.numSplats); device.scope.resolve("dstNumSplats").setValue(this._dstResource.numSplats); for (const [name, value] of this._srcResource.parameters) { device.scope.resolve(name).setValue(value); } for (const [, param] of this._parameters) { param.scopeId.setValue(param.data); } this._renderPass.blendState = this.blendState; this._renderPass.render(); } } export { GSplatProcessor };