Swapchains are a mechanism by which the rendering of the drawing primitive is shown on a platform-specific presentation window/surface. A swapchain may contain single or multiple drawing images. These drawing images are called color images. A color image is simply an array of pixel information that resides in a special layout in the memory. The number of draw images in a swapchain is very specific to the implementation. When double images are used, it's called double buffering, and when three surfaces are used, triple buffering.
Among these images, when one image completes the drawing process in the background, it is swapped to the presentation window. In order to fully utilize the GPU, a different image is then treated as a background buffer for the drawing process. This process is repeated back and forth and the swapping of the images takes place continuously. Making use of multiple images improves the frame rate output as the GPU is always constantly busy with the processing part with one of the image, reducing the overall idle time.
The swapping or flipping of the drawing image is dependent upon the presentation mode; this may be updated during the Vertical Blanking Interval (VBI) or as soon as the drawing is made available. This means that when the monitor is refreshed, the background image is swapped with the front image displaying the new image. A swapchain is available in the form of an API extension, which needs to be enabled with VK_KHR_SWAPCHAIN_EXTENSION_NAME
. Refer to the Querying swapchain extensions section for more information.
The following diagram will provide you with an overview of swapchain implementation from start to finish. This will cover each and every part of the flow in a very brief fashion, allowing you to remain connected throughout the implementation, which will be covered in detail in upcoming sections:
Let's get into the flow and quickly take an overview of each of the specifics:
VkImage
, and are used by the application.VkImage
), follow the next steps to allocate the memory, create image layout and finally producing the image view object (VkImageView
). VkImageView
). The image cannot be directly used in the application; they must be in an image view form.The created color image view will be submitted to the graphics queue to let the presentation engine render it on the display window. By the end of this chapter, you will be able to display the blank window since nothing is rendered on the swapchain color images so far.
This section will act as a brief introduction to the upcoming section where we will implement the swapchain. This will help us understand the role of these classes as we inch towards a detailed implementation.
In this chapter, we are introducing three new user-defined custom classes: VulkanRenderer
, VulkanSwapChain
, and VulkanPipeline
. These classes are not associated with any official Vulkan specification API or data structures. These are user-defined and will help us manage the application in an organized way.
The following block diagram shows these module classes and their hierarchical relationship. Besides this, this pictorial representation will also inform you about the responsibility of each and every module. As we proceed to the next chapter, we will introduce a few more classes:
The VulkanRender
class is defined in VulkanRenderer.h/.cpp
. It manages a unique presentation window and its dependent resources, such as devices, swapchains, pipelines, and so on, in an application. An application can have multiple rendering windows, as described in the following diagram; each renderer takes care of an individual presentation window with all its corresponding resources.
However, since we are at the beginner level, our samples assume only one output presentation window. Therefore, VulkanApplication
contains a single VulkanRenderer
class object:
The following is the declaration of the VulkanRenderer
header file. This class creates the presentation layer's empty window (createPresentationWindow()
), which will be later filled with the color images of the swapchain. The empty window creation process is very platform-specific; this sample example is only implemented for the Windows platform:
In addition, the VulkanRenderer
class manages the initialization (initialize()
) and renders the presentation window, (render()
). This class also manages the command pool for various command buffers:
/************* VulkanRenderer.h *************/ class VulkanRenderer{ // Many line skipped in this header, please refer to // corresponding source with this chapter. public: void initialize(); //Simple life cycle // Create an empty window void createPresentationWindow(int& w, int& height); // Windows procedure method for handling events. static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); void createCommandPool(); // Create command pool void createSwapChain(); // Create swapchain Color image void createDepthImage(); // Create Depth image public: HINSTANCE connection; // hInstance - Windows Instance char name[80]; // name - App name appearing on the window HWND window; // hWnd - the window handle // Data structure used for depth image struct{ VkFormat format; VkImage image; VkDeviceMemory mem; VkImageView view; }Depth; VkCommandBuffer cmdDepthImage; // Depth image command buffer VkCommandPool cmdPool; // Command pool int width, height; // Window Width and Height private: // Class managers VulkanSwapChain* swapChainObj; VulkanApplication* application; // The device object associated with this Presentation layer. VulkanDevice* deviceObj; }; VulkanRenderer::VulkanRenderer(VulkanApplication * app, VulkanDevice* deviceObject){ // Many lines skipped application = app; deviceObj = deviceObject; swapChainObj = new VulkanSwapChain(this); }
This section will implement the windowing system that will be used to present the swapchain color images on the display window. This example primarily focuses on Windows and uses the CreateWindowEx()
API to create an overlapped pop-up or child window with an extended window style. Creating windows is common, and discussing this topic is beyond the scope of this book.
Refer to the online MSDN documentation for more information on the related APIs (https://msdn.microsoft.com/en-us/library/windows/desktop/ms632680(v=vs.85).aspx):
void VulkanRenderer::createPresentationWindow(const int& windowWidth, const int& windowHeight){ width = windowWidth; height = windowHeight; WNDCLASSEX winInfo; // Initialize the window class structure: memset(&winInfo, 0, sizeof(WNDCLASSEX)); winInfo.cbSize = sizeof(WNDCLASSEX); winInfo.lpfnWndProc = WndProc; winInfo.hInstance = connection; winInfo.lpszClassName = name; // Register window class if (!RegisterClassEx(&winInfo)) { exit(1); } // Create window with the registered class RECT wr = { 0, 0, width, height }; AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE); window = CreateWindowEx(0, name, name, WS_OVERLAPPEDWINDOW |WS_VISIBLE | WS_SYSMENU, 100, 100, wr.right - wr.left, wr.bottom - wr.top, NULL, NULL, connection, NULL); SetWindowLongPtr(window,GWLP_USERDATA,&application); }
On a Microsoft Windows window, we need a windows procedure function to handle event processing. Here we will process the WM_PAINT
, WM_SIZE
and WM_CLOSE
events. The WM_CLOSE
event is called when a user clicks on the close button on the window. However, the WM_PAINT
event is used to render the window. We will handle the WM_PAINT
and WM_SIZE
message in later chapters:
// Windows procedure handlers for MS Windows
LRESULT CALLBACK VulkanRenderer::WndProc(HWND hWnd, UINT uMsg,
WPARAM wParam, LPARAM lParam){
VulkanApplication* appObj = VulkanApplication::GetInstance();
switch (uMsg){
case WM_CLOSE: PostQuitMessage(0); break;
default: break;
}
return (DefWindowProc(hWnd, uMsg, wParam, lParam));
}
The initialization creates a presentation window with a given dimension and fulfills various prerequisites for the swapchain implementation. This includes querying the swapchain extension APIs, creating surface objects, finding the best-supported queue for the presentation layer, getting the compatible image format for drawing, and so on. We will discuss each and every part in detail as we proceed through the chapter. The initialization of the Renderer
class is done using the initialize()
function, as described in the following code; refer to the comments highlighted in bold to get an overview:
void VulkanRenderer::initialize(){ // Create an empty window with dimension 500x500 createPresentationWindow(500, 500); // Initialize swapchain swapChainObj->intializeSwapChain(); // We need command buffers, so create a command buffer pool createCommandPool(); // Let's create the swapchain color images buildSwapChainAndDepthImage(); }
The VulkanRenderer
class contains the command pool. This pool will be used for command buffer allocation and for various operations such as the creation of the depth image, pipeline state setup, drawing primitives, and many more. This chapter will demonstrate the first command buffer in action, where we will create the depth image.
In the following code, the command pool is created using the various attributes specified in the VkCommandPoolCreateInfo
control structure. This control structure contains the index of the graphics queue for which the command buffers need to be allocated. Vulkan specifies such information in the control structures ahead of time, allowing the underlying pipeline to take full advantage of pre-optimizations. For more information on command buffers and command pools, refer to the Understanding the command pool and buffer APIs section in Chapter 5, Command Buffer and Memory Management in Vulkan:
void VulkanRenderer::createCommandPool(){ VulkanDevice* deviceObj = application->deviceObj; VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType=VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.queueFamilyIndex = deviceObj-> graphicsQueueWithPresentIndex; vkCreateCommandPool(deviceObj->device, &cmdPoolInfo, NULL, &cmdPool); }
The buildSwapChainAndDepthImage()
function is the entry point for creating swapchain and depth images. We will look at these internal functions in detail as we proceed:
void VulkanRenderer::buildSwapChainAndDepthImage(){ // Get the appropriate queue to submit the command into deviceObj->getDeviceQueue(); // Create swapchain and get the color images swapChainObj->createSwapChain(cmdDepthImage); // Create the depth image createDepthImage(); }
Draw the presentation window on the display and handle Windows messages. When the user presses the close button, exit the presentation window and break the infinite rendering loop:
void VulkanRenderer::render(){ MSG msg; // message while (1) { PeekMessage(&msg, NULL, 0, 0, PM_REMOVE); if (msg.message == WM_QUIT) { break; // If Quit message the exit the render loop } TranslateMessage(&msg); DispatchMessage(&msg); RedrawWindow(window, NULL, NULL, RDW_INTERNALPAINT); } }
A swapchain is implemented in the VulkanSwapChain
class inside VulkanSwapChain.h/.cpp
. This class manages the entire swapchain life cycle from its initialization and creation to its destruction (public functions). The life cycle comprises several smaller intermediate stages, such as querying the API extensions, getting the appropriate image format, getting surface capabilities and the presentation mode, and so on (private functions).
The following is the header file declaration of the VulkanSwapChain
class, we will go through all the important functionalities:
class VulkanSwapChain{ // Many line are skipped, please refer to source code public: void intializeSwapChain(); void createSwapChain(const VkCommandBuffer& cmd); void destroySwapChain(); private: VkResult createSwapChainExtensions(); void getSupportedFormats(); VkResult createSurface(); uint32_t getGraphicsQueueWithPresentationSupport(); void getSurfaceCapabilitiesAndPresentMode(); void managePresentMode(); void createSwapChainColorBufferImages(); void createColorImageView(const VkCommandBuffer& cmd); public: // User define structure containing public variables used // by the swapchain private and public functions. SwapChainPublicVariables scPublicVars; private: // User define structure containing private variables used // by the swapchain private and public functions. SwapChainPrivateVariables scPrivateVars; VulkanRenderer* rendererObj; VulkanApplication* appObj; };
In addition to this, the class has two user-defined structures, namely SwapChainPrivateVariables
and SwapChainPublicVariables
. This includes the class's private and public member variables:
struct SwapChainPrivateVariables { // Store the image surface capabilities VkSurfaceCapabilitiesKHR surfCapabilities; // Stores the number of present modes uint32_t presentModeCount; // Arrays for retrived present modes std::vector<VkPresentModeKHR> presentModes; // Size of the swapchain color images VkExtent2D swapChainExtent; // Number of color images supported uint32_t desiredNumberOfSwapChainImages; VkSurfaceTransformFlagBitsKHR preTransform; // Stores present mode bitwise flag for swapchain creation VkPresentModeKHR swapchainPresentMode; // The retrived drawing color swapchain images std::vector<VkImage> swapchainImages; std::vector<VkSurfaceFormatKHR> surfFormats; }; struct SwapChainPublicVariables { // The logical platform dependent surface object VkSurfaceKHR surface; // Number of buffer image used for swapchain uint32_t swapchainImageCount; // Swapchain object VkSwapchainKHR swapChain; // List of color swapchain images std::vector<SwapChainBuffer> colorBuffer; // Current drawing surface index in use uint32_t currentColorBuffer; // Format of the image VkFormat format; };
The swapchain implementation needs a set of APIs. These APIs are not part of the Vulkan SDK and are dynamically available. The APIs are very specific to the platform implementation and are available in the form of API extensions, which are queried dynamically and stored in the form of a function pointer. This API extension can be queried successfully by specifying the VK_KHR_SWAPCHAIN_EXTENSION_NAME
device extension when the VkDevice
object is created:
std::vector<const char *> deviceExtensionNames = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; // Look into VulkanDevice::createDevice() for more info VkDeviceCreateInfo deviceInfo = {}; deviceInfo.ppEnabledExtensionNames = deviceExtensionNames; vkCreateDevice(*gpu, &deviceInfo, NULL, &device);
The following code snippet codes a macro, thereby helping us query instance- and device-specific extensions. We use this macro to get the WSI extension API's function pointer. For more information on instance- and device-level extensions, refer to the Introduction to layer and extensions section in Chapter 3, Shaking Hands with the Device:
#define INSTANCE_FUNC_PTR(instance, entrypoint){ fp##entrypoint = (PFN_vk##entrypoint) vkGetInstanceProcAddr (instance, "vk"#entrypoint); if (fp##entrypoint == NULL) { exit(-1); } #define DEVICE_FUNC_PTR(dev, entrypoint){ fp##entrypoint = (PFN_vk##entrypoint)vkGetDeviceProcAddr (dev, "vk"#entrypoint); if (fp##entrypoint == NULL) { exit(-1); }
The following are the extension names at the instance and device level:
std::vector<const char *> instanceExtensionNames = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_EXTENSION_NAME, }; std::vector<const char *> deviceExtensionNames = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
This macro uses vkGetInstanceProcAddr()
and vkGetDeviceProcAddr()
to get the instance- and device-level extensions. For more information on the vkGetInstanceProcAddr()
API, refer to the Implementing debugging in Vulkan section in Chapter 4, Debugging in Vulkan.
The following code queries instance- and device-level extensions using the user-defined createSwapChainExtensions()
function:
VkResult VulkanSwapChain::createSwapChainExtensions(){ // Dependency on createPresentationWindow() VkInstance& instance = appObj->instanceObj.instance; VkDevice& device = appObj->deviceObj->device; // Get Instance based swapchain extension function pointer INSTANCE_FUNC_PTR(instce,GetPhysicalDeviceSurfaceSupportKHR); INSTANCE_FUNC_PTR(instance, GetPhysicalDeviceSurfaceCapabilitiesKHR); INSTANCE_FUNC_PTR(instance, GetPhysicalDeviceSurfaceFormatsKHR); INSTANCE_FUNC_PTR(instance, GetPhysicalDeviceSurfacePresentModesKHR); INSTANCE_FUNC_PTR(instance, DestroySurfaceKHR); // Get Device based swapchain extension function pointer DEVICE_FUNC_PTR(device, CreateSwapchainKHR); DEVICE_FUNC_PTR(device, DestroySwapchainKHR); DEVICE_FUNC_PTR(device, GetSwapchainImagesKHR); DEVICE_FUNC_PTR(device, AcquireNextImageKHR); DEVICE_FUNC_PTR(device, QueuePresentKHR); }
The following table shows the extension APIs that will be required to implement and manage the swapchain. These extensions are at both the instance and device levels and they must be queried. As we go through the various function implementations, we will discuss their extensions in detail:
Instance |
Device |
For |
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
For
the function pointer is
|
The retrieved API extensions are stored in the user-defined function pointer variable. They are renamed by replacing the prefix vk
with fp
. For example, the API extension vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
is stored as fpGetPhysicalDeviceSurfaceCapabilitiesKHR()
. This is similar for others API extensions as well.
The Vulkan API can be implemented seamlessly on every platform. A platform is an abstraction of windowing and OS services. Some examples include MS Windows, Android, and Wayland. Window System Integration (WSI) provides a platform-independent way to implement windowing or surface management.
Vulkan represents the logical surface object using VkSurfaceKHR
. Different platforms have different APIs to create the VkSurfaceKHR
surface object, such as vkCreateWin32-SurfaceKHR()
, vkCreateAndroidSurfaceKHR()
, and vkCreateXcbSurfaceKHR()
:
This example will focus on the Windows platform:
VkResult vkCreateWin32SurfaceKHR( VkInstance instance, const VkWin32SurfaceCreateInfoKHR* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* surface);
The following is the description of each and every field:
Parameters |
Description |
|
This refers to the |
|
This refers to the |
|
This is used to control the host-specific memory allocation process. |
|
This returns the pointer of the created surface object. |
The API takes the VkWin32SurfaceCreateInfo
structure as an input parameter, where various control properties on surface management can be specified. The following is the API syntax:
typedef struct VkWin32SurfaceCreateInfoKHR { VkStructureType type; const void* next; VkWin32SurfaceCreateFlagsKHR flags; HINSTANCE hinstance; HWND hwnd; } VkWin32SurfaceCreateInfoKHR;
Let's take a look at all the parameters of the control structure:
Parameters |
Description |
|
This is the type information of this structure, which must be |
|
This refers to |
|
This field is reserved for future use. |
|
This is the instance ID of the created window. |
|
This is the handle of the created window; |
The VulkanSwapChain
class implements the creation of the logical WSI surface using the createSurface()
method:
// Depends on createPresentationWindow(), need window handle VkResult VulkanSwapChain::createSurface(){ VkInstance& instance = appObj->instanceObj.instance; // Construct the surface description: VkWin32SurfaceCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR; createInfo.pNext = NULL; createInfo.hinstance = rendererObj->connection; createInfo.hwnd = rendererObj->window; // Create the VkSurfaceKHR object return result = vkCreateWin32SurfaceKHR(instance, &createInfo, NULL, &scPublicVars.surface); }
When swapchain color images are painted by the drawing primitives commands, they then submitted to the presentation engine to be displayed on the screen. This request is submitted in the form of a command buffer into the queue which can take up presentation requests and process it. Therefore, we need a graphic queue which is not only capable of accepting drawing command buffers but also supports the presentation. This can be done by querying each graphic queue available on the physical device and checking whether they support the presentation properties or not. The following code help us to achieve the same, we implement a help function getGraphicsQueueWithPresentationSupport()
where we end up retrieving a single queue supporting graphics and presentation command buffer requests:
uint32_t VulkanSwapChain::getGraphicsQueueWithPresentationSupport(){ VulkanDevice* device = appObj->deviceObj; uint32_t queueCount = device->queueCount; VkPhysicalDevice gpu = *device->gpu; vector<VkQueueFamilyProperties>& queueProps = device->queueProps; // Iterate each queue and get presentation status for each. VkBool32* supportsPresent = (VkBool32 *)malloc(queueCount * sizeof(VkBool32)); for (uint32_t i = 0; i < queueCount; i++) { fpGetPhysicalDeviceSurfaceSupportKHR(gpu, i, scPublicVars.surface, &supportsPresent[i]); } // Search for a graphics queues that supports presentation uint32_t graphicsQueueNodeIndex = UINT32_MAX; uint32_t presentQueueNodeIndex = UINT32_MAX; for (uint32_t i = 0; i < queueCount; i++) { if ((queueProps[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) { if (graphicsQueueNodeIndex == UINT32_MAX) { graphicsQueueNodeIndex = i; } if (supportsPresent[i] == VK_TRUE) { graphicsQueueNodeIndex = i; presentQueueNodeIndex = i; break; } } } if (presentQueueNodeIndex == UINT32_MAX) { // If didn't find a queue that supports both graphics // and present, then find a separate present queue. for (uint32_t i = 0; i < queueCount; ++i) { if (supportsPresent[i] == VK_TRUE) { presentQueueNodeIndex = i; break; } } } free(supportsPresent); // Generate error if could not find queue with present queue if (graphicsQueueNodeIndex == UINT32_MAX || presentQueueNodeIndex == UINT32_MAX) {return UINT32_MAX;} return graphicsQueueNodeIndex; }
The swapchain needs a supported color-space format for the surface. All the supported formats can be retrieved using the vkGetPhysicalDeviceSurfaceFormatsKHR()
API. Here's the syntax of this:
VkResult vkGetPhysicalDeviceSurfaceFormatsKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats);
The following table describes the various fields of this API:
Parameters |
Description |
|
This refers to the logical device associated with the swapchain. |
|
This refers to the logical surface created for the swapchain. |
|
This is an in and out parameter. When |
|
This is to retrieve the supported surface formats. |
The following implementation gets the number of supported image formats. Using formatCount
, the surface formats are retrieved into the VkSurfaceFormatKHR
array. In the event no preferred surface format information is found, we treat the surface format as 32-bit RGBA:
void VulkanSwapChain::getSupportedFormats() { VkPhysicalDevice gpu = *rendererObj->getDevice()->gpu; VkResult result; // Get the number of VkFormats supported: uint32_t formatCount; fpGetPhysicalDeviceSurfaceFormatsKHR (gpu, scPublicVars.surface, &formatCount, NULL); scPrivateVars.surfFormats.clear(); scPrivateVars.surfFormats.resize(formatCount); // Get VkFormats in allocated objects result = fpGetPhysicalDeviceSurfaceFormatsKHR(gpu, scPublicVars.surface, &formatCount, &scPrivateVars.surfFormats[0]); // In case it's a VK_FORMAT_UNDEFINED, then surface has no // preferred format. We use RGBA 32 bit format if (formatCount == 1 && surfFormats[0].format == VK_FORMAT_UNDEFINED) { scPublicVars.format = VK_FORMAT_B8G8R8A8_UNORM; } else {scPublicVars.format = surfFormats[0].format;} }
In the following subsection, we will learn the implementation of the swapchain. This consists of querying surface capabilities and presentation modes, retrieving color images, and creating image views.
The swapchain creation process requires you to know two things in order to create the image surface: surface capabilities and the presentation mode:
vkGetPhysicalDeviceSurfaceCapabilitiesKHR()
API extension can be used to query these capabilities. The extension is stored in the user-defined function pointer fpGetPhysicalDeviceSurface-CapabilitiesKHR()
. Its syntax is as follows:VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* surfaceCapabilities);
The advertised capabilities are retrieved in the VkSurfaceCapabilitiesKHR
object. This includes useful information such as the min/max number of image surfaces supported, image dimension range, the maximum number of image arrays possible, the kinds of transformation features supported by the surface (such as rotation or mirror rotation through 90, 180, and 270 degrees), and so on.
vkGetPhysicalDeviceSurfacePresentModesKHR()
API extension dynamically . The retrieved information is exposed via the VkPresentModeKHR
enumeration supporting four types of presentation modes (see the next section for more information on the modes). The presentModelCount
contains the number of presentation modes. The syntax of this API is as follows:VkResult vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* presentModeCount, VkPresentModeKHR* presentModes);
The following code retrieves the surface capabilities along with the presentation modes and stores the required information in the private class member variables:
void VulkanSwapChain::getSurfaceCapabilitiesAndPresentMode(){ // Some lines are skipped, please refer to the source code VkPhysicalDevice gpu = *appObj->deviceObj->gpu; fpGetPhysicalDeviceSurfaceCapabilitiesKHR(gpu, scPublicVars. surface, &scPrivateVars.surfCapabilities); fpGetPhysicalDeviceSurfacePresentModesKHR(gpu, scPublicVars. surface, &scPrivateVars.presentModeCount, NULL); scPrivateVars.presentModes.clear(); scPrivateVars.presentModes.resize (scPrivateVars.presentModeCount); assert(scPrivateVars.presentModes.size()>=1); result = fpGetPhysicalDeviceSurfacePresentModesKHR (gpu, scPublicVars.surface, &scPrivateVars.presentModeCount, &scPrivateVars.presentModes[0]); fpGetPhysicalDeviceSurfacePresentModesKHR(gpu, scPublicVars. surface, &scPrivateVars.presentModeCount, scPrivateVars.presentModes); if(scPrivateVars.surfCapabilities.currentExtent.width == (uint32_t)-1){ // If the surface width and height is not defined, // then set it equal to image size. scPrivateVars.swapChainExtent.width = rendererObj->width; scPrivateVars.swapChainExtent.height = rendererObj->height; } else{ // If the surface size is defined, then it must // match the swapchain size scPrivateVars.swapChainExtent = scPrivateVars. surfCapabilities.currentExtent; } }
The color images contained by the swapchain are managed by the presentation engine using the presentation mode schemes. These schemes determine how the incoming presentation requests will be processed and queued internally. The VkPresentModeKHR()
API supports four types of presentation mode:
Presentation mode |
Description |
|
This mode immediately renders the presentation requests without waiting for vertical blanking. No internal queue management is required for presentation requests. This mode is highly susceptible to image tearing. |
|
Here, the presentation requests are queued up in a single entry queue, and they wait for the next vertical blanking signal that will allow the presentation engine to update the image. This mode does not cause the tearing effect. When the queue is full, the latest presentation request replaces the prior one. In the event of vertical blanking, a single queue request is popped up and is processed. Any image associated with the prior entry becomes available for reuse by the application. |
|
Here, the presentation requests are queued up in a single entry queue, and they wait for the next vertical blanking to update the current image, where the front image is removed and processed (hence FIFO). Tearing cannot be observed here. A new request is added to the end of the queue and removed from the beginning. |
|
Here, the presentation engine generally updates the current image during the vertical blanking period. If a vertical blanking period has already passed since the last update of the current image, then the presentation engine does not wait for the subsequent vertical blanking period to push the next presentation image to be processed. The next image is immediately followed for the update. This mode may result in visible tearing. New requests are appended to the end of the queue, and one request is removed from the beginning of the queue and processed during or after each vertical blanking period in which the queue is not empty. |
The following code implements the presentation mode scheme. First it checks whether the MAILBOX
scheme (non-tearing) is available. In case this mode is not available, then the IMMEDIATE
mode is preferred. The default fallback scheme is FIFO
:
void VulkanSwapChain::managePresentMode() { // MAILBOX - lowest-latency non-tearing mode.If not,try // IMMEDIATE, the fastest (but tears). Else, fall back to FIFO. scPrivateVars.swapchainPresentMode = VK_PRESENT_MODE_FIFO_KHR; for (size_t i = 0; i < scPrivateVars.presentModeCount; i++) { if(scPrivateVars.presentModes[i]==VK_PRESENT_MODE_MAILBOX_KHR){ scPrivateVars.swapchainPresentMode = VK_PRESENT_MODE_MAILBOX_KHR; break; } if(scPrivateVars.swapchainPresentMode!=VK_PRESENT_MODE_MAILBOX_KHR &&scPrivateVars.presentModes[i]== VK_PRESENT_MODE_IMMEDIATE_KHR){ scPrivateVars.swapchainPresentMode=VK_PRESENT_MODE_IMMEDIATE_KHR; } } // Determine the number of VkImage's to use in the swapchain scPrivateVars.desiredNumberOfSwapChainImages = scPrivateVars. surfCapabilities.minImageCount + 1; if ((scPrivateVars.surfCapabilities.maxImageCount > 0) && (scPrivateVars.desiredNumberOfSwapChainImages > scPrivateVars.surfCapabilities.maxImageCount)) { // Application must settle for fewer images than desired: scPrivateVars.desiredNumberOfSwapChainImages = scPrivateVars.surfCapabilities.maxImageCount; } if(scPrivateVars.surfCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { scPrivateVars.preTransform=VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; } else { scPrivateVars.preTransform= scPrivateVars.surfCapabilities.currentTransform; } }
Swapchain color images are retrieved by creating the VkSwapchainKHR
swapchain object using the vkCreateSwapchainKHR()
API extension function pointer. The syntax to do this is as follows:
VkResult vkCreateSwapchainKHR( VkDevice device, const VkSwapchainCreateInfoKHR* createInfo, const VkAllocationCallbacks* allocator, VkSwapchainKHR* swapchain);
This API accepts the VKSwapChainCreateInforKHR
control structure. This structure contains the necessary information to control the creation of the swapchain object retrieved in the VkSwapchainKHR
structure. Here's the syntax of this:
typedef struct VkSwapchainCreateInfoKHR { VkStructureType type; const void* next; 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* queueFamilyIndices; VkSurfaceTransformFlagBitsKHR preTransform; VkCompositeAlphaFlagBitsKHR compositeAlpha; VkPresentModeKHR presentMode; VkBool32 clipped; VkSwapchainKHR oldSwapchain; } VkSwapchainCreateInfoKHR;
The VkSwapchainCreateInfoKHR
structure has the following parameters:
Parameters |
Description |
|
This specifies the type of the structure. It must be |
|
This refers to |
|
This must be zero. This field is reserved for future use. |
|
This is the surface to which the swapchain images will be presented. |
|
This refers to the minimum number of presentable images needed by the application to implement the swapchain mechanism. |
|
This is the format to be used for swapchain color images. |
|
This represents the color space ( |
|
This refers to the swapchain's image size or dimensions specified in pixels. |
|
This represents the number of views in a multiview/stereo surface. |
|
This is a |
|
This is the sharing mode used for the images of the swapchain. |
|
The refers to the number of queue families that have access to the images of the swapchain if |
|
This is an array of queue family indices that have access to the images of the swapchain if |
|
This is a bit field of |
|
This is the |
|
This is the presentation mode the swapchain will use. |
|
This indicates whether the Vulkan implementation is allowed to discard the rendering operations that affect the regions of the surface that aren't visible. |
|
This is non-null, and it specifies the swapchain that will be replaced by the new swapchain being created. Upon calling |
In the following code, the createSwapChainColorBufferImage()
function creates the swapchain using the function pointer of vkCreateSwapchainKHR (fpCreateSwapchainKHR())
. Upon successful creation of the swapchain, the VkImage
object image surfaces are created behind the scenes. Before acquiring the image surfaces, we allocate sufficient memory space to hold the image buffers. The number of swapchain images and physical surfaces is returned by the vkGetSwapchainImagesKHR()
API extension's function pointer, namely (fpGetSwapchainImagesKHR()
).
VkSwapchainCreateInfoKHR
control structure with the required field values, such as the format of the image, size, presentation mode, color space, and so on. This information is used by the fpCreateSwapchainKHR()
API function pointer to create the swapchain.VkSwapchainKHR
swapchain object (swapchainImages
) is created successfully, use it to acquire the image using the vkGetSwapchainImagesKHR()
API. This API is called twice:NULL
, it retrieves the number of image (swapchainImageCount
) present in the swapchain.swapchainImageCount
is used to allocate sufficient memory to hold the surface image arrays (VkImage*
). The same API when called the second time retrieves the image in the allocated VkImage
array objects called swapchainImages
, as explained in the following API description:
VkResult vkGetSwapchainImagesKHR( VkDevice device, VkSwapchainKHR swapchain, uint32_t* swapchainImageCount, VkImage* swapchainImages);
Parameters |
Description |
|
This is the logical device associated with the swapchain. |
|
This refers to the |
|
This refers to the number of image the swapchain contains. |
|
This refers to the |
The following code shows the implementation of the swapchain object creation process and the retrieval of the color images. The color images are used to store the color information for each corresponding pixel:
void VulkanSwapChain::createSwapChainColorBufferImages() { VkSwapchainCreateInfoKHR scInfo = {}; scInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; scInfo.pNext = NULL; scInfo.surface = scPublicVars.surface; scInfo.minImageCount = scPrivateVars.desiredNumberOfSwapChainImages; scInfo.imageFormat = scPublicVars.format; scInfo.imageExtent.width = scPrivateVars.swapChainExtent.width; scInfo.imageExtent.height = scPrivateVars.swapChainExtent.height; scInfo.preTransform = scPrivateVars.preTransform; scInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; scInfo.imageArrayLayers = 1; scInfo.presentMode = scPrivateVars.swapchainPresentMode; scInfo.oldSwapchain = VK_NULL_HANDLE; scInfo.clipped = true; scInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; scInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; scInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; scInfo.queueFamilyIndexCount = 0; scInfo.pQueueFamilyIndices = NULL; // Create the swapchain object fpCreateSwapchainKHR(rendererObj->getDevice()->device, &swapChainInfo, NULL, &scPublicVars.swapChain); // Get the number of images the swapchain has fpGetSwapchainImagesKHR(rendererObj->getDevice()->device, scPublicVars.swapChain, &scPublicVars.swapchainImageCount, NULL); scPrivateVars.swapchainImages.clear(); // Make array of swapchain image to retrieve the images scPrivateVars.swapchainImages.resize (scPublicVars.swapchainImageCount); assert(scPrivateVars.swapchainImages.size() >= 1); // Retrieve the swapchain image surfaces fpGetSwapchainImagesKHR(rendererObj->getDevice()->device, scPublicVars.swapChain, &scPublicVars.swapchainImageCount, scPrivateVars.swapchainImages); }
As discussed in the beginning of this chapter, images are not directly used in the form of image objects (VkImage
) by the application; instead, image views are used (VkImageView
). In this section, we will learn how to create an image view using image objects.
Our application implements the image view creation process in the createColorImageView()
function. The image view is created using the vkCreateImageView()
API. This API accepts important parameters, such as the image view format, mipmap level, level count, number of array layers, and so on. For more information on image view APIs, refer to the Creating the image view subsection under the Understanding image resources section in this chapter.
In the following implementation, for each swapchain image object (VkImage
), we create a corresponding image view by iterating through the list of the image objects available in scPublicVars.swapchainImageCount
. This count is retrieved using the fpGetSwapchainImagesKHR()
API, as mentioned in the previous section. The created image views are then pushed back to a vector list, where they will be used later to refer to the correct front and back buffer images:
void VulkanSwapChain::createColorImageView(const VkCommandBuffer& cmd){ VkResult result; for(uint32_t i = 0; i < scPublicVars.swapchainImageCount; i++){ SwapChainBuffer sc_buffer; VkImageViewCreateInfo imgViewInfo = {}; imgViewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; imgViewInfo.pNext = NULL; imgViewInfo.format = scPublicVars.format; imgViewInfo.components.r = VK_COMPONENT_SWIZZLE_R; imgViewInfo.components.g = VK_COMPONENT_SWIZZLE_G; imgViewInfo.components.b = VK_COMPONENT_SWIZZLE_B; imgViewInfo.components.a = VK_COMPONENT_SWIZZLE_A; imgViewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; imgViewInfo.subresourceRange.baseMipLevel = 0; imgViewInfo.subresourceRange.levelCount = 1; imgViewInfo.subresourceRange.baseArrayLayer = 0; imgViewInfo.subresourceRange.layerCount = 1; imgViewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; imgViewInfo.flags = 0; sc_buffer.image = scPrivateVars.swapchainImages[i]; // Since the swapchain is not owned by us we cannot set // the image layout. Upon setting, the implementation // may give error, the images layout were // created by the WSI implementation not by us. imgViewInfo.image = sc_buffer.image; result = vkCreateImageView(rendererObj->getDevice()->device, &imgViewInfo, NULL, &sc_buffer.view); scPublicVars.colorBuffer.push_back(sc_buffer); } scPublicVars.currentColorBuffer = 0; }
18.119.104.95