Skip to content

Commit e8bb5ad

Browse files
mvaligurskyMartin Valigursky
andauthored
refactor: split GSplatRenderer into base class with GSplatQuadRenderer derived class (#8520)
Refactor GSplatRenderer into a thin base class that holds common state (device, node, cameraNode, layer, workBuffer, renderMode) and a derived GSplatQuadRenderer with all instanced-quad rendering logic (material, mesh instance, blend modes, indirect draw). Prepares the architecture for future compute-shader-based splat renderers. Also fixes a pre-existing bug where switching from GPU to CPU sorting could render frames with stale order data before the CPU worker delivers fresh sort results. Made-with: Cursor Co-authored-by: Martin Valigursky <mvaligursky@snapchat.com>
1 parent 4e34b40 commit e8bb5ad

File tree

3 files changed

+486
-359
lines changed

3 files changed

+486
-359
lines changed

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { GraphNode } from '../graph-node.js';
55
import { GSplatInfo } from './gsplat-info.js';
66
import { GSplatUnifiedSorter } from './gsplat-unified-sorter.js';
77
import { GSplatWorkBuffer } from './gsplat-work-buffer.js';
8-
import { GSplatRenderer } from './gsplat-renderer.js';
8+
import { GSplatQuadRenderer } from './gsplat-quad-renderer.js';
99
import { GSplatOctreeInstance } from './gsplat-octree-instance.js';
1010
import { GSplatOctreeResource } from './gsplat-octree.resource.js';
1111
import { GSplatWorldState } from './gsplat-world-state.js';
@@ -29,6 +29,7 @@ import { BlockAllocator } from '../../core/block-allocator.js';
2929
* @import { Layer } from '../layer.js'
3030
* @import { GSplatDirector } from './gsplat-director.js'
3131
* @import { MemBlock } from '../../core/block-allocator.js'
32+
* @import { GSplatRenderer } from './gsplat-renderer.js'
3233
*/
3334

3435
const cameraPosition = new Vec3();
@@ -401,7 +402,7 @@ class GSplatManager {
401402
this._allocator = new BlockAllocator(budget > 0 ? Math.ceil(budget * allocatorGrowMultiplier) : 0, allocatorGrowMultiplier);
402403

403404
this.workBuffer = new GSplatWorkBuffer(device, this.scene.gsplat.format);
404-
this.renderer = new GSplatRenderer(device, this.node, this.cameraNode, layer, this.workBuffer);
405+
this.renderer = new GSplatQuadRenderer(device, this.node, this.cameraNode, layer, this.workBuffer);
405406
this._workBufferFormatVersion = this.workBuffer.format.extraStreamsVersion;
406407

407408
// Initialize sorting strategy based on current gpuSorting setting
@@ -530,11 +531,8 @@ class GSplatManager {
530531
this.cpuSorter.updateCentersForSplats(currentState.splats);
531532
}
532533

533-
// Disable indirect draw on the renderer
534+
// Disable indirect draw on the renderer (also hides until update() restores visibility)
534535
this.renderer.disableIndirectDraw();
535-
536-
// Hide mesh until the CPU sort delivers fresh data
537-
this.renderer.meshInstance.visible = false;
538536
}
539537

540538
get material() {
@@ -1470,8 +1468,11 @@ class GSplatManager {
14701468

14711469
// Apply work buffer updates first (both GPU and CPU)
14721470
// For GPU: ensures sort uses current data
1471+
// Skip when sortedBefore is false — the state is waiting for fresh sort data
1472+
// (e.g. after switching from GPU to CPU sorting) and rendering with stale
1473+
// order data would produce incorrect frames.
14731474
const sortedState = this.worldStates.get(this.sortedVersion);
1474-
if (sortedState) {
1475+
if (sortedState?.sortedBefore) {
14751476
if (this._workBufferRebuildRequired) {
14761477
const count = sortedState.totalActiveSplats;
14771478
this.rebuildWorkBuffer(sortedState, count, true);
@@ -1548,7 +1549,7 @@ class GSplatManager {
15481549
}
15491550

15501551
// renderer update and camera tracking
1551-
if (sortedState) {
1552+
if (sortedState?.sortedBefore) {
15521553
this.renderer.frameUpdate(this.scene.gsplat);
15531554
this.updateColorCameraTracking();
15541555
}

0 commit comments

Comments
 (0)