Skip to content

Fix LoD dirty stall - #456

Closed
asundqui wants to merge 2 commits into
sparkjsdev:mainfrom
asundqui:fix-lod-dirty-stall
Closed

asundqui wants to merge 2 commits into
sparkjsdev:mainfrom
asundqui:fix-lod-dirty-stall

Conversation

@asundqui

Copy link
Copy Markdown
Contributor

This PR builds on top of #452 , #451 , and #449 and fixes issue labeled D3 from earlier draft PR #428 . The last commit in this PR is stacked on top. Once the previous PRs are merged I'll rebase this onto main before merging. The delta can be seen here: asundqui/spark@fix-paged-late-chunks-add-spark-hooks...fix-lod-dirty-stall

Two new browser tests were added, which both fail before the fixes in the PR and succeed with these changes:

  1. lod.test.ts: "applies a LoD budget change made while the LoD callback is busy": changes the LoD splat budget while the LoD callback is busy and makes sure it automatically drives the update and shows the new splat budget
  2. paged-lod.test.ts: "pages in a chunk that landed while the LoD callback was busy": paged LoD chunk arrives while the LoD callback is busy and makes sure it automatically drives the update and renders the fetched splats

Both tests use the lod.beforeCleanup hook introduced in the last PR.

In order to run these tests deterministically we add two options to Harness.settle():

  • requestRender (default true): Fire off a render request to begin with before waiting to settle
  • ignorePendingLod (default false): Ignore the SparkRenderer.lodDirty flag and SplatPager.hasQueued() signals while waiting to settle. By setting this to true we allow the above new test runner to "settle" even though the LoD state says it's pending, which the bug causes.

Without the fix, if the update happened while the LoD callback was busy, setDirty wouldn't be called, and the LoD stalls until something triggers a re-render and driveLod. Without the ignorePendingLod option, our tests would timeout after 60s, which is a viable test. However, by ignoring the pendingLod we can make the tests finish quickly and see that it never updated from the screenshot.

The fix is to check if there are LoD updates pending after finishing the exclusive LoD worker, and fire off another render loop if so:

      try {
        await this.driveLodExclusive(worker, {
...
        });
      } finally {
        if (
          this.lodDirty ||
          this.lodInitQueue.length > 0 ||
          this.pager?.hasQueued()
        ) {
          this.setDirty();
        }
      }

CI Browser test result with new tests at test nr. 6+12: https://github.com/asundqui/spark/actions/runs/35942654087

@oscarlorentzon oscarlorentzon left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good!

- SparkRenderer: move callback body to driveLodExclusive; finally requests a render if lodDirty, lodInitQueue or queued pager data remain
- Harness: settle() requestRender/ignorePendingLod options, waitUntil options object
- Tests: budget change during callback (lod), chunk landing during callback (paged-lod)
- ci-browser: also run on push to fix-lod-dirty-stall
@asundqui

Copy link
Copy Markdown
Contributor Author

Rebased after merge of #452 . All tests including browser tests successful.

@dmarcos

dmarcos commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

needs rebase

@asundqui

Copy link
Copy Markdown
Contributor Author

@dmarcos This PR was stacked under #457 , which has been merged, so this is effectively merged already!

@asundqui asundqui closed this Sep 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants