Introducing swapchains

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.

Understanding the swapchain implementation flow

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:

Understanding the swapchain implementation flow

Let's get into the flow and quickly take an overview of each of the specifics:

  1. Create an empty window: This process provides an empty native platform window that is connected to the swapchain's color images. Each time a frame (image) is written, it is swapped to the presentation layer. The presentation layer relays this information to the attached native window for display purposes.
  2. Querying swap chain extensions: The swapchain APIs are not part of the standard API specification. They are implementation-specific, and the loader is used to load these APIs in the form of extensions. The loaded extensions are stored in the form of function pointers whose signatures are predefined in the Vulkan specification.
  3. Creating a surface and associate it with the created window: This process creates a logical platform-specific surface object. At this time, it has no memory backing for the color images. This logical surface object is attached to the empty window, declaring the window as its owner.
  4. Getting supported image formats: In this step, we query the physical device to check all the image formats it supports.
  5. Querying swapchain image surface capabilities: This obtains the information on basic surface capabilities, since it is required when we create swapchains images. In addition, it checks the present modes that are available.
  6. Managing the present mode information: This uses the available present mode information and decide the present mode technique that should be used in the swapchain. A swapchain's present mode decides how the incoming presentation  requests will be internally processed and queued.
  7. Creating the swapchain and retrieving the presentation images: Let's use the above-gathered information and create the swapchain images. The WSI extension returns the color image object; these images are of the type VkImage, and are used by the application.

    Tip

    The WSI images are not owned by the application it belongs to WSI and hence the image layout cannot applied. The image layout can only be applied to images that are owned by the application.

  8. Creating color image views: Depending upon the system capabilities, the WSI implementation may return 1–3 swapchain images (based on single, double, or triple buffering). For using each image in the application, you will need to create a corresponding image view.
  9. Creating a depth image: Similar to the color images, you will need a depth image for depth testing; but unlike swapchain images, which are prebaked by the WSI, the depth image needs to be created by the application. First, you will need to create a depth image object (VkImage), follow the next steps to allocate the memory, create image layout and finally producing the image view object (VkImageView).    
  10. Depth image memory allocation: You will need to allocate the physical device memory and bind it to the depth image object.
  11. Creating the command pool: We need the command buffer for depth image since this image is owned by us; we will use the command buffer to apply the image layouts.
  12. Creating the command buffer: You will need to create the command buffer and begin to record the commands responsible for creating the depth image layout using the created depth image object.
  13. Image layout: This lets you apply a depth/stencil compatible image layout on the depth image object.
  14. Add pipeline barriers: In order to ensure that the image layout is always executed before the creation of the image view, add a pipeline barrier. When a pipeline barrier is inserted, it ensures that the commands prior to it is executed before the commands that follow it in the command buffer.
  15. Ending command buffer recording: This allows you to stop the command buffer recording .
  16. Creating a depth image view: After the image object is converted into the compatible image layout, create an image view  object (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.

The swapchain implementation's class block diagram

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.

Note

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 swapchain implementation's class block diagram

Renderer - a window management custom class

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:

Renderer - a window management custom class

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:

Renderer - a window management custom class

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); 
} 

Creating the presentation window

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)); 
} 

Initializing the renderer

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(); 
} 

Creating the command pool

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); 
} 

Building swapchain and depth images

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(); 
} 

Rendering the presentation window

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); 
   } 
} 

VulkanSwapChain - the swapchain manager

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; 
}; 

Querying swapchain extensions

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 vkGetPhysicalDeviceSurfaceSupportKHR, the function pointer is fpGetPhysicalDeviceSurfaceSupportKHR.

For vkCreateSwapchainKHR

the function pointer is fpGetPhysical.

For vkGetPhysicalDeviceSurfaceCapabilitiesKHR

the function pointer is fpGetPhysicalDeviceSurfaceCapabilitiesKHR.

For vkDestroySwapchainKHR

the function pointer is fpDestroySwapchainKHR.

For vkGetPhysicalDeviceSurfaceFormatsKHR

the function pointer is fpGetPhysicalDeviceSurfaceFormatsKHR.

For vkGetSwapchainImagesKHR

the function pointer is fpGetSwapchainImagesKHR.

For vkGetPhysicalDeviceSurfacePresentModesKHR

the function pointer is fpGetPhysicalDeviceSurfacePresentModesKHR.

For vkAcquireNextImageKHR

the function pointer is fpAcquireNextImageKHR.

For vkDestroySurfaceKHR

the function pointer is fpDestroySurfaceKHR.

For vkQueuePresentKHR

the function pointer is fpQueuePresentKHR.

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.

Creating the surface with WSI and associating it with the created window

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():

Creating the surface with WSI and associating it with the created window

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

instance

This refers to the VkInstance object associated with the surface.

pCreateInfo

This refers to the VkWin32SurfaceCreateInfoKHR structure object to control surface management. More information in provided in the following information box.

pAllocator

