diff --git a/include/SDL3/SDL_gpu.h b/include/SDL3/SDL_gpu.h index 0e7c9fd84a..5cfb487bc5 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 1ca616f709..145a4622ee 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 691e7f184c..652e2cd0c0 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 d39ad72331..f36cc06f8a 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 83ffbf6f97..8bd63c960a 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 bd2a2f42bc..8806d2fb8b 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 36e328a7f4..28db6c12ad 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 6c496a5c11..ebea8d5f99 100644 --- a/src/gpu/metal/SDL_gpu_metal.m +++ b/src/gpu/metal/SDL_gpu_metal.m @@ -641,6 +641,7 @@ struct MetalRenderer id 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 SDL_GPUDevice *METAL_CreateDevice(bool debugMode, bool preferLowPower, SD // 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 a8202b54fd..80ca23581c 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);