From 22e56661e90fab3fe95759a71259882a9d0814b7 Mon Sep 17 00:00:00 2001
From: cosmonaut <evan@moonside.games>
Date: Fri, 1 Jan 2021 22:07:15 -0800
Subject: [PATCH] started exposing command buffers

---
 include/Refresh.h           |  18 +
 src/Refresh.c               |  11 +
 src/Refresh_Driver.h        |   6 +
 src/Refresh_Driver_Vulkan.c | 785 ++++++++++++++++++++----------------
 4 files changed, 481 insertions(+), 339 deletions(-)

diff --git a/include/Refresh.h b/include/Refresh.h
index b37f78d..682e3da 100644
--- a/include/Refresh.h
+++ b/include/Refresh.h
@@ -66,6 +66,7 @@ typedef struct REFRESH_ShaderModule REFRESH_ShaderModule;
 typedef struct REFRESH_RenderPass REFRESH_RenderPass;
 typedef struct REFRESH_ComputePipeline REFRESH_ComputePipeline;
 typedef struct REFRESH_GraphicsPipeline REFRESH_GraphicsPipeline;
+typedef struct REFRESH_CommandBuffer REFRESH_CommandBuffer;
 
 typedef enum REFRESH_PresentMode
 {
@@ -1349,6 +1350,23 @@ REFRESHAPI void REFRESH_BindComputeTextures(
 
 /* Submission/Presentation */
 
+/* Returns an allocated REFRESH_CommandBuffer* object.
+ *
+ * NOTE:
+ * 	A command buffer may only be used on the thread that
+ * 	it was acquired on. Using it on any other thread is an error.
+ *
+ * fixed:
+ * 	If a command buffer is designated as fixed, it can be
+ * 	acquired once, have commands recorded into it, and
+ * 	be re-submitted indefinitely.
+ *
+ */
+REFRESHAPI REFRESH_CommandBuffer* REFRESH_AcquireCommandBuffer(
+	REFRESH_Device *device,
+	uint8_t fixed
+);
+
 /* Queues an image to be presented to the screen.
  * The image will be presented upon the next REFRESH_Submit call.
  *
diff --git a/src/Refresh.c b/src/Refresh.c
index a553c75..b439bff 100644
--- a/src/Refresh.c
+++ b/src/Refresh.c
@@ -885,6 +885,17 @@ void REFRESH_BindComputeTextures(
     );
 }
 
+REFRESH_CommandBuffer* REFRESH_AcquireCommandBuffer(
+    REFRESH_Device *device,
+    uint8_t fixed
+) {
+    NULL_RETURN_NULL(device);
+    return device->AcquireCommandBuffer(
+        device->driverData,
+        fixed
+    );
+}
+
 void REFRESH_QueuePresent(
     REFRESH_Device *device,
     REFRESH_TextureSlice* textureSlice,
diff --git a/src/Refresh_Driver.h b/src/Refresh_Driver.h
index ba4d660..bea283b 100644
--- a/src/Refresh_Driver.h
+++ b/src/Refresh_Driver.h
@@ -523,6 +523,11 @@ struct REFRESH_Device
         REFRESH_Texture **pTextures
     );
 
+    REFRESH_CommandBuffer* (*AcquireCommandBuffer)(
+        REFRESH_Renderer *driverData,
+        uint8_t fixed
+    );
+
     void(*QueuePresent)(
         REFRESH_Renderer *driverData,
         REFRESH_TextureSlice *textureSlice,
@@ -590,6 +595,7 @@ struct REFRESH_Device
     ASSIGN_DRIVER_FUNC(BindComputePipeline, name) \
     ASSIGN_DRIVER_FUNC(BindComputeBuffers, name) \
     ASSIGN_DRIVER_FUNC(BindComputeTextures, name) \
+    ASSIGN_DRIVER_FUNC(AcquireCommandBuffer, name) \
     ASSIGN_DRIVER_FUNC(QueuePresent, name) \
     ASSIGN_DRIVER_FUNC(Submit, name)
 
diff --git a/src/Refresh_Driver_Vulkan.c b/src/Refresh_Driver_Vulkan.c
index 069b0eb..01ed4a6 100644
--- a/src/Refresh_Driver_Vulkan.c
+++ b/src/Refresh_Driver_Vulkan.c
@@ -1142,6 +1142,40 @@ static inline void ComputePipelineLayoutHashArray_Insert(
 
 /* Context */
 
+typedef struct VulkanCommandPool VulkanCommandPool;
+
+typedef struct VulkanCommandBuffer
+{
+	VkCommandBuffer commandBuffer;
+	uint8_t fixed;
+	uint8_t submitted;
+
+	VulkanCommandPool *commandPool;
+
+	/* FIXME: do we even use this? */
+	uint32_t numActiveCommands;
+
+	VulkanComputePipeline *currentComputePipeline;
+	VulkanGraphicsPipeline *currentGraphicsPipeline;
+	VulkanFramebuffer *currentFramebuffer;
+
+	VulkanBuffer *boundComputeBuffers[MAX_BUFFER_BINDINGS];
+	uint32_t boundComputeBufferCount;
+} VulkanCommandBuffer;
+
+struct VulkanCommandPool
+{
+	SDL_threadID threadID;
+
+	VkCommandPool commandPool;
+
+	uint32_t allocatedCommandBufferCount;
+
+	VulkanCommandBuffer **inactiveCommandBuffers;
+	uint32_t inactiveCommandBufferCapacity;
+	uint32_t inactiveCommandBufferCount;
+};
+
 typedef struct VulkanRenderer
 {
     VkInstance instance;
@@ -1182,35 +1216,14 @@ typedef struct VulkanRenderer
 	VkSemaphore imageAvailableSemaphore;
 	VkSemaphore renderFinishedSemaphore;
 
-	VkCommandPool commandPool;
-	VkCommandBuffer *inactiveCommandBuffers;
-	VkCommandBuffer *activeCommandBuffers;
-	VkCommandBuffer *submittedCommandBuffers;
-	uint32_t inactiveCommandBufferCount;
-	uint32_t activeCommandBufferCount;
+	VulkanCommandBuffer **submittedCommandBuffers;
 	uint32_t submittedCommandBufferCount;
-	uint32_t allocatedCommandBufferCount;
-	uint32_t currentCommandCount;
-	VkCommandBuffer currentCommandBuffer;
-	uint32_t numActiveCommands;
-
-	VulkanComputePipeline *currentComputePipeline;
-	VulkanGraphicsPipeline *currentGraphicsPipeline;
-	VulkanFramebuffer *currentFramebuffer;
-
-	VulkanBuffer *boundComputeBuffers[MAX_BUFFER_BINDINGS];
-	uint32_t boundComputeBufferCount;
+	uint32_t submittedCommandBufferCapacity;
 
 	DescriptorSetLayoutHashTable descriptorSetLayoutHashTable;
 
 	GraphicsPipelineLayoutHashTable graphicsPipelineLayoutHashTable;
 	ComputePipelineLayoutHashTable computePipelineLayoutHashTable;
-	/*
-	 * TODO: we can get rid of this reference when we
-	 * come up with a clever descriptor set reuse system
-	 */
-	VkDescriptorPool *descriptorPools;
-	uint32_t descriptorPoolCount;
 
 	/* initialize baseline descriptor info */
 	VkDescriptorPool defaultDescriptorPool;
@@ -1259,8 +1272,10 @@ typedef struct VulkanRenderer
 	uint32_t frameIndex;
 
 	SDL_mutex *allocatorLock;
-	SDL_mutex *commandLock;
 	SDL_mutex *disposeLock;
+	SDL_mutex *uniformBufferLock;
+	SDL_mutex *descriptorSetLock;
+	SDL_mutex *boundBufferLock;
 
 	/* Deferred destroy storage */
 
@@ -1353,21 +1368,9 @@ typedef struct VulkanRenderer
 
 /* Forward declarations */
 
-static void VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer);
-static void VULKAN_Submit(REFRESH_Renderer *driverData);
-static void VULKAN_SubmitAndSync(REFRESH_Renderer *driverData);
-
-/* Macros */
-
-#define RECORD_CMD(cmdCall)					\
-	SDL_LockMutex(renderer->commandLock);			\
-	if (renderer->currentCommandBuffer == NULL)		\
-	{							\
-		VULKAN_INTERNAL_BeginCommandBuffer(renderer);	\
-	}							\
-	cmdCall;						\
-	renderer->numActiveCommands += 1;			\
-	SDL_UnlockMutex(renderer->commandLock);
+static void VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer, VulkanCommandBuffer *commandBuffer);
+static void VULKAN_Submit(REFRESH_Renderer *driverData, REFRESH_CommandBuffer **pCommandBuffers, uint32_t commandBufferCount);
+static void VULKAN_SubmitAndSync(REFRESH_Renderer *driverData, REFRESH_CommandBuffer *commandBuffer);
 
 /* Error Handling */
 
@@ -1898,6 +1901,7 @@ static uint8_t VULKAN_INTERNAL_FindAvailableMemory(
 
 static void VULKAN_INTERNAL_BufferMemoryBarrier(
 	VulkanRenderer *renderer,
+	VulkanCommandBuffer *commandBuffer,
 	VulkanResourceAccessType nextResourceAccessType,
 	VulkanBuffer *buffer,
 	VulkanSubBuffer *subBuffer
@@ -1952,8 +1956,8 @@ static void VULKAN_INTERNAL_BufferMemoryBarrier(
 		dstStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
 	}
 
-	RECORD_CMD(renderer->vkCmdPipelineBarrier(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdPipelineBarrier(
+		commandBuffer->commandBuffer,
 		srcStages,
 		dstStages,
 		0,
@@ -1963,13 +1967,15 @@ static void VULKAN_INTERNAL_BufferMemoryBarrier(
 		&memoryBarrier,
 		0,
 		NULL
-	));
+	);
+	commandBuffer->numActiveCommands += 1;
 
 	buffer->resourceAccessType = nextResourceAccessType;
 }
 
 static void VULKAN_INTERNAL_ImageMemoryBarrier(
 	VulkanRenderer *renderer,
+	VulkanCommandBuffer *commandBuffer,
 	VulkanResourceAccessType nextAccess,
 	VkImageAspectFlags aspectMask,
 	uint32_t baseLayer,
@@ -2041,8 +2047,8 @@ static void VULKAN_INTERNAL_ImageMemoryBarrier(
 		dstStages = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
 	}
 
-	RECORD_CMD(renderer->vkCmdPipelineBarrier(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdPipelineBarrier(
+		commandBuffer->commandBuffer,
 		srcStages,
 		dstStages,
 		0,
@@ -2052,7 +2058,8 @@ static void VULKAN_INTERNAL_ImageMemoryBarrier(
 		NULL,
 		1,
 		&memoryBarrier
-	));
+	);
+	commandBuffer->numActiveCommands += 1;
 
 	*resourceAccessType = nextAccess;
 }
@@ -3252,13 +3259,6 @@ static uint8_t VULKAN_INTERNAL_CreateBuffer(
 
 		buffer->subBuffers[i]->resourceAccessType = resourceAccessType;
 		buffer->subBuffers[i]->bound = -1;
-
-		VULKAN_INTERNAL_BufferMemoryBarrier(
-			renderer,
-			buffer->resourceAccessType,
-			buffer,
-			buffer->subBuffers[i]
-		);
 	}
 
 	return 1;
@@ -3266,67 +3266,25 @@ static uint8_t VULKAN_INTERNAL_CreateBuffer(
 
 /* Command Buffers */
 
-static void VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer)
-{
-	VkCommandBufferAllocateInfo allocateInfo;
+static void VULKAN_INTERNAL_BeginCommandBuffer(
+	VulkanRenderer *renderer,
+	VulkanCommandBuffer *commandBuffer
+) {
 	VkCommandBufferBeginInfo beginInfo;
 	VkResult result;
 
 	beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
 	beginInfo.pNext = NULL;
-	beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+	beginInfo.flags = 0;
 	beginInfo.pInheritanceInfo = NULL;
 
-	/* If we are out of unused command buffers, allocate some more */
-	if (renderer->inactiveCommandBufferCount == 0)
+	if (!commandBuffer->fixed)
 	{
-		renderer->activeCommandBuffers = SDL_realloc(
-			renderer->activeCommandBuffers,
-			sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount * 2
-		);
-
-		renderer->inactiveCommandBuffers = SDL_realloc(
-			renderer->inactiveCommandBuffers,
-			sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount * 2
-		);
-
-		renderer->submittedCommandBuffers = SDL_realloc(
-			renderer->submittedCommandBuffers,
-			sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount * 2
-		);
-
-		allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-		allocateInfo.pNext = NULL;
-		allocateInfo.commandPool = renderer->commandPool;
-		allocateInfo.commandBufferCount = renderer->allocatedCommandBufferCount;
-		allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-
-		result = renderer->vkAllocateCommandBuffers(
-			renderer->logicalDevice,
-			&allocateInfo,
-			renderer->inactiveCommandBuffers
-		);
-
-		if (result != VK_SUCCESS)
-		{
-			LogVulkanResult("vkAllocateCommandBuffers", result);
-			return;
-		}
-
-		renderer->inactiveCommandBufferCount = renderer->allocatedCommandBufferCount;
-		renderer->allocatedCommandBufferCount *= 2;
+		beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
 	}
 
-	renderer->currentCommandBuffer =
-		renderer->inactiveCommandBuffers[renderer->inactiveCommandBufferCount - 1];
-
-	renderer->activeCommandBuffers[renderer->activeCommandBufferCount] = renderer->currentCommandBuffer;
-
-	renderer->activeCommandBufferCount += 1;
-	renderer->inactiveCommandBufferCount -= 1;
-
 	result = renderer->vkBeginCommandBuffer(
-		renderer->currentCommandBuffer,
+		commandBuffer->commandBuffer,
 		&beginInfo
 	);
 
@@ -3337,12 +3295,13 @@ static void VULKAN_INTERNAL_BeginCommandBuffer(VulkanRenderer *renderer)
 }
 
 static void VULKAN_INTERNAL_EndCommandBuffer(
-	VulkanRenderer* renderer
+	VulkanRenderer* renderer,
+	VulkanCommandBuffer *commandBuffer
 ) {
 	VkResult result;
 
 	result = renderer->vkEndCommandBuffer(
-		renderer->currentCommandBuffer
+		commandBuffer->commandBuffer
 	);
 
 	if (result != VK_SUCCESS)
@@ -3350,11 +3309,9 @@ static void VULKAN_INTERNAL_EndCommandBuffer(
 		LogVulkanResult("vkEndCommandBuffer", result);
 	}
 
-	renderer->currentComputePipeline = NULL;
-	renderer->boundComputeBufferCount = 0;
-
-	renderer->currentCommandBuffer = NULL;
-	renderer->numActiveCommands = 0;
+	commandBuffer->currentComputePipeline = NULL;
+	commandBuffer->boundComputeBufferCount = 0;
+	commandBuffer->numActiveCommands = 0;
 }
 
 /* Public API */
@@ -3369,8 +3326,6 @@ static void VULKAN_DestroyDevice(
 	VulkanMemorySubAllocator *allocator;
 	uint32_t i, j, k;
 
-	VULKAN_Submit(device->driverData);
-
 	waitResult = renderer->vkDeviceWaitIdle(renderer->logicalDevice);
 
 	if (waitResult != VK_SUCCESS)
@@ -3409,11 +3364,7 @@ static void VULKAN_DestroyDevice(
 		NULL
 	);
 
-	renderer->vkDestroyCommandPool(
-		renderer->logicalDevice,
-		renderer->commandPool,
-		NULL
-	);
+	/* TODO: destroy command pools */
 
 	for (i = 0; i < NUM_PIPELINE_LAYOUT_BUCKETS; i += 1)
 	{
@@ -3569,16 +3520,14 @@ static void VULKAN_DestroyDevice(
 
 	SDL_free(renderer->memoryAllocator);
 
-	SDL_DestroyMutex(renderer->commandLock);
 	SDL_DestroyMutex(renderer->allocatorLock);
 	SDL_DestroyMutex(renderer->disposeLock);
+	SDL_DestroyMutex(renderer->uniformBufferLock);
+	SDL_DestroyMutex(renderer->descriptorSetLock);
+	SDL_DestroyMutex(renderer->boundBufferLock);
 
 	SDL_free(renderer->buffersInUse);
 
-	SDL_free(renderer->inactiveCommandBuffers);
-	SDL_free(renderer->activeCommandBuffers);
-	SDL_free(renderer->submittedCommandBuffers);
-
 	renderer->vkDestroyDevice(renderer->logicalDevice, NULL);
 	renderer->vkDestroyInstance(renderer->instance, NULL);
 
@@ -3588,6 +3537,7 @@ static void VULKAN_DestroyDevice(
 
 static void VULKAN_Clear(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Rect *clearRect,
 	REFRESH_ClearOptions options,
 	REFRESH_Color *colors,
@@ -3596,6 +3546,8 @@ static void VULKAN_Clear(
 	int32_t stencil
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+
 	uint32_t attachmentCount, i;
 	VkClearAttachment clearAttachments[MAX_COLOR_TARGET_BINDINGS + 1];
 	VkClearRect vulkanClearRect;
@@ -3607,7 +3559,7 @@ static void VULKAN_Clear(
 
 	uint8_t shouldClearDepthStencil = (
 		(shouldClearDepth || shouldClearStencil) &&
-		renderer->currentFramebuffer->depthStencilTarget != NULL
+		vulkanCommandBuffer->currentFramebuffer->depthStencilTarget != NULL
 	);
 
 	if (!shouldClearColor && !shouldClearDepthStencil)
@@ -3687,17 +3639,19 @@ static void VULKAN_Clear(
 		attachmentCount += 1;
 	}
 
-	RECORD_CMD(renderer->vkCmdClearAttachments(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdClearAttachments(
+		vulkanCommandBuffer->commandBuffer,
 		attachmentCount,
 		clearAttachments,
 		1,
 		&vulkanClearRect
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 }
 
 static void VULKAN_DrawInstancedPrimitives(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	uint32_t baseVertex,
 	uint32_t minVertexIndex,
 	uint32_t numVertices,
@@ -3710,43 +3664,48 @@ static void VULKAN_DrawInstancedPrimitives(
 	uint32_t fragmentParamOffset
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+
 	VkDescriptorSet descriptorSets[4];
 	uint32_t dynamicOffsets[2];
 
-	descriptorSets[0] = renderer->currentGraphicsPipeline->vertexSamplerDescriptorSet;
-	descriptorSets[1] = renderer->currentGraphicsPipeline->fragmentSamplerDescriptorSet;
-	descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
-	descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
+	descriptorSets[0] = vulkanCommandBuffer->currentGraphicsPipeline->vertexSamplerDescriptorSet;
+	descriptorSets[1] = vulkanCommandBuffer->currentGraphicsPipeline->fragmentSamplerDescriptorSet;
+	descriptorSets[2] = vulkanCommandBuffer->currentGraphicsPipeline->vertexUBODescriptorSet;
+	descriptorSets[3] = vulkanCommandBuffer->currentGraphicsPipeline->fragmentUBODescriptorSet;
 
 	dynamicOffsets[0] = vertexParamOffset;
 	dynamicOffsets[1] = fragmentParamOffset;
 
-	RECORD_CMD(renderer->vkCmdBindDescriptorSets(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindDescriptorSets(
+		vulkanCommandBuffer->commandBuffer,
 		VK_PIPELINE_BIND_POINT_GRAPHICS,
-		renderer->currentGraphicsPipeline->pipelineLayout->pipelineLayout,
+		vulkanCommandBuffer->currentGraphicsPipeline->pipelineLayout->pipelineLayout,
 		0,
 		4,
 		descriptorSets,
 		2,
 		dynamicOffsets
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	RECORD_CMD(renderer->vkCmdDrawIndexed(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdDrawIndexed(
+		vulkanCommandBuffer->commandBuffer,
 		PrimitiveVerts(
-			renderer->currentGraphicsPipeline->primitiveType,
+			vulkanCommandBuffer->currentGraphicsPipeline->primitiveType,
 			primitiveCount
 		),
 		instanceCount,
 		startIndex,
 		baseVertex,
 		0
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 }
 
 static void VULKAN_DrawIndexedPrimitives(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	uint32_t baseVertex,
 	uint32_t minVertexIndex,
 	uint32_t numVertices,
@@ -3759,6 +3718,7 @@ static void VULKAN_DrawIndexedPrimitives(
 ) {
 	VULKAN_DrawInstancedPrimitives(
 		driverData,
+		commandBuffer,
 		baseVertex,
 		minVertexIndex,
 		numVertices,
@@ -3774,64 +3734,71 @@ static void VULKAN_DrawIndexedPrimitives(
 
 static void VULKAN_DrawPrimitives(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	uint32_t vertexStart,
 	uint32_t primitiveCount,
 	uint32_t vertexParamOffset,
 	uint32_t fragmentParamOffset
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VkDescriptorSet descriptorSets[4];
 	uint32_t dynamicOffsets[2];
 
-	descriptorSets[0] = renderer->currentGraphicsPipeline->vertexSamplerDescriptorSet;
-	descriptorSets[1] = renderer->currentGraphicsPipeline->fragmentSamplerDescriptorSet;
-	descriptorSets[2] = renderer->currentGraphicsPipeline->vertexUBODescriptorSet;
-	descriptorSets[3] = renderer->currentGraphicsPipeline->fragmentUBODescriptorSet;
+	descriptorSets[0] = vulkanCommandBuffer->currentGraphicsPipeline->vertexSamplerDescriptorSet;
+	descriptorSets[1] = vulkanCommandBuffer->currentGraphicsPipeline->fragmentSamplerDescriptorSet;
+	descriptorSets[2] = vulkanCommandBuffer->currentGraphicsPipeline->vertexUBODescriptorSet;
+	descriptorSets[3] = vulkanCommandBuffer->currentGraphicsPipeline->fragmentUBODescriptorSet;
 
 	dynamicOffsets[0] = vertexParamOffset;
 	dynamicOffsets[1] = fragmentParamOffset;
 
-	RECORD_CMD(renderer->vkCmdBindDescriptorSets(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindDescriptorSets(
+		vulkanCommandBuffer->commandBuffer,
 		VK_PIPELINE_BIND_POINT_GRAPHICS,
-		renderer->currentGraphicsPipeline->pipelineLayout->pipelineLayout,
+		vulkanCommandBuffer->currentGraphicsPipeline->pipelineLayout->pipelineLayout,
 		0,
 		4,
 		descriptorSets,
 		2,
 		dynamicOffsets
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	RECORD_CMD(renderer->vkCmdDraw(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdDraw(
+		vulkanCommandBuffer->commandBuffer,
 		PrimitiveVerts(
-			renderer->currentGraphicsPipeline->primitiveType,
+			vulkanCommandBuffer->currentGraphicsPipeline->primitiveType,
 			primitiveCount
 		),
 		1,
 		vertexStart,
 		0
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 }
 
 static void VULKAN_DispatchCompute(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	uint32_t groupCountX,
 	uint32_t groupCountY,
 	uint32_t groupCountZ,
 	uint32_t computeParamOffset
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
-	VulkanComputePipeline *computePipeline = renderer->currentComputePipeline;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+	VulkanComputePipeline *computePipeline = vulkanCommandBuffer->currentComputePipeline;
 	VulkanBuffer *currentBuffer;
 	VkDescriptorSet descriptorSets[3];
 	uint32_t i;
 
-	for (i = 0; i < renderer->boundComputeBufferCount; i += 1)
+	for (i = 0; i < vulkanCommandBuffer->boundComputeBufferCount; i += 1)
 	{
-		currentBuffer = renderer->boundComputeBuffers[i];
+		currentBuffer = vulkanCommandBuffer->boundComputeBuffers[i];
 		VULKAN_INTERNAL_BufferMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_COMPUTE_SHADER_READ_OTHER,
 			currentBuffer,
 			currentBuffer->subBuffers[currentBuffer->currentSubBufferIndex]
@@ -3842,8 +3809,8 @@ static void VULKAN_DispatchCompute(
 	descriptorSets[1] = computePipeline->imageDescriptorSet;
 	descriptorSets[2] = computePipeline->computeUBODescriptorSet;
 
-	RECORD_CMD(renderer->vkCmdBindDescriptorSets(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindDescriptorSets(
+		vulkanCommandBuffer->commandBuffer,
 		VK_PIPELINE_BIND_POINT_COMPUTE,
 		computePipeline->pipelineLayout->pipelineLayout,
 		0,
@@ -3851,22 +3818,25 @@ static void VULKAN_DispatchCompute(
 		descriptorSets,
 		1,
 		&computeParamOffset
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	RECORD_CMD(renderer->vkCmdDispatch(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdDispatch(
+		vulkanCommandBuffer->commandBuffer,
 		groupCountX,
 		groupCountY,
 		groupCountZ
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	for (i = 0; i < renderer->boundComputeBufferCount; i += 1)
+	for (i = 0; i < vulkanCommandBuffer->boundComputeBufferCount; i += 1)
 	{
-		currentBuffer = renderer->boundComputeBuffers[i];
+		currentBuffer = vulkanCommandBuffer->boundComputeBuffers[i];
 		if (currentBuffer->usage & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT)
 		{
 			VULKAN_INTERNAL_BufferMemoryBarrier(
 				renderer,
+				vulkanCommandBuffer,
 				RESOURCE_ACCESS_VERTEX_BUFFER,
 				currentBuffer,
 				currentBuffer->subBuffers[currentBuffer->currentSubBufferIndex]
@@ -3876,6 +3846,7 @@ static void VULKAN_DispatchCompute(
 		{
 			VULKAN_INTERNAL_BufferMemoryBarrier(
 				renderer,
+				vulkanCommandBuffer,
 				RESOURCE_ACCESS_INDEX_BUFFER,
 				currentBuffer,
 				currentBuffer->subBuffers[currentBuffer->currentSubBufferIndex]
@@ -4139,6 +4110,8 @@ static uint8_t VULKAN_INTERNAL_AllocateDescriptorSets(
 	VkDescriptorSetAllocateInfo descriptorSetAllocateInfo;
 	VkDescriptorSetLayout *descriptorSetLayouts = SDL_stack_alloc(VkDescriptorSetLayout, descriptorSetCount);
 
+	SDL_LockMutex(renderer->descriptorSetLock);
+
 	for (i = 0; i < descriptorSetCount; i += 1)
 	{
 		descriptorSetLayouts[i] = descriptorSetLayout;
@@ -4163,6 +4136,8 @@ static uint8_t VULKAN_INTERNAL_AllocateDescriptorSets(
 		return 0;
 	}
 
+	SDL_UnlockMutex(renderer->descriptorSetLock);
+
 	SDL_stack_free(descriptorSetLayouts);
 	return 1;
 }
@@ -5622,19 +5597,6 @@ static REFRESH_ColorTarget* VULKAN_CreateColorTarget(
 		);
 		colorTarget->multisampleTexture->colorFormat = colorTarget->texture->colorFormat;
 		colorTarget->multisampleCount = multisampleCount;
-
-		VULKAN_INTERNAL_ImageMemoryBarrier(
-			renderer,
-			RESOURCE_ACCESS_COLOR_ATTACHMENT_READ_WRITE,
-			VK_IMAGE_ASPECT_COLOR_BIT,
-			0,
-			colorTarget->multisampleTexture->layerCount,
-			0,
-			colorTarget->multisampleTexture->levelCount,
-			0,
-			colorTarget->multisampleTexture->image,
-			&colorTarget->multisampleTexture->resourceAccessType
-		);
 	}
 
 	/* create framebuffer compatible views for RenderTarget */
@@ -5792,6 +5754,7 @@ static void VULKAN_INTERNAL_MaybeExpandStagingBuffer(
 
 static void VULKAN_SetTextureData2D(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	uint32_t x,
 	uint32_t y,
@@ -5803,6 +5766,7 @@ static void VULKAN_SetTextureData2D(
 ) {
 	VkResult vulkanResult;
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *vulkanTexture = (VulkanTexture*) texture;
 	VkBufferImageCopy imageCopy;
 	uint8_t *mapPointer;
@@ -5833,6 +5797,7 @@ static void VULKAN_SetTextureData2D(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -5858,19 +5823,21 @@ static void VULKAN_SetTextureData2D(
 	imageCopy.bufferRowLength = 0;
 	imageCopy.bufferImageHeight = 0;
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		vulkanTexture->image,
 		AccessMap[vulkanTexture->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	if (vulkanTexture->usageFlags & REFRESH_TEXTUREUSAGE_SAMPLER_BIT)
 	{
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE,
 			VK_IMAGE_ASPECT_COLOR_BIT,
 			0,
@@ -5884,11 +5851,12 @@ static void VULKAN_SetTextureData2D(
 	}
 
 	/* Hard sync point */
-	VULKAN_SubmitAndSync(driverData);
+	VULKAN_SubmitAndSync(driverData, commandBuffer);
 }
 
 static void VULKAN_SetTextureData3D(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	uint32_t x,
 	uint32_t y,
@@ -5902,6 +5870,7 @@ static void VULKAN_SetTextureData3D(
 ) {
 	VkResult vulkanResult;
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *vulkanTexture = (VulkanTexture*) texture;
 	VkBufferImageCopy imageCopy;
 	uint8_t *mapPointer;
@@ -5932,6 +5901,7 @@ static void VULKAN_SetTextureData3D(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -5957,19 +5927,21 @@ static void VULKAN_SetTextureData3D(
 	imageCopy.bufferRowLength = 0;
 	imageCopy.bufferImageHeight = 0;
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		vulkanTexture->image,
 		AccessMap[vulkanTexture->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	if (vulkanTexture->usageFlags & REFRESH_TEXTUREUSAGE_SAMPLER_BIT)
 	{
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE,
 			VK_IMAGE_ASPECT_COLOR_BIT,
 			0,
@@ -5983,11 +5955,12 @@ static void VULKAN_SetTextureData3D(
 	}
 
 	/* Hard sync point */
-	VULKAN_SubmitAndSync(driverData);
+	VULKAN_SubmitAndSync(driverData, commandBuffer);
 }
 
 static void VULKAN_SetTextureDataCube(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	uint32_t x,
 	uint32_t y,
@@ -6000,6 +5973,7 @@ static void VULKAN_SetTextureDataCube(
 ) {
 	VkResult vulkanResult;
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *vulkanTexture = (VulkanTexture*) texture;
 	VkBufferImageCopy imageCopy;
 	uint8_t *mapPointer;
@@ -6030,6 +6004,7 @@ static void VULKAN_SetTextureDataCube(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6055,19 +6030,21 @@ static void VULKAN_SetTextureDataCube(
 	imageCopy.bufferRowLength = 0; /* assumes tightly packed data */
 	imageCopy.bufferImageHeight = 0; /* assumes tightly packed data */
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		vulkanTexture->image,
 		AccessMap[vulkanTexture->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	if (vulkanTexture->usageFlags & REFRESH_TEXTUREUSAGE_SAMPLER_BIT)
 	{
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE,
 			VK_IMAGE_ASPECT_COLOR_BIT,
 			0,
@@ -6081,11 +6058,12 @@ static void VULKAN_SetTextureDataCube(
 	}
 
 	/* Hard sync point */
-	VULKAN_SubmitAndSync(driverData);
+	VULKAN_SubmitAndSync(driverData, commandBuffer);
 }
 
 static void VULKAN_SetTextureDataYUV(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *y,
 	REFRESH_Texture *u,
 	REFRESH_Texture *v,
@@ -6097,6 +6075,7 @@ static void VULKAN_SetTextureDataYUV(
 	uint32_t dataLength
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *tex;
 	uint8_t *dataPtr = (uint8_t*) data;
 	int32_t yDataLength = BytesPerImage(yWidth, yHeight, REFRESH_SURFACEFORMAT_R8);
@@ -6146,6 +6125,7 @@ static void VULKAN_SetTextureDataYUV(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6162,14 +6142,15 @@ static void VULKAN_SetTextureDataYUV(
 	imageCopy.bufferRowLength = yWidth;
 	imageCopy.bufferImageHeight = yHeight;
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		tex->image,
 		AccessMap[tex->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	/* These apply to both U and V */
 
@@ -6192,6 +6173,7 @@ static void VULKAN_SetTextureDataYUV(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6203,14 +6185,15 @@ static void VULKAN_SetTextureDataYUV(
 		&tex->resourceAccessType
 	);
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		tex->image,
 		AccessMap[tex->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	/* V */
 
@@ -6231,6 +6214,7 @@ static void VULKAN_SetTextureDataYUV(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6242,19 +6226,21 @@ static void VULKAN_SetTextureDataYUV(
 		&tex->resourceAccessType
 	);
 
-	RECORD_CMD(renderer->vkCmdCopyBufferToImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyBufferToImage(
+		vulkanCommandBuffer->commandBuffer,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		tex->image,
 		AccessMap[tex->resourceAccessType].imageLayout,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	if (tex->usageFlags & REFRESH_TEXTUREUSAGE_SAMPLER_BIT)
 	{
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE,
 			VK_IMAGE_ASPECT_COLOR_BIT,
 			0,
@@ -6268,7 +6254,7 @@ static void VULKAN_SetTextureDataYUV(
 	}
 
 	/* Hard sync point */
-	VULKAN_SubmitAndSync(driverData);
+	VULKAN_SubmitAndSync(driverData, commandBuffer);
 }
 
 static void VULKAN_SetBufferData(
@@ -6339,17 +6325,21 @@ static void VULKAN_SetBufferData(
 
 static uint32_t VULKAN_PushVertexShaderParams(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	void *data,
 	uint32_t elementCount
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*)driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+
+	SDL_LockMutex(renderer->uniformBufferLock);
 
 	renderer->vertexUBOOffset += renderer->vertexUBOBlockIncrement;
-	renderer->vertexUBOBlockIncrement = renderer->currentGraphicsPipeline->vertexUBOBlockSize;
+	renderer->vertexUBOBlockIncrement = vulkanCommandBuffer->currentGraphicsPipeline->vertexUBOBlockSize;
 
 	if (
 		renderer->vertexUBOOffset +
-		renderer->currentGraphicsPipeline->vertexUBOBlockSize >=
+		vulkanCommandBuffer->currentGraphicsPipeline->vertexUBOBlockSize >=
 		UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
 	) {
 		REFRESH_LogError("Vertex UBO overflow!");
@@ -6361,25 +6351,31 @@ static uint32_t VULKAN_PushVertexShaderParams(
 		(REFRESH_Buffer*) renderer->vertexUBO,
 		renderer->vertexUBOOffset,
 		data,
-		elementCount * renderer->currentGraphicsPipeline->vertexUBOBlockSize
+		elementCount * vulkanCommandBuffer->currentGraphicsPipeline->vertexUBOBlockSize
 	);
 
+	SDL_UnlockMutex(renderer->uniformBufferLock);
+
 	return renderer->vertexUBOOffset;
 }
 
 static uint32_t VULKAN_PushFragmentShaderParams(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	void *data,
 	uint32_t elementCount
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*)driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+
+	SDL_LockMutex(renderer->uniformBufferLock);
 
 	renderer->fragmentUBOOffset += renderer->fragmentUBOBlockIncrement;
-	renderer->fragmentUBOBlockIncrement = renderer->currentGraphicsPipeline->fragmentUBOBlockSize;
+	renderer->fragmentUBOBlockIncrement = vulkanCommandBuffer->currentGraphicsPipeline->fragmentUBOBlockSize;
 
 	if (
 		renderer->fragmentUBOOffset +
-		renderer->currentGraphicsPipeline->fragmentUBOBlockSize >=
+		vulkanCommandBuffer->currentGraphicsPipeline->fragmentUBOBlockSize >=
 		UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
 	) {
 		REFRESH_LogError("Fragment UBO overflow!");
@@ -6391,25 +6387,31 @@ static uint32_t VULKAN_PushFragmentShaderParams(
 		(REFRESH_Buffer*) renderer->fragmentUBO,
 		renderer->fragmentUBOOffset,
 		data,
-		elementCount * renderer->currentGraphicsPipeline->fragmentUBOBlockSize
+		elementCount * vulkanCommandBuffer->currentGraphicsPipeline->fragmentUBOBlockSize
 	);
 
+	SDL_UnlockMutex(renderer->uniformBufferLock);
+
 	return renderer->fragmentUBOOffset;
 }
 
 static uint32_t VULKAN_PushComputeShaderParams(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	void *data,
 	uint32_t elementCount
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*)driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+
+	SDL_LockMutex(renderer->uniformBufferLock);
 
 	renderer->computeUBOOffset += renderer->computeUBOBlockIncrement;
-	renderer->computeUBOBlockIncrement = renderer->currentComputePipeline->computeUBOBlockSize;
+	renderer->computeUBOBlockIncrement = vulkanCommandBuffer->currentComputePipeline->computeUBOBlockSize;
 
 	if (
 		renderer->computeUBOOffset +
-		renderer->currentComputePipeline->computeUBOBlockSize >=
+		vulkanCommandBuffer->currentComputePipeline->computeUBOBlockSize >=
 		UBO_BUFFER_SIZE * (renderer->frameIndex + 1)
 	) {
 		REFRESH_LogError("Compute UBO overflow!");
@@ -6421,9 +6423,11 @@ static uint32_t VULKAN_PushComputeShaderParams(
 		(REFRESH_Buffer*) renderer->computeUBO,
 		renderer->computeUBOOffset,
 		data,
-		elementCount * renderer->currentComputePipeline->computeUBOBlockSize
+		elementCount * vulkanCommandBuffer->currentComputePipeline->computeUBOBlockSize
 	);
 
+	SDL_UnlockMutex(renderer->uniformBufferLock);
+
 	return renderer->computeUBOOffset;
 }
 
@@ -6728,6 +6732,7 @@ static VkDescriptorSet VULKAN_INTERNAL_FetchImageDescriptorSet(
 
 static void VULKAN_SetVertexSamplers(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture **pTextures,
 	REFRESH_Sampler **pSamplers
 ) {
@@ -6735,7 +6740,8 @@ static void VULKAN_SetVertexSamplers(
 	uint32_t i, samplerCount;
 
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
-	VulkanGraphicsPipeline *graphicsPipeline = renderer->currentGraphicsPipeline;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+	VulkanGraphicsPipeline *graphicsPipeline = vulkanCommandBuffer->currentGraphicsPipeline;
 	ImageDescriptorSetData vertexSamplerDescriptorSetData;
 
 	if (graphicsPipeline->pipelineLayout->vertexSamplerDescriptorSetCache == NULL)
@@ -6762,6 +6768,7 @@ static void VULKAN_SetVertexSamplers(
 
 static void VULKAN_SetFragmentSamplers(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture **pTextures,
 	REFRESH_Sampler **pSamplers
 ) {
@@ -6769,7 +6776,8 @@ static void VULKAN_SetFragmentSamplers(
 	uint32_t i, samplerCount;
 
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
-	VulkanGraphicsPipeline *graphicsPipeline = renderer->currentGraphicsPipeline;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+	VulkanGraphicsPipeline *graphicsPipeline = vulkanCommandBuffer->currentGraphicsPipeline;
 	ImageDescriptorSetData fragmentSamplerDescriptorSetData;
 
 	if (graphicsPipeline->pipelineLayout->fragmentSamplerDescriptorSetCache == NULL)
@@ -6796,6 +6804,7 @@ static void VULKAN_SetFragmentSamplers(
 
 static void VULKAN_INTERNAL_GetTextureData(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	int32_t x,
 	int32_t y,
@@ -6806,6 +6815,7 @@ static void VULKAN_INTERNAL_GetTextureData(
 	void* data
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *vulkanTexture = (VulkanTexture*) texture;
 	VulkanResourceAccessType prevResourceAccess;
 	VkBufferImageCopy imageCopy;
@@ -6821,6 +6831,7 @@ static void VULKAN_INTERNAL_GetTextureData(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_READ,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6848,19 +6859,21 @@ static void VULKAN_INTERNAL_GetTextureData(
 	imageCopy.imageSubresource.mipLevel = level;
 	imageCopy.bufferOffset = 0;
 
-	RECORD_CMD(renderer->vkCmdCopyImageToBuffer(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdCopyImageToBuffer(
+		vulkanCommandBuffer->commandBuffer,
 		vulkanTexture->image,
 		AccessMap[vulkanTexture->resourceAccessType].imageLayout,
 		renderer->textureStagingBuffer->subBuffers[0]->buffer,
 		1,
 		&imageCopy
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	/* Restore the image layout and wait for completion of the render pass */
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		prevResourceAccess,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -6873,7 +6886,7 @@ static void VULKAN_INTERNAL_GetTextureData(
 	);
 
 	/* Hard sync point */
-	VULKAN_SubmitAndSync(driverData);
+	VULKAN_SubmitAndSync(driverData, commandBuffer);
 
 	/* Read from staging buffer */
 
@@ -6906,6 +6919,7 @@ static void VULKAN_INTERNAL_GetTextureData(
 
 static void VULKAN_GetTextureData2D(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	uint32_t x,
 	uint32_t y,
@@ -6916,6 +6930,7 @@ static void VULKAN_GetTextureData2D(
 ) {
     VULKAN_INTERNAL_GetTextureData(
 		driverData,
+		commandBuffer,
 		texture,
 		x,
 		y,
@@ -6929,6 +6944,7 @@ static void VULKAN_GetTextureData2D(
 
 static void VULKAN_GetTextureDataCube(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture *texture,
 	uint32_t x,
 	uint32_t y,
@@ -6940,6 +6956,7 @@ static void VULKAN_GetTextureDataCube(
 ) {
     VULKAN_INTERNAL_GetTextureData(
 		driverData,
+		commandBuffer,
 		texture,
 		x,
 		y,
@@ -7174,6 +7191,7 @@ static void VULKAN_AddDisposeGraphicsPipeline(
 
 static void VULKAN_BeginRenderPass(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_RenderPass *renderPass,
 	REFRESH_Framebuffer *framebuffer,
 	REFRESH_Rect renderArea,
@@ -7182,6 +7200,7 @@ static void VULKAN_BeginRenderPass(
 	REFRESH_DepthStencilValue *depthStencilClearValue
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanFramebuffer *vulkanFramebuffer = (VulkanFramebuffer*) framebuffer;
 	VkClearValue *clearValues;
 	uint32_t i;
@@ -7194,6 +7213,7 @@ static void VULKAN_BeginRenderPass(
 	{
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_COLOR_ATTACHMENT_READ_WRITE,
 			VK_IMAGE_ASPECT_COLOR_BIT,
 			vulkanFramebuffer->colorTargets[i]->layer,
@@ -7217,6 +7237,7 @@ static void VULKAN_BeginRenderPass(
 
 		VULKAN_INTERNAL_ImageMemoryBarrier(
 			renderer,
+			vulkanCommandBuffer,
 			RESOURCE_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_WRITE,
 			depthAspectFlags,
 			0,
@@ -7263,35 +7284,40 @@ static void VULKAN_BeginRenderPass(
 	renderPassBeginInfo.pClearValues = clearValues;
 	renderPassBeginInfo.clearValueCount = clearCount;
 
-	RECORD_CMD(renderer->vkCmdBeginRenderPass(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBeginRenderPass(
+		vulkanCommandBuffer->commandBuffer,
 		&renderPassBeginInfo,
 		VK_SUBPASS_CONTENTS_INLINE
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	renderer->currentFramebuffer = vulkanFramebuffer;
+	vulkanCommandBuffer->currentFramebuffer = vulkanFramebuffer;
 
 	SDL_stack_free(clearValues);
 }
 
 static void VULKAN_EndRenderPass(
-	REFRESH_Renderer *driverData
+	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer
 ) {
 	uint32_t i;
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture *currentTexture;
 
-	RECORD_CMD(renderer->vkCmdEndRenderPass(
-		renderer->currentCommandBuffer
-	));
+	renderer->vkCmdEndRenderPass(
+		vulkanCommandBuffer->commandBuffer
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	for (i = 0; i < renderer->currentFramebuffer->colorTargetCount; i += 1)
+	for (i = 0; i < vulkanCommandBuffer->currentFramebuffer->colorTargetCount; i += 1)
 	{
-		currentTexture = renderer->currentFramebuffer->colorTargets[i]->texture;
+		currentTexture = vulkanCommandBuffer->currentFramebuffer->colorTargets[i]->texture;
 		if (currentTexture->usageFlags & REFRESH_TEXTUREUSAGE_SAMPLER_BIT)
 		{
 			VULKAN_INTERNAL_ImageMemoryBarrier(
 				renderer,
+				vulkanCommandBuffer,
 				RESOURCE_ACCESS_ANY_SHADER_READ_SAMPLED_IMAGE,
 				VK_IMAGE_ASPECT_COLOR_BIT,
 				0,
@@ -7305,15 +7331,17 @@ static void VULKAN_EndRenderPass(
 		}
 	}
 
-	renderer->currentGraphicsPipeline = NULL;
-	renderer->currentFramebuffer = NULL;
+	vulkanCommandBuffer->currentGraphicsPipeline = NULL;
+	vulkanCommandBuffer->currentFramebuffer = NULL;
 }
 
 static void VULKAN_BindGraphicsPipeline(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_GraphicsPipeline *graphicsPipeline
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanGraphicsPipeline* pipeline = (VulkanGraphicsPipeline*) graphicsPipeline;
 
 	/* bind dummy sets */
@@ -7327,13 +7355,14 @@ static void VULKAN_BindGraphicsPipeline(
 		pipeline->fragmentSamplerDescriptorSet = renderer->emptyFragmentSamplerDescriptorSet;
 	}
 
-	RECORD_CMD(renderer->vkCmdBindPipeline(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindPipeline(
+		vulkanCommandBuffer->commandBuffer,
 		VK_PIPELINE_BIND_POINT_GRAPHICS,
 		pipeline->pipeline
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	renderer->currentGraphicsPipeline = pipeline;
+	vulkanCommandBuffer->currentGraphicsPipeline = pipeline;
 }
 
 static void VULKAN_INTERNAL_MarkAsBound(
@@ -7348,6 +7377,8 @@ static void VULKAN_INTERNAL_MarkAsBound(
 
 	buf->bound = 1;
 
+	SDL_LockMutex(renderer->boundBufferLock);
+
 	if (renderer->buffersInUseCount == renderer->buffersInUseCapacity)
 	{
 		renderer->buffersInUseCapacity *= 2;
@@ -7359,16 +7390,20 @@ static void VULKAN_INTERNAL_MarkAsBound(
 
 	renderer->buffersInUse[renderer->buffersInUseCount] = buf;
 	renderer->buffersInUseCount += 1;
+
+	SDL_UnlockMutex(renderer->boundBufferLock);
 }
 
 static void VULKAN_BindVertexBuffers(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	uint32_t firstBinding,
 	uint32_t bindingCount,
 	REFRESH_Buffer **pBuffers,
 	uint64_t *pOffsets
 ) {
 	VkBuffer *buffers = SDL_stack_alloc(VkBuffer, bindingCount);
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanBuffer* currentBuffer;
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
 	uint32_t i;
@@ -7380,41 +7415,47 @@ static void VULKAN_BindVertexBuffers(
 		VULKAN_INTERNAL_MarkAsBound(renderer, currentBuffer);
 	}
 
-	RECORD_CMD(renderer->vkCmdBindVertexBuffers(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindVertexBuffers(
+		vulkanCommandBuffer->commandBuffer,
 		firstBinding,
 		bindingCount,
 		buffers,
 		pOffsets
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	SDL_stack_free(buffers);
 }
 
 static void VULKAN_BindIndexBuffer(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Buffer *buffer,
 	uint64_t offset,
 	REFRESH_IndexElementSize indexElementSize
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanBuffer* vulkanBuffer = (VulkanBuffer*) buffer;
 
 	VULKAN_INTERNAL_MarkAsBound(renderer, vulkanBuffer);
 
-	RECORD_CMD(renderer->vkCmdBindIndexBuffer(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBindIndexBuffer(
+		vulkanCommandBuffer->commandBuffer,
 		vulkanBuffer->subBuffers[renderer->frameIndex]->buffer,
 		offset,
 		RefreshToVK_IndexType[indexElementSize]
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 }
 
 static void VULKAN_BindComputePipeline(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_ComputePipeline *computePipeline
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanComputePipeline *vulkanComputePipeline = (VulkanComputePipeline*) computePipeline;
 
 	/* bind dummy sets */
@@ -7429,20 +7470,23 @@ static void VULKAN_BindComputePipeline(
 	}
 
 	renderer->vkCmdBindPipeline(
-		renderer->currentCommandBuffer,
+		vulkanCommandBuffer->commandBuffer,
 		VK_PIPELINE_BIND_POINT_COMPUTE,
 		vulkanComputePipeline->pipeline
 	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
-	renderer->currentComputePipeline = vulkanComputePipeline;
+	vulkanCommandBuffer->currentComputePipeline = vulkanComputePipeline;
 }
 
 static void VULKAN_BindComputeBuffers(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Buffer **pBuffers
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
-	VulkanComputePipeline *computePipeline = renderer->currentComputePipeline;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+	VulkanComputePipeline *computePipeline = vulkanCommandBuffer->currentComputePipeline;
 	VulkanBuffer *currentBuffer;
 	BufferDescriptorSetData bufferDescriptorSetData;
 	uint32_t i;
@@ -7461,10 +7505,10 @@ static void VULKAN_BindComputeBuffers(
 		bufferDescriptorSetData.descriptorBufferInfo[i].range = currentBuffer->subBuffers[currentBuffer->currentSubBufferIndex]->size;
 
 		VULKAN_INTERNAL_MarkAsBound(renderer, currentBuffer);
-		renderer->boundComputeBuffers[i] = currentBuffer;
+		vulkanCommandBuffer->boundComputeBuffers[i] = currentBuffer;
 	}
 
-	renderer->boundComputeBufferCount = computePipeline->pipelineLayout->bufferDescriptorSetCache->bindingCount;
+	vulkanCommandBuffer->boundComputeBufferCount = computePipeline->pipelineLayout->bufferDescriptorSetCache->bindingCount;
 
 	computePipeline->bufferDescriptorSet =
 		VULKAN_INTERNAL_FetchBufferDescriptorSet(
@@ -7476,10 +7520,12 @@ static void VULKAN_BindComputeBuffers(
 
 static void VULKAN_BindComputeTextures(
 	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_Texture **pTextures
 ) {
 	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
-	VulkanComputePipeline *computePipeline = renderer->currentComputePipeline;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
+	VulkanComputePipeline *computePipeline = vulkanCommandBuffer->currentComputePipeline;
 	VulkanTexture *currentTexture;
 	ImageDescriptorSetData imageDescriptorSetData;
 	uint32_t i;
@@ -7505,8 +7551,89 @@ static void VULKAN_BindComputeTextures(
 		);
 }
 
+static VulkanCommandBuffer* VULKAN_INTERNAL_GetInactiveCommandBufferFromPool(
+	VulkanRenderer *renderer,
+	SDL_threadID threadID
+) {
+	VulkanCommandPool *commandPool =
+		VULKAN_INTERNAL_GetCommandPool(renderer, threadID);
+	VulkanCommandBuffer *commandBuffer;
+	VkCommandBufferAllocateInfo allocateInfo;
+	VkResult vulkanResult;
+
+	if (commandPool->inactiveCommandBufferCount == 0)
+	{
+		commandPool->inactiveCommandBuffers = SDL_realloc(
+			commandPool->inactiveCommandBuffers,
+			sizeof(VulkanCommandBuffer*) * commandPool->allocatedCommandBufferCount * 2
+		);
+
+		allocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+		allocateInfo.pNext = NULL;
+		allocateInfo.commandPool = commandPool->commandPool;
+		allocateInfo.commandBufferCount = commandPool->allocatedCommandBufferCount;
+		allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+
+		vulkanResult = renderer->vkAllocateCommandBuffers(
+			renderer->logicalDevice,
+			&allocateInfo,
+			commandPool->inactiveCommandBuffers
+		);
+
+		if (vulkanResult != VK_SUCCESS)
+		{
+			LogVulkanResult("vkAllocateCommandBuffers", vulkanResult);
+			return NULL;
+		}
+
+		commandPool->inactiveCommandBufferCount = commandPool->allocatedCommandBufferCount;
+		commandPool->allocatedCommandBufferCount *= 2;
+	}
+
+	commandBuffer = commandPool->inactiveCommandBuffers[commandPool->inactiveCommandBufferCount];
+	commandPool->inactiveCommandBufferCount -= 1;
+
+	return commandBuffer;
+}
+
+static REFRESH_CommandBuffer* VULKAN_AcquireCommandBuffer(
+	REFRESH_Renderer *driverData,
+	uint8_t fixed
+) {
+	VulkanRenderer *renderer = (VulkanRenderer*) driverData;
+	uint32_t i;
+
+	SDL_threadID threadID = SDL_ThreadID();
+
+	VulkanCommandBuffer *commandBuffer =
+		VULKAN_INTERNAL_GetInactiveCommandBufferFromPool(renderer, threadID);
+
+	/* State tracking */
+
+	commandBuffer->currentComputePipeline = NULL;
+	commandBuffer->currentGraphicsPipeline = NULL;
+	commandBuffer->currentFramebuffer = NULL;
+
+	/* init bound compute buffer array */
+
+	for (i = 0; i < MAX_BUFFER_BINDINGS; i += 1)
+	{
+		commandBuffer->boundComputeBuffers[i] = NULL;
+	}
+	commandBuffer->boundComputeBufferCount = 0;
+
+	commandBuffer->numActiveCommands = 0;
+	commandBuffer->fixed = fixed;
+	commandBuffer->submitted = 0;
+
+	VULKAN_INTERNAL_BeginCommandBuffer(renderer, commandBuffer);
+
+	return (REFRESH_CommandBuffer*) commandBuffer;
+}
+
 static void VULKAN_QueuePresent(
 	REFRESH_Renderer* driverData,
+	REFRESH_CommandBuffer *commandBuffer,
 	REFRESH_TextureSlice* textureSlice,
 	REFRESH_Rect* sourceRectangle,
 	REFRESH_Rect* destinationRectangle
@@ -7519,6 +7646,7 @@ static void VULKAN_QueuePresent(
 	VkImageBlit blit;
 
 	VulkanRenderer* renderer = (VulkanRenderer*) driverData;
+	VulkanCommandBuffer *vulkanCommandBuffer = (VulkanCommandBuffer*) commandBuffer;
 	VulkanTexture* vulkanTexture = (VulkanTexture*) textureSlice->texture;
 
 	if (renderer->headless)
@@ -7575,6 +7703,7 @@ static void VULKAN_QueuePresent(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_READ,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -7588,6 +7717,7 @@ static void VULKAN_QueuePresent(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_TRANSFER_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -7623,8 +7753,8 @@ static void VULKAN_QueuePresent(
 	blit.dstSubresource.layerCount = 1;
 	blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
 
-	RECORD_CMD(renderer->vkCmdBlitImage(
-		renderer->currentCommandBuffer,
+	renderer->vkCmdBlitImage(
+		vulkanCommandBuffer->commandBuffer,
 		vulkanTexture->image,
 		VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
 		renderer->swapChainImages[swapChainImageIndex],
@@ -7632,10 +7762,12 @@ static void VULKAN_QueuePresent(
 		1,
 		&blit,
 		VK_FILTER_LINEAR
-	));
+	);
+	vulkanCommandBuffer->numActiveCommands += 1;
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_PRESENT,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -7649,6 +7781,7 @@ static void VULKAN_QueuePresent(
 
 	VULKAN_INTERNAL_ImageMemoryBarrier(
 		renderer,
+		vulkanCommandBuffer,
 		RESOURCE_ACCESS_COLOR_ATTACHMENT_READ_WRITE,
 		VK_IMAGE_ASPECT_COLOR_BIT,
 		0,
@@ -7823,11 +7956,38 @@ static void VULKAN_INTERNAL_ResetDescriptorSetData(VulkanRenderer *renderer)
 	}
 }
 
-static void VULKAN_SubmitAndSync(REFRESH_Renderer *driverData)
-{
+static void VULKAN_INTERNAL_ResetCommandBuffer(
+	VulkanRenderer *renderer,
+	VulkanCommandBuffer *commandBuffer
+) {
+	VkResult vulkanResult;
+	VulkanCommandPool *commandPool = commandBuffer->commandPool;
+
+	vulkanResult = renderer->vkResetCommandBuffer(
+		commandBuffer,
+		VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT
+	);
+
+	if (vulkanResult != VK_SUCCESS)
+	{
+		LogVulkanResult("vkResetCommandBuffer", vulkanResult);
+	}
+
+	commandBuffer->submitted = 0;
+
+	commandPool->inactiveCommandBuffers[
+		commandPool->inactiveCommandBufferCount
+	] = commandBuffer;
+	commandPool->inactiveCommandBufferCount += 1;
+}
+
+static void VULKAN_SubmitAndSync(
+	REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer *commandBuffer
+) {
 	VulkanRenderer* renderer = (VulkanRenderer*)driverData;
 
-	VULKAN_Submit(driverData);
+	VULKAN_Submit(driverData, &commandBuffer, 1);
 
 	renderer->vkWaitForFences(
 		renderer->logicalDevice,
@@ -7839,11 +7999,15 @@ static void VULKAN_SubmitAndSync(REFRESH_Renderer *driverData)
 }
 
 static void VULKAN_Submit(
-    REFRESH_Renderer *driverData
+    REFRESH_Renderer *driverData,
+	REFRESH_CommandBuffer **pCommandBuffers,
+	uint32_t commandBufferCount
 ) {
 	VulkanRenderer* renderer = (VulkanRenderer*)driverData;
 	VkSubmitInfo submitInfo;
 	VkResult vulkanResult, presentResult = VK_SUCCESS;
+	VulkanCommandBuffer *currentCommandBuffer;
+	VkCommandBuffer *commandBuffers;
 	uint32_t i;
 	uint8_t present;
 
@@ -7852,21 +8016,19 @@ static void VULKAN_Submit(
 
 	present = !renderer->headless && renderer->shouldPresent;
 
-	if (renderer->activeCommandBufferCount <= 1 && renderer->numActiveCommands == 0)
-	{
-		/* No commands recorded, bailing out */
-		return;
-	}
+	commandBuffers = SDL_stack_alloc(VkCommandBuffer, commandBufferCount);
 
-	if (renderer->currentCommandBuffer != NULL)
+	for (i = 0; i < commandBufferCount; i += 1)
 	{
-		VULKAN_INTERNAL_EndCommandBuffer(renderer);
+		currentCommandBuffer = (VulkanCommandBuffer*)pCommandBuffers[i];
+		VULKAN_INTERNAL_EndCommandBuffer(renderer, currentCommandBuffer);
+		commandBuffers[i] = currentCommandBuffer->commandBuffer;
 	}
 
 	submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
 	submitInfo.pNext = NULL;
-	submitInfo.commandBufferCount = renderer->activeCommandBufferCount;
-	submitInfo.pCommandBuffers = renderer->activeCommandBuffers;
+	submitInfo.commandBufferCount = commandBuffers;
+	submitInfo.pCommandBuffers = commandBufferCount;
 
 	if (present)
 	{
@@ -7905,24 +8067,14 @@ static void VULKAN_Submit(
 	/* Reset the previously submitted command buffers */
 	for (i = 0; i < renderer->submittedCommandBufferCount; i += 1)
 	{
-		vulkanResult = renderer->vkResetCommandBuffer(
-			renderer->submittedCommandBuffers[i],
-			VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT
-		);
-
-		if (vulkanResult != VK_SUCCESS)
+		if (!renderer->submittedCommandBuffers[i]->fixed)
 		{
-			LogVulkanResult("vkResetCommandBuffer", vulkanResult);
+			VULKAN_INTERNAL_ResetCommandBuffer(
+				renderer,
+				renderer->submittedCommandBuffers[i]
+			);
 		}
 	}
-
-	/* Mark the previously submitted command buffers as inactive */
-	for (i = 0; i < renderer->submittedCommandBufferCount; i += 1)
-	{
-		renderer->inactiveCommandBuffers[renderer->inactiveCommandBufferCount] = renderer->submittedCommandBuffers[i];
-		renderer->inactiveCommandBufferCount += 1;
-	}
-
 	renderer->submittedCommandBufferCount = 0;
 
 	/* Prepare the command buffer fence for submission */
@@ -7946,21 +8098,34 @@ static void VULKAN_Submit(
 		return;
 	}
 
-	/* Mark active command buffers as submitted */
-	for (i = 0; i < renderer->activeCommandBufferCount; i += 1)
+	if (renderer->submittedCommandBufferCount >= renderer->submittedCommandBufferCapacity)
 	{
-		renderer->submittedCommandBuffers[renderer->submittedCommandBufferCount] = renderer->activeCommandBuffers[i];
-		renderer->submittedCommandBufferCount += 1;
+		renderer->submittedCommandBufferCapacity *= 2;
+
+		renderer->submittedCommandBuffers = SDL_realloc(
+			renderer->submittedCommandBuffers,
+			sizeof(VulkanCommandBuffer*) * renderer->submittedCommandBufferCapacity
+		);
 	}
 
-	renderer->activeCommandBufferCount = 0;
+	/* Mark command buffers as submitted */
+	for (i = 0; i < commandBufferCount; i += 1)
+	{
+		((VulkanCommandBuffer*)pCommandBuffers[i])->submitted = 1;
+		renderer->submittedCommandBuffers[renderer->submittedCommandBufferCount] = pCommandBuffers[i];
+	}
+	renderer->submittedCommandBufferCount = commandBufferCount;
 
 	/* Reset UBOs */
 
+	SDL_LockMutex(renderer->uniformBufferLock);
 	renderer->vertexUBOOffset = UBO_BUFFER_SIZE * renderer->frameIndex;
 	renderer->vertexUBOBlockIncrement = 0;
 	renderer->fragmentUBOOffset = UBO_BUFFER_SIZE * renderer->frameIndex;
 	renderer->fragmentUBOBlockIncrement = 0;
+	renderer->computeUBOOffset = UBO_BUFFER_SIZE * renderer->frameIndex;
+	renderer->computeUBOBlockIncrement = 0;
+	SDL_UnlockMutex(renderer->uniformBufferLock);
 
 	/* Reset descriptor set data */
 	VULKAN_INTERNAL_ResetDescriptorSetData(renderer);
@@ -7992,7 +8157,7 @@ static void VULKAN_Submit(
 	renderer->swapChainImageAcquired = 0;
 	renderer->shouldPresent = 0;
 
-	VULKAN_INTERNAL_BeginCommandBuffer(renderer);
+	SDL_stack_free(commandBuffers);
 }
 
 /* Device instantiation */
@@ -8594,10 +8759,6 @@ static REFRESH_Device* VULKAN_CreateDevice(
 	VkFenceCreateInfo fenceInfo;
 	VkSemaphoreCreateInfo semaphoreInfo;
 
-	/* Variables: Create command pool and command buffer */
-	VkCommandPoolCreateInfo commandPoolCreateInfo;
-	VkCommandBufferAllocateInfo commandBufferAllocateInfo;
-
 	/* Variables: Descriptor set layouts */
 	VkDescriptorSetLayoutCreateInfo setLayoutCreateInfo;
 	VkDescriptorSetLayoutBinding vertexParamLayoutBinding;
@@ -8807,54 +8968,18 @@ static REFRESH_Device* VULKAN_CreateDevice(
 	/* Threading */
 
 	renderer->allocatorLock = SDL_CreateMutex();
-	renderer->commandLock = SDL_CreateMutex();
 	renderer->disposeLock = SDL_CreateMutex();
+	renderer->uniformBufferLock = SDL_CreateMutex();
+	renderer->descriptorSetLock = SDL_CreateMutex();
+	renderer->boundBufferLock = SDL_CreateMutex();
 
 	/*
-	 * Create command pool and buffers
+	 * Create submitted command buffer list
 	 */
 
-	commandPoolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
-	commandPoolCreateInfo.pNext = NULL;
-	commandPoolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
-	commandPoolCreateInfo.queueFamilyIndex = renderer->queueFamilyIndices.graphicsFamily;
-	vulkanResult = renderer->vkCreateCommandPool(
-		renderer->logicalDevice,
-		&commandPoolCreateInfo,
-		NULL,
-		&renderer->commandPool
-	);
-	if (vulkanResult != VK_SUCCESS)
-	{
-		LogVulkanResult("vkCreateCommandPool", vulkanResult);
-	}
-
-	renderer->allocatedCommandBufferCount = 4;
-	renderer->inactiveCommandBuffers = SDL_malloc(sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount);
-	renderer->activeCommandBuffers = SDL_malloc(sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount);
-	renderer->submittedCommandBuffers = SDL_malloc(sizeof(VkCommandBuffer) * renderer->allocatedCommandBufferCount);
-	renderer->inactiveCommandBufferCount = renderer->allocatedCommandBufferCount;
-	renderer->activeCommandBufferCount = 0;
+	renderer->submittedCommandBufferCapacity = 16;
 	renderer->submittedCommandBufferCount = 0;
-
-	commandBufferAllocateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-	commandBufferAllocateInfo.pNext = NULL;
-	commandBufferAllocateInfo.commandPool = renderer->commandPool;
-	commandBufferAllocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-	commandBufferAllocateInfo.commandBufferCount = renderer->allocatedCommandBufferCount;
-	vulkanResult = renderer->vkAllocateCommandBuffers(
-		renderer->logicalDevice,
-		&commandBufferAllocateInfo,
-		renderer->inactiveCommandBuffers
-	);
-	if (vulkanResult != VK_SUCCESS)
-	{
-		LogVulkanResult("vkAllocateCommandBuffers", vulkanResult);
-	}
-
-	renderer->currentCommandCount = 0;
-
-	VULKAN_INTERNAL_BeginCommandBuffer(renderer);
+	renderer->submittedCommandBuffers = SDL_malloc(sizeof(VulkanCommandBuffer*) * renderer->submittedCommandBufferCapacity);
 
 	/* Memory Allocator */
 
@@ -9215,24 +9340,6 @@ static REFRESH_Device* VULKAN_CreateDevice(
 		renderer->descriptorSetLayoutHashTable.buckets[i].capacity = 0;
 	}
 
-	/* Descriptor Pools */
-
-	renderer->descriptorPools = NULL;
-	renderer->descriptorPoolCount = 0;
-
-	/* State tracking */
-
-	renderer->currentGraphicsPipeline = NULL;
-	renderer->currentFramebuffer = NULL;
-
-	/* init bound compute buffer array */
-
-	for (i = 0; i < MAX_BUFFER_BINDINGS; i += 1)
-	{
-		renderer->boundComputeBuffers[i] = NULL;
-	}
-	renderer->boundComputeBufferCount = 0;
-
 	/* Deferred destroy storage */
 
 	renderer->colorTargetsToDestroyCapacity = 16;