UNPKG

playcanvas

Version:

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

185 lines (182 loc) 6.93 kB
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 };