Skip to content

Avoid LoD texture upload stalls during .rad animation - #420

Closed
miakh wants to merge 1 commit into
sparkjsdev:mainfrom
miakh:fix/347-lod-animation-stutter
Closed

Avoid LoD texture upload stalls during .rad animation#420
miakh wants to merge 1 commit into
sparkjsdev:mainfrom
miakh:fix/347-lod-animation-stutter

Conversation

@miakh

@miakh miakh commented Sep 3, 2026

Copy link
Copy Markdown

Summary

  • replace sampled LoD index textures instead of updating them in place
  • upload replacement ordering textures in four-row chunks and swap them in only after the upload completes
  • retain one previous texture generation so GPU work that still references it can finish safely
  • add focused coverage for texture replacement/disposal and row-offset uploads

Why

Updating a texture that the preceding frame is still sampling can synchronously wait for GPU work. With animated .rad splats, repeated LoD updates turn those waits into visible frame-time spikes during the first animation runs.

This keeps the existing LoD and sorting contracts intact. It changes only the texture upload lifecycle: build an unreferenced replacement, upload it incrementally, then atomically expose it to rendering.

Performance evidence

Reporter-derived reproduction: the same 2.5M-splat .rad scene, viewport, camera orbit, and 1.25 s GLTF animation on both revisions.

Metric Before After
Average frame time 19.2 ms 16.7 ms
95th percentile 67.7 ms 18.7 ms
Worst frame 93.4 ms 32.6 ms
Frames over 50 ms 4 0

Video proof

The same orbit and animation are shown before and after; watch the frame-time timeline and the first-run stalls disappear in the fixed build.

spark-347-before-after.mp4

Validation

  • npm run lint:summary --silent
  • npm test --silent (3/3 passing)
  • npm run build:dev --silent
  • reporter-derived browser reproduction with no page errors

Fixes #347

@oscarlorentzon

Copy link
Copy Markdown
Collaborator

Thanks for this, and welcome.

The diff does three things at once:

  1. It builds a new texture on each update and swaps it in, instead of writing into the old one.
  2. It keeps the previous texture around for one more update.
  3. It uploads the new ordering texture 4 rows per frame instead of all at once.

I have a few questions on the measurements:

  • What do 1 and 2 do to the upload time itself?
  • Were the three measured separately, or only together?
  • How long does 3 hold the sort result, and how does that scale with splat count?

One more question, on the delay. Does the sort order lag visibly during fast camera moves, and does that get worse with more splats?

Also, from the console. On a MacBook in Chrome I get these:

WebGL: INVALID_VALUE: texSubImage2D: no pixels     SparkRenderer.ts:1119

That line is the initTexture call on the new texture.

@miakh

miakh commented Sep 9, 2026

Copy link
Copy Markdown
Author

Thanks for pointing this out. After futher testing, and taking full scene in account, I cannot see that it makes it faster. The chunked upload also delays sorting more as splat count grows, so I’m closing this PR.

@miakh miakh closed this Sep 9, 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.

.rad files causing stutter during animation

2 participants