Drawing a triangle/Drawing/Frames in flight

现在我们的渲染循环有一个明显的缺陷。我们需要等待上一帧完成,然后才能开始渲染下一帧,这会导致主机不必要的空闲。

解决这一问题的方法是允许多帧同时运行,也就是说,允许一帧的渲染不干扰下一帧的录制。我们如何做到这一点?必须复制渲染期间访问和修改的任何资源。因此,我们需要多个命令缓冲区、信号灯和围栏。在后面的章节中,我们还将添加其他资源的多个实例,因此我们将看到这个概念再次出现。

首先在程序顶部添加一个常量,该常量定义应同时处理多少帧:

const int MAX_FRAMES_IN_FLIGHT = 2;

我们选择数字2是因为我们不希望CPU超过GPU太多。当有2帧在运行时,CPU和GPU可以同时执行各自的任务。如果CPU提前完成,它将等待GPU完成渲染后再提交更多工作。在3帧或更多帧的情况下,CPU可以领先GPU,增加延迟帧。通常,不需要额外的延迟。但让应用程序控制飞行中的帧数是Vulkan显式的另一个例子。

每个帧都应该有自己的命令缓冲区、信号量集和围栏。重命名,然后将其更改为对象的std::vectors:

std::vector<VkCommandBuffer> commandBuffers;

...

std::vector<VkSemaphore> imageAvailableSemaphores;
std::vector<VkSemaphore> renderFinishedSemaphores;
std::vector<VkFence> inFlightFences;

然后我们需要创建多个命令缓冲区。将createCommandBuffer重命名为createCommandBuffers。接下来,我们需要将命令缓冲区向量调整为MAX_FRAMES_IN_FLIGHT的大小,更改VkCommandBufferAllocateInfo以包含这么多命令缓冲区,然后将目标更改为命令缓冲区的vector:

void createCommandBuffers() {
    commandBuffers.resize(MAX_FRAMES_IN_FLIGHT);
    ...
    allocInfo.commandBufferCount = (uint32_t) commandBuffers.size();

    if (vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()) != VK_SUCCESS) {
        throw std::runtime_error("failed to allocate command buffers!");
    }
}

createSyncObjects函数应更改为创建所有对象:

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;
    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

    for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
        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!");
        }
    }
}

同样,它们也应该被清理:

void cleanup() {
    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);
    }

    ...
}

请记住,因为当我们释放命令池时,命令缓冲区会为我们释放,所以命令缓冲区清理无需额外操作。

要在每帧使用正确的对象,我们需要跟踪当前帧。为此,我们将使用框架索引:

uint32_t currentFrame = 0;

现在可以修改drawFrame函数以使用正确的对象:

void drawFrame() {
    vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX);
    vkResetFences(device, 1, &inFlightFences[currentFrame]);

    vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex);

    ...

    vkResetCommandBuffer(commandBuffers[currentFrame],  0);
    recordCommandBuffer(commandBuffers[currentFrame], imageIndex);

    ...

    submitInfo.pCommandBuffers = &commandBuffers[currentFrame];

    ...

    VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]};

    ...

    VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]};

    ...

    if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame]) != VK_SUCCESS) {
}

当然,我们不应该每次都忘记进入下一帧:

void drawFrame() {
    ...

    currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}

通过使用模(%)运算符,我们确保帧索引在每个MAX_FRAMES_IN_FLIGHT排队帧之后循环。

我们现在已经实现了所有所需的同步,以确保排队的工作帧不超过MAX_FRAMES_IN_FLIGHT,并且这些帧不会彼此重叠。请注意,代码的其他部分(如最终清理)可以依赖更粗略的同步,如vkDeviceWaitIdle。您应该根据性能要求决定使用哪种方法。

要通过示例了解有关同步的更多信息,请查看Khronos的这篇广泛概述

在下一章中,我们将处理一个行为良好的Vulkan程序所需的另一件小事。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值