首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >多线程中的Vulkan队列同步

多线程中的Vulkan队列同步
EN

Stack Overflow用户
提问于 2018-01-03 16:28:56
回答 2查看 2.6K关注 0票数 5

在我的应用程序中,必须在单独的线程中处理"state“和”图形“。例如," state“线程只关注更新对象位置,而”图形“线程只关心以图形方式输出当前状态。

为了简单起见,假设整个状态数据包含在一个VkBuffer中。"state“线程使用由Compute Pipeline支持的Storage Buffer创建一个VkBuffer,并定期使用vkCmdDispatch更新VkBuffer

同时,“图形”线程创建一个由同一个Graphics Pipeline支持的Uniform BufferVkBuffer,并定期绘制/vkQueuePresentKHR

显然,在“状态”线程写入“状态”线程时,必须有某种同步机制来防止“图形”线程从VkBuffer读取。

我唯一的想法是在两个线程中使用从vkQueueSubmitvkWaitForFences的主机互斥体。

我想知道,也许还有其他更有效的方法,或者这被认为是可以的?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-01-03 20:05:49

试着用信号灯。它们只用于同步GPU上的操作,这比在应用程序中等待和在之前的工作完全处理后提交工作要优化得多。

当您提交工作时,您可以提供一个信号量,该信号量将在这项工作完成时发出信号。当您提交另一份工作时,您可以提供第二批应该等待的相同信号量。第二批处理将在信号量得到信号时自动启动(此信号量也会自动取消信号并可重用)。

(我认为在使用与队列相关的信号量方面存在一些限制。我稍后会更新答案,当我确认这一点,但它们应该足够你的目的。

编辑有限制使用信号量,但它不应该影响您-当您使用一个信号量作为等待信号量在提交期间,没有其他队列可以等待在同一个信号量。)

在Vulkan也有一些事件可以用于类似的目的,但是它们的使用稍微复杂一些。

如果您确实需要同步GPU和您的应用程序,请使用栅栏。它们以与信号量类似的方式发出信号。但是您可以在应用程序端检查它们的状态,在再次使用之前,您需要手动解除它们的信号。

编辑

我增加了一个图片,或多或少地显示了我认为你应该做什么。一个线程计算状态,每个提交都在列表的顶部添加一个信号量(或者@NicolasBolas所写的环形缓冲区)。当提交完成时,这个信号量会得到信号(在“计算”批处理提交期间,它是在pSignalSemaphores中提供的)。

第二个线程呈现你的场景。它管理自己的信号量列表,类似于计算线程。但是,当您想要呈现东西时,您需要确保计算线程完成了计算。这就是为什么您需要使用最新的“计算”信号量并等待它(在“呈现”批处理提交期间在pWaitSemaphores中提供它)。当提交呈现命令时,计算线程无法启动和修改数据,因为它可能会影响呈现的结果。因此,计算线程还需要等待最近的呈现完成。这就是为什么计算线程还需要提供等待信号量(最近的“呈现”信号量)。

你只需要同步提交。当计算线程提交命令时,呈现线程不能启动,反之亦然。这就是为什么应该同步向列表中添加信号量(并从列表中获取信号量)。但这和Vulkan无关。一些互斥可能会有帮助(例如C++-ish std::lock_guard<std::mutex>)。但是,只有在只有一个缓冲区时,这种同步才是一个问题。

另一件事是如何处理两个列表中的旧信号量。您不能直接检查它们的状态,也不能直接解除它们的信号。信号量的状态可以通过使用与每个提交一起提供的附加栅栏来检查。您不需要等待它们,而是不时地检查给定的栅栏是否已发出信号,如果是的话,您可以销毁旧的信号量(因为您不能从应用程序中解除它的信号),或者您可以提交一个没有命令缓冲区的空提交,并使用该信号量作为等待信号量。这样,信号量将被取消信号,您可以重用它。但我不知道哪种解决方案更理想:破坏旧的,创建新的信号量,或者用空的提交来解除它们的信号。

