SDL: GPU: Swapchain image count respects number of allowed frames in flight

From 279836d2598837679cc8e3ba5bfd9bd57679a3f1 Mon Sep 17 00:00:00 2001
From: cosmonaut <[EMAIL REDACTED]>
Date: Sun, 8 Dec 2024 09:07:50 -0800
Subject: [PATCH] GPU: Swapchain image count respects number of allowed frames
 in flight

---
 src/gpu/d3d12/SDL_gpu_d3d12.c   | 47 ++++++++++++++++++++++++++-------
 src/gpu/vulkan/SDL_gpu_vulkan.c | 29 +++++++++++++-------
 2 files changed, 56 insertions(+), 20 deletions(-)

diff --git a/src/gpu/d3d12/SDL_gpu_d3d12.c b/src/gpu/d3d12/SDL_gpu_d3d12.c
index 28db6c12ad88a..1af6af10e777a 100644
--- a/src/gpu/d3d12/SDL_gpu_d3d12.c
+++ b/src/gpu/d3d12/SDL_gpu_d3d12.c
@@ -708,6 +708,8 @@ typedef struct D3D12WindowData
     Uint32 frameCounter;
 
     D3D12TextureContainer textureContainers[MAX_FRAMES_IN_FLIGHT];
+    Uint32 swapchainTextureCount;
+
     SDL_GPUFence *inFlightFences[MAX_FRAMES_IN_FLIGHT];
     Uint32 width;
     Uint32 height;
