Vulkan Programming Guide::Chapter1::Overview of VulKan(纵观VulKan)

Vulkan Programming Guide 第一章(1) 内容 图片说明 表格说明 程序片段清单 关于本书 关于示例代码 错误纠正 致谢 关于作者第一章 Vulkan概述 1.1 介绍 1.2 实例,设备和队列 1.2.1 Vulkan实例 1.2.2 Vulkan物理设备 1.2.3 物理设备内存 1.2.4 设备队列 1.2.5 创建逻辑设备 1.3 对象类型和函 阅读详情

VulKan 编程指南::第一章::纵观VulKan


在这一章你将会学习什么

  • 什么是VulKan和背后的原理
  • 如何去创建最简单的VulKan应用
  • 本书会用到的一些专业术语和概念

在这一章我们将会介绍并解释什么是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)和固定功能硬件。功能被分为三类,这三类彼此互相影响。

  1. VulKan传输类别,将上层应用传输给底层设备(这就是驱动干的事情,应用和硬件的桥梁)
  2. 计算类别,被用于运行Shader(GPU用的程序,一般比较短小),和上层应用计算
  3. 图形类别,包括光栅化,图元装配,混合,深度和模板测试和其他的一些功能,这些对于专门做图形的人来说会很熟悉(确实是这样)。

而且VulKan支持扩展(比如英伟达的RTX实时光线追踪就是一个扩展),通过是否开启扩展,来选择是否使用相应的扩展功能。

注::在这里简单解释一下

  1. Shader---着色器。专门用于GPU计算,特别是光照计算。有两个重要的Shader,一个是VertexShader(顶点着色器)另一个是FragmentShader(片元着色器)。上层应用准备模型顶点位置,顶点法线等数据,然后传给顶点着色器进行一些位置变换(简单的有放大缩小等),之后传给片元主色器来决定投影到屏幕上的像素到底是什么颜色。然后我们就能在屏幕上看到图像。
  2. 光栅化---假如画个圆形(别太小),图像投影到屏幕上到底占多少像素?算一下这个图像影响哪些像素,然后将这些像素填充使我们看着图像像圆形,其实放大看并不是连续的圆形,而是一个个像素拼接成的。
  3. 图元装配---传给顶点着色器是一堆点,这些点连线填色形成一个物体,这就是图元装配。
  4. 混合---多数用来实现透明效果,用于模拟玻璃之类的效果。假如说有一块玻璃,玻璃是白色的,后面有个红色箱子。混合就是当你从玻璃看箱子时箱子的颜色和玻璃的颜色混合,所以你会看到玻璃后面的箱子。
  5. 深度测试---接着上面混合说,你是如何知道箱子在玻璃后面?深度测试就是用来测试物体间遮挡关系的。
  6. 模板测试---你有一个100x100的窗口显示图像,现在我只想显示20x20以内的图像(有点类似PS的框选区域)。这时候就可以用模板测试。
  7. (解释这些,累死我了<手动滑稽>)

实例化,设备和队列

VulKan有着结构良好的功能层次。在最顶层是实例(instance),实例可以查询汇总所有支持VulKan的设备。每一个设备会有一个或者多个队列(queue)。执行你应用的请求最后是交给队列来执行。

实例是一个逻辑抽象(可以理解成面向对象的类),实例将上层应用和底层之间抽象出来。物理设备(显卡)也是实例的成员(可以理解成实例类的子类),并且物理设备上会有多个可选队列。

一个物理设备(physical device)通常表现为一个硬件(一张显卡)或者是内部集成的硬件。在任一的一个系统上硬件数量是固定的,除非该系统支持热插拔。

注:: 热插拔---就是在一些电脑或服务器上有一些设备可以在机器运作的情况下直接拔出和安装,无需在关机后插拔。一般家用PC机是不允许做出此种行为的,高实时性需求的硬件会有此功能。热插拔多出现于高级计算机和云服务器上(学云计算的小伙伴和玩服务器的知道,<手动滑稽>)。鼠标键盘也应该算是热插拔。

一个逻辑设备(logical device),也是使用实例来创建的。逻辑设备是物理设备的抽象,一个物理设备可以创建多个逻辑设备(学云计算的小伙伴应该会很熟),也就是说,假如一张显卡6G显存,我一次性都用不了,我就在物理设备上创建一个逻辑设备,分配够用的资源就行,其他剩余的资源留给别的程序用,如果给我分配的那部分内存不够用了还可以从剩下的资源再划出资源分配给我。非常灵活。而且队列是位于逻辑设备上,而且可以有多个队列,并且你的应用大部分时间是和逻辑设备打交道。

如下图:

一个应用可以有多个实例,一个实例可以有多个物理设备,一个物理设备可以有多个逻辑设备,一个逻辑设备可以有多个队列。

接下来我们将会讨论如何去创建VulKan的实例并且查询物理设备,在其中的某个物理设备上创建一个逻辑设备,并且检索一下设备上公开队列的信息。


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结构体,里面存储着创建实例所需要的一些信息。
  • pAllocator :: 指针,指向VkAllocationCallbacks结构体,用于指向一个主机内存分配器,这样就可以支持VulKan系统使用这些内存。这将会在第二章【内存和资源】中详细说明,目前可以直接赋nullptr。
  • pInstance :: 指针,指向VkInstance结构体,在这里pInstance这种形式的操作经常叫【句柄】。
  • VkResult :: 返回值,枚举类型,当返回VkResult::VK_SUCCESS说明创建成功。注意VkResult::VK_SUCCESS的值为0,所以不能直接使用if(vkCreateInstance(a,b,c))判断。

