在上一节中,我们搭建了学习Vulkan所需的环境。今天,我们将会初步了解“地图”顶层的内容。 

如图所示,“地图”的顶层有两个模块: Instance和SurfaceKHR. 其中,Instance表示应用程序的一个实例,它用于保存一些状态,我们可以在应用程序中创建多个实例,但目前我们只创建一个实例;SurfaceKHR与图像在屏幕上的显示有关。

Vulkan并不仅仅是图形API,它还可以实现纯计算的任务,此类任务并不需要窗口,因此与显示相关的功能作为Vulkan的扩展功能,而不是核心功能。

接下来,我们将了解如何创建Instance和SurfaceKHR对象,并在此过程中了解到创建Vulkan对象的一些套路。不过在此之前,需要新建一个应用程序类。

1. VulkanApp类

VulkanApp类的框架如下:

class VulkanApp {
public:
VulkanApp() {}
~VulkanApp() {}
void Run() {}
private:
// TODO: 此处添加变量和函数
};

在VulkanApp类中,我们在构造函数中创建各种Vulkan对象,并在析构函数中按照与创建顺序相反的顺序销毁它们,此外还有一个Run方法,一遍又一遍地绘制窗口。以后会定义一系列的变量和函数,它们都被定义为私有的成员变量或函数(即写在private的下方).

在main函数中按照以下方式使用这个类:

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

2. 导入glfw库

对于不同的操作系统,其窗口系统有着不同的实现,如果我们希望代码在不同操作系统上运行,就需要针对不同的系统使用不同的编程接口,例如使用Xlib或Xcb接口。或者,我们可以使用别人封装好的跨平台库,glfw正是这样的一个库。

使用glfw库前,需要引入相应的头文件:

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

glfw最早是为OpenGL(也是一种图形API,是Vulkan的老前辈)设计的,为了启用Vulkan相关的代码,需要定义GLFW_INCLUDE_VULKAN这个宏。之后,与Vulkan相关的头文件都会通过glfw.h被引入,无需额外引入Vulkan相关的头文件。

在构造函数的起始部分初始化glfw,并进行相应的设置:

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

其中:

glfwInit():初始化glfw库,使用glfw库之前需要先初始化这个库;

glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API):前面曾提到,glfw库最初是为OpenGL量身定做的,所以需要通过此函数调用,告诉glfw不要创建OpenGL相关的内容;

glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE):在Vulkan中,当用户调节窗口的大小时,需要手动完成一系列的设置,例如改变底层的窗口大小等等,因此我们暂时禁止调整窗口大小,以简化代码。

3. 创建实例(Instance)

Vulkan中首先需要创建的对象是实例对象,之后创建的对象都直接或间接地依赖它。函数vkCreateInstance用于创建一个实例,其原型为:

VkResult vkCreateInstance(
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);

Vulkan中的函数以vk开头,例如这里的vkCreateInstance.

各参数的含义如下:

pCreateInfo: 是一个指向结构体的指针。多数Vulkan对象的创建都需要填充一个结构体。在这里,如果我们想创建一个实例,就需要向VkInstanceCreateInfo结构体中填入相应的信息,最后通过指向此结构体的指针将结构体传入函数;

pAllocator: 与内存管理有关,用户可以自定义内存分配的方式。在本系列文章中,只要遇到此参数,我们就将其设置为nullptr;

  pInstance: 指向创建好的实例。

函数的返回值表示函数执行的状态,是一个类型为VkResult的枚举值。如果创建成功,则返回VK_SUCCESS;否则会返回其它类型的值。

创建VkInstance的关键在于填充VkInstanceCreateInfo结构体,其定义如下:

typedef sturct VkInstanceCreateInfo {
VKStructureType sType;
const void* pNext;
VkInstanceCreateFlags flags;
const VkApplicationInfo* pApplicationInfo;
uint32_t enableLayerCount;
const char* const* ppEnabledLayerNames;
uint32_t enableExtensionCount;
const char* const* ppEnabledExtensionNames;
} VkInstanceCreateInfo;

各成员的含义如下:

sType: 几乎所有的XXXCreateInfo结构体,其第一个成员都是sType,它是一个枚举类型的变量,表示此结构体的类型。例如,在这里将sType设置为VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,表示此结构体的类型是VkInstanceCreateInfo;

pNext: 有了它,方便向函数传入结构体链表,在本系列文章的绝大部分情况下,都将其设置为nullptr;

flags: 保留,供以后使用。Vulkan的很多结构体都有这个位,与XXXCreateInfo相关的flags成员大多保留,通常设置为0.

