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

栏目: 后端 · 发布时间: 5年前

内容简介:[译]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 S

[译]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上它需要 HWNDHMODULE 。因此有一个平台相关的扩展,在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个重要的参数: hwndhinstance 。它们是窗口和进程的句柄。

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  initVulkan right 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  VkSurfaceKHR variable. 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窗口指针,自定义内存申请函数和 VkSurfaceKHR variable变量的指针。它简单地传递来自相关平台调用产生的结果 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++代码


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

零基础学Minecraft编程

零基础学Minecraft编程

Martin O''Hanlon、David Whale / 中文Minecraft Wiki翻译团队 / 人民邮电出版社 / 2015-9-7 / 79

在你体验Minecraft冒险的同时,学习宝贵的编程技能! 如果你很喜欢玩Minecraft,却被游戏中的建造耗费大量时间而困扰,并且你想要对游戏添加一些改动,那么本书就是为你而设计的。在游戏中,你可以学习许多Python编程技能,在PC、Mac或树莓派上与游戏进行互动。这些冒险不仅局限在虚拟世界——你也将会学习如何将Minecraft与电子元件连接起来,这样你的Minecraft世界就能够......一起来看看 《零基础学Minecraft编程》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

随机密码生成器
随机密码生成器

多种字符组合密码