操作系统:Windows8.1

显卡:Nivida GTX965M

开发工具:Visual Studio 2017


Introduction

现在我们已经成功的在屏幕上绘制出三角形,但是在某些情况下,它会出现异常情况。窗体surface会发生改变,使得交换链不在与其兼容。可能导致这种情况发生的原因之一是窗体的大小变化。我们必须在这个时机重新创建交换链。

Recreating the swap chain

添加新的函数recreateSwapChain并调用createSwapChain及依赖于交换链或者窗体大小的对象相关的所有创建函数。

void recreateSwapChain() {
vkDeviceWaitIdle(device); createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createFramebuffers();
createCommandBuffers();
}

我们首先调用vkDeviceIdle,就像前一个章节提到的,我们不能触碰正在使用中的资源。很明显,要做的第一件事情就是重新创建交换链本身。图像视图也需要重新创建,因为它们直接建立在交换链图像基础上。渲染通道需要重新创建,因为它依赖交换链图像的格式。在窗体调整大小的操作期间,交换链图像格式很少发生变化,但仍应该被处理。在创建图形管线期间指定Viewport和scissor 矩形大小,所以管线需要重新构建。可以使用动态状态改变viewports和scissor rectangles,避免重新创建。最后帧缓冲区和命令缓冲区也需要重新创建,因为它们也依赖交换链的图像。

为了确保重新创建相关的对象之前,老版本的对象被系统正确回收清理,我们需要移动一些cleanup代码到不同的函数中,这样可以在recreateSwapChain函数调用。该函数定义为cleanupSwapChain:

void cleanupSwapChain() {

}

void recreateSwapChain() {
vkDeviceWaitIdle(device); cleanupSwapChain(); createSwapChain();
createImageViews();
createRenderPass();
createGraphicsPipeline();
createFramebuffers();
createCommandBuffers();
}

我们从cleanup中将需要被重新创建的对象所对应的清理代码移动到cleanupSwapChain中:

void cleanupSwapChain() {
for (size_t i = ; i < swapChainFramebuffers.size(); i++) {
vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr);
} vkFreeCommandBuffers(device, commandPool, static_cast<uint32_t>(commandBuffers.size()), commandBuffers.data()); vkDestroyPipeline(device, graphicsPipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyRenderPass(device, renderPass, nullptr); for (size_t i = ; i < swapChainImageViews.size(); i++) {
vkDestroyImageView(device, swapChainImageViews[i], nullptr);
} vkDestroySwapchainKHR(device, swapChain, nullptr);
} void cleanup() {
cleanupSwapChain(); vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); vkDestroyCommandPool(device, commandPool, nullptr); vkDestroyDevice(device, nullptr);
DestroyDebugReportCallbackEXT(instance, callback, nullptr);
vkDestroySurfaceKHR(instance, surface, nullptr);
vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate();
}

我们重头创建命令对象池command pool,但是比较浪费看起来。相反的,我们选择借助vkFreeCommandBuffers函数清理已经存在的命令缓冲区。这种方式可以重用对象池中已经分配的命令缓冲区。

以上部分就是重新创建交换链的工作!然而这样做的缺点就是在重新创建交换链完毕之前,会造成渲染停止。创建新交换链的同时允许在旧的交换链的图像上继续绘制命令。需要将之前的交换链传递到VkSwapchainCreateInfoKHR结构体中的oldSwapChain字段,并在使用之后立即销毁。

Window resizing


现在我们需要搞清楚哪些情况下重新创建交换链是必要的,并调用recreateSwapChain函数。一个通常的条件是窗体的大小变化。让我们调整窗体的大小,并观察捕捉到的事件。修改initWindow函数不再包含GLFW_RESIZABLE行,或者将其参数从GLFW_FALSE修改为GLFW_TRUE

void initWindow() {
glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr); glfwSetWindowUserPointer(window, this);
glfwSetWindowSizeCallback(window, HelloTriangleApplication::onWindowResized);
} ... static void onWindowResized(GLFWwindow* window, int width, int height) {
if (width == || height == ) return; HelloTriangleApplication* app = reinterpret_cast<HelloTriangleApplication*>(glfwGetWindowUserPointer(window));
app->recreateSwapChain();
}

glfwSetWindowSizeCallback函数会在窗体发生大小变化的时候被事件回调。遗憾的是,它只能接受一个指针作为参数,所以我们不能直接使用成员函数。但幸运的是,GLFW允许我们使用glfwSetWindowUserPointer将任意指针存储在窗体对象中,因此可以指定静态类成员调用glfwGetWindowUserPointer返回原始的实例对象。然后我们可以继续调用recreateSwapChain,这种情况通常发生在,窗体最小化并且导致交换链创建失败时.

chooseSwapExtent函数应该增加更新逻辑,使用窗体最新的widthheight代替最初的WIDTHHEIGHT:

int width, height;
glfwGetWindowSize(window, &width, &height); VkExtent2D actualExtent = {width, height};

Suboptimal or out-of-date swap chain


有些时候Vulkan可能告诉我们当前的交换链在presentation时不再兼容。vkAcquireNextImageKHRvkQueuePresentKHR函数可以返回具体的值明确。

  • VK_ERROR_OUT_DATE_KHR: 交换链与surface不再兼容,不可进行渲染
  • VK_SUBOPTIMAL_KHR: 交换链仍然可以向surface提交图像,但是surface的属性不再匹配准确。比如平台可能重新调整图像的尺寸适应窗体大小。
