首页 > 其他分享 >Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)

Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)

时间:2024-01-30 16:34:39浏览次数:28  
标签:VkDevice const 04 队列 创建 VK nullptr 苦旅 设备

设备是对物理设备的一种抽象,使我们更加方便地使用它。更准确地说,应该称其为“逻辑设备”,但由于逻辑设备在Vulkan中极为常用,后面几乎所有的API都需要它作为第一个参数,因此在Vulkan中直接简称为设备。

1. 实例、物理设备与设备的关系

在之前的几篇文章中,我们依次创建了实例和物理设备,但很多人对它们之前的关系可能不太清楚,它们的关系如下图所示:

简单来说,在一个应用程序中,可以创建多个实例(VkInstance),每个实例可以使用多个物理设备(VkPhysicalDevice),基于每个物理设备可以创建多个设备(VkDevice). 目前我们使用的是最为简单的组合:一个实例+一个物理设备+一个设备。

接下来将创建一个设备,既然设备是对物理设备的抽象,那么创建设备自然离不了物理设备,除此之外还需要一些其它的信息。

2. 创建设备的流程

让我们再次回顾第一篇文章中的那幅“地图”,现在我们已经来到了第三层:

从图中可以看到,需要基于一个物理设备(VkPhysicalDevice)创建设备(VkDevice)。此外,我们需要从设备中获取队列(VkQueue), 从而向这些队列提交命令。在选择物理设备时,我们分别选择了支持图形与显示的队列族索引mGraphicsQueueFamilyIndexmPresentQueueFamilyIndex, 创建好设备后,将会通过这两个索引获取队列。

 

函数vkCreateDevice用于创建设备,其原型如下:

VkResult vkCreateDevice(
    VkPhysicalDevice physicalDevice,
    const VkDeviceCreateInfo* pCreateInfo,
    const VkAllocationCallbacks* pAllocator,
    VkDevice* pDevice);

第一个参数传入上一节选择好的物理设备mPhysicalDevice, 目前的主要任务是填充结构体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设置为结构体的类型,pNext设置为nullptr, flags设置为0即可,和之前填充的结构体类似;

queueCreateInfoCount, pQueueCraeteInfos: 需要将一个VkDeviceQueueCreateInfo数组传给此结构体;

enabledLayerCount, ppEnabledLayerNames: 将mRequiredLayers这个vector传给结构体;

enableExtensionCount, ppEnabledExtensionsNames: 将mRequiredExtensions这个vector传给结构体;

pEnabledFeatures: 表示应用程序需要哪些扩展,暂时将其设置为nullptr.

上述结构体的大部分成员我们都很熟悉,只有VkDeviceQueueCreateInfo这个结构体有些陌生,此结构体的定义如下:

typedef struct VkDeviceQueueCreateInfo {
    VkStructureType sType;
    const void* pNext;
    VkDeviceQueueCreateFlags flags;
    uint32_t queueFamilyIndex;  // 队列族的索引
    uint32_t queueCount;  // 希望在此队列族中创建的队列个数
    const float* pQueuePriorities;  // 队列的优先级,与调度有关
} VkDeviceQueueCreateInfo;

此结构体中,需要关注的成员是最后三个:

queueFamilyIndex: 队列族的索引,传入mGraphicsQueueFamilyIndexmPresentQueuFamilyIndex;

queueCount: 此队列族中创建队列的个数,前面提到过,队列族是功能相同队列的集合,目前只需要1个队列即可;

pQueuePriorities: 指向队列优先级的指针。优先级在0.0~1.0之间,优先级更高的队列会被更频繁地调度,目前设置为1.0即可。

因此,创建设备的步骤大致为:

  1. 填充VkDeviceQueueCreateInfo结构体;
  2. 填充VkDeviceCreateInfo结构体;
  3. 调用vkCreateDevice创建设备。

3. 填充队列信息

在创建物理设备时,我们曾提到:mGraphicsQueueFamilyIndexmPresentQueueFamilyIndex很可能相等(即这个队列族既支持图形处理,又支持显示相关的功能),如果二者相等,没有必要创建两个队列。因此,最终的代码如下:

/*  填充VkDeviceQueueCreateInfo结构体  */
vector<VkDeviceQueueCreateInfo> deviceQueueCreateInfos;
float queuePriority = 1.0f;  // 必须指定优先级,如果pQueuePriorities设置为nullptr会报错

