Vulkan学习--13.重建交换链

实现窗口大小改变时刷新图像:

示例代码:


    //标记窗口大小是否发生改变:
    bool framebufferResized = false;
    //为静态函数才能将其用作回调函数
    static void framebufferResizeCallback(GLFWwindow* window,int width ,
                                           int height){
        auto app =
        reinterpret_cast<HelloTriangle*>(glfwGetWindowUserPointer(window));
        app->framebufferResized = true;
    }

    ///初始化glfw
    void initWindow(){
        glfwInit();//初始化glfw库
        //显示阻止自动创建opengl上下文
        glfwWindowHint(GLFW_CLIENT_API,GLFW_NO_API);
        //禁止窗口大小改变
        //glfwWindowHint(GLFW_RESIZABLE,GLFW_FALSE);
        /**
        glfwCreateWindow 函数:
        前三个参数指定了要创建的窗口的宽度,高度和标题.
        第四个参数用于指定在哪个显示器上打开窗口,
        最后一个参数与 OpenGL 相关
          */
        //创建窗口
        window = glfwCreateWindow(WIDTH,HEIGHT,"vulakn",
                                  nullptr,nullptr);
        //存储在 GLFW 窗口相关的数据
        glfwSetWindowUserPointer(window , this);
        //窗口大小改变的回调函数
        glfwSetFramebufferSizeCallback(window,framebufferResizeCallback);
    }

    //绘制图像--12
    void drawFrame(){
        /**
        vkWaitForFences 函数可以用来等待一组栅栏 (fence) 中的一个或
        全部栅栏 (fence) 发出信号。上面代码中我们对它使用的 VK_TRUE
        参数用来指定它等待所有在数组中指定的栅栏 (fence)。我们现在只有
        一个栅栏 (fence) 需要等待,所以不使用 VK_TRUE 也是可以的。和
        vkAcquireNextImageKHR 函数一样,vkWaitForFences 函数也有一个超时
        参数。和信号量不同,等待栅栏发出信号后,我们需要调用 vkResetFences
        函数手动将栅栏 (fence) 重置为未发出信号的状态。
          */
        //等待我们当前帧所使用的指令缓冲结束执行
        vkWaitForFences(device,1,&inFlightFences[currentFrame],
                        VK_TRUE,std::numeric_limits<uint64_t>::max());
        vkResetFences(device , 1 , &inFlightFences[currentFrame]);

        uint32_t imageIndex;
        /**
          vkAcquireNextImageKHR参数:
          1.使用的逻辑设备对象
          2.我们要获取图像的交换链,
          3.图像获取的超时时间,我们可以通过使用无符号 64 位整型所能表示的
            最大整数来禁用图像获取超时
          4,5.指定图像可用后通知的同步对象.可以指定一个信号量对象或栅栏对象,
            或是同时指定信号量和栅栏对象进行同步操作。
            在这里,我们指定了一个叫做 imageAvailableSemaphore 的信号量对象
          6.用于输出可用的交换链图像的索引,我们使用这个索引来引用我们的
            swapChainImages数组中的VkImage对象,并使用这一索引来提交对应的指令缓冲
          */
        //从交换链获取一张图像
        //交换链是一个扩展特性,所以与它相关的操作都会有 KHR 这一扩展后缀
        VkResult result =vkAcquireNextImageKHR(device,swapChain,
                              std::numeric_limits<uint64_t>::max(),
                              imageAvailableSemaphores[currentFrame],
                              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!");
        }

        //提交信息给指令队列
        VkSubmitInfo submitInfo = {};
        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};
        //这里需要写入颜色数据到图像,所以我们指定等待图像管线到达可以写入颜色附着的管线阶段
        VkPipelineStageFlags waitStages[] = {
            VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
        /**
        waitSemaphoreCount、pWaitSemaphores 和pWaitDstStageMask 成员变量用于指定
        队列开始执行前需要等待的信号量,以及需要等待的管线阶段
          */
        submitInfo.waitSemaphoreCount = 1;
        submitInfo.pWaitSemaphores = waitSemaphores;
        //waitStages 数组中的条目和 pWaitSemaphores 中相同索引的信号量相对应。
        submitInfo.pWaitDstStageMask = waitStages;
        //指定实际被提交执行的指令缓冲对象
        //我们应该提交和我们刚刚获取的交换链图像相对应的指令缓冲对象
        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers = &commandBuffers[imageIndex];
        VkSemaphore signalSemaphores [ ] = {
            renderFinishedSemaphores[currentFrame]};
        //指定在指令缓冲执行结束后发出信号的信号量对象
        submitInfo.signalSemaphoreCount = 1;
        submitInfo.pSignalSemaphores = signalSemaphores;
        /**
        vkQueueSubmit 函数使用vkQueueSubmit结构体数组作为参数,可以同时大批量提交数.。
        vkQueueSubmit 函数的最后一个参数是一个可选的栅栏对象,
        可以用它同步提交的指令缓冲执行结束后要进行的操作。
        在这里,我们使用信号量进行同步,没有使用它,将其设置为VK_NULL_HANDLE
          */
        /**
         * @brief vkResetFences
         * 在重建交换链时,重置栅栏 (fence) 对象,
         * 以防导致我们使用的栅栏 (fence) 处于我们不能确定得状态
         */
        vkResetFences(device,1,&inFlightFences[currentFrame]);
        //提交指令缓冲给图形指令队列
        if(vkQueueSubmit(graphicsQueue,1,&submitInfo,
                         inFlightFences[currentFrame])!= VK_SUCCESS){
            throw std::runtime_error("failed to submit draw command buffer!");
        }

        /**
         * 将渲染的图像返回给交换链进行呈现操作
         */
        //配置呈现信息
        VkPresentInfoKHR presentInfo = {};
        presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        //指定开始呈现操作需要等待的信号量
        presentInfo.waitSemaphoreCount = 1;
        presentInfo.pWaitSemaphores = signalSemaphores;
        //指定了用于呈现图像的交换链,以及需要呈现的图像在交换链中的索引
        VkSwapchainKHR swapChains [ ] = {swapChain };
        presentInfo.swapchainCount = 1;
        presentInfo.pSwapchains = swapChains;
        presentInfo.pImageIndices = &imageIndex;
        /**
        我们可以通过 pResults 成员变量获取每个交换链的呈现操作是否成功
        的信息。在这里,由于我们只使用了一个交换链,可以直接使用呈现函数
        的返回值来判断呈现操作是否成功
          */
        presentInfo.pResults = nullptr;
        /**
          vkQueuePresentKHR函数返回的信息来判定交换链是否需要重建:
            VK_ERROR_OUT_OF_DATE_KHR:交换链不能继续使用。通常发生在窗口大小改变后
            VK_SUBOPTIMAL_KHR:交换链仍然可以使用,但表面属性已经不能准确匹配
          */
        //请求交换链进行图像呈现操作
        result = vkQueuePresentKHR( presentQueue , &presentInfo ) ;
        if(result == VK_ERROR_OUT_OF_DATE_KHR ||
                result == VK_SUBOPTIMAL_KHR || framebufferResized){
            //交换链不完全匹配时也重建交换链
            framebufferResized = false;
            recreateSwapChain();
        }else if(result != VK_SUCCESS){
            throw std::runtime_error("failed to present swap chain image!");
        }
        /**
        如果开启校验层后运行程序,观察应用程序的内存使用情况,
        可以发现我们的应用程序的内存使用量一直在慢慢增加。这是由于我
        们的 drawFrame 函数以很快地速度提交指令,但却没有在下一次指令
        提交时检查上一次提交的指令是否已经执行结束。也就是说 CPU 提交
        指令快过 GPU 对指令的处理速度,造成 GPU 需要处理的指令大量堆
        积。更糟糕的是这种情况下,我们实际上对多个帧同时使用了相同的
        imageAvailableSemaphore 和 renderFinishedSemaphore 信号量。
        最简单的解决上面这一问题的方法是使用 vkQueueWaitIdle 函数来等
        待上一次提交的指令结束执行,再提交下一帧的指令:
        但这样做,是对 GPU 计算资源的大大浪费。图形管线可能大部分时
        间都处于空闲状态.
          */
        //等待一个特定指令队列结束执行
        //vkQueueWaitIdle ( presentQueue ) ;

        //更新currentFrame
        currentFrame = (currentFrame+1) %MAX_FRAMES_IN_FLIGHT;
    }

    //重建交换链
    void recreateSwapChain(){
        //设置应用程序在窗口最小化后停止渲染,直到窗口重新可见时重建交换链
        int width=0,height = 0;
        while(width == 0 || height == 0){
            glfwGetFramebufferSize(window,&width,&height);
            glfwWaitEvents();
        }
        //等待设备处于空闲状态,避免在对象的使用过程中将其清除重建
        vkDeviceWaitIdle(device);

        cleanupSwapChain();//清除交换链相关

        //重新创建了交换链
        createSwapChain();
        //图形视图是直接依赖于交换链图像的,所以也需要被重建
        createImageViews();
        //渲染流程依赖于交换链图像的格式,窗口大小改变不会引起使用的交换链图像格式改变
        createRenderPass();
        //视口和裁剪矩形在管线创建时被指定,窗口大小改变,这些设置也需要修改
        //我们可以通过使用动态状态来设置视口和裁剪矩形来避免重建管线
        createGraphicsPipeline();
        //帧缓冲和指令缓冲直接依赖于交换链图像
        createFramebuffers();
        createCommandBuffers();
    }
    //清除交换链相关
    void cleanupSwapChain(){
        //销毁帧缓冲对象
        for(auto framebuffer : swapChainFramebuffers){
            vkDestroyFramebuffer(device,framebuffer,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(auto imageView : swapChainImageViews){
            vkDestroyImageView(device,imageView,nullptr);
        }
        //销毁交换链对象,在逻辑设备被清除前调用
        vkDestroySwapchainKHR(device,swapChain,nullptr);
    }

猜你喜欢

转载自blog.csdn.net/yuxing55555/article/details/88998467