[Metal][Mac] FMetalDescriptorHeap destructor is empty: every FlushPendingDescriptorUpdates leaks an FRHIHeapDescriptorAllocator + FMetalRHIBuffer (steady memory growth on METAL_SM6)

Summary

On macOS with the METAL_SM6 shader platform, bindless is on by default (BaseMacEngine.ini, [ShaderPlatformConfig METAL_SM6] BindlessConfiguration=All). In Engine/Source/Runtime/Apple/MetalRHI/Private/MetalBindlessDescriptors.cpp, FMetalBindlessDescriptorManager::FlushPendingDescriptorUpdates (line 319) creates a temporary heap on every flush:

FMetalDescriptorHeap* Heap = new FMetalDescriptorHeap(Device, ERHIDescriptorTypeMask::Standard);
Heap->Init(RHICmdList, 256);
...
FMetalDynamicRHI::Get().DeferredDelete([...Heap...]() { delete Heap; ... });

FMetalDescriptorHeap::Init (line 128) allocates ResourceHeap = new FMetalRHIBuffer(…) and Manager = new FRHIHeapDescriptorAllocator(…), but FMetalDescriptorHeap::~FMetalDescriptorHeap() (line 124) is empty, so neither is ever freed. Flushes happen whenever a command context has pending descriptor updates (texture streaming, new views), which in a running game is many times per second.

Engine: 5.8.3 launcher build (CL 58210709). Suggested fix: in ~FMetalDescriptorHeap(), delete Manager and release ResourceHeap (or hold both in owning pointers); reusing one small staging heap per context instead of creating one per flush would also avoid the per-flush buffer creation.

What Type of Bug are you experiencing?

Rendering (Graphics / Niagara)

Steps to Reproduce

  1. Any project on Mac using the default METAL_SM6 shader platform (Apple Silicon, macOS 15+).
  2. Package a Development build and run a level with texture streaming; stand still.
  3. Measure the process over 15+ minutes: footprint, heap PID snapshots (or heap --diffFrom with MallocStackLogging), or Memory Insights / LLM tag SceneRender.

Expected Result

Memory stays flat while idle; each temporary descriptor heap frees its FRHIHeapDescriptorAllocator and its FMetalRHIBuffer when it is deleted.

Observed Result

Steady growth of about 1.1-1.4 MB per minute while standing still. A heap --diffFrom between minute 4 and minute 14 (malloc stack logging on) shows about 8,700 new live 192-byte FRHIHeapDescriptorAllocator objects and 8,700 80-byte arrays per minute, all allocated from FlushPendingDescriptorUpdates → FMetalDescriptorHeap::Init (about 145 flushes per second at 30 fps). Seen on an M3 MacBook Air (16 GB), macOS 26.6.2, UE 5.8.3 packaged Development build.

Workaround: [ShaderPlatformConfig METAL_SM6] BindlessConfiguration=Disabled avoids the code path, but changes the binding model of every shader.

Affects Versions

5.8

Platform(s)

Mac