From ec14a62f7fd634fd7f523e0c4ee04cc15c113951 Mon Sep 17 00:00:00 2001 From: Onder Ilke Sever Date: Thu, 23 Apr 2026 08:50:20 +0200 Subject: [PATCH] fix: fire setDirty when pager fetch pipeline drains (closes #298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a static camera is combined with on-demand rendering, the onDirty chain breaks during paged LOD loading because chunk fetches complete asynchronously — outside any render frame — and nothing signals the renderer that new data is ready to process. Fix: call setDirty() via a new onDirty callback on SplatPager when processFetched() fully empties the fetched queue. This fires after every individual chunk lands, giving progressive rendering. The consumeLodTreeUpdates path is intentionally unchanged; SparkRenderer already calls setDirty() there via the existing lodDirty flow. Co-Authored-By: Claude Sonnet 4.6 --- src/SparkRenderer.ts | 1 + src/SplatPager.ts | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/SparkRenderer.ts b/src/SparkRenderer.ts index 83a96028..a54ea942 100644 --- a/src/SparkRenderer.ts +++ b/src/SparkRenderer.ts @@ -1233,6 +1233,7 @@ export class SparkRenderer extends THREE.Mesh { extSplats: this.pagedExtSplats, maxSplats: this.maxPagedSplats, numFetchers: this.numLodFetchers, + onDirty: () => this.setDirty(), }); const { lodId } = (await worker.call("newLodTree", { diff --git a/src/SplatPager.ts b/src/SplatPager.ts index 58af2f6d..132bf7ef 100644 --- a/src/SplatPager.ts +++ b/src/SplatPager.ts @@ -462,6 +462,7 @@ export interface SplatPagerOptions { * @default 3 */ numFetchers?: number; + onDirty?: () => void; } export class SplatPager { @@ -478,6 +479,7 @@ export class SplatPager { autoDrive: boolean; numFetchers: number; fetchPause = 0; + onDirty?: () => void; splatsChunkToPage: Map< PagedSplats, @@ -573,6 +575,7 @@ export class SplatPager { this.autoDrive = options.autoDrive ?? true; this.numFetchers = options.numFetchers ?? 3; + this.onDirty = options.onDirty; this.splatsChunkToPage = new Map(); this.pageToSplatsChunk = new Array(this.maxPages); @@ -1336,11 +1339,14 @@ export class SplatPager { private processFetched() { const now = performance.now(); + let processed = false; while (true) { const fetched = this.fetched.shift(); if (!fetched) { + if (processed) this.onDirty?.(); break; } + processed = true; const { splats, chunk, data } = fetched; let page = this.allocatePage();