whn*_*whn 5 c++ synchronization gpu vulkan
我计划使用vulkan 同步示例之一作为如何处理不经常更新的统一缓冲区的参考。具体来说,我正在看这个:
vkBeginCommandBuffer(...);
// Submission guarantees the host write being complete, as per
// https://www.khronos.org/registry/vulkan/specs/1.0/html/vkspec.html#synchronization-submission-host-writes
// So no need for a barrier before the transfer
// Copy the staging buffer contents to the vertex buffer
VkBufferCopy vertexCopyRegion = {
.srcOffset = stagingMemoryOffset,
.dstOffset = vertexMemoryOffset,
.size = vertexDataSize};
vkCmdCopyBuffer(
commandBuffer,
stagingBuffer,
vertexBuffer,
1,
&vertexCopyRegion);
// If the graphics queue and transfer queue are the same queue
if (isUnifiedGraphicsAndTransferQueue)
{
// If there is a semaphore signal + wait between this being submitted and
// the vertex buffer being used, then skip this pipeline barrier.
// Pipeline barrier before using the vertex data
// Note that this can apply to all buffers uploaded in the same way, so
// ideally batch all copies before this.
VkMemoryBarrier memoryBarrier = {
...
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TRANSFER_BIT , // srcStageMask
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, // dstStageMask
1, // memoryBarrierCount
&memoryBarrier, // pMemoryBarriers
...);
vkEndCommandBuffer(...);
vkQueueSubmit(unifiedQueue, ...);
}
else
{
// Pipeline barrier to start a queue ownership transfer after the copy
VkBufferMemoryBarrier bufferMemoryBarrier = {
...
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = transferQueueFamilyIndex,
.dstQueueFamilyIndex = graphicsQueueFamilyIndex,
.buffer = vertexBuffer,
...};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TRANSFER_BIT , // srcStageMask
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, // dstStageMask
1, // bufferMemoryBarrierCount
&bufferMemoryBarrier, // pBufferMemoryBarriers
...);
vkEndCommandBuffer(...);
// Ensure a semaphore is signalled here which will be waited on by the graphics queue.
vkQueueSubmit(transferQueue, ...);
// Record a command buffer for the graphics queue.
vkBeginCommandBuffer(...);
// Pipeline barrier before using the vertex buffer, after finalising the ownership transfer
VkBufferMemoryBarrier bufferMemoryBarrier = {
...
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT,
.srcQueueFamilyIndex = transferQueueFamilyIndex,
.dstQueueFamilyIndex = graphicsQueueFamilyIndex,
.buffer = vertexBuffer,
...};
vkCmdPipelineBarrier(
...
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, // dstStageMask
...
1, // bufferMemoryBarrierCount
&bufferMemoryBarrier, // pBufferMemoryBarriers
...);
vkEndCommandBuffer(...);
vkQueueSubmit(graphicsQueue, ...);
}
Run Code Online (Sandbox Code Playgroud)
在这个例子中,我将其简化为:
map updated buffer which is host coherent
perform transfer in transfer queue to device local memory
make sure to put a buffer memory barrier to handle the queue ownership transfer
perform normal draw commands
make sure to put a buffer memory barrier to handle receiving of buffer in queue ownership
Run Code Online (Sandbox Code Playgroud)
然后我必须恢复传输队列再次复制该数据的能力吗?这些例子似乎都没有提到它,但我可能会错过它。我真的看不出添加另一个缓冲区屏障如何适用于同一个绘制命令缓冲区,因为即使我没有任何要传输的内容,它也会在下一次提交时停止,所以我只需要提交另一个命令缓冲区在提交下一个转移操作之前排队所有权转移?
IE
//begin with transfer ownership
submit(copy)
submit(ownership to graphics)
submit(draw)
submit(ownership to transfer)
submit(copy)
submit(ownership to graphics)
submit(draw)
submit(draw)
submit(draw)
submit(ownership to transfer)
submit(copy)
submit(ownership to graphics)
submit(draw)
Run Code Online (Sandbox Code Playgroud)
如果是这种情况,我不确定如何处理绘制和传输以及复制和绘制之间的信号量信号。一开始很简单,但后来由于多个飞行帧而变得奇怪,因为绘制提交之间不存在依赖关系。基本上,我想我需要设置我提交的最新绘制命令,以便有一个信号量来发出所有权转移的信号,这将发出副本的信号,这将发出图形的所有权的信号,并且如果它位于单独的线程上然后,我会检查此副本是否已提交,并要求等待图形传输的所有权并重置副本提交检查。但我不确定没有这种依赖性的下一帧会发生什么,并且可以在按时间顺序排列的前一帧之前完成?
只要您不介意数据变得未定义,您就可以在任何队列系列上使用资源(无需传输)。您仍然需要一个信号量来确保不存在内存危险。
老规格:
笔记
如果应用程序在从一个队列族传输到另一个队列族时不需要资源内容保持有效,则应跳过所有权转移。
例子就不提了,因为只是例子。
至于同步(这是与 QFOT 不同的问题),作为 的一部分的信号量信号vkQueueSubmit覆盖了之前提交顺序中的所有内容。因此,当您提交副本时,您将让它等待上次提交的绘制已发出信号的信号量。这意味着在另一个队列上开始复制之前,该队列上的绘制和任何先前的绘制都已完成。
然后,您通过副本发出信号量信号,并在您提交的下一个抽奖时等待它。这意味着副本在绘制(以及任何后续绘制)在图形队列上读取它之前完成写入。
例如:
submit(copy, release ownership from tranfer, semaphore signal)
submit(semaphore wait, acquire ownership to graphics, draw)
submit(draw)
submit(draw)
submit(draw)
submit(draw)
submit(draw, semaphore signal)
submit(semaphore wait, copy, release ownership from tranfer, semaphore signal)
submit(semaphore wait, acquire ownership to graphics, draw)
submit(draw)
submit(draw)
etc
Run Code Online (Sandbox Code Playgroud)
尽管请注意,上述方法实际上序列化了两种访问,因此它可能不是最佳的。采用双缓冲(或通常是 N 缓冲)可能会更好。如果您有更多缓冲区,您可以开始复制到一个缓冲区中,而不必担心它已被其他缓冲区使用。这意味着复制可以与平局同时进行,这会很棒。