[译]Vulkan教程(09)窗口表面

[译]Vulkan教程(09)窗口表面

 

Since Vulkan is a platform agnostic API, it can not interface directly with the window system on its own. To establish the connection between Vulkan and the window system to present results to the screen, we need to use the WSI (Window System Integration) extensions. In this chapter we'll discuss the first one, which is VK_KHR_surface. It exposes a VkSurfaceKHR object that represents an abstract type of surface to present rendered images to. The surface in our program will be backed by the window that we've already opened with GLFW.

由于Vulkan是平台无关论的API,它不能直接与窗口系统交互。为建立Vulkan与窗口的关联,以在屏幕上呈现渲染结果,我们需要用WSI(窗口系统集成)扩展。本章我们将讨论第一个,即VK_KHR_surface。它暴露一个对象,其代表抽象的surface(表面)类型,surface可以将image呈现其上。我们程序中的surface将由GLFW打开的窗口制作。

The VK_KHR_surface extension is an instance level extension and we've actually already enabled it, because it's included in the list returned by glfwGetRequiredInstanceExtensions. The list also includes some other WSI extensions that we'll use in the next couple of chapters.

扩展VK_KHR_surface 是instance层的扩展,我们实际上已经启用了它,因为它被包含在了由glfwGetRequiredInstanceExtensions返回的列表中。这个列表还包含其他的WSI扩展,我们在后续章节中会用到。

The window surface needs to be created right after the instance creation, because it can actually influence the physical device selection. The reason we postponed this is because window surfaces are part of the larger topic of render targets and presentation for which the explanation would have cluttered the basic setup. It should also be noted that window surfaces are an entirely optional component in Vulkan, if you just need off-screen rendering. Vulkan allows you to do that without hacks like creating an invisible window (necessary for OpenGL).

窗口surface需要在instance被创建后立即被创建,因为它实际上会影响物理设备的选择。我们推后了对它的介绍,是因为创建surface是渲染目标和presentation(呈现)这个更大的话题的一部分,它会让基础的设置过程更杂乱。要注意的是,窗口surface是Vulkan中完全可选的组件,如果你只需要离屏渲染的话。Vulkan允许你在不创建可见窗口的前提下就完成离屏渲染(OpenGL中则必须创建窗口)。

Window surface creation 创建窗口surface

Start by adding a surface class member right below the debug callback.

首先,添加成员surface 到回调函数之后。

1 VkSurfaceKHR surface;

 

Although the VkSurfaceKHR object and its usage is platform agnostic, its creation isn't because it depends on window system details. For example, it needs the HWND and HMODULE handles on Windows. Therefore there is a platform-specific addition to the extension, which on Windows is called VK_KHR_win32_surface and is also automatically included in the list from glfwGetRequiredInstanceExtensions.

尽管VkSurfaceKHR 对象及其用法是平台无关的,它的创建过程却不是平台无关的,因为它依赖窗口系统的细节。例如,在Windows上它需要HWND 和HMODULE 。因此有一个平台相关的扩展,在Windows上是VK_KHR_win32_surface ,它也被自动包含在由glfwGetRequiredInstanceExtensions得到的列表中了。

I will demonstrate how this platform specific extension can be used to create a surface on Windows, but we won't actually use it in this tutorial. It doesn't make any sense to use a library like GLFW and then proceed to use platform-specific code anyway. GLFW actually has glfwCreateWindowSurface that handles the platform differences for us. Still, it's good to see what it does behind the scenes before we start relying on it.

我将演示这个平台相关的扩展如何被用于在Windows上创建surface,但是我们实际上不会在本教程用它。用GLFW这样的库,然后再用平台相关的代码,这没有道理。GLFW实际上有glfwCreateWindowSurface ,它处理了平台相关的差异。但是,在我们用它之前,看看场面背后的东西,还是有好处的。

Because a window surface is a Vulkan object, it comes with a VkWin32SurfaceCreateInfoKHR struct that needs to be filled in. It has two important parameters: hwnd and hinstance. These are the handles to the window and the process.

因为窗口surface是Vulkan对象,它需要你填入一个VkWin32SurfaceCreateInfoKHR 结构体。它有2个重要的参数:hwnd 和hinstance。它们是窗口和进程的句柄。