VkResult result = vkAcquireNextImageKHR(device, swapChain, std::numeric_limits<uint64_t>::max(), imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);

if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
throw std::runtime_error("failed to acquire swap chain image!");
}

如果交换链获取图像timeout,表明不再可用。所以我们需要立即重新创建交换链,并在下一次drawFrame调用中尝试获取。

你也可以选择在交换链不是最佳状态的时候,选择重新创建,比如刚才说的大小不匹配问题。在这里因为我们已经获得了一个图像,所以继续进行。VK_SUCCESSVK_SUBOPTIMAL_KHR都被认为是“成功”返回码。

result = vkQueuePresentKHR(presentQueue, &presentInfo);

if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
recreateSwapChain();
} else if (result != VK_SUCCESS) {
throw std::runtime_error("failed to present swap chain image!");
} vkQueueWaitIdle(presentQueue);

vkQueuePresentKHR函数返回同样的值。在我们的案例中我们,如果是非最佳状态,也选择重新创建交换链。因为我们需要最好的效果。尝试调整窗体的大小,帧缓冲区的大小变化与窗体匹配。

Congratulations,我们完结了第一个运行比较完整的Vulkan程序,在下面的章节中我们尝试摆脱之前的硬编码,使用顶点缓冲区代替vertex shader中写死顶点数据。

项目代码 GitHub地址。

Vulkan Tutorial 18 重构交换链的更多相关文章

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

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

  2. Vulkan Tutorial 08 交换链

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 在这一章节,我们了解一下将渲染图像提交到屏幕的基本机制.这种机制成为交换链,并且需要 ...

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

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

  4. [译]Vulkan教程(18)命令buffers

    [译]Vulkan教程(18)命令buffers Command buffers 命令buffer Commands in Vulkan, like drawing operations and me ...

  5. Vulkan Tutorial 05 逻辑设备与队列

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 在选择要使用的物理设备之后,我们需要设置一个逻辑设备用于交 ...

  6. Vulkan Tutorial 07 Window surface

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 到目前为止,我们了解到Vulkan是一个与平台特性无关联的API集合.它不能直接与窗 ...

  7. Vulkan Tutorial 12 Fixed functions

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 早起的图形API在图形渲染管线的许多阶段提供了默认的状态.在Vulkan中,从vie ...

  8. Vulkan Tutorial 13 Render passes

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Setup 在我们完成管线的创建工作,我们接下来需要告诉Vulkan渲染时候使用的f ...

  9. Vulkan Tutorial 14 Integration pipeline

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 我们现在整合前几章节的结构体和对象创建图形管线!以下是我们现在用到的对象类型,作为一 ...

随机推荐

  1. JQuery获取元素类名

    一般而言,JQuery可以利用attr来获取元素的属性值, 1.$("元素").attr("属性");                //获取指定属性的值 2. ...

  2. PHP填补数字前后的0

    PHP数字填补0 经常会遇到这样的问题: 自然数字是0,1,2,3...而我们需要的却是满足多少多少位数的数字,如:001,002. 在ID,编号,学号中我们会经常用到补全前面或者后面的空位(一般为前 ...

  3. 【Spark2.0源码学习】-5.Worker启动

         Worker作为Endpoint的具体实例,下面我们介绍一下Worker启动以及OnStart指令后的额外工作   一.脚本概览      下面是一个举例: /opt/jdk1..0_79/ ...

  4. ASP.NET Core实现强类型Configuration读取配置数据

    前言 实现读取JSON文件几种方式,在项目中采取老办法简单粗暴,结果老大过来一看,恩,这样不太可取,行吧那我就用.NET Core中最新的方式诺,切记,适合的才是最好的,切勿懒. .NET Core读 ...

  5. jade模板引擎简明用法

    ①.特性 首个单词为标签,有一些不能识别的标签可作为code,如each for case if  else if unless   zen coding风格添加标签,如 .nb#hello 生成 & ...

  6. IOS开发常见错误

    . 问题表现:什么情况?方法居然无法拉线? 问题简述:ios的空间拉线到一个.h .m文件中 居然多次拖动无效.. 问题解决:ios的空间响应单单在代码中创建一个方法是没用的,这个时候通常跟空间是没有 ...

  7. cd命令使用详解

    cd命令是目录切换命令,是shell内置命令. 语法: cd [-L|-P] [dir] 选项: -p 如果要切换到的目标目录是一个符号连接,直接切换到符号连接指向的目标目录 -L 如果要切换的目标目 ...

  8. SpringAOP原理

    原理 AOP(Aspect Oriented Programming),也就是面向方面编程的技术.AOP基于IoC基础,是对OOP的有益补充.AOP将应用系统分为两部分,核心业务逻辑(Core bus ...

  9. ASP实现计算机爱好者网站,可以直接浏览

    利用ASP制作的计算机爱好者协会网站,内容比较全面,具有母版和子页的功能,利用ACCESS数据库进行资源存储.适合新手学习和网页制作比赛参考 下载地址:http://download.csdn.net ...

  10. (入门篇 NettyNIO开发指南)第四章-TIP黏包/拆包问题解决之道

    熟悉TCP编程的读者可能都知道,无论是服务端还是客户端,当我们读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制.木章开始我们先简单介绍TCP粘包/拆包的基础知识,然后模拟一个没有考虑TCP ...