Describe the bug
When AssetLoader::createInstance() is called for an asset that contains morph targets, the later instance creation overwrites the morphTargetBuffer pointer inside the shared Primitive stored in MeshCache.
As a result, ResourceLoader uploads morph target data to the wrong MorphTargetBuffer, and morphing stops working on the first-created instance (for example, texelFetch returns zero-equivalent data and visible deformation does not occur).
This appears to be caused by storing a per-instance MorphTargetBuffer* in Primitive, even though Primitive is shared across instances through MeshCache.
To Reproduce
Steps to reproduce the behavior:
- Load a GLB file that has morph targets (e.g., a VRM model with facial blend shapes)
- Create the first-created instance with
assetLoader.createAsset(buffer)
- Create a second instance with
assetLoader.createInstance(asset)
- Load resources with
resourceLoader.loadResources(asset)
- Set morph weights on the primary instance's renderable entities via
RenderableManager.setMorphWeights()
- Morph targets have no visible effect on the primary instance
Expected behavior
Morph targets should deform the mesh correctly for the first-created instance regardless of whether additional instances exist.
Smartphone (please complete the following information):
- Device: Pixel 6a
- OS: Android 16 (API 36)
- Backend: OpenGL ES (Filament default)
Additional context
The Primitive struct (FFilamentAsset.h:96-104) is shared across all instances via MeshCache:
https://github.com/google/filament/blob/v1.71.0/libs/gltfio/src/FFilamentAsset.h#L96-L104
struct Primitive {
VertexBuffer* vertices = nullptr;
IndexBuffer* indices = nullptr;
Aabb aabb;
UvMap uvmap;
MorphTargetBuffer* morphTargetBuffer = nullptr; // shared, but written per-instance
uint32_t morphTargetOffset;
std::vector<int> slotIndices;
};
using MeshCache = utils::FixedCapacityVector<utils::FixedCapacityVector<Primitive>>;
In AssetLoader.cpp, createRenderable() (line 798-810) creates a new MorphTargetBuffer per instance but stores it in this shared Primitive:
https://github.com/google/filament/blob/v1.71.0/libs/gltfio/src/AssetLoader.cpp#L798-L810
if (numMorphTargets) {
MorphTargetBuffer* morphTargetBuffer = MorphTargetBuffer::Builder()
.count(numMorphTargets)
.vertexCount(morphingVertexCount)
.build(mEngine);
fAsset->mMorphTargetBuffers.push_back(morphTargetBuffer);
builder.morphing(morphTargetBuffer);
outputPrim = prims.data();
inputPrim = &mesh->primitives[0];
for (cgltf_size index = 0; index < primitiveCount; ++index, ++outputPrim, ++inputPrim) {
outputPrim->morphTargetBuffer = morphTargetBuffer; // line 810: overwrites shared Primitive
The BufferSlot entries (line 836, 854) also read from the overwritten outputPrim->morphTargetBuffer, so ResourceLoader::uploadBuffers() uploads morph data to the last-created
instance's buffer for all instances.
Introduced in commit bef004e Confirmed unchanged through v1.71.0.
Describe the bug
When
AssetLoader::createInstance()is called for an asset that contains morph targets, the later instance creation overwrites themorphTargetBufferpointer inside the sharedPrimitivestored inMeshCache.As a result,
ResourceLoaderuploads morph target data to the wrongMorphTargetBuffer, and morphing stops working on the first-created instance (for example,texelFetchreturns zero-equivalent data and visible deformation does not occur).This appears to be caused by storing a per-instance
MorphTargetBuffer*inPrimitive, even thoughPrimitiveis shared across instances throughMeshCache.To Reproduce
Steps to reproduce the behavior:
assetLoader.createAsset(buffer)assetLoader.createInstance(asset)resourceLoader.loadResources(asset)RenderableManager.setMorphWeights()Expected behavior
Morph targets should deform the mesh correctly for the first-created instance regardless of whether additional instances exist.
Smartphone (please complete the following information):
Additional context
The Primitive struct (FFilamentAsset.h:96-104) is shared across all instances via MeshCache:
https://github.com/google/filament/blob/v1.71.0/libs/gltfio/src/FFilamentAsset.h#L96-L104
In AssetLoader.cpp, createRenderable() (line 798-810) creates a new MorphTargetBuffer per instance but stores it in this shared Primitive:
https://github.com/google/filament/blob/v1.71.0/libs/gltfio/src/AssetLoader.cpp#L798-L810
The BufferSlot entries (line 836, 854) also read from the overwritten
outputPrim->morphTargetBuffer, soResourceLoader::uploadBuffers()uploads morph data to the last-createdinstance's buffer for all instances.
Introduced in commit bef004e Confirmed unchanged through v1.71.0.