1 VkWin32SurfaceCreateInfoKHR createInfo = {};
2 createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
3 createInfo.hwnd = glfwGetWin32Window(window);
4 createInfo.hinstance = GetModuleHandle(nullptr);

 

The glfwGetWin32Window function is used to get the raw HWND from the GLFW window object. The GetModuleHandle call returns the HINSTANCE handle of the current process.

函数glfwGetWin32Window 用于从GLFW窗口对象获取原始HWND 。调用GetModuleHandle 函数会返回当前进程的句柄HINSTANCE 。

After that the surface can be created with vkCreateWin32SurfaceKHR, which includes a parameter for the instance, surface creation details, custom allocators and the variable for the surface handle to be stored in. Technically this is a WSI extension function, but it is so commonly used that the standard Vulkan loader includes it, so unlike other extensions you don't need to explicitly load it.

然后,就可以用vkCreateWin32SurfaceKHR创建surface了,它的参数为instance指针,surface细节,自定义的内存申请函数和用于保存surface句柄的变量的指针。严格来说,它是个WSI扩展函数,但是它太常用了,以至于标准Vulkan加载器纳入了它,所以你不需要(像其它扩展一样)显式地加载它。

1 if (vkCreateWin32SurfaceKHR(instance, &createInfo, nullptr, &surface) != VK_SUCCESS) {
2     throw std::runtime_error("failed to create window surface!");
3 }

 

The process is similar for other platforms like Linux, where vkCreateXcbSurfaceKHR takes an XCB connection and window as creation details with X11.

这一过程对其它平台例如Linux是类似的,其中X11上的vkCreateXcbSurfaceKHR 函数接收一个XCB链接和一个窗口作为创建细节。

The glfwCreateWindowSurface function performs exactly this operation with a different implementation for each platform. We'll now integrate it into our program. Add a function createSurface to be called from initVulkanright after instance creation and setupDebugCallback.

函数glfwCreateWindowSurface 对各个平台实施完全相同的操作。我们现在将其集成到我们的程序。添加createSurface 函数,在initVulkan中调用它,置于instance的创建和setupDebugCallback函数之后。

 1 void initVulkan() {
 2     createInstance();
 3     setupDebugCallback();
 4     createSurface();
 5     pickPhysicalDevice();
 6     createLogicalDevice();
 7 }
 8  
 9 void createSurface() {
10  
11 }

 

The GLFW call takes simple parameters instead of a struct which makes the implementation of the function very straightforward:

GLFW调用接收简单的参数,而不是结构体,这让它的实现十分直观:

1 void createSurface() {
2     if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
3         throw std::runtime_error("failed to create window surface!");
4     }
5 }

 

The parameters are the VkInstance, GLFW window pointer, custom allocators and pointer to VkSurfaceKHRvariable. It simply passes through the VkResult from the relevant platform call. GLFW doesn't offer a special function for destroying a surface, but that can easily be done through the original API:

参数是VkInstance,GLFW窗口指针,自定义内存申请函数和VkSurfaceKHRvariable变量的指针。它简单地传递来自相关平台调用产生的结果VkResult 。GLFW不提供销毁surface的函数,但是这可以通关原始API很容易地实现:

void cleanup() {
        ...
        vkDestroySurfaceKHR(instance, surface, nullptr);
        vkDestroyInstance(instance, nullptr);
        ...
    }

 

Make sure that the surface is destroyed before the instance.

要确保surface在instance之前被销毁。

Querying for presentation support 查询对presentation的支持

Although the Vulkan implementation may support window system integration, that does not mean that every device in the system supports it. Therefore we need to extend isDeviceSuitable to ensure that a device can present images to the surface we created. Since the presentation is a queue-specific feature, the problem is actually about finding a queue family that supports presenting to the surface we created.

尽管Vulkan实现可能支持窗口系统集成,那不等于系统中的每个设备都支持它。因此我们需要扩展isDeviceSuitable ,以确保设备能将image呈现到我们创建的surface上。由于presentation是个队列相关的特性,这个问题实际上是要找到一个队列家族,其支持呈现到我们创建的surface。

