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