Error message here!

Hide Error message here!


Error message here!


Hide Error message here!


Error message here!




BIT祝威 2019-06-08 20:00:00 阅读数:318 评论数:0 点赞数:0 收藏数:0




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 was announced in Feb-2016 and after 24 years with OpenGL it is a completely new standard and a departure from the current model.


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.


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.


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


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.


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:




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.


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


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.


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



System Setup 系统安装

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


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


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.


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


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


On Linux the process may be a bit more involved.


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


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


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.


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



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.


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


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




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


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):


  • bash$ chmod +x
  • base$ ./ --target VulkanSDK- --noexec
  • base$ ln -s ~/VulkanSDK-1.0.30/ ~/VulkanSDK

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


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


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


This link makes it easier to switch your development environment to newer versions of the 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/
  • bash$ ./

If everything went well the examples were built into '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.


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:


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

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


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.


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


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.


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.




Installation on Windows is simpler than on 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.


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 建设和运行


My main development environment on Linux is Netbeans.


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


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/>
  • 加入编译命令:-I<path to VulkanSDK/>
  • To the link command: -L<path to VulkanSDK/> -lxcb -lvulkan'
  • 加入链接命令:-L<path to VulkanSDK/> -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.


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'。


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:


  1. Many Vulkan functions (particularly the ones used to create objects) take a structure as one of the parameters.
    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.
    The Vulkan architects decided to place a member called sType as the first member in all these structures.
    This member is of an enum type and every structure has its own code.
    This allows the driver to identify the type of the structure using only its address.
    All of these enum code have a VK_STRUCTURE_TYPE_ prefix.
    For example, the code for the structure used in the creation of the instance is called 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.
    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.
    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.
    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.
    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.
    These allocators allow you to control the process of allocating memory that the Vulkan functions need.
    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.
  5. Vulkan does not guarantee that its functions will be automatically exposed by the implementing library.
    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.
    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).
    My personal experience with my driver was that only vkCreateDebugReportCallbackEXT() was not available.
    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:


  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.
    You can enable the validation layer here by uncommenting ENABLE_DEBUG_LAYERS.
    This file contains a few Vulkan helper functions and macros as well as the definition of the VulkanWindowControl class.
  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.


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.


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


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


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


Here's the relevant piece of code:


 #ifdef _WIN32
 #include "vulkan/vulkan.h"
 #include "vulkan/vk_sdk_platform.h"
 #include <vulkan/vulkan.h>
 #include <vulkan/vk_sdk_platform.h>

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


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):


class OgldevVulkanCore
    OgldevVulkanCore(const char* pAppName);
    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; }
    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).


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.


Let's see what it does.


 void OgldevVulkanCore::Init(VulkanWindowControl* pWindowControl)
     std::vector<VkExtensionProperties> ExtProps;
 #ifdef WIN32
     m_surface = pWindowControl->CreateSurface(m_inst);
     printf("Surface created\n");
     VulkanGetPhysicalDevices(m_inst, m_surface, m_physDevices);

This function takes a pointer to a VulkanWindowControl object.


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.


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.


