[譯]Vulkan教程(02)概況
[譯]Vulkan教程(02)概況
這是我翻譯(https://vulkan-tutorial.com)上的Vulkan教程的第2篇。
This chapter will start off with an introduction of Vulkan and the problems it addresses. After that we're going to look at the ingredients that are required for the first triangle. This will give you a big picture to place each of the subsequent chapters in. We will conclude by covering the structure of the Vulkan API and the general usage patterns.
本章首先將介紹Vulkan和它解決的問題。然後我們要看一下繪製第一個三角形需要的材料。這會給你一個全域性觀念,以便(在頭腦中)安放後續章節。最後,我們將總結Vulkan API的結構和一般的使用模式。
Origin of Vulkan Vulkan起源
Just like the previous graphics APIs, Vulkan is designed as a cross-platform abstraction over GPUs. The problem with most of these APIs is that the era in which they were designed featured graphics hardware that was mostly limited to configurable fixed functionality. Programmers had to provide the vertex data in a standard format and were at the mercy of the GPU manufacturers with regards to lighting and shading options.
和之前的圖形API一樣,Vulkan被設計為一個跨平臺的對GPU的抽象。在之前的API被設計的年代,它們都是針對當時的圖形硬體實現了可配置的固定功能,這是它們的問題。程式設計師不得不用標準的格式提供頂點資料,聽命於GPU廠商提供的光照和著色選項。
As graphics card architectures matured, they started offering more and more programmable functionality. All this new functionality had to be integrated with the existing APIs somehow. This resulted in less than ideal abstractions and a lot of guesswork on the graphics driver side to map the programmer's intent to the modern graphics architectures. That's why there are so many driver updates for improving the performance in games, sometimes by significant margins. Because of the complexity of these drivers, application developers also need to deal with inconsistencies between vendors, like the syntax that is accepted for shaders. Aside from these new features, the past decade also saw an influx of mobile devices with powerful graphics hardware. These mobile GPUs have different architectures based on their energy and space requirements. One such example is tiled rendering, which would benefit from improved performance by offering the programmer more control over this functionality. Another limitation originating from the age of these APIs is limited multi-threading support, which can result in a bottleneck on the CPU side.
隨著圖形卡架構的成熟,它們開始提供越來越多的可程式設計功能。所有的這些新功能都必須以某種方式加入現有的API中。這導致了抽象不夠理想,圖形driver端(在將程式設計師的意圖對映到現代圖形架構時)要做很多判斷。這就是驅動頻繁更新以提升遊戲效能(有時提升十分明顯)的原因。由於driver的複雜性,app開發者也需要處理不同廠商之間的不一致問題,例如shader的語法。除了這些新特性,過去幾十年也湧現了帶有強大圖形硬體的移動裝置。由於能量和空間限制,這些移動裝置的GPU有不同的架構。一個例子是排列式成像,它通過提供給程式設計師更多的控制權得到了更高的效能。這些API的年紀帶來的另一個限制它們是對多執行緒的支援有限,這會導致CPU端的瓶頸。
Vulkan solves these problems by being designed from scratch for modern graphics architectures. It reduces driver overhead by allowing programmers to clearly specify their intent using a more verbose API, and allows multiple threads to create and submit commands in parallel. It reduces inconsistencies in shader compilation by switching to a standardized byte code format with a single compiler. Lastly, it acknowledges the general purpose processing capabilities of modern graphics cards by unifying the graphics and compute functionality into a single API.
Vulkan是從零開始為現代圖形架構設計的,它解決了上述問題。它允許程式設計師用一個冗繁得多的API,清楚地表明他們的意圖,還允許在多執行緒中併發地建立和提交命令,從而減少了driver開銷。它用一個唯一的編譯器得到標準的位元組碼,從而減少了在shader編譯的不一致性。最後,它用統一的圖形和計算功能API來呼叫現代圖形卡的處理能力。(譯者注:Vulkan既可以用於圖形渲染,又可以用於非圖形計算。)
What it takes to draw a triangle 畫一個三角形的代價
We'll now look at an overview of all the steps it takes to render a triangle in a well-behaved Vulkan program. All of the concepts introduced here will be elaborated on in the next chapters. This is just to give you a big picture to relate all of the individual components to.
現在我們將概覽在一個表現良好的Vulkan程式中渲染一個三角形所需的所有步驟。這裡介紹的所有概念都將在接下來的章節中詳述。這裡只是給你個全域性概念,讓你知道各個獨立的元件之間的關係。
Step 1 - Instance and physical device selection 步驟1 - 選擇instance和物理裝置
A Vulkan application starts by setting up the Vulkan API through a VkInstance
. An instance is created by describing your application and any API extensions you will be using. After creating the instance, you can query for Vulkan supported hardware and select one or more VkPhysicalDevice
s to use for operations. You can query for properties like VRAM size and device capabilities to select desired devices, for example to prefer using dedicated graphics cards.
Vulkan應用程式開始時要通過一個VkInstance來構建Vulkan API。一個instance通過描述你的app和你將要使用的所有API擴充套件來建立。建立完instance後,你可以查詢Vulkan支援的硬體,選擇一個或多個VkPhysicalDevice,用於後續操作。你可以查詢VRAM大小、裝置功能等,用以選擇想要的裝置,例如選擇某種專用圖形卡。
Step 2 - Logical device and queue families 步驟2 – 邏輯裝置和queue家族
After selecting the right hardware device to use, you need to create a VkDevice
(logical device), where you describe more specifically which VkPhysicalDeviceFeatures
you will be using, like multi viewport rendering and 64 bit floats. You also need to specify which queue families you would like to use. Most operations performed with Vulkan, like draw commands and memory operations, are asynchronously executed by submitting them to a VkQueue
. Queues are allocated from queue families, where each queue family supports a specific set of operations in its queues. For example, there could be separate queue families for graphics, compute and memory transfer operations. The availability of queue families could also be used as a distinguishing factor in physical device selection. It is possible for a device with Vulkan support to not offer any graphics functionality, however all graphics cards with Vulkan support today will generally support all queue operations that we're interested in.
選擇了正確的硬體裝置後,你需要建立一個VkDevice (邏輯裝置),它描述了你要使用哪些VkPhysicalDeviceFeatures ,例如多視口渲染和64位浮點數。你也需要標明你想使用哪個queue家族。Vulkan實施的大多數操作,例如繪製命令和記憶體操作,都是通過提交它們到一個VkQueue,來非同步執行的。Queue是從queue家族分配的,每個queue家族裡的queue都支援特定的一些操作(這些操作構成一個集合)。例如,有的queue家族支援圖形操作,有的支援計算操作,有的支援記憶體轉移操作。Queue家族的能力也可以用於選擇物理裝置的區分因素。可能存在完全不支援圖形功能的Vulkan裝置,但是當今所有的Vulkan圖形卡一般都支援我們感興趣的所有queue操作。
Step 3 - Window surface and swap chain 步驟3 – 視窗surface和交換鏈
Unless you're only interested in offscreen rendering, you will need to create a window to present rendered images to. Windows can be created with the native platform APIs or libraries like GLFW and SDL. We will be using GLFW in this tutorial, but more about that in the next chapter.
除非你只對離屏渲染感興趣,你將需要建立一個視窗來呈現渲染的影象。視窗可以用本地平臺API或GLFW和SDL這樣的庫建立。本教程中我們將使用GLFW,但是下一章再細說。
We need two more components to actually render to a window: a window surface (VkSurfaceKHR
) and a swap chain (VkSwapchainKHR
). Note the KHR
postfix, which means that these objects are part of a Vulkan extension. The Vulkan API itself is completely platform agnostic, which is why we need to use the standardized WSI (Window System Interface) extension to interact with the window manager. The surface is a cross-platform abstraction over windows to render to and is generally instantiated by providing a reference to the native window handle, for example HWND
on Windows. Luckily, the GLFW library has a built-in function to deal with the platform specific details of this.
我們還需要2個元件來渲染到視窗:一個視窗surface(VkSurfaceKHR)和一個交換鏈(VkSwapchainKHR)。注意,字尾KHR 意思是這些物件是Vulkan擴充套件的一部分。Vulkan API是完全的平臺不可知論者,這就是我們需要用標準化WSI(視窗系統介面)擴充套件與視窗管理器互動的原因。Surface是對可渲染視窗的跨平臺抽象,一般通過提供一個本地控制代碼的方式來例項化,例如在Windows上提供的控制代碼是HWND 。幸運的是,GLFW庫有個內建函式處理平臺相關的細節。
The swap chain is a collection of render targets. Its basic purpose is to ensure that the image that we're currently rendering to is different from the one that is currently on the screen. This is important to make sure that only complete images are shown. Every time we want to draw a frame we have to ask the swap chain to provide us with an image to render to. When we've finished drawing a frame, the image is returned to the swap chain for it to be presented to the screen at some point. The number of render targets and conditions for presenting finished images to the screen depends on the present mode. Common present modes are double buffering (vsync) and triple buffering. We'll look into these in the swap chain creation chapter.
交換鏈是渲染目標的集合。它的基本目的是確保當前正在渲染的image(影象)與當前正在呈現到螢幕的,不是同一個。為確保只有完整的image被呈現,這很重要。每次我們想繪製一幀時,我們不得不請求交換鏈提供給我們一個用於渲染的image。當我們完成了繪製這一幀,這個image就返回到交換鏈,準備在某時呈現到螢幕。渲染目標的數量,呈現image到螢幕的條件,都依賴於呈現模式。常見的呈現模式是雙快取(垂直同步)和三快取。我們將在交換鏈建立章節詳述這些。
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.
有的平臺允許你直接渲染到顯示器,無需與視窗管理器互動,只要使用VK_KHR_display 和VK_KHR_display_swapchain 擴充套件即可。這樣你就可以建立一個代表整個顯示器區域的surface,用其實現自己的視窗管理器。
Step 4 - Image views and framebuffers 步驟4 – image檢視和幀快取
To draw to an image acquired from the swap chain, we have to wrap it into a VkImageView
and VkFramebuffer
. An image view references a specific part of an image to be used, and a framebuffer references image views that are to be used for color, depth and stencil targets. Because there could be many different images in the swap chain, we'll preemptively create an image view and framebuffer for each of them and select the right one at draw time.
為了在一個從交換鏈上得到的image上繪製,我們不得不將其封裝到VkImageView 和VkFramebuffer。一個image檢視指定image的哪一部分被使用,一個幀快取指定image檢視是被用作顏色、深度還是模板目標。因為交換鏈上可能有很多不同的image,我們將先發制人地為每個image建立一個image檢視和幀快取,然後在繪製時選擇正確的那個。
Step 5 - Render passes 步驟5 – 渲染pass
Render passes in Vulkan describe the type of images that are used during rendering operations, how they will be used, and how their contents should be treated. In our initial triangle rendering application, we'll tell Vulkan that we will use a single image as color target and that we want it to be cleared to a solid color right before the drawing operation. Whereas a render pass only describes the type of images, a VkFramebuffer
actually binds specific images to these slots.
Vulkan中的渲染pass描述用於渲染操作的image型別,它們將被如何使用,它們的內容將被用於何處。在我們最初的渲染三角形app中,我們會告訴Vulkan我們將用一個image作為顏色目標,將其清空為一個固定顏色,之後再執行繪製操作。一個渲染pass只描述image的型別,但VkFramebuffer 實際上繫結的是具體的image。
Step 6 - Graphics pipeline 步驟6 – 圖形管道
The graphics pipeline in Vulkan is set up by creating a VkPipeline
object. It describes the configurable state of the graphics card, like the viewport size and depth buffer operation and the programmable state using VkShaderModule
objects. The VkShaderModule
objects are created from shader byte code. The driver also needs to know which render targets will be used in the pipeline, which we specify by referencing the render pass.
Vulkan中的圖形管道通過建立VkPipeline 物件來構建。它描述了圖形卡的可配置的狀態,例如視口大小、深度快取操作和VkShaderModule 物件的可程式設計狀態。VkShaderModule 物件從shader位元組碼建立。Driver也需要知道哪個渲染目標會被用於管道中,這一點,我們通過引用渲染pass來標明。
One of the most distinctive features of Vulkan compared to existing APIs, is that almost all configuration of the graphics pipeline needs to be set in advance. That means that if you want to switch to a different shader or slightly change your vertex layout, then you need to entirely recreate the graphics pipeline. That means that you will have to create many VkPipeline
objects in advance for all the different combinations you need for your rendering operations. Only some basic configuration, like viewport size and clear color, can be changed dynamically. All of the state also needs to be described explicitly, there is no default color blend state, for example.
Vulkan與原有API區別最大的特性之一是,幾乎所有的圖形管道配置工作都需要提前做好。意思是,如果你想切換到不同的shader或稍微改變你的頂點佈局,那麼你需要整個重建圖形管道。這意味著你不得不提前建立很多VkPipeline 物件,以用於不同的渲染操作的組合。只有一些基本的配置,例如視口大小和清空顏色,可以被動態地改變。所有狀態都需要被顯式地描述才會有,例如,不存在預設的顏色混合狀態。
The good news is that because you're doing the equivalent of ahead-of-time compilation versus just-in-time compilation, there are more optimization opportunities for the driver and runtime performance is more predictable, because large state changes like switching to a different graphics pipeline are made very explicit.
好訊息是,由於你做的提前編譯(而不是即時編譯),driver有更多的優化機會,執行時效能也更加可預測,因為重量級狀態改變(例如切換到另一個圖形管道)被顯式地指出了。
Step 7 - Command pools and command buffers 步驟7 – 命令池和命令快取
As mentioned earlier, many of the operations in Vulkan that we want to execute, like drawing operations, need to be submitted to a queue. These operations first need to be recorded into a VkCommandBuffer
before they can be submitted. These command buffers are allocated from a VkCommandPool
that is associated with a specific queue family. To draw a simple triangle, we need to record a command buffer with the following operations:
- Begin the render pass
- Bind the graphics pipeline
- Draw 3 vertices
- End the render pass
如前所述,在Vulkan中,我們想要執行的很多操作,例如繪製操作,需要被提交到一個queue裡。在提交前,這些操作首先需要被記錄到VkCommandBuffer 中。這些命令快取是從一個命令池VkCommandPool 中申請的,命令池與一個特定的queue家族關聯。為了繪製一個三角形,我們需要記錄一個有下述操作的命令快取:
- 開始渲染pass
- 繫結圖形管道
- 繪製3個頂點
- 結束渲染pass
Because the image in the framebuffer depends on which specific image the swap chain will give us, we need to record a command buffer for each possible image and select the right one at draw time. The alternative would be to record the command buffer again every frame, which is not as efficient.
因為幀快取中的image依賴於交換鏈給我們哪個image,我們需要對每個可能的image記錄一個命令快取,並在繪製時選擇正確的那個。另一個方式是,每一幀都記錄一次命令快取,但這就不效率了。
Step 8 - Main loop 步驟8 – 主迴圈
Now that the drawing commands have been wrapped into a command buffer, the main loop is quite straightforward. We first acquire an image from the swap chain with vkAcquireNextImageKHR
. We can then select the appropriate command buffer for that image and execute it with vkQueueSubmit
. Finally, we return the image to the swap chain for presentation to the screen with vkQueuePresentKHR
.
既然繪製命令已經被封裝到命令快取裡,主迴圈就相當直截了當了。我們首先用函式vkAcquireNextImageKHR從交換鏈請求一個image。然後我們就可以為此image選擇恰當的命令快取,並用函式vkQueueSubmit執行它。最後,我們用函式vkQueuePresentKHR將image返回到交換鏈,以使之被呈現到螢幕。
Operations that are submitted to queues are executed asynchronously. Therefore we have to use synchronization objects like semaphores to ensure a correct order of execution. Execution of the draw command buffer must be set up to wait on image acquisition to finish, otherwise it may occur that we start rendering to an image that is still being read for presentation on the screen. The vkQueuePresentKHR
call in turn needs to wait for rendering to be finished, for which we'll use a second semaphore that is signaled after rendering completes.
提交到queue的操作是被非同步執行的。因此我們不得不使用同步物件(例如訊號)來確保執行的正確順序。必須等待image的請求結束後,才能執行繪製命令快取的操作。否則,可能發生我們開始渲染到image了但是image還在被用於呈現到螢幕上的情況。依序,呼叫函式vkQueuePresentKHR 前需要等待渲染操作完成,為此我們將用另一個訊號物件(在渲染完成後發訊號)。
Summary 總結
This whirlwind tour should give you a basic understanding of the work ahead for drawing the first triangle. A real-world program contains more steps, like allocating vertex buffers, creating uniform buffers and uploading texture images that will be covered in subsequent chapters, but we'll start simple because Vulkan has enough of a steep learning curve as it is. Note that we'll cheat a bit by initially embedding the vertex coordinates in the vertex shader instead of using a vertex buffer. That's because managing vertex buffers requires some familiarity with command buffers first.
這次旋風之旅應該給你一個基礎的理解,知道繪製第一個三角形之前的工作有哪些。實際的app包含更多的步驟,例如申請頂點快取、建立uniform快取和上傳texture image,這些會在後續章節介紹。但我們從簡單的開始,因為Vulkan的學習曲線已經很陡峭了。注意,我們將耍個滑頭,在vertex shader中初始化一些內嵌的頂點座標,而非使用頂點快取。這是因為管理頂點快取的前提條件之一是熟悉命令快取。
So in short, to draw the first triangle we need to:
- Create a
VkInstance
- Select a supported graphics card (
VkPhysicalDevice
) - Create a
VkDevice
andVkQueue
for drawing and presentation - Create a window, window surface and swap chain
- Wrap the swap chain images into
VkImageView
- Create a render pass that specifies the render targets and usage
- Create framebuffers for the render pass
- Set up the graphics pipeline
- Allocate and record a command buffer with the draw commands for every possible swap chain image
- Draw frames by acquiring images, submitting the right draw command buffer and returning the images back to the swap chain
簡單來說,為了繪製第一個三角形,我們需要:
- 建立一個VkInstance物件
- 選擇一個圖形卡(VkPhysicalDevice)
- 為繪製和呈現建立一個VkDevice 和VkQueue 。
- 建立一個視窗,視窗surface和交換鏈
- 將交換鏈的image封裝進VkImageView
- 建立渲染pass,它標明渲染目標和用法
- 建立幀快取,它引用渲染pass
- 構建圖形管道
- 申請命令快取,為交換鏈的每個image記錄繪製命令
- 渲染一幀:請求image,提交正確的繪製命令快取,將image返回到交換鏈
It's a lot of steps, but the purpose of each individual step will be made very simple and clear in the upcoming chapters. If you're confused about the relation of a single step compared to the whole program, you should refer back to this chapter.
步驟很多啊,但是每個獨立步驟的目的將會十分簡單清楚地展現在後續章節中。如果你不明白某一步驟在整個程式中的作用,你就應該重讀本章。
API concepts API概念
This chapter will conclude with a short overview of how the Vulkan API is structured at a lower level.
本章最後將簡要介紹Vulkan API在低層上的結構。
Coding conventions 編碼約定
All of the Vulkan functions, enumerations and structs are defined in the vulkan.h
header, which is included in the Vulkan SDK developed by LunarG. We'll look into installing this SDK in the next chapter.
所有的Vulkan函式、列舉和結構體都在標頭檔案vulkan.h 中定義,由LunarG開發的Vulkan SDK裡有這個檔案。
Functions have a lower case vk
prefix, types like enumerations and structs have a Vk
prefix and enumeration values have a VK_
prefix. The API heavily uses structs to provide parameters to functions. For example, object creation generally follows this pattern:
函式帶有小寫的vk 字首,列舉等型別和結構體有Vk 字首,列舉值有VK_ 字首。這個API大量使用結構體做為函式的引數。例如,建立物件的過程普遍遵循這樣的模式:
1 VkXXXCreateInfo createInfo = {}; 2 createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO; 3 createInfo.pNext = nullptr; 4 createInfo.foo = ...; 5 createInfo.bar = ...; 6 7 VkXXX object; 8 if (vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS) { 9 std::cerr << "failed to create object" << std::endl; 10 return false; 11 }
Many structures in Vulkan require you to explicitly specify the type of structure in the sType
member. The pNext
member can point to an extension structure and will always be nullptr
in this tutorial. Functions that create or destroy an object will have a VkAllocationCallbacks
parameter that allows you to use a custom allocator for driver memory, which will also be left nullptr
in this tutorial.
Vulkan中的許多結構體要求你顯式地在成員sType標明結構體的型別。成員pNext 可能指向一個擴充套件結構體,在本教程中它將始終為nullptr 。
Almost all functions return a VkResult
that is either VK_SUCCESS
or an error code. The specification describes which error codes each function can return and what they mean.
幾乎所有函式都返回一個VkResult ,它要麼是VK_SUCCESS ,要麼是一個錯誤碼。說明書裡描述了每個函式可能返回哪些錯誤碼及其含義。
Validation layers 驗證層
As mentioned earlier, Vulkan is designed for high performance and low driver overhead. Therefore it will include very limited error checking and debugging capabilities by default. The driver will often crash instead of returning an error code if you do something wrong, or worse, it will appear to work on your graphics card and completely fail on others.
如前所述,設計Vulkan是為了更高的效能和更低的driver開銷。因此它預設只有很有限的錯誤檢查和除錯能力。如果你做錯了什麼,driver會經常崩潰,而不是返回錯誤碼,甚至更糟,它在你的圖形卡上能工作但是在其他的圖形卡上就完全不行。
Vulkan allows you to enable extensive checks through a feature known as validation layers. Validation layers are pieces of code that can be inserted between the API and the graphics driver to do things like running extra checks on function parameters and tracking memory management problems. The nice thing is that you can enable them during development and then completely disable them when releasing your application for zero overhead. Anyone can write their own validation layers, but the Vulkan SDK by LunarG provides a standard set of validation layers that we'll be using in this tutorial. You also need to register a callback function to receive debug messages from the layers.
Vulkan允許你通過一個被稱為驗證層的特性來啟用額外的檢查。驗證層是一段程式碼,可以插進API和圖形驅動之間,做一些例如額外檢查函式引數和追蹤記憶體管理問題的事。好處是你可以在開發期間啟用它,在釋出app時徹底禁用它,以消除此開銷。任何人都可以寫自己的驗證層,但是LunarG開發的Vulkan SDK提供了一個驗證層的標準集,我們在本教程中就用這個。你還需要註冊一個回撥函式來接收這些層送來的除錯資訊。
Because Vulkan is so explicit about every operation and the validation layers are so extensive, it can actually be a lot easier to find out why your screen is black compared to OpenGL and Direct3D!
因為Vulkan的每個操作都是如此的顯式,驗證層又是如此的可擴充套件,想要找到為什麼你的螢幕是一片漆黑,這要比OpenGL和Direct3D簡單得多!
There's only one more step before we'll start writing code and that's setting up the development environment.
距離開始寫程式碼還差一步,那就是配置開發環境。
- Previous上一章
- Next下一章
&n