playcanvas
Version:
PlayCanvas WebGL game engine
212 lines (209 loc) • 8.05 kB
JavaScript
import { Debug } from '../../core/debug.js';
import { Vec2 } from '../../core/math/vec2.js';
import { BoundingBox } from '../../core/shape/bounding-box.js';
import { TYPE_UINT32, SEMANTIC_ATTR13, BUFFER_STATIC, ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST } from '../../platform/graphics/constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { VertexFormat } from '../../platform/graphics/vertex-format.js';
import { VertexBuffer } from '../../platform/graphics/vertex-buffer.js';
import { Mesh } from '../mesh.js';
import { ShaderMaterial } from '../materials/shader-material.js';
import { WorkBufferRenderInfo } from '../gsplat-unified/gsplat-work-buffer.js';
/**
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
* @import { GSplatData } from './gsplat-data.js'
* @import { GSplatCompressedData } from './gsplat-compressed-data.js'
* @import { GSplatSogsData } from './gsplat-sogs-data.js'
*/ let id = 0;
const tempMap = new Map();
/**
* Base class for a GSplat resource and defines common properties.
*
* @ignore
*/ class GSplatResourceBase {
constructor(device, gsplatData){
/** @type {number} */ this.id = id++;
/** @type {Map<string, WorkBufferRenderInfo>} */ this.workBufferRenderInfos = new Map();
/**
* @type {number}
* @private
*/ this._refCount = 0;
this.device = device;
this.gsplatData = gsplatData;
this.centers = gsplatData.getCenters();
this.aabb = new BoundingBox();
gsplatData.calcAabb(this.aabb);
// construct the mesh
this.mesh = GSplatResourceBase.createMesh(device);
this.instanceIndices = GSplatResourceBase.createInstanceIndices(device, gsplatData.numSplats);
// keep extra reference since mesh is shared between instances
this.mesh.incRefCount();
this.mesh.aabb.copy(this.aabb);
}
destroy() {
this.mesh?.destroy();
this.instanceIndices?.destroy();
this.workBufferRenderInfos.forEach((info)=>info.destroy());
this.workBufferRenderInfos.clear();
}
/**
* Increments the reference count.
*
* @ignore
*/ incRefCount() {
this._refCount++;
}
/**
* Decrements the reference count.
*
* @ignore
*/ decRefCount() {
this._refCount--;
}
/**
* Gets the current reference count. This represents how many times this resource is currently
* being used internally by the engine. For {@link GSplatComponent#asset|assets} assigned to
* {@link GSplatComponent#unified|unified} gsplat components, this tracks active usage during
* rendering and sorting operations.
*
* Resources should not be unloaded while the reference count is non-zero, as they are still
* in use by the rendering pipeline.
*
* @type {number}
*/ get refCount() {
return this._refCount;
}
/**
* Get or create a QuadRender for rendering to work buffer.
*
* @param {boolean} useIntervals - Whether to use intervals.
* @param {number} colorTextureFormat - The format of the color texture (RGBA16F or RGBA16U).
* @param {boolean} colorOnly - Whether to render only color (not full MRT).
* @returns {WorkBufferRenderInfo} The WorkBufferRenderInfo instance.
*/ getWorkBufferRenderInfo(useIntervals, colorTextureFormat, colorOnly = false) {
// configure defines to fetch cached data
this.configureMaterialDefines(tempMap);
if (useIntervals) tempMap.set('GSPLAT_LOD', '');
if (colorOnly) tempMap.set('GSPLAT_COLOR_ONLY', '');
const key = Array.from(tempMap.entries()).map(([k, v])=>`${k}=${v}`).join(';');
// get or create quad render
let info = this.workBufferRenderInfos.get(key);
if (!info) {
const material = new ShaderMaterial();
this.configureMaterial(material);
// copy tempMap to material defines
tempMap.forEach((v, k)=>material.setDefine(k, v));
// create new cache entry
info = new WorkBufferRenderInfo(this.device, key, material, colorTextureFormat, colorOnly);
this.workBufferRenderInfos.set(key, info);
}
tempMap.clear();
return info;
}
static createMesh(device) {
// number of quads to combine into a single instance. this is to increase occupancy
// in the vertex shader.
const splatInstanceSize = GSplatResourceBase.instanceSize;
// build the instance mesh
const meshPositions = new Float32Array(12 * splatInstanceSize);
const meshIndices = new Uint32Array(6 * splatInstanceSize);
for(let i = 0; i < splatInstanceSize; ++i){
meshPositions.set([
-1,
-1,
i,
1,
-1,
i,
1,
1,
i,
-1,
1,
i
], i * 12);
const b = i * 4;
meshIndices.set([
0 + b,
1 + b,
2 + b,
0 + b,
2 + b,
3 + b
], i * 6);
}
const mesh = new Mesh(device);
mesh.setPositions(meshPositions, 3);
mesh.setIndices(meshIndices);
mesh.update();
return mesh;
}
static createInstanceIndices(device, splatCount) {
const splatInstanceSize = GSplatResourceBase.instanceSize;
const numSplats = Math.ceil(splatCount / splatInstanceSize) * splatInstanceSize;
const numSplatInstances = numSplats / splatInstanceSize;
const indexData = new Uint32Array(numSplatInstances);
for(let i = 0; i < numSplatInstances; ++i){
indexData[i] = i * splatInstanceSize;
}
const vertexFormat = new VertexFormat(device, [
{
semantic: SEMANTIC_ATTR13,
components: 1,
type: TYPE_UINT32,
asInt: true
}
]);
const instanceIndices = new VertexBuffer(device, vertexFormat, numSplatInstances, {
usage: BUFFER_STATIC,
data: indexData.buffer
});
return instanceIndices;
}
static get instanceSize() {
return 128; // number of splats per instance
}
get numSplats() {
return this.gsplatData.numSplats;
}
configureMaterial(material) {}
configureMaterialDefines(defines) {}
/**
* Evaluates the size of the texture based on the number of splats.
*
* @param {number} count - Number of gaussians.
* @returns {Vec2} Returns a Vec2 object representing the size of the texture.
*/ evalTextureSize(count) {
return Vec2.ZERO;
}
/**
* Creates a new texture with the specified parameters.
*
* @param {string} name - The name of the texture to be created.
* @param {number} format - The pixel format of the texture.
* @param {Vec2} size - The size of the texture in a Vec2 object, containing width (x) and height (y).
* @param {Uint8Array|Uint16Array|Uint32Array} [data] - The initial data to fill the texture with.
* @returns {Texture} The created texture instance.
*/ createTexture(name, format, size, data) {
return new Texture(this.device, {
name: name,
width: size.x,
height: size.y,
format: format,
cubemap: false,
mipmaps: false,
minFilter: FILTER_NEAREST,
magFilter: FILTER_NEAREST,
addressU: ADDRESS_CLAMP_TO_EDGE,
addressV: ADDRESS_CLAMP_TO_EDGE,
...data ? {
levels: [
data
]
} : {}
});
}
instantiate() {
Debug.removed('GSplatResource.instantiate is removed. Use gsplat component instead');
}
}
export { GSplatResourceBase };