diff --git a/src/scene/gsplat/gsplat-instance.js b/src/scene/gsplat/gsplat-instance.js index d2a44e72319..8675054ba93 100644 --- a/src/scene/gsplat/gsplat-instance.js +++ b/src/scene/gsplat/gsplat-instance.js @@ -120,10 +120,6 @@ class GSplatInstance { const orderTarget = this.orderBuffer ?? this.orderTexture; this.sorter = new GSplatSorter(device, options.scene); this.sorter.init(orderTarget, numSplats, centers, chunks); - this.sorter.on('updated', (count) => { - this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize); - this.material.setParameter('numSplats', count); - }); this.setHighQualitySH(options.highQualitySH ?? false); } @@ -215,8 +211,12 @@ class GSplatInstance { update() { - // Apply deferred sort results (at most one upload per frame). - this.sorter?.applyPendingSorted(); + // Apply deferred sort results (at most one GPU upload per frame). + const count = this.sorter?.applyPendingSorted() ?? -1; + if (count >= 0) { + this.meshInstance.instancingCount = Math.ceil(count / GSplatResourceBase.instanceSize); + this.material.setParameter('numSplats', count); + } if (this.cameras.length > 0) { diff --git a/src/scene/gsplat/gsplat-sorter.js b/src/scene/gsplat/gsplat-sorter.js index 6d35ab265ca..8a640017302 100644 --- a/src/scene/gsplat/gsplat-sorter.js +++ b/src/scene/gsplat/gsplat-sorter.js @@ -57,13 +57,16 @@ class GSplatSorter extends EventHandler { order: oldOrder }, [oldOrder]); - // Store result for deferred application. Only the latest result is kept, + // Store result for deferred GPU upload. Only the latest result is kept, // avoiding redundant uploads when multiple worker messages arrive between frames. this.orderData = newOrder; this.pendingSorted = { count: msgData.count, data: new Uint32Array(newOrder) }; + + // Notify immediately so listeners can request a new frame (e.g. renderNextFrame). + this.fire('updated'); }; const workerSource = `(${SortWorker.toString()})()`; @@ -118,16 +121,19 @@ class GSplatSorter extends EventHandler { } /** - * Applies the most recent pending sorted result (if any), uploading to GPU - * and firing the 'updated' event. Call once per frame from the instance's update(). + * Applies the most recent pending sorted result (if any), uploading order data to the GPU. + * Call once per frame from the instance's update(). + * + * @returns {number} The splat count from the applied result, or -1 if nothing was pending. */ applyPendingSorted() { if (this.pendingSorted) { const { count, data } = this.pendingSorted; this.pendingSorted = null; this.uploadStream.upload(data, this.target); - this.fire('updated', count); + return count; } + return -1; } setMapping(mapping) {