pApplicationInfo: 指向VkApplication结构体,此结构体用于记录应用程序的名称、版本号等;

enableLayerCount:启用层(Layer)的个数;

ppEnabledLayerNames:一个字符串数组的首地址,需要启用的层名称记录在一个字符串数组中;

enableExtensionCount:启用扩展(Extension)的个数;

ppEnabledExtensionNames:一个字符串数组的首地址,需要启用的扩展名称记录在一个字符串数组中。

结构体中的层和扩展可能会让人困惑,它们分别是什么呢?

层(Layer)提供了调试、日志、性能分析等功能。例如,有的层可以检查函数的参数是否合法,这对于调试代码很有用,但是一旦程序调试完成,这样的检查只会降低性能。因此,我们可以只在调试阶段启用一些具有特定功能的层。

我们将需要的层记录在一个vector中,为此,首先引入相应的头文件:

#include <vector>
using std::vector; // 由于vector在接下来经常会用到,这样可以每次少写个std::

在VulkanApp中定义字符串的vector成员变量mRequiredLayers, 并在其中写下我们需要启用的扩展:

const vector<const char*> mRequiredLayers = {
"VK_LAYER_KHRONOS_validation"
};

扩展(Extension)提供了额外的功能,例如在VSCode中安装的各种扩展。在这里,我们至少需要启用glfw需要的扩展,通过以下代码获取glfw所需的扩展:

uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

VulkanApp类中定义成员变量VkInstance mInstance表示创建的实例:

class VulkanApp {
private:
...
VkInstance mInstance; // Vulkan实例
};

所有的Vulkan对象都以Vk开头,例如这里的VkInstance.

接下来,定义成员函数createInstance创建一个实例,此函数的具体实现如下:

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(vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance) != VK_SUCCESS) {
exit(-1);
}
}

我们经常需要向结构体传递数组,传递的是数组的大小和首地址。有以下两种方式:

1. 直接使用数组,例如上面的glfwExtensions数组;

2. 使用vector:使用.size()传入数组大小,使用.data()传入数组首指针,例如上面的mRequiredLayers.

由于VkApplicationInfo这个结构体没有什么重要的东西,在这里就不另花篇幅介绍这个结构体了。

VkInstanceCreateInfo结构体中填充必要的信息,最后调用vkCreateInstance创建结构体,这就是创建一个VkInstance对象的全部步骤。

Vulkan创建对象的一般步骤

在Vulkan中,VkXXX类型的对象一般按如下方式创建:

1. VkXXX会有个与之关联的、名为VKXXXCreateInfo的结构体, 首先填充这个结构体;

2. 有个名为vkCreateXXX的函数,向函数中传入上一步填充的结构体(传入指向结构体的指针),创建这个对象;

3. 检查上一步中函数的返回值是否为VK_SUCCESS, 如果是,则表示VkXXX对象创建成功;否则需要作进一步处理,我们这里简单粗暴地使用exit(-1)终止程序。

后面,我们会反复使用类似的步骤创建各种Vulkan对象,总有一天你会看吐的。对于每个vkCreateXXX函数,都需要检查对象是否创建成功,为了方便,将检查返回值的过程封装为函数if_fail

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

当Vulkan对象创建失败时,输出错误信息message并终止程序,为了使用输出,请将#include<iostream>添加到代码的开头部分。有了这个函数,上面检查vkCreateInstance是否创建成功的代码可改写为:

if_fail(
vkCreateInstance(&instanceCreateInfo, nullptr, &mInstance),
"failed to create instance"
);

不要忘记在析构函数中销毁mInstance对象:

~VulkanDemo() {
vkDestroyInstance(mInstance, nullptr); // 销毁mInstance glfwDestroyWindow(mWindow);
glfwTerminate();
}

以后,当创建完一个对象后,我们都会立即在析构函数中添加销毁此对象的代码。

在函数vkDestroyInstance中,第二个参数与内存分配有关,与vkCreateInstance中的参数pAllocator含义相同,在接下来的文章中,此类参数全部设置为nullptr.

vkCreateXXX有一个与之对应的函数vkDestroyXXX,用于销毁某一对象。

4. 创建表面(Surface)

接下来需要创建一个glfw窗口,它是对不同系统中窗口的抽象,创建完成后会得到一个GLFWwindow*类型的窗口指针。为了创建窗口,需要提供窗口的宽和高,我们将这些值定义为VulkanApp的私有成员变量:

