Vulkan. Developer Guide

I work as a technical translator for the Izhevsk IT company CG Tribe, which invited me to contribute to the community and start publishing translations of interesting articles and guides.



This is where I will post a translation of the Vulkan API manual. Source link - vulkan-tutorial.com . Since another Habr user, kiwhy (https://habr.com/ru/users/kiwhy/), is engaged in the translation of the same manual, we agreed to

share the lessons among ourselves. In my publications, I will provide links to chapters translated by kiwhy.



Content
1.



2.



3.



4.



  1. (pipeline)


5.



  1. Staging


6. Uniform-



  1. layout
  2. sets


7.



  1. Image view image sampler
  2. image sampler


8.



9.



10. -



11. Multisampling



FAQ







1. Introduction



See the article by the author kiwhy - habr.com/ru/post/462137



2. Overview



Background of Vulkan



How to draw a triangle?



  1. Step 1 - Instance and Physical Devices
  2. Step 2 - Logic Unit and Queue Families
  3. Step 3 - Window surface and swap chains
  4. Step 4 - Image views and framebuffers
  5. Step 5 - Render Passes
  6. Step 6 - The Graphics Pipeline
  7. Step 7 - Command Pool and Command Buffers
  8. Step 8 - main loop
  9. conclusions


API concepts



  1. Code formatting standard
  2. Validation layers


In this chapter, we'll get started with Vulkan and see what problems it can solve. We will describe the steps required to create your first triangle. This will give you an overview of the standard and allow you to understand the logic behind the layout of subsequent chapters. We conclude with a look at the Vulkan API structure and typical use cases.



Prerequisites for Vulkan



Like previous graphics APIs, Vulkan is conceived as a cross-platform abstraction over the GPU . The main problem with most of these APIs is that during their development they used graphics hardware that was limited to fixed functionality. The developers had to provide vertex data in a standard format and were completely dependent on the GPU manufacturers for lighting and shadows.



As the architecture of video cards developed, more and more programmable functions began to appear in it. All new features needed to be combined with existing APIs in some way. This led to imperfect abstractions and many hypotheses on the part of the graphics driver about how to translate the programmer's intention into modern graphics architectures. Therefore, a large number of driver updates are released to improve performance in games. Due to the complexity of such drivers, there are often discrepancies among vendors, for example, in the syntax adopted for shaders... Apart from this, the last decade has also seen an influx of mobile devices with powerful graphics hardware. The architectures of these mobile GPUs can vary greatly depending on size and power requirements. One such example is tiled rendering , which can provide better performance through better control over functionality. Another limitation due to the age of the API is the limited support for multithreading, which can lead to a bottleneck on the CPU side.



Vulkan helps solve these problems because it was built from the ground up for modern graphics architectures. This reduces driver-side overhead by allowing developers to clearly describe their goals using a verbose API. Vulkan allows you to create and send commands in parallel in multiple threads. It also reduces compilation discrepancies between shaders by moving to a standardized bytecode format and using a single compiler. Finally, Vulkan brings the core capabilities of today's graphics cards together by integrating graphics and computing capabilities into a single API.



How to draw a triangle?



We'll take a quick look at the steps required to draw a triangle. This will give you an overview of the process. A detailed description of each concept will be given in the following chapters.



Step 1 - Instance and Physical Devices



Working with Vulkan begins by configuring the Vulkan API through VkInstance (instance). An instance is created using your program description and any extensions you want to use. After instantiation, you can query which hardware Vulkan supports and select one or more VkPhysicalDevices to perform operations. You can inquire about parameters such as VRAM size and device capabilities to select the devices you want if you prefer to use specialized graphics cards.



Step 2 - Logic Device and Queue Families



After you select the appropriate hardware device to use, you need to create a VkDevice (logical device), where you will describe in more detail what features ( VkPhysicalDeviceFeatures ) you will use, for example, rendering to multiple viewports. s (multi viewport rendering) and 64-bit floats. You also need to establish which queue families you would like to use. Many of the operations done with Vulkan, such as drawing commands and in-memory operations, are performed asynchronously after being sent to VkQueue... Queues are allocated from a family of queues, where each family supports a specific set of operations. For example, separate families of queues may exist for graphics operations, computation operations, and memory data transfers. In addition, their availability can be used as a key parameter when choosing a physical device. Some Vulkan-enabled devices do not offer any graphics capabilities, however, all modern Vulkan-enabled graphics cards generally support all the queuing operations we need.



Step 3 - Window surface and swap chains



If you are interested in more than just offscreen rendering, you need to create a window to display the rendered images. Windows can be created using native platform APIs or libraries such as GLFW and SDL . We'll be using GLFW for this tutorial, which we'll cover in more detail in the next chapter.



We need two more components to render to the application window: the window surface ( VkSurfaceKHR) and the show chain ( VkSwapchainKHR). Pay attention to the postfixKHRwhich denotes that these objects are part of the Vulkan extension. The Vulkan API is completely platform independent, so we need to use a standardized WSI (Window System Interface) extension to interact with the window manager. Surface is a cross-platform window abstraction for rendering that is typically created by referencing a native window handle, such HWNDas on Windows. Fortunately, the GLFW library has a built-in function for working with platform specific details.



A show chain is a set of render targets. Its task is to ensure that the image that is currently being rendered differs from the one displayed on the screen. This allows you to keep track of that only rendered images are displayed. Every time we need to create a frame, we have to make a request for the show chain to provide us with an image to render. After the frame is created, the image is returned to the display chain to be displayed on the screen at some point. The number of rendering targets and conditions for displaying finished images on the screen depends on the current mode. These modes include double buffering (vsync) and triple buffering. We'll cover them in the chapter on creating a show chain.



Some platforms allow rendering directly to the screen through extensions VK_KHR_displayand VK_KHR_display_swapchainwithout interacting with any window manager. This allows you to create a surface that represents the entire screen and can be used, for example, to implement your own window manager.



Step 4 - Image views and framebuffers



In order to draw into the image obtained from the display chain, we have to wrap it in a VkImageView and VkFramebuffer . Image view refers to a specific part of the image being used, and the framebuffer refers to image views, which are used as color, depth and stencil buffers. Since there can be many different images in the display chain, we will create an image view and a framebuffer for each of them in advance and select the required image during drawing.



Step 5 - Render



Passes Vulkan's render passes describe the type of images used during render operations, how they are used, and how their content should be handled. Before drawing the triangle, we tell Vulkan that we want to use a single image as a color buffer and that we need to clear it before drawing. If the render pass only describes the type of images used as buffers, then VkFramebuffer actually associates specific images with those slots.



Step 6 - Graphics Pipeline The



graphics pipeline in Vulkan is configured by creating a VkPipeline object . It describes the configurable state of the video card, such as viewport size or depth buffer operation, as well as programmable state using VkShaderModule objects . VkShaderModule objects are created from shader bytecode . The driver also needs to specify which render targets will be used in the pipeline. We set them by referencing the render pass.



One of the most distinguishing features of Vulkan compared to existing APIs is that almost all system graphics pipeline settings must be pre-configured. This means that if you want to switch to a different shader or slightly change the vertex layout, you need to completely re-create the graphics pipeline. Therefore, you will have to create many VkPipeline objects in advance for all the combinations required for the rendering operations. Only some basic settings, such as viewport size and clear color, can be dynamically changed. All states must be explicitly described. So, for example, there is no default color blend state.



Fortunately, because the process is more like compilation ahead, instead of compiling on the fly, the driver has more optimization opportunities and performance is more predictable because significant state changes, such as switching to a different graphics pipeline, are explicitly specified.



Step 7 - Command Pool and Command Buffers



As mentioned, many operations in Vulkan, such as drawing operations, must be queued. Before sending operations, they must be written to the VkCommandBuffer . Command buffers come from the VkCommandPool , which is associated with a specific queue family. To draw a simple triangle, we need to write a command buffer with the following operations:



  • Start render pass
  • Bind graphics pipeline
  • Draw 3 vertices
  • End render pass


Since the instance of the image in the framebuffer depends on which image the display chain will give us, we need to write a command buffer for each possible image and select the one we need during drawing. We can write the command buffer every time for every frame, but this is less efficient.



Step 8 - Main Loop



After we have sent the drawing commands to the command buffer, the main loop seems simple enough. First, we get the image from the show chain with vkAcquireNextImageKHR. We can then select the appropriate command buffer for this image and run it with vkQueueSubmit . Finally, we return the image to the display chain for display using vkQueuePresentKHR.



Operations sent to the queue are performed asynchronously. Therefore, we must use synchronization objects - semaphores - to ensure the correct startup order. It is necessary to configure the execution of the drawing command buffer in such a way that it is carried out only after the image has been fetched from the display chain, otherwise a situation may arise when we start rendering an image that is still being read for display on the screen. The call vkQueuePresentKHR, in turn, must wait for the rendering to complete, for which we will use the second semaphore. It will notify about the end of rendering.



Conclusions



This quick overview gives you an overview of the work ahead of drawing your first triangle. In reality, there are many more steps. These include allocating vertex buffers, creating uniform buffers, and loading texture images - all of which we'll cover in the next chapters, but for now, let's start simple. The further we move, the more difficult the material will be. Note that we decided to go the tricky way by initially embedding the vertex coordinates in the vertex shader instead of using the vertex buffer. This decision is due to the fact that in order to manage the vertex buffers, you first need to be familiar with the command buffers.



Let's briefly summarize. To draw the first triangle, we need:



  • Create VkInstance
  • Select a supported video card ( VkPhysicalDevice )
  • Create VkDevice and VkQueue for drawing and display
  • Create window, window surface and show chain
  • Wrap display chain images in VkImageView
  • Create a render pass that defines the render targets and their usage
  • Create framebuffer for render pass
  • Configure the graphics pipeline
  • Distribute and write drawing commands to the buffer for each image in the display chain
  • Render frames to received images by sending the correct command buffer and returning the images back to the display chain


Despite the fact that there are many steps, the meaning of each of them will be clear in the following chapters. If you cannot figure out a step, return to this chapter.



API concepts



This chapter will conclude with a brief overview of how Vulkan APIs are structured at a lower level.



Coding Standard



All Vulkan functions, enumerations and structures are labeled under a heading vulkan.hthat is included in the Vulkan SDK developed by LunarG. Installing the SDK will be covered in the next chapter.



Functions are prefixed vkin lowercase, enumerated types (enum) and structures are prefixed Vk, and enumerated values ​​are prefixed VK_. The API makes extensive use of structures to provide parameters to functions. For example, objects are usually created according to the following pattern:



image



Many structures in Vulkan require you to explicitly specify the structure type in the member sType. A member pNextcan point to an extension structure and will always be of typenullptr... Functions that create or destroy an object will have a VkAllocationCallbacks parameter , which allows you to use your own memory allocator and which in the manual will also have a type nullptr.



Almost all functions return VkResult , which is VK_SUCCESSeither an error code or an error code. The specification states which error codes each function can return and what they mean.



Validation Layers



As mentioned, Vulkan was designed to provide high performance with low driver loads. Therefore, it includes very limited automatic error detection and correction capabilities. If you make a mistake, the driver will crash or worse, continue to work on your graphics card, but fail on other graphics cards.



Therefore Vulkan allows you to run advanced validations using a feature known as validation layers... Validation layers are pieces of code that can be inserted between the API and the graphics driver to perform additional validation on function parameters and track memory management issues. This is convenient in that you can start them during development and then completely disable them when starting the program at no additional cost. Anyone can write their own validation layers, but LunarG's Vulkan SDK provides a standard set that we'll use throughout the tutorial. You also need to register a callback function to receive debug messages from layers.



Since the operations in Vulkan are very detailed, and the validation layers are quite extensive, it will be much easier for you to determine the cause of the black screen compared to OpenGL and Direct3D.



There is only one step left before we start coding, and that is setting up the development environment.



All Articles