From fa5f84fb6ec308bf7bd4819b6c6887a684571695 Mon Sep 17 00:00:00 2001
From: Evan Hemsley <[EMAIL REDACTED]>
Date: Fri, 6 Dec 2024 11:56:20 -0800
Subject: [PATCH] GPU: Add SetGPUAllowedFramesInFlight (#11599)
---
include/SDL3/SDL_gpu.h | 23 +++++++++++++++++++++++
src/dynapi/SDL_dynapi.sym | 1 +
src/dynapi/SDL_dynapi_overrides.h | 1 +
src/dynapi/SDL_dynapi_procs.h | 1 +
src/gpu/SDL_gpu.c | 19 +++++++++++++++++++
src/gpu/SDL_sysgpu.h | 5 +++++
src/gpu/d3d12/SDL_gpu_d3d12.c | 28 ++++++++++++++++++++++------
src/gpu/metal/SDL_gpu_metal.m | 20 +++++++++++++++++++-
src/gpu/vulkan/SDL_gpu_vulkan.c | 20 ++++++++++++++++++--
9 files changed, 109 insertions(+), 9 deletions(-)
diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h
index 0e7c9fd84a8cf..5cfb487bc5241 100644
--- a/include/SDL3/SDL_gpu.h
+++ b/include/SDL3/SDL_gpu.h
@@ -3496,6 +3496,29 @@ extern SDL_DECLSPEC bool SDLCALL SDL_SetGPUSwapchainParameters(
SDL_GPUSwapchainComposition swapchain_composition,
SDL_GPUPresentMode present_mode);
+/**
+ * Configures the maximum allowed number of frames in flight.
+ *
+ * The default value when the device is created is 2.
+ * This means that after you have submitted 2 frames for presentation, if the GPU has not finished working on the first frame, SDL_AcquireGPUSwapchainTexture() will block or return false depending on the present mode.
+ *
+ * Higher values increase throughput at the expense of visual latency.
+ * Lower values decrease visual latency at the expense of throughput.
+ *
+ * Note that calling this function will stall and flush the command queue to prevent synchronization issues.
+ *
+ * The minimum value of allowed frames in flight is 1, and the maximum is 3.
+ *
+ * \param device a GPU context.
+ * \param allowed_frames_in_flight the maximum number of frames that can be pending on the GPU before AcquireSwapchainTexture blocks or returns false.
+ * \returns true if successful, false on error; call SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.2.0.
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_SetGPUAllowedFramesInFlight(
+ SDL_GPUDevice *device,
+ Uint32 allowed_frames_in_flight);
+
/**
* Obtains the texture format of the swapchain for the given window.
*
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 1ca616f709e40..145a4622ee265 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1203,6 +1203,7 @@ SDL3_0.0.0 {
SDL_ShowFileDialogWithProperties;
SDL_IsMainThread;
SDL_RunOnMainThread;
+ SDL_SetGPUAllowedFramesInFlight;
# extra symbols go here (don't modify this line)
local: *;
};
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 691e7f184c90a..652e2cd0c0833 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1228,3 +1228,4 @@
#define SDL_ShowFileDialogWithProperties SDL_ShowFileDialogWithProperties_REAL
#define SDL_IsMainThread SDL_IsMainThread_REAL
#define SDL_RunOnMainThread SDL_RunOnMainThread_REAL
+#define SDL_SetGPUAllowedFramesInFlight SDL_SetGPUAllowedFramesInFlight_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index d39ad72331ab9..f36cc06f8a1bb 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -1234,3 +1234,4 @@ SDL_DYNAPI_PROC(bool,SDL_LoadFileAsync,(const char *a, SDL_AsyncIOQueue *b, void
SDL_DYNAPI_PROC(void,SDL_ShowFileDialogWithProperties,(SDL_FileDialogType a, SDL_DialogFileCallback b, void *c, SDL_PropertiesID d),(a,b,c,d),)
SDL_DYNAPI_PROC(bool,SDL_IsMainThread,(void),(),return)
SDL_DYNAPI_PROC(bool,SDL_RunOnMainThread,(SDL_MainThreadCallback a,void *b,bool c),(a,b,c),return)
+SDL_DYNAPI_PROC(bool,SDL_SetGPUAllowedFramesInFlight,(SDL_GPUDevice *a,Uint32 b),(a,b),return)
diff --git a/src/gpu/SDL_gpu.c b/src/gpu/SDL_gpu.c
index 83ffbf6f9725e..8bd63c960ad84 100644
--- a/src/gpu/SDL_gpu.c
+++ b/src/gpu/SDL_gpu.c
@@ -2650,6 +2650,25 @@ bool SDL_SetGPUSwapchainParameters(
present_mode);
}
+bool SDL_SetGPUAllowedFramesInFlight(
+ SDL_GPUDevice *device,
+ Uint32 allowed_frames_in_flight)
+{
+ CHECK_DEVICE_MAGIC(device, false);
+
+ if (device->debug_mode) {
+ if (allowed_frames_in_flight < 1 || allowed_frames_in_flight > 3)
+ {
+ SDL_assert_release(!"allowed_frames_in_flight value must be between 1 and 3!");
+ }
+ }
+
+ allowed_frames_in_flight = SDL_clamp(allowed_frames_in_flight, 1, 3);
+ return device->SetAllowedFramesInFlight(
+ device->driverData,
+ allowed_frames_in_flight);
+}
+
SDL_GPUTextureFormat SDL_GetGPUSwapchainTextureFormat(
SDL_GPUDevice *device,
SDL_Window *window)
diff --git a/src/gpu/SDL_sysgpu.h b/src/gpu/SDL_sysgpu.h
index bd2a2f42bc7f2..8806d2fb8b78b 100644
--- a/src/gpu/SDL_sysgpu.h
+++ b/src/gpu/SDL_sysgpu.h
@@ -791,6 +791,10 @@ struct SDL_GPUDevice
SDL_GPUSwapchainComposition swapchainComposition,
SDL_GPUPresentMode presentMode);
+ bool (*SetAllowedFramesInFlight)(
+ SDL_GPURenderer *driverData,
+ Uint32 allowedFramesInFlight);
+
SDL_GPUTextureFormat (*GetSwapchainTextureFormat)(
SDL_GPURenderer *driverData,
SDL_Window *window);
@@ -927,6 +931,7 @@ struct SDL_GPUDevice
ASSIGN_DRIVER_FUNC(ClaimWindow, name) \
ASSIGN_DRIVER_FUNC(ReleaseWindow, name) \
ASSIGN_DRIVER_FUNC(SetSwapchainParameters, name) \
+ ASSIGN_DRIVER_FUNC(SetAllowedFramesInFlight, name) \
ASSIGN_DRIVER_FUNC(GetSwapchainTextureFormat, name) \
ASSIGN_DRIVER_FUNC(AcquireCommandBuffer, name) \
ASSIGN_DRIVER_FUNC(AcquireSwapchainTexture, name) \
diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index 36e328a7f478a..28db6c12ad88a 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -750,6 +750,7 @@ struct D3D12Renderer
// FIXME: these might not be necessary since we're not using custom heaps
bool UMA;
bool UMACacheCoherent;
+ Uint32 allowedFramesInFlight;
// Indirect command signatures
ID3D12CommandSignature *indirectDrawCommandSignature;
@@ -6809,6 +6810,20 @@ static bool D3D12_SetSwapchainParameters(
return true;
}
+static bool D3D12_SetAllowedFramesInFlight(
+ SDL_GPURenderer *driverData,
+ Uint32 allowedFramesInFlight)
+{
+ D3D12Renderer *renderer = (D3D12Renderer *)driverData;
+
+ if (!D3D12_Wait(driverData)) {
+ return false;
+ }
+
+ renderer->allowedFramesInFlight = allowedFramesInFlight;
+ return true;
+}
+
static SDL_GPUTextureFormat D3D12_GetSwapchainTextureFormat(
SDL_GPURenderer *driverData,
SDL_Window *window)
@@ -7569,7 +7584,7 @@ static bool D3D12_Submit(
windowData->inFlightFences[windowData->frameCounter] = (SDL_GPUFence*)d3d12CommandBuffer->inFlightFence;
(void)SDL_AtomicIncRef(&d3d12CommandBuffer->inFlightFence->referenceCount);
- windowData->frameCounter = (windowData->frameCounter + 1) % MAX_FRAMES_IN_FLIGHT;
+ windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight;
}
// Check for cleanups
@@ -8181,10 +8196,10 @@ static bool D3D12_INTERNAL_TryInitializeD3D12DebugInfoQueue(D3D12Renderer *rende
}
static void WINAPI D3D12_INTERNAL_OnD3D12DebugInfoMsg(
- D3D12_MESSAGE_CATEGORY category,
- D3D12_MESSAGE_SEVERITY severity,
- D3D12_MESSAGE_ID id,
- LPCSTR description,
+ D3D12_MESSAGE_CATEGORY category,
+ D3D12_MESSAGE_SEVERITY severity,
+ D3D12_MESSAGE_ID id,
+ LPCSTR description,
void *context)
{
char *catStr;
@@ -8288,7 +8303,7 @@ static void D3D12_INTERNAL_TryInitializeD3D12DebugInfoLogger(D3D12Renderer *rend
D3D12_MESSAGE_CALLBACK_FLAG_NONE,
NULL,
NULL);
-
+
ID3D12InfoQueue1_Release(infoQueue);
}
#endif
@@ -8776,6 +8791,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD
renderer->disposeLock = SDL_CreateMutex();
renderer->debug_mode = debugMode;
+ renderer->allowedFramesInFlight = 2;
renderer->semantic = SDL_GetStringProperty(props, SDL_PROP_GPU_DEVICE_CREATE_D3D12_SEMANTIC_NAME_STRING, "TEXCOORD");
diff --git a/src/gpu/metal/SDL_gpu_metal.m b/src/gpu/metal/SDL_gpu_metal.m
index 6c496a5c11402..ebea8d5f9984a 100644
--- a/src/gpu/metal/SDL_gpu_metal.m
+++ b/src/gpu/metal/SDL_gpu_metal.m
@@ -641,6 +641,7 @@ static MTLDepthClipMode SDLToMetal_DepthClipMode(
id<MTLCommandQueue> queue;
bool debugMode;
+ Uint32 allowedFramesInFlight;
MetalWindowData **claimedWindows;
Uint32 claimedWindowCount;
@@ -3817,6 +3818,22 @@ static bool METAL_SetSwapchainParameters(
}
}
+static bool METAL_SetAllowedFramesInFlight(
+ SDL_GPURenderer *driverData,
+ Uint32 allowedFramesInFlight)
+{
+ @autoreleasepool {
+ MetalRenderer *renderer = (MetalRenderer *)driverData;
+
+ if (!METAL_Wait(driverData)) {
+ return false;
+ }
+
+ renderer->allowedFramesInFlight = allowedFramesInFlight;
+ return true;
+ }
+}
+
// Submission
static bool METAL_Submit(
@@ -3843,7 +3860,7 @@ static bool METAL_Submit(
(void)SDL_AtomicIncRef(&metalCommandBuffer->fence->referenceCount);
- windowData->frameCounter = (windowData->frameCounter + 1) % MAX_FRAMES_IN_FLIGHT;
+ windowData->frameCounter = (windowData->frameCounter + 1) % renderer->allowedFramesInFlight;
}
// Notify the fence when the command buffer has completed
@@ -4301,6 +4318,7 @@ static void METAL_INTERNAL_DestroyBlitResources(
// Remember debug mode
renderer->debugMode = debugMode;
+ renderer->allowedFramesInFlight = 2;
// Set up colorspace array
SwapchainCompositionToColorSpace[0] = kCGColorSpaceSRGB;
diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c
index a8202b54fda01..80ca23581cf1d 100644
--- a/src/gpu/vulkan/SDL_gpu_vulkan.c
+++ b/src/gpu/vulkan/SDL_gpu_vulkan.c
@@ -1123,6 +1123,8 @@ struct VulkanRenderer
bool debugMode;
bool preferLowPower;
+ Uint32 allowedFramesInFlight;
+
VulkanExtensions supports;
bool supportsDebugUtils;
bool supportsColorspace;
@@ -9898,6 +9900,20 @@ static bool VULKAN_SetSwapchainParameters(
return true;
}
+static bool VULKAN_SetAllowedFramesInFlight(
+ SDL_GPURenderer *driverData,
+ Uint32 allowedFramesInFlight)
+{
+ VulkanRenderer *renderer = (VulkanRenderer *)driverData;
+
+ if (!VULKAN_Wait(driverData)) {
+ return false;
+ }
+
+ renderer->allowedFramesInFlight = allowedFramesInFlight;
+ return true;
+}
+
// Submission structure
static VulkanFenceHandle *VULKAN_INTERNAL_AcquireFenceFromPool(
@@ -10348,8 +10364,7 @@ static bool VULKAN_Submit(
}
presentData->windowData->frameCounter =
- (presentData->windowData->frameCounter + 1) % MAX_FRAMES_IN_FLIGHT;
-
+ (presentData->windowData->frameCounter + 1) % renderer->allowedFramesInFlight;
}
// Check if we can perform any cleanups
@@ -11438,6 +11453,7 @@ static SDL_GPUDevice *VULKAN_CreateDevice(bool debugMode, bool preferLowPower, S
SDL_memset(renderer, '\0', sizeof(VulkanRenderer));
renderer->debugMode = debugMode;
renderer->preferLowPower = preferLowPower;
+ renderer->allowedFramesInFlight = 2;
if (!VULKAN_INTERNAL_PrepareVulkan(renderer)) {
SDL_free(renderer);