首页 > 解决方案 > 使用哪种方式来同步 vkQueueSubmit()?

问题描述


我有一个将数据从一个缓冲区复制到另一个缓冲区的函数,我需要同步它的执行。
我有一个糟糕的选择:
void MainWindow::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
{
    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(logicalDevice, &allocInfo, &commandBuffer);

    //Start recording
    vkBeginCommandBuffer(commandBuffer, &beginInfo);
    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
    vkEndCommandBuffer(commandBuffer);

    //Run command buffer
    vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
    //Waiting for completion
    vkQueueWaitIdle(graphicsQueue);

    vkFreeCommandBuffers(logicalDevice, commandPool, 1, &commandBuffer);
}

这个选项不好,因为如果我想多次执行 copyBuffer() 函数,那么所有缓冲区将被严格地一次复制一个。

我想为每个函数调用使用一个栅栏,以便多个调用可以并行运行。

到目前为止,我只有这样一个解决方案:

void MainWindow::copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size)
{
    VkCommandBuffer commandBuffer;
    vkAllocateCommandBuffers(logicalDevice, &allocInfo, &commandBuffer);
    
    //Create fence
    VkFenceCreateInfo fenceInfo{};
    fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

    VkFence executionCompleteFence = VK_NULL_HANDLE;
    if (vkCreateFence(logicalDevice, &fenceInfo, VK_NULL_HANDLE, &executionCompleteFence) != VK_SUCCESS) {
        throw MakeErrorInfo("Failed to create fence");
    }

    //Start recording
    vkBeginCommandBuffer(commandBuffer, &beginInfo);
    vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);

    vkEndCommandBuffer(commandBuffer);

    //Run command buffer
    vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
    
    vkWaitForFences(logicalDevice, 1, &executionCompleteFence, VK_TRUE, UINT64_MAX);
    vkResetFences(logicalDevice, 1, &executionCompleteFence);

    vkFreeCommandBuffers(logicalDevice, commandPool, 1, &commandBuffer);
    vkDestroyFence(logicalDevice, executionCompleteFence, VK_NULL_HANDLE);
}

这些选项中哪个更好?
第二个选项写对了吗?

标签: c++synchronizationvulkan

解决方案


这两个功能都以同样的方式不好。它们都阻止 CPU 做任何事情,直到传输完成。它们都可以用于潜在地将多个 CB 提交到同一帧中的同一队列,但使用不同的提交命令。

如果性能是您关心的事情,那么两者都不是可取的。

最终,您需要做的是让您的copyBuffer功能不实际执行复制。你应该有一个函数来构建一个命令缓冲区来进行复制。然后将该 CB 存储在稍后与其他复制 CB一起提交的位置。或者更好的是,您可以只有一个复制 CB,每个命​​令添加到其中(帧中调用的第一个将创建 CB)。

在将来的某个时候,在您提交将使用此数据的工作之前,您需要提交传输操作。其工作方式取决于您是否在与将使用它们的工作相同的队列上提交传输操作。

如果它们在同一个队列中,那么您需要做的就是在批处理末尾的命令缓冲区中设置一个事件,以将传输操作与其接收器同步。如果您想更聪明一点,每个传输操作都可以有自己的事件,接收操作将等待该事件。

在同队列传输中,您还需要确保在与其他工作相同的vkQueueSubmit调用中提交传输。或者换一种说法,你永远不应该对特定帧中的特定队列进行多次调用。vkQueueSubmit

如果您正在处理单独的队列,那么情况就会改变。一点点。如果时间线信号量不是一个选项,您需要在提交接收操作之前提交您的传输工作。这是因为传输批处理需要发出信号量,接收操作将等待。并且不能等待二进制信号量,直到发出信号的操作已提交到队列。

但除此之外,其他一切都保持不变。当然,您不需要事件,因为您是通过信号量进行同步的。


推荐阅读