设备是对物理设备的一种抽象,使我们更加方便地使用它。更准确地说,应该称其为“逻辑设备”,但由于逻辑设备在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,总之,未来可期。

Vulkan学习苦旅04:创建设备(逻辑设备VkDevice)的更多相关文章

  1. linux驱动开发学习一:创建一个字符设备

    首先是内核初始化函数.代码如下.主要是三个步骤.1 生成设备号. 2 注册设备号.3 创建设备. #include <linux/module.h> #include <linux/ ...

  2. 机器学习实战(Machine Learning in Action)学习笔记————04.朴素贝叶斯分类(bayes)

    机器学习实战(Machine Learning in Action)学习笔记————04.朴素贝叶斯分类(bayes) 关键字:朴素贝叶斯.python.源码解析作者:米仓山下时间:2018-10-2 ...

  3. Redis:学习笔记-04

    Redis:学习笔记-04 该部分内容,参考了 bilibili 上讲解 Redis 中,观看数最多的课程 Redis最新超详细版教程通俗易懂,来自 UP主 遇见狂神说 10. Redis主从复制 1 ...

  4. Java学习笔记-多线程-创建线程的方式

    创建线程 创建线程的方式: 继承java.lang.Thread 实现java.lang.Runnable接口 所有的线程对象都是Thead及其子类的实例 每个线程完成一定的任务,其实就是一段顺序执行 ...

  5. devtmpfs文件系统创建设备节点

    分类: LINUX 原文地址:devtmpfs文件系统创建设备节点 作者:wangbaolin719 http://blog.chinaunix.net/uid-27097876-id-4334356 ...

  6. C++学习笔记22:设备

    设备类型 设备文件的性质 设备文件不是普通的磁盘文件 读写设备的数据需要与相应的设备驱动器通信 设备文件的类型 字符设备:读写串行数据字节流,如串口.终端等 块设备:随机读写固定尺寸数据块,如磁盘设备 ...

  7. Netty学习之客户端创建

    一.客户端开发时序图 图片来源:Netty权威指南(第2版) 二.Netty客户端开发步骤 使用Netty进行客户端开发主要有以下几个步骤: 1.用户线程创建Bootstrap Bootstrap b ...

  8. springmvc学习笔记---idea创建springmvc项目

    前言: 真的是很久没搞java的web服务开发了, 最近一次搞还是读研的时候, 想来感慨万千. 英雄没落, Eclipse的盟主地位隐隐然有被IntelliJ IDEA超越的趋势. Spring从2. ...

  9. class_create(),device_create自动创建设备文件结点

    class_create(),device_create自动创建设备文件结点 从linux 内核2.6的某个版本之后,devfs不复存在,udev成为devfs的替代.相比devfs,udev有很多优 ...

  10. soliworks三维机柜布局(一)创建设备型号库

    以某直升机电气系统为例:为电路中的各个设备创建设备型号库是进行三维线束设计的前提之一(如下图所示:窗口中箭头所指的3D部件一定要为每个设备都添加) 设备只有添加了3d模型,在solidworks进行机 ...

随机推荐

  1. Kubernetes 疑难杂症汇总

    1. 部署报错:The requested fsGroup is 123, but the volume local-pv-c7ef339e has GID 1000710000. The volum ...

  2. 01-module/分频器/激励写法

    1.module module有出入接口,输出接口 module有时钟和复位 // input clock; rest_n; // n表示低电平复位 //output o_data; module m ...

  3. 12-Verilog-同步FIFO设计

    同步FIFO和异步FIFO FIFO分为一个同步FIFO,一个异步FIFO,FIFO有读口和写口 读写时钟是一个,就是同步FIFO;读写时钟不是一个,异步FIFO IP核设计中,一般使用同步FIFO设 ...

  4. [转帖]mysql8 ALGORITHM=INSTANT 亿级数据秒速增加字段

    一.概述 登录后复制 在线DDL之快速增加列(秒级别的),并不会造成业务抖动.该功能自 MySQL 8.0.12 版本引入,是由腾讯游戏DBA团队贡献,此功能只适用于 InnoDB 表.实际上MySQ ...

  5. [转帖]如何查看Docker容器环境变量,如何向容器传递环境变量

    https://www.cnblogs.com/larrydpk/p/13437535.html 1 前言 欢迎访问南瓜慢说 www.pkslow.com获取更多精彩文章! 了解Docker容器的运行 ...

  6. [转帖]一文读懂 HugePages(大内存页)的原理

    https://juejin.cn/post/6956541214426398757 在介绍 HugePages 之前,我们先来回顾一下 Linux 下 虚拟内存 与 物理内存 之间的关系. 物理内存 ...

  7. [转帖]TiDB 内存控制文档

    https://docs.pingcap.com/zh/tidb/stable/configure-memory-usage 目前 TiDB 已经能够做到追踪单条 SQL 查询过程中的内存使用情况,当 ...

  8. [转帖]3.3.8. KWR运行期对比报告 KWR DIFF

    https://help.kingbase.com.cn/v8/perfor/performance-optimization/performance-optimization-6.html#sys- ...

  9. [转帖]PostgreSQL(三) 内存参数优化和原理(work_mem)内存表 pgfincore插件使用方法

    1.常用内存参数 1.1 shared_buffers shared_buffers是PostgreSQL用于共享缓冲区的内存,是由8kb大小的块所形成的数组.PostgreSQL在进行更新.查询等操 ...

  10. [转帖]Tcpdump抓包命令

    tcpdump和ethereal可以用来获取和分析网络通讯活动,他们都是使用libpcap库来捕获网络封包的. ​在混杂模式下他们可以监控网络适配器的所有通讯活动并捕获网卡所接收的所有帧. ​要想设置 ...