欢迎光临散文网 会员登陆 & 注册

Vulkan 01 简介

2023-03-25 08:31 作者:rabanastre  | 我要投稿

这一章介绍Vulkan,然后看看怎么绘制第一个三角形。后面的章节会详细介绍。


Vulkan的起源

和之前的图形接口OpenGL一样,Vulkan也是一个跨平台的GPU接口。以前的接口的问题是它们局限于固定功能。编程人员必须用标准的格式提交顶点数据,且只能靠GPU制造商提供的光照和着色选项。

随着显卡的发展,它们已经能提供更多的可编程能力。在不推翻原有API构架的基础上,通过增补来提供新的API,结果是一个导致一个越来越不不理想的接口。最近几年移动端GPU使用Tile Based Rendering可以降低功耗。旧的API的另一个问题是它们对多线程渲染支持不好。

Vulkan重新设计了一套图形接口。编程人员可以清楚的指定GPU的工作,并且多个线程并行的创建和提交命令。Vulkan只使用一个专门的offline着色器编译器,减少了各个厂商自行实现的on the fly编译器的不一致性。最后,它把graphics functionality和compute functionality合并成为一套统一的API。


如何绘制一个三角形

简要介绍vulkan如何初始化和绘制第一个三角形。


1 - 实例(Instance)和物理设备选择

Vulkan程序始于VkInstance(),它创建一个vulkan实例。

实例创建后,就可以查询机器上支持vulkan的硬件,并且选择一个或多个VkPhysicalDevice来使用。你可以查询如VRam大小、设备能力,最后选择你喜欢的设备,例如优先选择独显。


2 - 逻辑设备和queue family

决定选哪个设备后,你应该创建一个VkDevice(逻辑设备),来代表那个设备。

你还需要指定要使用那个queue family。vulkan可以执行的操作,如绘制命令命令、内存操作,都是提交到VkQueue之后异步执行的。图形命令,计算命令,内存传输操作,都有各自对应的队列类型。显卡可以只支持某些类型的队列而不是全部,但是现在的显卡基本都支持所有的队列类型了。


3 - window surface和swap chain

除非你只想做离屏渲染(offscreen rendering),你都需要创建一个窗体来显示渲染结果。

你可以用操作系统提供的API创建窗口,也可以用封装后的库例如GLFW和SDL。

我们将使用GLFW。

我们需要这两个组件:window surface(VkSurfaceKHR)和 swap chain(VkSwapChainKHR)。

注意后缀KHR,它表示这些类型是vulkan的扩展部分。因为vulkan api本身是和平台无关的,所以我们需要使用标准的WSI(Window System Interface)扩展来和窗口交互。Surface是一个跨平台的对窗体的抽象,它在初始化时需要一个本地类型的窗口handle作参数,例如windows的HWND。好消息是GLFW已经帮我们处理了平台相关的细节。

Swap chain是一系列的render target。它的基本目的是保证当我们在渲染到一个缓存区时,这个缓冲区没有被用来显示。这很重要,这样才能保住只有已经渲染完了的图像会被用来显示。

当我们渲染完一帧后,这个图像被返还给swap chain等待显示。Render target的数量和present条件取决于当前设置的模式。通常的模式是双缓冲(vsync)以及三层缓冲。


4 - image view和framebuffer

要在从swap chain得到一个image上绘制,你必须把它包裹进VkImageView和VkFrameBuffer。Image view用来指定image的那个部分将被使用,framebuffer用来包装用于渲染的颜色view、深度view和stencil view。因为swap chain里面可能有很多image,我们预先给它们各自都创建image view和framebuffer,然后在绘图时选择对应的那个。


5 - render pass

Vulkan的render pass用来描述渲染操作中使用的image类型,它们会如何被使用,如何对待它们的内容。在我们的第一个渲染三角形的程序中,我们将告诉vulkan我们会只使用一个image作为color target。


6 - graphics pipeline

创建一个VkPipeline对象,它代表显示卡的那些可配置的状态,例如viewport size和深度缓冲的参数。创建一个VkShaderModule对象,来代表着色器。

