From 92ae03a9c8783bc224e8ffd1bb675d648c33b460 Mon Sep 17 00:00:00 2001
From: Jordan Halase <jordan@halase.me>
Date: Thu, 7 Nov 2019 16:07:31 -0600
Subject: Begin adding swapchain recreation support

---
 main.c | 109 +++++++++++++++++++++++++++++++++++++++++------------------------
 1 file changed, 69 insertions(+), 40 deletions(-)

diff --git a/main.c b/main.c
index 2a7f242..e0debb0 100755
--- a/main.c
+++ b/main.c
@@ -60,6 +60,10 @@ PERFORMANCE OF THIS SOFTWARE.
 #define APP_THREAD_LOCAL	__declspec(thread)
 #endif
 
+#define MIN(a, b)	((a) <= (b) ? (a) : (b))
+#define MAX(a, b)	((a) >= (b) ? (a) : (b))
+#define CLAMP(x, l, h)	((x) <= (l) ? (l) : (x) >= (h) ? (h) : (x))
+
 struct Arguments {
 	bool validation;
 	bool verbose;
@@ -190,6 +194,7 @@ struct SwapchainVulkan {
 	VkImage			*images;
 	VkImageView		*imageViews;
 	VkFence			*fences;
+	VkCommandBuffer		*commandBuffers;
 };
 
 struct SemaphoreVulkan {
@@ -228,7 +233,6 @@ struct RenderVulkan {
 	VkQueue				graphicsQueue;
 	VkCommandPool			commandPool;
 	struct SwapchainVulkan		*swapchain;
-	VkCommandBuffer			*commandBuffers;
 	struct SyncVulkan		sync;
 };
 
@@ -720,6 +724,7 @@ int rvkCreateSurface(struct RenderVulkan *vk, const PuglRect frame)
 	puglSetFrame(vk->view, frame);
 	puglSetHandle(vk->view, vk);
 	puglSetBackend(vk->view, puglStubBackend());
+	puglSetViewHint(vk->view, PUGL_RESIZABLE, true);
 
 	PuglStatus status;
 	if ((status = puglCreateWindow(vk->view, "Vulkan Application Using Pugl"))) {
@@ -1318,15 +1323,16 @@ int rvkConfigureSurface(struct RenderVulkan *vk)
 
 static int rvkCreateRawSwapchain(struct RenderVulkan *vk, int width, int height)
 {
-	// TODO: Clamp
+	// FIXME: We actually need a new surfaceCapabilities for every new swapchain.
+	//width = CLAMP(width, vk->surfaceCapabilities.minImageExtent.width, vk->surfaceCapabilities.maxImageExtent.width);
+	//height = CLAMP(height, vk->surfaceCapabilities.minImageExtent.height, vk->surfaceCapabilities.maxImageExtent.height);
+
 	vk->swapchain->extent.width = width;
 	vk->swapchain->extent.height = height;
 
 	vk->swapchain->nImages = vk->surfaceCapabilities.minImageCount;
 	//vk->swapchain->nImages = vk->surfaceCapabilities.maxImageCount;
 
-	printf("Using %d swapchain images\n", vk->swapchain->nImages);
-
 	VkSwapchainCreateInfoKHR createInfo = { 0 };
 	createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
 	createInfo.surface = vk->surface;
@@ -1340,7 +1346,9 @@ static int rvkCreateRawSwapchain(struct RenderVulkan *vk, int width, int height)
 	createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
 	createInfo.presentMode = vk->presentMode;
 	createInfo.clipped = VK_TRUE;
-	createInfo.oldSwapchain = VK_NULL_HANDLE;	// TODO
+	if (vk->swapchain->pNext) {
+		createInfo.oldSwapchain = vk->swapchain->pNext->rawSwapchain;
+	}
 	VkResult result;
 	if ((result = vk->dev->vkCreateSwapchainKHR(vk->device, &createInfo, ALLOC_VK, &vk->swapchain->rawSwapchain))) {
 		rvkSetErrMsg(vk, "Could not create swapchain: %d", result);
@@ -1349,11 +1357,10 @@ static int rvkCreateRawSwapchain(struct RenderVulkan *vk, int width, int height)
 	return 0;
 }
 
-static void rvkDestroyRawSwapchain(struct RenderVulkan *vk)
+static void rvkDestroyRawSwapchain(struct RenderVulkan *vk, struct SwapchainVulkan *swapchain)
 {
-	if (vk->swapchain && vk->swapchain->rawSwapchain) {
-		vk->dev->vkDestroySwapchainKHR(vk->device, vk->swapchain->rawSwapchain, ALLOC_VK);
-		memset(&vk->swapchain, 0, sizeof(vk->swapchain));
+	if (swapchain && swapchain->rawSwapchain) {
+		vk->dev->vkDestroySwapchainKHR(vk->device, swapchain->rawSwapchain, ALLOC_VK);
 	}
 }
 
@@ -1425,19 +1432,18 @@ static VkResult allocateCommandBuffers(struct RenderVulkan *vk)
 	allocInfo.commandPool = vk->commandPool;
 	allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
 	allocInfo.commandBufferCount = vk->swapchain->nImages;
-	vk->commandBuffers = malloc(vk->swapchain->nImages * sizeof(vk->commandBuffers));
-	return vk->dev->vkAllocateCommandBuffers(vk->device, &allocInfo, vk->commandBuffers);
+	vk->swapchain->commandBuffers = malloc(vk->swapchain->nImages * sizeof(vk->swapchain->commandBuffers));
+	return vk->dev->vkAllocateCommandBuffers(vk->device, &allocInfo, vk->swapchain->commandBuffers);
 }
 
-static void freeCommandBuffers(struct RenderVulkan *vk)
+static void freeCommandBuffers(struct RenderVulkan *vk, struct SwapchainVulkan *swapchain)
 {
-	if (vk->commandBuffers) {
+	if (swapchain && swapchain->commandBuffers) {
 		vk->dev->vkFreeCommandBuffers(vk->device,
 				vk->commandPool,
-				vk->swapchain->nImages,
-				vk->commandBuffers);
-		free(vk->commandBuffers);
-		vk->commandBuffers = NULL;
+				swapchain->nImages,
+				swapchain->commandBuffers);
+		free(swapchain->commandBuffers);
 	}
 }
 
@@ -1483,24 +1489,24 @@ static int recordCommandBuffers(struct RenderVulkan *vk)
 		toPresentBarrier.image = vk->swapchain->images[i];
 		toPresentBarrier.subresourceRange = range;
 
-		vk->dev->vkBeginCommandBuffer(vk->commandBuffers[i], &beginInfo);
-		vk->dev->vkCmdPipelineBarrier(vk->commandBuffers[i],
+		vk->dev->vkBeginCommandBuffer(vk->swapchain->commandBuffers[i], &beginInfo);
+		vk->dev->vkCmdPipelineBarrier(vk->swapchain->commandBuffers[i],
 				VK_PIPELINE_STAGE_TRANSFER_BIT,
 				VK_PIPELINE_STAGE_TRANSFER_BIT,
 				0,
 				0, NULL,
 				0, NULL,
 				1, &toClearBarrier);
-		vk->dev->vkCmdClearColorImage(vk->commandBuffers[i], vk->swapchain->images[i],
+		vk->dev->vkCmdClearColorImage(vk->swapchain->commandBuffers[i], vk->swapchain->images[i],
 				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clearValue, 1, &range);
-		vk->dev->vkCmdPipelineBarrier(vk->commandBuffers[i],
+		vk->dev->vkCmdPipelineBarrier(vk->swapchain->commandBuffers[i],
 				VK_PIPELINE_STAGE_TRANSFER_BIT,
 				VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
 				0,
 				0, NULL,
 				0, NULL,
 				1, &toPresentBarrier);
-		vk->dev->vkEndCommandBuffer(vk->commandBuffers[i]);
+		vk->dev->vkEndCommandBuffer(vk->swapchain->commandBuffers[i]);
 	}
 
 	return 0;
@@ -1508,7 +1514,9 @@ static int recordCommandBuffers(struct RenderVulkan *vk)
 
 int rvkCreateSwapchain(struct RenderVulkan *vk, const int width, const int height)
 {
+	struct SwapchainVulkan *oldSwapchain = vk->swapchain;
 	vk->swapchain = calloc(1, sizeof(*vk->swapchain));
+	vk->swapchain->pNext = oldSwapchain;
 	if (rvkCreateRawSwapchain(vk, width, height)) {
 		return -1;
 	}
@@ -1537,24 +1545,33 @@ int rvkCreateSwapchain(struct RenderVulkan *vk, const int width, const int heigh
 	return 0;
 }
 
-void rvkDestroySwapchain(struct RenderVulkan *vk)
+// TODO: Move device into RenderDev to limit scope
+void rvkDestroySwapchain(struct RenderVulkan *vk, struct SwapchainVulkan *swapchain)
 {
-	if (vk->swapchain && vk->swapchain->fences) {
-		for (uint32_t i = 0; i < vk->swapchain->nImages; ++i) {
-			if (vk->swapchain->fences[i]) {
+	if (swapchain && swapchain->fences) {
+		for (uint32_t i = 0; i < swapchain->nImages; ++i) {
+			if (swapchain->fences[i]) {
 				vk->dev->vkDestroyFence(vk->device,
-						vk->swapchain->fences[i],
+						swapchain->fences[i],
 						ALLOC_VK);
 			}
 		}
-		free(vk->swapchain->fences);
-		vk->swapchain->fences = NULL;
-	}
-	freeCommandBuffers(vk);
-	destroySwapchainImageViews(vk->device, vk->dev, vk->swapchain);
-	rvkDestroyRawSwapchain(vk);
-	free(vk->swapchain);
-	vk->swapchain = NULL;
+		free(swapchain->fences);
+	}
+	freeCommandBuffers(vk, swapchain);
+	destroySwapchainImageViews(vk->device, vk->dev, swapchain);
+	rvkDestroyRawSwapchain(vk, swapchain);
+	free(swapchain);
+}
+
+// TODO: Destroy retired swapchains during rendering; not just all at once at destroy time.
+void rvkDestroyAllSwapchains(struct RenderVulkan *vk)
+{
+	struct SwapchainVulkan *swapchain, *pNext;
+	for (swapchain = vk->swapchain; swapchain; swapchain = pNext) {
+		pNext = swapchain->pNext;
+		rvkDestroySwapchain(vk, swapchain);
+	}
 }
 
 /** Creates any semaphores or fences for this application. */
@@ -1584,7 +1601,8 @@ static void rvkCloseDevice(struct RenderVulkan *vk)
 	if (vk->device) {
 		if (vk->dev->vkDeviceWaitIdle) vk->dev->vkDeviceWaitIdle(vk->device);
 		rvkDestroySyncObjects(vk);
-		rvkDestroySwapchain(vk);
+		//rvkDestroySwapchain(vk);
+		rvkDestroyAllSwapchains(vk);
 		if (vk->commandPool) {
 			vk->dev->vkDestroyCommandPool(vk->device, vk->commandPool, ALLOC_VK);
 			vk->commandPool = VK_NULL_HANDLE;
@@ -1682,18 +1700,29 @@ PuglStatus onDisplay(PuglView *view)
 {
 	struct RenderVulkan *vk = puglGetHandle(view);
 	uint32_t imageIndex;
+	PuglRect frame;
 	VkResult result;
 
 	// TODO: Swapchain recreation when resizing, minimizing, etc.
-	if ((result = vk->dev->vkAcquireNextImageKHR(
+	while ((result = vk->dev->vkAcquireNextImageKHR(
 			vk->device,
 			vk->swapchain->rawSwapchain,
 			UINT64_MAX,
 			vk->sync.semaphore.presentComplete,
 			VK_NULL_HANDLE,
 			&imageIndex))) {
-		rvkSetErrMsg(vk, "Could not acquire swapchain image: %d", result);
-		return PUGL_FAILURE;
+		switch (result) {
+			case VK_SUCCESS:
+				break;
+			case VK_SUBOPTIMAL_KHR:
+			case VK_ERROR_OUT_OF_DATE_KHR:
+				frame = puglGetFrame(vk->view);
+				CHECK_RVK(vk, rvkCreateSwapchain(vk, frame.width, frame.height));
+				continue;
+			default:
+				rvkSetErrMsg(vk, "Could not acquire swapchain image: %d", result);
+				rvkFatal(vk);
+		}
 	}
 
 	/* If running continuously and with an asynchronous presentation engine,
@@ -1716,7 +1745,7 @@ PuglStatus onDisplay(PuglView *view)
 	submitInfo.pWaitSemaphores = &vk->sync.semaphore.presentComplete;
 	submitInfo.pWaitDstStageMask = &waitStage;
 	submitInfo.commandBufferCount = 1;
-	submitInfo.pCommandBuffers = &vk->commandBuffers[imageIndex];
+	submitInfo.pCommandBuffers = &vk->swapchain->commandBuffers[imageIndex];
 	submitInfo.signalSemaphoreCount = 1;
 	submitInfo.pSignalSemaphores = &vk->sync.semaphore.renderFinished;
 
-- 
cgit v1.2.3