diff --git a/src/rendering/vulkan/system/vk_framebuffer.cpp b/src/rendering/vulkan/system/vk_framebuffer.cpp index 529b8d7853..fbff8fa255 100644 --- a/src/rendering/vulkan/system/vk_framebuffer.cpp +++ b/src/rendering/vulkan/system/vk_framebuffer.cpp @@ -157,20 +157,7 @@ void VulkanFrameBuffer::Update() Flush3D.Clock(); - int newWidth = GetClientWidth(); - int newHeight = GetClientHeight(); - if (lastSwapWidth != newWidth || lastSwapHeight != newHeight) - { - swapChain.reset(); - swapChain = std::make_unique(device); - - lastSwapWidth = newWidth; - lastSwapHeight = newHeight; - } - - VkResult result = vkAcquireNextImageKHR(device->device, swapChain->swapChain, std::numeric_limits::max(), mSwapChainImageAvailableSemaphore->semaphore, VK_NULL_HANDLE, &presentImageIndex); - if (result != VK_SUCCESS) - throw std::runtime_error("Failed to acquire next image!"); + presentImageIndex = swapChain->AcquireImage(GetClientWidth(), GetClientHeight(), mSwapChainImageAvailableSemaphore.get()); GetPostprocess()->SetActiveRenderTarget(); @@ -180,7 +167,8 @@ void VulkanFrameBuffer::Update() mRenderState->EndRenderPass(); mRenderState->EndFrame(); - mPostprocess->DrawPresentTexture(mOutputLetterbox, true, true); + if (presentImageIndex != 0xffffffff) + mPostprocess->DrawPresentTexture(mOutputLetterbox, true, true); SubmitCommands(true); @@ -191,17 +179,8 @@ void VulkanFrameBuffer::Update() Finish.Reset(); Finish.Clock(); - VkSemaphore waitSemaphores[] = { mRenderFinishedSemaphore->semaphore }; - VkSwapchainKHR swapChains[] = { swapChain->swapChain }; - VkPresentInfoKHR presentInfo = {}; - presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; - presentInfo.waitSemaphoreCount = 1; - presentInfo.pWaitSemaphores = waitSemaphores; - presentInfo.swapchainCount = 1; - presentInfo.pSwapchains = swapChains; - presentInfo.pImageIndices = &presentImageIndex; - presentInfo.pResults = nullptr; - vkQueuePresentKHR(device->presentQueue, &presentInfo); + if (presentImageIndex != 0xffffffff) + swapChain->QueuePresent(presentImageIndex, mRenderFinishedSemaphore.get()); vkWaitForFences(device->device, 1, &mRenderFinishedFence->fence, VK_TRUE, std::numeric_limits::max()); vkResetFences(device->device, 1, &mRenderFinishedFence->fence); diff --git a/src/rendering/vulkan/system/vk_framebuffer.h b/src/rendering/vulkan/system/vk_framebuffer.h index e27a16c827..5a64d4b683 100644 --- a/src/rendering/vulkan/system/vk_framebuffer.h +++ b/src/rendering/vulkan/system/vk_framebuffer.h @@ -23,7 +23,7 @@ class VulkanFrameBuffer : public SystemBaseFrameBuffer public: VulkanDevice *device; std::unique_ptr swapChain; - uint32_t presentImageIndex = 0; + uint32_t presentImageIndex = 0xffffffff; VulkanCommandBuffer *GetTransferCommands(); VulkanCommandBuffer *GetDrawCommands(); @@ -124,9 +124,6 @@ private: std::unique_ptr mRenderFinishedFence; VkRenderBuffers *mActiveRenderBuffers = nullptr; - - int lastSwapWidth = 0; - int lastSwapHeight = 0; }; inline VulkanFrameBuffer *GetVulkanFrameBuffer() { return static_cast(screen); } diff --git a/src/rendering/vulkan/system/vk_swapchain.cpp b/src/rendering/vulkan/system/vk_swapchain.cpp index 4309e946ab..389571c4ba 100644 --- a/src/rendering/vulkan/system/vk_swapchain.cpp +++ b/src/rendering/vulkan/system/vk_swapchain.cpp @@ -1,5 +1,6 @@ #include "vk_swapchain.h" +#include "vk_objects.h" #include "c_cvars.h" #include "version.h" @@ -18,10 +19,10 @@ VulkanSwapChain::VulkanSwapChain(VulkanDevice *device) : vsync(vid_vsync), devic { SelectFormat(); SelectPresentMode(); - CreateSwapChain(); + if (!CreateSwapChain()) + throw std::runtime_error("Could not create vulkan swapchain"); GetImages(); CreateViews(); - // SetHdrMetadata(); // This isn't required it seems } catch (...) { @@ -35,7 +36,101 @@ VulkanSwapChain::~VulkanSwapChain() ReleaseResources(); } -void VulkanSwapChain::CreateSwapChain() +uint32_t VulkanSwapChain::AcquireImage(int width, int height, VulkanSemaphore *semaphore, VulkanFence *fence) +{ + if (lastSwapWidth != width || lastSwapHeight != height || !swapChain) + { + Recreate(); + lastSwapWidth = width; + lastSwapHeight = height; + } + + uint32_t imageIndex; + while (true) + { + if (!swapChain) + { + imageIndex = 0xffffffff; + break; + } + + VkResult result = vkAcquireNextImageKHR(device->device, swapChain, std::numeric_limits::max(), semaphore ? semaphore->semaphore : VK_NULL_HANDLE, fence ? fence->fence : VK_NULL_HANDLE, &imageIndex); + if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) + { + Recreate(); + } + else if (result == VK_SUCCESS) + { + break; + } + else if (result == VK_TIMEOUT || result == VK_NOT_READY) + { + imageIndex = 0xffffffff; + break; + } + else + { + throw std::runtime_error("Failed to acquire next image!"); + } + } + return imageIndex; +} + +void VulkanSwapChain::QueuePresent(uint32_t imageIndex, VulkanSemaphore *semaphore) +{ + VkPresentInfoKHR presentInfo = {}; + presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; + presentInfo.waitSemaphoreCount = semaphore ? 1 : 0; + presentInfo.pWaitSemaphores = semaphore ? &semaphore->semaphore : VK_NULL_HANDLE; + presentInfo.swapchainCount = 1; + presentInfo.pSwapchains = &swapChain; + presentInfo.pImageIndices = &imageIndex; + presentInfo.pResults = nullptr; + VkResult result = vkQueuePresentKHR(device->presentQueue, &presentInfo); + if (result == VK_SUBOPTIMAL_KHR || result == VK_ERROR_OUT_OF_DATE_KHR) + { + lastSwapWidth = 0; + lastSwapHeight = 0; + } + else if (result == VK_ERROR_OUT_OF_HOST_MEMORY || result == VK_ERROR_OUT_OF_DEVICE_MEMORY) + { + // The spec says we can recover from this. + // However, if we are out of memory it is better to crash now than in some other weird place further away from the source of the problem. + + throw std::runtime_error("vkQueuePresentKHR failed: out of memory"); + } + else if (result == VK_ERROR_DEVICE_LOST) + { + throw std::runtime_error("vkQueuePresentKHR failed: device lost"); + } + else if (result == VK_ERROR_SURFACE_LOST_KHR) + { + throw std::runtime_error("vkQueuePresentKHR failed: surface lost"); + } + else if (result != VK_SUCCESS) + { + throw std::runtime_error("vkQueuePresentKHR failed"); + } +} + +void VulkanSwapChain::Recreate() +{ + ReleaseViews(); + swapChainImages.clear(); + + VkSwapchainKHR oldSwapChain = swapChain; + CreateSwapChain(oldSwapChain); + if (oldSwapChain) + vkDestroySwapchainKHR(device->device, oldSwapChain, nullptr); + + if (swapChain) + { + GetImages(); + CreateViews(); + } +} + +bool VulkanSwapChain::CreateSwapChain(VkSwapchainKHR oldSwapChain) { int width, height; I_GetVulkanDrawableSize(&width, &height); @@ -45,6 +140,11 @@ void VulkanSwapChain::CreateSwapChain() actualExtent = { static_cast(width), static_cast(height) }; actualExtent.width = std::max(surfaceCapabilities.minImageExtent.width, std::min(surfaceCapabilities.maxImageExtent.width, actualExtent.width)); actualExtent.height = std::max(surfaceCapabilities.minImageExtent.height, std::min(surfaceCapabilities.maxImageExtent.height, actualExtent.height)); + if (actualExtent.width == 0 || actualExtent.height == 0) + { + swapChain = VK_NULL_HANDLE; + return false; + } uint32_t imageCount = surfaceCapabilities.minImageCount + 1; if (surfaceCapabilities.maxImageCount > 0 && imageCount > surfaceCapabilities.maxImageCount) @@ -80,11 +180,16 @@ void VulkanSwapChain::CreateSwapChain() swapChainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; // If alpha channel is passed on to the DWM or not swapChainCreateInfo.presentMode = swapChainPresentMode; swapChainCreateInfo.clipped = VK_TRUE; - swapChainCreateInfo.oldSwapchain = VK_NULL_HANDLE; + swapChainCreateInfo.oldSwapchain = oldSwapChain; VkResult result = vkCreateSwapchainKHR(device->device, &swapChainCreateInfo, nullptr, &swapChain); if (result != VK_SUCCESS) - throw std::runtime_error("Could not create vulkan swapchain"); + { + swapChain = VK_NULL_HANDLE; + return false; + } + + return true; } void VulkanSwapChain::CreateViews() @@ -218,14 +323,18 @@ void VulkanSwapChain::GetImages() throw std::runtime_error("vkGetSwapchainImagesKHR failed (2)"); } -void VulkanSwapChain::ReleaseResources() +void VulkanSwapChain::ReleaseViews() { for (auto &view : swapChainImageViews) { vkDestroyImageView(device->device, view, nullptr); } swapChainImageViews.clear(); +} +void VulkanSwapChain::ReleaseResources() +{ + ReleaseViews(); if (swapChain) vkDestroySwapchainKHR(device->device, swapChain, nullptr); } diff --git a/src/rendering/vulkan/system/vk_swapchain.h b/src/rendering/vulkan/system/vk_swapchain.h index ce515c13e3..0aed7fcf20 100644 --- a/src/rendering/vulkan/system/vk_swapchain.h +++ b/src/rendering/vulkan/system/vk_swapchain.h @@ -2,12 +2,20 @@ #include "vk_device.h" +class VulkanSemaphore; +class VulkanFence; + class VulkanSwapChain { public: VulkanSwapChain(VulkanDevice *device); ~VulkanSwapChain(); + uint32_t AcquireImage(int width, int height, VulkanSemaphore *semaphore = nullptr, VulkanFence *fence = nullptr); + void QueuePresent(uint32_t imageIndex, VulkanSemaphore *semaphore = nullptr); + + void Recreate(); + bool vsync; VkSwapchainKHR swapChain = VK_NULL_HANDLE; VkSurfaceFormatKHR swapChainFormat; @@ -21,11 +29,12 @@ public: private: void SelectFormat(); void SelectPresentMode(); - void CreateSwapChain(); + bool CreateSwapChain(VkSwapchainKHR oldSwapChain = VK_NULL_HANDLE); void CreateViews(); void SetHdrMetadata(); void GetImages(); void ReleaseResources(); + void ReleaseViews(); VkSurfaceCapabilitiesKHR GetSurfaceCapabilities(); std::vector GetSurfaceFormats(); @@ -33,6 +42,9 @@ private: VulkanDevice *device = nullptr; + int lastSwapWidth = 0; + int lastSwapHeight = 0; + VulkanSwapChain(const VulkanSwapChain &) = delete; VulkanSwapChain &operator=(const VulkanSwapChain &) = delete; };