1. 程式人生 > >CSharpGL(56)[譯]Vulkan入門

CSharpGL(56)[譯]Vulkan入門

CSharpGL(56)[譯]Vulkan入門

本文是對(http://ogldev.atspace.co.uk/www/tutorial50/tutorial50.html)的翻譯,作為學習Vulkan的一次嘗試。

不翻譯的話,每次都在看第一句,那就學不完了。 

Background 背景

You've probably heard by now quite a bit about Vulkan, the new Graphics API from Khronos (the non profit organization responsible for the development of OpenGL).

你可能聽過Vulkan,Khronos建立的新的圖形API。Khronos是負責開發OpenGL的非盈利組織。

Vulkan was announced in Feb-2016 and after 24 years with OpenGL it is a completely new standard and a departure from the current model.

在OpenGL出現24年後,Vulkan於2016年2月被髮布。它放棄了當前的模型,它是全新的標準。

I won't go into many details about the various features of Vulkan only to say that in comparison to OpenGL it is much more low level and provides a lot of power and performance opportunities for the developer.

我不會上來就丟擲一堆新的特性的細節。簡單來說,相比OpenGL,Vulkan底層得多,給開發者提供了巨大的能量和提升效能的機會。

But with great power comes great responsibility.

但是能力越大,責任就越大。

The developer has to take charge of various aspects such as command buffer, synchronization and memory management that were previously the sole responsibility of the driver.

開發者必須負責各種方面,例如命令快取、同步、記憶體管理,這些之前都是驅動的責任。

Through the unique knowledge that the developer has about the way the application is structured, the usage of the Vulkan API can be tailored in a way to increase the overall performance of the system.

基於開發者對應用程式結構的知識,他可以調整Vulkan API的用法,獲得更高的系統性能。

 

The thing that surprises people the most, IMHO, about Vulkan is the amount of code that must be written only to get the first triangle on the screen.

最讓人吃驚的,恕我直言,是在螢幕上顯示第一個三角形所需的巨大程式碼量。

Comparing this to the few lines we had to write in OpenGL in the first few tutorials this is a major change and becomes a challenge when one tries to write a tutorial about it.

相比在最初的教程中我們寫的那幾行OpenGL程式碼,這是很大的改變,也是寫Vulkan教程的難點。

Therefore, as always with OGLDEV, I'll try to present the material step by step.

因此,像OGLDEV往常一樣,我將一步一步地展開本教程。

We will develop our first triangle demo in a few tutorials, making additional progress in each one.

我們將在多個教程中完成第一個三角形示例,在每個教程中進展一點點。

In addition, instead of laying out the dozens of APIs in one long piece of code I'll present a simple software design that I hope will make it simpler for you to understand without imposing too much restrictions on your future apps.

另外,我不展示一大段API,而是展示有設計思路的軟體。我希望這能便於讀者理解。

Consider this an educational design which you are free to throw away later.

這個設計以教學為目的,等你學會了,就可以扔掉了。

 

We will study the core components of Vulkan one by one as we make progress through the code so at this point I just want to present a diagram of the general picture:

隨著程式碼,我們將一個個地學習Vulkan核心元件。現在我們先看一下概覽圖:

 

 

This diagram is by all means not a complete representation.

當然這個圖並沒有展示所有的元件。

It includes only the major components that will probably be present in most applications.

它只包含會在大部分應用程式中使用的主要元件。

The connectors between the objects represent the dependencies between them at creation or enumeration time.

物件之間的連線表示在建立或列舉時的依賴關係。

For example, in order to create a surface you need an instance object and when you enumerate the physical devices on your system you also need an instance.

例如,為了建立一個surface,你需要一個instance物件;當你列舉你係統上的物理裝置時,你也需要一個instance物件。

The two colors roughly describe the software design that we will use.

兩種顏色粗略地描述了我們將使用的軟體設計方案。

The dark red objects will go into something I call the "core" and the light green objects will go into the "app".

暗紅色物件將屬於“core”,淺綠色物件將屬於“app”。

We will later see why this makes sense.

我們稍後再看為什麼是這樣。

The application code that you will write will actually inherit from "app" and all of its members will be available for you for further use.

應用程式程式碼將繼承自app,app的所有成員以後都將可用。

I hope this design will provide a solid base to develop future Vulkan tutorials.

我希望這樣的設計能提供一個堅實的基礎,用於開發將來的Vulkan教程。

 

System Setup 系統安裝

The first thing we need to do is to make sure your system supports Vulkan and get everything ready for development.

我們要做的第一件事,是確保你的系統支援Vulkan,準備好開發所需的一切。

You need to verify that your graphics card supports Vulkan and install the latest drivers for it.

你需要驗證你的圖形卡是否支援Vulkan,並安裝最新的驅動程式。

Since Vulkan is still new it's best to check for drivers updates often because hardware vendors will probably fix a lot of bugs before everything stabilizes.

由於Vulkan還很新,最好經常檢查驅動更新,因為硬體廠商可能會在驅動穩定前修復很多bug。

Since there are many GPUs available I can't provide much help here.

由於有太多種GPU,我這裡愛莫能助。

Updating/installing the driver on Windows should be fairly simple.

在Windows上更新/安裝驅動應該相當簡單。

On Linux the process may be a bit more involved.

在Linux上,就有點難纏。

My main development system is Linux Fedora and I have a GT710 card by NVIDIA.

我的開發系統是Linux的Fedora版本,顯示卡是NVIDIA的GT710。

NVIDIA provide a binary run file which can only be installed from the command line.

NVIDIA提供一個二進位制執行檔案,只能從命令列安裝。

Other vendors have their own processes.

其他廠商有各自的方式。

On Linux you can use the 'lspci' to scan your system for devices and see what GPU you have.

在Linux上你可以用'lspci'命令掃描你的系統,看看有哪些裝置,用的什麼GPU。

You can use the '-v', '-vv' and '-vvv' options to get increasingly more info on your devices.

你可以用'-v'、'-vv'、'-vvv'來得到你的裝置的越來越詳細的資訊。

 

The second thing we need is the Vulkan SDK by Khronos, available here.

第二件事,我們需要Khronos的Vulkan SDK,可在此下載。

The SDK includes the headers and libraries we need as well as many samples that you can use to get more info beyond what this tutorial provides.

SDK包含標頭檔案和庫檔案,很多示例,比本教程多得多的資訊。

At the time of writing this the latest version is 1.0.30.0 and I urge you to update often because the SDK is in active development.

寫作本文時最新版本是1.0.30.0,我推薦讀者時常更新,因為SDK還處於活躍地開發中。

That version number will be used throughout the next few sections so make sure you change it according to the version you have.

接下來的章節都將使用這個版本號,所以,根據你的版本號,相應地替換之。

 

Linux

Khronos provides a package only for Ubuntu in the form of an executable run file.

Khronos只給Ubuntu提供了一個可執行檔案。

Executing this file should install everything for you but on Fedora I encoutered some difficulties so I used the following procedure (which is also forward looking in terms of writing the code later):

執行這個檔案就可以安裝所需的一切。但是在Fedora上我遇到了一些困難,所以我用下述步驟(也是預覽一下程式碼):

  • bash$ chmod +x vulkansdk-linux-x86_64-1.0.30.0.run
  • base$ ./vulkansdk-linux-x86_64-1.0.30.0.run --target VulkanSDK-1.0.30.0 --noexec
  • base$ ln -s ~/VulkanSDK-1.0.30/1.0.30.0 ~/VulkanSDK

The above commands extract the contents of the package without running its internal scripts.

上述命令提取出包的內容,並執行裡面的指令碼。

After extraction the directory VulkanSDK-1.0.30.0 will contain a directory called 1.0.30.0 where the actual content of the package will be located.

提取完成後,資料夾VulkanSDK-1.0.30.0會包含一個子資料夾1.0.30.0,裡面是實際的內容。

Let's assume I ran the above commands in my home directory (a.k.a in bash as '~') so we should end up with a '~/VulkanSDK' symbolic link to the directory with the actual content (directories such as 'source', 'samples', etc).

假定我是在home資料夾下執行的上述命令(即'~'),那麼會有一個'~/VulkanSDK'符號連結到實際內容(資料夾'source'、'samples'等)。

This link makes it easier to switch your development environment to newer versions of the SDK.

這個連結使得切換到SDK的新版本更容易。

It points to the location of the headers and libraries that we need.

它指向我們需要的標頭檔案和庫檔案的位置。

We will see later how to connect them to the rest of the system. Now do the following:

稍後我們將看到如何將它們連線到系統的其他部分。現在執行下述命令:

  • bash$ cd VulkanSDK/1.0.30.0
  • bash$ ./build_examples.sh

If everything went well the examples were built into 'examples/build'.

如果一切順利,示例會出現在資料夾'examples/build'。

To run the examples you must first cd into that directory.

為執行示例,你首先要進入這個資料夾。

You can now run './cube' and './vulkaninfo' to make sure Vulkan runs on your system and get some useful information on the driver.

你現在可以執行'./cube'和'./vulkaninfo'命令來確認Vulkan跑在你的系統上了,還可以得到一些驅動的有用資訊。

Hopefully everything is OK so far so we want to create some symbolic links that will make the files we need for development easily accessible from our working environment.

單元一切順利,目前我們想建立一些符號連結,方便我們使用開發過程中會用到的檔案。

Change to the root user (by executing 'su' and entering the root password) and execute the following:

跳到root使用者(執行'su'命令,輸入root密碼),執行下述命令:

  • bash# ln -s /home/<your username>/VulkanSDK/x86_x64/include/vulkan /usr/include
  • base# ln -s /home/<your username>/VulkanSDK/x86_x64/lib/libvulkan.so.1 /usr/lib64
  • base# ln -s /usr/lib64/libvulkan.so.1 /usr/lib64/libvulkan.so

What we did in the above three commands is to create a symbolic link from /usr/include to the vulkan header directory.

上述3個命令,建立了一個從/usr/include到Vulkan標頭檔案夾的符號連結。

We also created a couple of symbolic links to the shared object files against which we are going to link our executables.

我們還建立了一些共享物件的符號連結,今後會將我們的程式連結到這些共享物件。

From now one whenever we download a new version of the SDK we just need to change the symbolic link '~/VulkanSDK' to the new location in order to keep the entire system up to date.

從現在開始,無論何時我們下載了新版本的SDK,我們只需將符號連結'~/VulkanSDK'修改為指向新位置,就可以讓整個系統更新完畢。

To emphasis: the procedure as the root user must only be executed once.

強調一點:root使用者執行的過程必須只執行一次。

When you get a newer version of the SDK you will only need to extract it and update the symbolic link from your home directory.

當你得到了新版SDK,你只需提取它的內容,從你的home資料夾更新符號連結。

You are free to place that link anywhere you want but the code I provide will assume it is in the home directory so you will need to fix that.

你可以將這個連結房子任何你喜歡的地方,但是我提供的程式碼中都假定它在home資料夾。所以你需要相應地修改之。

 

Windows

Installation on Windows is simpler than on Linux.

在Windows上按照比在Linux上簡單。

You just need to get the latest version from here, double click the executable installer and after agreeing to the license agreement and selecting the target directory you are done.

你只需從這裡下載最新的版本,雙擊安裝包,同意license,選擇目標資料夾,萬事大吉。

I suggest you install the SDK under c:\VulkanSDK to make it compatible with the Visual Studio solution that I provide, but it is not a must. If you install it somewhere else make sure you update the include and link directories in the project files.

我建議將SDK按照到資料夾c:\VulkanSDK,這樣和我提供的Visual Studio解決方案相容,但不是必須這樣。如果你把它安裝到其他位置,確保你更新了專案檔案中的include和link資料夾。

See details in the next section.

詳見下一節。

Building and Running 建設和執行

Linux

My main development environment on Linux is Netbeans.

在Linux上我的主要開發環境是Netbeans。

The source code that accompanies all my tutorials contains project files which can be used with the C/C++ Netbeans download bundle.

本教程的原始碼包含專案檔案,可以用C/C++Netbeans開啟。

If you followed the above system setup procedure then these projects should work out of the box for you (and please let me know if there are any problems).

如果你遵循上述系統建設步驟,那麼, 這些專案應該立即可用了(如果有困難請聯絡我)。

If you are using a different build system you need to make sure to add the following:

如果你在用不同的系統,你需要確保新增下述步驟:

  • To the compile command: -I<path to VulkanSDK/1.0.30.0/x86_64/include>
  • 加入編譯命令:-I<path to VulkanSDK/1.0.30.0/x86_64/include>
  • To the link command: -L<path to VulkanSDK/1.0.30.0/x86_64/lib> -lxcb -lvulkan'
  • 加入連結命令:-L<path to VulkanSDK/1.0.30.0/x86_64/lib> -lxcb -lvulkan'

Even if you don't use Netbeans I suggest you go into 'ogldev/tutorial50' after you unzip the tutorial source package and run 'make'.

即使你不使用Netbeans,我也建議你解壓tutorial source package後,開啟資料夾'ogldev/tutorial50',執行'make'。

I provide the makefiles that Netbeans generates so you can check whether your system is able to build them or something is missing.

我提供Netbeans生成的makefile,這樣你就可以檢查你的系統能建設它們,或是缺少什麼。

If everything was ok you can now run 'dist/Debug/GNU-Linux-x86/tutorial50' from within 'ogldev/tutorial50'.

如果一切順利, 現在你可以執行資料夾'ogldev/tutorial50'下的'dist/Debug/GNU-Linux-x86/tutorial50'。

Windows

If you installed the SDK under 'c:\VulkanSDK' then the Visual Studio project files I supply should work out of the box.

如果你將SDK安裝在資料夾'c:\VulkanSDK',那麼我提供的Visual Studio專案檔案就已經可用了。

If you haven't or you want to setup a Visual Studio project from scratch then follow the steps below.

如果不是,或者你想從零開始設定Visual Studio專案,那麼遵循以下步驟。

 

To update the include directory right click on the project in the solution explorer, go to 'Properties' and then to 'Configuration Properties -> C/C++ -> General'.

更新include資料夾:在solution explorer面板的專案上右鍵,點選'Properties',選擇'Configuration Properties -> C/C++ -> General'。

Now you must add 'c:\VulkanSDK\<version>\Include' to 'Additional Include Directories'.

現在,必須將'c:\VulkanSDK\<version>\Include'新增到'Additional Include Directories'。

See example below:

示例如下:

 

To update the link directory right click on the project in the solution explorer, go to 'Properties' and then to 'Configuration Properties -> Link -> General'.

更新link資料夾:在solution explorer面板的專案上右鍵,點選'Properties',選擇'Configuration Properties -> Link -> General'。

Now you must add 'c:\VulkanSDK\<version>\Bin32' to 'Additional Library Directories'.

現在,必須將'c:\VulkanSDK\<version>\Bin32'新增到'Additional Library Directories'。

See example below:

示例如下:

 

While you are still in the linker settings go to 'Input' (just one below 'General') and add 'vulkan-1.lib' to 'Additional Dependencies".

趁你還在連結器設定面板,選擇'Input'(在'General'下面一個),新增'vulkan-1.lib'到'Additional Dependencies"。

General Comments 基礎命令

Before we get going I have a few comments about some of my design choices with regard to Vulkan:

正式開始前,我要對我關於Vulkan的設計選擇作幾點說明:

  1. Many Vulkan functions (particularly the ones used to create objects) take a structure as one of the parameters.
    很多Vulkan函式(特別是建立物件的函式)接收一個struct作為引數之一。
    This structure usually serve as a wrapper for most of the parameters the function needs and it helps in keeping the number of parameters to the function low.
    這個struct一般用於封裝函式需要的大多數引數,利於使函式的引數數量保持在比較低的水平。
    The Vulkan architects decided to place a member called sType as the first member in all these structures.
    Vulkan架構決定將所有這些struct的第一個引數設定為名為sType的成員。
    This member is of an enum type and every structure has its own code.
    這個成員是個列舉型別,每個struct都有自己的列舉值。
    This allows the driver to identify the type of the structure using only its address.
    這可以讓驅動程式只需知道struct的地址就可以確定它的型別。
    All of these enum code have a VK_STRUCTURE_TYPE_ prefix.
    所有這些列舉值都有字首VK_STRUCTURE_TYPE_。
    For example, the code for the structure used in the creation of the instance is called VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO.
    例如,在建立instance中使用的struct的列舉值名字是VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO。
    Whenever I declare a variable of one of these structure types the first member I update will be sType.
    無論何時我宣告一個這樣的struct變數,第一個更新的成員都是sType。
    To save time I won't comment about it later in the source walkthrough.
    為節省時間,我以後不會提及這一點。
  2. Another comment on these Vulkan structures - they contain quite a lot of stuff which we don't need in our first few steps.
    關於這些Vulkan的struct的另一個說明——它們包含很多我們初期不需要的東西。
    To keep the code as short as possible (as well as the text here...) I always initialize the memory of all structures to zero (using the struct = {} notation) and I will only set and describe the structure members that cannot be zero.
    為了讓程式碼和文字儘可能短,我總是將所有成員初始化為0(使用struct = {}概念),只會描述不能為0的成員。
    I will discuss the stuff that I skipped in future tutorials as they become relevant.
    我將在以後的教程中討論這裡跳過的東西,等需要用它們的時候。
  3. Vulkan functions are either void or they return a VkResult which is the error code.
    Vulkan函式要麼返回void要麼返回一個VkResult,表示錯誤碼。
    The error code is an enum where VK_SUCCESS is zero and everything else is greater than zero.
    錯誤碼是個列舉型別,其中VK_SUCCESS 是0,其他列舉值都大於0。
    When it is possible I check the return value for errors.
    可能的話我會檢查返回值,看看有沒有錯誤。
    If an error occured I print a message to the console (on Windows there should be a message box) and exit.
    如果發生錯誤,我列印一個訊息到控制檯(在Windows上應該有個訊息盒),然後退出程式。
    Error handling in real world applications tend to make the code more complex and I want to keep it as simple as possible.
    真實世界應用程式的錯誤處理程式碼,會更加複雜。作為教程,我希望它儘可能簡單。
  4. Many Vulkan functions (particularly of creation type) can take a pointer to an allocator function.
    很多Vulkan函式(特別是建立型的)接收一個指標作為allocator函式。
    These allocators allow you to control the process of allocating memory that the Vulkan functions need.
    這些allocator允許你控制Vulkan函式需要的記憶體被分配的過程。
    I consider this as an advanced topic and will not discuss it.
    我認為這是個高階話題,就不討論它了。
    We will pass NULL as the allocators so the driver will use its default.
    我們將NULL傳給allocator,這樣驅動會用預設的allocator。
  5. Vulkan does not guarantee that its functions will be automatically exposed by the implementing library.
    Vulkan不保證它的函式會被自動地被實現庫暴露。
    This means that on some platforms you might get a segmentation fault when you call a Vulkan function because it turns out to be a NULL.
    這意味著在某些平臺上你在呼叫Vulkan函式時可能遇到段錯誤,因為函式指標實際上是NULL。
    In these cases you have to use vkGetInstanceProcAddr() to get the function address before it is used (remember that with OpenGL we had GLEW to save us from all this hassle).
    此時你必須在使用函式前用vkGetInstanceProcAddr()得到函式地址。(回憶在OpenGL中我們用GLEW拯救自己於這些困擾中)
    My personal experience with my driver was that only vkCreateDebugReportCallbackEXT() was not available.
    我對我的驅動的經驗是,只有vkCreateDebugReportCallbackEXT()不可用。
    This function is only required for the optional validation layer.
    這個函式只在可選驗證層需要。
    Therefore, I decided to take a risk and release the tutorial without fetching the addresses for all the functions that I used.
    因此,我決定冒險放出這些教程,不保證所有的函式都能找到地址。
    If readers will report problems on their platforms I will update the code.
    如果讀者反饋他們平臺上遇到的問題,我將更新我的程式碼。
  6. Every serious software has to deal with object deallocation or it will eventually run out of memory.
    每個正經軟體都要處理物件釋放問題,否則最終會記憶體不足。
    In this tutorial I'm keeping things simple and not destroying any of the objects that I allocate.
    本教程中一切從簡,不負責銷燬申請的記憶體。
    They are destroyed anyway when the program shuts down.
    反正程式關閉時它們都會被銷燬。
    I will probably revisit this topic in the future but for now just remember that almost every <vkCreate*() function has a corresponding vkDestroy*() and you need to be careful if you are destroying stuff while the program is running.
    將來我可能會重提這個話題,但現在就記住幾乎所有函式都有對應的函式,如果你要在程式執行時銷燬物件,要十分小心。
    You can find more information about it here.
    你可以在此找到更多資訊。

Code Structure 程式碼結構

Here's a short summary of the files that contain the code that we are going to review.

先概述一下我們即將遇到的程式碼檔案。

The path relates to the root of the ogldev software package:

路徑是相對ogldev軟體包的根目錄的:

  1. tutorial50/tutorial50.cpp - location of the main() function.
    tutorial50/tutorial50.cpp - main()函式的位置。
  2. include/ogldev_vulkan.h - primary header for all of our Vulkan code.
    include/ogldev_vulkan.h -我們的Vulkan程式碼的主要標頭檔案。
    This is the only place where the Vulkan headers by Khronos are included.
    唯一包含Khronos的Vulkan標頭檔案的地方。
    You can enable the validation layer here by uncommenting ENABLE_DEBUG_LAYERS.
    你可以通過取消註釋ENABLE_DEBUG_LAYERS來啟用驗證層。
    This file contains a few Vulkan helper functions and macros as well as the definition of the VulkanWindowControl class.
    這個檔案包含一些Vulkan輔助函式、巨集定義和型別VulkanWindowControl的定義。
  3. Common/ogldev_vulkan.cpp - implementation of the functions defined in ogldev_vulkan.h
    Common/ogldev_vulkan.cpp – 在ogldev_vulkan.h中定義的函式的實現。
  4. include/ogldev_vulkan_core.h - declaration of the OgldevVulkanCore which is the primary class that we will develop.
    include/ogldev_vulkan_core.h - OgldevVulkanCore的宣告,是我們要開發的主要型別。
  5. Common/ogldev_vulkan_core.cpp - implementation of the OgldevVulkanCore class.
    Common/ogldev_vulkan_core.cpp -型別OgldevVulkanCore的實現。
  6. include/ogldev_xcb_control.h - declaration of the XCBControl class that creates a window surface on Linux.
    include/ogldev_xcb_control.h -在Linux上建立視窗的型別XCBControl的宣告。
  7. Common/ogldev_xcb_control.cpp - implementation of XCBControl.
    Common/ogldev_xcb_control.cpp -型別XCBControl的實現。
  8. include/ogldev_win32_control.h - declaration of the Win32Control class that creates a window surface on Windows.
    include/ogldev_win32_control.h -在Windows上場景視窗表面的型別Win32Control 的宣告。
  9. Common/ogldev_win32_control.cpp - implementation of Win32Control.
    Common/ogldev_win32_control.cpp -型別Win32Control的實現。

Note that on both Netbeans and Visual Studio the files are divided between the 'tutorial50' and 'Common' projects.

注意,在Netbeans和Visual Studio中這些檔案被分到'tutorial50'和'Common'專案中。

Source walkthru 原始碼瀏覽

I hope that you successfully completed the above procedures and you are now ready to dive into the internals of Vulkan itself.

我希望你已經成功地完成了上述步驟,現在你可以深入Vulkan內部了。

As I said, we are going to develop our first demo in several steps.

我說過,我們計劃分幾步來開發第一個示例。

The first step will be to setup four important Vulkan objects: the instance, surface, physical device and logical device.

第一步是建設4個重要的Vulkan物件:instance,surface,physical device和logical device。(譯者注:關鍵名詞我就不翻譯了,這樣反而更便於理解。)

I'm going to describe this by walking through my software design but you are welcomed to throw this away and just follow the Vulkan calls themselves.

我計劃在介紹我的軟體設計過程中描述這些物件,但是你完全可以扔掉這一思路,直接面對Vulkan函式。

The first thing we need to do is to include the Vulkan headers.

首先,我們要include一下Vulkan標頭檔案。

I've added ogldev_vulkan.h as the primary Vulkan include file in my projects.

我已經在專案的ogldev_vulkan.h中加入了Vulkan的基礎include檔案。

This will be the only place where I will include the Vulkan header files and everything else will just include this file.

這是我唯一include了Vulkan標頭檔案的地方,其他位置都只include這個檔案。(從而間接incdlue的Vulkan標頭檔案)

Here's the relevant piece of code:

相關程式碼如下:

1 #ifdef _WIN32
2 #define VK_USE_PLATFORM_WIN32_KHR
3 #include "vulkan/vulkan.h"
4 #include "vulkan/vk_sdk_platform.h"
5 #else
6 #define VK_USE_PLATFORM_XCB_KHR
7 #include <vulkan/vulkan.h>
8 #include <vulkan/vk_sdk_platform.h>
9 #endif

Note that we define different _PLATFORM_ macros for Windows and Linux.

注意,我們為Windows和Linux定義了不同的_PLATFORM_巨集。

These macros enable the extensions that support the windowing systems in each OS.

這些巨集定義啟用了支援各個作業系統的視窗的擴充套件。

The reason that we include the headers like that is that on Linux they are installed in a system directory ( /usr/include/vulkan ) whereas on Windows they are installed in a standard directory.

我們這樣include標頭檔案的原因,是在Linux上它們位於系統資料夾(/usr/include/vulkan )而在Windows上它們位於標準資料夾。

Let's start by reviewing the class OgldevVulkanCore whose job is to create and maintain the core objects (note that I'm using red in order to mark all Vulkan structs, enums, functions, etc):

