Vulkan学习--12.渲染和呈现

子流程依赖:

渲染流程的子流程会自动进行图像布局变换。这一变换过程由子流程的依赖所决定。子流程的依赖包括子流程之间的内存和执行的依赖关系。虽然我们现在只使用了一个子流程,但子流程执行之前和子流程执行之后
的操作也被算作隐含的子流程。在渲染流程开始和结束时会自动进行图像布局变换,但在渲染流程开始时进行的自动变换的时机和我们的需求不符,变换发生在管线开始时,但那时我们可能还没有获取到交换链图像。有两种方式可以解决这个问题。

1.设置 imageAvailableSemaphore 信号量的 waitStages 为 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,确保渲染流程在我们获取交换链图像之前不会开始。

2.设置渲染流程等待VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 管线阶段。

在这里,为了让读者能够了解子流程依赖如何控制图像布局变换,我们使用第二种方式

部分示例Demo:

//可以同时并行处理的帧数--12
const int MAX_FRAMES_IN_FLIGHT = 2;


    //为每一帧创建属于它们自己的信号量
    //信号量发出图像已经被获取,可以开始渲染的信号--12
    std::vector<VkSemaphore> imageAvailableSemaphores ;
    //量发出渲染已经结果,可以开始呈现的信号--12
    std::vector<VkSemaphore> renderFinishedSemaphores ;
    //追踪当前渲染的是哪一帧--12
    size_t currentFrame = 0;
    /**
    需要使用栅栏(fence) 来进行 CPU 和 GPU 之间的同步,
    来防止有超过 MAX_FRAMES_IN_FLIGHT帧的指令同时被提交执行。
    栅栏 (fence) 和信号量 (semaphore) 类似,可以用来发出信号和等待信号
      */
    //每一帧创建一个VkFence 栅栏对象--12
    std::vector<VkFence> inFlightFences ;

    //创建信号量和VkFence--12
    void createSyncObjects(){
        //创建每一帧需要的信号量对象
        imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT) ;
        renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT) ;
        inFlightFences.resize(MAX_FRAMES_IN_FLIGHT) ;

        VkSemaphoreCreateInfo semaphoreInfo = {};
        semaphoreInfo.sType =
                VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

        VkFenceCreateInfo fenceInfo = {};
        fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
        /**
        默认情况下,栅栏 (fence) 对象在创建后是未发出信号的状态。
        这就意味着如果我们没有在 vkWaitForFences 函数调用
        之前发出栅栏 (fence) 信号,vkWaitForFences 函数调用将会一直处于等待状态。

        这里设置初始状态为已发出信号
          */
        fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

        for( size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++){
            //创建信号量和VkFence 对象
            if(vkCreateSemaphore(device,&semaphoreInfo,nullptr,
                                 &imageAvailableSemaphores[i]) != VK_SUCCESS ||
               vkCreateSemaphore(device,&semaphoreInfo,nullptr,
                                 &renderFinishedSemaphores[i]) != VK_SUCCESS ||
               vkCreateFence(device,&fenceInfo,nullptr,
                             &inFlightFences[i]) != VK_SUCCESS){
                throw std::runtime_error(
                 "failed to create synchronization objects for a frame!");
            }

        }
    }

    /**
     * @brief drawFrame
     * 1.从交换链获取一张图像
     * 2.对帧缓冲附着执行指令缓冲中的渲染指令
     * 3.返回渲染后的图像到交换链进行呈现操作
    上面这些操作每一个都是通过一个函数调用设置的, 但每个操作的实际
    执行却是异步进行的。函数调用会在操作实际结束前返回,并且操作的实
    际执行顺序也是不确定的。而我们需要操作的执行能按照一定的顺序,所
    以就需要进行同步操作。
    有两种用于同步交换链事件的方式:栅栏 (fence) 和信号量 (semaphore)。
    它们都可以完成同步操作。
    栅栏 (fence) 和信号量 (semaphore) 的不同之处是,我们可以通过调
    用 vkWaitForFences 函数查询栅栏 (fence) 的状态,但不能查询信号量
    (semaphore) 的状态。通常,我们使用栅栏 (fence) 来对应用程序本身和渲
    染操作进行同步。使用信号量 (semaphore) 来对一个指令队列内的操作或
    多个不同指令队列的操作进行同步。这里,我们想要通过指令队列中的绘
    制操作和呈现操作,显然,使用信号量 (semaphore) 更加合适。
     */
    //绘制图像--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 这一扩展后缀
        vkAcquireNextImageKHR(device,swapChain,
                              std::numeric_limits<uint64_t>::max(),
                              imageAvailableSemaphores[currentFrame],
                              VK_NULL_HANDLE,&imageIndex);
        //提交信息给指令队列
        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
          */
        //提交指令缓冲给图形指令队列
        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( presentQueue , &presentInfo ) ;
        /**
        如果开启校验层后运行程序,观察应用程序的内存使用情况,
        可以发现我们的应用程序的内存使用量一直在慢慢增加。这是由于我
        们的 drawFrame 函数以很快地速度提交指令,但却没有在下一次指令
        提交时检查上一次提交的指令是否已经执行结束。也就是说 CPU 提交
        指令快过 GPU 对指令的处理速度,造成 GPU 需要处理的指令大量堆
        积。更糟糕的是这种情况下,我们实际上对多个帧同时使用了相同的
        imageAvailableSemaphore 和 renderFinishedSemaphore 信号量。
        最简单的解决上面这一问题的方法是使用 vkQueueWaitIdle 函数来等
        待上一次提交的指令结束执行,再提交下一帧的指令:
        但这样做,是对 GPU 计算资源的大大浪费。图形管线可能大部分时
        间都处于空闲状态.
          */
        //等待一个特定指令队列结束执行
        vkQueueWaitIdle ( presentQueue ) ;
        //更新currentFrame
        currentFrame = (currentFrame+1) %MAX_FRAMES_IN_FLIGHT;
    }

        //清除为每一帧创建的信号量和VkFence 对象--12
        for(size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++){
            //所有它所同步的指令执行结束后,对它进行清除
            vkDestroySemaphore(device,renderFinishedSemaphores[i],nullptr);
            vkDestroySemaphore(device,imageAvailableSemaphores[i],nullptr);
            vkDestroyFence(device ,inFlightFences[i], nullptr);
        }
到现在,可以看到屏幕绘制了一个渐变的彩色三角形
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力减肥的小胖子5

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值