VkDeviceQueueCreateInfo deviceGraphicsQueueCreateInfo{
	VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,  // .sType
	nullptr,  // .pNext
	0,  // .flags
	mGraphicsQueueFamilyIndex,  // .queueFamilyIndex
	1,  // .queueCount
	&queuePriority,  // .pQueuePriorities
};
deviceQueueCreateInfos.push_back(deviceGraphicsQueueCreateInfo);

if (mPresentQueueFamilyIndex != mGraphicsQueueFamilyIndex) {
	VkDeviceQueueCreateInfo devicePresentQueueCreateInfo{
		VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,  // .sType
		nullptr,  // .pNext
		0,  // .flags
		mPresentQueueFamilyIndex,  // .queueFamilyIndex
		1,  // .queueCount
		&queuePriority,  // .pQueuePriorities
	};
	deviceQueueCreateInfos.push_back(devicePresentQueueCreateInfo);
}

只有两个索引不相等时,才会添加额外的创建信息。

4. 填充设备信息并创建队列

有了以上内容,就可以填充VkDeviceCreateInfo结构体了:

/*  填充VkDeviceCreateInfo结构体  */
VkDeviceCreateInfo deviceCreateInfo{
	VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,  // .sType
	nullptr,  // .pNext
	0,  // .flags
	deviceQueueCreateInfos.size(),  // .queueCreateInfoCount
	deviceQueueCreateInfos.data(),  // .pQueueCreateInfos
	mRequiredLayers.size(),  // .enabledLayerCount
	mRequiredLayers.data(),  // .ppEnabledLayerNames
	mRequiredExtensions.size(),  // .enabledExtensionCount
	mRequiredExtensions.data(),  // .ppEnabledExtensionNames
	nullptr,  // .pEnabledFeatureks
};

在VulkanApp类中添加表示设备的成员:

VkDevice mDevice;  // (逻辑)设备

最后调用vkCreateDevice创建设备:

if_fail(
	vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice),
	"failed to create device!"
);
Log("create device successfully");

 

由于我们需要向队列提交命令,因此需要获取创建好的队列。在VulkanApp类中,定义成员mGraphicsQueuemPresentQueue分别表示用于图形处理和显示的队列:

VkQueue mGraphicsQueue;  // 支持图形的队列
VkQueue mPresentQueue;  // 支持显示的队列

在创建设备时,我们传入了队列信息,因此设备已经帮我们创建好了队列,只要通过函数vkGetDeviceQueue从设备中获取队列即可:

vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue);
vkGetDeviceQueue(mDevice, mPresentQueueFamilyIndex, 0, &mPresentQueue);

此函数的第一个参数是刚才创建的设备mDevice,第二个参数是队列族的索引,第三个参数是队列的索引(由于我们在队列族中只使用了一个队列,因此此处队列的索引传入0),最后一个参数返回相应的队列。

从此处的函数调用可以看出,如果mGraphicsQueueFamilyIndex == mPresentQueueFamilyIndex, 那么mGraphicsQueuemPresentQueue应当是同一个队列。

 

别忘了最后在析构函数中销毁设备对象:

~VulkanApp() {
	vkDestroyDevice(mDevice, nullptr);

	......
}

5. 到目前位置的完整代码

如果成功创建设备,应当能看到Log输出的信息:[INFO] create device successfully.

在下篇博客中,我们将了解什么是交换链(Swapchain)以及如何创建交换链对象。

到目前为止的完整代码
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <vector>
using std::vector;
#include <cstring>

#define Log(message) std::cout << "[INFO] " << message << std::endl
#define Error(message) std::cerr << "[ERROR] " << message << std::endl; exit(-1)

static void if_fail(VkResult result, const char* message);


class VulkanApp {
public:
	VulkanApp() {
		glfwInit();  // 初始化glfw库
		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);  // 禁用OpenGL相关的API
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);  // 禁止调整窗口大小

		createInstance();
		createSurface();

		selectPhysicalDevice();
		createDevice();
	}

	~VulkanApp() {
		vkDestroyDevice(mDevice, nullptr);

		vkDestroySurfaceKHR(mInstance, mSurface, nullptr);
		vkDestroyInstance(mInstance, nullptr);

		glfwDestroyWindow(mWindow);
		glfwTerminate();
	}

	void Run() {
		while (!glfwWindowShouldClose(mWindow)) {
			glfwPollEvents();
		}
	}

