在我的申请中,#34; state"和"图形"在单独的线程中处理。例如,"州"线程仅涉及更新对象位置,以及"图形"线程只关注图形输出当前状态。
为简单起见,我们要说整个状态数据都包含在一个VkBuffer
中。 "州"线程创建Compute Pipeline
Storage Buffer
支持VkBuffer
,并定期vkCmdDispatch
更新VkBuffer
。
同时,"图形"线程创建Graphics Pipeline
,Uniform Buffer
由同一VkBuffer
支持,并定期绘制/ vkQueuePresentKHR
。
显然必须有某种同步机制来防止"图形"来自VkBuffer
的线索,而#34;状态"线程正在写它。
我唯一的想法是在两个线程中使用从vkQueueSubmit
到vkWaitForFences
的主机互斥锁。
我想知道,是否有其他方法更有效率或者这被认为是好的?
答案 0 :(得分:2)
尝试使用信号量。它们仅用于在GPU上同步操作,这比在应用程序中等待并在完成前一工作后提交工作更加优化。
提交作品时您可以提供一个信号量,在完成此工作后会发出信号。当您提交另一个工作时您可以提供第二个批处理应该等待的相同信号量。当信号量发出信号时,第二批处理将自动开始(此信号量也会自动取消信号并且可以重复使用)。
(我认为使用与队列相关联的信号量存在一些限制。我稍后会在确认时更新答案,但它们应该足以达到您的目的。
[编辑]使用信号量有一些限制,但它不会影响你 - 当你在提交过程中使用信号量作为等待信号量时,没有其他队列可以在同一个信号量上等待。)
Vulkan中也有可用于类似目的的事件,但它们的使用有点复杂。
如果您确实需要同步GPU和您的应用程序,请使用防护。它们以与信号量类似的方式发出信号。但您可以在应用程序端检查其状态,并且您需要手动取消信号,然后才能再次使用。
[编辑]
我添加的图片或多或少地显示了我认为你应该做的事情。一个线程计算状态,每个提交都会在列表顶部添加一个信号量(或者像@NicolasBolas所写的那样在环形缓冲区中添加)。提交完成后,此信号量将发出信号(在"计算"批量提交期间在pSignalSemaphores中提供)。
第二个线程渲染你的场景。它与计算线程类似地管理它自己的信号量列表。但是当你想渲染东西时,你需要确保计算线程完成了计算。这就是为什么你需要采用最新的"计算"信号量并等待它(在&#34期间在pWaitSemaphores中提供它;渲染"批量提交)。提交渲染命令时,计算线程无法启动和修改数据,因为它可能会影响渲染结果。因此,计算线程也需要等到最近的渲染完成。这就是为什么计算线程还需要提供等待信号量(最近的"呈现"信号量)的原因。
您只需要同步提交。当计算线程提交命令时,渲染线程无法启动,反之亦然。这就是为什么应该同步将信号量添加到列表(以及从列表中获取信号量)的原因。但这与Vulkan无关。可能一些互斥量会有所帮助(例如C ++ - ish std::lock_guard<std::mutex>
)。但只有当您有一个缓冲区时,此同步才会出现问题。
另一件事是如何处理两个列表中的旧信号量。你不能直接检查他们的状态是什么,你不能直接取消他们的信号。可以使用每次提交提供的附加栅栏来检查信号量的状态。你不等他们,但不时检查给定的栅栏是否有信号,如果是,你可以销毁旧的信号量(因为你不能从应用程序中取消信号)或者你可以空提交,没有命令缓冲区,并使用该信号量作为等待信号量。这样,信号量将无信号,您可以重复使用它。但是我不知道哪种解决方案更优化:破坏旧的信号并创建新的信号量,或者用空提交信号来消除它们。
当你有一个缓冲区时,单元素列表/环可能已经足够了。但是更优化的解决方案会有一些乒乓球缓冲区 - 您从一个缓冲区读取数据,但将结果存储在另一个缓冲区中。在下一步中你交换它们。这就是为什么在上图中,信号量(环)列表可能包含更多元素,具体取决于您的设置。列表中的独立缓冲区和信号量越多(当然要合理计算),您将获得最佳性能,因为您可以减少等待时间浪费的时间。但是这会使您的代码变得复杂,并且它也可能会增加延迟(渲染线程获取的数据比计算线程当前处理的数据略长)。因此,您可能需要平衡性能,代码复杂性和渲染延迟。
答案 1 :(得分:0)
如何执行此操作取决于两个因素:
是否要在与相应图形操作相同的队列上调度计算操作。
计算操作与其对应图形操作的比率。
#2是最重要的部分。
即使它们是在不同的线程中生成的,也必须至少有一些想法,即图形操作是由特定的计算操作提供的(否则,图形线程将如何知道数据的读取位置?)。那么,你是怎么做到的?
在一天结束时,那部分与Vulkan无关。您需要使用一些线程间通信机制来允许图形线程询问“我应该使用哪个计算任务的数据?”
通常,这可以通过让计算线程将其执行的每个计算操作添加到某种循环缓冲区来实现(当然是线程安全的。并且非锁定)。当图形线程决定从何处读取其数据时,它会向循环缓冲区请求最近添加的计算操作。
除了“从哪里读取数据”信息之外,这还将为图形线程提供适当的Vulkan同步原语,用于将其命令缓冲区与计算操作的CB同步。
如果在同一队列上调度计算和图形操作,那么这非常简单。实际上不必是同步原语。只要在批处理中的计算CB之后发布图形CB,所有图形CB都需要在前面有一个vkCmdPipelineBarrier
,它等待来自计算阶段的所有内存操作。
srcStageMask
将是STAGE_COMPUTE_SHADER_BIT
,其中dstStageMask
就是,几乎所有东西(你可以缩小范围,但这无关紧要,因为至少你的顶点着色阶段将需要在那里)。
管道障碍中需要一个VkMemoryBarrier
。它是srcAccessMask
SHADER_WRITE_BIT
,而dstAccessMask
则是你打算阅读它。如果计算操作写了一些顶点数据,则需要VERTEX_ATTRIBUTE_READ_BIT
。如果他们写了一些统一的缓冲区数据,则需要UNIFORM_READ_BIT
。等等。
如果您在不同的队列上调度这些操作,则需要实际的同步对象。
有几个问题:
您无法检测用户代码是否已发出Vulkan信号量信号。您也不能通过用户代码将信号量设置为未信号状态。你也不能合理地提交一个目前已发出信号并且没有人在等待它的具有信号量的批次。你可以做后者,但它不会做正确的事。
简而言之,除非你某些某个进程要等待它,否则你永远不能提交一个发出信号量信号的批处理。
您不能发出等待信号量的批处理,除非发出信号的批处理是“待执行”。也就是说,你的图形线程不能vkQueueSubmit
它的批处理,直到某些计算队列提交了它的信号批处理。
所以你要做的就是这个。当图形队列去获取其计算数据时,它必须向计算线程发送信号以向其下一个提交调用添加信号量。当图形线程提交其图形操作时,它会等待该信号量。
但是为了确保正确排序,图形线程在计算线程提交了信号量信令操作之前无法提交其操作。这需要某种形式的CPU同步操作。它可以像图形线程轮询由计算线程设置的原子变量一样简单。