在之后你会看到句柄和此函数的用法,我会给一个简单的例子。

注::

  • pCreateInfo参数中的"pxxxxxxx"中的p是【pointer】指针的意思
  • 【句柄】:: 写过OpenGL的小伙伴应该再熟悉不过了(glCreatexxx(),glGenxxx())。句柄就是类似于C#中的out和C++中在函数中返回赋值完成的参数的地址(有点绕口)。在这vkCreateInstance函数调用中,pCreateInfo和pAllocator这两个参数我可以自己定制,也就是说这两个参数我可以自己创建并且结构体里的参数我可以自己赋值,但是pInstance参数我们不知道赋什么值,所以我们通过前两个参数特别是pCreateInfo参数来告诉VulKan,我想生成什么样的实例。VulKan通过读取前两个参数得知:“啊啊!!你是想要这样的实例啊。”,然后生成你想要的实例然后在内部将第三个参数,也就是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;
  • sType :: 类型枚举,告诉VulKan你创建的类型,核心VulKan中的所有构造和扩展都会有一个枚举标记。通过这个标记VulKan的工具(tools),层(layer)和驱动可以进行对应的验证和扩展支持。在这里赋值为VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO。
  • pNext :: 按照书上的话翻译过来说:【pNext传入一个链表,你可以传入一连串的数据用于扩展参数,而不需要改变函数的形参接口。由于我们在这儿使用的是核心VulKan实例,所以赋值nullptr】,再就没有更多信息了,等到时候看到讲解了,再补上。
  • flags :: 保留位,待将来使用。目前设置为0。
  • pApplicationInfo :: 应用信息,指针,指向VkApplicationInfo结构体,用于存储你的应用信息。
  • enabledLayerCount :: 激活layer数量,目前不需要layer赋值为0。
  • ppEnabledLayerNames :: 激活的layer的名称,目前不使用layer所以赋值为nullptr。
  • enabledExtensionCount :: 激活扩展的数量,目前不需要扩展赋值为0。
  • ppEnabledExtensionNames :: 激活的扩展名称,目前不使用扩展赋值为nullptr。

注:: 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;
  • sType :: 和之前的VkInstanceCreateInfo中的sType概念相同,在这里赋值为VK_STRUCTURE_TYPE_APPLICATION_INFO。
  • pNext :: 和之前的VkInstanceCreateInfo中的pNext 概念相同,在这里赋值为nullptr。
  • pApplicationName :: 应用名称,一个指向以【\0】为结束符的字符串,这个你可以随便设,你喜欢就行。
  • applicationVersion :: 应用的版本,用于告诉VulKan的工具(tools)和驱动知道如何对待的你的应用,而不是去猜测你运行的是哪个应用。在这里赋值为1就行。
  • pEngineName :: 引擎名称,表明基于你的应用或者中间件,引擎叫什么名字,官方给的模板赋值为"VulKan SDK"。
  • engineVersion :: 引擎版本,表明基于你的应用或者中间件,引擎的版本,官方给的模板赋值为1。
  • apiVersion :: API版本,表明您所使用的VulKanAPI的版本。有两种赋值:VK_MAKE_VERSION(1, 0, 0)和VK_API_VERSION_1_0。其中各自的定义为:
/*
    major::主版本
    minor::副版本
    patch::补丁号
*/
#define VK_MAKE_VERSION(major, minor, patch);

#define VK_API_VERSION_1_0 VK_MAKE_VERSION(1, 0, 0);

期待已久的例子::创建实例

#include <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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;
	}

        return 0;
}
    

VulKan的物理设备

一旦我们创建了实例,我们可以使用他来查询被系统中能够支持VulKan的设备。VulKan拥有两种设备:物理设备和逻辑设备。比如一张显卡,加速器,DSP(数字型号处理器)或者是其他的设备。一般系统都有特定个数的硬件并且有着特定的功能。一个逻辑设备是软件方面对于物理硬件的抽象,由应用的指定方式配置。逻辑设备是应用程序真正需要花大量时间进行处理的设备。在我们能够创建逻辑设备之前,我们需要先找到物理设备。为了找到物理设备,我们调用vkEnumeratePhysicalDevices()函数,声明如下:

VkResult vkEnumeratePhysicalDevices (
    VkInstance instance,
    uint32_t* pPhysicalDeviceCount,
    VkPhysicalDevice* pPhysicalDevices
);
  • instance :: 之前调用vkCreateInstance函数返回的实例句柄。
  • pPhysicalDeviceCount :: 指向一个无符号整形数,用于获得物理设备个数。用法和句柄很像。
  • pPhysicalDevices :: 指向一个数组,用于将pPhysicalDeviceCount个系统中支持VulKan的设备句柄(引用),通过此函数赋值到对应数组中的对应元素中。

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 <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkPhysicalDevice> 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
);
  • physicalDevice :: 物理设备的句柄,之前调用vkEnumeratePhysicalDevices()初始化的物理设备数组中的某一项。
  • pProperties :: 物理设备属性,指向VkPhysicalDeviceProperties结构体,VulKan会将其初始化成physicalDevice参数对应的物理设备属性。

