[译]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 到回调函数之后。

 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。它们是窗口和进程的句柄。

 VkWin32SurfaceCreateInfoKHR createInfo = {};
createInfo.sType = VK_STRUCTURE_TYPE_WIN32_SURFACE_CREATE_INFO_KHR;
createInfo.hwnd = glfwGetWin32Window(window);
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加载器纳入了它,所以你不需要(像其它扩展一样)显式地加载它。

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

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函数之后。

 void initVulkan() {
createInstance();
setupDebugCallback();
createSurface();
pickPhysicalDevice();
createLogicalDevice();
} void createSurface() { }

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

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

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

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队列:

 struct QueueFamilyIndices {
std::optional<uint32_t> graphicsFamily;
std::optional<uint32_t> presentFamily; bool isComplete() {
return graphicsFamily.has_value() && presentFamily.has_value();
}
};

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 >  && 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个家族创建队列。一个优雅的方式是,为了需要的队列,创建一系列队列家族:

 #include <set>

 ...

 QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

 std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f;
for (uint32_t queueFamily : uniqueQueueFamilies) {
VkDeviceQueueCreateInfo queueCreateInfo = {};
queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo.queueFamilyIndex = queueFamily;
queueCreateInfo.queueCount = ;
queueCreateInfo.pQueuePriorities = &queuePriority;
queueCreateInfos.push_back(queueCreateInfo);
}

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(), , &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++代码

[译]Vulkan教程(09)窗口表面的更多相关文章

  1. [译]Vulkan教程(10)交换链

    [译]Vulkan教程(10)交换链 Vulkan does not have the concept of a "default framebuffer", hence it r ...

  2. [译]Vulkan教程(33)多重采样

    [译]Vulkan教程(33)多重采样 Multisampling 多重采样 Introduction 入门 Our program can now load multiple levels of d ...

  3. [译]Vulkan教程(30)深度缓存

    [译]Vulkan教程(30)深度缓存 Depth buffering 深度缓存 Introduction 入门 The geometry we've worked with so far is pr ...

  4. [译]Vulkan教程(26)描述符池和set

    [译]Vulkan教程(26)描述符池和set Descriptor pool and sets 描述符池和set Introduction 入门 The descriptor layout from ...

  5. [译]Vulkan教程(25)描述符布局和buffer

    [译]Vulkan教程(25)描述符布局和buffer Descriptor layout and buffer 描述符布局和buffer Introduction 入门 We're now able ...

  6. [译]Vulkan教程(20)重建交换链

    [译]Vulkan教程(20)重建交换链 Swap chain recreation 重建交换链 Introduction 入门 The application we have now success ...

  7. [译]Vulkan教程(19)渲染和呈现

    [译]Vulkan教程(19)渲染和呈现 Rendering and presentation 渲染和呈现 Setup 设置 This is the chapter where everything ...

  8. [译]Vulkan教程(14)图形管道基础之固定功能

    [译]Vulkan教程(14)图形管道基础之固定功能 Fixed functions 固定功能 The older graphics APIs provided default state for m ...

  9. [译]Vulkan教程(12)图形管道基础之入门

    [译]Vulkan教程(12)图形管道基础之入门 Introduction 入门 Over the course of the next few chapters we'll be setting u ...

随机推荐

  1. Python3 文件处理

    目录 文件操作的流程 文件的三种打开模式 读取: rt read_text 针对文本内容只读 清空写入: wt write_text 针对文本内容只写 追加写入: at append_text 针对文 ...

  2. 大数据学习笔记——Hadoop编程实战之Mapreduce

    Hadoop编程实战——Mapreduce基本功能实现 此篇博客承接上一篇总结的HDFS编程实战,将会详细地对mapreduce的各种数据分析功能进行一个整理,由于实际工作中并不会过多地涉及原理,因此 ...

  3. 《Java基础知识》Java接口和抽象类的区别

    抽象类 抽象类必须用 abstract 修饰,子类必须实现抽象类中的抽象方法,如果有未实现的,那么子类也必须用 abstract 修饰.抽象类默认的权限修饰符为 public,可以定义为 public ...

  4. IJKPlayerView设置Header播放视频的方法

    播放b站视频连接的实测图 https://github.com/Rukey7/IjkPlayerView 使用库的连接 在用这个库播放b站视频连接的时候总是播放不了 检查了一下是因为b站视频连接需要验 ...

  5. linux之寻找男人的帮助,man和info,

    1.在linux下寻求帮助是一个很好的习惯,幸运的是系统提供了帮助的命令man和info,由于linux指令很多,记忆起来简直麻烦,比如以a开头的指令有100条,linux命令算起来得几千条,记忆却是 ...

  6. WFS服务查询方法

    基于Geoserver发布的wfs服务,实现空间和属性信息的查询.wfs包含getFeature操作,用来检索要素信息,支持返回gml格式的地理要素表达. WFS的getFeature操作需要提供的参 ...

  7. kafka2.3.1+zookeeper3.5.6+kafka-manager2.0.0.2集群部署(centos7.7)

    一.准备三台服务器,配置好主机名和ip地址 二.服务器初始化:包括安装常用命令工具,修改系统时区,校对系统时间,关闭selinux,关闭firewalld,修改主机名,修改系统文件描述符,优化内核参数 ...

  8. Things 3 for Mac是什么?如何使用?

    为大家介绍一款实用的效率管理软件“Things 3 for Mac”,它通过使用标签和智能过滤条,东西结合了强大的功能和简单性,Leopard风格的来源列表可以快速轻松地进行对焦.与一个美丽的用户界面 ...

  9. IT兄弟连 HTML5教程 CSS3属性特效 渐变1

    渐变背景一直以来在Web页面中都是一种常见的视觉元素.但一直以来,Web设计师都是通过图形软件设计这些渐变效果,然后以图片形式或者背景图片的形式运用到页面中.Web页面上实现的效果,仅从页面的视觉效果 ...

  10. XGBoost缺失值引发的问题及其深度分析

    1. 背景 XGBoost模型作为机器学习中的一大“杀器”,被广泛应用于数据科学竞赛和工业领域,XGBoost官方也提供了可运行于各种平台和环境的对应代码,如适用于Spark分布式训练的XGBoost ...