UNPKG

playcanvas

Version:

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

282 lines (281 loc) 10.8 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, DebugHelper } from "../../core/debug.js"; import { ADDRESS_CLAMP_TO_EDGE, PIXELFORMAT_R32U, PIXELFORMAT_RGBA16U, BUFFERUSAGE_COPY_DST, SEMANTIC_POSITION, getGlslShaderType } 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 { /** * @param {GraphicsDevice} device - The graphics device. * @param {string} key - Cache key for this render info. * @param {ShaderMaterial} material - The material to use. * @param {boolean} colorOnly - Whether to render only color (not full MRT). * @param {GSplatFormat} format - The work buffer format descriptor. */ constructor(device, key, material, colorOnly, format) { /** @type {ShaderMaterial} */ __publicField(this, "material"); /** @type {QuadRender} */ __publicField(this, "quadRender"); 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 : void 0; 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, fragmentOutputTypes }; if (useInstanced) { shaderOptions.vertexGLSL = glslGsplatCopyInstancedQuadVS; shaderOptions.vertexWGSL = wgslGsplatCopyInstancedQuadVS; } else { shaderOptions.vertexChunk = "fullscreenQuadVS"; } const shader = ShaderUtils.createShader(device, shaderOptions); this.quadRender = new QuadRender(shader); } destroy() { this.material?.destroy(); this.quadRender?.destroy(); } } class GSplatWorkBuffer { /** * @param {GraphicsDevice} device - The graphics device. * @param {GSplatFormat} format - The work buffer format descriptor. */ constructor(device, format) { /** @type {GraphicsDevice} */ __publicField(this, "device"); /** @type {GSplatFormat} */ __publicField(this, "format"); /** @type {number} */ __publicField(this, "id", id++); /** * Manages textures for format streams. * * @type {GSplatStreams} */ __publicField(this, "streams"); /** * Main MRT render target for all work buffer streams. * * @type {RenderTarget} */ __publicField(this, "renderTarget"); /** * Color-only render target for updating just the dataColor stream. * * @type {RenderTarget} */ __publicField(this, "colorRenderTarget"); /** @type {Texture|undefined} */ __publicField(this, "orderTexture"); /** @type {StorageBuffer|undefined} */ __publicField(this, "orderBuffer"); /** @type {UploadStream} */ __publicField(this, "uploadStream"); /** @type {GSplatWorkBufferRenderPass} */ __publicField(this, "renderPass"); /** @type {GSplatWorkBufferRenderPass} */ __publicField(this, "colorRenderPass"); /** * GPU frustum culler for octree node visibility. * * @type {GSplatFrustumCuller} */ __publicField(this, "frustumCuller"); 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, !device.isWebGPU); if (device.isWebGPU) { this.orderBuffer = new StorageBuffer(device, 4, BUFFERUSAGE_COPY_DST); DebugHelper.setName(this.orderBuffer, "GsplatWorkBuffer.order"); } 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); } /** * Creates or recreates render targets from current textures. * * @private */ _createRenderTargets() { Debug.assert( this.format.instanceStreams.length === 0, "Work buffer format does not support instance-level streams (GSPLAT_STREAM_INSTANCE)" ); this.renderTarget?.destroy(); this.colorRenderTarget?.destroy(); const colorBuffers = this.streams.getTexturesInOrder(); this.renderTarget = new RenderTarget({ name: `GsplatWorkBuffer-MRT-${this.id}`, 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); } /** * Syncs textures and render targets with the format when extra streams are added. * Call this before rendering to ensure all streams have textures. */ syncWithFormat() { const prevVersion = this.streams._formatVersion; this.streams.syncWithFormat(this.format); if (prevVersion !== this.streams._formatVersion) { this._createRenderTargets(); } } /** * Gets a texture by name. * * @param {string} name - The texture name. * @returns {Texture|undefined} The texture, or undefined if not found. */ 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) { const size = this.textureSize; if (this.device.isWebGPU) { Debug.assert(data.length <= size * size); this.uploadStream.upload(data, this.orderBuffer, 0, data.length); } else { Debug.assert(data.length === size * size); this.uploadStream.upload(data, this.orderTexture, 0, data.length); } } /** * @param {number} textureSize - The texture size to resize to. */ resize(textureSize) { Debug.assert(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); DebugHelper.setName(this.orderBuffer, "GsplatWorkBuffer.order"); } } else { this.orderTexture.resize(textureSize, textureSize); } } /** * Render given splats to the work buffer. * * @param {GSplatInfo[]} splats - The splats to render. * @param {GraphNode} cameraNode - The camera node. * @param {number[][]|undefined} colorsByLod - Array of RGB colors per LOD. Index by lodIndex; if a * shorter array is provided, index 0 will be reused as fallback. * @param {Set<number>|null} [changedAllocIds] - When provided, only render sub-draws for intervals * whose allocIds are in this set (per-node partial update). */ render(splats, cameraNode, colorsByLod, changedAllocIds = null) { if (this.renderPass.update(splats, cameraNode, colorsByLod, changedAllocIds)) { this.renderPass.render(); } } /** * Render only the color data to the work buffer (not geometry/covariance). * * @param {GSplatInfo[]} splats - The splats to render. * @param {GraphNode} cameraNode - The camera node. * @param {number[][]|undefined} colorsByLod - Array of RGB colors per LOD. Index by lodIndex; if a * shorter array is provided, index 0 will be reused as fallback. * @param {Set<number>|null} [changedAllocIds] - Set of changed allocIds for partial render. */ renderColor(splats, cameraNode, colorsByLod, changedAllocIds = null) { if (this.colorRenderPass.update(splats, cameraNode, colorsByLod, changedAllocIds)) { this.colorRenderPass.render(); } } } export { GSplatWorkBuffer, WorkBufferRenderInfo };