Getting Started With Swapchains
You might have noticed that in a previous section, we made used this value: VK_KHR_SWAPCHAIN_EXTENSION_NAME
. So, what is a swap chain? It is essentially an array of images ready to be presented. One use is frame rate control. Using two buffers is called double buffering. The GPU renders completely to a single frame and then displays it. Once it has finished drawing the first frame, it begins drawing the second frame. This occurs even if we're rendering above the rate we're supposed to. Let's assume we're rendering faster than the physical display can display our images. We would then wait and flip the second buffer onto the screen. By flipping the image onto the screen during the vertical blanking interval, we can write our data while the display is blank. When it refreshes, our image appears on the screen. Other techniques such as triple buffering exist.
Initializing the Swapchain
For the next few sections, we're going to be focusing on writing the initSwapchain
method in our VulkanSwapchain
class. It is defined simply as:
void VulkanSwapchain::initSwapchain() {}
Getting Physical Device Surface Capabilities
We created a surface in the last chapter. Now we need to check the surface resolution so we can later inform our swapchain. To get the resolution of the surface, we'll have to ask it for its capabilities. We'll be using fpGetPhysicalDeviceSurfaceCapabilitiesKHR
which has the same definition as vkGetPhysicalDeviceSurfaceCapabilitiesKHR
.
Usage for vkGetPhysicalDeviceSurfaceCapabilitiesKHR
:
VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface,
VkSurfaceCapabilitiesKHR* pSurfaceCapabilities);
Documentation for `vkGetPhysicalDeviceSurfaceCapabilitiesKHR:
physicalDevice
is the physical device that will be associated with the swapchain to be created, as described forvkCreateSwapchainKHR
.surface
is the surface that will be associated with the swapchain.pSurfaceCapabilities
is a pointer to an instance of theVkSurfaceCapabilitiesKHR
structure that will be filled with information.
Usage for vkGetPhysicalDeviceSurfaceCapabilitiesKHR
:
Note that it takes a pointer to a VkSurfaceCapabilitiesKHR
object. We'll have to create that to pass it in. Let's do that and verify we were successful:
VkSurfaceCapabilitiesKHR caps = {};
VkResult result = fpGetPhysicalDeviceSurfaceCapabilitiesKHR(
physicalDevice, surface, &caps);
assert(result == VK_SUCCESS);
Vulkan Extents
Windows uses RECT
to define properties of rectangles. Similarly, Vulkan uses VkExtent2D
for the same purpose. The VkExtent2D
object will be used later, but for now, let's check something. The caps
variable has a currentExtent
field. This informs us about the surface's size. In the case that either the width
or height
are -1
, we'll need to set the extent size ourselves. Otherwise, we can just use caps.currentExtent
for the swapchain. Let's see how this looks in code:
VkExtent2D swapchainExtent = {};
if (caps.currentExtent.width == -1 || caps.currentExtent.height == -1) {
swapchainExtent.width = windowWidth;
swapchainExtent.height = windowHeight;
} else {
swapchainExtent = caps.currentExtent;
}
Get Physical Device Surface Present Modes
In Vulkan, there are multiple ways images can be presented. We'll talk about the options later in this section, but for now, we need to figure out which are supported. We can use fpGetPhysicalDeviceSurfacePresentModesKHR
to get the present modes as the name suggests. The definition is the same as vkGetPhysicalDeviceSurfacePresentModesKHR
.
Definition for vkGetPhysicalDeviceSurfacePresentModesKHR
:
VkResult vkGetPhysicalDeviceSurfacePresentModesKHR(
VkPhysicalDevice physicalDevice,
VkSurfaceKHR surface,
uint32_t* pPresentModeCount,
VkPresentModeKHR* pPresentModes);
Documentation for vkGetPhysicalDeviceSurfacePresentModesKHR
:
physicalDevice
is the physical device that will be associated with the swapchain to be created, as described forvkCreateSwapchainKHR
.surface
is the surface that will be associated with the swapchain.pPresentModeCount
is a pointer to an integer related to the number of format pairs available or queried, as described below.pPresentModes
is eitherNULL
or a pointer to an array ofVkPresentModeKHR
structures.
Usage for vkGetPhysicalDeviceSurfacePresentModesKHR
:
uint32_t presentModeCount = 0;
result = fpGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, surface, &presentModeCount, NULL);
Before we move on, let's verify success. We should check the result
and check make sure one or more present modes are available.
assert(result == VK_SUCCESS);
assert(presentModeCount >= 1);
Now let's go ahead and call the function again with our vector
:
std::vector<VkPresentModeKHR> presentModes(presentModeCount);
result = fpGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, surface, &presentModeCount, presentModes.data());
assert(result == VK_SUCCESS);
Now we should take a look at the available present modes. There are a few different types:
VK_PRESENT_MODE_IMMEDIATE_KHR
- Our engine will not ever wait for the vertical blanking interval. This may result in visible tearing if our frame misses the interval and is presented too late.VK_PRESENT_MODE_MAILBOX_KHR
- Our engine waits for the next vertical blanking interval to update the image. If we render another image, the image waiting to be displayed is overwritten.- You can think of the underlying data structure as an array of images of length
1
.
- You can think of the underlying data structure as an array of images of length
VK_PRESENT_MODE_FIFO_KHR
- Our engine waits for the next vertical blanking interval to update the image. If we've missed an interval, we wait until the next one. We will append already rendered images to the pending presentation queue. We follow the "first in first out" (FIFO) philosophy as the name suggests. There will not be any visible tearing.- You can think of the underlying data structure as something similar to a heap).
VK_PRESENT_MODE_FIFO_RELAXED_KHR
- Our engine waits for the next vertical blanking interval to update the image. If we've missed the interval, we do not wait. We will append already rendered images to the pending presentation queue. We present as soon as possible. We follow the "first in first out" (FIFO) philosophy as the name suggests. This may result in tearing.- You can think of the underlying data structure as something similar to a heap).
If you do not care about tearing, you might want VK_PRESENT_MODE_IMMEDIATE_KHR
. However, if you want a low-latency tear-less presentation mode, you would choose VK_PRESENT_MODE_MAILBOX_KHR
. Now let's look through our present modes and see if we can find VK_PRESENT_MODE_MAILBOX_KHR
. If we find it, stop looking. Otherwise, if we see our next preference VK_PRESENT_MODE_IMMEDIATE_KHR
, keep that. Finally, if we didn't find anything, we should continue with our default VK_PRESENT_MODE_FIFO_KHR
. The code would look like this:
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
for (uint32_t i = 0; i < presentModeCount; i++) {
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
break;
}
if (presentModes[i] == VK_PRESENT_MODE_IMMEDIATE_KHR)
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
Swapchain Create Information
Next up, we're going to prepare the information needed to create our VkSwapchainKHR
.
Definition for VkSwapchainCreateInfoKHR
:
typedef struct VkSwapchainCreateInfoKHR {
VkStructureType sType;
const void* pNext;
VkSwapchainCreateFlagsKHR flags;
VkSurfaceKHR surface;
uint32_t minImageCount;
VkFormat imageFormat;
VkColorSpaceKHR imageColorSpace;
VkExtent2D imageExtent;
uint32_t imageArrayLayers;
VkImageUsageFlags imageUsage;
VkSharingMode imageSharingMode;
uint32_t queueFamilyIndexCount;
const uint32_t* pQueueFamilyIndices;
VkSurfaceTransformFlagBitsKHR preTransform;
VkCompositeAlphaFlagBitsKHR compositeAlpha;
VkPresentModeKHR presentMode;
VkBool32 clipped;
VkSwapchainKHR oldSwapchain;
} VkSwapchainCreateInfoKHR
Documentation for VkSwapchainCreateInfoKHR
:
sType
is the type of this structure and must beVK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
.pNext
isNULL
or a pointer to an extension-specific structure.flags
is reserved for future use, and must be zero.surface
is the surface that the swapchain will present images to.minImageCount
is the minimum number of presentable images that the application needs. The platform will either create the swapchain with at least that many images, or will fail to create the swapchain.imageFormat
is aVkFormat
that is valid for swapchains on the specified surface.imageColorSpace
is aVkColorSpaceKHR
that is valid for swapchains on the specified surface.imageExtent
is the size (in pixels) of the swapchain. Behavior is platform-dependent when the image extent does not match the surface’scurrentExtent
as returned byvkGetPhysicalDeviceSurfaceCapabilitiesKHR
.imageArrayLayers
is the number of views in a multiview/stereo surface. For non-stereoscopic-3D applications, this value is1
.imageUsage
is a bitfield ofVkImageUsageFlagBits
, indicating how the application will use the swapchain’s presentable images.imageSharingMode
is the sharing mode used for the images of the swapchain.queueFamilyIndexCount
is the number of queue families having access to the images of the swapchain in caseimageSharingMode
isVK_SHARING_MODE_CONCURRENT
.pQueueFamilyIndices
is an array of queue family indices having access to the images of the swapchain in caseimageSharingMode
isVK_SHARING_MODE_CONCURRENT
.preTransform
is a bitfield ofVkSurfaceTransformFlagBitsKHR
, describing the transform, relative to the presentation engine’s natural orientation, applied to the image content prior to presentation. If it does not match thecurrentTransform
value returned byvkGetPhysicalDeviceSurfaceCapabilitiesKHR
, the presentation engine will transform the image content as part of the presentation operation.compositeAlpha
is a bitfield ofVkCompositeAlphaFlagBitsKHR
, indicating the alpha compositing mode to use when this surface is composited together with other surfaces on certain window systems.presentMode
is the presentation mode the swapchain will use. A swapchain’s present mode determines how incoming present requests will be processed and queued internally.clipped
indicates whether the Vulkan implementation is allowed to discard rendering operations that affect regions of the surface which aren’t visible.
Usage for VkSwapchainCreateInfoKHR
:
That's quite a definition. Before we can fill in the values, we'll need to figure out minImageCount
. First let's check verify our surface supports images:
assert(caps.maxImageCount >= 1);
Now let's say we want at least one image:
uint32_t imageCount = caps.minImageCount + 1;
If we asked for more images than the implementation supports, we'll have to settle for less:
if (imageCount > caps.maxImageCount)
imageCount = caps.maxImageCount;
Finally, we can put it all together:
VkSwapchainCreateInfoKHR swapchainCreateInfo = {};
swapchainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapchainCreateInfo.surface = surface;
swapchainCreateInfo.minImageCount = imageCount;
swapchainCreateInfo.imageFormat = colorFormat;
swapchainCreateInfo.imageColorSpace = colorSpace;
swapchainCreateInfo.imageExtent = { swapchainExtent.width, swapchainExtent.height };
swapchainCreateInfo.imageArrayLayers = 1;
swapchainCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
swapchainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
swapchainCreateInfo.queueFamilyIndexCount = 1;
swapchainCreateInfo.pQueueFamilyIndices = { 0 };
swapchainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
swapchainCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
swapchainCreateInfo.presentMode = presentMode;
Creating the Swapchain
This function will simply take in our swapchainCreateInfo
and create the swapchain with that configuration.
Definition for vkCreateSwapchainKHR
:
VkResult vkCreateSwapchainKHR(
VkDevice device,
const VkSwapchainCreateInfoKHR* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSwapchainKHR* pSwapchain);
Documentation for vkCreateSwapchainKHR
:
device
is the device to create the swapchain for.pCreateInfo
is a pointer to an instance of theVkSwapchainCreateInfoKHR
structure specifying the parameters of the created swapchain.pAllocator
is the allocator used for host memory allocated for the swapchain object when there is no more specific allocator available.pSwapchain
is a pointer to aVkSwapchainKHR
handle in which the created swapchain object will be returned.
Usage for vkCreateSwapchainKHR
:
result = fpCreateSwapchainKHR(device, &swapchainCreateInfo, NULL, &swapchain);
assert(result == VK_SUCCESS);
Getting Swapchain Images
We will need to get the available images from the swapchain. In a later section of this chapter, we'll actually get them ready for use, but right now, let's focus on this part. We'll be using a function pointer we got earlier called fpGetSwapchainImagesKHR
. The definition is the same as vkGetSwapchainImagesKHR
.
Definition for vkGetSwapchainImagesKHR
:
VkResult vkGetSwapchainImagesKHR(
VkDevice device,
VkSwapchainKHR swapchain,
uint32_t* pSwapchainImageCount,
VkImage* pSwapchainImages);
Documentation for vkGetSwapchainImagesKHR
:
device
is the device associated with swapchain.swapchain
is the swapchain to query.pSwapchainImageCount
is a pointer to an integer related to the number of format pairs available or queried, as described below.pSwapchainImages
is eitherNULL
or a pointer to an array ofVkSwapchainImageKHR
structures.
Usage for vkGetSwapchainImagesKHR
:
result = fpGetSwapchainImagesKHR(device, swapchain, &imageCount, NULL);
assert(result == VK_SUCCESS);
assert(imageCount > 0);
We'll need to make a new struct
to contain a few things. Let's look at it:
struct SwapChainBuffer {
VkImage image;
VkImageView view;
VkFramebuffer frameBuffer;
};
This will become more relevant later on when we get closer to rendering. For now, we'll create two variables in the VulkanSwapchain
class. These will be:
std::vector<VkImage> images;
std::vector<SwapChainBuffer> buffers;
Let's make sure we resize
these to fit the number of images we'll get back:
images.resize(imageCount);
buffers.resize(imageCount);
And of course, we'll call the function again and check for errors:
result =
fpGetSwapchainImagesKHR(device, swapchain, &imageCount, images.data());
assert(result == VK_SUCCESS);