開始時,我們來了解一下OgldevVulkanCore型別,它負責建立和維護核心物件(注意,我用紅色標記所有Vulkan的struct,enum,函式等):

class OgldevVulkanCore
{
public:
    OgldevVulkanCore(const char* pAppName);
    ~OgldevVulkanCore();
    
    bool Init(VulkanWindowControl* pWindowControl);
              
    const VkPhysicalDevice& GetPhysDevice() const;
    
    const VkSurfaceFormatKHR& GetSurfaceFormat() const;
    
    const VkSurfaceCapabilitiesKHR GetSurfaceCaps() const;
    
    const VkSurfaceKHR& GetSurface() const { return m_surface; }
    
    int GetQueueFamily() const { return m_gfxQueueFamily; }
    
    VkInstance& GetInstance() { return m_inst; }
    
    VkDevice& GetDevice() { return m_device; }
    
private:
    void CreateInstance();
    void CreateSurface();
    void SelectPhysicalDevice();
    void CreateLogicalDevice();

    // Vulkan objects
    VkInstance m_inst;
    VkDevice m_device;
    VkSurfaceKHR m_surface;
    VulkanPhysicalDevices m_physDevices;
        
    // Internal stuff
    std::string m_appName;
    int m_gfxDevIndex;
    int m_gfxQueueFamily;
};

