在上一节中,我们搭建了学习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);
}
}
标签:02,VkInstance,VkSurfaceKHR,窗口,函数,创建,VulkanApp,glfw,Vulkan From: https://www.cnblogs.com/overxus/p/vulkan02.html