Skip to content

Decode SOG in chunks to avoid wasm32 OOM on large files (fixes #490) - #491

Open
n3ur0m4n wants to merge 1 commit into
sparkjsdev:mainfrom
n3ur0m4n:fix/sog-chunked-decode
Open

n3ur0m4n wants to merge 1 commit into
sparkjsdev:mainfrom
n3ur0m4n:fix/sog-chunked-decode

Conversation

@n3ur0m4n

Copy link
Copy Markdown
Contributor

Fixes #490

Cause

SogsDecoder kept everything alive at the same time: the zip buffer, the cache of compressed files, the five decoded RGBA images, full-size f32 arrays for every property, plus the receiving PackedSplats. On a 39.5M-splat SOG this adds up to about 4.5 GB, over the 4 GiB wasm32 address space. The allocation failure aborts, which surfaces as unreachable in the worker.

Changes (rust/spark-lib/src/sogs.rs only)

  • finish() takes the buffer with std::mem::take and passes it by value; the zip bytes are dropped right after the files are preloaded.
  • get_file removes an entry from the cache on its last use (use count per name taken from meta.json).
  • The compressed file is dropped right after its image is decoded.
  • No full-size f32 arrays: splats are emitted in MAX_SPLAT_CHUNK blocks with reused buffers, one set_batch per block.
  • shN centroids/labels are loaded once and decoded per block.

Results (Chrome, SOG v2, 39,548,487 splats, 415 MB, no shN)

Peak worker wasm memory Outcome
Before 4028 MiB crash (unreachable)
After 1177 MB loads completely

No regression on a 9.3M-splat SOG and on a streamed SOG.

Tests

Two new tests build 70,000-splat SOG zips in memory (more than one chunk, last one partial), v1 and v2, with 3 shN bands, feed them to SogsDecoder in several pushes and compare every value against a per-splat reference (relative tolerance 1e-6).

The memory64 fix proposed in #490 is independent and not included here.

The decoder kept the zip buffer, the compressed file cache, all decoded
RGBA images and full-size f32 arrays alive at the same time, which
exceeds the 4 GiB wasm32 limit on large files (~40M splats).

- finish() moves the zip buffer into decode_sogs, dropped after preload
- cache entries are removed on their last use
- compressed bytes are dropped right after each image decode
- splats are emitted in MAX_SPLAT_CHUNK blocks via set_batch, with
  reused buffers; shN centroids/labels are loaded once and decoded per
  block
- tests: 70k-splat v1/v2 SOG zips with 3 SH bands, compared against a
  per-splat reference

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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.

Large non-paged SOG (39.5M splats) fails with wasm unreachable: decoder exceeds the 4 GiB wasm32 memory limit

1 participant