现在我们介绍一下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;
  • apiVersion :: 存储此物理设备支持的最高版本的VulKan。
  • driverVersion :: 存储用于控制物理设备的驱动版本。
  • vendorID :: 硬件提供商的ID号。
  • deviceID :: 设备ID号。
  • deviceType :: 物理设备的类型(是独立显卡还是集成显卡还是CPU等等)。
  • deviceName :: 人类可读懂的设备名称(比如说GTX1080,RTX2080等等)。
  • pipelineCacheUUID :: 用于渲染管线缓存,将会在第六章"着色器和渲染管线"中详细讲解。
  • limits :: 存储此物理设备最大和最小的限制。
  • sparseProperties :: 跟sparse texture(稀疏纹理相关)

关于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;
  • VK_PHYSICAL_DEVICE_TYPE_OTHER :: 其他
  • VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU :: 集成显卡
  • VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU :: 独立显卡
  • VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU :: 虚拟显卡,云服务器常见
  • VK_PHYSICAL_DEVICE_TYPE_CPU :: CPU

除了上面的核心特征外,一些显卡有自己的更高的可配置特性。VulKan有很多特性也许此张物理设备支持,如果物理设备支持此特性,次特性必须持续开启(有点像扩展),请注意,一旦你开启了此特性,VulKan将会对待该特性与VulKan核心特性一样平等对待。查询物理设备特性函数为vkGetPhysicalDeviceFeatures(),其声明为:

void vkGetPhysicalDeviceFeatures (
    VkPhysicalDevice physicalDevice,
    VkPhysicalDeviceFeatures* pFeatures
);
  • physicalDevice :: 物理设备的句柄,之前调用vkEnumeratePhysicalDevices()初始化的物理设备数组中的某一项。
  • pFeatures :: 存储着物理设备特性,指向VkPhysicalDeviceFeatures结构体。

由于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 <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkPhysicalDevice> 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 <<std::endl;
					std::cout << i << "::PhysicalDeviceProperties::driverVersion::" << physicalDeviceProperties ->driverVersion<<std::endl;
					std::cout << i << "::PhysicalDeviceProperties::vendorID::" << physicalDeviceProperties->vendorID <<std::endl;
					std::cout << i << "::PhysicalDeviceProperties::devicceID::" << physicalDeviceProperties->deviceID <<std::endl;
					std::cout << i << "::PhysicalDeviceProperties::deviceType::" << physicalDeviceProperties->deviceType <<std::endl;
					std::cout << i << "::PhysicalDeviceProperties::deviceName::" << physicalDeviceProperties->deviceName <<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;
}

我的当前电脑配置是:GTX1060 独立显卡

输出:


物理设备内存

在大多数情况下,VulKan的设备都是一个单独分出来的硬件(比如独立显卡),工作方式和主机的内存不大一样。VulKan中的设备内存(Device memory)指的是可访问的,并且有能力后备存储像纹理数据等其他数据的内存(可以包括显卡内存和主机内存)。内存被分为很多种类型,每一种类型都有自己的属性。每一个内存都是由一个或多个堆(heaps)组成。

查询堆的配置和设备支持的内存种类,调用vkGetPhysicalDeviceMemoryProperties()函数,其声明如下:

void vkGetPhysicalDeviceMemoryProperties (
    VkPhysicalDevice physicalDevice,
    VkPhysicalDeviceMemoryProperties* pMemoryProperties
);
  • physicalDevice :: 物理设备(句柄),之前调用vkEnumeratePhysicalDevices()函数获得的物理设备数组中的某一项
  • pMemoryProperties :: 内存属性,指向VkPhysicalDeviceMemoryProperties结构体,用于获取内存属性。

查询的结果被放在了pMemoryProperties参数所对应的变量中,该变量是VkPhysicalDeviceMemoryProperties结构体类型,此结构体定义如下:

