Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)
设备是对物理设备的一种抽象,使我们更加方便地使用它。更准确地说,应该称其为“逻辑设备”,但由于逻辑设备在Vulkan中极为常用,后面几乎所有的API都需要它作为第一个参数,因此在Vulkan中直接简称为设备。
1. 实例、物理设备与设备的关系
在之前的几篇文章中,我们依次创建了实例和物理设备,但很多人对它们之前的关系可能不太清楚,它们的关系如下图所示:

简单来说,在一个应用程序中,可以创建多个实例(VkInstance),每个实例可以使用多个物理设备(VkPhysicalDevice),基于每个物理设备可以创建多个设备(VkDevice). 目前我们使用的是最为简单的组合:一个实例+一个物理设备+一个设备。
接下来将创建一个设备,既然设备是对物理设备的抽象,那么创建设备自然离不了物理设备,除此之外还需要一些其它的信息。
2. 创建设备的流程
让我们再次回顾第一篇文章中的那幅“地图”,现在我们已经来到了第三层:

从图中可以看到,需要基于一个物理设备(VkPhysicalDevice)创建设备(VkDevice)。此外,我们需要从设备中获取队列(VkQueue), 从而向这些队列提交命令。在选择物理设备时,我们分别选择了支持图形与显示的队列族索引mGraphicsQueueFamilyIndex和mPresentQueueFamilyIndex, 创建好设备后,将会通过这两个索引获取队列。
函数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: 队列族的索引,传入mGraphicsQueueFamilyIndex或mPresentQueuFamilyIndex;
queueCount: 此队列族中创建队列的个数,前面提到过,队列族是功能相同队列的集合,目前只需要1个队列即可;
pQueuePriorities: 指向队列优先级的指针。优先级在0.0~1.0之间,优先级更高的队列会被更频繁地调度,目前设置为1.0即可。
因此,创建设备的步骤大致为:
- 填充
VkDeviceQueueCreateInfo结构体; - 填充
VkDeviceCreateInfo结构体; - 调用
vkCreateDevice创建设备。
3. 填充队列信息
在创建物理设备时,我们曾提到:mGraphicsQueueFamilyIndex与mPresentQueueFamilyIndex很可能相等(即这个队列族既支持图形处理,又支持显示相关的功能),如果二者相等,没有必要创建两个队列。因此,最终的代码如下:
/* 填充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类中,定义成员mGraphicsQueue和mPresentQueue分别表示用于图形处理和显示的队列:
VkQueue mGraphicsQueue; // 支持图形的队列
VkQueue mPresentQueue; // 支持显示的队列
在创建设备时,我们传入了队列信息,因此设备已经帮我们创建好了队列,只要通过函数vkGetDeviceQueue从设备中获取队列即可:
vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue);
vkGetDeviceQueue(mDevice, mPresentQueueFamilyIndex, 0, &mPresentQueue);
此函数的第一个参数是刚才创建的设备mDevice,第二个参数是队列族的索引,第三个参数是队列的索引(由于我们在队列族中只使用了一个队列,因此此处队列的索引传入0),最后一个参数返回相应的队列。
从此处的函数调用可以看出,如果
mGraphicsQueueFamilyIndex == mPresentQueueFamilyIndex, 那么mGraphicsQueue和mPresentQueue应当是同一个队列。
别忘了最后在析构函数中销毁设备对象:
~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,总之,未来可期。
Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)的更多相关文章
- linux驱动开发学习一:创建一个字符设备
首先是内核初始化函数.代码如下.主要是三个步骤.1 生成设备号. 2 注册设备号.3 创建设备. #include <linux/module.h> #include <linux/ ...
- 机器学习实战(Machine Learning in Action)学习笔记————04.朴素贝叶斯分类(bayes)
机器学习实战(Machine Learning in Action)学习笔记————04.朴素贝叶斯分类(bayes) 关键字:朴素贝叶斯.python.源码解析作者:米仓山下时间:2018-10-2 ...
- Redis:学习笔记-04
Redis:学习笔记-04 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 10. Redis主从复制 1 ...
- Java学习笔记-多线程-创建线程的方式
创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...
- devtmpfs文件系统创建设备节点
分类: LINUX 原文地址:devtmpfs文件系统创建设备节点 作者:wangbaolin719 http://blog.chinaunix.net/uid-27097876-id-4334356 ...
- C++学习笔记22:设备
设备类型 设备文件的性质 设备文件不是普通的磁盘文件 读写设备的数据需要与相应的设备驱动器通信 设备文件的类型 字符设备:读写串行数据字节流,如串口.终端等 块设备:随机读写固定尺寸数据块,如磁盘设备 ...
- Netty学习之客户端创建
一.客户端开发时序图 图片来源:Netty权威指南(第2版) 二.Netty客户端开发步骤 使用Netty进行客户端开发主要有以下几个步骤: 1.用户线程创建Bootstrap Bootstrap b ...
- springmvc学习笔记---idea创建springmvc项目
前言: 真的是很久没搞java的web服务开发了, 最近一次搞还是读研的时候, 想来感慨万千. 英雄没落, Eclipse的盟主地位隐隐然有被IntelliJ IDEA超越的趋势. Spring从2. ...
- class_create(),device_create自动创建设备文件结点
class_create(),device_create自动创建设备文件结点 从linux 内核2.6的某个版本之后,devfs不复存在,udev成为devfs的替代.相比devfs,udev有很多优 ...
- soliworks三维机柜布局(一)创建设备型号库
以某直升机电气系统为例:为电路中的各个设备创建设备型号库是进行三维线束设计的前提之一(如下图所示:窗口中箭头所指的3D部件一定要为每个设备都添加) 设备只有添加了3d模型,在solidworks进行机 ...
随机推荐
- 【每日一题】11.黑白树 (树上DFS)
补题链接:Here 题目描述 一棵 \(n\) 个点的有根树,\(1\) 号点为根,相邻的两个节点之间的距离为 \(1\) .树上每个节点 \(i\)对应一个值\(k[i]\).每个点都有一个颜色,初 ...
- Kite 使用教程 轻量级代码提示
概述 今天看小甲鱼视频的VSC Python 安装教程里视频博主强烈推荐安装Kite插件 ,这是什么玩意? 下载下来试一试? 原来:就是一个代码提示插件.. 说白了" 就是让开发者 在轻量级 ...
- Spring相关原理
Spring是什么? Spring是一个轻量级的IoC和AOP容器框架.常见的配置方式有三种:基于XML的配置.基于注解的配置.基于Java的配置. 模块分为以下:Spring Core:Spring ...
- 七、mycat-ER分片
系列导航 一.Mycat实战---为什么要用mycat 二.Mycat安装 三.mycat实验数据 四.mycat垂直分库 五.mycat水平分库 六.mycat全局自增 七.mycat-ER分片 一 ...
- http连接池配置及spring boot restTemplate配置http连接池
本文为博主原创,转载请注明出处: 项目中存在第三方系统之间的服务调用通信,且会进行频繁调用,由于很早之前实现的调用方式为每调用一次外部接口,就需要新建一个HttpClient 对象.由于频繁调用,会存 ...
- 02-MySQL基本操作
SQL 的一些简单语法规则 结束符 SQL 指令需要语句结束符,默认是英文分号;. 当然,还有另外两个结束符: \g 与英文分号;等效. \G:将查到的结构旋转90度变成纵向. 反引号`` SQL语句 ...
- [转帖]mysql 里 CST 时区的坑
mysql 里 CST 时区的坑 一.问题简述 mysql 里 CST 时区是个非常坑的概念,因为在 mysql 里CST既表示中国也表示美国的时区.但是在Jdk代码里,CST 这个字符串被理解为Ce ...
- [转帖]InfluxDB 修改数据存储路径
1.创建数据存储目录 mkdir -p /home/data/influxdb 说明:目录可以根据实际情况进行修改. 2.设置目录访问权限 sudo chown influxdb.influxdb / ...
- [转帖]TiUP 常见运维操作
https://docs.pingcap.com/zh/tidb/stable/maintain-tidb-using-tiup 本文介绍了使用 TiUP 运维 TiDB 集群的常见操作,包括查看集群 ...
- [转帖]centos7离线安装postgresql13
https://www.cnblogs.com/summer-88/p/15341918.html 在一台可以联网的centos上安装postgresql源 yum install -y https: ...