private:
	const vector<const char*> mRequiredLayers = {
		"VK_LAYER_KHRONOS_validation"
	};
	const vector<const char*> mRequiredExtensions = {
		VK_KHR_SWAPCHAIN_EXTENSION_NAME,  // 等价于字符串"VK_KHR_swapchain"
	};
	VkInstance mInstance;  // 实例
	VkPhysicalDevice mPhysicalDevice;  // 物理设备
	int mGraphicsQueueFamilyIndex = -1;  // 支持图形功能的队列族索引
	int mPresentQueueFamilyIndex = -1;  // 支持显示功能的队列族索引

	int mWidth = 800;  // 窗口宽度
	int mHeight = 600;  // 窗口高度
	GLFWwindow* mWindow = nullptr;  // glfw窗口指针
	VkSurfaceKHR mSurface;

	VkDevice mDevice;  // (逻辑)设备
	VkQueue mGraphicsQueue;  // 支持图形的队列
	VkQueue mPresentQueue;  // 支持显示的队列

	void createInstance() {
		/* 填充VkApplicationInfo结构体 */
		VkApplicationInfo appInfo{
			VK_STRUCTURE_TYPE_APPLICATION_INFO,  // .sType
			nullptr,  // .pNext
			"I don't care",  // .pApplicationName
			VK_MAKE_VERSION(1, 0, 0),  // .applicationVersion
			"I don't care",  // .pEngineName
			VK_MAKE_VERSION(1, 0, 0),  // .engineVersion
			VK_API_VERSION_1_0,  // .apiVersion
		};

		/* 获取glfw要求支持的扩展 */
		uint32_t glfwExtensionCount = 0; 
		const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

		/* 输出glfw所需的扩展 */
		std::cout << "[INFO] glfw needs the following extensions:\n";
		for (int i = 0; i < glfwExtensionCount; i++) {
			std::cout << "    " << glfwExtensions[i] << std::endl;
		}

		/* 填充VkInstanceCreateInfo结构体 */
		VkInstanceCreateInfo instanceCreateInfo{
			VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,  // .sType
			nullptr,  // .pNext
			0,  // .flags
			&appInfo,  // .pApplicationInfo
			mRequiredLayers.size(),  // .enabledLayerCount
			mRequiredLayers.data(),  // .ppEnabledLayerNames
			glfwExtensionCount,  // .enabledExtensioncount
			glfwExtensions,  // .ppEnabledExtensionNames
		};

		/* 如果创建实例失败,终止程序 */
		if_fail(
			vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance),
			"failed to create instance"
		);
	}

	void createSurface() {
		mWindow = glfwCreateWindow(mWidth, mHeight, "Vulkan App", nullptr, nullptr);  // 创建glfw窗口
		if (mWindow == nullptr) {
			std::cerr << "failed to create window\n";
			exit(-1);
		}

		/* 创建VkSurfaceKHR对象 */
		if_fail(
			glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface),
			"failed to create surface"
		);
	}

	void selectPhysicalDevice() {
		/* 查找所有可选的物理设备 */
		uint32_t physicalDeviceCount = 0;
		vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, nullptr);
		vector<VkPhysicalDevice> physicalDevices(physicalDeviceCount);
		vkEnumeratePhysicalDevices(mInstance, &physicalDeviceCount, physicalDevices.data());

		mPhysicalDevice = VK_NULL_HANDLE;

		for (VkPhysicalDevice physicalDevice : physicalDevices) {
			/* 1. 检查物理设备是否支持扩展 */
			/* 获取物理设备支持的扩展信息 */
			uint32_t extensionCount = 0;
			vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr);
			vector<VkExtensionProperties> availableExtensions(extensionCount);
			vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, availableExtensions.data());

			bool isAllRequiredExtensionsSupported = true;  // 检查此物理设备是否支持所有的扩展
			for (const char* requiredExtensionName : mRequiredExtensions) {
				bool isSupported = false;
				for (const auto& availableExtension : availableExtensions) {
					if (strcmp(requiredExtensionName, availableExtension.extensionName) == 0) {
						isSupported = true;
						break;
					}
				}
				if (isSupported == false) {
					isAllRequiredExtensionsSupported = false;
					break;
				}
			}
			if (isAllRequiredExtensionsSupported) {
				Log("all required extensions are supported");
			}
			else {
				continue;
			}

			/* 2. 检查物理设备是否支持几何着色器 */
			VkPhysicalDeviceFeatures physicalDeviceFeatures;
			vkGetPhysicalDeviceFeatures(physicalDevice, &physicalDeviceFeatures);
			if (physicalDeviceFeatures.geometryShader) {
				Log("geometry shader is supported");
			}
			else {
				continue;
			}

			/* 获取队列族的信息 */
			uint32_t queueFamilyCount = 0;
			vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
			vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
			vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data());

			for (int i = 0; i < queueFamilyCount; i++) {
				/*  5.3. 检查是否支持图形功能 */
				if (mGraphicsQueueFamilyIndex < 0 && (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
					Log("find graphics queue family index " << i);
					mGraphicsQueueFamilyIndex = i;  // 保留队列族的索引
				}

				/*  5.4. 检查是否支持显示功能  */
				if (mPresentQueueFamilyIndex < 0) {
					VkBool32 isPresentSupport = false;
					vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, mSurface, &isPresentSupport);
					if (isPresentSupport) {
						mPresentQueueFamilyIndex = i;
						Log("find present queue family index " << i);
					}
					else {
						Log("present is not supported");
					}
				}
			}

			if (mGraphicsQueueFamilyIndex >= 0 && mPresentQueueFamilyIndex >= 0) {
				mPhysicalDevice = physicalDevice;

				/*  获取物理设备的属性  */
				VkPhysicalDeviceProperties physicalDeviceProperties;
				vkGetPhysicalDeviceProperties(mPhysicalDevice, &physicalDeviceProperties);
				Log("select physical device: " << physicalDeviceProperties.deviceName);
			}
		}

		/* 如果没找到合适的物理设备 */
		if (mPhysicalDevice == VK_NULL_HANDLE) {
			Error("can't find suitable physical device");
		}
	}

	void createDevice() {
		/*  填充VkDeviceQueueCreateInfo结构体  */
		vector<VkDeviceQueueCreateInfo> deviceQueueCreateInfos;
		float queuePriority = 1.0f;  // 必须指定优先级,如果pQueuePriorities设置为nullptr会报错

		VkDeviceQueueCreateInfo deviceGraphicsQueueCreateInfo{
			VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,  // .sType
			nullptr,  // .pNext
			0,  // .flags
			mGraphicsQueueFamilyIndex,  // .queueFamilyIndex
			1,  // .queueCount
			&queuePriority,  // .pQueuePriorities
		};
		deviceQueueCreateInfos.push_back(deviceGraphicsQueueCreateInfo);

		if (mPresentQueueFamilyIndex != mGraphicsQueueFamilyIndex) {
			VkDeviceQueueCreateInfo devicePresentQueueCreateInfo{
				VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,  // .sType
				nullptr,  // .pNext
				0,  // .flags
				mPresentQueueFamilyIndex,  // .queueFamilyIndex
				1,  // .queueCount
				&queuePriority,  // .pQueuePriorities
			};
			deviceQueueCreateInfos.push_back(devicePresentQueueCreateInfo);
		}

		/*  填充VkDeviceCreateInfo结构体  */
		VkDeviceCreateInfo deviceCreateInfo{
			VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,  // .sType
			nullptr,  // .pNext
			0,  // .flags
			deviceQueueCreateInfos.size(),  // .queueCreateInfoCount
			deviceQueueCreateInfos.data(),  // .pQueueCreateInfos
			mRequiredLayers.size(),  // .enabledLayerCount
			mRequiredLayers.data(),  // .ppEnabledLayerNames
			mRequiredExtensions.size(),  // .enabledExtensionCount
			mRequiredExtensions.data(),  // .ppEnabledExtensionNames
			nullptr,  // .pEnabledFeatureks
		};

		if_fail(
			vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice),
			"failed to create device!"
		);
		Log("create device successfully");

		vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue);
		vkGetDeviceQueue(mDevice, mPresentQueueFamilyIndex, 0, &mPresentQueue);
	}
};