typedef struct VkPhysicalDeviceMemoryProperties {
    uint32_t memoryTypeCount;
    VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
    uint32_t memoryHeapCount;
    VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;
  • memoryTypeCount :: 内存种类个数。最大可能会返回VK_MAX_MEMORY_TYPES的值,为32。
  • memoryTypes :: 内存类型数组,存储的个数和memoryTypeCount参数返回值相同。
  • memoryHeapCount :: 内存堆个数,最大为VK_MAX_MEMORY_HEAPS的值,为16。
  • memoryHeaps ::  内存堆数组,存储的个数和memoryHeapCount参数返回值相同。

VkMemoryType结构体描述了每一个内存的种类,VkMemoryType的声明如下:

typedef struct VkMemoryType {
    VkMemoryPropertyFlags propertyFlags;
    uint32_t heapIndex;
} VkMemoryType;
  • propertyFlags :: 内存类型标志位,用于标志内存类型
  • heapIndex :: 该内存类型处于索引值为heapIndex的堆上,在VkPhysicalDeviceMemoryProperties结构体中不是有memoryHeaps的VkMemoryHeaps类型的数组吗,这个heapIndex索引值就对应这memoryHeaps数组的下标索引值。

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;
  • VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT :: 意思是这个内存是本地连接的设备,也就是说显卡直接插在主板上,本地访问,本地使用。如果没有设置这个标志位,可以假定该内存是主机内存。(在后面的例子中VkMemoryType中的propertyFlags参数会返回0,我怀疑就是主机内存的意思,目前到这里此书还没有对返回0作解释)。
  • VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT :: 意思是该内存可由主机进行映射、读取或写入。(是不是可以理解成CPU直接访问处理该类型的GPU内存?)。如果没有设置这个标志位主机不能直接访问该类型内存,只能由该设备(GPU等)访问。
  • VK_MEMORY_PROPERTY_HOST_COHERENT_BIT :: 意思是如果设备和主机同时是访问该设备的内存,可以做到同步,数据保持一致,也就是说如果设备(GPU等)修改该值主机获得的该值也发生改变。如果没有该标志位,数据不能同步使用,也就是说加入主机修改了该数值,设备是不知道主机修改了该数值还保持原来的数值,除非缓存显示刷新。
  • VK_MEMORY_PROPERTY_HOST_CACHED_BIT :: 意思是该数据在该类型的内存中是由主机内存存储。读取操作该类型的内存要比其他方法快的多。如果没有设置该标志位,设备(GPU等)访问该类型内存将会有一些轻微的延迟,特别是访问连续存储的内存时。
  • VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT :: 意思是该内存分配器不会立刻从堆上分配空间,而是会推迟物理内存分配,直到内存对象被用于备份资源。(是不是可以理解成,类似于TCP传输文件,将传输过来的数据流保存到一个临时缓存中,等传输完成再拷贝到硬盘中)。

每一个内存种类都会返回一个heapIndex,该值对应着VkPhysicalDeviceMemoryProperties结构体中的memoryHeaps数组的下标索引,memoryHeaps数组中的每一个元素都描述一个VkMemoryHeap类型的设备内存堆(heaps),关于VkMemoryHeap类型的定义如下:

typedef struct VkMemoryHeap {
    VkDeviceSize size;
    VkMemoryHeapFlags flags;
} VkMemoryHeap;
  • size :: 内存堆的大小,单位是bytes(字节)
  • flags ::  堆标志位,目前在VulKan1.0版本中,只有一种值,VK_MEMORY_HEAP_DEVICE_LOCAL_BIT。如果该值被设置,表明该堆是本地连接到主板上(显卡直接连接到主板)。

但是目前我使用的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 <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkPhysicalDevice> 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::"<<physicalDeviceMemoryProperties->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
);
  • physicalDevice :: 物理设备句柄(在这里就不在赘述句柄了,我相信小伙伴们都已经理解了)。
  • pQueueFamilyPropertyCount :: 该设备队列族的数量。
  • 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 :: 代表该队列的功能类型。
  • queueCount :: 该队列族内的队列数量。
  • timestampValidBits :: 表示时间截(timestamp,网上说:类似一个密码,多用于知识产权)的有效位数。如果该值为0说明这个队列不支持时间截。如果不是0那么至少36位。
  • minImageTransferGranularity :: 队列支持的图像传输单元(如果有的话)。(目前不知道这是个啥)

有关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;
  • VK_QUEUE_GRAPHICS_BIT :: 此队列支持图形操作,比如画点,线和三角形。
  • VK_QUEUE_GRAPHICS_BIT :: 此队列支持计算操作,比如调度计算着色器。
  • VK_QUEUE_TRANSFER_BIT :: 此队列支持传输操作,比如拷贝内存和图像信息。
  • VK_QUEUE_SPARSE_BINDING_BIT :: 此队列支持绑定操作用于稀疏资源的更新。(目前不知道是干啥的)
  • VK_QUEUE_PROTECTED_BIT :: 看字面意思应该是投影,是用于渲染管线投影坐标系变化。(这个此书没写)。

需要注意的是,设备可能返回多个相同的属性。每一个队列族中的队列其实是一样的,只不过队列处于不同的队列族导致拥有不同的内部功能。对此VulKanAPI不能很容易的表达出来。由于这个原因实现统计在不同队列族中的相似队列,这将会给如何将资源分享给其他队列增加了限制,减小差异性。(说实话,我不知道这段作者想说啥。等后面看到解释了再补上)。

注:在创建逻辑设备之前,需要检索队列族属性。创建逻辑设备将会在之后讲解。


期待已久的例子::查询设备队列

#include <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkPhysicalDevice> 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<VkQueueFamilyProperties> 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
);
  • physicalDevice :: 之前调用vkEnumeratePhysicalDevices()函数获得的物理设备句柄。
  • pCreateInfo :: 逻辑设备创建的信息,在这里可以设置是否开启扩展等。
  • pAllocator :: 内存分配器,这将会在第二章【内存和资源】中详细说明,目前可以直接赋nullptr。
  • pDevice :: 逻辑设备句柄,需要一个在外已经声明好了的VkDevice变量,与获取物理设备句柄原理一样。

关于创建逻辑设备需要一个指定,指定你要创建一个什么样的逻辑设备,也就是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;
  • sType :: 创建类型,此处赋值为VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO。
  • pNext :: 与之前创建VulKan实例使用VkApplication中的pNext一样。并且除非你使用扩展,要是不使用赋值为nullptr。
  • flags :: 在当前版本,此flags标志位还没有定义,赋值为0。
  • queueCreateInfoCount :: 需要创建的队列个数,需要和下一个参数pQueueCreateInfos配合使用。
  • pQueueCreateInfos :: 队列创建数组,数组大小需要和queueCreateInfoCount 相一致。
  • enabledLayerCount :: 激活的Layer(层)数量,需要与ppEnabledLayerNames参数配合使用。关于查询Layer后文会讲。
  • ppEnabledLayerNames :: 想要激活Layer名称的字符串数组,数组大小需要和enabledLayerCount相一致。
  • enabledExtensionCount :: 激活扩展数量,需要与ppEnabledExtensionNames参数配合使用。
  • ppEnabledExtensionNames :: 激活扩展名称的字符串数组,数组大小需要和enabledExtensionCount相一致。
  • pEnabledFeatures  :: 激活设备特性。

