Files
how-to-vulkan/ktx/tests/loadtests/appfwSDL/VulkanAppSDL/VulkanSwapchain.cpp
T
2026-06-14 19:09:18 +01:00

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 = &currentBuffer;
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 = &currentBuffer;
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);
}