playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
178 lines (177 loc) • 6.37 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 {
splats = [];
colorsByLod = void 0;
cameraNode = null;
workBuffer;
colorOnly;
_modelScaleData = new Float32Array(3);
_modelRotationData = new Float32Array(4);
_textureSize = new Int32Array(2);
_subDrawTexture;
_partialData = [];
constructor(device, workBuffer, colorOnly = false) {
super(device);
this.workBuffer = workBuffer;
this.colorOnly = colorOnly;
this._subDrawTexture = Texture.createDataTexture2D(device, "GsplatSubDrawData", 1, 1, PIXELFORMAT_RGBA32U);
}
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(void 0, void 0, subDrawCount);
}
}
export {
GSplatWorkBufferRenderPass
};