godotengine / godotengine/godot
`MultiMeshInstance2D` culling stale after `set_buffer` mutation
- Dominant language
- C++
- Stars
- 117k
- Forks
- 26.8k
- PR merge metrics
- PR metrics pending
Description
### Tested versions
Godot 4.6.x stable. Reproduces on **both** Vulkan and Compatibility (OpenGL) renderers.
### System information
Windows 11, RTX 5070 Ti, both render drivers (Vulkan + Compatibility).
### Issue description
`MultiMeshInstance2D` instances are intermittently culled despite being well within the camera viewport. The CanvasItem's cached visibility rect does not update when the underlying `MultiMesh.buffer` is mutated per-frame (or when the parent `Node2D`'s transform — e.g., `scale` — is mutated per-frame). The renderer reads the stale cached rect and skips the draw call, while every visibility / modulate / z_index property reads as "should draw."
This is the same root cause as #54854 (fixed in 3.x via #54899) and #66849 (closed referencing PR #67116, which was closed unmerged). Sibling: #94151 (same staleness pattern with `ImmediateMesh` + `MeshInstance2D`).
### Two confirmed triggers
1. **Per-frame `MultiMesh.buffer = ...` (or `set_buffer`) mutation.** Capture-and-replay of a frozen buffer renders solidly forever; resuming per-frame buffer assignment intermittently culls all instances.
2. **Per-frame parent-transform mutation.** Even with the buffer frozen, mutating `scale` on the `MultiMeshInstance2D`'s parent `Node2D` per-frame reproduces the cull. Both triggers point to the same stale-CanvasItem-rect mechanism.
### Workaround
Calling `queue_redraw()` on the node holding the `MultiMesh` (the `MultiMeshInstance2D` itself, or its parent if scripts are attached there) immediately after the buffer/transform mutation marks the canvas-item rect dirty so it recomputes. Verified at full 500-instance load on both renderers.
```gdscript
multimesh_instance.multimesh.buffer = new_xf_floats
multimesh_instance.queue_redraw() # workaround
```
### Steps to reproduce
Minimal repro (will attach MRP on request):
1. Add a `MultiMeshInstance2D` with an arbitrary mesh and N instances.
2. From `_physics_process`, mutate `multimesh.buffer` each frame to update instance transforms.
3. Move the camera around — instances disappear intermittently for sustained windows of frames despite remaining well within view.
Adding `queue_redraw()` after the buffer assignment eliminates the issue.
### Notes
- This affects games that use `MultiMeshInstance2D` for crowd / particle / agent rendering with per-frame transform updates — the standard use case.
- Filing as a fresh issue rather than reopening #66849 because that issue closed COMPLETED in 2022 and the actual fix landed in 3.x only; the 4.x port (#67116) was closed unmerged. Happy to consolidate if a maintainer prefers.
- Glad to send an MRP and/or a PR with the equivalent of the 3.x fix ported to 4.x if a maintainer gives a direction.
Contributor guide
Research direction
Start at MultiMeshInstance2D and the CanvasItem visibility-rect invalidation path, then compare the 3.x fix in #54899 with the unmerged 4.x attempt in #67116. Reproduce the per-frame buffer and parent-transform mutations using the listed steps, and verify that instances remain visible without queue_redraw() on both Vulkan and Compatibility renderers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- game-dev, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100