Learning the fundamentals of Vulkan
This section will cover the basics of Vulkan. Here we will discuss the following:
- Vulkan's execution model
- Vulkan's queue
- The object model
- Object life-time and command syntax
- Error checking and validation
Vulkan's execution model
A Vulkan-capable system is able to query the system and expose the number of physical devices available on it. Each of the physical devices advertises one or more queues. These queues are categorized into different families, where each family has very specific functionalities. For example, these functionalities could include graphics, compute, data transfer, and sparse memory management. Each member of the queue family may contain one or more similar queues, making them compatible with each other. For example, a given implementation may support data transfer and graphics operations on the same queue.
Vulkan allows you to explicitly manage memory control via the application. It exposes the various types of heap available on the device, where each heap belongs to a different memory region. Vulkan's execution model is fairly simple and straightforward. Here, command buffers are submitted into queues, which are then consumed by the physical device in order to be processed.
A Vulkan application is responsible for controlling a set of Vulkan-capable devices by recording a number of commands into command buffers and submitting them into a queue. This queue is read by the driver that executes the jobs upfront in the submitted order. The command buffer construction is expensive; therefore, once constructed, it can be cached and submitted to the queue for execution as many times as required. Further, several command buffers can be built simultaneously in parallel using multiple threads in an application.
The following diagram shows a simplified pictorial representation of the execution model:
In this, the application records two command buffers containing several commands. These commands are then given to one or more queues depending upon the job nature. The queues submit these command buffer jobs to the device for processing. Finally, the device processes the results and either displays them on the output display or returns them to the application for further processing.
In Vulkan, the application is responsible for the following:
- Producing all the necessary prerequisites for the successful execution of commands:
- This may include preparing resources, precompiling a shader, and attaching the resources to the shader; specifying the render states; building a pipeline; and drawing calls
- Memory management
- Synchronization
- Between the host and device
- Between the different queues available on the device
- Hazard management
Vulkan's queues
Queues are the medium in Vulkan through which command buffers are fed into the device. The command buffers record one or more commands and submit them to the required queue. The device may expose multiple queues; therefore, it is the application's responsibility to submit the command buffer to the correct queue.
The command buffers can be submitted to the following:
- Single queue:
- The order of the submission of the command buffer and execution or playback are maintained
- Command buffers are executed in a serial fashion
- Multiple queues:
- Allows the execution of the command buffer in parallel in two or more queues.
- The order of the submission and execution of command buffers are not guaranteed unless specified explicitly. It is the application's responsibility to synchronize this; in its absence, the execution may be completely out of order with respect.
Vulkan provides various synchronization primitives to allow you to have relative control of the work execution within a single queue or across queues. These are as follows:
- Semaphore: This synchronizes work across multiple queues or a coarse-grained command buffer submission in a single queue.
- Events: Events controls fine-grained synchronization and are applied on a single queue, allowing us to synchronize work within a single command buffer or sequence of command buffers submitted to a single queue. The host can also participate in event-based synchronization.
- Fences: These allow synchronization between the host and device.
- Pipeline barriers: A pipeline barrier is an inserted instruction that ensures that commands prior to it must be executed before commands specified after it in the command buffer.
The object model
At the application level, all the entities, including devices, queues, command buffers, framebuffers, pipelines, and so on, are called Vulkan objects. Internally, at the API level, these Vulkan objects are recognized with handles. These handles can be of two types: dispatchable and non-dispatchable.
- A dispatchable handle: This is a pointer that refers to an opaque-shaped entity inside. Opaque types do not allow you to have direct access to the structure's field. The fields can only be accessed using API routines. Each dispatchable handle has an associated dispatchable type that is used to pass as a parameter in the API command. Here's an example of this:
|
|
|
|
|
- Non-dispatchable handles: These are 64-bit integer-type handles that may contain the object information itself, rather than a pointer to the structure. Here's an example of this:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Object lifetime and command syntax
In Vulkan, objects are created and destroyed explicitly as per application logic, and it is the responsibility of an application to manage this.
Objects in Vulkan are created using Create
and destroyed using the Destroy
command:
- Create syntax: Objects are created using the
vkCreate*
command; this accepts aVk*CreateInfo
structure as a parameter input - Destroy syntax: The objects produced using the
Create
command are destroyed usingvkDestroy*
Objects created as part of the existing object pool or heap are created using the Allocate
command and released from the pool or heap with Free
.
- Allocate syntax: Objects that are created as part of an object pool use
vkAllocate*
along withVk*AllocateInfo
as an argument input. - Freeing syntax: Objects are released back to the pool or memory using the
vk
Free*
command.
Any given implementation information can be easily accessed using the vkGet*
command. The API implementation of the form vkCmd*
is used to record commands in the command buffer.
Error checking and validation
Vulkan is specially designed to offer maximum performance by keeping error checks and validations optional. At runtime, the error checks and validations are really minimal, making the building of a command buffer and submission highly efficient. These optional capabilities can be enabled using Vulkan's layered architecture, which allows the dynamic injection of various layers (debugging and validation) into the running system.