[Vulkan教程]概述

本章将首先介绍Vulkan和它解决的问题。我们介绍绘制第一个三角形所需的各项内容。让你对之后的每一章有直观的感受。最后我们会学习Vulkan API的结构和通用使用模式。

Vulkan起源

和之前的图形API相同,Vulkan被设计为一个跨平台的。大多数API受限于时代特色的图形硬件和可配置的固定功能。程序员必须以标准给是提供顶点数据,并在光照和着色选项方面受GPU制造商的支配。

随着显卡架构的成熟,它们开始提供越来越多的可编程功能。所有的这些新功能都必须以某种方式与现有的API继承。这使得图形驱动对现代图形架构的抽象不太理想,程序员意图不能被正确处理。这就是为什么驱动需要经常更新以提高游戏性能,甚至有时提升是巨大的。由于驱动程序的复杂性,开发人员还需要处理不同供应商之间的差异性,比如不同的着色器语法。除了新功能的出现,过去十年还有很多移动平台的设备出现。因为功率和空间的限制,它们还有着不同的架构。比如平铺渲染,程序员可以通过对该功能的更多控制来提高程序性能。API的另一个限制就是对多线程的支持,这导致程序在CPU端出现瓶颈。

  1. 对现代图形架构的抽象不理想
  2. 着色器语法差异性
  3. 新的嵌入式平台架构
  4. 多线程支持

Vulkan针对现代图形架构从头开始设计,以解决这些问题。它允许程序员使用更精确的API调用来表明他们的意图,以减少驱动程序的开销,它允许多个线程并行创建和提交命令。它通过使用标准化字节码来处理着色器编译的不一致问题。它通过将图形和计算功能统一到一个API中以承认现代显卡的通用处理能力。

  1. 新的设计
  2. 开放的API调用
  3. 多线程支持
  4. 标准字节码着色器
  5. 统一图形和计算接口

绘制三角形

接下来,我们介绍在性能良好的Vulkan程序中,渲染三角形所需的所有步骤。这里涉及的所有概念都会在后续的章节中详细说明。这里是为了给你一个总体的感受,希望未来你可以将所有组件都关联起来。

第一步 - 创建实例和选择物理设备

Vulkan程序应首先创建一个VkInstance。创建VkInstance后,你可以查询支持的硬件,然后选择一个或多个VkPhysicalDevice来使用。你可以通过查询VRAM大小和设备的功能来选择合适的设备。例如选择一张专用显卡。

第二步 - 逻辑设备和队列家族

选择硬件设备后,需创建一个逻辑设备VkDevice。通过VkPhysicalDeviceFeature来启用你想要的特性,例如多视口渲染(multi viewport rendering)和64位浮点数。你还要指定要用到的队列家族,这是必须的,因为Vulkan执行的大多数操作都要提交给VkQueue,如绘制和内存管理。队列是从队列簇中分配的。每个队列簇都支持一组特定的操作。图形、计算和内存传输可能有单独的队列簇。队列簇可视为选择物理设备的区别因素。支持Vulkan的设备可能不提供任何图形功能。但今天所有支持Vulkan的显卡通常都支持我们感兴趣的队列簇。

第三步 - 窗口表面和交换链

我们需要创建一个窗口来呈现渲染图像,当然如果你只对离屏渲染感兴趣,你可以不创建。我们可以使用本地平台API或GLFWSDL来创建窗口。本教程使用GLFW,我们在下一章会详细介绍。

我们需要两个组件来渲染到窗口:窗口表面VkSurfaceKHR和交换链VkSwapchainKHR。KHR后缀代表它们是Vulkan扩展。Vulkan API本身和平台无关,所以我们需要WSI(窗口系统接口)扩展来与窗口管理器交互。表面是对要渲染的窗口的跨平台抽象,通常通过本机窗口的句柄来初始化,例如Windows上的HWND。GLFW库有一个内置函数处理平台特定的细节。

交换链有若干个渲染目标,它的目的是确保我们当前渲染的图像与屏幕上渲染的图像是不同的。这对于确保只显示完整的图像很重要。当我们要渲染一帧时,我们都必须要求交换链为我们提供一个渲染目标。当我们完成一帧的渲染后,我们将渲染目标还给交换链,以便让其在某个时刻呈现到屏幕上。渲染目标的数量和在屏幕上呈现渲染目标的时机,取决于呈现模式(present mode)。常见的模式是双缓冲(vsync)和三重缓冲。我们将在交换链创建章节中研究这些。

一些平台允许你直接渲染到显示器而无需VK_KHR_displayVK_KHR_display_swapchain扩展。这允许你创建一个代表整个屏幕的表面,并可以用于实现你自己的窗口管理器。

第四步 - 图像视图和帧缓冲

要绘制内容到交换链给我们的图像上,我们需要把它们包装成VkImageViewVkFramebuffer。图像视图引用一个图像的一部分来使用,帧缓冲引用几个图像视图,用于绘制颜色、深度和模板。交换链给我们提供了多个图像,我们会事先为每个图像创建一个图像视图帧缓冲,在绘制的时候,选择一个绘制。

第五步 - 渲染通道

Vulkan中的渲染通道描述了渲染过程中使用的若干图像的类型、如何使用它们、以及如何处理它们的内容。最初,在我们绘制三角形的应用程序中,我们会使用单个图像作为颜色目标,并在绘制操作之前把他们清除为纯色。渲染通道仅描述图像(插槽)的类型,VkFramebuffer将特定的图像绑定到这些插槽。

第六步 - 图形管线