Members of Khronos can publish their own extensions and add them to the 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:


 void VulkanEnumExtProps(std::vector& ExtProps)
     uint NumExt = ;
     VkResult res = vkEnumerateInstanceExtensionProperties(NULL, &NumExt, NULL);
     CHECK_VULKAN_ERROR("vkEnumerateInstanceExtensionProperties error %d\n", res);
     printf("Found %d extensions\n", NumExt);
     res = vkEnumerateInstanceExtensionProperties(NULL, &NumExt, &ExtProps[]);
     CHECK_VULKAN_ERROR("vkEnumerateInstanceExtensionProperties error %d\n", res);        
     for (uint i =  ; i < NumExt ; i++) {
         printf("Instance extension %d - %s\n", i, ExtProps[i].extensionName);

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


The way we use this function is very common in 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.


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.



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:


 void OgldevVulkanCore::CreateInstance()
     VkApplicationInfo appInfo = {};       
     appInfo.pApplicationName = m_appName.c_str();
     appInfo.engineVersion = ;
     appInfo.apiVersion = VK_API_VERSION_1_0;
     const char* pInstExt[] = {
 #ifdef _WIN32    
     const char* pInstLayers[] = {
     VkInstanceCreateInfo instInfo = {};
     instInfo.pApplicationInfo = &appInfo;
     instInfo.enabledLayerCount = ARRAY_SIZE_IN_ELEMENTS(pInstLayers);
     instInfo.ppEnabledLayerNames = pInstLayers;
     instInfo.enabledExtensionCount = ARRAY_SIZE_IN_ELEMENTS(pInstExt);
     instInfo.ppEnabledExtensionNames = pInstExt;         
     VkResult res = vkCreateInstance(&instInfo, NULL, &m_inst);
     CHECK_VULKAN_ERROR("vkCreateInstance %d\n", res);
     // Get the address to the vkCreateDebugReportCallbackEXT function
     my_vkCreateDebugReportCallbackEXT = reinterpret_cast(vkGetInstanceProcAddr(m_inst, "vkCreateDebugReportCallbackEXT"));
     // Register the debug callback
     VkDebugReportCallbackCreateInfoEXT callbackCreateInfo;
     callbackCreateInfo.sType       = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
     callbackCreateInfo.pNext       = NULL;
     callbackCreateInfo.flags       = VK_DEBUG_REPORT_ERROR_BIT_EXT |
                                      VK_DEBUG_REPORT_WARNING_BIT_EXT |
     callbackCreateInfo.pfnCallback = &MyDebugReportCallback;
     callbackCreateInfo.pUserData   = NULL;
     VkDebugReportCallbackEXT callback;
     res = my_vkCreateDebugReportCallbackEXT(m_inst, &callbackCreateInfo, NULL, &callback);
     CheckVulkanError("my_vkCreateDebugReportCallbackEXT error %d\n", res);

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


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. 


VkInstanceCreateInfo also takes a pointer to a VkApplicationInfo structure.


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.


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


We are requesting version 1.0 so it should be ok.


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.


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:


 PFN_vkCreateDebugReportCallbackEXT my_vkCreateDebugReportCallbackEXT = NULL; 
 VKAPI_ATTR VkBool32 VKAPI_CALL MyDebugReportCallback(
     VkDebugReportFlagsEXT       flags,
     VkDebugReportObjectTypeEXT  objectType,
     uint64_t                    object,
     size_t                      location,
     int32_t                     messageCode,
     const char*                 pLayerPrefix,
     const char*                 pMessage,
     void*                       pUserData)
     printf("%s\n", pMessage);
     return VK_FALSE;    // Since we don't want to fail the original call

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.


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


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.


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.


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.


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:


 struct VulkanPhysicalDevices {
     std::vector<VkPhysicalDevice> m_devices;
     std::vector<VkPhysicalDeviceProperties> m_devProps;
     std::vector< std::vector<VkQueueFamilyProperties> > m_qFamilyProps;
     std::vector< std::vector<VkBool32> > m_qSupportsPresent;
     std::vector< std::vector<VkSurfaceFormatKHR> > m_surfaceFormats;
     std::vector<VkSurfaceCapabilitiesKHR> m_surfaceCaps;

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


The first two parameters are the instance and surface.


The third parameter is where the result will go to.


We will review this function step by step.


 void VulkanGetPhysicalDevices(const VkInstance& inst, const VkSurfaceKHR& Surface, VulkanPhysicalDevices& PhysDevices)
     uint NumDevices = ;
     VkResult res = vkEnumeratePhysicalDevices(inst, &NumDevices, NULL);
     CHECK_VULKAN_ERROR("vkEnumeratePhysicalDevices error %d\n", res);
     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的数量。我们再次看到了“先获取项目数量,再获取项目内容”的用法。


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


     res = vkEnumeratePhysicalDevices(inst, &NumDevices, &PhysDevices.m_devices[]);
     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.


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.


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,我们提取更多的信息。

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

We start by getting the properties of the current device.


m_devProps is a vector of VkPhysicalDeviceProperties.


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


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


         printf("Device name: %s\n", PhysDevices.m_devProps[i].deviceName);
         uint32_t apiVer = PhysDevices.m_devProps[i].apiVersion;
         printf("    API version: %d.%d.%d\n", VK_VERSION_MAJOR(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 :


  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.


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.


The queues in each family all support the family functionality.


For example, my GPU has two families.


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


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.


         uint NumQFamily = ;         
         vkGetPhysicalDeviceQueueFamilyProperties(PhysDev, &NumQFamily, NULL);
         printf("    Num of family queues: %d\n", NumQFamily);
         vkGetPhysicalDeviceQueueFamilyProperties(PhysDev, &NumQFamily, &(PhysDevices.m_qFamilyProps[i][]));

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.


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

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。

         uint NumFormats = ;
         vkGetPhysicalDeviceSurfaceFormatsKHR(PhysDev, Surface, &NumFormats, NULL);
         assert(NumFormats > );
         res = vkGetPhysicalDeviceSurfaceFormatsKHR(PhysDev, Surface, &NumFormats, &(PhysDevices.m_surfaceFormats[i][]));
         CHECK_VULKAN_ERROR("vkGetPhysicalDeviceSurfaceFormatsKHR error %d\n", res);
         for (uint j =  ; j < NumFormats ; j++) {
             const VkSurfaceFormatKHR& SurfaceFormat = PhysDevices.m_surfaceFormats[i][j];
             printf("    Format %d color space %d\n", SurfaceFormat.format , SurfaceFormat.colorSpace);

Next up is the surface format.


Each surface can support one or more formats.


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


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


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


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


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.


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


         res = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(PhysDev, Surface, &(PhysDevices.m_surfaceCaps[i]));
         CHECK_VULKAN_ERROR("vkGetPhysicalDeviceSurfaceCapabilitiesKHR error %d\n", res);

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:


 void OgldevVulkanCore::SelectPhysicalDevice()
     for (uint i =  ; i < m_physDevices.m_devices.size() ; i++) {
         for (uint j =  ; j < m_physDevices.m_qFamilyProps[i].size() ; j++) {
             VkQueueFamilyProperties& QFamilyProp = m_physDevices.m_qFamilyProps[i][j];
             printf("Family %d Num queues: %d\n", j, QFamilyProp.queueCount);
             VkQueueFlags flags = QFamilyProp.queueFlags;
             printf("    GFX %s, Compute %s, Transfer %s, Sparse binding %s\n",
                     (flags & VK_QUEUE_GRAPHICS_BIT) ? "Yes" : "No",
                     (flags & VK_QUEUE_COMPUTE_BIT) ? "Yes" : "No",
                     (flags & VK_QUEUE_TRANSFER_BIT) ? "Yes" : "No",
                     (flags & VK_QUEUE_SPARSE_BINDING_BIT) ? "Yes" : "No");
             if (flags & VK_QUEUE_GRAPHICS_BIT) {
                 if (!m_physDevices.m_qSupportsPresent[i][j]) {
                     printf("Present is not supported\n");
                 m_gfxDevIndex = i;
                 m_gfxQueueFamily = j;
                 printf("Using GFX device %d and queue family %d\n", m_gfxDevIndex, m_gfxQueueFamily);
     if (m_gfxDevIndex == -) {
         printf("No GFX device found!\n");

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


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.


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


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:

 void OgldevVulkanCore::CreateLogicalDevice()
     float qPriorities = 1.0f;
     VkDeviceQueueCreateInfo qInfo = {};
     qInfo.queueFamilyIndex = m_gfxQueueFamily;
     qInfo.queueCount = ;
     qInfo.pQueuePriorities = &qPriorities;
     const char* pDevExt[] = {
     VkDeviceCreateInfo devInfo = {};
     devInfo.enabledExtensionCount = ARRAY_SIZE_IN_ELEMENTS(pDevExt);
     devInfo.ppEnabledExtensionNames = pDevExt;
     devInfo.queueCreateInfoCount = ;
     devInfo.pQueueCreateInfos = &qInfo;
     VkResult res = vkCreateDevice(GetPhysDevice(), &devInfo, NULL, &m_device);
     CHECK_VULKAN_ERROR("vkCreateDevice error %d\n", res);
     printf("Device created\n");

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.


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.


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.


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.


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


 class VulkanWindowControl
     VulkanWindowControl() {};
     ~VulkanWindowControl() {};
     virtual bool Init(uint Width, uint Height) = ;
     virtual VkSurfaceKHR CreateSurface(VkInstance& inst) = ;

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.


You can only create an instance of a derived class.


There are two public methods.


One to initialize the object and one to create a 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.


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:


 class XCBControl : public VulkanWindowControl
     virtual bool Init(uint Width, uint Height);
     virtual VkSurfaceKHR CreateSurface(VkInstance& inst);
     xcb_connection_t* m_pXCBConn;
     xcb_screen_t* m_pXCBScreen;
     xcb_window_t m_xcbWindow;           

XWindow is the most common windowing system on Unix and 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.


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


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.


Let's start by creating the window.


 void XCBControl::Init(uint Width, uint Height)
     m_pXCBConn = xcb_connect(NULL, NULL);
     int error = xcb_connection_has_error(m_pXCBConn);
     if  (error) {
         printf("Error opening xcb connection error %d\n", error);
     printf("XCB connection opened\n");

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


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


xcb_connect() opens a connection to that server.


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


XWindow is very flexible.


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.


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


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


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


     const xcb_setup_t *pSetup = xcb_get_setup(m_pXCBConn);
     xcb_screen_iterator_t iter = xcb_setup_roots_iterator(setup);
     m_pXCBScreen =;
     printf("XCB screen %p\n", m_pXCBScreen);

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


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.


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


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.


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.


      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.


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


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


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


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


This is somewhat unique in the approach where the server doesn't say "here's your new resource and its identifer is 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_create_window( m_pXCBConn,             // the connection to the XWindow server
                        XCB_COPY_FROM_PARENT,                     // color depth - copy from parent window
                        m_xcbWindow,                              // XID of the new window
                        m_pXCBScreen->root,                       // parent window of the new window
                        ,                                        // X coordinate
                        ,                                        // Y coordinate
                        Width,                                    // window width
                        Height,                                   // window height
                        ,                                        // border width
                        XCB_WINDOW_CLASS_INPUT_OUTPUT,            // window class - couldn't find any documentation on it
                        m_pXCBScreen->root_visual,                // the visual describes the color mapping

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


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.


     xcb_map_window(m_pXCBConn, m_xcbWindow);
     xcb_flush (m_pXCBConn);

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


    VkSurfaceKHR XCBControl::CreateSurface(VkInstance& inst)
     VkXcbSurfaceCreateInfoKHR surfaceCreateInfo = {};
     surfaceCreateInfo.connection = m_pXCBConn;
     surfaceCreateInfo.window = m_xcbWindow;
     VkSurfaceKHR surface;
     VkResult res = vkCreateXcbSurfaceKHR(inst, &surfaceCreateInfo, NULL, &surface);
     CHECK_VULKAN_ERROR("vkCreateXcbSurfaceKHR error %d\n", res);
     return surface;

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


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.


Now let's review the corresponding class for Windows:


 class Win32Control : public VulkanWindowControl
     Win32Control(const char* pAppName);
     virtual void Init(uint Width, uint Height);
     virtual VkSurfaceKHR CreateSurface(VkInstance& inst);
     HINSTANCE   m_hinstance;
     HWND        m_hwnd;
     std::wstring m_appName;

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.


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


 Win32Control::Win32Control(const char* pAppName) 
     m_hinstance = GetModuleHandle(NULL);;
     m_hwnd = ;
     std::string s(pAppName); 
     m_appName = std::wstring(s.begin(), s.end()); }

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.


The standard wstring class helps us with that.


 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
     return DefWindowProc(hwnd, uMsg, wParam, lParam);
 void Win32Control::Init(uint Width, uint Height)
     WNDCLASSEX wndcls = {};
     wndcls.cbSize = sizeof(wndcls);
     wndcls.lpfnWndProc = WindowProc;
     wndcls.hInstance = m_hinstance; 
     wndcls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
     wndcls.lpszClassName = L"ogldev";
     if (!RegisterClassEx(&wndcls)) {
         DWORD error = GetLastError();
         OGLDEV_ERROR("RegisterClassEx error %d", error);
     m_hwnd = CreateWindowEx(,
                             L"ogldev",                        // class name
                             WS_OVERLAPPEDWINDOW | WS_VISIBLE, // window style
                             , ,                         // window start
     if (m_hwnd == ) {
         DWORD error = GetLastError();
         OGLDEV_ERROR("CreateWindowEx error %d", error);
     ShowWindow(m_hwnd, SW_SHOW);


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


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


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


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


 VkSurfaceKHR Win32Control::CreateSurface(VkInstance& inst)
     VkWin32SurfaceCreateInfoKHR surfaceCreateInfo = {};
     surfaceCreateInfo.hinstance = m_hinstance;
     surfaceCreateInfo.hwnd = m_hwnd;
     VkSurfaceKHR surface;
     VkResult res = vkCreateWin32SurfaceKHR(inst, &surfaceCreateInfo, NULL, &surface);
     CHECK_VULKAN_ERROR("vkCreateXcbSurfaceKHR error %d\n", res);
     return surface;


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


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


 int main(int argc, char** argv)
     VulkanWindowControl* pWindowControl = NULL;
 #ifdef WIN32
     pWindowControl = new Win32Control(pAppName);
     pWindowControl = new XCBControl();
     pWindowControl->Init(WINDOW_WIDTH, WINDOW_HEIGHT);
     OgldevVulkanCore core("tutorial 50");
     return ;


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


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


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.


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