Vulkan

Publish Date

This post serves as my notes for my journey on Vulkan development. Several years ago, I followed the guide on vulkan-tutorial.com and built a rendering engine. While it yielded success, I didn't learn from it about the architecture of Vulkan since I had a hard time keeping up with the pace of it. Last Monday, I stumbled upon vkguide.dev, which provides me with a warm welcome to Vulkan and a means of getting my hands wet. The thing about this tutorial is that it is a revised version of the original vulkan-tutorial, as its predecessor was written when developers first learned about using it. I hope that I can fully tap into the immense knowledge provided in this guide.

Specifications

Resources

Concepts

Physical vs Logical Devices

In Vulkan, many objects are handles pointing (yes, pointers) to something that already exists. These handles specifically used to point to physical components on your computer are called physical devices. Now, when we want to interact with the components, we need a logical device, instantiated by feeding vkCreateDevice with our physical device (think about it as a description) and other information.

Sync Structures

Modern computer graphics is usually married with parallel computing. For instance, GPUs are the physical representation of parallel computing/processing, since they can simultaneously process data on different cores (see SIMD on Wikipedia); meanwhile, CPUs can also have multiple threads running on multiple physical cores.

As beneficial as parallel computing seems, it raises some looming problems caused by desynchronization -- our commands (on the GPU after being submitted to VkQueue) need to wait for the swapchain to yield an image so that it can compute/render it. How do we know if it's ready? Enter VkSemaphore, which is a way to make sure GPU commands can be sync-ed with each other. A semaphore is split into two types: wait semaphores and signal semaphores. There can only be one signal semaphores, while there's no uppper limit for the number of wait semaphores. Semaphores in Vulkan are used exclusively for GPU-to-GPU operations, so the developers at Khronos Group come up with VkFence for GPU-to-CPU operations, which works identically1.

Command Execution Process

The way Vulkan handles commands sent to a GPU is by using VkQueue structs. A sequence of command is represented by VkCommandBuffer, the allocation of which is done by allocators of VkCommandPool. A VkQueue is a handle pointing to an existing structure of your system, similar to VkPhysicalDevice. Remember there can be conflicts between different queues (such as graphics queue versus compute queue), so sync structures come in handy.

The above image is sourced from vkguide.dev. Note that renderpass is not commonly used in newer versions of Vulkan. Instead, we use vkBeginCommandBuffer.

Images

Images in Vulkan are represented as VkImage. These opaque handles are best accessed via image views. By default, images sent to/from GPUs (swapchain images) aren't readable by us, since they are in the optimal format for GPU access. To write to our images, we need to change their format, using our function vkutil::transition_image. Nonetheless, it isn't a good idea to write to swapchain images directly since we have to create a new image should any properties of them change on the fly; instead, we should write to another independent image, and blit (BITBLT) it to our swapchain images, hence the need of vkutil::copy_image_to_image.

Descriptor Sets

A descriptor is a handle/pointer that accesses specific Vulkan structures. Take a buffer descriptor as an example. We can use it to access command buffers. To allocate descriptors, we have to initialize them in batch. The cluster of them is called a descriptor set which can be instantiated with VkDescriptorPool following the instructions of VkDescriptorSetLayout. For shaders to read data, we must bind descriptors to our rendering pipelines.

Libraries

WIP... (◉ 3 ◉)

Edit Logs

Footnotes

  1. I might have to research about this later.