在Vulkan中,图形管线通过创建VkPipline对象来设置。它描述了图形显卡可配置的状态,例如视口大小、深度缓存操作和着色器(通过VkShaderModule对象)等。VkShaderModule从着色器代码创建。我们通过引用渲染通道来告诉驱动,管线的绘制目标。

和其它现有的API相比,Vulkan最显著的特性是几乎所有图形管线的配置都需要提前设置好。这意味着,如果你要切换着色器或稍微改变顶点布局,就需要完全重建图形管道。你必须为渲染操作所需的所有不同组合提前创建许多VkPipline对象。只有一些像视口大小和清除颜色等基本配置可以动态更改。所有的状态都需要明确指定,像没有颜色混合。

好消息是,因为你提前了这些操作,驱动可以有更多的优化机会。并且因为像切换管线这种大的状态改变非常明确,所以运行时性能可以被预见。

第七步 - 命令池和命令缓冲区

像前面说的,Vulkan中的许多操作需要提交到一个队列中执行。这些操作在被提交之前,需要先记录到VkCommandBuffer。这些命令缓冲区从VkCommandPool中创建。VkCommandPool和特定队列簇相关联。为了绘制一个简单的三角形,我们创建一个命令缓冲区并记录以下操作:

  • 开始渲染管道(Begin the render pass)
  • 绑定图形管线(Bind the graphics pipline)
  • 绘制三个顶点(Draw 3 vertices)
  • 结束渲染管道(End the render pass)

缓冲区中的图像是交换链返回给我们的特定图像,我们为每一个可能的图像记录一个缓冲区,绘制的时候选择一个(TODO)。另一种方法是每一帧都重新记录缓冲区,效率会低一些。

第八步 - 主循环

有命令缓冲区后,主循环就非常简单。首先,我们通过vkAcquireNextImageKHR从交换链中获取图像,然后,我们为该图像选择适当的命令缓冲区,然后,通过vkQueueSubmit执行命令缓冲区。最后,我们通过vkQueuePresentKHR指令将返回给交换链的图片显示到屏幕上。

提交到队列的操作是异步执行的,我们需要使用信号量(semaphores)等同步对象来确保程序正确的执行顺序。绘制命令缓冲区的执行必须等待图片绘制完成。否则,我们可能绘制到一个正在被读取以显示到屏幕的图像。同样的,vkQueuePresentKHR的调用也要等待绘制完成,也就是说,绘制完成的时候,我们需要发出第二个信号量。

总结

相信,通过上述描述,你已经为我们即将绘制的第一个三角形的步骤有了基本的认识。正式的程序会包含更多的步骤,像分配顶点缓冲区、创建uniform缓冲区和上传纹理图像等,这些将会在后续章节中介绍。Vulkan的学习曲线还是比较陡峭的,刚开始我们从简单的来。像最初,我们会将顶点坐标嵌入到顶点着色器中,而不是使用顶点缓冲区,因为管理顶点缓冲区需要我们熟悉命令缓冲区。

简而言之,绘制第一个三角形,我们需要:

  • 创建一个VkInstance
  • 选择一个合适的图形显卡(VkPhysicalDevice
  • 为了绘制和显示创建VkDeviceVkQueue
  • 创建窗口、窗口表面和交换链
  • 封装交换链的图片到VkImageView
  • 创建一个渲染通道,指定渲染目标和用途
  • 为渲染通道创建帧缓冲
  • 设置图形管线
  • 为每个可能的交换链图片,创建命令缓冲区并记录绘制命令
  • 按照获取图像、提交绘制命令缓冲区、返回图像给交换链的顺序绘制各帧

虽然有很多步骤,但阅读之后的章节后,每步的意图都将变得非常简单和清楚。如果你对单个步骤和整个程序之间的关系感到困惑,你应该回头再看看这一章。

API概念

本章最后简要概述 Vulkan API 的底层结构。

编码约定

所有的Vulkan函数、枚举和结构都定义在vulkan.h中。vulkan.h在LunarG开发的Vulkan SDK中。在下一章中,我们会介绍如何安装该SDK。

Vulkan函数都有vk前缀,枚举和结构体类型都有Vk前缀,枚举元素有VK_前缀。Vulkan 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::cerr << "failed to create object" << std::endl;
    return false;
}

Vulkan中的许多结构体都需要你通过sType显式地指定其类型。pNext指向一个扩展结构体,本教程将一直为nullptr。创建或销毁对象的函数会包含一个VkAllocationCallbacks的参数,他允许你为驱动内存使用一个自定义的分配器。本教程其也将一直为nullptr

绝大多数函数会返回一个VkResult,要么为VK_SUCCESS,要么代表有错误发生。规范中描述了每个函数可能返回的错误代码以及它们的含义。

验证层

如前所述,Vulkan旨在高性能和低驱动开销。因此,它默认只有一些有限的错误检查和调试功能。如果你做错了什么,驱动程序通常会崩溃而不是返回错误代码。更糟的情况是,程序可能在你的显卡上工作正常,在其它显卡上却完全不行。

Vulkan允许你通过验证层开启更全面检查。验证层指的是可以插入到API和图形驱动程序之间的一段代码,可用于执行函数参数检查、跟踪内存管理问题等操作。而且它允许你在开发期间启用它们,而在发布程序时完全禁止它们,实现零开销。每个人都可以写自己的验证层。LunarG在Vulkan SDK中提供了一组标准验证层。你需要注册一个回调函数来接收来自验证层的调试信息。

Vulkan中每步都十分明确,并且验证层检查非常全面,因此,和OpenGL与Direct3D相比,更容易找出为什么屏幕是黑色的原因。

在我们开始编码之前,只剩一步了,那就是设置开发环境

目录
上一节 介绍
下一节 开发环境

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值