[LITE] loadGltf - why meshes always duplicated

Hello,

Why in Babylon-Lite gltf-loader is not sharing mesh data (eg when in GLTF file mesh index is the same). “_gpu” on Mesh seems to be ref-counted already, _cpu* could be set to the same values too I guess?..

Best regards,

Pavel

Checking! :slight_smile:

Good catch
This is not intentional. We need a separate Lite Mesh for each glTF node/primitive because its transform and state may differ, but nodes referencing the same glTF mesh should share the immutable geometry.
Currently, loadGltf uploads new GPU buffers for every node/primitive pair despite _gpu already supporting reference counting. The _cpu* geometry can also be shared. I’ll add primitive-level caching so the geometry is uploaded once and retained by each instance.
Thanks for reporting it!

PR is on its way!

That is great, thanks!

PR: fix(loader): share glTF primitive geometry by deltakosh · Pull Request #435 · BabylonJS/Babylon-Lite · GitHub

Thanks again, I see it made it to the master already. :slightly_smiling_face:

May I ask a question though, you don’t have to answer but it might help me understand Babylon-Lite better if you do (an absolute beginner here, not only with the Babylon but also whole JavaScript etc - in my spare time I am relaxing from C++ :wink: ). I thought the fix would have been instead of extractAllMeshes(…) simply grabbing mesh data by index every time and re-creating mesh data from scratch it would be decoupled: Mesh and mesh data separately, mesh data (“_cpu*”, “_gpu”) being created and uploaded independently from scene Mesh which would simply grab needed attributes - it wouldn’t have to even wait for them to finish if they are pre-allocated?). “_gpu” incremented when it is not the first time (if GLTF has stale mesh data’s it could be destroyed later but probably unlikely so not worth optimizing that i guess). I didn’t see yet this device-lost thing you have, but probably since “_gpu” is already ref-counted you have something elsewhere not to re-create/re-upload things more than once? (completely guessing here).

What am I missing, what i failed to understand yet? (I do not mean it as a critique or anything - i am trying to learn).

Thanks,

Pavel

You are not missing anything fundamental :slight_smile:

Your mental model is essentially correct. The confusing part is probably the name extractAllMeshes() : it really extracts mesh instances ( node × primitive ), not unique geometry.

Each glTF node still needs a separate Lite Mesh because transforms, bounds, skins, metadata, visibility, animation state, etc. may differ. However, repeated nodes now share the same immutable CPU arrays and the exact same MeshGPU object. The first owner is implicit; every additional owner increments _gpu._refCount . Disposal decrements it and destroys the GPU buffers only after the final owner disappears. CPU arrays need no explicit refcount because JavaScript’s garbage collector handles shared references.

A separate MeshGeometry { cpu, gpu } object would also be a valid design. I kept this fix more surgical and bundle-conscious: the sharing implementation is dynamically loaded only when an asset actually repeats a primitive. Most accessor extraction is already zero-copy: it creates typed-array views into the GLB data. So the important issue was avoiding persistent duplicate GPU uploads and derived CPU data. An earlier primitive cache could still avoid some temporary derived allocations, so your suggested architecture is not wrong at all.

There is also no GPU-upload wait here: vertex/index buffers are created mapped-at-creation, filled synchronously, then unmapped. WebGPU handles execution asynchronously and preserves ordering. loadGltf() mainly awaits fetching, image decoding, materials, and feature modules because its contract is to return a ready-to-use container.

And yes, your device-loss guess is correct. Device loss invalidates every GPU buffer. Shared geometry has a recovery hook: the first owner encountered uploads one replacement MeshGPU for the new device, and all subsequent owners reuse and retain that same object. Interleaved CPU data also remains lazy and uses copy-on-write so editing one mesh does not alter its siblings.

So the implementation is effectively the ownership model you described, just without introducing a formal long-lived MeshGeometry wrapper :slight_smile:

Thanks for your reply.

I guess bundling could explain it, i was just surprised to see that you’ve made a separate execution branch for this, so now if new feature would be needed, I don’t know, some hooks, or someone would suggest immutable buffers deduplication or something :wink: you would have to do things now in two places. Also there is an extra test only to select which branch to execute. While benefiting from what there is already in GLTF file for free seemed kind of simpler to implement, but it wouldn’t be surgical indeed. Making two branches of code doing essentially the same thing is not something i would normally do so i got confused if maybe I am missing something there… But i think i get it - won’t affect where it is not used and keeping changes to the minimum :slightly_smiling_face:

This is the core philosophy: you do not pay the price of what you do not use.
If this is not going against perf.

For babylon.js, I would have done differently but Lite must be..well small :smiley: At all cost beside perf