CSharpGL(56)[譯]Vulkan入門

CSharpGL(56)[譯]Vulkan入門html

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

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

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).windows

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

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.bash

我不會上來就拋出一堆新的特性的細節。簡單來講,相比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. 

既然咱們還在討論queue 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.

咱們要找這樣一個physical device及其queue family——它支持圖形功能,且能在初始化了數據庫的surface上顯示。

When we find such device and family we store their corresponding indices and quit the loop.

當咱們找到這樣的device和family時,咱們保存它們的索引,退出循環。

This device and family pair is going to serve us throughout this tutorial.

這個device和family對將爲咱們整個教程服務。

If no such pair is found the application aborts.

若是沒有這樣的對,應用程序就停止。

It means the system doesn't meet the minimum requirements to run the code.

這意味着系統不支持運行此代碼所需的最低要求。

The last thing we need to do to initialiaze our core object is to create a logical device:

關於初始化核心對象,最後要作的是建立logical device:

 1 void OgldevVulkanCore::CreateLogicalDevice()  2 {  3     float qPriorities = 1.0f;  4     VkDeviceQueueCreateInfo qInfo = {};  5     qInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;  6     qInfo.queueFamilyIndex = m_gfxQueueFamily;  7     qInfo.queueCount = 1;  8     qInfo.pQueuePriorities = &qPriorities;  9     
10     const char* pDevExt[] = { 11  VK_KHR_SWAPCHAIN_EXTENSION_NAME 12  }; 13     
14     VkDeviceCreateInfo devInfo = {}; 15     devInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 16     devInfo.enabledExtensionCount = ARRAY_SIZE_IN_ELEMENTS(pDevExt); 17     devInfo.ppEnabledExtensionNames = pDevExt; 18     devInfo.queueCreateInfoCount = 1; 19     devInfo.pQueueCreateInfos = &qInfo; 20        
21     VkResult res = vkCreateDevice(GetPhysDevice(), &devInfo, NULL, &m_device); 22 
23     CHECK_VULKAN_ERROR("vkCreateDevice error %d\n", res); 24    
25     printf("Device created\n"); 26 }

The Vulkan architecture separates the concept of a physical device which is part of the real system and the logical device which is an abstraction on top of it.

Vulkan架構區分了physical device和logical device,一個是真實的系統的一部分,另外一個是在此基礎上的抽象。

The logical device is what we use in the application to create most of the objects that are below the device level (queues, swap chains, etc).

logical device是咱們在應用程序中用於建立大多數屬於device層對象(queue,交換鏈,等)的工具。

This design choice enables flexibility in the management of device resource as well as configuration of device behavior to fit our needs.

這個設計決策增長了可擴展性,使得device資源管理和行爲配置能靈活適應咱們的需求。

The logical device allows us to expose only parts of the physical device.

且logical device容許咱們只暴露physical device的一部分。

For example, if the physical device supports both graphics and compute we can expose only graphics via the logical device.

例如,若是physical device同時支持圖形和計算,咱們能夠經過logical device來只暴露圖形部分。

 

In order to create the device we need one VkDeviceCreateInfo structure and one or more VkDeviceQueueCreateInfo structures. 

爲建立device,咱們須要1個VkDeviceCreateInfo 結構體和若干VkDeviceQueueCreateInfo 結構體。

VkDeviceCreateInfo is the main point of device definition.

VkDeviceCreateInfo 是device定義的主要部分。

In that struct we set a pointer to an array of extensions we want to enable.

這個結構體中,咱們設置一個須要啓用的擴展的數組的指針。

We need to enable the swap chain which is defined as part of an extension and not part of the core spec.

咱們須要啓用交換鏈,它是擴展的一部分,不是核心。

A swap chain is an array of surface images that can be presented.

交換鏈是surface圖像數組,能夠被顯示。

We also need the size of the extension array.

咱們還須要擴展數組的大小。

The second thing we need is a pointer to an array of VkDeviceQueueCreateInfo structs (and the size of that array).

咱們須要的第二個是VkDeviceQueueCreateInfo 結構體數組的指針(及其大小)。

