Vulkan命令缓冲区间的同步怎么做?

阅读次数 92

这里有几种在 Vulkan 中处理同步的方法。这是我对其的理解:

  • Fences(栅栏): 用于 GPU 到 CPU 的同步。
  • Semaphores(信号量): 用于 GPU 到 GPU 的同步,它们用来同步队列提交(在同一队列或不同队列上)。
  • Events(事件): 更为通用,可以在 CPU 和 GPU 上重置和检查。
  • Barriers(屏障): 用于命令缓冲区内部的同步。
    在我的情况下,我有两个命令缓冲区。我希望第二个命令缓冲区在第一个之后执行。
submitInfo.pCommandBuffers = &firstCommandBuffer;
vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);

// wait for first command buffer to finish
submitInfo.pCommandBuffers = &secondCommandBuffer;
vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);

这种情况下最适合哪种同步机制?

如果使用 vkQueueWaitIdle(queue),这与使用栅栏(Fence)的效果是否相同?或者我应该使用事件(Event)或信号量(Semaphore)来实现同步?

如果我同时发送多个命令缓冲区到队列:

std::vector<VkCommandBuffer> submitCmdBuffers = {
        firstCommandBuffer,
        secondCommandBuffer
    };
submitInfo.commandBufferCount = submitCmdBuffers.size();
submitInfo.pCommandBuffers = submitCmdBuffers.data();

这样做,第二个CmdBuffer执行的时候,所有的资源是否已经是第一个CmdBuffer写入的状态了?

1 Answers

看一下我刚分享的文章吧

同步分为单个Device Queue内同步与多个Device Queue之间同步,

  • Device Queue内同步有:
    • Pipeline Barriers(管道屏障)
    • Events(事件)
  • 多个Device Queue间的同步有:
    • Semaphores(信号量)
    • Fences(栅栏)

所以从你的使用场景来看,你采用的是两个Device Queue间的同步(两次queue submit),所以Semaphores(信号量)与 Fences(栅栏)会更适合一些,个人认为信号量会更适合一些,因为它是GPU到GPU的过程,而栅栏是GPU到CPU的过程。当然新版本也引入的Timeline Semaphores(时间线信号量),它是信号量与栅栏的超集,既可以完成GPU到CPU的过程,也可以进行GPU到GPU的过程,甚至于CPU到GPU也可以进行。

当你将多个命令缓冲区提交到同一个队列时,Vulkan 会保证这些命令缓冲区会按照它们被提交的顺序执行。这意味着第二个命令缓冲区 (secondCommandBuffer) 只会在第一个命令缓冲区 (firstCommandBuffer) 完全执行完毕后开始执行。因此,在 secondCommandBuffer 执行的时候,所有由 firstCommandBuffer 写入的资源都将处于最新的状态。但是需要注意如果你在 firstCommandBuffer 中修改了一个图像或缓冲区,并且 secondCommandBuffer 需要读取这个资源,你应该在 firstCommandBuffer 中插入一个管道屏障,确保资源的状态从写入状态转换为读取状态。

关于 vkQueueWaitIdle 和栅栏之间的区别,vkQueueWaitIdle 会使 CPU 阻塞直到队列中的所有操作都已完成,而栅栏则允许你选择性地等待特定的提交完成。vkQueueWaitIdle 影响整个队列,可能会导致不必要的等待,因此不推荐用于精细的同步控制。相反,使用栅栏可以提供更精确的控制,只等待你需要的操作完成。

我觉得如果一个命令缓冲区能完成的事情最好还是在一个命令缓冲区内完成,比如可以采用多个renderpass的形式,这样就可以更加直接的使用Device Queue内的同步操作。

我去,大佬牛阿