I’ve realized how explicit and involved effort in setting up Vulkan code even prior to the first simple triangle rendered on screen. So here is Part I for code overview in setting up working rendering code with Vulkan. It explores various areas either required or are better to use for performance gained.
Part I covers all essential and basic initializations, and rendering loop leaving some areas which will be working on top the foundation like Vertex buffer, Uniform buffers for later parts. So it is pure stuff required to get a simple triangle able to be rendered on screen. Subsequent parts will expand more gradually.
VK_LAYER_KHRONOS_validation
VkApplicationInfo
.apiVersion = VK_API_VERSION_1_2
for Vulkan’s api version to link againstVkInstanceCreateInfo
.enabledExtension = <size of extensions>
.ppEnabledExtensionNames = <vector array of extension names>
VkDebugUtilsMessengerCreateInfoExt
to set up debug call prior to the call to create a new instance by setting to its .pNext
field (we will be doing the similar thing again in Setup debug messenger for whole other calls).
.pfnUserCallback
by seeing the function signature at PFN_vkDebugUtilsMessengerCallbackEXTvkCreateInstance
to actually create an instance.This is different from 1. as this will hook up the entire call chain into the input VkInstance
PFN_vkCreateDebugUtilsMessengerEXT
callback signature to get function address of “vkCreateDebugUtilsMessengerEXT
” through vkGetInstanceProcAddr
.This is specifically for window system/library you might be using, in usual case it is probably be glfw by using the following function
glfwCreateWindowSurface()
to create VkSurfaceKHR
vkEnumeratePhysicalDevices
- to get device count and devices to iterate laterVkDeviceQueueCreateInfo
to hold to-be-created unique VkDeviceQueueCreateInfo
VkPhysicalDeviceFeatures
to enable any device features when create. In case of no special device features to be enabled, then specify default initialized of such structure of nullptr
.VkDeviceCreateInfo
- create info for logical device
.pQueueCreateInfos
- set them from what we have.pEnableFeatures
- device features to be enabled.enabledExtensionCount
- number of device extensions to enable.ppEnabledExtensionNames
- device extension names to enable.enabledLayerCount
and .ppEnabledLayerNames
are deprecated and ignoredvkCreateDevice
- to create logical devicevkGetDeviceQueue()
Relationship for its underlying components
VkFramebuffer
-> VkImageView
-> VkImage
VkSurfaceFormatKHR
- choose desire surface format (from SwapChainSupportDetails
)VkPresentModeKHR
- choose desire presentation mode (from SwapChainSupportDetails
)VkExtent2D
- choose desire swap extent (from SwapChainSupportDetails
)VkSwapchainCreateInfoKHR
- our main swapchain object
.minImageCount = 3
- it is usually 2 or 3 for double buffering or tripple buffering respectively. See here for difference between double and tripple buffering..imageFormat
and .imageColorSpace
- set them properly.imageSharingMode
- it can be either exclusive or sharing depending on whether graphics and presentation queues we got are the same, if so they should be exclusive..queueFamilyIndexCount
- it should be number of queues we got (all types) if each queue we got is independent thus not the same, only taken into account when .imageSharingMode
is concurrentvkCreateSwapchainKHR
- to create a swapchainvkGetSwapchainImagesKHR
then cache them for later useSwapChainSupportDetails
structure.VkImageViewCreateInfo
- create info structure to create imageView (repeat for number of swapchain’s images)
.viewType
- mostly it would be VK_IMAGE_VIEW_TYPE_2D
.components
.subresourceRange
. It is able to automatically set the remaining of mipmap levels to all remaining levels - per se max out as it can be via VK_REMAINING_MIP_LEVELS
and VK_REMAINING_ARRAY_LAYERS
.VkAttachmentDescription
.loadOp
, .storeOp
, .stencilLoadOp
, and .stencilStoreOp
..initialLayout
, and .finalLayout
..samples
.VkAttachmentReference
VkSubpassDescription
VkSubpassDependency
- define dependency, operation requirement for source/destination, specify source/destination stageVkRenderPassCreateInfo
- use info from VkAttachmentReference
, VkSubpassDescription
, and VkSubpassDependency
vkCreateRenderPass
- use info from VkRenderPassCreateInfo
VkShaderModuleCreateInfo
, and VkShaderModule
- structure used to hold shader modulevkCreateShaderModule
- call to create a shader moduleVkPipelineShaderStageCreateInfo
for both vertex and fragment shader stage (VK_SHADER_STAGE_VERTEX_BIT
, and VK_SHADER_STAGE_FRAGMENT_BIT
)VkPipelineVertexInputStateCreateInfo
VkVertexInputBindingDescription
- specify binding number, stride size, and input rate whether it’s vertex index or instance index (instance rendering).VkVertexInputAttributeDescription
- specify format of vertex attributesVkPipelineInputAssemblyStateCreateInfo
- for topology i.e. triangle list, triangle fan, etc, as well as whether or not to enable indexed drawing via .primitiveRestartEnable
VkViewport
, VkRect2D
, VkPipelineViewportStateCreateInfo
VkPipelineRasterizationCreateInfo
VkPipelineMultisampleStateCreateInfo
VkPipelineColorBlendAttachmentState
- blending state i.e. src/dst blend factor, src/dst alpha blend factor. Disable/Enable via .blendEnable
.VkPipelineColorBlendStateCreateInfo
- blending state to surface. Disable/Enable via .logicOpEnable
.VkDynamicState
- for dynamic states which can be set in runtime preventing re-creation of related graphics pipepline objects from scratch i.e. VK_DYNAMIC_STATE_VIEWPORT
, or VK_DYNAMIC_STATE_LINE_WIDTH
. (not use at the moment)VkPipelineDynamicStateCreateInfo
(not use at the moment)VkPipelineLayoutCreateInfo
vkCreatePipelineLayout
- create pipeline layout firstVkGraphicsPipelineCreateInfo
- use created structures to set its fieldsvkCreateGraphicsPipelines
- able to create multiple graphics pipelines at the same timevkDestroyShaderModule
- as we finish using it, it’s safe to destroyVkFramebufferCreateInfo
- use VkImageView
for attachmentsvkCreateFramebuffer
VkCommandPoolCreateInfo
with selected queue family index set to its .queueFamilyIndex
vkCreateCommandPool
VkCommandBufferAllocateInfo
, and call vkAllocateCommandBuffers
.level
set to VK_COMMAND_BUFFER_LEVEL_PRIMARY
.commandPool
set to command pool we’ve createdVkCommandBufferBeginInfo
vkBeginCommandBuffers
- begin recording the command bufferVkRenderPassBeginInfo
VkClearValue
- set its to a desire clearing color. It is able to set to multiple colors for multiple target of attachments of a framebuffervkCmdBeginRenderPass
- if it’s a primary command buffer then use VK_SUBPASS_CONTENTS_INLINE
for contents
parameter, if you use execute command via its secondary command buffer then use VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS
. This will begin the render passvkCmdBindPipeline
- bind a pipeline at specific binding point either compute, graphics or ray-tracing operationsvkCmdDraw
- actual drawing commandvkCmdEndRenderPass
- end the render passvkEndCommandBuffer
- end the recording of a command bufferThis depends on your synchronizing logic, but at the baseline with good performance, it’s good to follow along with synchronizing approach used in GL_vs_VK - Test1.
As such, the number of semaphores (VkSemaphore
) is 2 x number of swapchain’s images in which in this case we have 4 for double buffering (2 images), or 6 for tripple buffering (3 images)
As well, the number of fence (VkFence
) is just number of swapchain’s images.
VkFence
, or VkSemaphore
) to have size of swapchain’s imagesVkSemaphoreCreateInfo
, and VkFenceCreateInfo
for create info for VkSemaphore
and VkFence
respectively.vkCreateSemaphore
, and vkCreateFence
to create semaphore and fence respectively.Mostly the vector arrays used to hold to-be-created structures across the code base will have the size of swapchain’s images.
Actually we can interchangeably say the size of swapchain’s framebuffers, or imageviews, or images. They are all related deep down to the size of images. All higher-up structures are created on top of the size of images.
Get the semaphore index before the call to vkAcquireNextImageKHR
.
Note: that there are 2 index-group. Semaphore uses different set of indexes as used by a group of fences, command buffers and presentable images.
vkAcquireNextImageKHR
to get next available image index.
Note: that such returned image index can still be in presenting state and can be in any order, that is why we need image in-flight state thus each uses different index set.
VkSubmitInfo
- create a submit info
.pWaitSemaphore
- set to image_available_semaphores[curr_semaphore_index]
.pWaitDstStageMask
- set to VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
..pCommandBuffers
- set to VkCommandBuffer
as created once (recorded should be better term) at startup.pSignalSemaphores
- set to render_finished_semaphores[curr_semaphore_index]
vkQueueSubmit
- submit the queue
images_inflight[image_index]
wheres image_index
is index returned from vkAcquireNextImageKHR
. This will signal such fence whenever all command buffers completely executed.VkPresentInfoKHR
.pWaitSemaphores
- set to render_finished_semaphore[curr_semaphore_index]
to wait a semaphore until it is completely rendered first.pImageIndices
- set to &image_index
as acquired from vkAcquireNextImageKHR
.pResults
- set to nullptr
in case we has no need to know the result of presenting, or non-null otherwise.vkQueuePresentKHR
- to finally present
vkDestroySemaphore
, and vkDestroyFence
- Destroy all semaphores, and fencesvkDestroyCommandPool
- Destroy all command pools (implicitly destroy command buffers without explicitly call to destroy command buffers via vkDestroyCommandBuffers
)vkDestroyFramebuffer
- Destroy framebuffersvkDestoryPipeline
- Destroy pipelinevkDestroyPipelineLayout
- Destroy pipeline layoutvkDestroyImageView
- Destroy imageViewvkDestroySwapchainKHR
- Destroy swapchainvkDestroyDevice
- Destroy logical deviceDestroyDebugUtilsMessengerEXT
which wraps the call to get actual function address to vkDestroyDebugUtilsMessengerEXT
via the call to vkGetInstanceProcAddr
.vkDestroySurfaceKHR
- Destroy surfacevkDestroyInstance
- Destroy instanceglfwDestroyWindow
and glfwTerminate
- Destroy window and terminate. If you use other library, then the call will be different.SwapChainSupportDetails
structureIt consists of cached surface’s capabilities, formats, and presentation mode. If has the following fields
VkSurfaceCapabilitiesKHR capabilities;
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
findQueueFamilies(VkPhysicalDevice device)
vkGetPhysicalDeviceQueueFamilyProperties
vkGetPhysicalDeviceSurfaceSupportKHR
- to check whether a surface supports presentationquerySwapChainSupport(VkPhysicalDevice device)
SwapChainSupportDetails
for its field by field in which you have to calls the following
vkGetPhysicalDeviceSurfaceCapabilitiesKHR
- to get capabilitiesvkGetPhysicalDeviceSurfaceFormatsKHR
- to get surface formats, called twice, once for getting count and another to fill the datavkGetPhysicalDeviceSurfacePresentModesKHR
- to get presentation mode, called twice, once for getting count and another to fill the data
First published on April, 18, 2020
Written by Wasin Thonkaew
In case of reprinting, comments, suggestions
or to do anything with the article in which you are unsure of, please
write e-mail to wasin[add]wasin[dot]io
Copyright © 2019-2021 Wasin Thonkaew. All Rights Reserved.