|
| 1 | +--- |
| 2 | +title: 内容概览 |
| 3 | +--- |
| 4 | + |
| 5 | +This chapter will start off with an introduction of Vulkan and the problems |
| 6 | +it addresses. After that we're going to look at the ingredients that are |
| 7 | +required for the first triangle. This will give you a big picture to place each |
| 8 | +of the subsequent chapters in. We will conclude by covering the structure of the |
| 9 | +Vulkan API and the general usage patterns. |
| 10 | + |
| 11 | +## Origin of Vulkan |
| 12 | + |
| 13 | +Just like the previous graphics APIs, Vulkan is designed as a cross-platform |
| 14 | +abstraction over [GPUs](https://en.wikipedia.org/wiki/Graphics_processing_unit). |
| 15 | +The problem with most of these APIs is that the era in which they were designed |
| 16 | +featured graphics hardware that was mostly limited to configurable fixed |
| 17 | +functionality. Programmers had to provide the vertex data in a standard format |
| 18 | +and were at the mercy of the GPU manufacturers with regards to lighting and |
| 19 | +shading options. |
| 20 | + |
| 21 | +As graphics card architectures matured, they started offering more and more |
| 22 | +programmable functionality. All this new functionality had to be integrated with |
| 23 | +the existing APIs somehow. This resulted in less than ideal abstractions and a |
| 24 | +lot of guesswork on the graphics driver side to map the programmer's intent to |
| 25 | +the modern graphics architectures. That's why there are so many driver updates |
| 26 | +for improving the performance in games, sometimes by significant margins. |
| 27 | +Because of the complexity of these drivers, application developers also need to |
| 28 | +deal with inconsistencies between vendors, like the syntax that is accepted for |
| 29 | +[shaders](https://en.wikipedia.org/wiki/Shader). Aside from these new features, |
| 30 | +the past decade also saw an influx of mobile devices with powerful graphics |
| 31 | +hardware. These mobile GPUs have different architectures based on their energy |
| 32 | +and space requirements. One such example is [tiled rendering](https://en.wikipedia.org/wiki/Tiled_rendering), |
| 33 | +which would benefit from improved performance by offering the programmer more |
| 34 | +control over this functionality. Another limitation originating from the age of |
| 35 | +these APIs is limited multi-threading support, which can result in a bottleneck |
| 36 | +on the CPU side. |
| 37 | + |
| 38 | +Vulkan solves these problems by being designed from scratch for modern graphics |
| 39 | +architectures. It reduces driver overhead by allowing programmers to clearly |
| 40 | +specify their intent using a more verbose API, and allows multiple threads to |
| 41 | +create and submit commands in parallel. It reduces inconsistencies in shader |
| 42 | +compilation by switching to a standardized byte code format with a single |
| 43 | +compiler. Lastly, it acknowledges the general purpose processing capabilities of |
| 44 | +modern graphics cards by unifying the graphics and compute functionality into a |
| 45 | +single API. |
| 46 | + |
| 47 | +## What it takes to draw a triangle |
| 48 | + |
| 49 | +We'll now look at an overview of all the steps it takes to render a triangle in |
| 50 | +a well-behaved Vulkan program. All of the concepts introduced here will be |
| 51 | +elaborated on in the next chapters. This is just to give you a big picture to |
| 52 | +relate all of the individual components to. |
| 53 | + |
| 54 | +### Step 1 - Instance and physical device selection |
| 55 | + |
| 56 | +A Vulkan application starts by setting up the Vulkan API through a `VkInstance`. |
| 57 | +An instance is created by describing your application and any API extensions you |
| 58 | +will be using. After creating the instance, you can query for Vulkan supported |
| 59 | +hardware and select one or more `VkPhysicalDevice`s to use for operations. You |
| 60 | +can query for properties like VRAM size and device capabilities to select |
| 61 | +desired devices, for example to prefer using dedicated graphics cards. |
| 62 | + |
| 63 | +### Step 2 - Logical device and queue families |
| 64 | + |
| 65 | +After selecting the right hardware device to use, you need to create a VkDevice |
| 66 | +(logical device), where you describe more specifically which |
| 67 | +VkPhysicalDeviceFeatures you will be using, like multi viewport rendering and |
| 68 | +64 bit floats. You also need to specify which queue families you would like to |
| 69 | +use. Most operations performed with Vulkan, like draw commands and memory |
| 70 | +operations, are asynchronously executed by submitting them to a VkQueue. Queues |
| 71 | +are allocated from queue families, where each queue family supports a specific |
| 72 | +set of operations in its queues. For example, there could be separate queue |
| 73 | +families for graphics, compute and memory transfer operations. The availability |
| 74 | +of queue families could also be used as a distinguishing factor in physical |
| 75 | +device selection. It is possible for a device with Vulkan support to not offer |
| 76 | +any graphics functionality, however all graphics cards with Vulkan support today |
| 77 | +will generally support all queue operations that we're interested in. |
| 78 | + |
| 79 | +### Step 3 - Window surface and swap chain |
| 80 | + |
| 81 | +Unless you're only interested in offscreen rendering, you will need to create a |
| 82 | +window to present rendered images to. Windows can be created with the native |
| 83 | +platform APIs or libraries like [GLFW](http://www.glfw.org/) and [SDL](https://www.libsdl.org/). |
| 84 | +We will be using GLFW in this tutorial, but more about that in the next chapter. |
| 85 | + |
| 86 | +We need two more components to actually render to a window: a window surface |
| 87 | +(VkSurfaceKHR) and a swap chain (VkSwapchainKHR). Note the `KHR` postfix, which |
| 88 | +means that these objects are part of a Vulkan extension. The Vulkan API itself |
| 89 | +is completely platform agnostic, which is why we need to use the standardized |
| 90 | +WSI (Window System Interface) extension to interact with the window manager. The |
| 91 | +surface is a cross-platform abstraction over windows to render to and is |
| 92 | +generally instantiated by providing a reference to the native window handle, for |
| 93 | +example `HWND` on Windows. Luckily, the GLFW library has a built-in function to |
| 94 | +deal with the platform specific details of this. |
| 95 | + |
| 96 | +The swap chain is a collection of render targets. Its basic purpose is to ensure |
| 97 | +that the image that we're currently rendering to is different from the one that |
| 98 | +is currently on the screen. This is important to make sure that only complete |
| 99 | +images are shown. Every time we want to draw a frame we have to ask the swap |
| 100 | +chain to provide us with an image to render to. When we've finished drawing a |
| 101 | +frame, the image is returned to the swap chain for it to be presented to the |
| 102 | +screen at some point. The number of render targets and conditions for presenting |
| 103 | +finished images to the screen depends on the present mode. Common present modes |
| 104 | +are double buffering (vsync) and triple buffering. We'll look into these in the |
| 105 | +swap chain creation chapter. |
| 106 | + |
| 107 | +Some platforms allow you to render directly to a display without interacting with any window manager through the `VK_KHR_display` and `VK_KHR_display_swapchain` extensions. These allow you to create a surface that represents the entire screen and could be used to implement your own window manager, for example. |
| 108 | + |
| 109 | +### Step 4 - Image views and framebuffers |
| 110 | + |
| 111 | +To draw to an image acquired from the swap chain, we have to wrap it into a |
| 112 | +VkImageView and VkFramebuffer. An image view references a specific part of an |
| 113 | +image to be used, and a framebuffer references image views that are to be used |
| 114 | +for color, depth and stencil targets. Because there could be many different |
| 115 | +images in the swap chain, we'll preemptively create an image view and |
| 116 | +framebuffer for each of them and select the right one at draw time. |
| 117 | + |
| 118 | +### Step 5 - Render passes |
| 119 | + |
| 120 | +Render passes in Vulkan describe the type of images that are used during |
| 121 | +rendering operations, how they will be used, and how their contents should be |
| 122 | +treated. In our initial triangle rendering application, we'll tell Vulkan that |
| 123 | +we will use a single image as color target and that we want it to be cleared |
| 124 | +to a solid color right before the drawing operation. Whereas a render pass only |
| 125 | +describes the type of images, a VkFramebuffer actually binds specific images to |
| 126 | +these slots. |
| 127 | + |
| 128 | +### Step 6 - Graphics pipeline |
| 129 | + |
| 130 | +The graphics pipeline in Vulkan is set up by creating a VkPipeline object. It |
| 131 | +describes the configurable state of the graphics card, like the viewport size |
| 132 | +and depth buffer operation and the programmable state using VkShaderModule |
| 133 | +objects. The VkShaderModule objects are created from shader byte code. The |
| 134 | +driver also needs to know which render targets will be used in the pipeline, |
| 135 | +which we specify by referencing the render pass. |
| 136 | + |
| 137 | +One of the most distinctive features of Vulkan compared to existing APIs, is |
| 138 | +that almost all configuration of the graphics pipeline needs to be set in advance. |
| 139 | +That means that if you want to switch to a different shader or slightly |
| 140 | +change your vertex layout, then you need to entirely recreate the graphics |
| 141 | +pipeline. That means that you will have to create many VkPipeline objects in |
| 142 | +advance for all the different combinations you need for your rendering |
| 143 | +operations. Only some basic configuration, like viewport size and clear color, |
| 144 | +can be changed dynamically. All of the state also needs to be described |
| 145 | +explicitly, there is no default color blend state, for example. |
| 146 | + |
| 147 | +The good news is that because you're doing the equivalent of ahead-of-time |
| 148 | +compilation versus just-in-time compilation, there are more optimization |
| 149 | +opportunities for the driver and runtime performance is more predictable, |
| 150 | +because large state changes like switching to a different graphics pipeline are |
| 151 | +made very explicit. |
| 152 | + |
| 153 | +### Step 7 - Command pools and command buffers |
| 154 | + |
| 155 | +As mentioned earlier, many of the operations in Vulkan that we want to execute, |
| 156 | +like drawing operations, need to be submitted to a queue. These operations first |
| 157 | +need to be recorded into a VkCommandBuffer before they can be submitted. These |
| 158 | +command buffers are allocated from a `VkCommandPool` that is associated with a |
| 159 | +specific queue family. To draw a simple triangle, we need to record a command |
| 160 | +buffer with the following operations: |
| 161 | + |
| 162 | +* Begin the render pass |
| 163 | +* Bind the graphics pipeline |
| 164 | +* Draw 3 vertices |
| 165 | +* End the render pass |
| 166 | + |
| 167 | +Because the image in the framebuffer depends on which specific image the swap |
| 168 | +chain will give us, we need to record a command buffer for each possible image |
| 169 | +and select the right one at draw time. The alternative would be to record the |
| 170 | +command buffer again every frame, which is not as efficient. |
| 171 | + |
| 172 | +### Step 8 - Main loop |
| 173 | + |
| 174 | +Now that the drawing commands have been wrapped into a command buffer, the main |
| 175 | +loop is quite straightforward. We first acquire an image from the swap chain |
| 176 | +with vkAcquireNextImageKHR. We can then select the appropriate command buffer |
| 177 | +for that image and execute it with vkQueueSubmit. Finally, we return the image |
| 178 | +to the swap chain for presentation to the screen with vkQueuePresentKHR. |
| 179 | + |
| 180 | +Operations that are submitted to queues are executed asynchronously. Therefore |
| 181 | +we have to use synchronization objects like semaphores to ensure a correct |
| 182 | +order of execution. Execution of the draw command buffer must be set up to wait |
| 183 | +on image acquisition to finish, otherwise it may occur that we start rendering |
| 184 | +to an image that is still being read for presentation on the screen. The |
| 185 | +vkQueuePresentKHR call in turn needs to wait for rendering to be finished, for |
| 186 | +which we'll use a second semaphore that is signaled after rendering completes. |
| 187 | + |
| 188 | +### Summary |
| 189 | + |
| 190 | +This whirlwind tour should give you a basic understanding of the work ahead for |
| 191 | +drawing the first triangle. A real-world program contains more steps, like |
| 192 | +allocating vertex buffers, creating uniform buffers and uploading texture images |
| 193 | +that will be covered in subsequent chapters, but we'll start simple because |
| 194 | +Vulkan has enough of a steep learning curve as it is. Note that we'll cheat a |
| 195 | +bit by initially embedding the vertex coordinates in the vertex shader instead |
| 196 | +of using a vertex buffer. That's because managing vertex buffers requires some |
| 197 | +familiarity with command buffers first. |
| 198 | + |
| 199 | +So in short, to draw the first triangle we need to: |
| 200 | + |
| 201 | + * Create a VkInstance |
| 202 | +* Select a supported graphics card (VkPhysicalDevice) |
| 203 | +* Create a VkDevice and VkQueue for drawing and presentation |
| 204 | +* Create a window, window surface and swap chain |
| 205 | +* Wrap the swap chain images into VkImageView |
| 206 | +* Create a render pass that specifies the render targets and usage |
| 207 | +* Create framebuffers for the render pass |
| 208 | +* Set up the graphics pipeline |
| 209 | +* Allocate and record a command buffer with the draw commands for every possible |
| 210 | +swap chain image |
| 211 | +* Draw frames by acquiring images, submitting the right draw command buffer and |
| 212 | +returning the images back to the swap chain |
| 213 | + |
| 214 | +It's a lot of steps, but the purpose of each individual step will be made very |
| 215 | +simple and clear in the upcoming chapters. If you're confused about the relation |
| 216 | +of a single step compared to the whole program, you should refer back to this |
| 217 | +chapter. |
| 218 | + |
| 219 | +## API concepts |
| 220 | + |
| 221 | +This chapter will conclude with a short overview of how the Vulkan API is |
| 222 | +structured at a lower level. |
| 223 | + |
| 224 | +### Coding conventions |
| 225 | + |
| 226 | +All of the Vulkan functions, enumerations and structs are defined in the |
| 227 | +`vulkan.h` header, which is included in the [Vulkan SDK](https://lunarg.com/vulkan-sdk/) |
| 228 | +developed by LunarG. We'll look into installing this SDK in the next chapter. |
| 229 | + |
| 230 | +Functions have a lower case `vk` prefix, types like enumerations and structs |
| 231 | +have a `Vk` prefix and enumeration values have a `VK_` prefix. The API heavily |
| 232 | +uses structs to provide parameters to functions. For example, object creation |
| 233 | +generally follows this pattern: |
| 234 | + |
| 235 | +```c++ |
| 236 | +VkXXXCreateInfo createInfo{}; |
| 237 | +createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; |
| 238 | +createInfo.pNext = nullptr; |
| 239 | +createInfo.foo = ...; |
| 240 | +createInfo.bar = ...; |
| 241 | + |
| 242 | +VkXXX object; |
| 243 | +if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { |
| 244 | + std::cerr << "failed to create object" << std::endl; |
| 245 | + return false; |
| 246 | +} |
| 247 | +``` |
| 248 | + |
| 249 | +Many structures in Vulkan require you to explicitly specify the type of |
| 250 | +structure in the `sType` member. The `pNext` member can point to an extension |
| 251 | +structure and will always be `nullptr` in this tutorial. Functions that create |
| 252 | +or destroy an object will have a VkAllocationCallbacks parameter that allows you |
| 253 | +to use a custom allocator for driver memory, which will also be left `nullptr` |
| 254 | +in this tutorial. |
| 255 | + |
| 256 | +Almost all functions return a VkResult that is either `VK_SUCCESS` or an error |
| 257 | +code. The specification describes which error codes each function can return and |
| 258 | +what they mean. |
| 259 | + |
| 260 | +### Validation layers |
| 261 | + |
| 262 | +As mentioned earlier, Vulkan is designed for high performance and low driver |
| 263 | +overhead. Therefore it will include very limited error checking and debugging |
| 264 | +capabilities by default. The driver will often crash instead of returning an |
| 265 | +error code if you do something wrong, or worse, it will appear to work on your |
| 266 | +graphics card and completely fail on others. |
| 267 | + |
| 268 | +Vulkan allows you to enable extensive checks through a feature known as |
| 269 | +*validation layers*. Validation layers are pieces of code that can be inserted |
| 270 | +between the API and the graphics driver to do things like running extra checks |
| 271 | +on function parameters and tracking memory management problems. The nice thing |
| 272 | +is that you can enable them during development and then completely disable them |
| 273 | +when releasing your application for zero overhead. Anyone can write their own |
| 274 | +validation layers, but the Vulkan SDK by LunarG provides a standard set of |
| 275 | +validation layers that we'll be using in this tutorial. You also need to |
| 276 | +register a callback function to receive debug messages from the layers. |
| 277 | + |
| 278 | +Because Vulkan is so explicit about every operation and the validation layers |
| 279 | +are so extensive, it can actually be a lot easier to find out why your screen is |
| 280 | +black compared to OpenGL and Direct3D! |
| 281 | + |
| 282 | +There's only one more step before we'll start writing code and that's [setting |
| 283 | +up the development environment](!en/Development_environment). |
0 commit comments