Vulkan学习小结——框架搭建

毕设要用Vulkan做个渲染模块,最近几天才开始学习....

一个典型的Vulkan程序框架如下所示:

class VulkanApplication
{
public:
	void run()
	{
		initWindow();
		initVulkan();
		mainLoop();
	}
private:
void initWindow()
	{
		glfwInit();
		glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
		glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
		window = glfwCreateWindow(WINDOW_HEIGHT, WINDOW_WIDTH, WINDOW_TITLE, nullptr, nullptr);
	}

	void initVulkan()
	{
		createInstance();
		setUpDebugCallback();
		createSurface();
		pickPhysicalDevice();
		createLogicalDevice();
		createSwapChain();
		createImageViews();
		createRenderPass();
		createGriphicsPipeline();
		createFragmentBuffer();
		createCommandPool();
		createCommandBuffer();
		createSemaphores();
	}

	void mainLoop()
	{
		while (!glfwWindowShouldClose(window))
		{
			glfwPollEvents();
			drawFrame();
		}
		vkDeviceWaitIdle(device);
		glfwDestroyWindow(window);
		glfwTerminate();
	}
}
一.Vulkan创建流程的回顾:
1.使用GLFW初始化Window,设置ClientAPI类型,窗口是否可以拉伸,最后创建窗口。
2.初始化Vulkan部分:
(1)创建VkInstance
使用VkInstanceCreateInfo初始化信息,其包含VkApplicationInfo以及Extension的Validation Layer的信息。由于Vulkan是平台独立的API,与特定的窗口系统交互时需要引入额外的Extension,使用glfwGetReqiuredInstanceExtension获取窗口系统需要的Extension,使用vkEnumerateInstanceExtensionProperties获取当前版本Vulkan所支持的Extension。另外一个可选的属性就是Validation Layer,使用vkEnumerateInstanceLayerProperties检测当前Vulkan支持的Layer。
(2)设置调试信息
如果在上一步中开启了Debug使用的validationLayer,需要设置对应的回调,即创建vKDebugReportCallback的实例,对于每一个Vulkan对象,创建时都需要一个对应的CreateInfo,因此这里需要设置VkDebugReportCallbackCreateInfoEXT(扩展方法带后缀EXT),这里可以指定输出日志的级别,一般输出Warning和Error的信息。关于Validation Layer更详细的信息可以参考SDK目录下的vk_layer_settings.txt文件。
(3)创建Surface
Vulkan不能直接与窗口系统交互,需要使用WSI扩展来完成这一步骤。Surface对应Vulkan里的VkSurfaceKHR,glfw库提供了一个glfwCreateWindowSurface方法来屏蔽平台相关的操作(例如在Win32平台下需要获取窗口的HWND和HMODULE)。这一步是可选的,比如使用Vulkan做一些计算操作就不需要可视化的窗口。
(4)选择物理设备
这一步是选择一个支持Vulkan的显卡设备,使用vkEnumeratePhysicalDevices枚举出所有的显卡信息,随后检测每个设备是否支持应用所需要的Extension,QueueFamily以及SwapChain,如果要进行渲染操作,一般需要支持SwapChain。对每个筛选后的设备可以进行二次评分,最后选择最佳设备。
(5)创建逻辑设备
对于每个逻辑设备,需要创建其需要的Queue,如果要完成正常的渲染与显示,需要Graphics Queue和Present Queue。还需要指定使用的Extension以及Validation Layer的信息。在Device创建完成后,使用vkGetDeviceQueue取得对应Queue的引用。
(6)创建SwapChain
首先需要检查物理设备对SwapChain的支持信息,包括VkSurfaceCapabilitiesKHR,VkSurfaceFormatKHR和VkPresentModeKHR,接着根据物理设备对三者的支持程度选择最佳的一个,其中VkSurfaceCapabilitiesKHR对应VkExtent2D。在创建SwapChain之后,使用vkGetSwapchainImagesKHR获取其包含Image的引用。
(7)创建ImageView
为上一步的每个Image创建对应的VkImageView,通过ImageView来使用Image。
(8)创建RenderPass
RenderPass的创建需要Attachment(VkAttackmentDescription)以及SubPass(VkSubPassDescription)的信息,SubPass又需要VkSubpassDependency以及VkAttachmentReference的信息。SubPass在这里是必备的。即使并不使用SubPass。
(9)创建GraphicsPipeLine
首先创建VkShaderMoudle,Vulkan使用GLSL作为CG语言,但是不支持直接读取文本之后创建Shader对象,这是为了防止编译器错误导致程序崩溃,Vulkan使用的Shader在使用GLSL编写完成后,需要调用Shader的编译器将其编译成后缀为SPV的二进制文件,这一步可以检查出Shader的语言错误,确保在运行时Shader可以正确的运行。ShaderModule创建完成后,需要创建VkPipelineShaderStageCreateInfo来指定Shader的类型(顶点还是片元,入口函数)。
接着创建VkPipelineVertexInputStateCreateInfo,该对象配置了顶点的输入信息。
然后创建VkPipelineInputAssemblyStateCreateInfo,配置了图元的拓扑结构(点,线等)。
之后创建VkPipelineViewportStateCreateInfo,该配置需要VkViewport和裁剪区域VkRect2D的信息。
接下来是光栅化的配置VkPipelineRasterizationStateCreateInfo,多重采样的配置VkPipelineMultisampleStateCreateInfo以及颜色混合配置的VkPipelineColorBlendStateCreateInfo,动态配置VkPipelineDynamicStateCreateInfo和布局配置VkPipelineLayoutCreateInfo。
所有这些配置完成后,填充VkGraphicsPipelineCreateInfo结构并创建GraphicsPipeLine。
(10)创建FragmentBuffer
为每个VkImage创建对应的VkFragment对象,其attachments指向VkImageView。
(11)创建CommandPool
创建CommandPool来缓存Command,需要为CommandPool指定QueueFamilyIndex。
(12)创建CommandBuffer
创建CommandBuffer,使用vkBeginCommandBuffer和vkEndCommandBuffer记录CommandBuffer的操作,使用vkCmdBeginRenderPass和vkCmdEndRenderPass以及vkCmdDraw进行绘制
(13)创建信号量Semaphore
由于绘图和交换的操作的都是异步的,这里需要创建信号量来指示可用的image和渲染完成的状态。
这里使用ImageAvailableSemaphore和RenderFinishedSemaphore两个信号量来标识两者。
(14)在主循环中提交CommandBuffer并进行显示
每帧进行绘制时,首先通过vkAcquireNextImageKHR取得可用Image的索引,随后创建VkCommitInfo并使用vkQueueSubmit将绘制的CommandBuffer的命令提交到队列中,绘制完成后,将触发RenderFinishedSemaphore信号,这时使用使用VkPresentInfoKHR以及vkQueuePresentKHR方法将其显示到屏幕上。
3.mainLoop循环
处理窗口事件以及绘制。

