From 269d66a4f60e40196b6eb78331e4adad970b75a2 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Wed, 4 Mar 2026 14:10:31 +0000 Subject: [PATCH] Fire GSplatSorter 'updated' event immediately for renderNextFrame support Move the 'updated' event from applyPendingSorted() to the worker message callback so listeners are notified as soon as sort results arrive. This ensures renderNextFrame is triggered promptly when autoRender is disabled. The splat count is now returned from applyPendingSorted() instead of being passed via the event, and GSplatInstance.update() applies it directly. Made-with: Cursor --- src/scene/gsplat/gsplat-instance.js | 12 ++++++------ src/scene/gsplat/gsplat-sorter.js | 14 ++++++++++---- 2 files changed, 16 insertions(+), 10 deletions(-) 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) {