435 lines
15 KiB
C++
435 lines
15 KiB
C++
/* -*- tab-width: 4; -*- */
|
|
/* vi: set sw=2 ts=4 expandtab: */
|
|
|
|
/*
|
|
* Copyright 2017-2020 Mark Callow.
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
/**
|
|
* @internal
|
|
* @class VulkanSwapchain
|
|
* @~English
|
|
*
|
|
* @brief Manage the swapchain for a Vulkan app.
|
|
*
|
|
* A swap chain is a collection of image buffers used for rendering
|
|
* The images can then be presented to the windowing system for display.
|
|
*
|
|
* @author Mark Callow, github.com/MarkCallow.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string>
|
|
#include <fstream>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <vector>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <fcntl.h>
|
|
#include <io.h>
|
|
#else
|
|
#endif
|
|
|
|
#include "VulkanSwapchain.h"
|
|
|
|
#include <SDL3/SDL.h>
|
|
#include <SDL3/SDL_vulkan.h>
|
|
#include "AppBaseSDL.h"
|
|
#include "unused.h"
|
|
|
|
#define ERROR_RETURN(msg) \
|
|
(void)SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, theApp->name(), \
|
|
msg, NULL); \
|
|
return false;
|
|
|
|
#define GET_INSTANCE_PROC_ADDR(inst, entrypoint) \
|
|
{ \
|
|
pfn##entrypoint = \
|
|
(PFN_vk##entrypoint)vkGetInstanceProcAddr(inst, "vk"#entrypoint); \
|
|
if (pfn##entrypoint == NULL) { \
|
|
ERROR_RETURN("vkGetInstanceProcAddr: unable to find vk"#entrypoint); \
|
|
} \
|
|
}
|
|
|
|
#define GET_DEVICE_PROC_ADDR(device, entrypoint) \
|
|
{ \
|
|
pfn##entrypoint = \
|
|
(PFN_vk##entrypoint)vkGetDeviceProcAddr(device, "vk"#entrypoint); \
|
|
if (pfn##entrypoint == NULL) { \
|
|
ERROR_RETURN("vkGetDeviceProcAddr: unable to find vk"#entrypoint); \
|
|
} \
|
|
}
|
|
|
|
// Creates an os specific surface
|
|
// Tries to find a graphics and a present queue
|
|
bool
|
|
VulkanSwapchain::initSurface(SDL_Window* window)
|
|
{
|
|
U_ASSERT_ONLY VkResult err;
|
|
|
|
if (!SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface)) {
|
|
std::string msg = "SDL_CreateVulkanSurface failed: ";
|
|
msg += SDL_GetError();
|
|
ERROR_RETURN(msg.c_str());
|
|
}
|
|
|
|
// Get available queue family properties
|
|
uint32_t queueCount;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount, NULL);
|
|
assert(queueCount >= 1);
|
|
|
|
std::vector<VkQueueFamilyProperties> queueProps(queueCount);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueCount,
|
|
queueProps.data());
|
|
|
|
// Iterate over the queues looking for ones which support presenting.
|
|
std::vector<VkBool32> supportsPresent(queueCount);
|
|
for (uint32_t i = 0; i < queueCount; i++) {
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface,
|
|
&supportsPresent[i]);
|
|
}
|
|
|
|
// Search for a graphics- and present-capable queue.
|
|
uint32_t graphicsQueueIndex = UINT32_MAX;
|
|
uint32_t presentQueueIndex = UINT32_MAX;
|
|
for (uint32_t i = 0; i < queueCount; i++) {
|
|
if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0)
|
|
{
|
|
if (graphicsQueueIndex == UINT32_MAX)
|
|
{
|
|
graphicsQueueIndex = i;
|
|
}
|
|
|
|
if (supportsPresent[i] == VK_TRUE)
|
|
{
|
|
graphicsQueueIndex = i;
|
|
presentQueueIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (presentQueueIndex == UINT32_MAX) {
|
|
// If there's no queue that supports both present and graphics
|
|
// try to find a separate present queue
|
|
for (uint32_t i = 0; i < queueCount; ++i)
|
|
{
|
|
if (supportsPresent[i] == VK_TRUE)
|
|
{
|
|
presentQueueIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Exit if either a graphics or a presenting queue hasn't been found
|
|
if (graphicsQueueIndex == UINT32_MAX
|
|
|| presentQueueIndex == UINT32_MAX)
|
|
{
|
|
ERROR_RETURN("Could not find a graphics or presenting queue!");
|
|
}
|
|
|
|
// TODO: Add support for separate graphics and presenting queue
|
|
if (graphicsQueueIndex != presentQueueIndex)
|
|
{
|
|
ERROR_RETURN("Separate graphics and present queues not yet supported!");
|
|
}
|
|
|
|
queueIndex = graphicsQueueIndex;
|
|
|
|
// Get list of supported surface formats
|
|
uint32_t formatCount;
|
|
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
|
|
&formatCount, NULL);
|
|
assert(err == VK_SUCCESS);
|
|
assert(formatCount > 0);
|
|
|
|
std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount);
|
|
err = vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface,
|
|
&formatCount,
|
|
surfaceFormats.data());
|
|
assert(err == VK_SUCCESS);
|
|
|
|
// If the surface format list only includes one entry with
|
|
// VK_FORMAT_UNDEFINED, there is no preferred format.
|
|
// Assume VK_FORMAT_B8G8R8A8_RGB.
|
|
// TODO: Consider passing in desired format from app.
|
|
if ((formatCount == 1) && (surfaceFormats[0].format == VK_FORMAT_UNDEFINED))
|
|
{
|
|
colorFormat = VK_FORMAT_B8G8R8A8_SRGB;
|
|
}
|
|
else
|
|
{
|
|
assert(formatCount >= 1);
|
|
uint32_t i;
|
|
for (i = 0; i < formatCount; i++) {
|
|
if (surfaceFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB) {
|
|
break;
|
|
}
|
|
}
|
|
if (i == formatCount) {
|
|
// Pick the first available, if no SRGB.
|
|
// FIXME probably should raise an error...
|
|
i = 0;
|
|
}
|
|
colorFormat = surfaceFormats[i].format;
|
|
}
|
|
colorSpace = surfaceFormats[0].colorSpace;
|
|
return true;
|
|
}
|
|
|
|
|
|
// Connect to the instance and device and get all required function pointers
|
|
bool
|
|
VulkanSwapchain::connectDevice(VkDevice targetDevice)
|
|
{
|
|
this->device = targetDevice;
|
|
#if USE_FUNCPTRS_FOR_KHR_EXTS
|
|
GET_DEVICE_PROC_ADDR(device, CreateSwapchainKHR);
|
|
GET_DEVICE_PROC_ADDR(device, DestroySwapchainKHR);
|
|
GET_DEVICE_PROC_ADDR(device, GetSwapchainImagesKHR);
|
|
GET_DEVICE_PROC_ADDR(device, AcquireNextImageKHR);
|
|
GET_DEVICE_PROC_ADDR(device, QueuePresentKHR);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
// Connect to the instance and device and get all required function pointers
|
|
bool
|
|
VulkanSwapchain::connectInstance(VkInstance targetInstance,
|
|
VkPhysicalDevice targetPhysicalDevice)
|
|
{
|
|
this->instance = targetInstance;
|
|
this->physicalDevice = targetPhysicalDevice;
|
|
#if USE_FUNCPTRS_FOR_KHR_EXTS
|
|
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceSupportKHR);
|
|
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR);
|
|
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfaceFormatsKHR);
|
|
GET_INSTANCE_PROC_ADDR(instance, GetPhysicalDeviceSurfacePresentModesKHR);
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
|
|
// Create the swap chain and get images with given width and height
|
|
void
|
|
VulkanSwapchain::create(uint32_t *width, uint32_t *height,
|
|
bool vsync)
|
|
{
|
|
U_ASSERT_ONLY VkResult err;
|
|
VkSwapchainKHR oldSwapchain = swapchain;
|
|
|
|
// Get physical device surface properties and formats
|
|
VkSurfaceCapabilitiesKHR surfCaps;
|
|
err = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface,
|
|
&surfCaps);
|
|
assert(err == VK_SUCCESS);
|
|
|
|
// Get available present modes
|
|
uint32_t presentModeCount;
|
|
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
|
|
&presentModeCount, NULL);
|
|
assert(err == VK_SUCCESS);
|
|
assert(presentModeCount > 0);
|
|
|
|
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
|
|
|
|
err = vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface,
|
|
&presentModeCount,
|
|
presentModes.data());
|
|
assert(err == VK_SUCCESS);
|
|
|
|
VkExtent2D swapchainExtent = {};
|
|
// width and height are either both 0xFFFFFFFF, or both not 0xFFFFFFFF.
|
|
if (surfCaps.currentExtent.width == UINT32_MAX)
|
|
{
|
|
// If the surface size is undefined, the size is set to
|
|
// the size of the images requested.
|
|
swapchainExtent.width = *width;
|
|
swapchainExtent.height = *height;
|
|
}
|
|
else
|
|
{
|
|
swapchainExtent = surfCaps.currentExtent;
|
|
*width = surfCaps.currentExtent.width;
|
|
*height = surfCaps.currentExtent.height;
|
|
}
|
|
|
|
|
|
// Select a present mode for the swapchain
|
|
|
|
// The VK_PRESENT_MODE_FIFO_KHR mode must always be present as per spec
|
|
// This mode waits for the vertical blank ("v-sync")
|
|
VkPresentModeKHR swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR;
|
|
|
|
// If v-sync is not requested, try to find a mailbox mode if present
|
|
// It's the lowest latency non-tearing present mode available
|
|
if (!vsync)
|
|
{
|
|
for (size_t i = 0; i < presentModeCount; i++)
|
|
{
|
|
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR)
|
|
{
|
|
swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR;
|
|
break;
|
|
}
|
|
if ((swapchainPresentMode != VK_PRESENT_MODE_MAILBOX_KHR)
|
|
&& (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR))
|
|
{
|
|
swapchainPresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Determine the number of images
|
|
uint32_t desiredNumberOfSwapchainImages = surfCaps.minImageCount + 1;
|
|
if ((surfCaps.maxImageCount > 0)
|
|
&& (desiredNumberOfSwapchainImages > surfCaps.maxImageCount))
|
|
{
|
|
desiredNumberOfSwapchainImages = surfCaps.maxImageCount;
|
|
}
|
|
|
|
VkSurfaceTransformFlagsKHR preTransform;
|
|
if (surfCaps.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) {
|
|
preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
|
} else {
|
|
preTransform = surfCaps.currentTransform;
|
|
}
|
|
|
|
VkSwapchainCreateInfoKHR swapchainCI = {};
|
|
swapchainCI.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
|
|
swapchainCI.pNext = NULL;
|
|
swapchainCI.surface = surface;
|
|
swapchainCI.minImageCount = desiredNumberOfSwapchainImages;
|
|
swapchainCI.imageFormat = colorFormat;
|
|
swapchainCI.imageColorSpace = colorSpace;
|
|
swapchainCI.imageExtent = { swapchainExtent.width, swapchainExtent.height };
|
|
swapchainCI.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
|
swapchainCI.preTransform = (VkSurfaceTransformFlagBitsKHR)preTransform;
|
|
swapchainCI.imageArrayLayers = 1;
|
|
swapchainCI.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
swapchainCI.queueFamilyIndexCount = 0;
|
|
swapchainCI.pQueueFamilyIndices = NULL;
|
|
swapchainCI.presentMode = swapchainPresentMode;
|
|
swapchainCI.oldSwapchain = oldSwapchain;
|
|
swapchainCI.clipped = true;
|
|
swapchainCI.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
|
|
|
err = vkCreateSwapchainKHR(device, &swapchainCI, nullptr, &swapchain);
|
|
assert(err == VK_SUCCESS);
|
|
|
|
// If an existing swap chain is re-created, destroy the old swap chain
|
|
// This also cleans up all the presentable images
|
|
if (oldSwapchain != VK_NULL_HANDLE)
|
|
{
|
|
for (uint32_t i = 0; i < imageCount; i++)
|
|
{
|
|
vkDestroyImageView(device, buffers[i].view, nullptr);
|
|
}
|
|
vkDestroySwapchainKHR(device, oldSwapchain, nullptr);
|
|
}
|
|
|
|
err = vkGetSwapchainImagesKHR(device, swapchain, &imageCount, NULL);
|
|
assert(err == VK_SUCCESS);
|
|
|
|
// Get the swap chain images
|
|
images.resize(imageCount);
|
|
err = vkGetSwapchainImagesKHR(device, swapchain,
|
|
&imageCount, images.data());
|
|
assert(err == VK_SUCCESS);
|
|
|
|
// Get the swap chain buffers containing the image and imageview
|
|
buffers.resize(imageCount);
|
|
for (uint32_t i = 0; i < imageCount; i++)
|
|
{
|
|
VkImageViewCreateInfo colorAttachmentView = {};
|
|
colorAttachmentView.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
|
colorAttachmentView.pNext = NULL;
|
|
colorAttachmentView.format = colorFormat;
|
|
colorAttachmentView.components = {
|
|
VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY,
|
|
VK_COMPONENT_SWIZZLE_IDENTITY
|
|
};
|
|
colorAttachmentView.subresourceRange.aspectMask
|
|
= VK_IMAGE_ASPECT_COLOR_BIT;
|
|
colorAttachmentView.subresourceRange.baseMipLevel = 0;
|
|
colorAttachmentView.subresourceRange.levelCount = 1;
|
|
colorAttachmentView.subresourceRange.baseArrayLayer = 0;
|
|
colorAttachmentView.subresourceRange.layerCount = 1;
|
|
colorAttachmentView.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
colorAttachmentView.flags = 0;
|
|
|
|
buffers[i].image = images[i];
|
|
|
|
colorAttachmentView.image = buffers[i].image;
|
|
|
|
err = vkCreateImageView(device, &colorAttachmentView, nullptr,
|
|
&buffers[i].view);
|
|
assert(err == VK_SUCCESS);
|
|
}
|
|
}
|
|
|
|
|
|
// Acquires the next image in the swap chain
|
|
VkResult
|
|
VulkanSwapchain::acquireNextImage(VkSemaphore presentCompleteSemaphore,
|
|
uint32_t *currentBuffer)
|
|
{
|
|
return vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
|
|
presentCompleteSemaphore,
|
|
(VkFence)nullptr,
|
|
currentBuffer);
|
|
}
|
|
|
|
|
|
// Present the current image to the queue
|
|
VkResult
|
|
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer)
|
|
{
|
|
VkPresentInfoKHR presentInfo = {};
|
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
presentInfo.pNext = NULL;
|
|
presentInfo.swapchainCount = 1;
|
|
presentInfo.pSwapchains = &swapchain;
|
|
presentInfo.pImageIndices = ¤tBuffer;
|
|
return vkQueuePresentKHR(queue, &presentInfo);
|
|
}
|
|
|
|
|
|
// Present the current image to the queue when semaphore signaled.
|
|
VkResult
|
|
VulkanSwapchain::queuePresent(VkQueue queue, uint32_t currentBuffer,
|
|
VkSemaphore waitSemaphore)
|
|
{
|
|
VkPresentInfoKHR presentInfo = {};
|
|
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
|
presentInfo.pNext = NULL;
|
|
presentInfo.swapchainCount = 1;
|
|
presentInfo.pSwapchains = &swapchain;
|
|
presentInfo.pImageIndices = ¤tBuffer;
|
|
if (waitSemaphore != VK_NULL_HANDLE)
|
|
{
|
|
presentInfo.pWaitSemaphores = &waitSemaphore;
|
|
presentInfo.waitSemaphoreCount = 1;
|
|
}
|
|
return vkQueuePresentKHR(queue, &presentInfo);
|
|
}
|
|
|
|
|
|
// Free all Vulkan resources used by the swap chain
|
|
void
|
|
VulkanSwapchain::cleanup()
|
|
{
|
|
for (uint32_t i = 0; i < imageCount; i++)
|
|
{
|
|
vkDestroyImageView(device, buffers[i].view, nullptr);
|
|
}
|
|
vkDestroySwapchainKHR(device, swapchain, nullptr);
|
|
vkDestroySurfaceKHR(instance, surface, nullptr);
|
|
}
|
|
|