int main() {
	VulkanApp app;
	app.Run();
}

static void if_fail(VkResult result, const char* message) {
	if (result != VK_SUCCESS) {
		std::cerr << "[error] " << message << std::endl;
		exit(-1);
	}
}

目前的代码大约有三百行,这意味着我们已经搞定了一千行代码的1/3,总之,未来可期。

标签:VkDevice,const,04,队列,创建,VK,nullptr,苦旅,设备
From: https://www.cnblogs.com/overxus/p/vulkan04.html

相关文章

  • openssh 9.6交叉编译ls1043
    准备工作zlib:官网下载:https://www.zlib.net版本:zlib-1.2.2.tar.gzopenssl:官网下载:https://www.openssl.org/source版本:openssl-1.1.1k.tar.gzopenssh:官网下载:http://www.openssh.com/portable.html阿里云镜像:https://mirrors.aliyun.com/pub/OpenBSD/OpenSSH/portable......
  • 洛谷题单指南-排序-P1104 生日
    原题链接:https://www.luogu.com.cn/problem/P1104题意解读:将学生按照年龄由大到小排序,如果年龄相同,后输入的排在前面,输出排序后的学生姓名。解题思路:此题是一个排序常规题,年龄大排在前说明年、月、日越小越在前面,核心的排序思路如下:1、如果年份不同,按年份由小到大排序2、如果......
  • ubuntu18.04 局域网配置git server步骤
    1.安装ssh2.安装gitsudoapt-getupdatesudoaptinstallvimsudoaptinstallopenssh-serversudoaptinstallgit3.cdhome目录mkdircodecdcodegitinit--baretest.git#创建git-server仓库远程服务器远程地址:/home/joker/code/test.git4.本地电脑目录操作......
  • ubuntu18.04 配置1920 1080
    添加/etc/X11/xorg.conf文件,将此模式保存为默认分辨率。1、$sudovim/etc/X11/xorg.conf(如果没有安装vim,可以使用“sudogedit/etc/X11/xorg.conf”)粘贴以下内容:Section"Monitor"Identifier"ConfiguredMonitor"Modeline"1920x1080_60.00"173.0019202......
  • IR2104
    IR2104详解从NMOS到电机驱动关键词:NMOS、半桥、死区、自举升压目录基础知识NMOS半桥IR2104简介芯片参数引脚定义应用电路详解工作原理自举死区总结页面内跳转文件的纯文本引入:IR2104是我上手的第一个半桥栅极驱动芯片,使用两片IR2104就可以搭建一个......
  • 【算法】004_链表
    哈希表哈希表增删改查是常数时间,但是这个常数时间比较大放入哈希表的东西,如果是基础类型,内部按值传递,内存占用就是这个东西的大小放入哈希表的东西,如果不是基础类型,内部按引用传递,内存占用就是这个东西的内存地址的大小有序表有序表的增删改查是O(logn)级别的放入有序表......
  • Gym104095L 送外卖
    https://codeforces.com/gym/104095/attachments/download/18184/statements.pdf首先这个\(n\le14\)的数据范围可以直接考虑状压了。设\(f_{i,S,time}\)为当前骑手在\(i\)号城市,已经把外卖送给了状态为\(S\)的城市,此时的时间为\(times\)所能获得的最大收益。当\(time......
  • Gym104270E Kawa Exam
    题意简述有\(n\)道题,每道题有\(10^5\)个选项,其中选项\(a_i\)是正确的。再给定\(m\)条限制\(u_i,v_i\),表示题目\(u_i,v_i\)必须要选择相同的选项。对于\(m\)条限制,求出若去掉这条限制,最多能回答多少问题。多组数据。\(n,m,a_i\le10^5,\sumn,\summ\le10^6\)。......
  • panghu week04 笔记
    长度最小的子数组一开始想的是框定一个区间,然后如果大于等于target,从区间头弹出一个元素,从尾部append进入一个元素,发现并不能覆盖所有的区间看了题解以后,可以定尾,然后移动头部进行比较funcminSubArrayLen(targetint,nums[]int)int{slide:=make([]int,0)slid......
  • Vulkan学习苦旅02:看不见的窗口(创建VkInstance与VkSurfaceKHR)
    在上一节中,我们搭建了学习Vulkan所需的环境。今天,我们将会初步了解“地图”顶层的内容。 如图所示,“地图”的顶层有两个模块:Instance和SurfaceKHR.其中,Instance表示应用程序的一个实例,它用于保存一些状态,我们可以在应用程序中创建多个实例,但目前我们只创建一个实例;SurfaceKH......