同步vkQueueSubmit()使用哪种方式?
Which way to synchronize vkQueueSubmit() to use?
我有一个函数可以将数据从一个缓冲区复制到另一个缓冲区,我需要同步它的执行。
我有一个糟糕的选择:
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, ©Region);
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, ©Region);
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);
}
这些选项中哪个更好?
第二个选项写对了吗?
这两个函数在语义上完全相同,并且执行完全相同的阻塞行为。
第二种稍微好一点。 vkQueueWaitIdle
是一种调试和非热点功能。它可能会导致隐藏的第二次提交以发出隐式围栏信号。
你不需要重置你随后摧毁的栅栏。您正在预先发出信号创建它,这是一个错误。你也忘了把它传递给 vkQueueSubmit
.
这两个功能都以同样的方式不好。他们都阻止 CPU 在传输完成之前做任何事情。它们都可以用于潜在地向同一帧中的同一队列提交多个 CB,但使用不同的提交命令。
如果您关心的是性能,则两者都不可取。
最终,您需要做的是让您的 copyBuffer
函数不实际执行复制。你应该有一个函数来构建一个命令缓冲区来做一个副本。然后将该 CB 存储在一个地方,以便稍后与 other 复制 CB 一起提交。或者更好的是,您可以只有一个复制 CB,每个命令都添加到该 CB(帧中调用的第一个 CB 将创建 CB)。
在将来的某个时候,在您提交将使用此数据的工作之前,您需要提交传输操作。它的工作方式取决于您是否在与将使用它们的工作相同的队列上提交传输操作。
如果它们在同一个队列中,那么您需要做的就是在批处理结束时在命令缓冲区中创建一个事件,以将传输操作与其接收者同步。如果你想更聪明,每个传输操作都可以有自己的事件,接收操作将等待。
并且在同队列传输中,您还需要确保在与您工作的 rest 相同的 vkQueueSubmit
调用中提交传输。或者换句话说,您永远不应该为特定帧中的特定队列多次调用 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, ©Region);
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, ©Region);
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);
}
这些选项中哪个更好?
第二个选项写对了吗?
这两个函数在语义上完全相同,并且执行完全相同的阻塞行为。
第二种稍微好一点。 vkQueueWaitIdle
是一种调试和非热点功能。它可能会导致隐藏的第二次提交以发出隐式围栏信号。
你不需要重置你随后摧毁的栅栏。您正在预先发出信号创建它,这是一个错误。你也忘了把它传递给 vkQueueSubmit
.
这两个功能都以同样的方式不好。他们都阻止 CPU 在传输完成之前做任何事情。它们都可以用于潜在地向同一帧中的同一队列提交多个 CB,但使用不同的提交命令。
如果您关心的是性能,则两者都不可取。
最终,您需要做的是让您的 copyBuffer
函数不实际执行复制。你应该有一个函数来构建一个命令缓冲区来做一个副本。然后将该 CB 存储在一个地方,以便稍后与 other 复制 CB 一起提交。或者更好的是,您可以只有一个复制 CB,每个命令都添加到该 CB(帧中调用的第一个 CB 将创建 CB)。
在将来的某个时候,在您提交将使用此数据的工作之前,您需要提交传输操作。它的工作方式取决于您是否在与将使用它们的工作相同的队列上提交传输操作。
如果它们在同一个队列中,那么您需要做的就是在批处理结束时在命令缓冲区中创建一个事件,以将传输操作与其接收者同步。如果你想更聪明,每个传输操作都可以有自己的事件,接收操作将等待。
并且在同队列传输中,您还需要确保在与您工作的 rest 相同的 vkQueueSubmit
调用中提交传输。或者换句话说,您永远不应该为特定帧中的特定队列多次调用 vkQueueSubmit
。
如果您处理的是单独的队列,那么情况就不同了。一点点。如果时间线信号量不是一个选项,您需要在提交接收操作之前提交您的传输工作。这是因为传输批处理需要发出接收操作将等待的信号量。在发出信号的操作已提交到队列之前,不能等待二进制信号量。
但除此之外,其他一切都保持不变。当然,你不需要事件,因为你是通过信号量同步的。