For each queue family we want to enable we must have one VkDeviceQueueCreateInfo struct which describes it.

想啓用一個queue family,就必須有一個描述它的VkDeviceQueueCreateInfo 結構體。

This struct specifies the index of the queue family (which we got earlier in SelectPhysicalDevice()), the number of queues we want to create from that family and for each queue we can specify a different priority.

這個結構體記錄了queue family的索引(咱們以前在SelectPhysicalDevice()中獲得了),咱們但願今後family中建立的queue的數量,對每一個queue咱們均可以指定一個不一樣的優先級。

In this tutorial we we won't deal with priorities.

本教程中咱們不會處理優先級的問題。

We have just one queue and we set the priority to 1.0.

咱們只有1個queue,將其優先級設置爲1.0。

This completes the initialization process of our OgldevVulkanCore class, but to actually call the Init() function we need a VulkanWindowControl which is a class I created to wrap the management of the window surface.

至此OgldevVulkanCore 類型的初始化就完成了。 可是想調用Init()函數,咱們須要一個VulkanWindowControl 類型,這是我建立的峯值了窗口surface的管理工做的類型。

Remember that this job is not part of the core Vulkan spec and since every operating system needs entirely different code here I found that it makes sense to separate all window management to a different class and implement OS specific versions in the derivatives of this class.

記住,這個工做不是Vulkan核心的一部分。因爲每種操做系統都須要徹底不一樣的代碼,我任務應該將窗口管理單獨成立一個類,而後繼承之,針對各個操做系統實現特定的程序。

This class is actually an interface and it is defined as follows:

這個類其實是個接口,其定義以下:

 1 class VulkanWindowControl  2 {  3 protected:  4  VulkanWindowControl() {};  5 
 6     ~VulkanWindowControl() {};  7 
 8 public:  9 
10     virtual bool Init(uint Width, uint Height) = 0; 11     
12     virtual VkSurfaceKHR CreateSurface(VkInstance& inst) = 0; 13 };

As you can see, this class is very simple.

如你所見,這個類很簡單。

It doesn't have any members.

它沒有任何成員。

Its constructor and destructor are protected so you can't create an instance of this class directly.

它的構造函數和析構函數都是protected,因此你不能直接建立此類的實例。

You can only create an instance of a derived class.

你只能建立它的繼承類的實例。

There are two public methods.

有2個public的方法。

One to initialize the object and one to create a Vulkan surface.

一個是初始化對象,另外一個是建立Vulkan的surface。

So for every OS we are free to do OS specific stuff but we have to return a VkSurfaceKHR which is a handle to the window surface.

因此,對每種操做系統,咱們能夠自由地作特定操做系統的事情,可是必須返回一個VkSurfaceKHR 對象,它是窗口surface的句柄。

Note that CreateSurface() takes an VkInstance reference as a parameter which means we can init this class before we create our VulkanCoreObject but we have to initialize the VulkanCoreObject before we call CreateSurface().

注意,函數CreateSurface()接收一個VkInstance 參數,這意味着咱們能夠在建立VulkanCoreObject以前初始化這個類,可是咱們必須在調用函數CreateSurface()以前初始化VulkanCoreObject 。

We will review this again when we get to the main() function so don't worry about it.

咱們將重審這一點,當咱們涉及main()函數的時候。因此不用擔憂它。

The concrete classes which implements VulkanWindowControl are XCBControl on Linux and Win32Control on Windows.

實現VulkanWindowControl 的具體類型,在Linux上是XCBControl ,在Windows上是Win32Control 。

We will review Linux first and then Windows:

咱們先來看Linux上的,而後再看Windows上的:

 1 class XCBControl : public VulkanWindowControl  2 {  3 public:  4  XCBControl();  5     
 6     ~XCBControl();  7     
 8     virtual bool Init(uint Width, uint Height);  9     
10     virtual VkSurfaceKHR CreateSurface(VkInstance& inst); 11 
12  private: 13     xcb_connection_t* m_pXCBConn; 14     xcb_screen_t* m_pXCBScreen; 15  xcb_window_t m_xcbWindow; 16 };