和OpenGL最大的不同是,几乎所有的可配置选项都必须预先设置。意思是,如果你需要切换到另一个着色器或者稍稍的改变顶点布局,你需要完全的重新创建graphics pipeline。这意味着你必须你将预先创建很多个VkPipeline的对象。只有少数一些基本的设置,如viewport size和clear color可以动态的改变,所有状态都需要明确描述,例如没有默认的颜色混合模式。

好消息是,因为这样做是ahead-of-time编译而不是just-in-time编译,所以驱动有更多的信息可以用于优化,程序的性能会更高。


7 - command pool和command buffer

如前面提到的,vulkan的很多操作如绘图命令,都是先提交到队列。命令要先存到VkCommandBuffer中,然后才能提交。这些缓冲是使用VkCommandPool创建的,并且需要指定队列类型。要绘制一个三角形,我们需要如下操作:

● 开始render pass

● 绑定graphics pipeline

● 绘制3个顶点

● 结束render pass

因为framebuffer中的图像取决于swap chain给我们的image,因此我们需要为每个可能的image存一个command buffer,然后在绘制时选择对应的command buffer。另一个方法时在每一帧都存command buffer,但不是最有效的。


8 - 主循环

当绘制命令包裹进command buffer后,主循环就很直截了当了。我们先从swap chain中获得一个image(调用函数vkAcquireNextImageKHR)。之后我们可以选择适合这个image的command buffer,并且用vkQueueSubmit提交命令。最后我们把image还给swap chain用于展示,用vkQueuePresentKHR函数。

提交的指令被异步的执行。因此我们需要使用同步对象例如信号量(semaphore)来保证非冲突访问。必须在image获取成功后才能开始执行command buffer,否则可能导致image正在被读取用于展示,但程序却企图在上面绘制。另外,vkQueuePresentKHR调用需要等待渲染结束,因此我们需要第二个信号量,它在渲染结束后被产生信号。


Vulkan API的一些概念

所有vulkan的函数,枚举类和结构体都定义在vulkan.h头文件中。Vulkan SDK是由LunarG开发的。

函数带有vk前缀,枚举和结构体类型有Vk前缀,枚举类型的值有VK_前缀。

API大量使用结构体来传递参数。

例如,对象创建基本上是如下形式:

VkXXXCreateInfo createInfo {};

createInfo.sType = VK_STRUCTURE_TYPE_XXX_CREATE_INFO;

createInfo.pNext = nullptr;

createInfo.foo = ...;

createInfo.bar = ...;


VkXXX object;

if(vkCreateXXX(&createInfo, nullptr, &object) != VK_SUCCESS)

{

    std::err << "failed to create object" << std::endl;

    return false;

}

Vulkan的很多结构体需要在sType字段指定结构体的类型。pNext字段可能指向一个扩展结构但是在本教程中将一直是nullptr。创建和销毁对象的函数,会有一个VkAllocationCallbacks的参数允许你使用自定义的内存分配器,本教程中也全部使用nullptr。

几乎所有的函数都返回要么VK_SUCCESS或者一个错误码。


验证层(Validation layers)

如前面提到的,vulkan被设计成高效的和低驱动负担的。所以它之后很有限的错误检查和调试能力的。如果程序写的不对,经常会直接崩溃而不是返回错误代码,可能更糟糕的是,它好像在你的显卡可以工作但是在别的显卡上会完全失败。

Vulkan允许你使用验证层来做大量的检查工作。验证层是插入到API和显示驱动间的代码,用来做检查工作,例如函数参数检查,跟踪内存管理问题。好的是你可以在开发是启用它,然后在发布时完全关闭它使程序没有这些额外负担。任何人都可以编写自己的验证层,但是LunarG的vulkan sdk提供了一套标准的验证层。此外,你还需要注册一个回调函数来接收这个层返回的调试消息。

接下来只差一步就可以开始写代码了,那就是配置开发环境。




















Vulkan 01 简介的评论 (共 条)

分享到微博请遵守国家法律