playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
159 lines (156 loc) • 6.67 kB
JavaScript
import { Vec2 } from '../../core/math/vec2.js';
import { Mat4 } from '../../core/math/mat4.js';
import { Vec3 } from '../../core/math/vec3.js';
import { Quat } from '../../core/math/quat.js';
import { RenderPass } from '../../platform/graphics/render-pass.js';
import { PIXELFORMAT_RGBA32U } from '../../platform/graphics/constants.js';
import { Texture } from '../../platform/graphics/texture.js';
import { TextureUtils } from '../../platform/graphics/texture-utils.js';
const _viewMat = new Mat4();
const _modelScale = new Vec3();
const _modelRotation = new Quat();
const _tmpSize = new Vec2();
const _whiteColor = [
1,
1,
1
];
class GSplatWorkBufferRenderPass extends RenderPass {
destroy() {
this.splats.length = 0;
this._subDrawTexture.destroy();
super.destroy();
}
init(renderTarget) {
super.init(renderTarget);
this.colorOps.clear = false;
this.depthStencilOps.clearDepth = false;
}
update(splats, cameraNode, colorsByLod, changedAllocIds = null) {
this.splats.length = 0;
this._partialData.length = 0;
this.colorsByLod = colorsByLod;
const textureWidth = this.workBuffer.textureSize;
if (changedAllocIds) {
const requiredCapacity = changedAllocIds.size * 3;
if (this._subDrawTexture.width * this._subDrawTexture.height < requiredCapacity) {
TextureUtils.calcTextureSize(requiredCapacity, _tmpSize);
this._subDrawTexture.resize(_tmpSize.x, _tmpSize.y);
}
const texData = this._subDrawTexture.lock();
let writeOffset = 0;
for(let i = 0; i < splats.length; i++){
const splatInfo = splats[i];
if (splatInfo.activeSplats <= 0) continue;
const intervals = splatInfo.intervals;
const numIntervals = intervals.length / 2;
if (numIntervals === 0) {
if (changedAllocIds.has(splatInfo.allocId)) {
this.splats.push(splatInfo);
this._partialData.push(0, 0);
}
} else {
const baseOffset = writeOffset;
const allocIds = splatInfo.intervalAllocIds;
for(let j = 0; j < numIntervals; j++){
if (changedAllocIds.has(allocIds[j])) {
writeOffset = splatInfo.appendSubDraws(texData, writeOffset, intervals[j * 2], intervals[j * 2 + 1] - intervals[j * 2], splatInfo.intervalOffsets[j], textureWidth);
}
}
const count = writeOffset - baseOffset;
if (count > 0) {
this.splats.push(splatInfo);
this._partialData.push(baseOffset, count);
}
}
}
this._subDrawTexture.unlock();
} else {
for(let i = 0; i < splats.length; i++){
const splatInfo = splats[i];
if (splatInfo.activeSplats > 0) {
this.splats.push(splatInfo);
this._partialData.push(0, 0);
}
}
}
for(let i = 0; i < this.splats.length; i++){
if (this._partialData[i * 2 + 1] === 0) {
this.splats[i].ensureSubDrawTexture(textureWidth);
}
}
this.cameraNode = cameraNode;
return this.splats.length > 0;
}
execute() {
const { device, splats, cameraNode, _partialData } = this;
device.setDrawStates();
const viewInvMat = cameraNode.getWorldTransform();
const viewMat = _viewMat.copy(viewInvMat).invert();
device.scope.resolve('matrix_view').setValue(viewMat.data);
for(let i = 0; i < splats.length; i++){
const count = _partialData[i * 2 + 1];
if (count > 0) {
this.renderSplat(splats[i], this._subDrawTexture, count, _partialData[i * 2]);
} else {
this.renderSplat(splats[i]);
}
}
}
renderSplat(splatInfo, overrideSubDrawTexture, overrideSubDrawCount, subDrawBase = 0) {
const { device, resource } = splatInfo;
const scope = device.scope;
const subDrawTexture = overrideSubDrawTexture ?? splatInfo.subDrawTexture;
const subDrawCount = overrideSubDrawCount ?? splatInfo.subDrawCount;
const workBufferModifier = splatInfo.getWorkBufferModifier?.() ?? null;
const formatHash = resource.format.hash;
const formatDeclarations = resource.format.getInputDeclarations();
const workBufferRenderInfo = resource.getWorkBufferRenderInfo(this.colorOnly, workBufferModifier, formatHash, formatDeclarations, this.workBuffer.format);
workBufferRenderInfo.material.setParameters(device);
const color = this.colorsByLod?.[splatInfo.lodIndex] ?? this.colorsByLod?.[0] ?? _whiteColor;
scope.resolve('uColorMultiply').setValue(color);
const worldTransform = splatInfo.node.getWorldTransform();
worldTransform.getScale(_modelScale);
_modelRotation.setFromMat4(worldTransform);
if (_modelRotation.w < 0) {
_modelRotation.mulScalar(-1);
}
this._modelScaleData[0] = _modelScale.x;
this._modelScaleData[1] = _modelScale.y;
this._modelScaleData[2] = _modelScale.z;
this._modelRotationData[0] = _modelRotation.x;
this._modelRotationData[1] = _modelRotation.y;
this._modelRotationData[2] = _modelRotation.z;
this._modelRotationData[3] = _modelRotation.w;
scope.resolve('matrix_model').setValue(worldTransform.data);
scope.resolve('model_scale').setValue(this._modelScaleData);
scope.resolve('model_rotation').setValue(this._modelRotationData);
scope.resolve('uId').setValue(splatInfo.placementId);
if (splatInfo.parameters) {
for (const param of splatInfo.parameters.values()){
param.scopeId.setValue(param.data);
}
}
const instanceStreams = splatInfo.getInstanceStreams?.();
if (instanceStreams) {
instanceStreams.syncWithFormat(splatInfo.resource.format);
for (const [name, texture] of instanceStreams.textures){
scope.resolve(name).setValue(texture);
}
}
scope.resolve('uSubDrawData').setValue(subDrawTexture);
scope.resolve('uSubDrawBase').setValue(subDrawBase);
const ts = this.workBuffer.textureSize;
this._textureSize[0] = ts;
this._textureSize[1] = ts;
scope.resolve('uTextureSize').setValue(this._textureSize);
workBufferRenderInfo.quadRender.render(undefined, undefined, subDrawCount);
}
constructor(device, workBuffer, colorOnly = false){
super(device), this.splats = [], this.colorsByLod = undefined, this.cameraNode = null, this._modelScaleData = new Float32Array(3), this._modelRotationData = new Float32Array(4), this._textureSize = new Int32Array(2), this._partialData = [];
this.workBuffer = workBuffer;
this.colorOnly = colorOnly;
this._subDrawTexture = Texture.createDataTexture2D(device, 'GsplatSubDrawData', 1, 1, PIXELFORMAT_RGBA32U);
}
}
export { GSplatWorkBufferRenderPass };