This class has three pure Vulkan members (m_inst, surface and m_device) as well as a vector of Vulkan objects called m_physDevices (see the definition below).

這個型別有3個純Vulkan成員(m_inst、surface和m_device),一個Vulkan物件的列表m_physDevices。

In addition, we have members to keep the application name, an index to the physical device we will be using and an index to the queue family.

另外,我們分別用一個成員記錄應用程式名,對physical device的索引和對queue family的索引。

The class also contains a few getter functions and an Init() function that set's everything up.

這個類還包含幾個getter函式和一個初始化函式Init()。

Let's see what it does.

我們看看它做了什麼。

 1 void OgldevVulkanCore::Init(VulkanWindowControl* pWindowControl)
 2 { 
 3     std::vector<VkExtensionProperties> ExtProps;
 4     VulkanEnumExtProps(ExtProps);
 5     
 6     CreateInstance();
 7     
 8 #ifdef WIN32
 9     assert(0);
10 #else
11     m_surface = pWindowControl->CreateSurface(m_inst);
12     assert(m_surface);          
13 #endif
14     printf("Surface created\n");
15 
16     VulkanGetPhysicalDevices(m_inst, m_surface, m_physDevices);
17     SelectPhysicalDevice();
18     CreateLogicalDevice();
19 }

This function takes a pointer to a VulkanWindowControl object.