目前而言为了简单,我们给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;
  • sType :: 创建类型,赋值为VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO。
  • pNext :: 和之前一样,目前赋值为nullptr。
  • flags :: 对于此标志还未定义,赋值为0。
  • queueFamilyIndex :: 队列族的索引,还记得之前调用vkGetPhysicalDeviceQueueFamilyProperties()函数查询设备队列组属性,其中有一个pQueueFamilyPropertyCount的属性。该queueFamilyIndex参数就对应的取值范围为0~pQueueFamilyPropertyCount-1。
  • queueCount :: 在当前队列族中设置你想用的队列数,当然前提是设备支持这么多的队列。
  • pQueuePriorities :: 队列优先级数组。用于指定每一个队列的优先级,并且数组中的每一个浮点数应该位于0~1之间包括0,1。优先级高的队列会被更活跃的调用。如果当前变量直接赋值为nullptr将会应用默认的队列优先级。

请求队列会按照优先级排列并且之后会给他们分发与设备有关的相对优先级。这些离散的优先级是设备专用。你可以通过调用vkGetPhysicalDeviceProperties()函数,也就是之前我们用来查询物理设备的属性所调用的函数。其中有一个VkPhysicalDeviceLimits结构体的变量,该结构体下有一个discreteQueuePriorities参数来查看相关优先级信息。比如设备只支持低优先级和高优先级的工作负载,discreteQueuePriorities将会返回2。现在所有设备最起码能够支持两个独立的优先级级别。如果设备支持更加高级的优先级,discreteQueuePriorities的返回值就会越高。

现在就剩下pEnabledFeatures 参数用于指定开启的设备特性。一般情况下我们会开启设备支持的所有特性,而不开启不支持的特性。你也可以开启不支持的特性但是这会给性能增加一些不必要的负担。所以如何获取设备支持的特性呢?还记得之前的vkGetPhysicalDeviceFeatures()函数吗?使用此函数获取设备支持特性。你将会看到类似如下代码:

VkPhysicalDeviceFeatures* physicalDeviceFeature = new VkPhysicalDeviceFeatures();
vkGetPhysicalDeviceFeatures(physicalDevices[0], physicalDeviceFeature);

这样physicalDeviceFeature变量就会存有设备所支持的所有特性。


期待已久的例子::创建逻辑设备

#include <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkPhysicalDevice> 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中的多线程

 


数学概念

单独写


向量和矩阵

单独写


坐标系

渲染管线:图形学里的坐标系,其实是为了给渲染管线做变换的。这是我之前写的渲染管线博客,可以做参考。


扩展VulKan

接下来的层和扩展就是用来扩展VulKan。


层(Layers)

层(layers)是VulKan的特性,VulKan允许层修改VulKan的行为(目前只能修改特定行为)。层可以通过拦截全部VulKan或者部分VulKan来增加功能,比如log日志,追踪,提供诊断和分析等等。一个层可以加入实例(instance)级别用于影响特定实例和这个实例下的所有物理设备。你也可以将层直接加入到设备(device)层。通过vkEnumerateInstanceLayerProperties()函数查询可用的层。vkEnumerateInstanceLayerProperties()函数的声明如下:

VkResult vkEnumerateInstanceLayerProperties (
    uint32_t* pPropertyCount,
    VkLayerProperties* pProperties
);
  • pPropertyCount :: 可用的Layer数量,前提是pProperties参数为nullptr。这种使用方法和查询物理设备的数量方法类似。
  • pPeoperties :: VkLayerProperties结构体的数组,内部存有每一个层的属性。

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;
  • layerName :: 层的名称
  • specVersion :: 层实现时对应的版本,该变量是为了适应将来的VulKan发展壮大。
  • implementationVersion :: 规范实现的版本,该变量是为了适应将来的VulKan发展壮大。书上说这可以提高性能、修复Bug、实现更广泛的可选特性等等(目前还看不出来,往后看看再说)。
  • description :: 该层的描述。

有关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
);
  • physicalDevice :: 查询到的物理设备的句柄。
  • pPropertyCount :: 和之前是一样的,略。
  • pProperties :: 和之前是一样的,略。

在官方的VulKan的SDK中包括了一些层,大部分都是用于Debug(调试),参数正确性验证和日志输出。部分层的含义如下:

  • VK_LAYER_LUNARG_api_dump :: 将VulKan调用和他们的参数和值打印到控制台。
  • VK_LAYER_LUNARG_core_validation ::用于参数验证和状态。比如渲染管线的状态,各种动态状态,对于SPIR-V模式和图形渲染管线的接口验证,并且追踪GPU内存使用。
  • VK_LAYER_LUNARG_device_limits :: 确保传给设备的参数或者结构体VulKan的设备能够接受。
  • VK_LAYER_LUNARG_image :: 验证图片格式是否支持。
  • VK_LAYER_LUNARG_object_tracker :: 验证VulKan的对象,用于试图捕获漏洞,使用于无报错但是程序结果不是希望的结果的情况(这种情况经常见,哈哈),或使用无效对象的情况下。
  • VK_LAYER_LUNARG_parameter_validation :: 确保传给VulKan的参数是正确的。
  • VK_LAYER_LUNARG_swapchain :: 对于WSI(Window System Intergration)窗口系统集成的功能进行验证(目前不知道是啥),将会在第五章介绍。
  • VK_LAYER_GOOGLE_threading :: 确保VulKan多线程的正确,防止多个线程不应该操作同一个对象时操作同一个对象了。
  • VK_LAYER_GOOGLE_unique_objects :: 确保每一个VulKan返回的句柄都是独一无二的,这样更容易调试追踪。避免重复删除相同句柄对象的情况。

