C语言 Vulkan - 同步对单个缓冲区的访问



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

我是 Vulkan 的新手,但我发现同步是最难理解的部分。我浏览了 Vulkan 规范、同步示例 (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples(、Vulkan 教程 (https://vulkan-tutorial.com/( 以及一堆 Stack Overflow 帖子。不过,我仍然不确定我是否真的"明白了"。

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

  • 在传输中有多个帧,如 Vulkan 教程 (https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation#page_Frames_in_flight( 中所述。
  • 让顶点着色器从单个存储缓冲区读取。
  • 通过主机本地和主机一致的"暂存缓冲区",每帧使用新数据更新存储缓冲区的一部分。
  • 将有多个暂存缓冲区 - 每个帧对应一个。

我认为帧 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命令执行,直到上一帧的顶点着色器完成读取存储缓冲区。我的理解是,这里不需要记忆障碍,因为这是一种"战争危险"(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不是一个"真正的"应用程序。 Hello可以有无限数量的帧在飞行中。但这不是"真实"应用程序中会发生的事情。

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

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

话虽如此,在飞行中拥有框架本身并不是一个理想的特征。不过,最好始终保持 GPU 和 CPU 的繁忙状态(假设有工作负载要完成(。这意味着队列中的每个工作都可以在完成当前工作后立即进行选择。

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

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

如前所述,可能没有必要有 N 个stagingBuffers。如果要更新新的每帧数据,理想情况下应该已经处理旧数据(可以通过围栏检查(。

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

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

相关内容

  • 没有找到相关文章

最新更新