[译]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
到回调函数之后。
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 initVulkan
right 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 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队列:
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++代码
- Previous上一章
- Next下一章
[译]Vulkan教程(09)窗口表面的更多相关文章
- [译]Vulkan教程(10)交换链
[译]Vulkan教程(10)交换链 Vulkan does not have the concept of a "default framebuffer", hence it r ...
- [译]Vulkan教程(33)多重采样
[译]Vulkan教程(33)多重采样 Multisampling 多重采样 Introduction 入门 Our program can now load multiple levels of d ...
- [译]Vulkan教程(30)深度缓存
[译]Vulkan教程(30)深度缓存 Depth buffering 深度缓存 Introduction 入门 The geometry we've worked with so far is pr ...
- [译]Vulkan教程(26)描述符池和set
[译]Vulkan教程(26)描述符池和set Descriptor pool and sets 描述符池和set Introduction 入门 The descriptor layout from ...
- [译]Vulkan教程(25)描述符布局和buffer
[译]Vulkan教程(25)描述符布局和buffer Descriptor layout and buffer 描述符布局和buffer Introduction 入门 We're now able ...
- [译]Vulkan教程(20)重建交换链
[译]Vulkan教程(20)重建交换链 Swap chain recreation 重建交换链 Introduction 入门 The application we have now success ...
- [译]Vulkan教程(19)渲染和呈现
[译]Vulkan教程(19)渲染和呈现 Rendering and presentation 渲染和呈现 Setup 设置 This is the chapter where everything ...
- [译]Vulkan教程(14)图形管道基础之固定功能
[译]Vulkan教程(14)图形管道基础之固定功能 Fixed functions 固定功能 The older graphics APIs provided default state for m ...
- [译]Vulkan教程(12)图形管道基础之入门
[译]Vulkan教程(12)图形管道基础之入门 Introduction 入门 Over the course of the next few chapters we'll be setting u ...
随机推荐
- JS-常见数据结构
常见数据结构 这一章节我们将来学习数据结构的内容.经常会有人提问说:学习数据结构或者算法对于前端工程师有用么? 总的来说,这些基础学科在短期内收效确实甚微,但是我们首先不要将自己局限在前端工程师这点上 ...
- Java中真的只有值传递么?
Java中真的只有值传递么? (本文非引战或diss,只是说出自己的理解,欢迎摆正心态观看或探讨) 回顾值传递和引用传递 关于Java是值传递还是引用传递,网上有不一样的说法. 1.基本类型或基本类型 ...
- 牛客练习赛31A 地、颜色、魔法(搜索+二维数组一维表示)
红色来源于山脉,象征着狂躁.愤怒.混乱,血雨腥风,电光火石. 蓝色来源于海岛,象征着控制.幻觉.诡计,运筹帷幄,谋定后动. 绿色来源于树林,象征着生命.蛮力.成长,横冲直撞,生生不息. 黑色来源于沼泽 ...
- LinuxMint配置GitHub(图文教程)
1.生成秘钥(直接回车,秘钥存放路径看命令行信息) 2.打开秘钥,需要注意的是.ssh可能是隐藏的,这时需要Ctrl+H显示隐藏文件夹 3.复制秘钥,添加到GitHub(Settings), 4.添加 ...
- 《Java知识应用》Java加密方式(Base64)详解
1. 说明 Base64加密方式:比较简单,加密快,对普通大众可以起到加密的作用.在程序员眼中和透明一样. Base64应用场景:图片转码(应用于邮件,img标签,http加密) 2. 案例 impo ...
- Linux系统入门简介<1>
linux系统入门简介 我们为什么要学习Linux? 在介绍Linux的历史前,我想先针对大家如何对Linux的发音说一下.我发现我身边的朋友对Linux的发音大致有这么几种: "里那克斯& ...
- CURL命令学习三
-I 只获取请求头 -k --insecure 每次SSL连接curl都需要验证是否安全.-k参数表示如果不安全也可以继续操作. -4 --ipv4 告诉curl只使用ipv4地址 -6 --ipv6 ...
- js反爬学习(一)谷歌镜像
1. url:https://ac.scmor.com/ 2. target:如下链接 3. 过程分析: 3.1 打开chrome调试,进行元素分析.随便定位一个“现在访问” 3.2 链接不是直接挂在 ...
- 解决Android调用相机拍照,要报“打开相机失败”查看debug日志显示“setParameters failed”的问题
使用CameraLibrary项目,在部分手机或平板上不能正常使用,要报“打开相机失败”查看debug日志显示“setParameters failed”. 找到CameraView.java中的se ...
- MySQL数据篇 (一)存储过程实现简单的数据修改及事务的使用
1.需求,手动给会员新增京币,并且添加分配日志,返回修改是否成功 CREATE DEFINER=`jszapi`@`%` PROCEDURE `p_allot_user_coin`(IN `_memb ...