注意,其中有一个叫VK_LAYER_LUNARG_standard_validation的层包括很多独立的层,你只需要简单激活他。这本书的应用程序通过激活这个层来建立Debug模式,但你要编译Release版本时请关闭删除所有的层。


期待已久的例子::查询实例可用的层

#include <vulkan/vulkan.hpp>
#include <iostream>
#include <vector>

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<VkLayerProperties> 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配置层,没啥特别的。红色框中的层和下图输出的内容应该是一致的。

输出:


扩展(Extensions)

扩展允许进行试验,创新并推动新技术的前进。如果当一个扩展在经过考验后,足够说明他有价值后,这个扩展将会加入到未来版本的VulKanAPI当中。

使用扩展并是需要付出一定的代价的,一些状态会由于增加扩展了扩展而需要去追踪,进行额外的检查或者是间接使用而造成一定程度的性能损耗。并且在使用扩展之前需要明确的激活对用的扩展。不使用扩展就不会付出任何代价。

扩展被分为两种:

  • 实例化扩展(instance extensions) :: 通常用于增强特点平台的VulKan。
  • 设备扩展(device extensions) :: 用于扩展一个或多个显卡特定品牌型号的特定功能。

每一个扩展都会有新的函数,类型。结构体,枚举等等。一旦开启某个扩展,该扩展将会变成VulKanAPI的一部分用于程序调用。要想使用实例扩展(instance extensions)必须先创建VulKan实例。想要使用设备扩展(device extensions)必须先创建设备。

实例扩展:

现在有个问题,我们如何在没有创建任何实例和设备的情况下知道都有什么扩展?查询实例扩展是VulKan中少数不需要实例的功能,通过调用vkEnumerateInstanceExtensionProperties()函数,有关vkEnumerateInstanceExtensionProperties()函数声明如下:

VkResult vkEnumerateInstanceExtensionProperties (
    const char* pLayerName,
    uint32_t* pPropertyCount,
    VkExtensionProperties* pProperties
);
  • pLayerName :: 当前pLayerName的层可能支持的扩展,目前先赋值为nullptr。
  • pPropertyCount :: 支持的扩展数。
  • 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;
  • extensionName :: 扩展名称,其中VK_MAX_EXTENSION_NAME_SIZE为256。
  • specVersion :: 扩展版本。

设备扩展:

与实例扩展类似,通过调用vkEnumerateDeviceExtensionProperties()函数,其声明如下:

VkResult vkEnumerateDeviceExtensionProperties (
    VkPhysicalDevice physicalDevice,
    const char* pLayerName,
    uint32_t* pPropertyCount,
    VkExtensionProperties* pProperties
);

此函数与vkEnumerateInstanceExtensionProperties()用法一致,这里就不再赘述。只不过多了一个参数-physicalDevice,物理设备句柄。通过特定的设备查询特定设备支持的扩展,因为不同的显卡扩展是不同的,这些扩展大部分完全由该设备的设备生厂商提供。

那么问题外来,我查询了实例和设备扩展我如何激活他呢?

激活实例扩展:还记得之前创建VulKan的实例时使用的一个叫VkInstanceCreateInfo的结构体吗?该结构体内部有一个叫ppEnabledExtensionNames的字符串数组指针,你只需要将需要激活的扩展名传过去就可以。如果传入成功后调用vkEnumerateInstanceExtensionProperties()后将会返回你激活的扩展。

激活设备扩展:还记得之前创建逻辑设备使用的VkDeviceCreateInfo的结构体吗?该结构体内部有一个叫ppEnabledExtensionNames的参数,使用方法与实例扩展一样,这里就不再赘述。


期待已久的例子::查询实例扩展

#include <vulkan/vulkan.hpp>
#include <vulkan/vulkan_core.h>
#include <iostream>
#include <vector>

int main()
{
	std::cout << "Hello VulKan" << std::endl;
	VkResult result = VK_SUCCESS;

	std::cout << "----------Extensions----------" << std::endl;

	unsigned int extensionCount = 0;
	std::vector<VkExtensionProperties> 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 :: 创建的实例句柄。
  • pName :: 引用的实例扩展函数的函数名,以"\0"结尾UTF-8字符编码的字符串。

如果你有多个实例,但是该函数只能在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
);
  • device :: 设备句柄
  • pName :: 函数名称

和之前vkGetInstanceProcAddr()函数大致相同,只不过,现在不在针对实例,而是针对设备。概念一样。即使是相同型号的设备,使用此函数声明返回的函数只能在device参数对应得设备中使用。


期待已久的例子::获取扩展函数

由于目前还写不出完整的获取扩展函数实例代码,因为本书第一章只讲了这么多,现在不知道pName这个参数到底传什么值。所以在这里给出逻辑(伪)代码:

PFN_vkVoidFunction testFunction;//声明函数指针
testFunction =vkGetInstanceProcAddr(*vkInstance, "extensionFunctionName");//获取函数
testFunction();//调用该函数