一些特点总结:
(1)每个Vulkan对象VkXXX创建时需要配置一个对象对应的VkXXXCreateInfo,复杂的VkXXXCreateInfo可能包含子CreateInfo,每个CreateInfo的sType需要显式的进行指定。
(2)有些Vulkan对象在VkInstance销毁时自动释放,更多的对象需要手动调用对应的销毁函数来释放,可以将其使用一个VDeleter类包装,构造时传入对应的销毁函数,在VDeleter的析构函数中进行调用。
template 
class VDeleter
{
public:

	VDeleter() : VDeleter([](T, VkAllocationCallbacks *) {}) {}

	VDeleter(std::function deletef)
	{
		this->deleter = [=](T obj) {deletef(obj, nullptr); };
	}

	VDeleter(const VDeleter &instance, std::function deletef)
	{
		this->deleter = [&instance, deletef](T obj) {deletef(instance, obj, nullptr); };
	}

	VDeleter(const VDeleter &instance, std::function deletef)
	{
		this->deleter = [&instance, deletef](T obj) {deletef(instance, obj, nullptr); };
	}

	~VDeleter()
	{
		cleanUp();
	}

	T* replace()
	{
		cleanUp();
		return &object;
	}

	operator T() const
	{
		return object;
	}

	const T* operator &() const
	{
		return &object;
	}

	void operator=(T rhs)
	{
		if (rhs != object)
		{
			cleanUp();
			object = rhs;
		}
	}

	template
	bool operator==(V rhs)
	{
		return object == T(rhs);
	}

private:
	T object{ VK_NULL_HANDLE };
	std::function deleter;

	void cleanUp()
	{
		if (object != VK_NULL_HANDLE)
			deleter(object);
		object = VK_NULL_HANDLE;
	}
};

运行结果:

Vulkan学习小结——框架搭建_第1张图片

可以看到1000行左右才画出来一个三角形,而且并没有使用VertexBuffer和IndexBuffer(顶点信息在Shader里进行了硬编码)。


你可能感兴趣的:(Vulkan,图形学)