playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
245 lines (244 loc) • 9.71 kB
JavaScript
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
import { Debug } from "../../core/debug.js";
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 { DebugGraphics } from "../../platform/graphics/debug-graphics.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 {
constructor(device, workBuffer, colorOnly = false) {
super(device);
/**
* Array of GSplatInfo objects to render in this pass.
*
* @type {GSplatInfo[]}
*/
__publicField(this, "splats", []);
/** @type {number[][]|undefined} */
__publicField(this, "colorsByLod");
/**
* The camera node used for rendering.
*
* @type {GraphNode}
*/
__publicField(
this,
"cameraNode",
/** @type {any} */
null
);
/** @type {GSplatWorkBuffer} */
__publicField(this, "workBuffer");
/** @type {boolean} */
__publicField(this, "colorOnly");
/** @type {Float32Array} */
__publicField(this, "_modelScaleData", new Float32Array(3));
/** @type {Float32Array} */
__publicField(this, "_modelRotationData", new Float32Array(4));
/** @type {Int32Array} */
__publicField(this, "_textureSize", new Int32Array(2));
/**
* Shared grow-only texture holding packed sub-draw data for all partial renders in a frame.
*
* @type {Texture}
*/
__publicField(this, "_subDrawTexture");
/**
* Flat array of interleaved [baseOffset, count] pairs, parallel to this.splats.
* For splat at index i: _partialData[i*2] = base offset into _subDrawTexture,
* _partialData[i*2+1] = sub-draw count (0 means use splat's own sub-draws).
*
* @type {number[]}
*/
__publicField(this, "_partialData", []);
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();
}
/**
* Initialize the render pass with the specified render target.
*
* @param {RenderTarget} renderTarget - The target to render to.
*/
init(renderTarget) {
super.init(renderTarget);
this.colorOps.clear = false;
this.depthStencilOps.clearDepth = false;
}
/**
* Update the render pass with splats to render and camera.
*
* @param {GSplatInfo[]} splats - Array of GSplatInfo objects to render.
* @param {GraphNode} cameraNode - The camera node for rendering.
* @param {number[][]|undefined} colorsByLod - Optional array of RGB colors per LOD index.
* @param {Set<number>|null} [changedAllocIds] - Set of changed allocIds for partial render.
* @returns {boolean} True if there are splats to render, false otherwise.
*/
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 = (
/** @type {Uint32Array} */
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;
DebugGraphics.pushGpuMarker(device, "GSplatWorkBuffer");
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]);
}
}
DebugGraphics.popGpuMarker(device);
}
/**
* Render a single splat info object. Optionally renders only a subset of sub-draws
* using an override texture and count (for partial work buffer updates).
*
* @param {GSplatInfo} splatInfo - The splat info to render.
* @param {Texture} [overrideSubDrawTexture] - Override sub-draw texture for partial renders.
* @param {number} [overrideSubDrawCount] - Override sub-draw count for partial renders.
* @param {number} [subDrawBase] - Base offset into the sub-draw texture.
*/
renderSplat(splatInfo, overrideSubDrawTexture, overrideSubDrawCount, subDrawBase = 0) {
const { device, resource } = splatInfo;
const scope = device.scope;
Debug.assert(resource);
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
};