[toc]

# 传统 RHI 和现代 RHI

传统 RHI 使用 RenderingContext 和状态机在 RHI 内部管理渲染流程和资源等 现代 RHI 基本把 GPU 接口都释放给你,自己去操作

# Vulkan 简单流程

  • 初始化层
    • 调用操作系统的 API 创建程序可以显示的窗口 (1)
    • 初始化 Vulkan
      • 程序和 Vulkan 库取得联系
      • Vulkan 和显卡设备取得联系
    • 创建 Swapchain
      • Vulkan 和显示窗口 (1) 取得联系
  • 渲染主循环
    • 输入
      • 几何信息
      • Uniform
    • 输出
      • 屏幕中显示的画面
    • 逻辑
      • 着色器
    • 更新 CPU 端的业务逻辑
    • 数据 CPU 拷贝到 GPU
    • 生成 Vulkan Command Buffer 把渲染任务交给 GPU 并在屏幕中显示
  • 退出程序
    • 释放 Vulkan 对象,CPU 数据
    • 关闭显示窗口

# 结合作业 0

# 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// OS specific macros for the example main entry points
// Most of the code base is shared for the different supported operating systems, but stuff like message handling differs

// Windows entry point
VulkanExample* vulkanExample;
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (vulkanExample != NULL)
{
vulkanExample->handleMessages(hWnd, uMsg, wParam, lParam);
}
return (DefWindowProc(hWnd, uMsg, wParam, lParam));
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR pCmdLine, int nCmdShow)
{
for (size_t i = 0; i < __argc; i++) { VulkanExample::args.push_back(__argv[i]); };
vulkanExample = new VulkanExample();
vulkanExample->initVulkan();
vulkanExample->setupWindow(hInstance, WndProc);
vulkanExample->prepare();
vulkanExample->renderLoop();
delete(vulkanExample);
return 0;
}

解释:
- 先构建一个 Vulkan 对象 - 初始化 Vulkan - 连接显示窗口 - 准备 Vulkan - 渲染循环 - 结束后销毁一切 上图是一个比较复杂的 Vulkan 初始化架构,一个应用带着多个 Vk 实例,一个实例有多个物理设备,每个物理设备可以有多个逻辑设备,最后实际的绘制命令会丢到 VkQueue 队列中。为什么这么复杂?
- 通常我们都是只需要一个实例,一个物理设备,一个逻辑设备,一个或多个队列
- 但是如果分布式 GPU 渲染时就可以将渲染任务拆分到各个地方 - 多个 GPU 设备 - 将任务拆分 - 计算任务一个队列 - 渲染任务一个队列 - blablabla

# VkInstance

VkInstance 是初始化 Vulkan liberary 用的
程序 -> VkInstance -> Vk 库

# VkLayer

Vulkan 将很多功能拆分到了不同的 Layer 中
比如 Debug 层: Validation Layer : VK_LAYER_KHRONOS_validation
RenderDoc 层: VK_LAYER_RENDERDOC_Capture 一种 Hook 的方式:

针对 Hook 的理解 (摘自百度):

钩子 (Hook),是 Windows 消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理 window 消息或特定事件。

可以认为是一种调试的工具流

# 物理设备

Vk 可以在一开始查询设备的配置信息:

1
2
3
4
5
6
7
8
std::cout << "Available Vulkan devices" << "\n";
for (uint32_t i = 0; i < gpuCount; i++) {
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(physicalDevices[i], &deviceProperties);
std::cout << "Device [" << i << "] : " << deviceProperties.deviceName << std::endl;
std::cout << " Type: " << vks::tools::physicalDeviceTypeString(deviceProperties.deviceType) << "\n";
std::cout << " API: " << (deviceProperties.apiVersion >> 22) << "." << ((deviceProperties.apiVersion >> 12) & 0x3ff) << "." << (deviceProperties.apiVersion & 0xfff) << "\n";
}

vkGetPhysicalDeviceProperties(device,descript) 函数用来获取设备信息,第二个参数是设备的信息

# vKQueue

Vulkan 将队列抽象成了 队列簇
每个队列簇支持的功能不同,比如 计算队列簇绘制队列簇 等,由于未来的 Gpu 硬件将会有不同运算单元的优化,所以给命令队列分簇也可以高效的调用硬件进行优化 队列簇称之为 vkFamilyhomework0 中有这样一段函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
* Default constructor
*
* @param physicalDevice Physical device that is to be used
*/
VulkanDevice::VulkanDevice(VkPhysicalDevice physicalDevice)
{
assert(physicalDevice);
this->physicalDevice = physicalDevice;

// Store Properties features, limits and properties of the physical device for later use
// Device properties also contain limits and sparse properties
vkGetPhysicalDeviceProperties(physicalDevice, &properties);
// Features should be checked by the examples before using them
vkGetPhysicalDeviceFeatures(physicalDevice, &features);
// Memory properties are used regularly for creating all kinds of buffers
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memoryProperties);
// Queue family properties, used for setting up requested queues upon device creation
uint32_t queueFamilyCount;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
assert(queueFamilyCount > 0);
queueFamilyProperties.resize(queueFamilyCount);
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());

// Get list of supported extensions
uint32_t extCount = 0;
vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, nullptr);
if (extCount > 0)
{
std::vector<VkExtensionProperties> extensions(extCount);
if (vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extCount, &extensions.front()) == VK_SUCCESS)
{
for (auto ext : extensions)
{
supportedExtensions.push_back(ext.extensionName);
}
}
}
}

即 VkDevice 的构造函数中会通过 获取device能力个数分配队列簇
vkGetPhysicalDeviceQueueFamilyProperties 其中 Flag 代表了对应 队列簇 的能力类型枚举

# 逻辑设备

构建完物理设备后就可以构建逻辑设备了,逻辑设备是直接与 Vulkan 库做交互的一层 可以在物理设备开启时判断设备是否支持一些功能
- 几何着色器
- 细分着色器 等等

# Vulkan 绘制

GPU 先绘制到 SwapChain 上,然后再绘制到显示窗口的 image 上 实际上 Vulkan 可以对绘制管线进行开关

# 顶点等数据的缓存设置

一般就是 BindBuffer

# 四种 Draw

# 光栅化阶段

视图与窗口的大小可以不一致

# 光栅化整体流程