UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

218 lines (217 loc) 6.91 kB
import { Mat4 } from "../../core/math/mat4.js"; import { Vec2 } from "../../core/math/vec2.js"; import { BoundingBox } from "../../core/shape/bounding-box.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 tmpSize = new Vec2(); let subDrawDataArray = new Uint32Array(0); const _fullRangeInterval = [0, 0]; class GSplatInfo { device; resource; node; lodIndex; placementId; allocId; parentPlacementId; numSplats; activeSplats = 0; intervals = []; intervalOffsets = []; intervalAllocIds = []; intervalNodeIndices = []; previousWorldTransform = new Mat4(); aabb = new BoundingBox(); subDrawTexture = null; subDrawCount = 0; numBoundsEntries = 0; boundsBaseIndex = 0; octreeNodes = null; nodeInfos = null; colorAccumulatedTranslation = 0; parameters = null; getWorkBufferModifier = null; getInstanceStreams = null; _consumeRenderDirty = null; constructor(device, resource, placement, consumeRenderDirty = null, octreeNodes = null, nodeInfos = null) { this.device = device; this.resource = resource; this.node = placement.node; this.lodIndex = placement.lodIndex; this.placementId = placement.id; this.allocId = placement.allocId; this.parentPlacementId = octreeNodes && placement.parentPlacement ? placement.parentPlacement.allocId : placement.allocId; this.numSplats = resource.numSplats; this.aabb.copy(placement.aabb); this.parameters = placement.parameters; this.getWorkBufferModifier = () => placement.workBufferModifier; this.getInstanceStreams = () => placement.streams; this._consumeRenderDirty = consumeRenderDirty; this.octreeNodes = octreeNodes; this.nodeInfos = nodeInfos; this.updateIntervals(placement.intervals); } destroy() { this.intervals.length = 0; this.intervalOffsets.length = 0; this.intervalAllocIds.length = 0; this.intervalNodeIndices.length = 0; this.subDrawTexture?.destroy(); this.subDrawTexture = null; this.subDrawCount = 0; } setLayout(intervalOffsets) { this.intervalOffsets = intervalOffsets; this.subDrawTexture?.destroy(); this.subDrawTexture = null; this.subDrawCount = 0; } ensureSubDrawTexture(textureWidth) { if (!this.subDrawTexture && textureWidth > 0) { this.updateSubDraws(textureWidth); } } updateIntervals(intervals) { const resource = this.resource; this.intervals.length = 0; this.intervalAllocIds.length = 0; this.intervalNodeIndices.length = 0; this.activeSplats = resource.numSplats; if (intervals.size > 0) { let totalCount = 0; let k = 0; this.intervals.length = intervals.size * 2; for (const [nodeIndex, interval] of intervals) { this.intervals[k++] = interval.x; this.intervals[k++] = interval.y + 1; totalCount += interval.y - interval.x + 1; if (this.nodeInfos) { this.intervalAllocIds.push(this.nodeInfos[nodeIndex].allocId); this.intervalNodeIndices.push(nodeIndex); } } if (this.octreeNodes) { this.activeSplats = totalCount; this.numBoundsEntries = this.octreeNodes.length; } else if (totalCount === this.numSplats) { this.intervals.length = 0; } else { this.activeSplats = totalCount; } } else { this.numBoundsEntries = 1; this.intervalAllocIds.push(this.allocId); const totalCapacity = resource.maxSplats; if (totalCapacity && this.activeSplats < totalCapacity) { this.intervals[0] = 0; this.intervals[1] = this.activeSplats; } } } appendSubDraws(subDrawData, subDrawCount, sourceBase, size, targetOffset, textureWidth) { let remaining = size; let row = targetOffset / textureWidth | 0; const col = targetOffset % textureWidth; if (col > 0) { const count = Math.min(remaining, textureWidth - col); const idx = subDrawCount * 4; subDrawData[idx] = row | 1 << 16; subDrawData[idx + 1] = col; subDrawData[idx + 2] = col + count; subDrawData[idx + 3] = sourceBase; subDrawCount++; sourceBase += count; remaining -= count; row++; } const fullRows = remaining / textureWidth | 0; if (fullRows > 0) { const idx = subDrawCount * 4; subDrawData[idx] = row | fullRows << 16; subDrawData[idx + 1] = 0; subDrawData[idx + 2] = textureWidth; subDrawData[idx + 3] = sourceBase; subDrawCount++; sourceBase += fullRows * textureWidth; remaining -= fullRows * textureWidth; row += fullRows; } if (remaining > 0) { const idx = subDrawCount * 4; subDrawData[idx] = row | 1 << 16; subDrawData[idx + 1] = 0; subDrawData[idx + 2] = remaining; subDrawData[idx + 3] = sourceBase; subDrawCount++; } return subDrawCount; } updateSubDraws(textureWidth) { let intervals = this.intervals; let numIntervals = intervals.length / 2; if (numIntervals === 0) { _fullRangeInterval[0] = 0; _fullRangeInterval[1] = this.activeSplats; intervals = _fullRangeInterval; numIntervals = 1; } const maxSubDraws = numIntervals * 3; const requiredSize = maxSubDraws * 4; if (subDrawDataArray.length < requiredSize) { subDrawDataArray = new Uint32Array(requiredSize); } const subDrawData = subDrawDataArray; let subDrawCount = 0; for (let i = 0; i < numIntervals; i++) { subDrawCount = this.appendSubDraws( subDrawData, subDrawCount, intervals[i * 2], intervals[i * 2 + 1] - intervals[i * 2], this.intervalOffsets[i], textureWidth ); } this.subDrawCount = subDrawCount; const { x: texWidth, y: texHeight } = TextureUtils.calcTextureSize(subDrawCount, tmpSize); this.subDrawTexture = Texture.createDataTexture2D(this.device, "subDrawData", texWidth, texHeight, PIXELFORMAT_RGBA32U); const texData = this.subDrawTexture.lock(); texData.set(subDrawData.subarray(0, subDrawCount * 4)); this.subDrawTexture.unlock(); } update() { const worldMatrix = this.node.getWorldTransform(); const worldMatrixChanged = !this.previousWorldTransform.equals(worldMatrix); if (worldMatrixChanged) { this.previousWorldTransform.copy(worldMatrix); } const renderDirty = this._consumeRenderDirty ? this._consumeRenderDirty() : false; return worldMatrixChanged || renderDirty; } writeBoundsSpheres(data, offset) { if (this.octreeNodes) { for (let i = 0; i < this.octreeNodes.length; i++) { const s = this.octreeNodes[i].boundingSphere; data[offset++] = s.x; data[offset++] = s.y; data[offset++] = s.z; data[offset++] = s.w; } } else { const aabb = this.resource.aabb; const he = aabb.halfExtents; const r = Math.sqrt(he.x * he.x + he.y * he.y + he.z * he.z); data[offset++] = aabb.center.x; data[offset++] = aabb.center.y; data[offset++] = aabb.center.z; data[offset++] = r; } } get hasSphericalHarmonics() { return this.resource.gsplatData?.shBands > 0; } } export { GSplatInfo };