我计划使用 vulkan synchronization examples 之一作为如何处理不经常更新的统一缓冲区的引用。具体来说,我正在看这个:

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, ...);
}

在这个例子中,我将其简化为:
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

然后我是否必须返回传输队列再次复制该数据的能力 ?似乎没有一个例子提到它,但我可能会错过它。我真的看不出添加另一个缓冲区屏障如何对同一个绘制命令缓冲区起作用,因为即使我没有任何东西要传输,它也会在下一次提交时停滞,所以我只需要提交另一个命令缓冲区在提交下一个转移操作之前排队所有权转移?

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)

如果是这种情况,我不确定如何处理绘制和传输以及复制和绘制之间的信号量信号。一开始很容易,但后来由于多个飞行帧而变得奇怪,因为绘制提交之间没有依赖性。基本上,我想我需要设置我提交的最近一次绘制命令的信号量来表示所有权的转移,这将表示拷贝,这将表示图形的所有权,如果它在单独的线程上然后我会检查该拷贝是否已提交,并需要等待图形传输的所有权并重置已提交的拷贝检查。但是我不确定下一帧没有这种依赖性会发生什么,并且可以在按时间顺序是前一帧之前完成?

最佳答案

只要您不介意数据变得未定义,您就可以在任何队列系列上使用资源(无需传输)。您仍然需要一个信号量来确保没有内存危险。

你老规范:



例子没有提到它,因为它们只是例子。

至于同步(这是一个独立于 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


尽管请注意上述方法实际上序列化了两种访问,因此它可能不是最理想的。采用双缓冲(或通常为 N 缓冲)会更好。如果你有更多的缓冲区,你可以开始复制到一个缓冲区,而不必担心它已经被其他东西使用了。这意味着拷贝可以与平局并行发生,这会很棒。

关于c++ - 我是否需要在下次转移时将所有权*回*转移到转移队列?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60310004/

10-12 12:25