這個函式接收一個VulkanWindowControl型別的物件指標。

We will review this object later.

我們稍後會得到這個物件。

For now it suffices to say that this is an OS specific class whose job is to create a window surface where rendering will take place.

現在這裡就可以說明,這是個作業系統相關的型別,它的工作是建立一個視窗表面,是渲染的場所。

As in OpenGL, the Vulkan core spec does not include windowing.

和OpenGL中一樣,Vulkan核心不包含視窗。

This task is left to extensions and we have windowing extensions for all major operating systems.

這個任務留給了擴充套件,我們有各個主流作業系統的擴充套件。

An extension is simply an addition to Vulkan which is not part of the core spec.

擴充套件,是一個Vulkan的附加內容,不屬於其核心。

Members of Khronos can publish their own extensions and add them to the registry.

Khronos的成員可以釋出他們自己的擴充套件,並新增到registry。

Driver vendors can decide which extension they want to implement.

驅動程式開發商可以決定他們想實現哪些擴充套件。

The developer can then query for the list of available extensions during runtime and proceed accordingly.

開發者可以在執行時查詢可用擴充套件,然後據此開展工作。

 

We start by enumerating all these extensions.

我們從列舉所有擴充套件開始。

This is done in the following wrapper function:

這通過下述封裝函式完成:

 1 void VulkanEnumExtProps(std::vector& ExtProps)
 2 {
 3     uint NumExt = 0;
 4     VkResult res = vkEnumerateInstanceExtensionProperties(NULL, &NumExt, NULL);
 5     CHECK_VULKAN_ERROR("vkEnumerateInstanceExtensionProperties error %d\n", res);
 6     
 7     printf("Found %d extensions\n", NumExt);
 8     
 9     ExtProps.resize(NumExt);
10 
11     res = vkEnumerateInstanceExtensionProperties(NULL, &NumExt, &ExtProps[0]);
12     CHECK_VULKAN_ERROR("vkEnumerateInstanceExtensionProperties error %d\n", res);        
13     
14     for (uint i = 0 ; i < NumExt ; i++) {
15         printf("Instance extension %d - %s\n", i, ExtProps[i].extensionName);
16     }
17 }

