playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
260 lines (257 loc) • 9.43 kB
JavaScript
import { PIXELFORMAT_RGBA32F, getWgslShaderType, pixelFormatInfo, SHADERSTAGE_COMPUTE, SAMPLETYPE_FLOAT, PIXELFORMAT_RGBA16F, getGlslShaderType, SAMPLETYPE_UINT, SAMPLETYPE_INT } from '../../platform/graphics/constants.js';
import { hashCode } from '../../core/hash.js';
import { GSPLAT_STREAM_INSTANCE, GSPLAT_STREAM_RESOURCE } from '../constants.js';
import { BindTextureFormat } from '../../platform/graphics/bind-group-format.js';
import glslStreamDecl from '../shader-lib/glsl/chunks/gsplat/vert/gsplatStreamDecl.js';
import wgslStreamDecl from '../shader-lib/wgsl/chunks/gsplat/vert/gsplatStreamDecl.js';
import wgslComputeStreamDecl from '../shader-lib/wgsl/chunks/gsplat/vert/gsplatComputeStreamDecl.js';
import glslStreamOutput from '../shader-lib/glsl/chunks/gsplat/vert/gsplatStreamOutput.js';
import wgslStreamOutput from '../shader-lib/wgsl/chunks/gsplat/vert/gsplatStreamOutput.js';
import gsplatContainerFloatReadVS$1 from '../shader-lib/glsl/chunks/gsplat/vert/formats/containerFloatRead.js';
import gsplatContainerFloatReadVS from '../shader-lib/wgsl/chunks/gsplat/vert/formats/containerFloatRead.js';
import glslContainerSimpleRead from '../shader-lib/glsl/chunks/gsplat/vert/formats/containerSimpleRead.js';
import wgslContainerSimpleRead from '../shader-lib/wgsl/chunks/gsplat/vert/formats/containerSimpleRead.js';
const serializeStreams = (streams)=>streams.map((s)=>`${s.name}:${s.format}:${s.storage}`).join(',');
const RE_NAME = /\{name\}/g;
const RE_SAMPLER = /\{sampler\}/g;
const RE_TEXTURE_TYPE = /\{textureType\}/g;
const RE_RETURN_TYPE = /\{returnType\}/g;
const RE_FUNC_NAME = /\{funcName\}/g;
const RE_BINDING = /\{binding\}/g;
const RE_INDEX = /\{index\}/g;
const RE_COLOR_SLOT = /\{colorSlot\}/g;
const RE_DEFINE_GUARD = /\{defineGuard\}/g;
class GSplatFormat {
get hash() {
if (this._hash === undefined) {
const streamsStr = serializeStreams(this.streams);
const extraStr = serializeStreams(this._extraStreams);
this._hash = hashCode(streamsStr + extraStr + this._read);
}
return this._hash;
}
get extraStreamsVersion() {
return this._extraStreamsVersion;
}
get extraStreams() {
return this._extraStreams;
}
get resourceStreams() {
if (this._resourceStreams === null) {
this._resourceStreams = [
...this.streams.filter((s)=>s.storage !== GSPLAT_STREAM_INSTANCE),
...this._extraStreams.filter((s)=>s.storage !== GSPLAT_STREAM_INSTANCE)
];
}
return this._resourceStreams;
}
get instanceStreams() {
if (this._instanceStreams === null) {
this._instanceStreams = this._extraStreams.filter((s)=>s.storage === GSPLAT_STREAM_INSTANCE);
}
return this._instanceStreams;
}
addExtraStreams(streams) {
if (!streams || streams.length === 0) return;
let added = false;
for (const s of streams){
if (this._streamNames.has(s.name)) {
continue;
}
this._extraStreams.push({
name: s.name,
format: s.format,
storage: s.storage ?? GSPLAT_STREAM_RESOURCE
});
this._streamNames.add(s.name);
added = true;
}
if (added) {
this._extraStreamsVersion++;
this._invalidateCaches();
}
}
removeExtraStreams(names) {
if (!this.allowStreamRemoval) {
return;
}
let removed = false;
for (const name of names){
const idx = this._extraStreams.findIndex((s)=>s.name === name);
if (idx !== -1) {
this._extraStreams.splice(idx, 1);
this._streamNames.delete(name);
removed = true;
}
}
if (removed) {
this._extraStreamsVersion++;
this._invalidateCaches();
}
}
getInputDeclarations(streamNames) {
const isWebGPU = this._device.isWebGPU;
const template = isWebGPU ? wgslStreamDecl : glslStreamDecl;
const getShaderType = isWebGPU ? getWgslShaderType : getGlslShaderType;
const lines = [];
let streams = [
...this.streams,
...this._extraStreams
];
if (streamNames) {
streams = streams.filter((s)=>streamNames.includes(s.name));
}
for (const stream of streams){
const info = getShaderType(stream.format);
const funcName = stream.name.charAt(0).toUpperCase() + stream.name.slice(1);
let textureType = info.textureType ?? '';
if (isWebGPU && stream.format === PIXELFORMAT_RGBA32F) {
textureType = 'texture_2d<uff>';
}
const decl = template.replace(RE_NAME, stream.name).replace(RE_SAMPLER, info.sampler ?? '').replace(RE_TEXTURE_TYPE, textureType).replace(RE_RETURN_TYPE, info.returnType).replace(RE_FUNC_NAME, funcName);
lines.push(decl);
}
return lines.join('\n');
}
getReadCode() {
return this._read;
}
getComputeInputDeclarations(startBinding, streamNames) {
const lines = [];
let streams = [
...this.streams,
...this._extraStreams
];
if (streamNames) {
streams = streams.filter((s)=>streamNames.includes(s.name));
}
for(let i = 0; i < streams.length; i++){
const stream = streams[i];
const info = getWgslShaderType(stream.format);
const funcName = stream.name.charAt(0).toUpperCase() + stream.name.slice(1);
let textureType = info.textureType ?? '';
if (stream.format === PIXELFORMAT_RGBA32F) {
textureType = 'texture_2d<uff>';
}
const decl = wgslComputeStreamDecl.replace(RE_BINDING, String(startBinding + i)).replace(RE_NAME, stream.name).replace(RE_TEXTURE_TYPE, textureType).replace(RE_RETURN_TYPE, info.returnType).replace(RE_FUNC_NAME, funcName);
lines.push(decl);
}
return lines.join('\n');
}
getComputeBindFormats(streamNames) {
let streams = [
...this.streams,
...this._extraStreams
];
if (streamNames) {
streams = streams.filter((s)=>streamNames.includes(s.name));
}
return streams.map((stream)=>{
const info = pixelFormatInfo.get(stream.format);
let sampleType = SAMPLETYPE_FLOAT;
if (info?.isUint) sampleType = SAMPLETYPE_UINT;
else if (info?.isInt) sampleType = SAMPLETYPE_INT;
return new BindTextureFormat(stream.name, SHADERSTAGE_COMPUTE, undefined, sampleType, false);
});
}
setWriteCode(writeGLSL, writeWGSL) {
this._write = this._device.isWebGPU ? writeWGSL : writeGLSL;
}
getWriteCode() {
return this._write;
}
getOutputDeclarations(outputStreams) {
const isWebGPU = this._device.isWebGPU;
const lines = [];
const template = isWebGPU ? wgslStreamOutput : glslStreamOutput;
const getShaderType = isWebGPU ? getWgslShaderType : getGlslShaderType;
for(let i = 0; i < outputStreams.length; i++){
const stream = outputStreams[i];
const info = getShaderType(stream.format);
const funcName = stream.name.charAt(0).toUpperCase() + stream.name.slice(1);
const colorSlot = i === 0 ? 'color' : `color${i}`;
const decl = template.replace(RE_FUNC_NAME, funcName).replace(RE_RETURN_TYPE, info.returnType).replace(RE_INDEX, String(i)).replace(RE_COLOR_SLOT, colorSlot).replace(RE_DEFINE_GUARD, '1');
lines.push(decl);
}
return lines.join('\n');
}
getOutputStubs(streams) {
const isWebGPU = this._device.isWebGPU;
const lines = [];
const template = isWebGPU ? wgslStreamOutput : glslStreamOutput;
const getShaderType = isWebGPU ? getWgslShaderType : getGlslShaderType;
for (const stream of streams){
const info = getShaderType(stream.format);
const funcName = stream.name.charAt(0).toUpperCase() + stream.name.slice(1);
const stub = template.replace(RE_FUNC_NAME, funcName).replace(RE_RETURN_TYPE, info.returnType).replace(RE_DEFINE_GUARD, '0');
lines.push(stub);
}
return lines.join('\n');
}
getStream(name) {
let stream = this.streams.find((s)=>s.name === name);
if (!stream) {
stream = this._extraStreams.find((s)=>s.name === name);
}
return stream;
}
_invalidateCaches() {
this._hash = undefined;
this._resourceStreams = null;
this._instanceStreams = null;
}
static createDefaultFormat(device) {
return new GSplatFormat(device, [
{
name: 'dataColor',
format: PIXELFORMAT_RGBA16F
},
{
name: 'dataCenter',
format: PIXELFORMAT_RGBA32F
},
{
name: 'dataScale',
format: PIXELFORMAT_RGBA16F
},
{
name: 'dataRotation',
format: PIXELFORMAT_RGBA16F
}
], {
readGLSL: gsplatContainerFloatReadVS$1,
readWGSL: gsplatContainerFloatReadVS
});
}
static createSimpleFormat(device) {
return new GSplatFormat(device, [
{
name: 'dataCenter',
format: PIXELFORMAT_RGBA32F
},
{
name: 'dataColor',
format: PIXELFORMAT_RGBA16F
}
], {
readGLSL: glslContainerSimpleRead,
readWGSL: wgslContainerSimpleRead
});
}
constructor(device, streams, options){
this.allowStreamRemoval = false;
this._extraStreams = [];
this._streamNames = new Set();
this._extraStreamsVersion = 0;
this._resourceStreams = null;
this._instanceStreams = null;
this._device = device;
this.streams = [
...streams
];
this._streamNames = new Set(this.streams.map((s)=>s.name));
const isWebGPU = device.isWebGPU;
this._read = isWebGPU ? options.readWGSL : options.readGLSL;
}
}
export { GSplatFormat };