The Big List of Vulkan concepts

A preliminary knowledge section outlining all the terminology used in Vulkan and what their functions are.

Rather than going straight into code examples and structure, it is best to explain the underlying concepts in Vulkan and their place in the overall API system.

This section will outline the various concepts and terms used throughout this tutorial, while also serving as a flowchart of sorts to help developers understand exactly what steps are being taken to go from code to on-screen render.

The following diagram shows the logical datapath of each frame being drawn in the Vulkan system. This diagram will be referred to during each stage of the rendering process. The individual sections will be outlined below.

However, before any of these can be converted to code, a lot of initialisation needs to happen first.

Images and Views

Vulkan has a concept of images and image views. An image view describes how to access the image as which parts of the image to access. This has specific applications depending on what the image is being used for. As an example, an image that is treated as a 2D texture with mipmap levels will have a different image view to the final scene being rendered. Specifically, images and buffers are not interchangable; while an image is technically a buffer by way of being an accessible block of raw memory, it has a very specific usecase which is immutable. The image view acts as an intermediary between the image and the frambuffer, and their main usage comes in the sampling and binding of textures.

The Swapchain

Like in many other graphics APIs, Vulkan uses framebuffers (effectively blocks of memory from where the screen reads the final image) in order to be able to present data to the screen. Like all other graphics APIs, Vulkan uses multiple buffers so that while one buffer is being rendered to, the other can be presented to the display. Vulkan handles this using an object called the swapchain.

Unlike in OpenGL which abstracts multiple buffer rendering behind a single "default" framebuffer representing the entire swapchain, in Vulkan, each image is represented as its own framebuffer in code. The swapchain is composed of multiple images that are treated as buffers that the application renders to and presents from.

The swapchain is a part of the application, but can be thought of as a way for the application to get access to images for it to modify and render into, while also allowing the presentation engine to access the presentable images that have already been rendered.

Surfaces

In short, a surface is an application-level abstraction of the actual native window; that is, the actual OS-specific part of the screen that is being rendered into, such as the window in a windowing system, or the entire screen for an application running in fullscreen mode. It is the presentation target for images in the swapchain, which act as handles for the surface. Different platforms require their surfaces created and initialised in different ways.

Pipelines

In order to process data and render to the swapchain images, most applications go through the graphics pipeline. These are the stages that operate on the buffers to write data to a swapchain image.

A simplified outline of an example graphics pipeline is as below:

  1. Vertex Input
  2. Vertex Shader
  3. Rasterization
  4. Fragment Shader
  5. Colour Blending
  6. Frame buffer output

In Vulkan, there is an object which is referred to as a VkPipeline. This may end up causing some confusion for those who have a brief knowledge of graphics programming, so the distinction will be drawn here.

A VkPipeline is an object whose job is to configure the GPU for the type of work being done. There are two flavours of a VkPipeline: graphics and compute. From this point onwards, when referring to a VkPipeline object, we will use Pipeline with the capitalisation. Thus, the sentence: "A graphics Pipeline is the configuration of the graphics pipeline" should make significantly more sense.

A graphics pipeline is made up of multiple stages of two types: fixed function and programmable.

A basic diagram of this pipeline as part of the overall application is shown at the bottom of this page.

The Vulkan Queues

GPUs are asynchronous devices designed specifically for highly parallel workloads commonly found in graphics. The hardware is optimised for functions relevant to the mathematics involved in graphics and rendering. Vulkan works on a system of commands that the application submits to the GPU. These commands can vary in their nature, from setting up memory to compute calculations, and more.

Commands are stored within command buffers, and these commands are submitted to a queue, where the driver sends these on to the GPU to actually perform work.

The GPU will execute these commands in whatever order is most efficient, meaning there are no implicit or explicit agreements on what order the tasks will be completed. While this does allow for significant performance optimisations, it does require the developer to do more work in the form of synchronisation (which other APIs may handle automatically), but this will be elaborated on later.

The queue families available to an application depend on the physical device that the application is running on. There are a number of different queue families, but in the case of this simple application, only the graphics queue needs to be used. However, it is good to be aware of others.

Command Buffers

There is a translation from Vulkan API calls to the work that gets sent to the GPU to do. The application must record commands to a buffer which are then queued up for execution.

The command buffer needs to have its state specified by the Pipeline configuration before any commands that would by affected are run. It is possible to bind different Pipeline objects in between, but this is all the responsibility of the developer.

Command buffers are allocated from a memory pool called a command pool. Command pools are linked to a specific queue family, and so a command buffer allocated from a specific command pool can only be submitted to queues from that queue family.

Render Pass

A render pass is what is used to refer to a "chunk of work" that is needed to render data into a specific framebuffer from start to finish. In a practical sense, it is everything that is contained within a command buffer from the vkCmdBeginRenderPass and vkCmdEndRenderPass. As can be seen in the code, there is quite a lot that needs to be configured within that pass, such as loading and storing data. There is a separate VkRenderPass object which contains the information or "blueprint" for a Framebuffer object. In this sense, one Renderpass can yield multiple Framebuffers. The importance of this is that compatible VkPipeline objects can be used with these Renderpasses to render into any valid Framebuffer. The Pipeline requires information about which render pass object it is compatible with on creation. This is so that each stage in the pipeline is aware of what data the framebuffer requires and what already exists.

Attachments are part of the Framebuffer, but their descriptors are relevant for the render pass. Attachments include Color, Depth, Stencil, Input, and Resolve. These values from the framebuffer pixels are written into the relevant attachments. These same images can be bound as Input attachments and accessed as input values by other render passes and subpasses.

Synchronisation

Because Vulkan is a very low-level API, synchronisation is left almost entirely up to the developer. While this helps boost performance, it can be very daunting for those who do not have much experience in managing their application's synchronisation at this granularity. The main reason Vulkan does not guarantee much order in command buffer execution is due to the need to achieve as much parallelism as possible. Vulkan's fine-grained synchronisation mechanisms can allow a well-designed application to achieve maximum performance from a GPU by considering their parallel architecture.

Generally, most Vulkan operations can overlap, but there are some order-sensitive actions; acquiring an image from the swapchain, rendering to that image, and then presenting that image back to the surface is something that needs to occur in that exact order.

Vulkan provides four types of synchronisation objects to the developer: fences, semaphores, and events. These will each be described in more detail in Creating the Synchronisation Objects.