class VulkanApp {
public:
...
private:
int mWidth;
int mHeight;
GLFWwindow* mWindow;
};

例如mWidth的命名仅仅是个人偏好,前面的m表示这个变量是类的一个成员(member), 你可以换成自己喜欢的命名方式,例如m_width等。

定义函数createSurface用于创建表面,我们首先在这个函数中创建一个glfw窗口:

void createSurface() {
mWindow = glfwCreateWindow(mWidth, mHeight, "Vulkan App", nullptr, nullptr); // 创建glfw窗口
}

glfwCreateWindow用于新建一个窗口,前两个参数是窗口的宽和高;第三个参数是窗口标题;最后两个参数不用管它们,全部设置为空指针nullptr即可。

记得在析构函数中销毁glfw窗口:

~VulkanApp() {
glfwDestroyWindow(mWindow); // 销毁glfw窗口
glfwTerminate(); // 终止glfw
}

接下来就可以创建Surface对象了,在VulkanApp类中定义一个成员变量:

class VulkanApp {
private:
...
VkSurfaceKHR mSurface; // 表面
};

谁是KHR?

  可能有的朋友会疑惑:好好的VkSurface, 为什么后面会有个KHR呢?前面提到过,显示功能是Vulkan的一个扩展功能,如果在网上搜索Vulkan,你会发现Vulkan与一个名为Khronos Group的组织密切相关,相信聪明的你已经猜到KHR是谁了吧?

接着借助glfw提供的函数创建VkSurfaceKHR对象:

void createSurface() {
mWindow = glfwCreateWindow(mWidth, mHeight, u8"快显示出三角形", nullptr, nullptr); // 创建glfw窗口 /* 创建VkSurfaceKHR对象 */
if_fail(
glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface), // 创建表面需要Vulkan实例和glfw窗口。
"failed to create surface"
);
}

5. 收尾工作

最后在函数Run中添加以下代码:

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

这是程序的主循环,以后我们会在这个循环中添加绘制各种物体的代码。glfwWindowShouldClose检查当前窗口是否关闭,例如当我们点击窗口的关闭按钮后,这个函数就会返回true,从而结束循环。

目前,所有的代码放在末尾,如果此时运行代码,是可以看到一个空白窗口的:

另外,还能在输出中看到glfw要求启用的扩展(博主是在Windows上运行的,所以需要VK_KHR_win32_surface):

在下一节中,我们将会研究物理设备,即与GPU相关的代码。

6. 到目前为止的完整代码

创建VkInstance和VkSurfaceKHR
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h> #include <iostream>
#include <vector>
using std::vector; 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();
} ~VulkanApp() {
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"
};
VkInstance mInstance; // 实例 int mWidth = 800; // 窗口宽度
int mHeight = 600; // 窗口高度
GLFWwindow* mWindow = nullptr; // glfw窗口指针
VkSurfaceKHR mSurface; 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"
);
}
}; 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);
}
}

