SDL: Vulkan Renderer - robustly handle running out of descriptor sets or constant buffer memory. Closes #9131. My previous...

From 35026cdcba90b297ccf064ca46957bd272d7e931 Mon Sep 17 00:00:00 2001
From: danginsburg <[EMAIL REDACTED]>
Date: Mon, 26 Feb 2024 11:02:19 -0500
Subject: [PATCH] Vulkan Renderer - robustly handle running out of descriptor
 sets or constant buffer memory.  Closes #9131.  My previous implementation of
 descriptor set handling was naive - it attempted to do VULKAN_IssueBatch when
 running out of descriptor sets or constant buffer space.  For one thing, this
 had a bug and wasn't working (causing the crash), but moreover it would have
 resulted in having to flush the GPU.  Instead, make the descriptor pools and
 constant buffer mapped buffers be resizeable so that if we need more it will
 grow to the size that is needed.

# Conflicts:
#	src/render/vulkan/SDL_render_vulkan.c
---
 src/render/vulkan/SDL_render_vulkan.c | 342 +++++++++++++++++---------
 1 file changed, 231 insertions(+), 111 deletions(-)

diff --git a/src/render/vulkan/SDL_render_vulkan.c b/src/render/vulkan/SDL_render_vulkan.c
index 698e249bba35..bce68ca9401f 100644
--- a/src/render/vulkan/SDL_render_vulkan.c
+++ b/src/render/vulkan/SDL_render_vulkan.c
@@ -308,11 +308,15 @@ typedef struct
     int *currentUploadBuffer;
 
     /* Data for updating constants */
-    VULKAN_Buffer *constantBuffers;
+    VULKAN_Buffer **constantBuffers;
+    uint32_t *numConstantBuffers;
+    uint32_t currentConstantBufferIndex;
     int32_t currentConstantBufferOffset;
 
     VkSampler samplers[SDL_VULKAN_NUM_SAMPLERS];
-    VkDescriptorPool *descriptorPools;
+    VkDescriptorPool **descriptorPools;
+    uint32_t *numDescriptorPools;
+    uint32_t currentDescriptorPoolIndex;
     uint32_t currentDescriptorSetIndex;
 
     int pipelineStateCount;
