playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
185 lines (182 loc) • 6.93 kB
JavaScript
import { hashCode } from '../../core/hash.js';
import { getGlslShaderType, SEMANTIC_POSITION } 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 {
destroy() {
this._renderTarget?.destroy();
this._renderTarget = null;
this._quadRender?.destroy();
this._quadRender = null;
this._renderPass?.destroy();
this._renderPass = null;
this._parameters.clear();
}
_resolveTexture(binding, name, resource) {
if (binding.component) {
const stream = resource.format.getStream(name);
if (stream?.storage === GSPLAT_STREAM_INSTANCE) {
const texture = binding.component.getInstanceTexture(name);
if (texture) {
return texture;
}
}
}
const texture = resource.getTexture(name);
return texture;
}
_createRenderTarget() {
const colorBuffers = [];
for (const stream of this._dstStreamDescriptors){
const texture = this._resolveTexture(this._destination, stream.name, this._dstResource);
if (texture) {
colorBuffers.push(texture);
}
}
if (colorBuffers.length > 0) {
this._renderTarget = new RenderTarget({
name: 'GSplatProcessor-MRT',
colorBuffers: colorBuffers,
depth: false,
flipY: true
});
}
}
_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 = new Map();
defines.set('SH_BANDS', '0');
const isWebGPU = device.isWebGPU;
const includes = 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: fragmentOutputTypes
});
this._quadRender = new QuadRender(shader);
}
setParameter(name, data) {
const scopeId = this._device.scope.resolve(name);
this._parameters.set(name, {
scopeId,
data
});
}
getParameter(name) {
return this._parameters.get(name)?.data;
}
deleteParameter(name) {
this._parameters.delete(name);
}
process() {
if (!this._renderPass) {
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();
}
constructor(device, source, destination, options){
this._srcTextures = [];
this._renderTarget = null;
this._quadRender = null;
this._renderPass = null;
this._parameters = new Map();
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;
this._dstStreamDescriptors = [];
this._dstStreamNames = new Set();
for (const streamName of destination.streams){
const stream = this._dstResource.format.getStream(streamName);
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);
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;
}
}
export { GSplatProcessor };