Metal-C++ bindings

Unreal Engine currently uses mtlpp, a C++ wrapper for Metal that was developed in 2016. At that time, Metal could only be used from Objective-C, which was a major hindrance. For example, I was an iOS beginner who had just invested time learning Swift, the language that replaced Objective-C. Yet the Metal sample code was all in ObjC! I had to manually convert it to another language I could understand (either Swift or C++).

mtlpp has been unmaintained since 2018, but UE5 still depends on it. It lacks support for Metal 3 features, such as GPU virtual addresses, and will experience more software rot in the future. Meanwhile, Apple released sample code in 2019 for using Metal with C++. In 2022, they officially announced the metal-cpp bindings. The API is very easy to use and has a pleasant tutorial. The documentation explains things like Cocoa’s ARC memory management. It is constantly being maintained and has an official C++ API from Apple.

For example, here are various examples of metal-cpp being used:

 // Use the buffer's allocated size, never its actual length, when doing size
// calculations.
MTL::Buffer* phantom_buffer = _heap->newBuffer(size, 0);
int64_t offset = phantom_buffer->gpuAddress() - _heap_va;
int64_t phantom_size = phantom_buffer->allocatedSize();
_phantom_buffers.insert({ offset, NS::TransferPtr(phantom_buffer) });

auto heapDesc = NS::TransferPtr(MTL::HeapDescriptor::alloc()->init());
heapDesc->setHazardTrackingMode(MTL::HazardTrackingModeUntracked);
heapDesc->setStorageMode(MTL::StorageModeShared);
heapDesc->setType(MTL::HeapTypeAutomatic);
heapDesc->setSize(0);
auto heap = device->newHeap(heapDesc.get());
if(!heap) {
  throw error_type::memory_allocation_error;
}
this->_heap = NS::TransferPtr(heap);
heap->setPurgeableState(MTL::PurgeableStateEmpty);

// There should be only one device.
NS::Array* all_devices = MTL::CopyAllDevices();
NS::UInteger num_devices = all_devices->count();

for (NS::UInteger i = 0; i < num_devices; ++i) {
  MTL::Device* mtl_device = (MTL::Device*)all_devices->object(i);
  metal_hardware_context hardware_context = metal_hardware_context{mtl_device};
  this->_devices.push_back(hardware_context);
}

I am humbly requesting that Epic Games migrate to the new bindings for engine source code.

2 Likes