The above function is a wrapper to the Vulkan API vkEnumerateInstanceExtensionProperties() which returns the extensions available on the system.

上述函式封裝了Vulkan函式vkEnumerateInstanceExtensionProperties(),它返回系統上可用的擴充套件。

The way we use this function is very common in Vulkan.

使用這個函式的這種方式,在Vulkan中很常見。

The first call returns the number of extensions which we use to resize the extension vector.

第一個函式呼叫返回擴充套件的數目,我們據此修改陣列的長度。

The second call retrieves the extensions themselves.

第二個函式呼叫獲取擴充套件本身。

The first parameter can be used to select a specific layer.

第一個引數可用於選擇個特定的層。

Vulkan is structured in a way that allows vendors to add logic layers that do stuff like validation, extra logging, etc.

Vulkan的結構,支援廠商新增邏輯層,做一些例如驗證、額外日誌等事情。

You can decide at runtime which layer you want to enable.

你可以決定在執行時你想啟用哪個層。

For example, while developing your application you can enable the validation layer and when distributing it to your users - disable it.

例如,在開發過程中你可以啟用驗證層,在釋出時則禁用它。

Since we are interested in all the extensions we use NULL as the layer.

由於我們對所有擴充套件都有興趣,我們用NULL為層引數。

 

Once we get the extension list we just print it.

一旦得到了擴充套件,就打印出來。

If you want to do some additional logic on the extension list you can do it here.

如果你想對擴充套件列表做點別的什麼,現在就可以。

The reason that we print it is to make sure the extensions we enable in the next function are included.

打印出來的目的是確保我們要在下一個函式中啟用的擴充套件是存在的。

Next in the initialization process is the creation of the Vulkan instance:

接下來,要做的初始化工作是建立Vulkan的instance:

 1 void OgldevVulkanCore::CreateInstance()
 2 {
 3     VkApplicationInfo appInfo = {};       
 4     appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
 5     appInfo.pApplicationName = m_appName.c_str();
 6     appInfo.engineVersion = 1;
 7     appInfo.apiVersion = VK_API_VERSION_1_0;
 8 
 9     const char* pInstExt[] = {
10 #ifdef ENABLE_DEBUG_LAYERS
11         VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
12 #endif        
13         VK_KHR_SURFACE_EXTENSION_NAME,
14 #ifdef _WIN32    
15         VK_KHR_WIN32_SURFACE_EXTENSION_NAME,
16 #else    
17         VK_KHR_XCB_SURFACE_EXTENSION_NAME
18 #endif            
19     };
20     
21 #ifdef ENABLE_DEBUG_LAYERS    
22     const char* pInstLayers[] = {
23         "VK_LAYER_LUNARG_standard_validation"
24     };
25 #endif    
26     
27     VkInstanceCreateInfo instInfo = {};
28     instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
29     instInfo.pApplicationInfo = &appInfo;
30 #ifdef ENABLE_DEBUG_LAYERS    
31     instInfo.enabledLayerCount = ARRAY_SIZE_IN_ELEMENTS(pInstLayers);
32     instInfo.ppEnabledLayerNames = pInstLayers;
33 #endif    
34     instInfo.enabledExtensionCount = ARRAY_SIZE_IN_ELEMENTS(pInstExt);
35     instInfo.ppEnabledExtensionNames = pInstExt;         
36 
37     VkResult res = vkCreateInstance(&instInfo, NULL, &m_inst);
38     CHECK_VULKAN_ERROR("vkCreateInstance %d\n", res);
39         
40 #ifdef ENABLE_DEBUG_LAYERS
41     // Get the address to the vkCreateDebugReportCallbackEXT function
42     my_vkCreateDebugReportCallbackEXT = reinterpret_cast(vkGetInstanceProcAddr(m_inst, "vkCreateDebugReportCallbackEXT"));
43     
44     // Register the debug callback
45     VkDebugReportCallbackCreateInfoEXT callbackCreateInfo;
46     callbackCreateInfo.sType       = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
47     callbackCreateInfo.pNext       = NULL;
48     callbackCreateInfo.flags       = VK_DEBUG_REPORT_ERROR_BIT_EXT |
49                                      VK_DEBUG_REPORT_WARNING_BIT_EXT |
50                                      VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT;
51     callbackCreateInfo.pfnCallback = &MyDebugReportCallback;
52     callbackCreateInfo.pUserData   = NULL;
53 
54     VkDebugReportCallbackEXT callback;
55     res = my_vkCreateDebugReportCallbackEXT(m_inst, &callbackCreateInfo, NULL, &callback);
56     CheckVulkanError("my_vkCreateDebugReportCallbackEXT error %d\n", res);
57 #endif    
58 }

