UNPKG

playcanvas

Version:

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

177 lines (174 loc) 6.69 kB
import { PIXELFORMAT_RGBA16U, getGlslShaderType, SEMANTIC_POSITION, BUFFERUSAGE_COPY_DST, ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_R32U } from '../../platform/graphics/constants.js'; import { RenderTarget } from '../../platform/graphics/render-target.js'; import { StorageBuffer } from '../../platform/graphics/storage-buffer.js'; import { Texture } from '../../platform/graphics/texture.js'; import { UploadStream } from '../../platform/graphics/upload-stream.js'; import { QuadRender } from '../graphics/quad-render.js'; import { ShaderUtils } from '../shader-lib/shader-utils.js'; import glslGsplatCopyToWorkBufferPS from '../shader-lib/glsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js'; import wgslGsplatCopyToWorkBufferPS from '../shader-lib/wgsl/chunks/gsplat/frag/gsplatCopyToWorkbuffer.js'; import glslGsplatCopyInstancedQuadVS from '../shader-lib/glsl/chunks/gsplat/vert/gsplatCopyInstancedQuad.js'; import wgslGsplatCopyInstancedQuadVS from '../shader-lib/wgsl/chunks/gsplat/vert/gsplatCopyInstancedQuad.js'; import { GSplatFrustumCuller } from './gsplat-frustum-culler.js'; import { GSplatWorkBufferRenderPass } from './gsplat-work-buffer-render-pass.js'; import { GSplatStreams } from '../gsplat/gsplat-streams.js'; let id = 0; class WorkBufferRenderInfo { destroy() { this.material?.destroy(); this.quadRender?.destroy(); } constructor(device, key, material, colorOnly, format){ this.material = material; const clonedDefines = new Map(material.defines); const colorStream = format.getStream('dataColor'); if (colorStream.format === PIXELFORMAT_RGBA16U) { clonedDefines.set('GSPLAT_COLOR_UINT', ''); } if (colorOnly) { clonedDefines.set('GSPLAT_COLOR_ONLY', ''); } if (format.getStream('pcId')) { clonedDefines.set('GSPLAT_ID', ''); } const fragmentIncludes = material.hasShaderChunks ? device.isWebGPU ? material.shaderChunks.wgsl : material.shaderChunks.glsl : undefined; const outputStreams = colorOnly ? [ colorStream ] : [ ...format.streams, ...format.extraStreams ]; const fragmentOutputTypes = []; for (const stream of outputStreams){ const info = getGlslShaderType(stream.format); fragmentOutputTypes.push(info.returnType); } const useInstanced = clonedDefines.has('GSPLAT_LOD'); const shaderOptions = { uniqueName: `SplatCopyToWorkBuffer:${key}`, attributes: { vertex_position: SEMANTIC_POSITION }, vertexDefines: clonedDefines, fragmentDefines: clonedDefines, fragmentGLSL: glslGsplatCopyToWorkBufferPS, fragmentWGSL: wgslGsplatCopyToWorkBufferPS, fragmentIncludes: fragmentIncludes, fragmentOutputTypes: fragmentOutputTypes }; if (useInstanced) { shaderOptions.vertexGLSL = glslGsplatCopyInstancedQuadVS; shaderOptions.vertexWGSL = wgslGsplatCopyInstancedQuadVS; } else { shaderOptions.vertexChunk = 'fullscreenQuadVS'; } const shader = ShaderUtils.createShader(device, shaderOptions); this.quadRender = new QuadRender(shader); } } class GSplatWorkBuffer { _createRenderTargets() { this.renderTarget?.destroy(); this.colorRenderTarget?.destroy(); const colorBuffers = this.streams.getTexturesInOrder(); this.renderTarget = new RenderTarget({ name: `GsplatWorkBuffer-MRT-${this.id}`, colorBuffers: colorBuffers, depth: false, flipY: true }); const colorTexture = this.streams.getTexture('dataColor'); this.colorRenderTarget = new RenderTarget({ name: `GsplatWorkBuffer-Color-${this.id}`, colorBuffer: colorTexture, depth: false, flipY: true }); this.renderPass?.init(this.renderTarget); this.colorRenderPass?.init(this.colorRenderTarget); } syncWithFormat() { const prevVersion = this.streams._formatVersion; this.streams.syncWithFormat(this.format); if (prevVersion !== this.streams._formatVersion) { this._createRenderTargets(); } } getTexture(name) { return this.streams.getTexture(name); } destroy() { this.renderPass?.destroy(); this.colorRenderPass?.destroy(); this.streams.destroy(); this.orderTexture?.destroy(); this.orderBuffer?.destroy(); this.renderTarget?.destroy(); this.colorRenderTarget?.destroy(); this.uploadStream.destroy(); this.frustumCuller.destroy(); } get textureSize() { return this.streams.textureDimensions.x; } setOrderData(data) { this.textureSize; if (this.device.isWebGPU) { this.uploadStream.upload(data, this.orderBuffer, 0, data.length); } else { this.uploadStream.upload(data, this.orderTexture, 0, data.length); } } resize(textureSize) { this.renderTarget.resize(textureSize, textureSize); this.colorRenderTarget.resize(textureSize, textureSize); this.streams.resize(textureSize, textureSize); if (this.device.isWebGPU) { const newByteSize = textureSize * textureSize * 4; if (this.orderBuffer.byteSize < newByteSize) { this.orderBuffer.destroy(); this.orderBuffer = new StorageBuffer(this.device, newByteSize, BUFFERUSAGE_COPY_DST); } } else { this.orderTexture.resize(textureSize, textureSize); } } render(splats, cameraNode, colorsByLod, changedAllocIds = null) { if (this.renderPass.update(splats, cameraNode, colorsByLod, changedAllocIds)) { this.renderPass.render(); } } renderColor(splats, cameraNode, colorsByLod, changedAllocIds = null) { if (this.colorRenderPass.update(splats, cameraNode, colorsByLod, changedAllocIds)) { this.colorRenderPass.render(); } } constructor(device, format){ this.id = id++; this.device = device; this.format = format; this.frustumCuller = new GSplatFrustumCuller(device); this.streams = new GSplatStreams(device); this.streams.init(format, 1); this._createRenderTargets(); this.uploadStream = new UploadStream(device); if (device.isWebGPU) { this.orderBuffer = new StorageBuffer(device, 4, BUFFERUSAGE_COPY_DST); } else { this.orderTexture = new Texture(device, { name: 'SplatGlobalOrder', width: 1, height: 1, format: PIXELFORMAT_R32U, mipmaps: false, addressU: ADDRESS_CLAMP_TO_EDGE, addressV: ADDRESS_CLAMP_TO_EDGE }); } this.renderPass = new GSplatWorkBufferRenderPass(device, this); this.renderPass.init(this.renderTarget); this.colorRenderPass = new GSplatWorkBufferRenderPass(device, this, true); this.colorRenderPass.init(this.colorRenderTarget); } } export { GSplatWorkBuffer, WorkBufferRenderInfo };