From 564e2f7088d630e62b5d7389a33d4e5518027b88 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Tue, 17 Feb 2026 15:24:29 +0000 Subject: [PATCH] macOS: fix memory leak in Metal driver Fix memory leaks in the Metal driver on macOS. Add destructor for MetalSwapChain, release devices array and add autorelease pool for MTLSTR objects. Release state and error objects. Signed-off-by: Rafael Kitover --- plume_metal.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plume_metal.cpp b/plume_metal.cpp index fd27894..4c33862 100644 --- a/plume_metal.cpp +++ b/plume_metal.cpp @@ -1327,6 +1327,7 @@ namespace plume { if (error != nullptr) { fprintf(stderr, "MTLDevice newLibraryWithSource: failed with error %s.\n", error->localizedDescription()->utf8String()); + error->release(); return; } } @@ -1361,6 +1362,7 @@ namespace plume { if (error != nullptr) { fprintf(stderr, "MTLLibrary newFunction: failed with error: %s.\n", error->localizedDescription()->utf8String()); + error->release(); return nullptr; } @@ -1434,6 +1436,7 @@ namespace plume { if (error != nullptr) { fprintf(stderr, "MTLDevice newComputePipelineStateWithDescriptor: failed with error %s.\n", error->localizedDescription()->utf8String()); + error->release(); return; } @@ -1587,6 +1590,7 @@ namespace plume { if (error != nullptr) { fprintf(stderr, "MTLDevice newRenderPipelineState: failed with error %s.\n", error->localizedDescription()->utf8String()); + error->release(); return; } @@ -1930,6 +1934,13 @@ namespace plume { } MetalSwapChain::~MetalSwapChain() { + // Release all retained drawables + for (auto& drawable : drawables) { + if (drawable.mtl) { + drawable.mtl->release(); + drawable.mtl = nullptr; + } + } } bool MetalSwapChain::present(const uint32_t textureIndex, RenderCommandSemaphore **waitSemaphores, const uint32_t waitSemaphoreCount) { @@ -3685,6 +3696,8 @@ namespace plume { assert(commandLists != nullptr); assert(commandListCount > 0); + NS::AutoreleasePool *releasePool = NS::AutoreleasePool::alloc()->init(); + // Create a new command buffer to encode the wait semaphores into MTL::CommandBuffer* cmdBuffer = mtl->commandBufferWithUnretainedReferences(); cmdBuffer->setLabel(MTLSTR("Wait Command Buffer")); @@ -3725,6 +3738,8 @@ namespace plume { MetalCommandList *mutableCommandList = const_cast(interfaceCommandList); mutableCommandList->commit(); + + releasePool->release(); } void MetalCommandQueue::waitForCommandFence(RenderCommandFence *fence) { @@ -3858,6 +3873,9 @@ namespace plume { clearVertexFunction->release(); clearColorFunction->release(); clearDepthFunction->release(); + clearDepthState->release(); + clearDepthStencilState->release(); + clearStencilState->release(); sharedBlitDescriptor->release(); if (gpuAddressableResidencySet != nullptr) { @@ -4084,6 +4102,7 @@ namespace plume { MTL::Library *clearShaderLibrary = mtl->newLibrary(NS::String::string(clear_shader, NS::UTF8StringEncoding), nullptr, &error); if (error != nullptr) { fprintf(stderr, "Error: %s\n", error->localizedDescription()->utf8String()); + error->release(); } assert(clearShaderLibrary != nullptr && "Failed to create clear color library"); @@ -4147,11 +4166,12 @@ namespace plume { releasePool->release(); // Fill device names. - const NS::Array* devices = MTL::CopyAllDevices(); + NS::Array* devices = MTL::CopyAllDevices(); for (NS::UInteger i = 0; i < devices->count(); i++) { NS::String* deviceName = ((MTL::Device *)devices->object(i))->name(); deviceNames.push_back(std::string(deviceName->utf8String())); } + devices->release(); } MetalInterface::~MetalInterface() {}