XWindow is the most common windowing system on Unix and Linux.

XWindow是Unix和Linux上最多見的窗口系統。

It works in a client/server model.

它以客戶端/服務器模式工做。

The server manages the screen, keyboard and mouse.

服務器管理屏幕,鍵盤和鼠標。

The client is an application that wants to display something on the server.

客戶端是應用程序,它想要顯示服務器的上的某些東西。

It connects to the server via the X11 protocol and sends it requests to create a window, control the mouse/keyboard, etc.

它經過X11協議與服務器連接,向一個窗口發送請求,控制鼠標鍵盤,等。

The two most common implementations of the X11 protocol is Xlib and XCB and Vulkan provides extensions to use both. 

X11協議的最多見的2種實現是Xlib和XCB,Vulkan提供了擴展來使用這2種實現。

XCB is more modern so this is what we will use on Linux. 

XCB 更現代化,因此咱們將在Linux上用它。

XCBControl implements the VulkanWindowControl class using XCB calls.

經過調用XCB,XCBControl 實現了VulkanWindowControl 類。

Remember that the target of this object is to create an OS specific window and connect it to a Vulkan surface so that Vulkan will be able to render to it.

記住,這個對象的目標,是建立操做系統特定的窗口,連接到Vulkan的surface,以便Vulkan能在它上面渲染。

Let's start by creating the window.

開始,讓咱們來建立窗口。

 1 void XCBControl::Init(uint Width, uint Height)  2 {  3     m_pXCBConn = xcb_connect(NULL, NULL);  4     
 5     int error = xcb_connection_has_error(m_pXCBConn);  6     
 7     if (error) {  8         printf("Error opening xcb connection error %d\n", error);  9         assert(0); 10  } 11     
12     printf("XCB connection opened\n");

The first thing we need to do is to connect to the XWindow server.

首先要作的,是連接到XWindow服務器。

You are probably running on a GUI environment so the server is already running in the background.

你可能政治運行一個GUI環境,因此服務器已經啓動了。

xcb_connect() opens a connection to that server.

函數xcb_connect()會打開一個到服務器的連接。

It takes two parameters - the name of the server and a pointer to a preferred screen number (which will be populated by the XCB library).

它接收2個參數——服務器名字和屏幕編號的指針(由XCB庫提供)。

XWindow is very flexible.

XWindow可擴展性很強。

For example, it allows you to run the server on one machine and the client on another.

例如,它容許你在一臺機器上運行服務器,在領另外一臺機器上運行客戶端。

You can also run multiple instances of the same server on the same machine.

你也能夠在同一臺機器上運行服務器的多個實例。

To connect remotely you need to specify the name or IP as well as a display number in some specific string format and provide it in the first parameter.

爲了連接到遠程服務器,你須要標明服務器名字或IP地址以及顯示編號到第一個參數(字符串格式)裏。

To run the application locally it is enough to use NULL for both parameters.

若是是在本地運行應用程序,使用NULL爲參數值便可。

xcb_connect() returns a connection pointer which we store in the class.

xcb_connect()返回連接指針,咱們將其保存到類裏。

It always returns something so we have to check for errors using xcb_connectionn_has_error() in the manner that we see above.

它總會返回一些東西,因此咱們必須檢查是否有錯誤。方法是,用相似上文的方式,使用函數xcb_connectionn_has_error()。

1     const xcb_setup_t *pSetup = xcb_get_setup(m_pXCBConn); 2     
3     xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup); 4     
5     m_pXCBScreen = iter.data; 6     
7     printf("XCB screen %p\n", m_pXCBScreen);

A XWindow server can control multiple monitors and potentially run multiple screens on each monitor.

一個XWindow服務器能夠控制多個顯示器,在每一個顯示器上均可以運行多個窗口。

A screen is where applications are eventually executed.

一個窗口是應用程序執行(顯示)的地方。

It is defined by a width and height, a color depth, etc.

它由寬度,高度,顏色深度,等定義。

We want to get a handle to the current screen and there are two steps that we need to do.

咱們想要獲得當前窗口的句柄,這須要2步。

