Vulkan - 同步访问单个缓冲区

Vulkan - synchronising access to a single Buffer

当多个帧在运行时,在 Vulkan 中同步访问单个缓冲区的最佳方法是什么?

我是 Vulkan 的新手,但我发现同步是最难理解的部分。我查看了 Vulkan 规范、同步示例 (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples), the Vulkan Tutorial (https://vulkan-tutorial.com/) 以及一堆 Stack Overflow 帖子。不过,我仍然不确定我是否真的 'getting it'。

为了帮助我学习,我正在尝试编写以下代码:

我认为第 N 帧(0 <= N < 最大飞行帧数)的命令缓冲区应该如下所示:

// Many parameters omitted for brevity
vkCmdCopyBuffer(commandBuffer[N], stagingBuffer[N], storageBuffer, ...);

VkMemoryBarrier barrier = {0};
barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;

vkCmdPipelineBarrier(
    commandBuffer[N],
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
    0,
    1,
    &barrier,
    ...
);

// begin render pass
// drawing commands
// end render pass
    
vkCmdPipelineBarrier(
    commandBuffer[N],
    VK_PIPELINE_STAGE_VERTEX_SHADER_BIT,
    VK_PIPELINE_STAGE_TRANSFER_BIT,
    0,
    0,
    NULL,
    ...
);

我认为需要第一个管线屏障来防止 GPU 在更新时允许顶点着色器从存储缓冲区中读取。

我认为需要第二个管道屏障来防止下一帧的 vkCmdCopyBuffers 命令在前一帧的顶点着色器完成读取存储缓冲区之前执行。我的理解是这里不需要内存屏障,因为这是一个 'WAR hazard' (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples#first-draw-samples-a-texture-in-the-fragment-shader-second-draw-writes-to-that-texture-as-a-color-attachment).

我的建议正确吗?还是我误解了什么?

注意:我知道我在上面采用的方法(即使正确)可能不是最好的 - 例如也许为飞行中的每个帧设置 N 个存储缓冲区会提供更好的性能。但是,我希望在继续进行之前先了解同步。

非常感谢 Vulkan 大师们能提供的任何帮助!

教程章节的重点是说 Hello 不是 "real" 应用程序。你好可以有无限数量的飞行帧。但这不会发生在 "real" 应用程序中。

驱动程序和图层可能会在栅栏上搭载清理,这意味着东西不再在飞行中。如果从来没有这样的同步,元数据可能会堆积起来。

您可能会在 "real" 应用程序中频繁更新数据,这意味着会经常出现这种同步。此外,您还将控制延迟,这意味着您不会有 N 个暂存缓冲区(如您所建议的那样)——如果尚未使用以前的每帧数据,那么不建议更新新的。当它们真正被使用时,它们已经太老了。然后,如果它不是交互式应用程序(例如渲染电影),它可能有意义。

话虽这么说,让帧在飞行中本身并不是一个真正理想的特性。尽管让 GPU 和 CPU 始终处于忙碌状态是可取的(假设有工作量要完成)。这意味着它可以在完成当前工作后立即选择队列中的每项工作。

您的管道屏障似乎足以同步 storageBuffer。尽管在某些情况下,使用专用传输队列可能更可取(这意味着不同的同步方案)。并且最好使用等效的外部子通道依赖项。

stagingBuffer 需要与主机域和设备域同步。

正如所讨论的,可能不需要 stagingBuffer 个 N。如果您要更新新的每帧数据,理想情况下应该已经处理了旧数据(可以通过围栏检查)。

您将 stagingBuffer 定义为连贯的,因此您无需在主机上执行任何操作,只需写入映射指针即可。 如果在那之后调用 vkQueueSubmit,那么所有这些写入都由主机写入顺序保证隐式同步。

您还必须确保主机不会在设备仍在读取内存时开始写入内存。在这些写入之前应该有某种 Fence 等待。