内容简介:[译]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上它需要 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 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++代码
- Previous 上一章
- Next 下一章
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 次表面散射
- matlab练习程序(点云表面法向量)
- 图解:链表的快慢指针,解决 80% 的链表面试题
- 深度长文:表面繁荣之下,人工智能的发展已陷入困境
- 想晋级高级工程师只知道表面是不够的!Git内部原理介绍
- 这届程序员:表面正经写代码,暗地里是个灵魂画手
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
零基础学Minecraft编程
Martin O''Hanlon、David Whale / 中文Minecraft Wiki翻译团队 / 人民邮电出版社 / 2015-9-7 / 79
在你体验Minecraft冒险的同时,学习宝贵的编程技能! 如果你很喜欢玩Minecraft,却被游戏中的建造耗费大量时间而困扰,并且你想要对游戏添加一些改动,那么本书就是为你而设计的。在游戏中,你可以学习许多Python编程技能,在PC、Mac或树莓派上与游戏进行互动。这些冒险不仅局限在虚拟世界——你也将会学习如何将Minecraft与电子元件连接起来,这样你的Minecraft世界就能够......一起来看看 《零基础学Minecraft编程》 这本书的介绍吧!