In order to initialize the Vulkan library we must create an VkInstance object.

為了初始化Vulkan庫,我們必須建立一個VkInstance物件。

This object carries all the state of the application.

這個物件持有應用程式的所有狀態。

The function that creates it is called vkCreateInstance() and it takes most of its parameters in a VkInstanceCreateInfo structure.

建立它的函式是vkCreateInstance(),此函式的引數大部分都在VkInstanceCreateInfo 這個struct裡。

The parameters that we are interested in are the extensions and (optionally) the layers we want to enable.

我們感興趣的引數是需要啟用的擴充套件和層(如果有的話)。

The extensions are the generic surface extension and the OS specific surface extension.

擴充套件是通用表面擴充套件和作業系統相關的表面擴充套件。

The extensions and layers are identified by their name strings and for some of them the Khronos SDK provides a macro. 

擴充套件和層由字串格式的名字標識,其中有的由Khronos的SDK提供巨集定義。

VkInstanceCreateInfo also takes a pointer to a VkApplicationInfo structure.

VkInstanceCreateInfo還接收一個VkApplicationInfo結構體的指標。

This structure describes the application and allows the developer to put in the application name and some internal engine version.

這個結構體描述了應用程式,允許開發者放進應用程式名和一些內部機器版本。

An important field of VkApplicationInfo is apiVersion.

VkApplicationInfo的一個重要欄位是apiVersion。

This is the Vulkan version that the application is requesting and if the driver doesn't support it the call will fail.

這是應用程式請求的Vulkan版本,如果驅動不支援此版本,請求就會失敗。

We are requesting version 1.0 so it should be ok.

我們要請求的版本是1.0,應該沒問題。

Once we get the handle of the instance object we can register a function in the validation layer that will print warning and error messages.

一旦我們得到instance物件的控制代碼,我們就可以在驗證層註冊一個函式,用於列印警告和錯誤資訊。

We must first get a pointer to a function called vkCreateDebugReportCallbackEXT, then we populate a VkDebugReportCallbackCreateInfoEXT structure with flags for the stuff we want the driver to notify us about and a pointer to our debug function.

我們首先必須得到函式vkCreateDebugReportCallbackEXT的指標,然後傳入結構體VkDebugReportCallbackCreateInfoEXT (標記著我們想要驅動通知我們的內容)和一個除錯函式。

The actual registration is done by calling the function whose pointer we previously acquired.

實際註冊通過呼叫函式(通過函式指標)來完成。

We define the pointer to vkCreateDebugReportCallbackEXT and our debug callback as follows:

我們這樣定義vkCreateDebugReportCallbackEXT的指標和我們的除錯回撥函式:

 1 PFN_vkCreateDebugReportCallbackEXT my_vkCreateDebugReportCallbackEXT = NULL; 
 2 VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback(
 3     VkDebugReportFlagsEXT       flags,
 4     VkDebugReportObjectTypeEXT  objectType,
 5     uint64_t                    object,
 6     size_t                      location,
 7     int32_t                     messageCode,
 8     const char*                 pLayerPrefix,
 9     const char*                 pMessage,
10     void*                       pUserData)
11 {
12     printf("%s\n", pMessage);
13     return VK_FALSE;    // Since we don't want to fail the original call
14 }

The next step is to create a window surface and for that we use the VulkanWindowControl object that the Init() function got as a pointer parameter.

下一步,要建立視窗surface,為此,我們使用VulkanWindowControl物件,它是Init()函式的引數

We will review this class later so let's skip it for now (note that we need an instance in order to create a surface so this is why we do stuff in this order).

我們稍後將細說這個型別,暫時先跳過(注意,我們需要用instatnce來建立surface,這是我們按此順序做事的原因)。

Once we have an instance and a surface we are ready to get all the information we need on the physical devices on your system.

一旦我們有了instance和surface,我們就可以獲取你的系統上的physical device的所有資訊。

A physical device is either a discrete or an integrated graphics card on the platform.

一個physical device是平臺上的一個獨立顯示卡或繼承顯示卡。

For example, your system may have a couple of NVIDIA cards in a SLI formation and an Intel HD graphics GPU integrated into the CPU.

例如,你的系統裡可能有2個NIVDIA顯示卡(構成SLI陣型)和1個Intel高清圖形GPU,整合到CPU上。

In this case you have three physical devices.

這樣你就又3個physical device。

The function below retrieves all the physical devices and some of their characteristics and populates the VulkanPhysicalDevices structure.

下面的函式檢索所有的physical device及其特性,還給出VulkanPhysicalDevices結構體。

This structure is essentially a database of physical devices and their properties.

這個結構體本質上是physical device及其屬性的資料庫。

It is made up of several vectors (sometimes vectors of vectors) of various Vulkan objects.

它由若干陣列(有時是陣列的陣列)組成,元素為各種Vulkan物件。

In order to access a specific device you simply go to one of the members and index into that vector using the physical device index.

為了使用某個特定的device,你只需通過physical device索引找到某個成員和向量的索引。

So to get all the information on physical device 2 access m_device[2], m_devProps[2], etc.

即,為得到physical device2的資訊,只需使用m_device[2]、m_devProps[2]等。

The reason I structured it like that (and not a structure per device with all the info inside it) is because it matches the way the Vulkan APIs work.

我這樣設計它的原因是,這樣和Vulkan的API風格匹配。

You provide an array of XYZ and get all the XYZ objects for all physical devices.

你提供XYZ陣列,就得到所有physical device的XYZ物件。

Here's the definition of that database structure:

下面是資料庫結構體的定義:

