@@ -27,6 +27,7 @@ const invModelMat = new Mat4();
27
27
const tempNonOctreePlacements = new Set ( ) ;
28
28
const tempOctreePlacements = new Set ( ) ;
29
29
const _updatedSplats = [ ] ;
30
+ const tempOctreesTicked = new Set ( ) ;
30
31
31
32
/**
32
33
* GSplatManager manages the rendering of splats using a work buffer, where all active splats are
@@ -41,6 +42,9 @@ class GSplatManager {
41
42
/** @type {GraphNode } */
42
43
node = new GraphNode ( 'GSplatManager' ) ;
43
44
45
+ /** @type {number } */
46
+ cooldownTicks ;
47
+
44
48
/** @type {GSplatWorkBuffer } */
45
49
workBuffer ;
46
50
@@ -86,13 +90,22 @@ class GSplatManager {
86
90
/** @type {Map<GSplatPlacement, GSplatOctreeInstance> } */
87
91
octreeInstances = new Map ( ) ;
88
92
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
+
89
101
constructor ( device , director , layer , cameraNode ) {
90
102
this . device = device ;
91
103
this . director = director ;
92
104
this . cameraNode = cameraNode ;
93
105
this . workBuffer = new GSplatWorkBuffer ( device ) ;
94
106
this . renderer = new GSplatRenderer ( device , this . node , this . cameraNode , layer , this . workBuffer ) ;
95
107
this . sorter = this . createSorter ( ) ;
108
+ this . cooldownTicks = this . director . assetLoader . cooldownTicks ;
96
109
}
97
110
98
111
destroy ( ) {
@@ -133,11 +146,16 @@ class GSplatManager {
133
146
}
134
147
}
135
148
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
137
150
for ( const [ placement , inst ] of this . octreeInstances ) {
138
151
if ( ! tempOctreePlacements . has ( placement ) ) {
139
152
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 ) ;
141
159
}
142
160
}
143
161
@@ -196,6 +214,33 @@ class GSplatManager {
196
214
} ) ;
197
215
198
216
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
+
199
244
this . worldStates . set ( this . lastWorldStateVersion , newState ) ;
200
245
201
246
this . layerPlacementsDirty = false ;
@@ -233,6 +278,16 @@ class GSplatManager {
233
278
234
279
// render all splats to work buffer
235
280
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
+
236
291
}
237
292
238
293
// update order texture
@@ -313,6 +368,18 @@ class GSplatManager {
313
368
_updatedSplats . length = 0 ;
314
369
}
315
370
}
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
+ }
316
383
}
317
384
318
385
/**
0 commit comments