Skip to content

Commit

Permalink
vulkan: fix a crash on minimization.
Browse files Browse the repository at this point in the history
Use a fake backbuffer when the system reports 0 for swap chain dimensions.
  • Loading branch information
slime73 committed Apr 7, 2024
1 parent e11ab50 commit 8140817
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 77 deletions.
232 changes: 155 additions & 77 deletions src/modules/graphics/vulkan/Graphics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,15 +340,22 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
VmaAllocation screenshotAllocation = VK_NULL_HANDLE;
VmaAllocationInfo screenshotAllocationInfo = {};

VkImage backbufferImage = fakeBackbuffer != nullptr ? (VkImage)fakeBackbuffer->getHandle() : swapChainImages.at(imageIndex);

if (submitMode == SUBMIT_PRESENT)
{
if (pendingScreenshotCallbacks.empty())
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
swapChainPixelFormat,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
{
if (fakeBackbuffer == nullptr)
{
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
backbufferImage,
swapChainPixelFormat,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
}
}
else
{
VkBufferCreateInfo bufferInfo{};
Expand All @@ -372,10 +379,9 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback
if (result != VK_SUCCESS)
throw love::Exception("failed to create screenshot readback buffer");

// TODO: swap chain images aren't guaranteed to support TRANSFER_SRC_BIT usage flags.
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
backbufferImage,
swapChainPixelFormat,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
Expand All @@ -391,25 +397,28 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback

vkCmdCopyImageToBuffer(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
backbufferImage,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
screenshotBuffer,
1, &region);

Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages.at(imageIndex),
backbufferImage,
swapChainPixelFormat,
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
fakeBackbuffer == nullptr ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}
}

endRecordingGraphicsCommands();

if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
if (!imagesInFlight.empty())
{
if (imagesInFlight[imageIndex] != VK_NULL_HANDLE)
vkWaitForFences(device, 1, &imagesInFlight.at(imageIndex), VK_TRUE, UINT64_MAX);
imagesInFlight[imageIndex] = inFlightFences[currentFrame];
}

std::array<VkCommandBuffer, 1> submitCommandbuffers = { commandBuffers.at(currentFrame) };

Expand All @@ -436,8 +445,11 @@ void Graphics::submitGpuCommands(SubmitMode submitMode, void *screenshotCallback

if (submitMode == SUBMIT_PRESENT)
{
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (!swapChainImages.empty())
{
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
}

vkResetFences(device, 1, &inFlightFences[currentFrame]);
fence = inFlightFences[currentFrame];
Expand Down Expand Up @@ -535,15 +547,32 @@ void Graphics::present(void *screenshotCallbackdata)

submitGpuCommands(SUBMIT_PRESENT, screenshotCallbackdata);

VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &renderFinishedSemaphores.at(currentFrame);
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &imageIndex;
VkResult result = VK_SUCCESS;

VkResult result = vkQueuePresentKHR(presentQueue, &presentInfo);
if (!swapChainImages.empty())
{
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &renderFinishedSemaphores.at(currentFrame);
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = &swapChain;
presentInfo.pImageIndices = &imageIndex;

result = vkQueuePresentKHR(presentQueue, &presentInfo);
}
else
{
// Presenting without a real swap chain can happen if the window is minimized.
// Check every frame to see if a proper one can be created, in this situation.
VkSurfaceCapabilitiesKHR capabilities = {};
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities) == VK_SUCCESS)
{
VkExtent2D extent = chooseSwapExtent(capabilities);
if (extent.width > 0 && extent.height > 0)
swapChainRecreationRequested = true;
}
}

if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || swapChainRecreationRequested)
{
Expand Down Expand Up @@ -1263,21 +1292,28 @@ void Graphics::beginFrame()
frameCounter = 0;
}

while (true)
if (swapChain != VK_NULL_HANDLE)
{
VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
while (true)
{
recreateSwapChain();
continue;
VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR)
{
recreateSwapChain();
continue;
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
throw love::Exception("failed to acquire swap chain image");

break;
}
else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR)
throw love::Exception("failed to acquire swap chain image");

break;
imageRequested = true;
}
else
{
imageRequested = false;
}

imageRequested = true;

for (auto &readbackCallback : readbackCallbacks.at(currentFrame))
readbackCallback();
Expand All @@ -1289,12 +1325,15 @@ void Graphics::beginFrame()

