From 6c10cdb6ebcdbede48a0019c1b538be662c49ef3 Mon Sep 17 00:00:00 2001
From: Caleb Cornett <[EMAIL REDACTED]>
Date: Sun, 21 Jun 2026 08:56:53 -0400
Subject: [PATCH] gpu: Metal fence fixes
(cherry picked from commit 3561f81400fd8cbf74290dabd1440569b46440ae)
---
src/gpu/metal/SDL_gpu_metal.m | 41 ++++++++++++-----------------------
1 file changed, 14 insertions(+), 27 deletions(-)
diff --git a/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m
index 260d3b8da2148..00456a97029f3 100644
--- a/src/gpu/metal/SDL_gpu_metal.m
+++ b/src/gpu/metal/SDL_gpu_metal.m
@@ -448,8 +448,7 @@ static MTLDepthClipMode SDLToMetal_DepthClipMode(
typedef struct MetalFence
{
- // can be NULL if the command buffer was recycled
- MetalCommandBuffer *commandBuffer;
+ id<MTLCommandBuffer> commandBuffer;
SDL_AtomicInt referenceCount;
} MetalFence;
@@ -2131,7 +2130,7 @@ static bool METAL_INTERNAL_AcquireFence(
// Associate the fence with the command buffer
commandBuffer->fence = fence;
- fence->commandBuffer = commandBuffer;
+ fence->commandBuffer = commandBuffer->handle;
(void)SDL_AtomicIncRef(&commandBuffer->fence->referenceCount);
return true;
@@ -3401,6 +3400,7 @@ static void METAL_ReleaseFence(
SDL_GPUFence *fence)
{
MetalFence *metalFence = (MetalFence *)fence;
+ metalFence->commandBuffer = nil;
if (SDL_AtomicDecRef(&metalFence->referenceCount)) {
METAL_INTERNAL_ReleaseFenceToPool(
(MetalRenderer *)driverData,
@@ -3512,8 +3512,6 @@ static void METAL_INTERNAL_CleanCommandBuffer(
METAL_ReleaseFence(
(SDL_GPURenderer *)renderer,
(SDL_GPUFence *)commandBuffer->fence);
- } else {
- commandBuffer->fence->commandBuffer = NULL;
}
// Return command buffer to pool
@@ -3587,11 +3585,7 @@ static void METAL_INTERNAL_PerformPendingDestroys(
static bool METAL_INTERNAL_IsFenceBusy(
MetalFence *fence
) {
- if (!fence->commandBuffer) {
- return false; // command buffer was recycled
- }
-
- MTLCommandBufferStatus status = fence->commandBuffer->handle.status;
+ MTLCommandBufferStatus status = fence->commandBuffer.status;
return status == MTLCommandBufferStatusCommitted || status == MTLCommandBufferStatusScheduled;
}
@@ -3607,25 +3601,19 @@ static bool METAL_WaitForFences(
if (waitAll) {
for (Uint32 i = 0; i < numFences; i += 1) {
MetalFence *fence = (MetalFence *)fences[i];
- if (METAL_INTERNAL_IsFenceBusy(fence)) {
- [fence->commandBuffer->handle waitUntilCompleted];
- }
+ [fence->commandBuffer waitUntilCompleted];
}
} else {
- dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
- for (Uint32 i = 0; i < numFences; i += 1) {
- MetalFence *fence = (MetalFence *)fences[i];
- // command buffer has completed and been recycled
- if(!fence->commandBuffer)
- return true;
-
- // even if it's completed, the handle will call back straight away
- [fence->commandBuffer->handle addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
- dispatch_semaphore_signal(semaphore);
- }];
+ bool waiting = true;
+ while (waiting) {
+ for (Uint32 i = 0; i < numFences; i += 1) {
+ MetalFence *fence = (MetalFence *)fences[i];
+ if (!METAL_INTERNAL_IsFenceBusy(fence)) {
+ waiting = false;
+ break;
+ }
+ }
}
-
- dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
METAL_INTERNAL_PerformPendingDestroys(renderer);
@@ -4119,7 +4107,6 @@ static bool METAL_Submit(
// Check if we can perform any cleanups
for (Sint32 i = renderer->submittedCommandBufferCount - 1; i >= 0; i -= 1) {
-
if (!METAL_INTERNAL_IsFenceBusy(renderer->submittedCommandBuffers[i]->fence)) {
METAL_INTERNAL_CleanCommandBuffer(
renderer,