@@ -6256,7 +6258,7 @@ static bool D3D12_INTERNAL_CreateSwapchain(
     createInfo.layer_count_or_depth = 1;
     createInfo.num_levels = 1;
 
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         texture = D3D12_INTERNAL_CreateTexture(renderer, &createInfo, true);
         texture->container = &windowData->textureContainers[i];
         windowData->textureContainers[i].activeTexture = texture;
@@ -6300,7 +6302,7 @@ static void D3D12_INTERNAL_DestroySwapchain(
     D3D12WindowData *windowData)
 {
     renderer->commandQueue->PresentX(0, NULL, NULL);
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         D3D12_INTERNAL_DestroyTexture(
             renderer,
             windowData->textureContainers[i].activeTexture);
@@ -6318,7 +6320,7 @@ static bool D3D12_INTERNAL_ResizeSwapchain(
     renderer->commandQueue->PresentX(0, NULL, NULL);
 
     // Clean up the previous swapchain textures
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         D3D12_INTERNAL_DestroyTexture(
             renderer,
             windowData->textureContainers[i].activeTexture);
@@ -6458,7 +6460,7 @@ static bool D3D12_INTERNAL_ResizeSwapchain(
     D3D12_Wait((SDL_GPURenderer *)renderer);
 
     // Release views and clean up
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         D3D12_INTERNAL_ReleaseCpuDescriptorHandle(
             renderer,
             &windowData->textureContainers[i].activeTexture->srvHandle);
@@ -6483,7 +6485,7 @@ static bool D3D12_INTERNAL_ResizeSwapchain(
     CHECK_D3D12_ERROR_AND_RETURN("Could not resize swapchain buffers", false)
 
     // Create texture object for the swapchain
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         if (!D3D12_INTERNAL_InitializeSwapchainTexture(
                 renderer,
                 windowData->swapchain,
@@ -6509,7 +6511,7 @@ static void D3D12_INTERNAL_DestroySwapchain(
     D3D12WindowData *windowData)
 {
     // Release views and clean up
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         D3D12_INTERNAL_ReleaseCpuDescriptorHandle(
             renderer,
             &windowData->textureContainers[i].activeTexture->srvHandle);
@@ -6551,6 +6553,9 @@ static bool D3D12_INTERNAL_CreateSwapchain(
 
     swapchainFormat = SwapchainCompositionToTextureFormat[swapchainComposition];
 
+    // Min swapchain image count is 2
+    windowData->swapchainTextureCount = SDL_clamp(renderer->allowedFramesInFlight, 2, 3);
+
     // Initialize the swapchain buffer descriptor
     swapchainDesc.Width = 0;  // use client window width
     swapchainDesc.Height = 0; // use client window height
@@ -6558,7 +6563,7 @@ static bool D3D12_INTERNAL_CreateSwapchain(
     swapchainDesc.SampleDesc.Count = 1;
     swapchainDesc.SampleDesc.Quality = 0;
     swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
-    swapchainDesc.BufferCount = MAX_FRAMES_IN_FLIGHT;
+    swapchainDesc.BufferCount = windowData->swapchainTextureCount;
     swapchainDesc.Scaling = DXGI_SCALING_STRETCH;
     swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
     swapchainDesc.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
@@ -6672,7 +6677,7 @@ static bool D3D12_INTERNAL_CreateSwapchain(
     /* If a you are using a FLIP model format you can't create the swapchain as DXGI_FORMAT_B8G8R8A8_UNORM_SRGB.
      * You have to create the swapchain as DXGI_FORMAT_B8G8R8A8_UNORM and then set the render target view's format to DXGI_FORMAT_B8G8R8A8_UNORM_SRGB
      */
-    for (Uint32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i += 1) {
+    for (Uint32 i = 0; i < windowData->swapchainTextureCount; i += 1) {
         if (!D3D12_INTERNAL_InitializeSwapchainTexture(
                 renderer,
                 swapchain3,
@@ -6820,7 +6825,29 @@ static bool D3D12_SetAllowedFramesInFlight(
         return false;
     }
 
+    // Destroy all swapchains
+    for (Uint32 i = 0; i < renderer->claimedWindowCount; i += 1) {
+        D3D12WindowData *windowData = renderer->claimedWindows[i];
+
+        D3D12_INTERNAL_DestroySwapchain(renderer, windowData);
+    }
+
+    // Set the frames in flight value
     renderer->allowedFramesInFlight = allowedFramesInFlight;
+
+    // Recreate all swapchains
+    for (Uint32 i = 0; i < renderer->claimedWindowCount; i += 1) {
+        D3D12WindowData *windowData = renderer->claimedWindows[i];
+
+        if (!D3D12_INTERNAL_CreateSwapchain(
+            renderer,
+            windowData,
+            windowData->swapchainComposition,
+            windowData->present_mode)) {
+            return false;
+        }
+    }
+
     return true;
 }
 
@@ -8510,7 +8537,7 @@ static SDL_GPUDevice *D3D12_CreateDevice(bool debugMode, bool preferLowPower, SD
         res = renderer->device->SetFrameIntervalX(
             NULL,
             D3D12XBOX_FRAME_INTERVAL_60_HZ,
-            MAX_FRAMES_IN_FLIGHT - 1,
+            renderer->allowedFramesInFlight - 1,
             D3D12XBOX_FRAME_INTERVAL_FLAG_NONE);
         if (FAILED(res)) {
             D3D12_INTERNAL_DestroyRenderer(renderer);
@@ -8864,7 +8891,7 @@ void SDL_GDKResumeGPU(SDL_GPUDevice *device)
     res = renderer->device->SetFrameIntervalX(
         NULL,
         D3D12XBOX_FRAME_INTERVAL_60_HZ,
-        MAX_FRAMES_IN_FLIGHT - 1,
+        renderer->allowedFramesInFlight - 1,
         D3D12XBOX_FRAME_INTERVAL_FLAG_NONE);
     if (FAILED(res)) {
         SDL_LogError(SDL_LOG_CATEGORY_GPU, "Could not set frame interval: %X", res);
diff --git a/src/gpu/vulkan/SDL_gpu_vulkan.c b/src/gpu/vulkan/SDL_gpu_vulkan.c
index 80ca23581cf1d..2c44ab116ea83 100644
--- a/src/gpu/vulkan/SDL_gpu_vulkan.c
+++ b/src/gpu/vulkan/SDL_gpu_vulkan.c
@@ -4556,7 +4556,7 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain(
         return VULKAN_INTERNAL_TRY_AGAIN;
     }
 
-    windowData->imageCount = MAX_FRAMES_IN_FLIGHT;
+    Uint32 requestedImageCount = renderer->allowedFramesInFlight;
 
 #ifdef SDL_PLATFORM_APPLE
     windowData->width = swapchainSupportDetails.capabilities.currentExtent.width;
@@ -4572,12 +4572,12 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain(
 #endif
 
     if (swapchainSupportDetails.capabilities.maxImageCount > 0 &&
-        windowData->imageCount > swapchainSupportDetails.capabilities.maxImageCount) {
-        windowData->imageCount = swapchainSupportDetails.capabilities.maxImageCount;
+        requestedImageCount > swapchainSupportDetails.capabilities.maxImageCount) {
+        requestedImageCount = swapchainSupportDetails.capabilities.maxImageCount;
     }
 
-    if (windowData->imageCount < swapchainSupportDetails.capabilities.minImageCount) {
-        windowData->imageCount = swapchainSupportDetails.capabilities.minImageCount;
+    if (requestedImageCount < swapchainSupportDetails.capabilities.minImageCount) {
+        requestedImageCount = swapchainSupportDetails.capabilities.minImageCount;
     }
 
     if (windowData->presentMode == SDL_GPU_PRESENTMODE_MAILBOX) {
@@ -4587,14 +4587,14 @@ static Uint32 VULKAN_INTERNAL_CreateSwapchain(
          * images, it's not real mailbox support, so let it fail hard.
          * -flibit
          */
-        windowData->imageCount = SDL_max(windowData->imageCount, 3);
+        requestedImageCount = SDL_max(requestedImageCount, 3);
     }
 
     swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
     swapchainCreateInfo.pNext = NULL;
     swapchainCreateInfo.flags = 0;
     swapchainCreateInfo.surface = windowData->surface;
-    swapchainCreateInfo.minImageCount = windowData->imageCount;
+    swapchainCreateInfo.minImageCount = requestedImageCount;
     swapchainCreateInfo.imageFormat = windowData->format;
     swapchainCreateInfo.imageColorSpace = windowData->colorSpace;
     swapchainCreateInfo.imageExtent.width = windowData->width;
@@ -9906,11 +9906,20 @@ static bool VULKAN_SetAllowedFramesInFlight(
 {
     VulkanRenderer *renderer = (VulkanRenderer *)driverData;
 
-    if (!VULKAN_Wait(driverData)) {
-        return false;
+    renderer->allowedFramesInFlight = allowedFramesInFlight;
+
+    for (Uint32 i = 0; i < renderer->claimedWindowCount; i += 1) {
+        WindowData *windowData = renderer->claimedWindows[i];
+
+        Uint32 recreateResult = VULKAN_INTERNAL_RecreateSwapchain(renderer, windowData);
+        if (!recreateResult) {
+            return false;
+        } else if (recreateResult == VULKAN_INTERNAL_TRY_AGAIN) {
+            // Edge case, swapchain extent is (0, 0) but this is not an error
+            windowData->needsSwapchainRecreate = true;
+        }
     }
 
-    renderer->allowedFramesInFlight = allowedFramesInFlight;
     return true;
 }