//等价如下写法
void(__stdcall *testFunction)(void);
testFunction =vkGetInstanceProcAddr(*vkInstance, "extensionFunctionName");
testFunction();

回收与关闭

先写到这吧,累死了(手动滑稽)。

Vulkan Programming Guide 第一章(3) 对象类型和函数约定事实上,Vulkan中的所有内容都被表示为一个由句柄引用的对象。句柄分为两大类:可分派对象和不可分散对象。在大多数情况下,这与应用程序无关,仅影响API的结构以及系统级组件(如Vulkan加载器和层)与这些对象的互操作性。 可分派对象是内部包含调度表的对象。这是各种组件使用的功能表,用于确定当您的应用程序调用Vulkan时要执行的代码部分。这些类型的对象通常是较大较复杂的结构,目 阅读详情

相关推荐

Vulkan编程指南:权威实践手册

Vulkan是一个为现代GPU设计的高性能图形API,它是由Khronos Group开发,旨在提供更高级别的硬件利用率和跨平台兼容性。Vulkan的推出,标志着图形API领域向前的一大步,结束了长期由OpenGL主导的时代。与它的前辈OpenGL相比,Vulkan提供了一种更为直接和底层的访问GPU硬件的方式,这使得开发者能够以更细粒度控制硬件,以实现更高性能。Vulkan的性能监控工具可以分为几个层面:API级别的跟踪、GPU级别的性能指标以及系统级别的资源监控。API级别的跟踪工具:如。

weixin_29050829的博客 1441

《Vulkan™ Programming Guide The Official Guide to Learning Vulkan 》翻译附带教程计划

由于本人最近在看《Vulkan™ Programming Guide The Official Guide to Learning Vulkan 》打算根据自己的进度将这本书翻译过来。本人是非常讨厌机翻的,看过《OpenGL 编程指南》的同学应该深有体会。我会在原意(非机翻)的基础上添加一些自己的理解,不用担心这些添加你看不懂,都是简单的添加,大部分人应该能够理解的。而且此书是对于VulKan的使...

AvatarForTest的博客 1573

Vulkan Programming Guide .pdf.zip

Vulkan Programming Guide Vulkan Programming Guide Vulkan Programming Guide

Vulkan Programming Guide第11章——同步

Vulkan被设计为在一个设备上含有多个队列中进行异步、并行地运行作业,并且与主机一起协同,以保持物理资源始终在使用状态且繁忙。在你应用程序中各种不同的点处(时间与代码位置),你需要保持主机与设备各种不同部分的同步状态。在本章,我们将为此目的会讨论对Vulkan应用程序可用的几种同步原语。

zenny_chen的专栏 893

Vulkan编程指南.pdf

Vulkan编程指南高清版,内容可复制,非常实用.

Vulkan Programming Guide: The Official Guide to Learning Vulkan

The next generation of the open graphics and compute specification, Vulkan, has been redesigned from the ground up, giving applications direct control over GPU acceleration for unprecedented performance and predictability. Vulkan™ Programming Guide is the essential, authoritative reference to this new standard for experienced graphics programmers in all Vulkan environments

Vulkan 编程指南 - 基础代码 (Vulkan Tutorial / Base code / 00_base_code.cpp)

Vulkan 编程指南 - 基础代码 (Vulkan Tutorial / Base code / 00_base_code.cpp)

程永强 1519

vulkan pdf 教程 英文.zip

Vulkan Programming Guide The Official Guide to Learning Vulkan Learning Vulkan by Parminder Singh (z-lib.org) C++ Game Development By Example Learn to build games and graphics with SFML, OpenGL, and Vulkan using C++ programming by Siddharth Shekar (z-lib.org) 来源 https://z-lib.org/

Vulkan 编程指南记录

个人记录

记录值得记录的东西 1920

Vulkan Programming Guide: The Official Guide to Learning Vulkan (OpenGL)

Vulkan Programming Guide: The Official Guide to Learning Vulkan (OpenGL) 最新官方权威原版

Vulkan Programming Guide The Official Guide to Learning Vulkan epub

Vulkan Programming Guide The Official Guide to Learning Vulkan 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除

【免费下载】 Vulkan编程指南高清版下载

Vulkan编程指南高清版下载 【下载地址】Vulkan编程指南高清版下载 Vulkan编程指南高清版下载 项目地址: https://gitcode.com/Open-source-documentation-tutorial/...

gitblog_06668的博客 1064

医学影像处理实战:用SimpleITK将CT数据重采样到统一尺寸(附完整代码)

本文针对医学影像分析中多源CT数据尺寸不一致导致深度学习模型训练失败的问题,提供了基于SimpleITK的实战解决方案。通过深入解析图像尺寸与间距的关系,详细阐述了如何通过调整体素间距进行重采样,而非简单补零,从而在保持图像物理尺寸不变的前提下,将数据统一到固定尺寸,确保输入网络的3D数据空间对齐且解剖信息准确。

weixin_29062671的博客 270

Vulkan Programming Guide::翻译::未济章节跟踪

第一章:纵观VulKan 对象类型和函数声明规则 内存管理 VulKan中的多线程 数学概念 向量和矩阵 坐标系 扩展 回收与关闭 第二章: 主机内存管理

AvatarForTest的博客 392

基于FMCW技术的毫米波雷达测速原理与应用