The first one is to use the xcb_get_setup() function to get access to the xcb_setup_t structure of the connection.

第一步是使用xcb_get_setup()函數獲得連接的結構體xcb_setup_t。

This struct contains a lot of info about the server.

這個結構體包括服務器的不少信息。

One of the things it includes is a list of screens.

其中之一是窗口列表。

To access this list we setup an iterator using the xcb_setup_roots_iterator() function.

爲讀取這個列表,咱們用xcb_setup_roots_iterator()函數設置一個枚舉器。

On a more robust piece of code what you will now see is a loop that traverses the list of screens, searching for the one the applications wants.

在一個更健壯的程序中,你將看到的是,在一個循環裏遍歷窗口列表,搜索應用程序須要的那個。

In our case it is enough to extract the first screen.

在咱們的例子中,只需突起第一個窗口便可。

The screen can be retrieved from the 'data' member of the iterator.

窗口能夠經過成員'data'來獲得。

1      m_xcbWindow = xcb_generate_id(m_pXCBConn);

We are now ready to create the actual window.

咱們如今能夠建立實際的窗口了。

The first step to do that is to generate a XID for it.

首先要生成一個XID。

The XID is an unsigned integer identifier of many XWindow resources.

XID是個無符號整數的標識符,用於標記不少XWindow資源。

When the client connects to a server it allocates a range of XIDs for it from a global range in the server.

當客戶端連接到服務器時,它在服務器全局範圍內申請若干XID。

When the client wants to create some resource on the server it starts by locally allocating an XID from the range it was given.

當客戶端想在服務器上建立某資源時,它從這若干XID上開始。

The following function calls can use that XID to identify the resource to be created.

下述函數可使用XID來標誌建立的資源。

This is somewhat unique in the approach where the server doesn't say "here's your new resource and its identifer is XYZ".

這和那種服務器說「這是你的心資源,它的標識符是XYZ」的方式不一樣。

Instead, the client says "hey server - I want to create a resource and here's the identifier for it".

相反,是客戶端說「嘿服務器,我想建立一個資源,這是它的標識符」。

xcb_generate_id() generates the XID for the window and we store it in the member variable m_xcbWindow.

xcb_generate_id()函數爲窗口生成XID,咱們將其保存到成員變量m_xcbWindow中。

 1     xcb_create_window( m_pXCBConn,             // the connection to the XWindow server
 2                        XCB_COPY_FROM_PARENT,                     // color depth - copy from parent window
 3                        m_xcbWindow,                              // XID of the new window
 4                        m_pXCBScreen->root,                       // parent window of the new window
 5                        0,                                        // X coordinate
 6                        0,                                        // Y coordinate
 7                        Width,                                    // window width
 8                        Height,                                   // window height
 9                        0,                                        // border width
10                        XCB_WINDOW_CLASS_INPUT_OUTPUT,            // window class - couldn't find any documentation on it
11                        m_pXCBScreen->root_visual,                // the visual describes the color mapping
12                        0, 13                        0);

xcb_create_window() does the window creation and takes no less than 13 parameters.

函數xcb_create_window()執行了建立窗口的任務,它接收很多於13個參數。

I have some comments on the parameters above and most of them are self-explanatory.

我給了這些參數註釋,其中有些參數是自說明的。

I won't go deeper than that.

我就再也不深刻了。

You can google for more info.

你能夠百度到更多信息。

1  xcb_map_window(m_pXCBConn, m_xcbWindow); 2  xcb_flush (m_pXCBConn); 3 }

In order to make the window visible we have to map it and flush the connection which is exactly what the above calls do.

爲了讓窗口可見,咱們必須把它映射和推動連接(譯者注:我也看不懂)。上述代碼就是作這些的。

 1    VkSurfaceKHR XCBControl::CreateSurface(VkInstance& inst)  2 {  3     VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {};  4     surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_XCB_SURFACE_CREATE_INFO_KHR;  5     surfaceCreateInfo.connection = m_pXCBConn;  6     surfaceCreateInfo.window = m_xcbWindow;  7     
 8  VkSurfaceKHR surface;  9     
10     VkResult res = vkCreateXcbSurfaceKHR(inst, &surfaceCreateInfo, NULL, &surface); 11     CHECK_VULKAN_ERROR("vkCreateXcbSurfaceKHR error %d\n", res); 12     
13     return surface; 14 }

