playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
328 lines (327 loc) • 12.7 kB
JavaScript
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
};