当您只有一个缓冲区时,单元素列表/环可能就足够了。但是更好的解决方案会有某种类型的乒乓缓冲区--从一个缓冲区读取数据,但存储在另一个缓冲区中。在接下来的步骤中,你可以交换它们。这就是为什么在上面的图像中,信号量(环)的列表可能有更多的元素,这取决于您的设置。列表中越独立的缓冲器和信号量(当然是一些合理的计数),当您减少在等待上浪费的时间时,您将获得最好的性能。但是这会使您的代码变得复杂,并且可能会增加延迟(呈现线程获取的数据比计算线程当前处理的数据稍早)。因此,您可能需要平衡性能、代码复杂性和呈现滞后。

票数 5
EN

Stack Overflow用户

发布于 2018-01-04 03:25:04

如何做到这一点取决于两个因素:

  1. 是否要在与其相应的图形操作相同的队列上调度计算操作。
  2. 计算操作与它们相应的图形操作的比率。

第二是最重要的部分。

即使它们是在单独的线程中生成的,至少必须有这样的想法:图形操作是由特定的计算操作提供的(否则,图形线程如何知道数据从哪里读取?)。那你是怎么做到的?

说到底,这部分与Vulkan无关。您需要使用一些线程间的通信机制来允许图形线程问:“我应该使用哪个计算任务的数据?”

通常,这将通过让计算线程将它所做的每一个计算操作添加到某种循环缓冲区(当然是线程安全)来完成。和非锁定)。当图形线程决定从哪里读取数据时,它会询问循环缓冲区最近添加的计算操作。

除了“从何处读取其数据”信息之外,这还将为图形线程提供一个适当的Vulkan同步原语,用于将其命令缓冲区与计算操作的CB同步。

如果计算和图形操作是在同一个队列上调度的,那么这非常简单。不一定要有同步原语。只要图形CBs是在批处理计算CBs之后发出的,所有图形CBs都需要在前面有一个vkCmdPipelineBarrier来等待从计算阶段开始的所有内存操作。

srcStageMask将是STAGE_COMPUTE_SHADER_BITdstStageMask几乎是所有的东西(你可以缩小它的范围,但这不重要,因为至少你的顶点着色阶段需要在那里)。

在管道屏障中需要一个单一的VkMemoryBarrier。它的srcAccessMask将是SHADER_WRITE_BIT,而dstAccessMask将是您想要阅读的。如果计算操作编写了一些顶点数据,则需要VERTEX_ATTRIBUTE_READ_BIT。如果他们编写了一些统一的缓冲区数据,那么您需要UNIFORM_READ_BIT。诸若此类。

如果要在单独的队列上分配这些操作,则需要一个实际的同步对象。

有几个问题:

  1. 您无法检测用户代码是否已发出Vulkan信号量。也不能通过用户代码将信号量设置为未发出信号的状态。您也不能合理地提交一个包含信号量的批处理,该批处理当前正在发出信号,而没有人在等待它。你可以做后一件事,但它不能做正确的事。 简而言之,除非您确信某个进程将等待它,否则您永远不能提交一个发出信号量的批处理。
  2. 您不能发出等待信号量的批处理,除非发出“待定执行”信号的批处理。也就是说,在确定计算队列提交了它的信令批之前,您的图形线程不能vkQueueSubmit它的批处理。

所以你要做的就是这个。当图形队列去获取其计算数据时,这必须向计算线程发送一个信号,以便向其下一个提交调用中添加一个信号量。当图形线程提交其图形操作时,它将等待该信号量。

但是为了确保正确的排序,在计算线程提交信号量信令操作之前,图形线程不能提交其操作。这需要某种形式的CPU同步操作。它可以像图形线程轮询由计算线程设置的原子变量一样简单。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/48081352

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档