设备是对物理设备的一种抽象,使我们更加方便地使用它。更准确地说,应该称其为“逻辑设备”,但由于逻辑设备在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. vivo 全球商城:从 0 到 1 代销业务的融合之路

    代销是 vivo 商城已经落地的成熟业务,本文提供给各位读者 vivo 商城代销业务中两个异构系统业务融合的对接经验和架构思路. 一.业务背景 近两年,内销商城业务的发展十分迅速,vivo 商城系统的 ...

  2. Python异步编程原理篇之协程的IO

    协程的IO asyncio 作为实现异步编程的库,任务执行中遇到系统IO的时能够自动切换到其他任务.协程使用的IO模型是IO多路复用.在 asyncio 低阶API 一篇中提到过 "以Lin ...

  3. fusionpbx简介

    概述 fusionpbx是以freeswitch作为底层框架开发而成的开源PBX,在freeswitch的基础上,优化了GUI的易用性. fusionpbx可用作高可用性的单租户或基于域的多租户 PB ...

  4. C#设计模式17——责任链模式的写法

    是什么: 责任链模式是一种行为型设计模式,它允许对象组成一个链并依次检查另一个对象是否可以处理请求.如果一个对象可以处理请求,它处理请求,并且负责将请求传递给下一个对象,直到请求被处理为止. 为什么: ...

  5. centos7使用nginx+uwsgi部署python django项目

    在django框架中,我们一般直接通过python manage.py runserver来启动提供服务,但是如果生产环境此方法不可行,而且容易导致异常退出,于是需要借助uwsgi来作为守护进程. 操 ...

  6. 【TouchGFX】IAR 下实现 touchgfx Caching Bitmaps 通过文件方式获取图像资源

    1.Caching Bitmaps 修改缓存方式 2.修改 blockCopy 方法(注意:忘记返回状态导致发生错误) 3.修改分散文件将位于 ExtFlashSection section 数据重定 ...

  7. [转帖]会长期锁表吗?Oracle add column default 在各版本的优化

    最近大家讨论到各类数据库在新增字段带默认值时是否会锁表的问题,发现Oracle在不同的版本其实是有对应优化的,更新了下之前的知识点. 主要考虑以下三种情形,例如: 不加default:alter ta ...

  8. [转帖]全表扫描却产生大量db file sequential read一例

    老熊 Oracle性能优化 2012-05-23 开发人员在进行新系统上线前的数据校验测试时,发现一条手工执行的SQL执行了超过1小时还没有返回结果.SQL很简单: SELECT * FROM MOB ...

  9. 人大金仓学习之一_kwr的简单学习

    人大金仓学习之一_kwr的简单学习 摘要 周末在家想着学习一下数据库相关的内容. 网上找了不少资料, 想着直接在本地机器上面进行一下安装与验证 理论上linux上面应该更加简单. windows 上面 ...

  10. [转帖]自动化回归测试工具 —— AREX 上手实践

    https://my.oschina.net/arextest/blog/8589156   AREX 是一款开源的自动化测试工具平台,基于 Java Agent 技术与比对技术,通过流量录制回放能力 ...