1 struct VulkanPhysicalDevices {
2     std::vector<VkPhysicalDevice> m_devices;
3     std::vector<VkPhysicalDeviceProperties> m_devProps;
4     std::vector< std::vector<VkQueueFamilyProperties> > m_qFamilyProps;
5     std::vector< std::vector<VkBool32> > m_qSupportsPresent;
6     std::vector< std::vector<VkSurfaceFormatKHR> > m_surfaceFormats;
7     std::vector<VkSurfaceCapabilitiesKHR> m_surfaceCaps;
8 };

Now let's take a look at the function that populates the database.

現在來看看用於填充資料庫的函式。

The first two parameters are the instance and surface.

前2個引數是instance和surface。

The third parameter is where the result will go to.

第三個引數是結果儲存的位置。

We will review this function step by step.

我們一步步地觀察這個函式。

1 void VulkanGetPhysicalDevices(const VkInstance& inst, const VkSurfaceKHR& Surface, VulkanPhysicalDevices& PhysDevices)
2 {
3     uint NumDevices = 0;
4     
5     VkResult res = vkEnumeratePhysicalDevices(inst, &NumDevices, NULL);
6     CHECK_VULKAN_ERROR("vkEnumeratePhysicalDevices error %d\n", res);
7     printf("Num physical devices %d\n", NumDevices);

The first thing we do is get the number of physical devices. Again we see the usage of dual call - first to get the number of items and then to get the items themselves.

我們要做的第一件事,是獲取physical device的數量。我們再次看到了“先獲取專案數量,再獲取專案內容”的用法。

1     PhysDevices.m_devices.resize(NumDevices);
2     PhysDevices.m_devProps.resize(NumDevices);
3     PhysDevices.m_qFamilyProps.resize(NumDevices);
4     PhysDevices.m_qSupportsPresent.resize(NumDevices);
5     PhysDevices.m_surfaceFormats.resize(NumDevices);
6     PhysDevices.m_surfaceCaps.resize(NumDevices);

We can now resize our database so that we will have enough space to retrieve the info on all devices.

我們現在可以設定資料庫的大小,讓它有足夠的空間來檢索所有device的資訊。

1     res = vkEnumeratePhysicalDevices(inst, &NumDevices, &PhysDevices.m_devices[0]);
2     CHECK_VULKAN_ERROR("vkEnumeratePhysicalDevices error %d\n", res);

We do the same call again, this time providing the address of a vector in VkPhysicalDevice as the result.

我們呼叫了相同的函式,這次提供了VkPhysicalDevice裡的陣列地址,用於儲存結果。

Using STL vectors is handly because they function the same way as standard arrays, so the address of the first element is the address of the array.

使用STL陣列很方便,因為它們和標準陣列功能相同,所以第一個元素的地址就是陣列的地址。

From our point of view VkPhysicalDevice is just a handle that represents the identity of the physical device.

從我們的角度看,VkPhysicalDevice 只是一個代表physical device的控制代碼。

Now we begin a loop over the number of physical devices where we will extract more info for one device at a time.

現在我們開始一個迴圈,對每個physical device,我們提取更多的資訊。

1 for (uint i = 0 ; i < NumDevices ; i++) {
2         const VkPhysicalDevice& PhysDev = PhysDevices.m_devices[i];
3         vkGetPhysicalDeviceProperties(PhysDev, &PhysDevices.m_devProps[i]);

We start by getting the properties of the current device.

開始,我們獲取當前device的屬性。

m_devProps is a vector of VkPhysicalDeviceProperties.

m_devProps是VkPhysicalDeviceProperties的陣列。

This structure contains information about the device such as a name, versions, IDs, etc.

這個struct包含device的資訊,例如名字,版本,編號,等。

We print some of these properties in the next couple of printf statements:

我們打印出一些屬性來:

1         printf("Device name: %s\n", PhysDevices.m_devProps[i].deviceName);
2         uint32_t apiVer = PhysDevices.m_devProps[i].apiVersion;
3         printf("    API version: %d.%d.%d\n", VK_VERSION_MAJOR(apiVer),
4                                           VK_VERSION_MINOR(apiVer),
5                                           VK_VERSION_PATCH(apiVer));

Next we get the properties of all the queue families that the physical device supports.

下一步,我們得到physical device支援的所有queue family的屬性。

There are four categories of operations that a GPU can perform :

一個GPU能施展的操作有4種:

  1. Graphics - 2D/3D rendering (same as OpenGL).
    圖形 - 2D/3D渲染(和OpenGL相同)
  2. Compute - general processing work which is not rendering in nature. This can be scientific calculations that need the parallel power of the GPU but not the 3D pipeline.
    計算 - 通用處理工作,不限於渲染。可以是科學計算(需要GPU的平行計算能力而不需要3D管道)。
  3. Transfer - copying of buffers and images.
    轉移 - 複製快取和影象。
  4. Sparse Memory Management - sparse resources are non continguous. This category includes operations to process them.
    稀疏記憶體管理 - 稀疏資源是不連續的。這類操作用來處理它們。

The work that we send to the device is executed in a queue.

我們傳送到device的工作會在一個queue裡執行。

A device exposes one or more queue families and each family contains one or more queues.

一個device暴露一個或多個queue family,每個family包含一個或多個queue。

Each family supports some combination of the four categories above.

每個family都支援這4種操作的組合。

The queues in each family all support the family functionality.

一個family的queue支援此family的全部功能。

For example, my GPU has two families.

例如,我的GPU有2個family。

The first one contains 16 queues that support all the four categories and the other has just one queue that only supports transfer.

第一個包含16個queue,它們支援所有4種操作;另一個只有1個queue,它只支援轉移操作。

You can take advantage of the specific architecture of the device at runtime in order to tailor the behavior of your app in order to increase performance.

你可以利用device的特定架構,在執行時定製你的app的行為,從而提升效能。

 1         uint NumQFamily = 0;         
 2         
 3         vkGetPhysicalDeviceQueueFamilyProperties(PhysDev, &NumQFamily, NULL);
 4     
 5         printf("    Num of family queues: %d\n", NumQFamily);
 6 
 7         PhysDevices.m_qFamilyProps[i].resize(NumQFamily);
 8         PhysDevices.m_qSupportsPresent[i].resize(NumQFamily);
 9 
10         vkGetPhysicalDeviceQueueFamilyProperties(PhysDev, &NumQFamily, &(PhysDevices.m_qFamilyProps[i][0]));

In the code above we get the number of family properties for the current device, resize m_qFamilyProps and m_qSupportsPresent (note that both are vectors of vectors so we must first index into the current device) and after that we get a vector of properties and store it in the database.

上述程式碼中,我們得到了當前device的family屬性的數目,修正m_qFamilyProps和m_qSupportsPresent的大小(注意,它們都是陣列的陣列,所以必須先索引到當前device),然後我們得到了屬性陣列,將其儲存到資料庫。

1         for (uint q = 0 ; q < NumQFamily ; q++) {
2             res = vkGetPhysicalDeviceSurfaceSupportKHR(PhysDev, q, Surface, &(PhysDevices.m_qSupportsPresent[i][q]));
3             CHECK_VULKAN_ERROR("vkGetPhysicalDeviceSurfaceSupportKHR error %d\n", res);
4         }

While we are still on the queue family subject let's query each family and check whether it supports presentation. 

既然我們孩子討論queu family,我們查詢一下每個family,檢查它是否支援presentation。

vkGetPhysicalDeviceSurfaceSupportKHR() takes a physical device, a surface and a queue family index and returns a boolean value which indicates whether this combination of device and family can present on the specified surface.

函式vkGetPhysicalDeviceSurfaceSupportKHR()接收一個physical device,一個surface和一個queue family索引為引數,返回一個boolean值,表示這個組合是否能表示一個特定的surface。

 1         uint NumFormats = 0;
 2         vkGetPhysicalDeviceSurfaceFormatsKHR(PhysDev, Surface, &NumFormats, NULL);
 3         assert(NumFormats > 0);
 4         
 5         PhysDevices.m_surfaceFormats[i].resize(NumFormats);
 6         
 7         res = vkGetPhysicalDeviceSurfaceFormatsKHR(PhysDev, Surface, &NumFormats, &(PhysDevices.m_surfaceFormats[i][0]));
 8         CHECK_VULKAN_ERROR("vkGetPhysicalDeviceSurfaceFormatsKHR error %d\n", res);
 9     
10         for (uint j = 0 ; j < NumFormats ; j++) {
11             const VkSurfaceFormatKHR& SurfaceFormat = PhysDevices.m_surfaceFormats[i][j];
12             printf("    Format %d color space %d\n", SurfaceFormat.format , SurfaceFormat.colorSpace);
13         }

Next up is the surface format.

接下來是surface的格式。

Each surface can support one or more formats.

每個surface都可以支援一個或多個格式。

A format is simply the way data is arranged on the surface.

格式是資料在surface上安排的方式。

In general, a format specifies the channels in each pixel and the type of each channel (float, int, etc).

一般的,格式描述了每個畫素的通道和每個通道的型別(float、int,等)。

For example, VK_FORMAT_R32G32B32_SFLOAT is a surface format with three channels (red, green and blue) of the 32bit floating point type.

例如,VK_FORMAT_R32G32B32_SFLOAT這個surface格式,有3個通道(RGB),每個都是32位的浮點型數。

The format of the surface is critical because it determines the way data on the surface is converted or interpreted before various operations (e.g. displaying it to the screen).

Surface的格式是嚴格的,因為它決定了資料在surface上的轉換和翻譯方式(例如在顯示到螢幕時)。

To get the format we need both the physical device and the surface itself because the devices may not be compatible in terms of the surface formats that they can work with.

為了得到格式,我們需要physical device和surface作為引數,因為device可能不相容surface的格式。

There can be multiple surface formats available which is why again we have a vector of vectors here.

Surface的格式可能有多個,所以我們需要用陣列的陣列。

We will need the surface format later which is why it is part of the database. Now we query the surface capabilities:

稍後我們將需要surface格式,所以它是資料庫的一部分。現在我們查詢surface的capabilities:

1         res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(PhysDev, Surface, &(PhysDevices.m_surfaceCaps[i]));
2         CHECK_VULKAN_ERROR("vkGetPhysicalDeviceSurfaceCapabilitiesKHR error %d\n", res);
3        
4         VulkanPrintImageUsageFlags(PhysDevices.m_surfaceCaps[i].supportedUsageFlags);
5     }
6 }

