在这一章我们将会介绍并解释什么是VulKan,我们将会介绍一些有关VulKan的关键概念,比如初始化,对象生命周期,VulKan的实例(实例化)和逻辑设备和物理设备,在这章的结束我们提供一个简单的VulKan应用可以进行初始化VulKan系统,找到VulKan可以使用的设备(物理设备,可以是你的独显或集成显卡等),并且得到他们的属性(比如显卡型号,显存多大等),最后我们收回内存清空关闭应用。
Vulkan是显卡和某些计算机设备的编程接口(说白了就是控制硬件的编程接口),VulKan的设备(显卡)是由一个处理器加上一些固定功能的硬件块组成并进行加速运算。由于硬件设备(下文大多都指显卡,但是还可以是别的硬件)大多都是多线程的所以VulKan可以进行并行运算。Vulkan还可以访问硬件设备的内存并且可以与主机内存(也就是插在主板上的内存条)进行沟通,双方内存能不能互相分享数据取决于你的应用需求。VulKan的硬件内存是向你公开的。
VulKan是一个显示API(我感觉更应该叫低级API),这是因为所有的任务你都要负起责任。一个驱动就是一个软件,这个软件携带着指令(你调用的VulKan函数)和数据结构(你给函数传的参数)并且翻译成硬件能够理解的语言(编译一下)。在老一点的API中比如OpenGL(欢迎元老级入场),OpenGL驱动需要追踪很多对象的很多状态(比如我画了个三角形,是用线显示,还是全部涂死,还是就是画三个点上去)并且管理内存并且同步,并且运行时还要检查你的应用错误(OpenGL大哥辛苦了)。这对于开发者来说是好事,但是OpenGL为了保证程序的正确运行会消耗大量的CPU时间和性能。为此VulKan做出了优化,在VulKan中所有的状态追踪,同步,内存管理和正确性检验全部交给了一个叫【Layers(层)】的东西,使用layers时必须激活它。在正常情况下layers不会参与你应用程序的执行(类似一个插件)。
就是因为是这样,VulKan可能用起来会有一些繁琐,并且需要小心使用。你需要做大量的前置工作才可以使得VulKan良好工作,不正确的使用或者一个不小心可能就会导致错误甚至是程序崩溃,在OpenGL老版本API中,你可能会通过调用获取信息的函数将信息输出。作为互补,VulKan提供更加丰富的硬件控制,一个干净整洁的线程模型,并且相比OpenGL能够拥有更加优越的性能。
而且VulKanAPI的设计初衷不仅仅被用于显卡,他可以被用于各种各样的硬件设备,比如数字信号处理器(digital signal processors,DSPs)和固定功能硬件。功能被分为三类,这三类彼此互相影响。
而且VulKan支持扩展(比如英伟达的RTX实时光线追踪就是一个扩展),通过是否开启扩展,来选择是否使用相应的扩展功能。
注::在这里简单解释一下
VulKan有着结构良好的功能层次。在最顶层是实例(instance),实例可以查询汇总所有支持VulKan的设备。每一个设备会有一个或者多个队列(queue)。执行你应用的请求最后是交给队列来执行。
实例是一个逻辑抽象(可以理解成面向对象的类),实例将上层应用和底层之间抽象出来。物理设备(显卡)也是实例的成员(可以理解成实例类的子类),并且物理设备上会有多个可选队列。
一个物理设备(physical device)通常表现为一个硬件(一张显卡)或者是内部集成的硬件。在任一的一个系统上硬件数量是固定的,除非该系统支持热插拔。
注:: 热插拔---就是在一些电脑或服务器上有一些设备可以在机器运作的情况下直接拔出和安装,无需在关机后插拔。一般家用PC机是不允许做出此种行为的,高实时性需求的硬件会有此功能。热插拔多出现于高级计算机和云服务器上(学云计算的小伙伴和玩服务器的知道,<手动滑稽>)。鼠标键盘也应该算是热插拔。
一个逻辑设备(logical device),也是使用实例来创建的。逻辑设备是物理设备的抽象,一个物理设备可以创建多个逻辑设备(学云计算的小伙伴应该会很熟),也就是说,假如一张显卡6G显存,我一次性都用不了,我就在物理设备上创建一个逻辑设备,分配够用的资源就行,其他剩余的资源留给别的程序用,如果给我分配的那部分内存不够用了还可以从剩下的资源再划出资源分配给我。非常灵活。而且队列是位于逻辑设备上,而且可以有多个队列,并且你的应用大部分时间是和逻辑设备打交道。
如下图:
一个应用可以有多个实例,一个实例可以有多个物理设备,一个物理设备可以有多个逻辑设备,一个逻辑设备可以有多个队列。
接下来我们将会讨论如何去创建VulKan的实例并且查询物理设备,在其中的某个物理设备上创建一个逻辑设备,并且检索一下设备上公开队列的信息。
Vulkan可以看作是上层应用的子系统。一旦你的应用程序和VulKan的库建立了链接,并且初始化了VulKan,VulKan将会追踪一些状态(这和OpenGL大同小异)。由于VulKan自己不会引入任何全局状态到你的应用中,所有的追踪状态必须由一个对象存储并提交给VulKan。说白了就是,VulKan一开始不会帮你干任何活,你想要的所有你都需要自己查询,自己分配,自己安排,这就提高了灵活性但是却带来了繁琐性,好在并不是特别的繁琐。
对于负责存储并提交的对象就是实例(instance)对象,由【VkInstance】表示,为了构造一个实例我们将会调用VulKan的第一个函数,【vkCreateIsntance】,原型如下:
VkResult vkCreateInstance (
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance
);
这种函数声明是经典的VulKan函数,在这里
在之后你会看到句柄和此函数的用法,我会给一个简单的例子。
注::
pCreateInfo参数需要传一个VkInstanceCreateInfo的指针,它的声明如下:
typedef struct VkInstanceCreateInfo {
VkStructureType sType;
const void* pNext;
VkInstanceCreateFlags flags;
const VkApplicationInfo* pApplicationInfo;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
} VkInstanceCreateInfo;
注:: unint32_t定义如下:
typedef unsigned int uint32_t;
pApplicationInfo参数需要传递一个VkApplicationInfo结构体的参数,它的声明如下:
typedef struct VkApplicationInfo {
VkStructureType sType;
const void* pNext;
const char* pApplicationName;
uint32_t applicationVersion;
const char* pEngineName;
uint32_t engineVersion;
uint32_t apiVersion;
} VkApplicationInfo;
/*
major::主版本
minor::副版本
patch::补丁号
*/
#define VK_MAKE_VERSION(major, minor, patch);
#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0);
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result=vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------"<
一旦我们创建了实例,我们可以使用他来查询被系统中能够支持VulKan的设备。VulKan拥有两种设备:物理设备和逻辑设备。比如一张显卡,加速器,DSP(数字型号处理器)或者是其他的设备。一般系统都有特定个数的硬件并且有着特定的功能。一个逻辑设备是软件方面对于物理硬件的抽象,由应用的指定方式配置。逻辑设备是应用程序真正需要花大量时间进行处理的设备。在我们能够创建逻辑设备之前,我们需要先找到物理设备。为了找到物理设备,我们调用vkEnumeratePhysicalDevices()函数,声明如下:
VkResult vkEnumeratePhysicalDevices (
VkInstance instance,
uint32_t* pPhysicalDeviceCount,
VkPhysicalDevice* pPhysicalDevices
);
vkEnumeratePhysicalDevices() 函数的第一个参数instance,就是之前我们使用vkCreateInstance()函数创建的实例,下一个指针参数pPhysicalDeviceCount是一个无符号整形(uint32_t的定义之前已经给过),此参数既可以作为写入也可以作为输出。作为输出,VulKan会将pPhysicalDeviceCount的值覆盖成查询到的物理设备数量。如果作为输入,你应该设置成目前电脑上拥有的最大的设备数。下一个参数pPhysicalDevices是一个指向一个数组的指针存储着每一个支持VulKan的物理设备句柄引用。
如果你只是想知道到底有多少个设备,直接设置pPhysicalDevices为nullptr,这样VulKan将会忽略pPhysicalDeviceCount的值,直接覆盖成查询到的物理设备数量。你可以动态地生成VkPhysicalDevice数组,通过两次调用vkEnumeratePhysicalDevices()函数。第一次调用仅仅是将pPhysicalDevices参数置nullptr(前提是pPhysicalDeviceCount参数正确的传入),第二次通过已经初始化好的物理设备数量设置VkPhysicalDevice数组再次调用vkEnumeratePhysicalDevices()函数(前提是instance和pPhysicalDeviceCount参数正确传入)。
假设一切顺利,vkEnumeratePhysicalDevices()函数将会返回VK_SUCCESS并且获得VulKan支持的物理设备数量,并且通过此数量初始化VkPhysicalDevice(物理设备)数组句柄引用,也就是数组中存储着物理设备的引用。
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
unsigned int physicalDeviceCount = 0;
result = vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Succeed----------------" << std::endl;
std::cout << "vkEnumeratePhysicalDevices::physicalDeviceCount::" << physicalDeviceCount << std::endl;
std::vector physicalDevices;
physicalDevices.resize(physicalDeviceCount);
result=vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, &physicalDevices[0]);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Succeed----------------" << std::endl;
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkCreateInstance::Failed----------------" << std::endl;
}
return 0;
}
我们接着说,关于VulKan的物理设备我们还有一些是需要知道的。之前获得的物理设备数组目的是查询物理设备的性能并最终创建逻辑设备。首先我们介绍vkGetPhysicalDeviceProperties()函数,此函数是用于获取特定物理设备的属性。其声明如下:
void vkGetPhysicalDeviceProperties (
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceProperties* pProperties
);
现在我们介绍一下VPhysicalDeviceProperties结构体,其声明如下:
typedef struct VkPhysicalDeviceProperties {
uint32_t apiVersion;
uint32_t driverVersion;
uint32_t vendorID;
uint32_t deviceID;
VkPhysicalDeviceType deviceType;
char deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
uint8_t pipelineCacheUUID[VK_UUID_SIZE];
VkPhysicalDeviceLimits limits;
VkPhysicalDeviceSparseProperties sparseProperties;
} VkPhysicalDeviceProperties;
关于limits和sparseProperties参数,我们将会在以后碰到时再进行讲解。
有关VK_MAX_PHYSICAL_DEVICE_NAME_SIZE和VK_UUID_SIZE定义如下:
#define VK_MAX_PHYSICAL_DEVICE_NAME_SIZE 256
#define VK_UUID_SIZE 16
题外话:当看到VK_UUID_SIZE为16时我怀疑不会是指OpenGL中的16个顶点属性吧?猜想一下,哈哈。
有关VkphysicalDeviceType的枚举声明如下:
typedef enum VkPhysicalDeviceType {
VK_PHYSICAL_DEVICE_TYPE_OTHER = 0,
VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,
VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,
VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,
VK_PHYSICAL_DEVICE_TYPE_CPU = 4,
VK_PHYSICAL_DEVICE_TYPE_BEGIN_RANGE = VK_PHYSICAL_DEVICE_TYPE_OTHER,
VK_PHYSICAL_DEVICE_TYPE_END_RANGE = VK_PHYSICAL_DEVICE_TYPE_CPU,
VK_PHYSICAL_DEVICE_TYPE_RANGE_SIZE = (VK_PHYSICAL_DEVICE_TYPE_CPU - VK_PHYSICAL_DEVICE_TYPE_OTHER + 1),
VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM = 0x7FFFFFFF
} VkPhysicalDeviceType;
除了上面的核心特征外,一些显卡有自己的更高的可配置特性。VulKan有很多特性也许此张物理设备支持,如果物理设备支持此特性,次特性必须持续开启(有点像扩展),请注意,一旦你开启了此特性,VulKan将会对待该特性与VulKan核心特性一样平等对待。查询物理设备特性函数为vkGetPhysicalDeviceFeatures(),其声明为:
void vkGetPhysicalDeviceFeatures (
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceFeatures* pFeatures
);
由于VkPhysicalDeviceFeatures结构体参数特别多,都是VkBool32类型参数(理解成bool类型就好)。由于笔者也不是全都知道,只了解一部分,所以在这里只给出声明:
typedef struct VkPhysicalDeviceFeatures {
VkBool32 robustBufferAccess;
VkBool32 fullDrawIndexUint32;
VkBool32 imageCubeArray;
VkBool32 independentBlend;
VkBool32 geometryShader;
VkBool32 tessellationShader;
VkBool32 sampleRateShading;
VkBool32 dualSrcBlend;
VkBool32 logicOp;
VkBool32 multiDrawIndirect;
VkBool32 drawIndirectFirstInstance;
VkBool32 depthClamp;
VkBool32 depthBiasClamp;
VkBool32 fillModeNonSolid;
VkBool32 depthBounds;
VkBool32 wideLines;
VkBool32 largePoints;
VkBool32 alphaToOne;
VkBool32 multiViewport;
VkBool32 samplerAnisotropy;
VkBool32 textureCompressionETC2;
VkBool32 textureCompressionASTC_LDR;
VkBool32 textureCompressionBC;
VkBool32 occlusionQueryPrecise;
VkBool32 pipelineStatisticsQuery;
VkBool32 vertexPipelineStoresAndAtomics;
VkBool32 fragmentStoresAndAtomics;
VkBool32 shaderTessellationAndGeometryPointSize;
VkBool32 shaderImageGatherExtended;
VkBool32 shaderStorageImageExtendedFormats;
VkBool32 shaderStorageImageMultisample;
VkBool32 shaderStorageImageReadWithoutFormat;
VkBool32 shaderStorageImageWriteWithoutFormat;
VkBool32 shaderUniformBufferArrayDynamicIndexing;
VkBool32 shaderSampledImageArrayDynamicIndexing;
VkBool32 shaderStorageBufferArrayDynamicIndexing;
VkBool32 shaderStorageImageArrayDynamicIndexing;
VkBool32 shaderClipDistance;
VkBool32 shaderCullDistance;
VkBool32 shaderFloat64;
VkBool32 shaderInt64;
VkBool32 shaderInt16;
VkBool32 shaderResourceResidency;
VkBool32 shaderResourceMinLod;
VkBool32 sparseBinding;
VkBool32 sparseResidencyBuffer;
VkBool32 sparseResidencyImage2D;
VkBool32 sparseResidencyImage3D;
VkBool32 sparseResidency2Samples;
VkBool32 sparseResidency4Samples;
VkBool32 sparseResidency8Samples;
VkBool32 sparseResidency16Samples;
VkBool32 sparseResidencyAliased;
VkBool32 variableMultisampleRate;
VkBool32 inheritedQueries;
} VkPhysicalDeviceFeatures;
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
unsigned int physicalDeviceCount = 0;
result = vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Succeed----------------" << std::endl;
std::cout << "vkEnumeratePhysicalDevices::physicalDeviceCount::" << physicalDeviceCount << std::endl;
std::vector physicalDevices;
physicalDevices.resize(physicalDeviceCount);
result=vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, &physicalDevices[0]);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Succeed----------------" << std::endl;
VkPhysicalDeviceProperties* physicalDeviceProperties = new VkPhysicalDeviceProperties();
for (unsigned int i = 0; i < physicalDevices.size(); i++)
{
vkGetPhysicalDeviceProperties(physicalDevices[i], physicalDeviceProperties);
std::cout << i <<"::PhysicalDeviceProperties::apiVersion::" << physicalDeviceProperties->apiVersion <driverVersion<vendorID <deviceID <deviceType <deviceName <
我的当前电脑配置是:GTX1060 独立显卡
在大多数情况下,VulKan的设备都是一个单独分出来的硬件(比如独立显卡),工作方式和主机的内存不大一样。VulKan中的设备内存(Device memory)指的是可访问的,并且有能力后备存储像纹理数据等其他数据的内存(可以包括显卡内存和主机内存)。内存被分为很多种类型,每一种类型都有自己的属性。每一个内存都是由一个或多个堆(heaps)组成。
查询堆的配置和设备支持的内存种类,调用vkGetPhysicalDeviceMemoryProperties()函数,其声明如下:
void vkGetPhysicalDeviceMemoryProperties (
VkPhysicalDevice physicalDevice,
VkPhysicalDeviceMemoryProperties* pMemoryProperties
);
查询的结果被放在了pMemoryProperties参数所对应的变量中,该变量是VkPhysicalDeviceMemoryProperties结构体类型,此结构体定义如下:
typedef struct VkPhysicalDeviceMemoryProperties {
uint32_t memoryTypeCount;
VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
uint32_t memoryHeapCount;
VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;
VkMemoryType结构体描述了每一个内存的种类,VkMemoryType的声明如下:
typedef struct VkMemoryType {
VkMemoryPropertyFlags propertyFlags;
uint32_t heapIndex;
} VkMemoryType;
VkMemoryType结构体仅仅由一个特性标志(propertyFlags)参数和一个内存类型的堆索引构成。这个propertyFlags参数描述了当前内存的类型,并且由VkMemoryPropertyFlagBits枚举类型组合而成。对应的propertyFlags的含义如下:(接下来的标志符,全都来自一个叫VkMemoryPropertyFlagBits的枚举),其声明如下:
typedef enum VkMemoryPropertyFlagBits {
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020,
VK_MEMORY_PROPERTY_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkMemoryPropertyFlagBits;
每一个内存种类都会返回一个heapIndex,该值对应着VkPhysicalDeviceMemoryProperties结构体中的memoryHeaps数组的下标索引,memoryHeaps数组中的每一个元素都描述一个VkMemoryHeap类型的设备内存堆(heaps),关于VkMemoryHeap类型的定义如下:
typedef struct VkMemoryHeap {
VkDeviceSize size;
VkMemoryHeapFlags flags;
} VkMemoryHeap;
但是目前我使用的VulKan版本是1.1.92.1所以该flags值有扩展,扩展值如下(对应VkMemoryHeapFlagBits枚举):
typedef enum VkMemoryHeapFlagBits {
VK_MEMORY_HEAP_DEVICE_LOCAL_BIT = 0x00000001,
VK_MEMORY_HEAP_MULTI_INSTANCE_BIT = 0x00000002,
VK_MEMORY_HEAP_MULTI_INSTANCE_BIT_KHR = VK_MEMORY_HEAP_MULTI_INSTANCE_BIT,
VK_MEMORY_HEAP_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkMemoryHeapFlagBits;
各个值的及含义目前不清楚,等到时候再补上。
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
unsigned int physicalDeviceCount = 0;
result = vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Succeed----------------" << std::endl;
std::cout << "vkEnumeratePhysicalDevices::physicalDeviceCount::" << physicalDeviceCount << std::endl;
std::vector physicalDevices;
physicalDevices.resize(physicalDeviceCount);
result=vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, &physicalDevices[0]);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Succeed----------------" << std::endl;
VkPhysicalDeviceMemoryProperties* physicalDeviceMemoryProperties = new VkPhysicalDeviceMemoryProperties();
for (unsigned int i = 0; i < physicalDevices.size(); i++)
{
vkGetPhysicalDeviceMemoryProperties(physicalDevices[i], physicalDeviceMemoryProperties);
std::cout << "---MemoryType---" << std::endl;
unsigned int memoryTypeCount = physicalDeviceMemoryProperties->memoryTypeCount;
std::cout << i << "::PhysicalDeviceMemoryProperties::memoryTypeCount::" << memoryTypeCount << std::endl;
for (unsigned int j = 0; j < memoryTypeCount; j++)
{
std::cout << j << "::MemoryType::propertyFlags::" << physicalDeviceMemoryProperties->memoryTypes[j].propertyFlags << std::endl;
std::cout << j << "::MemoryType::heapindex::" << physicalDeviceMemoryProperties->memoryTypes[j].heapIndex << std::endl;
}
std::cout << "---MemoryHeap---" << std::endl;
unsigned int memoryHeapCount = physicalDeviceMemoryProperties->memoryHeapCount;
std::cout << i << "::PhysicalDeviceMemoryProperties::memoryHeapCount::" << memoryHeapCount << std::endl;
for (unsigned int j = 0; j < memoryHeapCount; j++)
{
std::cout << j <<"::MemoryHeap::size::byte::"<memoryHeaps[j].size << std::endl;
unsigned int sizeMb=physicalDeviceMemoryProperties->memoryHeaps[j].size / (1024 * 1024);
std::cout << j<<"::MemoryHeap::size::mb::" << sizeMb << "mb" << std::endl;
std::cout << j << "::MemoryHeap::size::gb::" << sizeMb/1024 << "Gb" << std::endl;
std::cout << j << "::MemoryHeap::flags::" << physicalDeviceMemoryProperties->memoryHeaps[j].flags << std::endl;
}
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkCreateInstance::Failed----------------" << std::endl;
}
return 0;
}
{修改::更新::2019/4/6}将上面的第67行代码修改如下:
float sizeMb=physicalDeviceMemoryProperties->memoryHeaps[j].size / (1024.0f * 1024.0f)
我的当前电脑配置是:GTX1060 独立显卡,主机内存16G
VulKan设备上确切的任务是交给队列来完成的,每一个设备可以有一个或多个队列,并且每一个队列都属于一个队列族。一个队列族是一组具有相同功能并且能够并行运算的队列所组成。队列族的数量,每一个队列族的性能和队列族里有多少个队列这些全都是物理设备的属性,为了查询设备的队列族调用vkGetPhysicalDeviceQueueFamilyProperties()函数,其声明如下:
void vkGetPhysicalDeviceQueueFamilyProperties (
VkPhysicalDevice physicalDevice,
uint32_t* pQueueFamilyPropertyCount,
VkQueueFamilyProperties* pQueueFamilyProperties
);
该函数的使用方法类似于之前的vkEnumeratePhysicalDevices()函数(查询物理设备),在此你需要调用vkGetPhysicalDeviceQueueFamilyProperties()函数两遍。第一次给pQueueFamilyProperties参数赋值nullptr并且给pQueueFamilyPropertyCount参数和physicalDevice参数正确赋值。这样正确的数量值就会正确覆盖给pQueueFamilyPropertyCount参数。第二次就直接将pQueueFamilyProperties数组正确赋值,VulKan将会添加队列族的相关信息。
关于VkQueueFamilyProperties结构体的相关声明如下:
typedef struct VkQueueFamilyProperties {
VkQueueFlags queueFlags;
uint32_t queueCount;
uint32_t timestampValidBits;
VkExtent3D minImageTransferGranularity;
} VkQueueFamilyProperties;
有关queueFlags的队列功能有如下,类型对应着VkQueueFlagBits枚举,声明如下:
typedef enum VkQueueFlagBits {
VK_QUEUE_GRAPHICS_BIT = 0x00000001,
VK_QUEUE_COMPUTE_BIT = 0x00000002,
VK_QUEUE_TRANSFER_BIT = 0x00000004,
VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
VK_QUEUE_PROTECTED_BIT = 0x00000010,
VK_QUEUE_FLAG_BITS_MAX_ENUM = 0x7FFFFFFF
} VkQueueFlagBits;
typedef VkFlags VkQueueFlags;
需要注意的是,设备可能返回多个相同的属性。每一个队列族中的队列其实是一样的,只不过队列处于不同的队列族导致拥有不同的内部功能。对此VulKanAPI不能很容易的表达出来。由于这个原因实现统计在不同队列族中的相似队列,这将会给如何将资源分享给其他队列增加了限制,减小差异性。(说实话,我不知道这段作者想说啥。等后面看到解释了再补上)。
注:在创建逻辑设备之前,需要检索队列族属性。创建逻辑设备将会在之后讲解。
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
unsigned int physicalDeviceCount = 0;
result = vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Succeed----------------" << std::endl;
std::cout << "vkEnumeratePhysicalDevices::physicalDeviceCount::" << physicalDeviceCount << std::endl;
std::vector physicalDevices;
physicalDevices.resize(physicalDeviceCount);
result=vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, &physicalDevices[0]);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Succeed----------------" << std::endl;
VkPhysicalDeviceMemoryProperties* physicalDeviceMemoryProperties = new VkPhysicalDeviceMemoryProperties();
for (unsigned int i = 0; i < physicalDevices.size(); i++)
{
std::vector queueFamilyProperties;
unsigned int queueFamilyPrpertyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyPrpertyCount, nullptr);
std::cout << "----------------QueueFamilyProperty----------------" << std::endl;
std::cout << "PhysicalDeviceQueueFamilyProperties::QueueFamilyPropertyCount::" << queueFamilyPrpertyCount << std::endl;
queueFamilyProperties.resize(queueFamilyPrpertyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i], &queueFamilyPrpertyCount, &queueFamilyProperties[0]);
for (unsigned int j = 0; j < queueFamilyPrpertyCount; j++)
{
std::cout << j << "::QueueFamilyPrperties::queueFlags::" << queueFamilyProperties[j].queueFlags << std::endl;
std::cout << j << "::QueueFamilyPrperties::queueCount::" << queueFamilyProperties[j].queueCount << std::endl;
std::cout << j << "::QueueFamilyPrperties::timestampValidBits::" << queueFamilyProperties[j].timestampValidBits << std::endl;
}
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkCreateInstance::Failed----------------" << std::endl;
}
return 0;
}
在枚举了系统中所有的物理设备后,你的应用应该选择一个物理设备,并且在这个物理设备上创建一个逻辑设备。这个逻辑设备的状态将会处于初始化之后的状态。在创建逻辑设备时,你可以配置一些可选项,比如说开启你需要的扩展或者是特性等等。创建逻辑设备通过调用vkCreateDevice()函数。其函数声明如下:
VkResult vkCreateDevice (
VkPhysicalDevice physicalDevice,
const VkDeviceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkDevice* pDevice
);
关于创建逻辑设备需要一个指定,指定你要创建一个什么样的逻辑设备,也就是VulKan需要知道创建信息。关于创建信息pCreateInfo的VkDeviceCreateInfo的声明如下:
typedef struct VkDeviceCreateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceCreateFlags flags;
uint32_t queueCreateInfoCount;
const VkDeviceQueueCreateInfo* pQueueCreateInfos;
uint32_t enabledLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enabledExtensionCount;
const char* const* ppEnabledExtensionNames;
const VkPhysicalDeviceFeatures* pEnabledFeatures;
} VkDeviceCreateInfo;
目前而言为了简单,我们给enabledLayerCount赋值为0,ppEnabledLayerNames为nullptr,enabledExtensionCount赋值为0,ppEnabledExtensionNames为nullptr。我们先来看看pQueueCreateInfos的VkDeviceQueueCreateInfo结构体,其声明如下:
typedef struct VkDeviceQueueCreateInfo {
VkStructureType sType;
const void* pNext;
VkDeviceQueueCreateFlags flags;
uint32_t queueFamilyIndex;
uint32_t queueCount;
const float* pQueuePriorities;
} VkDeviceQueueCreateInfo;
请求队列会按照优先级排列并且之后会给他们分发与设备有关的相对优先级。这些离散的优先级是设备专用。你可以通过调用vkGetPhysicalDeviceProperties()函数,也就是之前我们用来查询物理设备的属性所调用的函数。其中有一个VkPhysicalDeviceLimits结构体的变量,该结构体下有一个discreteQueuePriorities参数来查看相关优先级信息。比如设备只支持低优先级和高优先级的工作负载,discreteQueuePriorities将会返回2。现在所有设备最起码能够支持两个独立的优先级级别。如果设备支持更加高级的优先级,discreteQueuePriorities的返回值就会越高。
现在就剩下pEnabledFeatures 参数用于指定开启的设备特性。一般情况下我们会开启设备支持的所有特性,而不开启不支持的特性。你也可以开启不支持的特性但是这会给性能增加一些不必要的负担。所以如何获取设备支持的特性呢?还记得之前的vkGetPhysicalDeviceFeatures()函数吗?使用此函数获取设备支持特性。你将会看到类似如下代码:
VkPhysicalDeviceFeatures* physicalDeviceFeature = new VkPhysicalDeviceFeatures();
vkGetPhysicalDeviceFeatures(physicalDevices[0], physicalDeviceFeature);
这样physicalDeviceFeature变量就会存有设备所支持的所有特性。
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
unsigned int physicalDeviceCount = 0;
result = vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Succeed----------------" << std::endl;
std::cout << "vkEnumeratePhysicalDevices::physicalDeviceCount::" << physicalDeviceCount << std::endl;
std::vector physicalDevices;
physicalDevices.resize(physicalDeviceCount);
result=vkEnumeratePhysicalDevices(*vkInstance, &physicalDeviceCount, &physicalDevices[0]);
if (result==VK_SUCCESS)
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Succeed----------------" << std::endl;
VkPhysicalDeviceFeatures physicalDeviceFeature;
vkGetPhysicalDeviceFeatures(physicalDevices[0], &physicalDeviceFeature);
VkDeviceQueueCreateInfo deviceQueueCreateInfo;
deviceQueueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
deviceQueueCreateInfo.pNext = nullptr;
deviceQueueCreateInfo.flags = 0;
deviceQueueCreateInfo.queueFamilyIndex = 0;
deviceQueueCreateInfo.queueCount = 0;
deviceQueueCreateInfo.pQueuePriorities = nullptr;
VkDeviceCreateInfo deviceCreateInfo = {};
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
deviceCreateInfo.pNext = nullptr;
deviceCreateInfo.flags = 0;
deviceCreateInfo.queueCreateInfoCount = 1;
deviceCreateInfo.pQueueCreateInfos = &deviceQueueCreateInfo;
deviceCreateInfo.enabledLayerCount = 0;
deviceCreateInfo.ppEnabledLayerNames = nullptr;
deviceCreateInfo.enabledExtensionCount = 0;
deviceCreateInfo.ppEnabledExtensionNames = nullptr;
deviceCreateInfo.pEnabledFeatures = &physicalDeviceFeature;
VkDevice device;
result=vkCreateDevice(physicalDevices[0], &deviceCreateInfo, nullptr, &device);
if (result == VK_SUCCESS)
{
std::cout << "-------Create Logical Device:: Succeed-------" << std::endl;
}
else
{
std::cout << "-------Create Logical Device:: Failed-------" << std::endl;
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceArray::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkEnumeratePhysicalDevices::DeviceCount::Failed----------------" << std::endl;
}
}
else
{
std::cout << "----------------VkCreateInstance::Failed----------------" << std::endl;
}
return 0;
}
第一章剩下的就是一些概念简单讲解:VulKan类和函数声明规则,内存管理,VulKan多线程。
一些数学概念:向量和矩阵,坐标系。
VulKan扩展:layers(层)的简单获取和属性等等,再就是查询扩展等
之后就是 清空VulKan实例收回内存,关闭应用。第一章就这么结束了。
由于本书对于向量和矩阵,坐标系讲解太过简单,我打算单独写相关知识梳理。对此有兴趣的可以看我写的【渲染基础-渲染管线】的那篇博客(说实话这篇渲染管线的博客由于当时是我不太会排版写的会有些乱,但是我感觉入门是够了)。
对于VulKan类和函数声明规则,内存管理,VulKan多线程。一些数学概念:向量和矩阵,坐标系这些 我先跳过。先写layer(层)和扩展的相关知识,之后在对未完成的进行补充。
单独写
单独写
渲染管线:图形学里的坐标系,其实是为了给渲染管线做变换的。这是我之前写的渲染管线博客,可以做参考。
接下来的层和扩展就是用来扩展VulKan。
层(layers)是VulKan的特性,VulKan允许层修改VulKan的行为(目前只能修改特定行为)。层可以通过拦截全部VulKan或者部分VulKan来增加功能,比如log日志,追踪,提供诊断和分析等等。一个层可以加入实例(instance)级别用于影响特定实例和这个实例下的所有物理设备。你也可以将层直接加入到设备(device)层。通过vkEnumerateInstanceLayerProperties()函数查询可用的层。vkEnumerateInstanceLayerProperties()函数的声明如下:
VkResult vkEnumerateInstanceLayerProperties (
uint32_t* pPropertyCount,
VkLayerProperties* pProperties
);
vkEnumerateInstanceLayerProperties(),我们可以调用两遍,第一遍pPropertyCount变量我们正确传入,pProperties参数赋值为nullptr之后,pPropertyCount所对应的变量就会覆盖成VulKan支持的层个数。(我相信经过前面的章节,现在读者应该能够完全理解调用两遍的作用)。
如果需要自己配置的话,自定义传入的pPropertyCount的值需要和pProperties数组的大小相匹配。
有关VkLayerProperties结构体的声明如下:
typedef struct VkLayerProperties {
char layerName[VK_MAX_EXTENSION_NAME_SIZE];
uint32_t specVersion;
uint32_t implementationVersion;
char description[VK_MAX_DESCRIPTION_SIZE];
} VkLayerProperties;
有关VK_MAX_EXTENSION_NAME_SIZE和VK_MAX_DESCRIPTION_SIZE定义如下:
#define VK_MAX_EXTENSION_NAME_SIZE 256
#define VK_MAX_DESCRIPTION_SIZE 256
这之前的层是实例(instance)级别可用的层,对于设备(device)级别也是有可用的层。关于查询设备级别的层通过调用vkEnumerateDeviceLayerProperties()函数,关于vkEnumerateDeviceLayerProperties()函数的声明如下:
VkResult vkEnumerateDeviceLayerProperties (
VkPhysicalDevice physicalDevice,
uint32_t* pPropertyCount,
VkLayerProperties* pProperties
);
在官方的VulKan的SDK中包括了一些层,大部分都是用于Debug(调试),参数正确性验证和日志输出。部分层的含义如下:
注意,其中有一个叫VK_LAYER_LUNARG_standard_validation的层包括很多独立的层,你只需要简单激活他。这本书的应用程序通过激活这个层来建立Debug模式,但你要编译Release版本时请关闭删除所有的层。
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
VkApplicationInfo* applicationInfo = new VkApplicationInfo();
applicationInfo->sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
applicationInfo->pNext = nullptr;
applicationInfo->pApplicationName = "c++ VulKan Program";
applicationInfo->applicationVersion = 1;
applicationInfo->pEngineName = "VulKan SDK";
applicationInfo->engineVersion = 1;
applicationInfo->apiVersion = VK_API_VERSION_1_0;
VkInstanceCreateInfo* instanceCreateInfo = new VkInstanceCreateInfo();
instanceCreateInfo->sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo->pNext = nullptr;
instanceCreateInfo->flags = 0;
instanceCreateInfo->pApplicationInfo = applicationInfo;
instanceCreateInfo->enabledLayerCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
instanceCreateInfo->enabledExtensionCount = 0;
instanceCreateInfo->ppEnabledExtensionNames = nullptr;
VkInstance* vkInstance = new VkInstance();
result = vkCreateInstance(instanceCreateInfo, nullptr, vkInstance);
if (result == VK_SUCCESS)
{
std::cout << "----------------VkCreateInstance::Succeed----------------" << std::endl;
}
else
{
std::cout << "----------------VkCreateInstance::Failed----------------" << std::endl;
}
unsigned int count = 0;
std::vector layerProperties;
result=vkEnumerateInstanceLayerProperties(&count, nullptr);
if (result==VK_SUCCESS)
{
std::cout << "LayerCount::" << count << std::endl;
std::cout << "---------------------------------------" << std::endl;
if (count!=0)
{
layerProperties.resize(count);
vkEnumerateInstanceLayerProperties(&count, &layerProperties[0]);
for (unsigned int j = 0; j < layerProperties.size(); j++)
{
std::cout << j << "::Layer::" << j << "::layerName::" << layerProperties[j].layerName << std::endl;
std::cout << j << "::Layer::" << j << "::specVersion::" << layerProperties[j].specVersion << std::endl;
std::cout << j << "::Layer::" << j << "::implementationVersion::" << layerProperties[j].implementationVersion << std::endl;
std::cout << j << "::Layer::" << j << "::description::" << layerProperties[j].description << std::endl;
std::cout << "---------------------------------------" << std::endl;
}
}
else
{
std::cout << "---!!!No Layers Match!!!---" << std::endl;
}
}
else
{
std::cout << "Layout::Faild" << std::endl;
}
return 0;
}
查询物理设备的Layers的代码:略。嘿嘿,看的这儿的小伙伴可以自己试试,自己查询。
现在请大家打开VulKanSDK的安装目录下的Tools文件夹下的【vkconfig.exe】或者【win】键进入VulKanSDK快捷菜单下的【Vulkan Configurator】。你会看到如下的界面被打开:
上图中的红色框中就是可以使用的层(layers),该程序就是可视化GUI配置层,没啥特别的。红色框中的层和下图输出的内容应该是一致的。
扩展允许进行试验,创新并推动新技术的前进。如果当一个扩展在经过考验后,足够说明他有价值后,这个扩展将会加入到未来版本的VulKanAPI当中。
使用扩展并是需要付出一定的代价的,一些状态会由于增加扩展了扩展而需要去追踪,进行额外的检查或者是间接使用而造成一定程度的性能损耗。并且在使用扩展之前需要明确的激活对用的扩展。不使用扩展就不会付出任何代价。
扩展被分为两种:
每一个扩展都会有新的函数,类型。结构体,枚举等等。一旦开启某个扩展,该扩展将会变成VulKanAPI的一部分用于程序调用。要想使用实例扩展(instance extensions)必须先创建VulKan实例。想要使用设备扩展(device extensions)必须先创建设备。
实例扩展:
现在有个问题,我们如何在没有创建任何实例和设备的情况下知道都有什么扩展?查询实例扩展是VulKan中少数不需要实例的功能,通过调用vkEnumerateInstanceExtensionProperties()函数,有关vkEnumerateInstanceExtensionProperties()函数声明如下:
VkResult vkEnumerateInstanceExtensionProperties (
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties
);
如果你就是要查询当前支持多少个实例扩展你需要调用两遍vkEnumerateInstanceExtensionProperties()函数(相信很多小伙伴应该对两遍调用不陌生了)。第一遍将pLayerName参数和pProperties参数赋值nullptr,将pPropertyCount参数正确传入,VulKan就会覆盖当前值。第二遍用当前查询后的扩展个数值初始化数组,将pLayerName赋值nullptr剩下两个参数正确赋值。
如果第一次调用时pProperties时不是nullptr,VulKan将会使用pPropertyCount参数对pProperties数组中的pPropertyCount个元素进行覆盖。
关于VkExtensionProperties结构体,其声明如下:
typedef struct VkExtensionProperties {
char extensionName[VK_MAX_EXTENSION_NAME_SIZE];
uint32_t specVersion;
} VkExtensionProperties;
设备扩展:
与实例扩展类似,通过调用vkEnumerateDeviceExtensionProperties()函数,其声明如下:
VkResult vkEnumerateDeviceExtensionProperties (
VkPhysicalDevice physicalDevice,
const char* pLayerName,
uint32_t* pPropertyCount,
VkExtensionProperties* pProperties
);
此函数与vkEnumerateInstanceExtensionProperties()用法一致,这里就不再赘述。只不过多了一个参数-physicalDevice,物理设备句柄。通过特定的设备查询特定设备支持的扩展,因为不同的显卡扩展是不同的,这些扩展大部分完全由该设备的设备生厂商提供。
那么问题外来,我查询了实例和设备扩展我如何激活他呢?
激活实例扩展:还记得之前创建VulKan的实例时使用的一个叫VkInstanceCreateInfo的结构体吗?该结构体内部有一个叫ppEnabledExtensionNames的字符串数组指针,你只需要将需要激活的扩展名传过去就可以。如果传入成功后调用vkEnumerateInstanceExtensionProperties()后将会返回你激活的扩展。
激活设备扩展:还记得之前创建逻辑设备使用的VkDeviceCreateInfo的结构体吗?该结构体内部有一个叫ppEnabledExtensionNames的参数,使用方法与实例扩展一样,这里就不再赘述。
#include
#include
#include
#include
int main()
{
std::cout << "Hello VulKan" << std::endl;
VkResult result = VK_SUCCESS;
std::cout << "----------Extensions----------" << std::endl;
unsigned int extensionCount = 0;
std::vector extensionProperties;
result = vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr);
if (result == VK_SUCCESS)
{
std::cout << "EnumerateInstanceExtensions::Succeed" << std::endl;
std::cout << "Extension::Count::" << extensionCount << std::endl;
extensionProperties.resize(extensionCount);
vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensionProperties.data());
for (unsigned i = 0; i < extensionProperties.size(); i++)
{
std::cout << i << "::Extension::specVersion::" << extensionProperties[i].specVersion << std::endl;
std::cout << i << "::Extension::extensionName::" << extensionProperties[i].extensionName << std::endl;
}
}
return 0;
}
这里就不给出设备扩展了,原理都差不多,读者可以自己尝试查询设备扩展。
有一些扩展提供了调用该扩展的新函数,这些扩展函数需要你自己查询并引用,基本原理使用的是函数指针(如果有小伙伴有写过OpenGL的经验,就应该知道一个叫GLEW或者GLAD的头文件,该头文件就是内部声明了OpenGL所有的函数指针,并在内部查找并初始化了所有函数指针,这样我们就可以直接调用OpenGL函数)。由于扩展有两种实例扩展和设备扩展所以扩展函数也分两类,实例扩展函数和设备扩展函数。你需要确保在激活了相应的扩展之后再查询引用相应扩展的扩展函数。通过调用vkGetInstanceProcAddr()函数获取相应的实例扩展函数指针。其声明如下:
PFN_vkVoidFunction vkGetInstanceProcAddr (
VkInstance instance,
const char* pName
);
如果你有多个实例,但是该函数只能在instance参数指定的实例下使用,就算是两个创建参数相同的实例,也只能是当前instance参数指定的实例可以使用该扩展函数。vkGetInstanceProcAddr该函数直接返回一个你可以直接调用的函数指针,有关PFN_vkVoidFunction定义如下:
#define VKAPI_CALL __stdcall
#define VKAPI_PTR VKAPI_CALL
typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void);
//转化一下也就是
typedef void (__stdcall *PFN_vkVoidFunction)(void);
//如果还是看不懂可以直接理解成如下
typedef void (*PFN_vkVoidFunction)(void);
PFN_vkVoidFunction声明与去掉typedef差不多,也就是 void (__stdcall *PFN_vkVoidFunction)(void)。唯一的区别就是,当你需要引用多个函数时,假如是10个函数,最原始的方法是声明10遍"void (__stdcall *XXX)(void)"。如果你声明了typedef void (VKAPI_PTR *PFN_vkVoidFunction)(void)你只需要声明10个"PFN_vkVoidFunction XXX",即可(其中XXX是声明的函数名称)。
注:__stdcall :: 函数调用约定,约定调用函数时,函数参数从右向左入栈。通常用于Win32 API中。
之前讲的都是实例扩展获得的函数,现在我们来看看如何获得设备扩展函数,通过调用vkGetDeviceProcAddr()函数获取,该函数声明如下:
PFN_vkVoidFunction vkGetDeviceProcAddr (
VkDevice device,
const char* pName
);
和之前vkGetInstanceProcAddr()函数大致相同,只不过,现在不在针对实例,而是针对设备。概念一样。即使是相同型号的设备,使用此函数声明返回的函数只能在device参数对应得设备中使用。
由于目前还写不出完整的获取扩展函数实例代码,因为本书第一章只讲了这么多,现在不知道pName这个参数到底传什么值。所以在这里给出逻辑(伪)代码:
PFN_vkVoidFunction testFunction;//声明函数指针
testFunction =vkGetInstanceProcAddr(*vkInstance, "extensionFunctionName");//获取函数
testFunction();//调用该函数
//等价如下写法
void(__stdcall *testFunction)(void);
testFunction =vkGetInstanceProcAddr(*vkInstance, "extensionFunctionName");
testFunction();
先写到这吧,累死了(手动滑稽)。