The last function from the XCBControl class which we want to review is CreateSurface().

咱們要評析的XCBControl 類的最後一個函數是CreateSurface()。

This is basically a wrapper around the Vulkan function from the XCB extension vkCreateXcbSurfaceKHR().

它基本上是對來自XCB擴展的vkCreateXcbSurfaceKHR()函數的封裝。

We populate the VkXcbSurfaceCreateInfoKHR struct with the XWindow server connection pointer and the window which we created earlier.

咱們填入VkXcbSurfaceCreateInfoKHR 結構體,XWindow服務器連接指針和以前建立的窗口。

In return we get a generic handle to a Vulkan surface which we return back to the caller.

咱們獲得的是一個Vulkan的surface句柄,咱們將其返回給調用者。

Now let's review the corresponding class for Windows:

如今咱們來評析Windows對應的類型:

 1 class Win32Control : public VulkanWindowControl  2 {  3 public:  4     Win32Control(const char* pAppName);  5     
 6     ~Win32Control();  7     
 8     virtual void Init(uint Width, uint Height);  9     
10     virtual VkSurfaceKHR CreateSurface(VkInstance& inst); 11 
12  private: 13 
14  HINSTANCE m_hinstance; 15  HWND m_hwnd; 16  std::wstring m_appName; 17 };

As you can see, the interface is very similar for both operating systems.

如你所見,兩種操做系統的接口很類似。

In fact, Init() and CreateSurface() are identical because they are virtual functions.

實際上,Init()函數和CreateSurface()函數是相同的,由於它們都是虛函數。

We also have private members to store two Windows specific handles - HINSTANE and HWND.

咱們還用private成員保存2個Windows特有的句柄——HINSTNACE和HWND。

1 Win32Control::Win32Control(const char* pAppName) 2 { 3     m_hinstance = GetModuleHandle(NULL);; 4  assert(m_hinstance); 5     m_hwnd = 0; 6     std::string s(pAppName); 7     m_appName = std::wstring(s.begin(), s.end());8 }

Above you can see the constructor for the Win32Control class and I'm only showing it here so that you can see the way that the app name which is provided as an array of char is transformed into a std::wstring.

如上述代碼所示,你能夠看到Win32Control 類的構造函數。我將其展現在此,是爲了讓你看到app的名字是如何由char數組轉換爲字符串std::string的。

We do this for the CreateWindowEx() function below that needs a window name parameter with the LPCTSTR type.

咱們爲CreateWindowEx()函數搞這麼一出,它須要LPCTSTR類型的名稱做參數。

The standard wstring class helps us with that.

標準wstring類幫助咱們完成這一任務。

 1 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)  2 {  3     return DefWindowProc(hwnd, uMsg, wParam, lParam);  4 }  5 
 6 void Win32Control::Init(uint Width, uint Height)  7 {  8     WNDCLASSEX wndcls = {};  9 
10     wndcls.cbSize = sizeof(wndcls); 11     wndcls.lpfnWndProc = WindowProc; 12     wndcls.hInstance = m_hinstance; 13     wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); 14     wndcls.lpszClassName = L"ogldev"; 15 
16     if (!RegisterClassEx(&wndcls)) { 17         DWORD error = GetLastError(); 18         OGLDEV_ERROR("RegisterClassEx error %d", error); 19  } 20 
21     m_hwnd = CreateWindowEx(0, 22                             L"ogldev",                        // class name
23  m_appName.c_str(), 24                             WS_OVERLAPPEDWINDOW | WS_VISIBLE, // window style
25                             100, 100,                         // window start
26  Width, 27  Height, 28  NULL, 29  NULL, 30  m_hinstance, 31  NULL); 32 
33     if (m_hwnd == 0) { 34         DWORD error = GetLastError(); 35         OGLDEV_ERROR("CreateWindowEx error %d", error); 36  } 37 
38  ShowWindow(m_hwnd, SW_SHOW); 39 }

 