Vulkan学习苦旅02:看不见的窗口(创建VkInstance与VkSurfaceKHR)的更多相关文章

  1. darktrace 亮点是使用的无监督学习(贝叶斯网络、聚类、递归贝叶斯估计)发现未知威胁——使用无人监督 机器学习反而允许系统发现罕见的和以前看不见的威胁,这些威胁本身并不依赖 不完善的训练数据集。 学习正常数据,发现异常!

    先说说他们的产品:企业免疫系统(基于异常发现来识别威胁) 可以看到是面向企业内部安全的! 优点整个网络拓扑的三维可视化企业威胁级别的实时全局概述智能地聚类异常泛频谱观测 - 高阶网络拓扑;特定群集,子 ...

  2. Telnet客户端连接服务器,看不见字符,只显示横线

    Telnet 窗口看不见字符,只显示小横线 在用telnet连接tomcat服务器的 时候,窗口中不显示字符,显示成一个一个的横线 解决办法: 按住“Ctrl+]” 回车解决问题

  3. 【转】 学习ios(必看经典)牛人40天精通iOS开发的学习方法【2015.12.2

    原文网址:http://bbs.51cto.com/thread-1099956-1.html 亲爱的学员们: 如今,各路开发者为淘一桶金也纷纷转入iOS开发的行列.你心动了吗?想要行动吗?知道如何做 ...

  4. SaToken学习笔记-02

    SaToken学习笔记-02 如果排版有问题,请点击:传送门 常用的登录有关的方法 - StpUtil.logout() 作用为:当前会话注销登录 调用此方法,其实做了哪些操作呢,我们来一起看一下源码 ...

  5. 【Bug】看不见的分隔符: Zero-width space

    今天在调试一段代码的时候,有一个输入不能为空的库函数抛出了异常(为空就会抛出异常,就是这么傲娇).自己暗骂了自己一番,怎么这么大意,于是追溯源头,开始寻找输入控制的地方.但是当我找到时我惊呆了,我明明 ...

  6. 软件测试之loadrunner学习笔记-02集合点

    loadrunner学习笔记-02集合点 集合点函数可以帮助我们生成有效可控的并发操作.虽然在Controller中多用户负载的Vuser是一起开始运行脚本的,但是由于计算机的串行处理机制,脚本的运行 ...

  7. 学习ios(必看经典)牛人40天精通iOS开发的学习方法

    学习ios(必看经典)牛人40天精通iOS开发的学习方法 描述 这是一套从一个对iOS开发感兴趣的学员到iOS开发高手的系统.专业的课程体系.以培养企业开发真正需要的人才为目标,每个知识点都用案例来讲 ...

  8. Oracle Statspack报告中各项指标含义详解~~学习性能必看!!!

    Oracle Statspack报告中各项指标含义详解~~学习性能必看!!! Data Buffer Hit Ratio#<#90# 数据块在数据缓冲区中的命中率,通常应该在90%以上,否则考虑 ...

  9. thinkphp学习笔记10—看不懂的路由规则

    原文:thinkphp学习笔记10-看不懂的路由规则 路由这部分貌似在实际工作中没有怎么设计过,只是在用默认的设置,在手册里面看到部分,艰涩难懂. 1.路由定义 要使用路由功能需要支持PATH_INF ...

  10. MySQL 笔记整理(3) --事务隔离,为什么你改了我还看不见?

    笔记记录自林晓斌(丁奇)老师的<MySQL实战45讲> 3) --事务隔离,为什么你改了我还看不见? 简单来说,事务就是要保证一组数据操作,要么全部成功,要么全部失败.在MySQL中,事务 ...

随机推荐

  1. 红黑树是什么?红黑树 与 B+树区别和应用场景?

    红黑树是什么?怎么实现?应用场景? 红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉树. 意味着它满足二叉查找树的特征:任意一个节点所包含的键值,大于等于左孩子的键值,小 ...

  2. 五、java操作swift对象存储(官网样例)

    系列导航 一.swift对象存储环境搭建 二.swift添加存储策略 三.swift大对象--动态大对象 四.swift大对象--静态态大对象 五.java操作swift对象存储(官网样例) 六.ja ...

  3. element-plus-admin学习笔记

    https://toscode.gitee.com/hsiangleev/element-plus-admin

  4. python进阶(4)--字典

    文档目录: 一.一个简单的字典二.字典-增删改三.遍历字典四.字典嵌套 ---------------------------------------分割线:正文------------------- ...

  5. 【动画进阶】神奇的 3D 卡片反光闪烁动效

    最近,有群里在群里发了这么一个非常有意思的卡片 Hover 动效,来源于此网站 -- key-drop,效果如下: 非常有意思酷炫的效果.而本文,我们不会完全还原此效果,而是基于此效果,尝试去制作这么 ...

  6. css - 使用 figure 和 figcaption 快速实现 图片加文字的垂直方向的布局 ( 不支持ie9以下版本 )

    一,属性介绍 1. 浏览器支持 注释:Internet Explorer 8 以及更早的版本不支持 <figure> 标签.Internet Explorer 9, Firefox, Op ...

  7. android应用申请加入电池优化白名单

    首先,在 AndroidManifest.xml 文件中配置一下权限: 1 <uses-permission android:name="android.permission.REQU ...

  8. [转帖]MySQL多版本并发控制机制(MVCC)-源码浅析

    https://zhuanlan.zhihu.com/p/144682180 MySQL多版本并发控制机制(MVCC)-源码浅析 前言 作为一个数据库爱好者,自己动手写过简单的SQL解析器以及存储引擎 ...

  9. [转帖]如何理解 kernel.pid_max & kernel.threads-max & vm.max_map_count

    https://www.cnblogs.com/apink/p/15728381.html 背景说明 运行环境信息,Kubernetes + docker .应用系统java程序 问题描述 首先从Ku ...

  10. [转帖]SQL SERVER中隐式转换的一些细节浅析

    https://www.cnblogs.com/kerrycode/p/5853257.html 其实这是一篇没有技术含量的文章,精通SQL优化的请绕道.这个缘起于在优化一个SQL过程中,同事问了我一 ...