feat(loader): support sparse accessors#394
Conversation
Implement glTF 2.0 sparse accessor resolution in the Lite glTF loader. A sparse accessor stores a base (a referenced bufferView, or zeros when the bufferView is absent) plus a compact list of (index, value) overrides; per spec, resolution overwrites sparse.count elements at sparse.indices[i] with sparse.values[i]. Previously resolveAccessor zero-filled bufferView-less accessors and never applied the substitution, so SimpleSparseAccessor rendered flat instead of bent. Implemented as a lazily-imported preParse feature (gltf-feature-sparse.ts) that materializes each sparse accessor into a freshly-appended, tightly-packed bufferView of the same componentType and clears .sparse, so resolveAccessor and every downstream consumer (geometry, animation, morph, skeleton, instancing) stay untouched. The module is dynamic-imported only when an accessor actually carries a .sparse property, and the assetUsesGltfFeatures gate is widened to detect sparse-only assets — non-sparse assets pay zero bytes and zero runtime. Fixes SimpleSparseAccessor (cx20/gltf-test); verified pixel-perfect vs Babylon.js. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds glTF 2.0 sparse accessor support to the Babylon Lite glTF loader by materializing sparse accessors during the loader’s preParse phase, so downstream accessor consumers can remain unchanged.
Changes:
- Introduce a new lazy-loaded glTF feature module that resolves sparse accessors by appending tightly-packed materialized data to the binary chunk.
- Register the sparse feature in the glTF feature registry (ordered between meshopt decompression and quantization).
- Expand the “features needed” gate and regenerate per-scene bundle manifests to reflect the new registry chunk hash/size.
Reviewed changes
Copilot reviewed 59 out of 59 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/babylon-lite/src/loader-gltf/load-gltf.ts | Broadens the feature-registry import gate to include sparse-only assets. |
| packages/babylon-lite/src/loader-gltf/gltf-feature-sparse.ts | New preParse feature that materializes sparse accessors into appended binary data and rewrites accessors to point at it. |
| packages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts | Registers the sparse feature with a JSON predicate and positions it in the pre-parse order. |
| lab/public/bundle/manifest/scene99.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene73.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene7.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene5.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene47.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene41.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene39.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene37.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene35.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene34.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene33.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene32.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene31.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene30.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene29.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene28.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene27.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene260.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene257.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene255.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene254.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene253.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene251.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene247.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene246.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene245.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene244.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene243.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene242.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene241.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene240.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene219.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene218.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene212.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene211.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene210.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene178.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene176.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene175.json | Updates runtime chunk hashes after rebuild. |
| lab/public/bundle/manifest/scene174.json | Updates runtime chunk hashes after rebuild. |
| lab/public/bundle/manifest/scene171.json | Updates runtime chunk hashes after rebuild. |
| lab/public/bundle/manifest/scene164.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene158.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene157.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene152.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene149.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene148.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene147.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene146.json | Updates measured gzip size after rebuild. |
| lab/public/bundle/manifest/scene144.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene12.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene115.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene112.json | Updates runtime chunk hashes after rebuild. |
| lab/public/bundle/manifest/scene11.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
| lab/public/bundle/manifest/scene105.json | Updates runtime chunk hashes after rebuild. |
| lab/public/bundle/manifest/scene104.json | Updates measured bundle size and registry chunk hash after feature-registry changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Maintainer-approved minimal bump to cover the ~0.1 KB growth of the shared per-scene gltf-feature-registry detection chunk from registering the lazy sparse-accessor feature: - scene41 (Physics Shape Debug Viewer): 93.2 -> 93.3 KB (measured 93.27) - scene47 (Physics Heightfield): 92.5 -> 92.6 KB (measured 92.59) No extra headroom added. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Address review feedback on gltf-feature-sparse.ts:
- Add a componentBytes() guard that throws a clear error for malformed or
unsupported accessor/index componentTypes, instead of producing NaN offsets
via non-null assertions.
- Require sparse.indices.componentType and bounds-check every sparse target
index (0 <= target < accessor.count) before writing, so an out-of-range
index throws instead of silently corrupting the next materialized accessor.
- Push a complete bufferView for the materialized region ({ buffer: 0,
byteOffset, byteLength }), matching gltf-ext-quantization and satisfying
consumers that expect byteLength.
No behavior change for valid assets; SimpleSparseAccessor still renders
pixel-perfect (resolveAccessor reads only byteOffset from the bin chunk).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
1 similar comment
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
…lues Coordinate the PR4 (sparse) → PR5 (IBL) merge stack: use the final combined bundle-size ceilings (identical to PR5) so scene-config.json does not conflict on merge and the shared gltf-feature-registry chunk growth from BOTH lazy registry entries never trips a mid-stack CI failure. - scene12 (PBR Shader Balls): 103.5 -> 103.6 KB - scene41 (Physics Shape Debug View): 93.3 -> 93.4 KB - scene47 (Physics Heightfield): 92.6 -> 92.7 KB User-approved. No golden changes. Bundle-size green (207 passed), scene12 verified via exact-byte comparison, not rounded KB. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…y cost The EXT_lights_image_based (PR5) lazy feature adds a ~90-byte entry to every glTF scene's gltf-feature-registry chunk. Three scenes sit at ~zero ceiling headroom, so the exact-byte bundle-size check tips over: - scene12 103.5 -> 103.6 (was over by 7 bytes) - scene41 93.2 -> 93.4 - scene47 92.5 -> 92.7 These are the final COMBINED ceilings covering BOTH the sparse (PR4 #394) and IBL (PR5) registry entries on the same zero-headroom scenes, so once both features coexist no further bump is needed. The ~90-byte registry entry is irreducible (any new lazy glTF feature requires it). scene264 stays under its existing 82 KB ceiling. No golden changes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…-accessors # Conflicts: # lab/public/bundle/manifest/scene104.json # lab/public/bundle/manifest/scene105.json # lab/public/bundle/manifest/scene11.json # lab/public/bundle/manifest/scene112.json # lab/public/bundle/manifest/scene115.json # lab/public/bundle/manifest/scene12.json # lab/public/bundle/manifest/scene144.json # lab/public/bundle/manifest/scene146.json # lab/public/bundle/manifest/scene147.json # lab/public/bundle/manifest/scene148.json # lab/public/bundle/manifest/scene149.json # lab/public/bundle/manifest/scene152.json # lab/public/bundle/manifest/scene157.json # lab/public/bundle/manifest/scene158.json # lab/public/bundle/manifest/scene164.json # lab/public/bundle/manifest/scene171.json # lab/public/bundle/manifest/scene174.json # lab/public/bundle/manifest/scene175.json # lab/public/bundle/manifest/scene176.json # lab/public/bundle/manifest/scene178.json # lab/public/bundle/manifest/scene210.json # lab/public/bundle/manifest/scene211.json # lab/public/bundle/manifest/scene212.json # lab/public/bundle/manifest/scene218.json # lab/public/bundle/manifest/scene219.json # lab/public/bundle/manifest/scene240.json # lab/public/bundle/manifest/scene241.json # lab/public/bundle/manifest/scene242.json # lab/public/bundle/manifest/scene243.json # lab/public/bundle/manifest/scene244.json # lab/public/bundle/manifest/scene245.json # lab/public/bundle/manifest/scene246.json # lab/public/bundle/manifest/scene247.json # lab/public/bundle/manifest/scene251.json # lab/public/bundle/manifest/scene253.json # lab/public/bundle/manifest/scene254.json # lab/public/bundle/manifest/scene255.json # lab/public/bundle/manifest/scene257.json # lab/public/bundle/manifest/scene260.json # lab/public/bundle/manifest/scene27.json # lab/public/bundle/manifest/scene28.json # lab/public/bundle/manifest/scene29.json # lab/public/bundle/manifest/scene30.json # lab/public/bundle/manifest/scene31.json # lab/public/bundle/manifest/scene32.json # lab/public/bundle/manifest/scene33.json # lab/public/bundle/manifest/scene34.json # lab/public/bundle/manifest/scene35.json # lab/public/bundle/manifest/scene37.json # lab/public/bundle/manifest/scene39.json # lab/public/bundle/manifest/scene41.json # lab/public/bundle/manifest/scene47.json # lab/public/bundle/manifest/scene5.json # lab/public/bundle/manifest/scene7.json # lab/public/bundle/manifest/scene73.json # lab/public/bundle/manifest/scene99.json
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
…-accessors # Conflicts: # lab/public/bundle/manifest/scene12.json # lab/public/bundle/manifest/scene152.json # lab/public/bundle/manifest/scene178.json # lab/public/bundle/manifest/scene244.json # lab/public/bundle/manifest/scene27.json # lab/public/bundle/manifest/scene28.json # lab/public/bundle/manifest/scene33.json # lab/public/bundle/manifest/scene34.json # lab/public/bundle/manifest/scene39.json # lab/public/bundle/manifest/scene73.json
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
Lab - Static SiteBuild 20260710.16 - merge @ d6494ca |
…-accessors # Conflicts: # lab/public/bundle/manifest/scene104.json # lab/public/bundle/manifest/scene105.json # lab/public/bundle/manifest/scene11.json # lab/public/bundle/manifest/scene112.json # lab/public/bundle/manifest/scene115.json # lab/public/bundle/manifest/scene117.json # lab/public/bundle/manifest/scene12.json # lab/public/bundle/manifest/scene144.json # lab/public/bundle/manifest/scene147.json # lab/public/bundle/manifest/scene148.json # lab/public/bundle/manifest/scene149.json # lab/public/bundle/manifest/scene152.json # lab/public/bundle/manifest/scene157.json # lab/public/bundle/manifest/scene158.json # lab/public/bundle/manifest/scene164.json # lab/public/bundle/manifest/scene171.json # lab/public/bundle/manifest/scene174.json # lab/public/bundle/manifest/scene175.json # lab/public/bundle/manifest/scene176.json # lab/public/bundle/manifest/scene178.json # lab/public/bundle/manifest/scene210.json # lab/public/bundle/manifest/scene211.json # lab/public/bundle/manifest/scene212.json # lab/public/bundle/manifest/scene218.json # lab/public/bundle/manifest/scene219.json # lab/public/bundle/manifest/scene229.json # lab/public/bundle/manifest/scene240.json # lab/public/bundle/manifest/scene241.json # lab/public/bundle/manifest/scene242.json # lab/public/bundle/manifest/scene243.json # lab/public/bundle/manifest/scene244.json # lab/public/bundle/manifest/scene245.json # lab/public/bundle/manifest/scene246.json # lab/public/bundle/manifest/scene247.json # lab/public/bundle/manifest/scene251.json # lab/public/bundle/manifest/scene253.json # lab/public/bundle/manifest/scene254.json # lab/public/bundle/manifest/scene255.json # lab/public/bundle/manifest/scene257.json # lab/public/bundle/manifest/scene259.json # lab/public/bundle/manifest/scene260.json # lab/public/bundle/manifest/scene27.json # lab/public/bundle/manifest/scene28.json # lab/public/bundle/manifest/scene29.json # lab/public/bundle/manifest/scene30.json # lab/public/bundle/manifest/scene31.json # lab/public/bundle/manifest/scene32.json # lab/public/bundle/manifest/scene33.json # lab/public/bundle/manifest/scene34.json # lab/public/bundle/manifest/scene35.json # lab/public/bundle/manifest/scene37.json # lab/public/bundle/manifest/scene39.json # lab/public/bundle/manifest/scene41.json # lab/public/bundle/manifest/scene47.json # lab/public/bundle/manifest/scene5.json # lab/public/bundle/manifest/scene51.json # lab/public/bundle/manifest/scene52.json # lab/public/bundle/manifest/scene7.json # lab/public/bundle/manifest/scene73.json # lab/public/bundle/manifest/scene92.json # lab/public/bundle/manifest/scene97.json # lab/public/bundle/manifest/scene99.json
Bundle Size ChangesIncreases
Sizes rounded to nearest KB. Run |
Summary
Implements glTF 2.0 sparse accessor resolution in the Lite glTF loader. Fixes the
SimpleSparseAccessormodel (from cx20/gltf-test), which previously rendered flat instead of bent. (model #1 of 5 parallel glTF-feature PRs)Spec
A sparse accessor stores a base set of values — either the referenced
bufferView, or an all-zero array whenbufferViewis absent — plus a compact list of(index, value)overrides. Per spec, resolution starts from the base and then overwritessparse.countelements: the element at positionsparse.indices[i]is replaced bysparse.values[i].sparse.indiceshas its ownbufferView+ unsignedcomponentType(UBYTE / USHORT / UINT).sparse.valueshas its ownbufferViewand shares the accessor'scomponentType.Previously
resolveAccessorzero-filled bufferView-less accessors and never applied the substitution, so sparse geometry rendered wrong.Implementation
Rather than teaching the synchronous
resolveAccessor(and its many consumers — geometry, animation, morph, skeleton, instancing) about sparse, this adds apreParsefeature (gltf-feature-sparse.ts) that materializes each sparse accessor before any accessor is read:bufferView(honoringbyteStride), or leave zeros when absent.indices[i] → values[i]), preserving the accessor'scomponentTypebyte-for-byte (normalized accessors are denormalized later, downstream, by their consumer).{ buffer: 0, byteOffset, byteLength }) anddelete a.sparse.Downstream, the accessor looks like any other plain bufferView-backed accessor, so no other loader code changes. This mirrors the existing
gltf-ext-quantization.tspreParse pattern. Feature order is meshopt → sparse → quantization, so a sparse base can read decompressed data and quantization still dequantizes the materialized result.Malformed input is rejected with clear errors: unsupported accessor/index
componentType, a missing requiredindices.componentType, and any out-of-range sparse index (target >= accessor.count) all throw instead of producing NaN offsets or silently corrupting an adjacent materialized accessor.Tree-shaking
Per
GUIDANCE.md§4c′ (always-use-extensions / lazy features):.sparseproperty — registered with an inline predicate ingltf-feature-registry.ts.assetUsesGltfFeaturesgate inload-gltf.tsis widened (/extras|sparse/) so sparse-only assets (like SimpleSparseAccessor, which uses no extensions) still load the registry.Validation
scene116— a pre-existing, documented shadow-depth RTT flake that isskipParityOnCI: trueand does not use the glTF loader.tsc+ ESLint clean.Bundle-size ceilings — combined sparse+IBL values (user-approved) ✅
Registering a lazy feature adds ~0.1 KB to the shared, per-scene
gltf-feature-registrydetection chunk, which every glTF scene loads. To keep the PR4 (sparse) → PR5 (IBL) merge stack conflict-free and avoid a mid-stack CI failure,scene-config.jsonis aligned to the final combined ceilings — identical to PR5 — covering both this PR's sparse entry and PR5's IBL entry (no extra headroom beyond that):On this branch alone all three already pass exact-byte comparison; the combined values leave room for PR5's registry entry so the shared
scene-config.jsonnever conflicts across the stack. The growth is purely the shared registry chunk (one predicate + dynamic-import thunk per feature); the sparse module itself is a separate lazy chunk that no committed scene loads. No golden reference changes.Files
packages/babylon-lite/src/loader-gltf/gltf-feature-sparse.tspackages/babylon-lite/src/loader-gltf/gltf-feature-registry.ts— lazy registrationpackages/babylon-lite/src/loader-gltf/load-gltf.ts— widen feature-detection gatescene-config.json— combined sparse+IBL ceilings for scenes 12/41/47 (matches PR5)lab/public/bundle/manifest/*.json— regenerated (56 scenes)