【问题标题】:Using fences to clean-up command buffers and synchronizing swap chain images at the same time使用栅栏清理命令缓冲区并同时同步交换链图像
【发布时间】:2020-12-14 17:05:14
【问题描述】:

假设我有一个由n 图像组成的交换链,并且我允许k“飞行中的帧”。我通过一组信号量imageAvailableSemaphorerenderFinishedSemaphore 和栅栏imageInFlight 确保vkAcquireNextImageKHRvkQueueSubmitvkQueuePresentKHR 之间的正确同步,就像在this tutorial 中所做的那样:

imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
    renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT);
    inFlightFences.resize(MAX_FRAMES_IN_FLIGHT);

需要栅栏来确保在 GPU 完成使用相应图像之前我们不会再次使用信号量。所以,这个栅栏需要在vkQueueSubmit中指定。

另一方面,我正在创建独立于“飞行中的帧”的命令缓冲区。它们是“一次性提交”命令缓冲区。因此,一旦提交,我会将它们添加到“待删除”列表中。我需要知道 GPU 何时完成执行此列表中的命令缓冲区。

但我无法在vkQueueSubmit 中指定另一个栅栏。我该如何解决这个问题?

【问题讨论】:

    标签: graphics vulkan


    【解决方案1】:

    我允许k“飞行中的帧”

    嗯,这就是你的答案。每个将为“帧”提供命令缓冲区的线程都应该有一些 k 命令缓冲区的倍数。他们应该以环形缓冲区的方式使用它们。这些命令缓冲区应该从临时分配池中创建。当他们从环形缓冲区中选择最近最少使用的 CB 时,他们应该在记录之前将其重置。

    您确保没有线程尝试重置仍在使用的 CB,方法是在过去的第 kth 帧完成之前开始下一帧的任何工作(使用栅栏)。

    如果由于某种原因你绝对不能告诉你的线程k是什么,你仍然需要告诉他们一些事情。当您开始处理线程时,您需要告诉他们还有多少帧仍在战斗中。这允许他们根据这个帧数检查他们的环形缓冲区的大小。如果环形缓冲区中的元素数小于帧数,则环形缓冲区中最旧的 CB 未使用。否则,它将不得不从池中分配一个新的 CB 并将其推入环形缓冲区。

    【讨论】:

    • (a) 因此,在单个线程中,对于每个帧 i = 0, ..., k - 1,我们有 a[i] 命令缓冲区 cb[i][0], ..., cb[i][a[i] - 1],所有这些都从 common 瞬态分配水池?还是建议为每个i 使用一个专用池? (b) 您建议在每个命令缓冲区使用一次后重置整个池?
    • (c) 无论如何,这似乎表明每个命令缓冲区恰好对应一个交换链。但是,如果我有一个命令缓冲区,它使用来自多个交换链的图像,或者不仅渲染为交换链图像,而且渲染为纹理,会发生什么?
    • 我对您的回复非常感兴趣;尤其是 (c)。
    • @0xbadf00d: (a) 和 (b) 过于笼统,无法提供答案,因为它们完全取决于您在做什么以及您打算如何在每个线程中执行此操作。而且(c)有点跑题了。如果您将“帧”定义为渲染到单个交换链图像,那么这就是您需要使用的。如果您将“帧”定义为渲染到两个交换链图像,那么 that 就是您需要使用的。关键是您需要正确传达允许新框架使用的数据。
    【解决方案2】:

    您可以为此使用时间线信号量。你可以在这里深入了解它们:https://www.khronos.org/blog/vulkan-timeline-semaphores

    时间线信号量携带一个特定值,而不是发出信号或未发出信号。您感兴趣的函数是 vkGetSemaphoreCounterValue,它允许您读取信号量的值而不会阻塞。

    要创建时间线信号量,您只需将 VkSemaphoreCreateInfo 的 pNext 值设置为 VkSemaphoreTypeCreateInfo,如下所示。

    VkSemaphoreTypeCreateInfo 
    timelineCreateInfo{VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO};
    timelineCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_TIMELINE;
    timelineCreateInfo.initialValue = 0;
    

    您的 VkSubmitInfo 的 pNext 值需要设置为 VkTimelineSemaphoreSubmitInfo。

    VkTimelineSemaphoreSubmitInfo timelineInfo{VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO};
    timelineInfo.signalSemaphoreValueCount = 1;
    timelineInfo.pSignalSemaphoreValues = &signalValue;
    

    在命令缓冲区完成后,信号量的值将是您设置 signalValue 的任何值。之后,您可以使用以下命令查询该值:

    uint64_t value;
    vkGetSemaphoreCounterValue(device, semaphore, &value);
    

    所以假设你在这里将 signalValue 设置为 1 值将是 1 或 0,这就是我们在 VkSemaphoreTypeCreateInfo 中给信号量作为初始值的值。之后,您可以安全地删除您的一次性命令缓冲区。

    注意:时间线信号量实际上是栅栏和二进制信号量的半替代品,应该是您使用的主要同步原语。我认为唯一需要二进制信号量的函数是 vkAcquireSwapchainImage。

    【讨论】:

    • 感谢您的回答。过去我已经看过时间线信号量,但有两个问题使我无法使用它们:(a) 它们只是一个设备扩展,因此可能不受 GPU 支持。 (b) 如博客文章中所述,“Vulkan 的窗口系统集成 API 尚不支持时间线信号量”。所以,我不知道如何使用它们来同步 vkAcquireSwapchainImagevkQueueSubmit
    • a) 它们是 Vulkan 1.2 的核心功能,任何支持它们的 GPU 都将拥有它们。 b)您可以将它们与二进制信号量结合使用。您所要做的就是将用于 vkAcquireSwapchainImage 的信号量添加到等待信号量列表中,并在时间线 sbmit 信息中添加虚拟值。
    • 感谢您的评论。我还不清楚:时间线信号量如何帮助我解决问题?普通信号量/栅栏无法实现我想要实现的哪个方面?
    • 对您的回复非常感兴趣。
    • @0xbadf00d 如果您仍然好奇,那么您尝试做的事情并没有什么需要时间线信号量。但是,时间线信号量很好:您可以从主机和设备发出信号并等待它们,并从主机查询它们。你不能从主机端触摸二进制信号量,你不能在主机上发出信号,也不能从设备上等待它们。此外,您可以用一个时间线信号量替换多个二进制信号量/栅栏,这样效率更高。可以说,它们也可以使代码更简洁。 khronos.org/blog/vulkan-timeline-semaphores
    猜你喜欢
    • 1970-01-01
    • 2016-09-25
    • 1970-01-01
    • 2017-10-05
    • 1970-01-01
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 2012-07-23
    相关资源
    最近更新 更多