应用开发者可以使用 Vulkan来打造在GPU上执行命令的应用,大幅降低开销。与 EGL 和 GLES 相比,Vulkan 还可以更直接地映射到当前图形硬件中的功能,最大限度地降低驱动程序的出错概率,并减少开发者的测试时间。
Vulkan组件
对 Vulkan 的支持包含以下组件:
以android平台为例(参照https://source.android.google.cn/devices/graphics/arch-vulkan):
组件名称 | 提供者 | 介绍 |
Vulkan 验证层 |
Android NDK | 在 Vulkan 应用开发期间使用的库,用于查找应用在 Vulkan API 的使用方面的错误。在找出此类错误后,应移除这些库。 |
Vulkan 运行时 |
Android | 原生库 (libvulkan.so) 提供原生 Vulkan API。大部分功能由 GPU供应商提供的驱动程序实现。Vulkan运行时会封装驱动程序、提供 API 拦截功能(针对调试和其他开发者工具)以及管理驱动程序与平台依赖项之间的交互。 |
Vulkan 驱动程序 |
SoC | 将 Vulkan API 映射到特定于硬件的 GPU 命令以及与内核图形驱动程序的交互。 |
Buffer Queue |
Android | BufferQueue 类将可生成图形数据缓冲区的组件(生产方)连接到接受数据以便进行显示或进一步处理的组件(使用方) |
Gralloc | Android | 内存分配器会进行缓冲区分配,并通过两个特定于供应商的 HIDL接口来进行实现 |
Vulkan基本概念
现在,我们通过一个“如何使用Vulkan绘制三角形”的示例来了解Vulkan的基本概念。这里介绍的所有概念会在接下来的文章中详细阐述。当前只是给你一个大的框架,希望能把主要的部件联系起来。
步骤1:实例和物理设备选择
应用程序是通过VkInstance来使用Vulkan API的。创建VkInstance后,就可以查询Vulkan支持的硬件,选择其中一个或多个VkPhysicalDevices 进行操作。可以通过查询设备属性如VRAM大小,选择一个适合的设备(如专业的显卡)。
步骤2:逻辑设备和队列族
选择完合适的硬件设备后,需要使用更具体的VkPhysicalDevice特性(比如多viewport,64位浮点)来创建一个逻辑设备VkDevice。还需要指定想要使用的队列族。Vulkan将诸如绘制、内存操作命令提交到VkQueue中进行异步执行。队列由队列族分配,每个队列族支持一个特定操作集合。队列族的可用性也可以作为物理设备选择中的一个区别因素。支持Vulkan的设备可能不提供任何图形功能,但是现在支持Vulkan的所有显卡通常都支持常用的队列操作。
步骤3:Window Suface和Swapchain
通常我们需要创建一个窗口来显示渲染的图像。Window可以通过原生平台的API或GLFW、SDL库来完成。实际上我们还需要两个组件才能完成窗口渲染:VkSurfaceKHR和VkSwapChainKHR。注意到这两个对象都有一个KHR后缀,这表示它们属于Vulkan扩展。Vulkan API本身是完全平台无关的,这就需要大家使用WSI(Window System Interface窗口系统接口)扩展与原生的窗口管理器进行交互。Surface是windows上的跨平台抽象,通常通过提供对本机窗口句柄(例如windows上的HWND)的引用来实例化。幸运的是,GLFW库有一个内置函数来处理平台特定的细节。
Swapchain是一个渲染目标集合。它可以保证我们正在渲染的图像和当前屏幕图像是两个不同的图像。这可以确保显示出来的图像是完整的。每次绘制一帧时,可以请求Swapchain提供一张图像。绘制完成后,图像被返回到Swapchain中,在之后某个时刻,图像被显示到屏幕上。渲染目标数量和图像显示到屏幕的条件依赖于当前模式。常用的模式有双缓冲(vsync,垂直同步) 和三缓冲。
有些平台允许您直接渲染到显示器上,通过VK_KHR_display和VK_KHR_display_swapchain扩展而无需与任何窗口管理器交互。它们允许您创建一个Surface代表整个屏幕,可用于实现您自己的窗口管理器。
步骤4:图像视图和帧缓冲
从Swapchain获取图像后,还不能直接在图像上进行绘制,需要将图像先包装为VkImageView和VkFramebuffer。图像视图引用图像的特定部分,帧缓冲引用图像视图作为颜色,深度和模板目标。Swapchain中可能有许多不同的图像,可以预先为每个图像都创建好图像视图和帧缓冲,然后在绘制时选择对应的视图或帧缓冲。
步骤5:渲染通道
Vulkan中的渲染通道描述了渲染操作中使用的图像类型、如何使用这些图像以及应如何处理图像中的内容。
步骤6:图形管线
Vulkan的图形管线可以通过VkPipeline对象建立。它描述了显卡的可配置状态,比如视口大小和深度缓冲操作,及使用VkShaderModule对象的可编程状态。VkShaderModule对象由着色器字节码创建而来。驱动程序知道图形管线使用哪些渲染目标。
Vulkan与之前的图形API的一个最大不同是都需要提前配置完成几乎所有图形管线。这意味着如果我们想要使用另外一个着色器或者顶点布局,需要重新创建整个图形管线。显然效率很低,这就迫使大家提前创建出所有需要的图形管线,在需要时直接使用已经创建好的图形管线。图形管线只有很少一部分配置可以动态修改,比如视口大小和清除颜色。需要显式地描述图形管线的所有状态,比如,不存在默认的颜色混合状态。
这样做的好处类似于预编译相比于即时编译,驱动程序有更大的优化空间,以图形管线为切换单位,预期渲染效果变得十分容易。
步骤7:指令池和指令缓冲
之前提到,Vulkan的许多操作需要提交到队列才能执行。这些操作首先被记录到一个VkCommandBuffer 对象中,然后提交给队列。VkCommandBuffer对象由一个关联了特定队列族的VkCommandPool分配。
由于帧缓冲区中的图像取决于Swapchain提供的特定图像,大家可以提前为每个图像建立一个命令缓冲,然后在绘制时,直接选择正确的命令缓冲。另一种方法是在每一帧都重新记录命令缓冲区,但这样做效率不高。
步骤8:主循环
绘制命令封装进指令缓冲后,主循环变得非常简单。首先使用vkAcquireNextImageKHR函数从交换链获取一张图像。然后为该图像选择适当的命令缓冲区,并使用vkQueueSubmit函数执行。最后,再将图像返回到Swapchain,以便用vkQueuePresentKHR显示到屏幕。
提交给队列的操作是异步执行的。因此必须使用同步对象(如信号量)来确保正确的执行顺序。绘制命令缓冲区的执行必须设置为等待图像采集完成之后,否则可能会出现以下情况:开始渲染正在被读取并显示到屏幕上的图像。反过来,vkQueuePresentKHR调用需要等待完成渲染,为此,需要使用发出第二个信号量以通知渲染结束。
看上去有很多步骤,但每个步骤都可以使得接下来的文章阅读起来更加简单和清晰。如果你对一个步骤与整个程序的关系感到困惑,也许你应该返回过来继续阅读此篇文章。