The code above is straightforward window creation stuff which I got from MSDN so I won't go too deeply into it.

上述代碼一目瞭然,是建立窗口相關的東西,我從MSDN拿來的,因此我就不深刻了。

We have to register the window class using RegisterClassEx().

咱們必須用RegisterClassEx()函數註冊窗口。

This class will be associated with the WindowProc() function that serve as the event handler for our window.

這個類將和WindowProc()函數關聯,它用做窗口的事件處理器。

Right now we are just calling the default handler of the system but on the next tutorial we will add more details to it.

目前咱們調用系統默認的處理器,下個教程咱們將添加更多細節。

The window is then created using CreateWindowEx() and finally displayed using ShowWindow().

如今窗口就被CreateWindowEx()建立好了,經過ShowWindow()顯示了出來。

 1 VkSurfaceKHR Win32Control::CreateSurface(VkInstance& inst)  2 {  3     VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};  4     surfaceCreateInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;  5     surfaceCreateInfo.hinstance = m_hinstance;  6     surfaceCreateInfo.hwnd = m_hwnd;  7     
 8  VkSurfaceKHR surface;  9     
10     VkResult res = vkCreateWin32SurfaceKHR(inst, &surfaceCreateInfo, NULL, &surface); 11     CHECK_VULKAN_ERROR("vkCreateXcbSurfaceKHR error %d\n", res); 12     
13     return surface; 14 }

 

CreateSurface() is also very similar to its Linux counterpart.

函數也和它的Linux版本很像。

The surfaceCreateInfo param just takes the instance instead of the XCB connection (and of course - the window handles are of different types).

參數接收instance,而不是XCB連接(固然還有不一樣類型的窗口句柄)。

 1 int main(int argc, char** argv)  2 {  3     VulkanWindowControl* pWindowControl = NULL;  4 #ifdef WIN32  5     pWindowControl = new Win32Control(pAppName);  6 #else            
 7     pWindowControl = new XCBControl();  8 #endif    
 9     pWindowControl->Init(WINDOW_WIDTH, WINDOW_HEIGHT); 10 
11     OgldevVulkanCore core("tutorial 50"); 12  core.Init(pWindowControl); 13     
14     return 0; 15 }

 

At last, we have reached the glue code in the form of the main() function.

最後,咱們抵達了main()函數中的膠水代碼。

If you are interested, you may start here and create the building blocks step by step so that you can check the return values of each Vulkan function call one at a time.

若是你有興趣,你能夠今後開始,一步步建立building block,這樣就能夠檢查每一個Vulkan函數的返回值了。

What happens in this function has already been generally discussed.

這個函數中的事咱們已經討論過了。

We allocate a derivative of the VulkanWindowControl class (be it Linux or Windows).

咱們申請一個VulkanWindowControl 類的子類(根據在Linux仍是Windows上)。

We initialize it (thus creating the OS specific window) and then create and init the OgldevVulkanCore object.

咱們初始化它(即建立操做系統特定的窗口),而後建立和初始化OgldevVulkanCore 對象。

We now have a Vulkan surface connected to an OS window, a Vulkan instance and device and a database with all the physical devices in the system.

咱們如今有了Vulkan的surface(連接到操做系統窗口),Vulkan的instance、device和系統中全部physical device的數據庫。

 

I hope you will find this tutorial useful.

我但願你發現這個教程有用。

The T-shirt that should go along with it says "I've written tons of Vulkan code and all I got was this lousy window".

T恤上寫着「我已經寫了成噸的Vulkan代碼,只獲得了這個糟糕的窗口」。

Indeed, we have accomplished a lot but we didn't get any rendering in return.

確實,咱們作了不少,可是沒有獲得任何渲染結果。

But don't despair.

可是不要絕望。

You now have a basic structure with a few of the core Vulkan objects.

你如今有了一些基礎結構和Vulkan核心對象。

On the next tutorials we will build on this further so stay tuned.

下一篇教程咱們將走得更遠,因此敬請關注。

相關文章
相關標籤/搜索