This is used to control the host-specific memory allocation process.

surface

This returns the pointer of the created surface object.

Note

The vkCreate<Platform>SurfaceKHR structure object is created on the logical surface. It has no backing of the physical memory yet.

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

type

This is the type information of this structure, which must be VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR.

next

This refers to NULL or an extension-specific pointer.

flag

This field is reserved for future use.

hInstance

This is the instance ID of the created window.

hwnd

This is the handle of the created window; hwnd and hInstance will be used to associate the surface with the presentation 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); 
} 

The graphics queue with present support

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; 
} 

Querying swapchain image formats

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

physicalDevice

This refers to the logical device associated with the swapchain.

surface

This refers to the logical surface created for the swapchain.

pSurfaceFormatCount

This is an in and out parameter. When vkGetPhysicalDeviceSurfaceFormatKHR is called with surfaceFormats as NULL, it returns the number of supported surface formats. Otherwise, it is used to retrieve the surfaces based on the number of surface pointers.

pSurfaceFormats

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;} 
} 

Creating the swapchain

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.

Swapchain surface capabilities and the presentation mode

The swapchain creation process requires you to know two things in order to create the image surface: surface capabilities and the presentation mode:

  1. Surface capabilities: This specifies the image surface capabilities offered by the physical device. The 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.

  2. The presentation mode: A swapchain can have various types of presentation mode, and can be retrieved using the 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; 
   } 
} 

Managing presentation mode information

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

VK_PRESENT_MODE_IMMEDIATE_KHR

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.

VK_PRESENT_MODE_MAILBOX_KHR

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.

VK_PRESENT_MODE_FIFO_KHR

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.

VK_PRESENT_MODE_FIFO_RELAXED_KHR

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; 
  } 
} 

Note

In the real application, it is advisable to prefer PRESENT_MODE_FIFO_RELAXED_KHR as the presentation mode as it only tears when the app misses but doesn't tear when the app is fast enough.

Retrieving the swapchain's color images

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

type

This specifies the type of the structure. It must be VK_-STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR.

next

This refers to NULL or a pointer to an extension-specific structure.

flags

This must be zero. This field is reserved for future use.

surface

This is the surface to which the swapchain images will be presented.

minImageCount

This refers to the minimum number of presentable images needed by the application to implement the swapchain mechanism.

imageFormat

This is the format to be used for swapchain color images.

imageColor-

Space

This represents the color space (VkColorSpaceKHR) supported by the swapchain.

imageExtent

This refers to the swapchain's image size or dimensions specified in pixels.

imageArray-

Layers

This represents the number of views in a multiview/stereo surface.

imageUsage

This is a VkImageUsageFlagBits bit field indicating how the application will use the swapchain's presentable images.

imageSharingMode

This is the sharing mode used for the images of the swapchain.

queueFamily-

IndexCount

The refers to the number of queue families that have access to the images of the swapchain if imageSharingMode is VK_SHARING_MODE_CONCURRENT.

queueFamily

Indices

This is an array of queue family indices that have access to the images of the swapchain if imageSharingMode is VK_SHARING_MODE_CONCURRENT.

preTransform

This is a bit field of VkSurfaceTransformFlag-BitsKHR that describes the transform relative to the presentation engine's natural orientation, which is applied to the image content prior to the presentation.

compositeAlpha

This is the VkCompositeAlphaFlagBitsKHR bit field indicating the alpha-compositing mode to use when this surface is composited together with other surfaces on certain windowing systems.

presentMode

This is the presentation mode the swapchain will use.

clipped

This indicates whether the Vulkan implementation is allowed to discard the rendering operations that affect the regions of the surface that aren't visible.

oldSwapchain

This is non-null, and it specifies the swapchain that will be replaced by the new swapchain being created. Upon calling vkCreateSwapchainKHR with an old non-null swapchain, any image owned by the presentation engine and not currently being displayed will be freed immediately. Any image that is being displayed will be freed once it is no longer being displayed. This may occur even if the creation of the new swapchain fails. The application must destroy the old swapchain to free all of the memory associated with the old swapchain, including any presentable images the application currently owns. It must wait for the completion of any outstanding rendering before doing so, with the exception of rendering to presentable images that have been successfully submitted to the presentation, as described next, but that are not yet owned by the presentation engine.

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()).

  1. First populate the 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.
  2. Once the VkSwapchainKHR swapchain object (swapchainImages) is created successfully, use it to acquire the image using the vkGetSwapchainImagesKHR() API. This API is called twice:
    • When it is called for the first time with the last parameter as NULL, it retrieves the number of image (swapchainImageCount) present in the swapchain.
    • The 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

device

This is the logical device associated with the swapchain.

swapchain

This refers to the VkSwapChainKHR object.

swapchainImageCount

This refers to the number of image the swapchain contains.

swapchainImages

This refers to the VkImage array object that retrieves the swapchain.

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); 
 } 

Creating color image views

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; 
} 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.104.95