It's actually possible that the queue families supporting drawing commands and the ones supporting presentation do not overlap. Therefore we have to take into account that there could be a distinct presentation queue by modifying the QueueFamilyIndices structure:

有可能,支持绘制命令和支持presentation的队列家族不重合。因此我们不得不考虑,修改QueueFamilyIndices 结构体,可能有另一个presentation队列:

1 struct QueueFamilyIndices {
2     std::optional<uint32_t> graphicsFamily;
3     std::optional<uint32_t> presentFamily;
4  
5     bool isComplete() {
6         return graphicsFamily.has_value() && presentFamily.has_value();
7     }
8 };

 

Next, we'll modify the findQueueFamilies function to look for a queue family that has the capability of presenting to our window surface. The function to check for that is vkGetPhysicalDeviceSurfaceSupportKHR, which takes the physical device, queue family index and surface as parameters. Add a call to it in the same loop as the VK_QUEUE_GRAPHICS_BIT:

下一步,我们将修改findQueueFamilies 函数,其查询能够呈现到窗口surface的队列家族。执行检查工作的函数是vkGetPhysicalDeviceSurfaceSupportKHR,其接收物理设备,队列家族索引和surface为参数。在的VK_QUEUE_GRAPHICS_BIT循环中添加对它的调用:

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

 

Then simply check the value of the boolean and store the presentation family queue index:

检查boolean值,保存presentation家族队列索引:

if (queueFamily.queueCount > 0 && presentSupport) {
    indices.presentFamily = i;
}

 

Note that it's very likely that these end up being the same queue family after all, but throughout the program we will treat them as if they were separate queues for a uniform approach. Nevertheless, you could add logic to explicitly prefer a physical device that supports drawing and presentation in the same queue for improved performance.

注意,这些最终很可能是同一个家族队列,但是通过这个程序我们将视它们为单独的队列,以保持程序的形式一致。然而,你可以添加一段逻辑来显式地选择一个物理设备,其在同一队列中同时支持绘制和presentation,以获得更好的性能。

Creating the presentation queue 创建presentation队列

The one thing that remains is modifying the logical device creation procedure to create the presentation queue and retrieve the VkQueue handle. Add a member variable for the handle:

剩下的一件事,就是修改逻辑设备创建的过程,以创建presentation队列,并检索VkQueue 句柄。为此句柄添加成员变量:

VkQueue presentQueue;

 

Next, we need to have multiple VkDeviceQueueCreateInfo structs to create a queue from both families. An elegant way to do that is to create a set of all unique queue families that are necessary for the required queues:

接下来,我们需要用多个VkDeviceQueueCreateInfo 结构体,来从2个家族创建队列。一个优雅的方式是,为了需要的队列,创建一系列队列家族:

 1 #include <set>
 2  
 3 ...
 4  
 5 QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
 6  
 7 std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
 8 std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};
 9  
10 float queuePriority = 1.0f;
11 for (uint32_t queueFamily : uniqueQueueFamilies) {
12     VkDeviceQueueCreateInfo queueCreateInfo = {};
13     queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
14     queueCreateInfo.queueFamilyIndex = queueFamily;
15     queueCreateInfo.queueCount = 1;
16     queueCreateInfo.pQueuePriorities = &queuePriority;
17     queueCreateInfos.push_back(queueCreateInfo);
18 }

 

And modify VkDeviceCreateInfo to point to the vector:

修改VkDeviceCreateInfo ,指向此向量:

createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
createInfo.pQueueCreateInfos = queueCreateInfos.data();

 

If the queue families are the same, then we only need to pass its index once. Finally, add a call to retrieve the queue handle:

如果队列家族相同, 那么我们只需传入索引一次。最后,检索队列句柄:

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

 

In case the queue families are the same, the two handles will most likely have the same value now. In the next chapter we're going to look at swap chains and how they give us the ability to present images to the surface.

如果队列家族相同,那么2个句柄很可能会有相同的值。下一章,我们将讨论交换链,以及它如何帮我们将image呈现到surface上。

C++ code C++代码

 

 

posted @ 2019-06-27 15:48  BIT祝威  阅读(1327)  评论(0编辑  收藏  举报
canvas start.

canvas end.