@@ -410,6 +414,7 @@ static void VULKAN_DestroyImage(VULKAN_RenderData *rendererData, VULKAN_Image *v
 static void VULKAN_ResetCommandList(VULKAN_RenderData *rendererData);
 static SDL_bool VULKAN_FindMemoryTypeIndex(VULKAN_RenderData *rendererData, uint32_t typeBits, VkMemoryPropertyFlags requiredFlags, VkMemoryPropertyFlags desiredFlags, uint32_t *memoryTypeIndexOut);
 static VkResult VULKAN_CreateWindowSizeDependentResources(SDL_Renderer *renderer);
+static VkDescriptorPool VULKAN_AllocateDescriptorPool(VULKAN_RenderData *rendererData);
 
 static void VULKAN_DestroyAll(SDL_Renderer *renderer)
 {
@@ -500,13 +505,17 @@ static void VULKAN_DestroyAll(SDL_Renderer *renderer)
         rendererData->commandPool = VK_NULL_HANDLE;
     }
     if (rendererData->descriptorPools) {
+        SDL_assert(rendererData->numDescriptorPools);
         for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) {
-            if (rendererData->descriptorPools[i] != VK_NULL_HANDLE) {
-                vkDestroyDescriptorPool(rendererData->device, rendererData->descriptorPools[i], NULL);
+            for (uint32_t j = 0; j < rendererData->numDescriptorPools[i]; j++) {
+                if (rendererData->descriptorPools[i][j] != VK_NULL_HANDLE) {
+                    vkDestroyDescriptorPool(rendererData->device, rendererData->descriptorPools[i][j], NULL);
+                }
             }
+            SDL_free(rendererData->descriptorPools[i]);
         }
         SDL_free(rendererData->descriptorPools);
-        rendererData->descriptorPools = NULL;
+        SDL_free(rendererData->numDescriptorPools);
     }
     for (uint32_t i = 0; i < NUM_SHADERS; i++) {
         if (rendererData->vertexShaderModules[i] != VK_NULL_HANDLE) {
@@ -544,10 +553,15 @@ static void VULKAN_DestroyAll(SDL_Renderer *renderer)
     }
 
     if (rendererData->constantBuffers) {
+        SDL_assert(rendererData->numConstantBuffers);
         for (uint32_t i = 0; i < rendererData->swapchainImageCount; ++i) {
-            VULKAN_DestroyBuffer(rendererData, &rendererData->constantBuffers[i]);
+            for (uint32_t j = 0; j < rendererData->numConstantBuffers[i]; j++) {
+                VULKAN_DestroyBuffer(rendererData, &rendererData->constantBuffers[i][j]);
+            }
+            SDL_free(rendererData->constantBuffers[i]);
         }
         SDL_free(rendererData->constantBuffers);
+        SDL_free(rendererData->numConstantBuffers);
         rendererData->constantBuffers = NULL;
     }
 
@@ -899,7 +913,9 @@ static void VULKAN_WaitForGPU(VULKAN_RenderData *rendererData)
 static void VULKAN_ResetCommandList(VULKAN_RenderData *rendererData)
 {
     vkResetCommandBuffer(rendererData->currentCommandBuffer, 0);
-    vkResetDescriptorPool(rendererData->device, rendererData->descriptorPools[rendererData->currentCommandBufferIndex], 0);
+    for (uint32_t i = 0; i < rendererData->numDescriptorPools[rendererData->currentCommandBufferIndex]; i++) {
+        vkResetDescriptorPool(rendererData->device, rendererData->descriptorPools[rendererData->currentCommandBufferIndex][i], 0);
+    }
 
     VkCommandBufferBeginInfo beginInfo = { 0 };
     beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
@@ -911,7 +927,9 @@ static void VULKAN_ResetCommandList(VULKAN_RenderData *rendererData)
     rendererData->issueBatch = SDL_FALSE;
     rendererData->cliprectDirty = SDL_TRUE;
     rendererData->currentDescriptorSetIndex = 0;
+    rendererData->currentDescriptorPoolIndex = 0;
     rendererData->currentConstantBufferOffset = -1;
+    rendererData->currentConstantBufferIndex = 0;
 
     /* Release any upload buffers that were inflight */
     for (int i = 0; i < rendererData->currentUploadBuffer[rendererData->currentCommandBufferIndex]; ++i) {
@@ -2114,35 +2132,29 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
         return result;
     }
 
-    /* Create descriptor pools */
-     if (rendererData->descriptorPools) {
+    /* Create descriptor pools - start by allocating one per swapchain image, let it grow if more are needed */
+    if (rendererData->descriptorPools) {
+        SDL_assert(rendererData->numDescriptorPools);
         for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) {
-            if (rendererData->descriptorPools[i] != VK_NULL_HANDLE) {
-                vkDestroyDescriptorPool(rendererData->device, rendererData->descriptorPools[i], NULL);
+            for (uint32_t j = 0; j < rendererData->numDescriptorPools[i]; j++) {
+                if (rendererData->descriptorPools[i][j] != VK_NULL_HANDLE) {
+                    vkDestroyDescriptorPool(rendererData->device, rendererData->descriptorPools[i][j], NULL);
+                }
             }
+            SDL_free(rendererData->descriptorPools[i]);
         }
         SDL_free(rendererData->descriptorPools);
+        SDL_free(rendererData->numDescriptorPools);
     }
-    rendererData->descriptorPools = SDL_calloc(sizeof(VkDescriptorPool), rendererData->swapchainImageCount);
+    rendererData->descriptorPools = SDL_calloc(sizeof(VkDescriptorPool*), rendererData->swapchainImageCount);
+    rendererData->numDescriptorPools = SDL_calloc(sizeof(uint32_t), rendererData->swapchainImageCount);
     for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) {
-        VkDescriptorPoolSize descriptorPoolSizes[2];
-        descriptorPoolSizes[0].descriptorCount = SDL_VULKAN_MAX_DESCRIPTOR_SETS;
-        descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLER;
-
-        /* Allocate enough to hold a maximum of each descriptor set having YUV textures */
-        const int numTexturesPerYUV = 3;
-        descriptorPoolSizes[1].descriptorCount = SDL_VULKAN_MAX_DESCRIPTOR_SETS * numTexturesPerYUV;
-        descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-
-        VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { 0 };
-        descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
-        descriptorPoolCreateInfo.poolSizeCount = SDL_arraysize(descriptorPoolSizes);
-        descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes;
-        descriptorPoolCreateInfo.maxSets = SDL_VULKAN_MAX_DESCRIPTOR_SETS;
-        result = vkCreateDescriptorPool(rendererData->device, &descriptorPoolCreateInfo, NULL, &rendererData->descriptorPools[i]);
+        /* Start by just allocating one pool, it will grow if needed */
+        rendererData->numDescriptorPools[i] = 1;
+        rendererData->descriptorPools[i] = SDL_calloc(sizeof(VkDescriptorPool), 1);
+        rendererData->descriptorPools[i][0] = VULKAN_AllocateDescriptorPool(rendererData);
         if (result != VK_SUCCESS) {
             VULKAN_DestroyAll(renderer);
-            SDL_LogError(SDL_LOG_CATEGORY_RENDER, "vkCreateDescriptorPool(): %s\n", SDL_Vulkan_GetResultString(result));
             return result;
         }
     }
@@ -2184,20 +2196,30 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
 
     /* Constant buffers */
     if (rendererData->constantBuffers) {
-        for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) {
-            VULKAN_DestroyBuffer(rendererData, &rendererData->constantBuffers[i]);
+        SDL_assert(rendererData->numConstantBuffers);
+        for (uint32_t i = 0; i < rendererData->swapchainImageCount; ++i) {
+            for (uint32_t j = 0; j < rendererData->numConstantBuffers[i]; j++) {
+                VULKAN_DestroyBuffer(rendererData, &rendererData->constantBuffers[i][j]);
+            }
+            SDL_free(rendererData->constantBuffers[i]);
         }
         SDL_free(rendererData->constantBuffers);
+        SDL_free(rendererData->numConstantBuffers);
+        rendererData->constantBuffers = NULL;
     }
-    rendererData->constantBuffers = SDL_calloc(sizeof(VULKAN_Buffer), rendererData->swapchainImageCount);
+    rendererData->constantBuffers = SDL_calloc(sizeof(VULKAN_Buffer*), rendererData->swapchainImageCount);
+    rendererData->numConstantBuffers = SDL_calloc(sizeof(VULKAN_Buffer*), rendererData->swapchainImageCount);
     for (uint32_t i = 0; i < rendererData->swapchainImageCount; i++) {
+        /* Start with just allocating one, will grow if needed */
+        rendererData->numConstantBuffers[i] = 1;
+        rendererData->constantBuffers[i] = SDL_calloc(sizeof(VULKAN_Buffer), 1);
         result = VULKAN_AllocateBuffer(rendererData,
             SDL_VULKAN_CONSTANT_BUFFER_DEFAULT_SIZE,
             VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
             VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
             VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
             VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
-            &rendererData->constantBuffers[i]);
+            &rendererData->constantBuffers[i][0]);
         if (result != VK_SUCCESS) {
             VULKAN_DestroyAll(renderer);
             SDL_LogError(SDL_LOG_CATEGORY_RENDER, "VULKAN_AllocateBuffer(): %s\n", SDL_Vulkan_GetResultString(result));
@@ -2205,6 +2227,7 @@ static VkResult VULKAN_CreateSwapChain(SDL_Renderer *renderer, int w, int h)
         }
     }
     rendererData->currentConstantBufferOffset = -1;
+    rendererData->currentConstantBufferIndex = 0;
 
     VULKAN_AcquireNextSwapchainImage(renderer);
 
@@ -3083,6 +3106,140 @@ static void VULKAN_SetupShaderConstants(SDL_Renderer *renderer, const SDL_Render
     }
 }
 
+static VkDescriptorPool VULKAN_AllocateDescriptorPool(VULKAN_RenderData *rendererData)
+{
+    VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
+    VkDescriptorPoolSize descriptorPoolSizes[2];
+    VkResult result;
+    descriptorPoolSizes[0].descriptorCount = SDL_VULKAN_MAX_DESCRIPTOR_SETS;
+    descriptorPoolSizes[0].type = VK_DESCRIPTOR_TYPE_SAMPLER;
+
+    /* Allocate enough to hold a maximum of each descriptor set having YUV textures */
+    const int numTexturesPerYUV = 3;
+    descriptorPoolSizes[1].descriptorCount = SDL_VULKAN_MAX_DESCRIPTOR_SETS * numTexturesPerYUV;
+    descriptorPoolSizes[1].type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+
+    VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { 0 };
+    descriptorPoolCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+    descriptorPoolCreateInfo.poolSizeCount = SDL_arraysize(descriptorPoolSizes);
+    descriptorPoolCreateInfo.pPoolSizes = descriptorPoolSizes;
+    descriptorPoolCreateInfo.maxSets = SDL_VULKAN_MAX_DESCRIPTOR_SETS;
+    result = vkCreateDescriptorPool(rendererData->device, &descriptorPoolCreateInfo, NULL, &descriptorPool);
+    if (result != VK_SUCCESS) {
+        SDL_SetError("[Vulkan] Unable to allocate descriptor pool vkCreateDescrptorPool: %s.\n", SDL_Vulkan_GetResultString(result));
+        return VK_NULL_HANDLE;
+    }
+
+    return descriptorPool;
+}
+
+static VkDescriptorSet VULKAN_AllocateDescriptorSet(SDL_Renderer *renderer, VULKAN_Shader shader, VkSampler sampler, VkBuffer constantBuffer, VkDeviceSize constantBufferOffset, int imageViewCount, VkImageView *imageViews)
+{
+    VULKAN_RenderData *rendererData = (VULKAN_RenderData *)renderer->driverdata;
+    uint32_t currentDescriptorPoolIndex = rendererData->currentDescriptorPoolIndex;
+    VkDescriptorPool descriptorPool = rendererData->descriptorPools[rendererData->currentCommandBufferIndex][currentDescriptorPoolIndex];
+
+    VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { 0 };
+    descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+    descriptorSetAllocateInfo.descriptorSetCount = 1;
+    descriptorSetAllocateInfo.descriptorPool = descriptorPool;
+    descriptorSetAllocateInfo.pSetLayouts = &rendererData->descriptorSetLayouts[shader];
+
+    VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
+    VkResult result = (rendererData->currentDescriptorSetIndex >= SDL_VULKAN_MAX_DESCRIPTOR_SETS) ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS;
+    if (result == VK_SUCCESS) {
+        result = vkAllocateDescriptorSets(rendererData->device, &descriptorSetAllocateInfo, &descriptorSet);
+    }
+    if (result != VK_SUCCESS) {
+        /* Out of descriptor sets in this pool - see if we have more pools allocated */
+        currentDescriptorPoolIndex++;
+        if (currentDescriptorPoolIndex < rendererData->numDescriptorPools[rendererData->currentCommandBufferIndex]) {
+            descriptorPool = rendererData->descriptorPools[rendererData->currentCommandBufferIndex][currentDescriptorPoolIndex];
+            descriptorSetAllocateInfo.descriptorPool = descriptorPool;
+            result = vkAllocateDescriptorSets(rendererData->device, &descriptorSetAllocateInfo, &descriptorSet);
+            if (result != VK_SUCCESS) {
+                /* This should not fail - we are allocating from the front of the descriptor set */
+                SDL_SetError("[Vulkan] Unable to allocate descriptor set.");
+                return VK_NULL_HANDLE;
+            }
+            rendererData->currentDescriptorPoolIndex = currentDescriptorPoolIndex;
+            rendererData->currentDescriptorSetIndex = 0;
+
+        }
+        /* We are out of pools, create a new one */
+        else {
+            descriptorPool = VULKAN_AllocateDescriptorPool(rendererData);
+            if (descriptorPool == VK_NULL_HANDLE) {
+                /* SDL_SetError called in VULKAN_AllocateDescriptorPool if we failed to allocate a new pool */
+                return VK_NULL_HANDLE;
+            }
+            rendererData->numDescriptorPools[rendererData->currentCommandBufferIndex]++;
+            VkDescriptorPool *descriptorPools = SDL_realloc(rendererData->descriptorPools[rendererData->currentCommandBufferIndex],
+                                                            sizeof(VkDescriptorPool) * rendererData->numDescriptorPools[rendererData->currentCommandBufferIndex]);
+            descriptorPools[rendererData->numDescriptorPools[rendererData->currentCommandBufferIndex] - 1] = descriptorPool;
+            rendererData->descriptorPools[rendererData->currentCommandBufferIndex] = descriptorPools;
+            rendererData->currentDescriptorPoolIndex = currentDescriptorPoolIndex;
+            rendererData->currentDescriptorSetIndex = 0;
+
+            /* Call recursively to allocate from the new pool */
+            return VULKAN_AllocateDescriptorSet(renderer, shader, sampler, constantBuffer, constantBufferOffset, imageViewCount, imageViews);
+        }
+    }
+    rendererData->currentDescriptorSetIndex++;
+    VkDescriptorImageInfo samplerDescriptor = { 0 };
+    samplerDescriptor.sampler = sampler;
+
+    VkDescriptorImageInfo imageDescriptors[3];
+    SDL_memset(imageDescriptors, 0, sizeof(imageDescriptors));
+    VkDescriptorBufferInfo bufferDescriptor = { 0 };
+    bufferDescriptor.buffer = constantBuffer;
+    bufferDescriptor.offset = constantBufferOffset;
+    bufferDescriptor.range = sizeof(PixelShaderConstants);
+
+    VkWriteDescriptorSet descriptorWrites[5];
+    SDL_memset(descriptorWrites, 0, sizeof(descriptorWrites));
+    uint32_t descriptorCount = 1; /* Always have the uniform buffer */
+
+    descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+    descriptorWrites[0].dstSet = descriptorSet;
+    descriptorWrites[0].dstBinding = 4;
+    descriptorWrites[0].dstArrayElement = 0;
+    descriptorWrites[0].descriptorCount = 1;
+    descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+    descriptorWrites[0].pBufferInfo = &bufferDescriptor;
+
+    if (sampler != VK_NULL_HANDLE) {
+        descriptorCount++;
+        descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        descriptorWrites[1].dstSet = descriptorSet;
+        descriptorWrites[1].dstBinding = 0;
+        descriptorWrites[1].dstArrayElement = 0;
+        descriptorWrites[1].descriptorCount = 1;
+        descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
+        descriptorWrites[1].pImageInfo = &samplerDescriptor;
+    }
+
+    uint32_t startImageViews = descriptorCount;
+    for (uint32_t i = 0; i < 3 && imageViewCount > 0; i++) {
+        descriptorCount++;
+        imageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+        /* There are up to 3 images in the shader, if we haven't specified that many, duplicate the first
+            one.  There is dynamic branching that determines how many actually get fetched, but we need
+            them all populated for validation. */
+        imageDescriptors[i].imageView = (i < imageViewCount) ? imageViews[i] : imageViews[0];
+        descriptorWrites[i+startImageViews].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+        descriptorWrites[i+startImageViews].dstSet = descriptorSet;
+        descriptorWrites[i+startImageViews].dstBinding = 1 + i;
+        descriptorWrites[i+startImageViews].dstArrayElement = 0;
+        descriptorWrites[i+startImageViews].descriptorCount = 1;
+        descriptorWrites[i+startImageViews].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+        descriptorWrites[i+startImageViews].pImageInfo = &imageDescriptors[i];
+    }
+    vkUpdateDescriptorSets(rendererData->device, descriptorCount, descriptorWrites, 0, NULL);
+
+    return descriptorSet;
+}
+
 static SDL_bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderCommand *cmd, VULKAN_Shader shader, const PixelShaderConstants *shader_constants,
                               VkPrimitiveTopology topology, int imageViewCount, VkImageView *imageViews, VkSampler sampler, const Float4X4 *matrix, VULKAN_DrawStateCache *stateCache)
 
