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'。
为了帮助我学习,我正在尝试编写以下代码:
- 有多个飞行帧,如 Vulkan 教程 (https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation#page_Frames_in_flight) 中所述。
- 从单个存储缓冲区读取顶点着色器。
- 通过主机本地和主机一致 'Staging Buffer'.
每帧使用新数据更新部分存储缓冲区
- 将有多个暂存缓冲区 - 一个用于每个飞行中的帧。
我认为第 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 等待。
当多个帧在运行时,在 Vulkan 中同步访问单个缓冲区的最佳方法是什么?
我是 Vulkan 的新手,但我发现同步是最难理解的部分。我查看了 Vulkan 规范、同步示例 (https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples), the Vulkan Tutorial (https://vulkan-tutorial.com/) 以及一堆 Stack Overflow 帖子。不过,我仍然不确定我是否真的 'getting it'。
为了帮助我学习,我正在尝试编写以下代码:
- 有多个飞行帧,如 Vulkan 教程 (https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation#page_Frames_in_flight) 中所述。
- 从单个存储缓冲区读取顶点着色器。
- 通过主机本地和主机一致 'Staging Buffer'. 每帧使用新数据更新部分存储缓冲区
- 将有多个暂存缓冲区 - 一个用于每个飞行中的帧。
我认为第 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 等待。