Skip to content

Commit 593aa72

Browse files
mvaligurskyMartin Valigursky
andauthored
GSplat using streaming LOD supports unloading files no longer needed (#7955)
* Fixes to SOGS data unloading * one more * some more * cleanup * simplified * inlined * fix * GSplat using streaming LOD supports unloading files no longer needed --------- Co-authored-by: Martin Valigursky <[email protected]>
1 parent 59dda76 commit 593aa72

File tree

11 files changed

+319
-45
lines changed

11 files changed

+319
-45
lines changed

examples/src/examples/gaussian-splatting/lod.example.mjs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@ app.on('destroy', () => {
4747
});
4848

4949
pc.Tracing.set(pc.TRACEID_SHADER_ALLOC, true);
50+
pc.Tracing.set(pc.TRACEID_OCTREE_RESOURCES, true);
5051

5152
const assets = {
5253
// church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/morocco.ply` }),
5354
church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/morocco/lod-meta.json` }),
5455
// church: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/garage/lod-meta.json` }),
5556
logo: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/lod/lod-meta.json` }),
5657
guitar: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/guitar.compressed.ply` }),
57-
skull: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/skull.ply` })
58+
skull: new pc.Asset('gsplat', 'gsplat', { url: `${rootPath}/static/assets/splats/skull.sog` })
5859
};
5960

6061
const assetListLoader = new pc.AssetListLoader(Object.values(assets), app.assets);
@@ -117,8 +118,10 @@ assetListLoader.load(() => {
117118
const cc = /** @type { CameraControls} */ (camera.script.create(CameraControls));
118119
Object.assign(cc, {
119120
sceneSize: 500,
120-
moveSpeed: 0.005,
121-
moveFastSpeed: 0.03,
121+
moveSpeed: 0.05,
122+
moveFastSpeed: 0.3,
123+
// moveSpeed: 0.005,
124+
// moveFastSpeed: 0.03,
122125
enableOrbit: false,
123126
enablePan: false
124127
});

src/core/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@ export const TRACEID_TEXTURES = 'Textures';
146146
*/
147147
export const TRACEID_RENDER_QUEUE = 'RenderQueue';
148148

149+
/**
150+
* Logs the loaded GSplat resources for individual LOD levels of an octree.
151+
*
152+
* @category Debug
153+
*/
154+
export const TRACEID_OCTREE_RESOURCES = 'OctreeResources';
155+
149156
/**
150157
* Logs the GPU timings.
151158
*

src/framework/components/gsplat/gsplat-asset-loader.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class GSplatAssetLoader extends GSplatAssetLoaderBase {
8282
unload(url) {
8383
const asset = this._urlToAsset.get(url);
8484
if (asset) {
85+
this._registry.remove(asset);
8586
asset.unload();
8687
this._urlToAsset.delete(url);
8788
}

src/scene/gsplat-unified/gsplat-asset-loader-base.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ import { Debug } from '../../core/debug.js';
77
* @category Asset
88
*/
99
class GSplatAssetLoaderBase {
10+
/**
11+
* Number of ticks to wait before unloading a zero-ref file.
12+
*/
13+
cooldownTicks = 100;
14+
1015
/**
1116
* Initiates loading of a gsplat asset. This is a fire-and-forget operation that starts
1217
* the loading process.

src/scene/gsplat-unified/gsplat-director.js

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import { GSplatManager } from './gsplat-manager.js';
22

33
/**
4-
* @import { GSplatOctree } from './gsplat-octree.js';
54
* @import { LayerComposition } from '../composition/layer-composition.js'
65
* @import { Camera } from '../camera.js'
76
* @import { Layer } from '../layer.js'
87
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
98
* @import { GraphNode } from '../graph-node.js'
10-
* @import { GSplatOctreeResource } from './gsplat-octree.resource.js'
119
* @import { GSplatAssetLoaderBase } from './gsplat-asset-loader-base.js'
1210
* @import { Scene } from '../scene.js'
1311
*/
@@ -90,13 +88,6 @@ class GSplatDirector {
9088
*/
9189
camerasMap = new Map();
9290

93-
/**
94-
* Global octrees, allowing sharing between its placements.
95-
*
96-
* @type {Map<GSplatOctreeResource, GSplatOctree>}
97-
*/
98-
octrees = new Map();
99-
10091
/**
10192
* @type {GSplatAssetLoaderBase}
10293
*/
@@ -118,6 +109,13 @@ class GSplatDirector {
118109
this.scene = scene;
119110
}
120111

112+
destroy() {
113+
114+
// destroy all gsplat managers
115+
this.camerasMap.forEach(cameraData => cameraData.destroy());
116+
this.camerasMap.clear();
117+
}
118+
121119
getCameraData(camera) {
122120
let cameraData = this.camerasMap.get(camera);
123121
if (!cameraData) {

src/scene/gsplat-unified/gsplat-manager.js

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const invModelMat = new Mat4();
2727
const tempNonOctreePlacements = new Set();
2828
const tempOctreePlacements = new Set();
2929
const _updatedSplats = [];
30+
const tempOctreesTicked = new Set();
3031

3132
/**
3233
* GSplatManager manages the rendering of splats using a work buffer, where all active splats are
@@ -41,6 +42,9 @@ class GSplatManager {
4142
/** @type {GraphNode} */
4243
node = new GraphNode('GSplatManager');
4344

45+
/** @type {number} */
46+
cooldownTicks;
47+
4448
/** @type {GSplatWorkBuffer} */
4549
workBuffer;
4650

@@ -86,13 +90,22 @@ class GSplatManager {
8690
/** @type {Map<GSplatPlacement, GSplatOctreeInstance>} */
8791
octreeInstances = new Map();
8892

93+
/**
94+
* Octree instances scheduled for destruction. We collect their releases and destroy them
95+
* when creating the next world state
96+
*
97+
* @type {GSplatOctreeInstance[]}
98+
*/
99+
octreeInstancesToDestroy = [];
100+
89101
constructor(device, director, layer, cameraNode) {
90102
this.device = device;
91103
this.director = director;
92104
this.cameraNode = cameraNode;
93105
this.workBuffer = new GSplatWorkBuffer(device);
94106
this.renderer = new GSplatRenderer(device, this.node, this.cameraNode, layer, this.workBuffer);
95107
this.sorter = this.createSorter();
108+
this.cooldownTicks = this.director.assetLoader.cooldownTicks;
96109
}
97110

98111
destroy() {
@@ -133,11 +146,16 @@ class GSplatManager {
133146
}
134147
}
135148

136-
// destroy and remove octree instances that are no longer present
149+
// remove octree instances that are no longer present and schedule them for destruction
137150
for (const [placement, inst] of this.octreeInstances) {
138151
if (!tempOctreePlacements.has(placement)) {
139152
this.octreeInstances.delete(placement);
140-
inst.destroy();
153+
154+
// mark world as dirty since octree set changed
155+
this.layerPlacementsDirty = true;
156+
157+
// queue the instance to be processed during next world state creation
158+
this.octreeInstancesToDestroy.push(inst);
141159
}
142160
}
143161

@@ -196,6 +214,33 @@ class GSplatManager {
196214
});
197215

198216
const newState = new GSplatWorldState(this.device, this.lastWorldStateVersion, splats);
217+
218+
// collect file-release requests from octree instances.
219+
for (const [, inst] of this.octreeInstances) {
220+
if (inst.removedCandidates && inst.removedCandidates.size) {
221+
for (const fileIndex of inst.removedCandidates) {
222+
// each entry represents a single decRef
223+
// pending releases will be applied on onSorted for this state
224+
newState.pendingReleases.push([inst.octree, fileIndex]);
225+
}
226+
inst.removedCandidates.clear();
227+
}
228+
}
229+
230+
// handle destruction of octree instances
231+
if (this.octreeInstancesToDestroy.length) {
232+
for (const inst of this.octreeInstancesToDestroy) {
233+
234+
// collect file-release requests from octree instances
235+
const toRelease = inst.getFileDecrements();
236+
for (const fileIndex of toRelease) {
237+
newState.pendingReleases.push([inst.octree, fileIndex]);
238+
}
239+
inst.destroy();
240+
}
241+
this.octreeInstancesToDestroy.length = 0;
242+
}
243+
199244
this.worldStates.set(this.lastWorldStateVersion, newState);
200245

201246
this.layerPlacementsDirty = false;
@@ -233,6 +278,16 @@ class GSplatManager {
233278

234279
// render all splats to work buffer
235280
this.workBuffer.render(worldState.splats, this.cameraNode);
281+
282+
// apply pending file-release requests
283+
if (worldState.pendingReleases && worldState.pendingReleases.length) {
284+
for (const [octree, fileIndex] of worldState.pendingReleases) {
285+
// decrement once for each staged release; refcount system guards against premature unload
286+
octree.decRefCount(fileIndex, this.cooldownTicks);
287+
}
288+
worldState.pendingReleases.length = 0;
289+
}
290+
236291
}
237292

238293
// update order texture
@@ -313,6 +368,18 @@ class GSplatManager {
313368
_updatedSplats.length = 0;
314369
}
315370
}
371+
372+
// tick cooldowns once per frame per unique octree
373+
if (this.octreeInstances.size) {
374+
for (const [, inst] of this.octreeInstances) {
375+
const octree = inst.octree;
376+
if (!tempOctreesTicked.has(octree)) {
377+
tempOctreesTicked.add(octree);
378+
octree.updateCooldownTick(this.director.assetLoader);
379+
}
380+
}
381+
tempOctreesTicked.clear();
382+
}
316383
}
317384

318385
/**

0 commit comments

Comments
 (0)