Request a render when a SplatMesh finishes loading - #457
Conversation
| this.initWatched.add(generator); | ||
| generator.initialized.then( | ||
| () => this.setDirty(), | ||
| () => {}, // load errors are reported by the mesh itself |
There was a problem hiding this comment.
SplatMesh does not report load errors itself. Could the comment say what the handler prevents instead?
| () => {}, // load errors are reported by the mesh itself | |
| () => {}, // avoid an unhandled rejection when loading fails |
There was a problem hiding this comment.
That's a good point, and the fact that it will suppress the "unhandled rejection" message is perhaps not good. They'd see the fetch error in the console, but they won't see the fact that they didn't handle it... Maybe not a deal breaker but let me explore other ways we could do this maybe with an event...
4acd45a to
98fa2f2
Compare
|
@oscarlorentzon I updated the PR to use an "initialized" event instead, so it no longer uses the promise chain and potentially alter what the user sees. Looks clean and tests successfully. |
- 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
- SparkRenderer: watch uninitialized visible meshes; initialized.then(setDirty) so on-demand apps show them (behind FIX_MESH_INIT_RENDER) - Harness: settle() waitForLoads option - basic test: mesh whose file lands after the last render - ci-browser: also run on push to fix-on-demand-mesh-init
- SplatGenerator: "initialized" event map; SplatMesh dispatches it once splats are ready - SparkRenderer: one-shot listener replaces initialized.then, so load failures stay visible
321bbf3 to
81cffa0
Compare
|
Rebased after #452 merge. All tests including browser tests still successful. |
| readback32 = new Uint32Array(0); | ||
|
|
||
| // Meshes seen while still loading; a render is requested when they finish. | ||
| private initWatched = new WeakSet<SplatMesh>(); |
There was a problem hiding this comment.
Nitpick: can be made readonly
| generator.removeEventListener("initialized", onInitialized); | ||
| this.setDirty(); |
There was a problem hiding this comment.
The generator can be removed from the initWatched at this point:
this.initWatched.delete(generator);…nt fires; drop the temporary flag
This PR builds on top of #456 , #452 , #451, and #449 and fixes the issue labeled D2 from the 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-lod-dirty-stall...fix-on-demand-mesh-init
In on-demand rendering, a SplatMesh that finishes loading after the app's last render is never shown. Uninitialized meshes are already part of visibleGenerators (contributing 0 splats), but since we don't observe mesh.initialized, when the file lands the scene stays blank until something else triggers a render. This behavior matches the on-demand docs that lists "SplatMesh finished loading" as an onDirty trigger.
One new browser test was added, which fails before the fix and succeeds with it:
basic.test.ts: "shows a mesh that finished loading after the last render": holds back the .spz request, renders once while it is held, releases it, then settles without requesting a render. With the fix it drives the render loop and renders the splats, otherwise renders blank.Normally
Harness.settle()will await all SplatMeshes loading, so we add a new option:waitForLoads(default true): Wait for all meshes' initialized promises before settling (disabled for this test)The fix is in SparkRenderer.updateInternal(): the first time a visible SplatMesh is seen uninitialized, request a render when it finishes:
initWatched is a WeakSet so disposed meshes are not retained, and setDirty() is a no-op when a render is already pending, so apps with a render loop see no extra frames. The fix is currently behind a temporary FIX_MESH_INIT_RENDER flag so reviewers can flip it to false to reproduce; it will be removed before merge, as with FIX_LATE_CHUNKS in #452 .
CI browser test result: https://github.com/asundqui/spark/actions/runs/35953568067
Note: @mrxz pointed out in #428 that perhaps it should be the user's responsibility to request a re-render when their splats finish loading using the
splatMesh.initialized.then(() => requestRender());pattern. However, I feel that not doing this automatically is an easy footgun to fix, with a very low cost of potentially rendering extra frames, one per loadedSplatMesh.