首页 > 其他分享 >Vulkan学习苦旅02:看不见的窗口(创建VkInstance与VkSurfaceKHR)

Vulkan学习苦旅02:看不见的窗口(创建VkInstance与VkSurfaceKHR)

时间:2024-01-27 23:24:19浏览次数:27  
标签:02 VkInstance VkSurfaceKHR 窗口 函数 创建 VulkanApp glfw Vulkan

在上一节中,我们搭建了学习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

相关文章

  • THUWC2024 游记
    省流:D1T3,Pretest97,D2和4.so决斗两小时(胜利)。day0从成都早上坐火车,中午到了重庆。坐轻轨到了酒店附近,虽然我不住酒店。lxs带着吃了一碗面。重庆的面挺好吃的。在酒店大厅坐着的时候有个东北老哥过来搭讪。但是我是社恐......
  • 【专题】2022中国工业机器人市场研究报告PDF合集分享(附原数据表)
    报告链接:https://tecdat.cn/?p=33224本报告合集将基于中国工业产业升级和智能制造的背景,通过对供应端市场和产业链的分析,结合投资视角,探讨工业机器人企业如何增强自身竞争力,推动中国工业产业发展,为企业带来新的增长和转型机会,并从而思考中国工业机器人行业的现状和未来趋势。阅读......
  • THUWC 2024 游记
    Day-1抵达重庆!吃火锅被辣死了/kkDay0试机!不会做题/tuuDay1起的很早!但是为什么我都到考场了天还是黑的。这个T1很签到啊!想了一会会了\(3^nm\),获得了77分的高分!又想了一会会了\(3^n+2^nm\),写了下过了,开T2。这个T2看着就很poly啊!先写了写\(m=1\),然后又写了矩乘......
  • 2024 上海个人购买新能源电动汽车送牌条件 All In One
    2024上海个人购买新能源电动汽车送牌条件AllInOne上海购买电动车条件上海市鼓励购买和使用新能源汽车实施办法上海市人民政府办公厅关于转发市发展改革委等五部门制订的《上海市鼓励购买和使用新能源汽车实施办法》的通知发布日期:2021-02-10第四条(消费者)本实施办法所......
  • (2024.1.22-2024.1.28)C语言学习小结
    本周主要围绕《HeadfirstC》这本书展开C语言学习,按照计划,我学习了前四章的内容。基本内容以下时学习做的思维导图(笔记)第1章虽然做的是思维导图,但实际上因为大多数内容已经掌握,所以实际上就是补充记了几个零散的点。第2、2.5章主要是指针、数组、字符串的内容,大多也已经......
  • THUSC2024 游记
    Day-1晚上没有晚自习,提前下播,然后好像一直在打隔膜很晚才睡觉。Day0坐车去了CQ,感觉酒店环境还是不错的,刷了inf个小时的B站。试了一下机子,感觉不是很慢,键盘还能用,但是VScode的自动补全有点极端了。试机赛构造想了30min才会T2,不会T3睡得比较早,但是睡眠质量良好。......
  • NOIP2023 游记
    记忆已经不太清晰,所以写的不多。终究还是懒,现在才开始写游记。先说战绩,146,1=线153,遗憾离场。考前一天晚上让zxy给买了点零食,他的品味还是不错的。跟他讨了一包陈皮味的压片糖,有点涩。晚上去历城二中试机,实际上键盘不错,和我们平常用的差不多,我看桌面上有vscode,但是没装插件......
  • 2024/1/27学习进度笔记
    1)NLP基本概念①NLP(NaturalLanguageProcessing),也就是人们常说的「自然语言处理」,就是研究如何让计算机读懂人类语言,即将人的自然语言转换为计算机可以阅读的指令。②分词是NLP任务的一个起始,分词的好坏会影响整体模型的好坏。并且分词不一样,语义不一样。1.中国北京大......
  • 2024.1.27寒假每日总结18
    算法题:2861.最大合金数-力扣(LeetCode)git学习Git是一款免费、开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。Git是一个开源的分布式版本控制系统,可以有效、高速的处理从很小到非常大的项目版本管理。Git是LinusTorvalds为了帮助管理Linux内核开发而......
  • 2019-2020 ICPC Southwestern European Regional Programming Contest (SWERC 2019-20
    Preface这场总体打的不错,虽然最后RushL题失败,没有想到关键优化导致没卡过去有点可惜,但奈何徐神还是太C了最后10题下班,赛后祁神发现L关键优化10min改完就过了,同时赛中徐神也看出了E的做法,感觉这场时间充足甚至有AK的可能的说A.Environment-FriendlyTravel很典的一个题,不难......