本部分结果可参考 08_Window_Surface

本章涉及到的关键对象和流程如下所示

Vulkan 是一个平台无关的 API,无法直接与操作系统的窗口系统交互。为了实现 Vulkan 与操作系统窗口系统的集成,需要使用 WSI(Window System Integration)相关扩展,其中最核心的是 VK_KHR_surface。Surface 是对操作系统窗口系统表面的抽象,允许 Vulkan 呈现图像到屏幕上。

Surface 表示一个可以作为渲染目标的窗口系统,应用通过与 Surface 的交互来访问系统窗口。

在本章中,将介绍如何通过 GLFW 创建 window surface,并修改之前章节中物理设备选择(物理设备和 Queue Family)和 创建 Logical Device的过程,通过创建的 Surface 来找寻可以正确处理 presentation 支持的队列族。

在图形 API(如 Vulkan、DirectX、OpenGL)中,Presentation 指的是“将渲染结果显示到屏幕上的过程”。

  • 渲染(Rendering):GPU 负责把你的 3D 场景、模型、纹理等数据,经过一系列计算,最终生成一张图像(通常是帧缓冲区中的一帧)。
  • 呈现(Presentation):把这张已经渲染好的图像,从 GPU 的帧缓冲区(或交换链中的图像)提交给操作系统的窗口系统,最终显示在用户屏幕上。

VK_KHR_surface 是 instance 级扩展。需要在创建 instance 时启用。可以通过 glfwGetRequiredInstanceExtensions 获取需要启用的扩展名称列表。

在 Vulkan 中,window surface 是可选的。如果应用仅用于离屏渲染,则无需创建 surface。

#创建 Window surface

首先定义类 SurfaceMgr 用于管理 surface 的创建和销毁,surface 句柄类型为 VkSurfaceKHR,初始值应为 VK_NULL_HANDLE

1
2
3
4
5
6
7
class SurfaceMgr
{
public:
static void createSurface(VkInstance instance, GLFWwindow* window);
static void destroySurface(VkInstance instance);
static VkSurfaceKHR surface;
};

函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
VkSurfaceKHR SurfaceMgr::surface = VK_NULL_HANDLE;

void SurfaceMgr::createSurface(VkInstance instance, GLFWwindow* window)
{
if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS)
{
throw std::runtime_error("failed to create window surface!");
}
}

void SurfaceMgr::destroySurface( VkInstance instance)
{
vkDestroySurfaceKHR(instance, surface, nullptr);
}

HelloTriangleApplicationinitVulkan 函数中调用 SurfaceMgr::createSurface 来创建 Surface,并在 cleanup 函数中调用 SurfaceMgr::destroySurface 来销毁 Surface。修改后的 initVulkancleanup 如下,注意创建 surface 需要在创建 instance 之后,Physical device 选择之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void HelloTriangleApplication::initVulkan()
{
createInstance();
DebugMessengerMgr::setupDebugMessenger(instance);
SurfaceMgr::createSurface(instance, window);
PhysicalDevicesMgr::pickPhysicalDevice(instance);
LogicDevicesMgr::createLogicalDevice();
}

void HelloTriangleApplication::cleanup()
{
LogicDevicesMgr::destroyLogicalDevice();
if (ValidationLayerMgr::enableValidationLayers)
DebugMessengerMgr::destroyDebugUtilsMessengerExt(instance, nullptr);

SurfaceMgr::destroySurface(instance);
vkDestroyInstance(instance, nullptr);
glfwDestroyWindow(window);
glfwTerminate();
}

glfwCreateWindowSurface 函数封装了在各个平台创建 Surface 的过程。在 Windows 平台下该函数即封装了为 Windows 提供的 vkCreateWin32SurfaceKHR

#找寻支持 Presentation 的物理设备

并非所有的硬件设备都支持将 Image 表现到创建的上述 Surface 上,因此在这里还需要检测设备的 Presentation 支持。因为 Presentation 是一个需要放置在 Queue 中的指令,因此我们需要查询是否有支持 Presenting 的 Queue family。如下在 QueueFamilyIndices 结构体中新增 presentFamily

1
2
3
4
5
6
7
8
9
10
11
struct QueueFamilyIndices
{
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily;
bool isComplete();
};

bool QueueFamilyIndices::isComplete()
{
return graphicsFamily.has_value() && presentFamily.has_value();
}

之后需要修改 QueueFamilyMgr::findQueueFamilies 函数,因为 Surface 是拓展,所以无法直接从 VkQueueFamilyPropertiesqueueFlags 判断。这里需要使用函数 vkGetPhysicalDeviceSurfaceSupportKHR 查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
QueueFamilyIndices QueueFamilyMgr::findQueueFamilies(VkPhysicalDevice device)
{
// ...
for (const auto& queueFamily : queueFamilies)
{
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)
{
indices.graphicsFamily = i;
}

VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, SurfaceMgr::surface, &presentSupport);

if (presentSupport)
indices.presentFamily = i;

if (indices.isComplete())
break;

++i;
}
// ...
}

这里虽然用了两个变量分别标识 graphics 和 present 的 Queue Family,但很可能这两个变量是一样的值。

#创建 Presentation Queue

LogicDeviceMgr 中增加类成员对象 presentQueue 表示 present 的 Queue,同时修改 createLogicalDevice 函数,此时该函数中需要同时创建 graphics 和 present 两个 queue,因此原先的 VkDeviceQueueCreateInfo 需要被修改为 std::vector<VkDeviceQueueCreateInfo>,并且需要在 VkDeviceCreateInfo 中设置 queueCreateInfoCount 为需要与 vector 的数量相匹配。

使用数据结构 std::set<uint32_t> 来存储两个 queue 对应的 queue families,即使两个 queue families 相同,则该数据结构中实际上只有一个元素。修改后的 LogicDeviceMgr::createLogicalDevice 如下:

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
void HelloTriangleApplication::createLogicalDevice()
{
// ...
const QueueFamilyIndices indices = QueueFamilyMgr::findQueueFamilies(PhysicalDevicesMgr::physicalDevice);

std::vector<VkDeviceQueueCreateInfo> queueCreateInfos{};
std::set uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
for (uint32_t queueFamily : uniqueQueueFamilies)
{

VkDeviceQueueCreateInfo queueCreateInfo{};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = 1;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}

// ...

VkDeviceCreateInfo createInfo{};
createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
createInfo.pQueueCreateInfos = queueCreateInfos.data();
createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
// ...

vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue);
vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue);
}