The VkSurfaceCapabilitiesKHR structure describes the capabilities of the physical device when working with a specific surface.

VkSurfaceCapabilitiesKHR 結構體描述了physical device與特定surface合作時的capabilities。

This includes the minimum and maximum images that can be created on the swap chain, the minimum and maximum extents (size of the area that can be rendered), supported rotation, etc.

這包含交換鏈上可建立的最小和最大影象,最小和最大擴充套件(可被渲染的大小),支援的旋轉,等。

There is one such structure for each combination of a physical device and surface and we store it in the m_surfaceCaps vector.

每個physical device和surface的組合都有一個這樣的結構體,我們將其儲存到m_surfaceCaps陣列。

We completed getting all the information on the physical devices! (note that some of it is specific to the combination of a device and surface).

我們完全獲取了physical device的所有資訊!(注意,某些是針對特定的device和surface組合的)

The next step in the Init() function is to select one of the physical devices and one of the queues to do the processing.

函式Init()中的下一步是,選擇一個physical device和一個queue。

The following function does exactly that:

下述函式實現了這個步驟:

 1 void OgldevVulkanCore::SelectPhysicalDevice()
 2 {
 3     for (uint i = 0 ; i < m_physDevices.m_devices.size() ; i++) {
 4                 
 5         for (uint j = 0 ; j < m_physDevices.m_qFamilyProps[i].size() ; j++) {
 6             VkQueueFamilyProperties& QFamilyProp = m_physDevices.m_qFamilyProps[i][j];
 7             
 8             printf("Family %d Num queues: %d\n", j, QFamilyProp.queueCount);
 9             VkQueueFlags flags = QFamilyProp.queueFlags;
10             printf("    GFX %s, Compute %s, Transfer %s, Sparse binding %s\n",
11                     (flags & VK_QUEUE_GRAPHICS_BIT) ? "Yes" : "No",
12                     (flags & VK_QUEUE_COMPUTE_BIT) ? "Yes" : "No",
13                     (flags & VK_QUEUE_TRANSFER_BIT) ? "Yes" : "No",
14                     (flags & VK_QUEUE_SPARSE_BINDING_BIT) ? "Yes" : "No");
15             
16             if (flags & VK_QUEUE_GRAPHICS_BIT) {
17                 if (!m_physDevices.m_qSupportsPresent[i][j]) {
18                     printf("Present is not supported\n");
19                     continue;
20                 }
21 
22                 m_gfxDevIndex = i;
23                 m_gfxQueueFamily = j;
24                 printf("Using GFX device %d and queue family %d\n", m_gfxDevIndex, m_gfxQueueFamily);
25                 break;
26             }
27         }
28     }
29     
30     if (m_gfxDevIndex == -1) {
31         printf("No GFX device found!\n");
32         assert(0);
33     }    
34 }

In a more advanced application you can have multiple queues on multiple devices but we are keeping it very simple.

在高階應用程式中你可以使用多個device上的多個queue,但是我們這裡就簡單為上。

The nested loop in this function traverses the list of devices and the list of queue families for each device.

函式中的巢狀迴圈遍歷device陣列及其每個queue family陣列。

We are searching for a physical device with a queue family that support graphics functionality as well as being able to present on the surface for which the database was initialized.

我們要找這樣一個phys