@@ -3093,6 +3250,7 @@ static SDL_bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderComm
     const Float4X4 *newmatrix = matrix ? matrix : &rendererData->identity;
     SDL_bool updateConstants = SDL_FALSE;
     PixelShaderConstants solid_constants;
+    VkDescriptorSet descriptorSet;
     VkBuffer constantBuffer;
     VkDeviceSize constantBufferOffset;
     int i;
@@ -3155,7 +3313,8 @@ static SDL_bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderComm
         VULKAN_SetupShaderConstants(renderer, cmd, NULL, &solid_constants);
         shader_constants = &solid_constants;
     }
-    constantBuffer = rendererData->constantBuffers[rendererData->currentCommandBufferIndex].buffer;
+
+    constantBuffer = rendererData->constantBuffers[rendererData->currentCommandBufferIndex][rendererData->currentConstantBufferIndex].buffer;
     constantBufferOffset = (rendererData->currentConstantBufferOffset < 0) ? 0 : rendererData->currentConstantBufferOffset;
     if (updateConstants ||
         SDL_memcmp(shader_constants, &rendererData->currentPipelineState->shader_constants, sizeof(*shader_constants)) != 0) {
@@ -3173,94 +3332,55 @@ static SDL_bool VULKAN_SetDrawState(SDL_Renderer *renderer, const SDL_RenderComm
             constantBufferOffset = rendererData->currentConstantBufferOffset;
         }
 
-        /* Upload constants to persistently mapped buffer */
-        if (rendererData->currentConstantBufferOffset > SDL_VULKAN_CONSTANT_BUFFER_DEFAULT_SIZE) {
-            VULKAN_IssueBatch(rendererData);
+        /* If we have run out of size in this constant buffer, create another if needed */
+        if (rendererData->currentConstantBufferOffset >= SDL_VULKAN_CONSTANT_BUFFER_DEFAULT_SIZE) {
+            uint32_t newConstantBufferIndex = (rendererData->currentConstantBufferIndex + 1);
+            /* We need a new constant buffer */
+            if (newConstantBufferIndex >= rendererData->numConstantBuffers[rendererData->currentCommandBufferIndex]) {
+                VULKAN_Buffer newConstantBuffer;
+                VkResult result = VULKAN_AllocateBuffer(rendererData,
+                    SDL_VULKAN_CONSTANT_BUFFER_DEFAULT_SIZE,
+                    VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
+                    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+                    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+                    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+                    &newConstantBuffer);
+
+                if (result != VK_SUCCESS) {
+                    SDL_SetError("[Vulkan] Could not allocate new memory for constant buffer.\n" );
+                    return SDL_FALSE;
+                }
+
+                rendererData->numConstantBuffers[rendererData->currentCommandBufferIndex]++;
+                VULKAN_Buffer *newConstantBuffers = SDL_realloc(rendererData->constantBuffers[rendererData->currentCommandBufferIndex],
+                                                                sizeof(VULKAN_Buffer) * rendererData->numConstantBuffers[rendererData->currentCommandBufferIndex]);
+                newConstantBuffers[rendererData->numConstantBuffers[rendererData->currentCommandBufferIndex] - 1] = newConstantBuffer;
+                rendererData->constantBuffers[rendererData->currentCommandBufferIndex] = newConstantBuffers;
+            }
+            rendererData->currentConstantBufferIndex = newConstantBufferIndex;
             rendererData->currentConstantBufferOffset = 0;
             constantBufferOffset = 0;
+            constantBuffer = rendererData->constantBuffers[rendererData->currentCommandBufferIndex][rendererData->currentConstantBufferIndex].buffer;
         }
-        uint8_t *dst = rendererData->constantBuffers[rendererData->currentCommandBufferIndex].mappedBufferPtr;
+
+        /* Upload constants to persistently mapped buffer */
+        uint8_t *dst = rendererData->constantBuffers[rendererData->currentCommandBufferIndex][rendererData->currentConstantBufferIndex].mappedBufferPtr;
         dst += constantBufferOffset;
         SDL_memcpy(dst, &rendererData->currentPipelineState->shader_constants, sizeof(PixelShaderConstants));
 
         SDL_memcpy(&rendererData->currentPipelineState->shader_constants, shader_constants, sizeof(*shader_constants));
     }
 
-    /* Allocate the descriptor set */
-    {
-        VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { 0 };
-        descriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
-        descriptorSetAllocateInfo.descriptorSetCount = 1;
-        descriptorSetAllocateInfo.descriptorPool = rendererData->descriptorPools[rendererData->currentCommandBufferIndex];
-        descriptorSetAllocateInfo.pSetLayouts = &rendererData->descriptorSetLayouts[shader];
-
-        VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
-        VkResult result = (rendererData->currentDescriptorSetIndex >= SDL_VULKAN_MAX_DESCRIPTOR_SETS) ? VK_ERROR_OUT_OF_DEVICE_MEMORY : VK_SUCCESS;
-        if (result == VK_SUCCESS) {
-            result = vkAllocateDescriptorSets(rendererData->device, &descriptorSetAllocateInfo, &descriptorSet);
-        }
-        // Out of descriptor sets
-        if (result != VK_SUCCESS) {
-            VULKAN_IssueBatch(rendererData);
-            result = vkAllocateDescriptorSets(rendererData->device, &descriptorSetAllocateInfo, &descriptorSet);
-            if (result != VK_SUCCESS) {
-                SDL_SetError("[Vulkan] Unable to allocate descriptor set.");
-            }
-        }
-        rendererData->currentDescriptorSetIndex++;
-        VkDescriptorImageInfo samplerDescriptor = { 0 };
-        samplerDescriptor.sampler = sampler;
-
-        VkDescriptorImageInfo imageDescriptors[3];
-        SDL_memset(imageDescriptors, 0, sizeof(imageDescriptors));
-        VkDescriptorBufferInfo bufferDescriptor = { 0 };
-        bufferDescriptor.buffer = constantBuffer;
-        bufferDescriptor.offset = constantBufferOffset;
-        bufferDescriptor.range = sizeof(PixelShaderConstants);
-
-        VkWriteDescriptorSet descriptorWrites[5];
-        SDL_memset(descriptorWrites, 0, sizeof(descriptorWrites));
-        uint32_t descriptorCount = 1; /* Always have the uniform buffer */
-
-        descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-        descriptorWrites[0].dstSet = descriptorSet;
-        descriptorWrites[0].dstBinding = 4;
-        descriptorWrites[0].dstArrayElement = 0;
-        descriptorWrites[0].descriptorCount = 1;
-        descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
-        descriptorWrites[0].pBufferInfo = &bufferDescriptor;
-
-        if (sampler != VK_NULL_HANDLE) {
-            descriptorCount++;
-            descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-            descriptorWrites[1].dstSet = descriptorSet;
-            descriptorWrites[1].dstBinding = 0;
-            descriptorWrites[1].dstArrayElement = 0;
-            descriptorWrites[1].descriptorCount = 1;
-            descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER;
-            descriptorWrites[1].pImageInfo = &samplerDescriptor;
-        }
-
-        uint32_t startImageViews = descriptorCount;
-        for (i = 0; i < 3 && imageViewCount > 0; i++) {
-            descriptorCount++;
-            imageDescriptors[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
-            /* There are up to 3 images in the shader, if we haven't specified that many, duplicate the first
-               one.  There is dynamic branching that determines how many actually get fetched, but we need
-               them all populated for validation. */
-            imageDescriptors[i].imageView = (i < imageViewCount) ? imageViews[i] : imageViews[0];
-            descriptorWrites[i+startImageViews].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
-            descriptorWrites[i+startImageViews].dstSet = descriptorSet;
-            descriptorWrites[i+startImageViews].dstBinding = 1 + i;
-            descriptorWrites[i+startImageViews].dstArrayElement = 0;
-            descriptorWrites[i+startImageViews].descriptorCount = 1;
-            descriptorWrites[i+startImageViews].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
-            descriptorWrites[i+startImageViews].pImageInfo = &imageDescriptors[i];
-        }
-        vkUpdateDescriptorSets(rendererData->device, descriptorCount, descriptorWrites, 0, NULL);
-        vkCmdBindDescriptorSets(rendererData->currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, rendererData->currentPipelineState->pipelineLayout,
-            0, 1, &descriptorSet, 0, NULL);
+    /* Allocate/update descriptor set with the bindings */
+    descriptorSet = VULKAN_AllocateDescriptorSet(renderer, shader, sampler, constantBuffer, constantBufferOffset, imageViewCount, imageViews);
+    if (descriptorSet == VK_NULL_HANDLE) {
+        return SDL_FALSE;
     }
+
+    /* Bind the descriptor set with the sampler/UBO/image views */
+    vkCmdBindDescriptorSets(rendererData->currentCommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, rendererData->currentPipelineState->pipelineLayout,
+            0, 1, &descriptorSet, 0, NULL);
+
     return SDL_TRUE;
 }