startRecordingGraphicsCommands();

Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages[imageIndex],
swapChainPixelFormat,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
if (!swapChainImages.empty())
{
Vulkan::cmdTransitionImageLayout(
commandBuffers.at(currentFrame),
swapChainImages[imageIndex],
swapChainPixelFormat,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
}

if (transitionColorDepthLayouts)
{
Expand Down Expand Up @@ -1338,6 +1377,20 @@ void Graphics::startRecordingGraphicsCommands()

initDynamicState();

// This must be done after vkBeginCommandBuffer (since newTexture needs an
// active command buffer for layout transitions), and before setDefaultRenderPass
// (since that tries to use fakeBackbuffer).
if (swapChainImages.empty() && fakeBackbuffer == nullptr)
{
Texture::Settings settings;
settings.format = swapChainPixelFormat;
settings.width = swapChainExtent.width;
settings.height = swapChainExtent.height;
settings.renderTarget = true;
settings.readable.set(false);
fakeBackbuffer.set((Texture*)newTexture(settings, nullptr), Acquire::NORETAIN);
}

setDefaultRenderPass();

if (defaultVertexBuffer)
Expand Down Expand Up @@ -1824,49 +1877,66 @@ void Graphics::createSwapChain()
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);

uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount;
if (extent.width > 0 && extent.height > 0)
{
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount)
imageCount = swapChainSupport.capabilities.maxImageCount;

VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;
VkSwapchainCreateInfoKHR createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = surface;

createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
createInfo.minImageCount = imageCount;
createInfo.imageFormat = surfaceFormat.format;
createInfo.imageColorSpace = surfaceFormat.colorSpace;
createInfo.imageExtent = extent;
createInfo.imageArrayLayers = 1;
createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;

QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value, indices.presentFamily.value };
QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
uint32_t queueFamilyIndices[] = { indices.graphicsFamily.value, indices.presentFamily.value };

if (indices.graphicsFamily.value != indices.presentFamily.value)
{
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
if (indices.graphicsFamily.value != indices.presentFamily.value)
{
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
else
{
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}

createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
createInfo.compositeAlpha = chooseCompositeAlpha(swapChainSupport.capabilities);
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS)
throw love::Exception("failed to create swap chain");

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
}
else
{
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0;
createInfo.pQueueFamilyIndices = nullptr;
}

createInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
createInfo.compositeAlpha = chooseCompositeAlpha(swapChainSupport.capabilities);
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
// Use a fake backbuffer. Creation is deferred until startRecordingGraphicsCommands
// because newTexture needs an active command buffer to do its initial
// layout transitions.
swapChainImages.clear();
extent.width = std::max(1, pixelWidth);
extent.height = std::max(1, pixelHeight);

if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS)
throw love::Exception("failed to create swap chain");

vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
swapChainImages.resize(imageCount);
vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());
if (isGammaCorrect())
surfaceFormat.format = VK_FORMAT_R8G8B8A8_SRGB;
else
surfaceFormat.format = VK_FORMAT_R8G8B8A8_UNORM;
}

swapChainImageFormat = surfaceFormat.format;
swapChainExtent = extent;
Expand Down Expand Up @@ -2374,13 +2444,19 @@ void Graphics::setDefaultRenderPass()

if (msaaSamples & VK_SAMPLE_COUNT_1_BIT)
{
framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
if (!swapChainImageViews.empty())
framebufferConfiguration.colorViews.push_back(swapChainImageViews.at(imageIndex));
else
framebufferConfiguration.colorViews.push_back(fakeBackbuffer->getRenderTargetView(0, 0));
framebufferConfiguration.staticData.resolveView = VK_NULL_HANDLE;
}
else
{
framebufferConfiguration.colorViews.push_back(colorImageView);
framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
if (!swapChainImageViews.empty())
framebufferConfiguration.staticData.resolveView = swapChainImageViews.at(imageIndex);
else
framebufferConfiguration.staticData.resolveView = fakeBackbuffer->getRenderTargetView(0, 0);
}

renderPassState.renderPassConfiguration = std::move(renderPassConfiguration);
Expand Down Expand Up @@ -3074,6 +3150,8 @@ void Graphics::cleanupSwapChain()
vkDestroyImageView(device, swapChainImageView, nullptr);
swapChainImageViews.clear();
vkDestroySwapchainKHR(device, swapChain, nullptr);
swapChainImages.clear();
fakeBackbuffer.set(nullptr);

swapChain = VK_NULL_HANDLE;
}
Expand Down
1 change: 1 addition & 0 deletions src/modules/graphics/vulkan/Graphics.h
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,7 @@ class Graphics final : public love::graphics::Graphics
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapChain = VK_NULL_HANDLE;
std::vector<VkImage> swapChainImages;
StrongRef<Texture> fakeBackbuffer;
VkFormat swapChainImageFormat = VK_FORMAT_UNDEFINED;
PixelFormat swapChainPixelFormat = PIXELFORMAT_UNKNOWN;
VkFormat depthStencilFormat = VK_FORMAT_UNDEFINED;
Expand Down

0 comments on commit 8140817

Please sign in to comment.