调频连续波(FMCW)技术是毫米波雷达中实现高精度测速的核心方法之一。本文详细介绍了FMCW测速的基本原理、信号处理流程以及在实际应用中的关键技术。通过分析FMCW雷达的多普勒效应和频率差测量方法,本文展示了如何利用FMCW技术实现目标速度的高精度检测。此外,本文还探讨了FMCW测速在自动驾驶、智能交通和工业自动化等领域的应用案例,为毫米波雷达开发者提供了理论支持和实践指导。

不谈枯燥公式,只聊雷达趋势。用大白话解读毫米波里的黑科技。 1137

致远OA公文管理模块相关操作

致远oa协同办公系统 公文管理模板 相关操作

weixin_59666050的博客 4275

Vulkan Programming Guide::Chapter2:: Memory and Resources(内存和资源)

VulKan 编程指南::第二章::内存和资源 在这一章你将会学习什么 VulKan是如何管理主机内存和设备内存 如何在你的程序中高效的使用内存 VulKan是如何使用图片数据和缓冲(buffers)去消耗空间并生成一些数据 几乎所有的系统都会有操作内存的功能,VulKan也是有内存操作功能。在VulKan中有两种基本类内存类型: 主机内存(内存条上的内存) 设备内存(显卡中的内存) ...

AvatarForTest的博客 606

手把手教你用组态王KingSCADA 3.53读取S7-1200 PLC数据(保姆级图文教程)

本文提供了一份详细的组态王KingSCADA 3.53读取S7-1200 PLC数据的实战教程,涵盖从硬件连接到软件配置、变量映射到故障排查的全流程。特别针对初学者常见问题提供解决方案,帮助工程师快速实现稳定通信,提升工业自动化系统效率。

weixin_26876073的博客 674

Vulkan Programming Guide::Chapter0::Configuration(配置)

请参阅: Vulkan::0.0::开始于VulKanSDK(Getting Started with the Vulkan SDK) 该文章详细说明了VulKan的配置和安装。是本人之前精心书写。请大家查阅。

AvatarForTest的博客 364

SPAD IMX479规格_分辨率

当然IMX479具备IMX459几乎全部的数字架构,但也有一个缺点就是贵,这个让头部的主机厂有些犹豫,毕竟大家都在集中火力想把车载LiDAR玩进千元机。如果用一片IMX479是不太可能实现千元内部的,当然国产也在奋起直追,特别在前面的几家SPAD厂商都具备开发IMX479的实力,但相对来说落地可能更是当下所考虑的,IMX479的迭代是基于IMX459,小编已经注意到国内有一两家已经在挑战479的规格了,如果国产SPAD没有大规模量产就直接流片IMX479的规格,可能就有点忽略市场的反馈闭环。

致力于lidar应用开发 2417

【STK】手把手教你利用STK进行仿真-STK软件简介02 STK专业分析模块

STK/Astrogator是一个交互式轨道机动和深空探测计划工具,用于飞行器运行及任务分析,通过定义推力模型、目标星历、飞行器姿态,提供轨道机动的解决及优化方案。应用Astrogator分析轨道机动的可见性和地面站覆盖,用户可以快速、方便地进行全局分析。任务控制序列保存在STK场景中,用于任务运行、快速制定精确轨道机动方案。为在轨运行的飞行器制定或执行机动计划时,分析人员可以根据飞行过程数据,如发动机定标参数、实际初始轨道,计算出推进器点火和点火时机数据,用于发出指令及精炼机动计划。

weixin_44184852的博客 1864

Vulkan Programming Guide翻译进度总结

为了翻译Vulkan Programming Guide(新的红宝书),这一段时间,我也算是拼了命的来做这个翻译工作,想尽快完成一遍,第二遍的时候再仔细校对。奈何也总是加班,九点回去了之后才能搞,时间不够啊。   以后翻译的文章就不在博客上发布了,首发在优快云上。完整的中文翻译网页放在http://vulkan.knowthyself.cn/  。所幸已经翻译到第八章了,还剩下不到1/3。加把

know yourself 2276

【LLM】大模型幻觉问题的原因和缓解方法

一、幻觉定义 备注(下面提及的两个名词): Faithfulness:是否遵循input content; Factualness:是否符合世界知识; 传统nlp任务重,幻觉大多数是faithfulness,比如Intrinsic Hallucination(冲突),摘要内容和document内容有冲突;再比如Extrinsic Hallucination(无中生有),生成内容中包含input要求的其他杂七杂八虚假信息;但LLM应该考虑的幻觉Factualness,因为数据源是Open-doman的世界知

发现问题,并解决问题,批判性思维 1万+

Vulkan简介

最近学习了一下Vulkan,通过这篇文章来对我所学的知识进行一个总结。 前言 Vulkan可以认为是Opengl版本的重写,它提供高性能和低CPU管理负担,天然支持多线程,能较好发挥多核CPU的性能,是一个能和DX12相提并论的东西。同时Vulkan几乎支持所有平台,跨平台API具有非常好的优势。Vulkan把驱动层做的很薄,把很多权限交给开发者,使开发者能更精确地控制渲染流程和资源管理。把很多...

Emmmwzh 8153

【WRF后处理】基于wrf-python处理wrf运行结果wrfout_d01

基于wrf-python处理wrf运行结果wrfout_d01

WW、forever的博客 5209

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

上一篇: 《Vulkan™ Programming Guide The Official Guide to Learning Vulkan 》翻译附带教程计划
下一篇: Vulkan Programming Guide::Chapter0::Configuration(配置)
FuXiii
博客等级 码龄9年 89粉丝 21原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值