Vulkan Synchronization 同步

Vulkan的同步有两个作用,一是控制Command的执行顺序,二是控制缓存的刷新。

Vulkan会保证在同一个Command Queue中的Command按顺序开始执行,但并不会保证谁先执行完成,而且会多个Command同时执行。例如Command1 开执行后会,立即开始执行Command2,两个命令会同时执行,且不保证谁先执行完。

GPU是incoherent caches(缓存不一致),Vulkan也不会保证缓存一致性,所以需要控制缓存的刷新,保证后面执行的Command读取到前面Command的执行结果。

Execution Barriers

VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,
    VkPipelineStageFlags                        srcStageMask,
    VkPipelineStageFlags                        dstStageMask,
    VkDependencyFlags                           dependencyFlags,
    uint32_t                                    memoryBarrierCount,
    const VkMemoryBarrier*                      pMemoryBarriers,
    uint32_t                                    bufferMemoryBarrierCount,
    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,
    uint32_t                                    imageMemoryBarrierCount,
    const VkImageMemoryBarrier*                 pImageMemoryBarriers);

Execution Barriers是用来控制命令的执行顺序的。函数vkCmdPipelineBarrier设置前3个参数(commandBuffer、srcStageMask、dstStageMask),其他参数设为null或0,就是设置了一个Execution Barriers。

设置Execution Barriers后会将Queue中的所有Command分成两部分,即Execution Barriers之前的Command和Execution Barriers之后的Command。这里的Command不限于当前commanBuffer中的Command,还包括当前commanBuffer提交之前提交的commandBuffer中的Command和提交之后的,也就是说Execution Barriers在queue中是全局。Execution Barriers的作用是:之后的Command执行dstStageMask阶段,需要等待之前Command执行完srcStageMask阶段

例如以下代码:

vkCmdPipelineBarrier(commandBuffer, TRANSFER_BIT, FRAGMENT_SHADER_BIT);
代码之后的命令执行到FRAGMENT_SHADER_BIT时,需要之前所有命令都执行完了TRANSFER_BIT。

Memory Available and Visible 

GPU缓存如下图:

GPU缓存

Vulkan并不会保证缓存一致性,所以单纯的控制执行顺序并不能保证后面执行的Command读取到前面Command的执行结果。因为Command执行后其执行结果会存在L1缓存,并不会立即刷新到L2缓存,所以后面在不同的SM上执行的Command就不能读取到最新的结果。为了保证后面执行的Command读取到前面Command的执行结果,还需要控制缓存的刷新。

Memory Available

当L2缓存中包含了最新的数据时,则当前的Memory是Avilable状态。如果一个Command执行时修改了数据,则对应的Memory是Undefined状态,此时其他命令读取Memory会读到错误的数据。为了让Memory在Command执行完后变成Available状态,需要在Comand后面的vkCmdPipelineBarrier中MemoryBarrier的srcAccessMask设置一个VK_ACCESS_*_WRITE_BIT。这样Command执行完srcStageMask阶段后,会将执行结果更新到L2缓存。

Memory Visible 

当前L1缓存中包含了最新数据,并设置了访问权限时,则当前Memory是Visible状态,也就是当前SM单元可以读取或修改数据。为了让Memory在Command执行完后变成Visible状态,首先需要保证数据为Avilable状态,然后在vkCmdPipelineBarrier中MemoryBarrier的dstAccessMask设置一个VK_ACCESS_*_WRITE_BIT/VK_ACCESS_*_READ_BIT权限。这样Command执行完dstStageMask阶段时,L1会重新刷新,从L2中读取到最新数据。

其他

  1. srcAccessMask不需要设置*READ_BIT标志,srcAccessMask是用来控制L1缓存中的结果写入到L2缓存的,*READ_BIT标志并不会有任何作用。
  2. TOP_OF_PIPE/BOTTOM_OF_PIPE阶段并执行任何Command,这两个阶段srcAccessMask/dstAccessMask只能设置为0。
  3. “making memory available” is all about flushing caches,将最新数据写入L2缓存
  4. “making memory visible” is invalidating caches。无效化缓存,加载最新的数据。

Image Memory Barrier and Layout

typedef struct VkImageMemoryBarrier {
    VkStructureType            sType;
    const void*                pNext;
    VkAccessFlags              srcAccessMask;
    VkAccessFlags              dstAccessMask;
    VkImageLayout              oldLayout;
    VkImageLayout              newLayout;
    uint32_t                   srcQueueFamilyIndex;
    uint32_t                   dstQueueFamilyIndex;
    VkImage                    image;
    VkImageSubresourceRange    subresourceRange;
} VkImageMemoryBarrier;

Image对象使用前需要设置Layout,因为不同的Layout有不同的用途。例如:纹理用作Frame Buffer时Layout需要是COLOR_ATTACHMENT_OPTIMAL,纹理用作着色器采样时Layout需要是SHADER_READ_ONLY_OPTIMAL。

VkImageMemoryBarrier可以用来转换纹理的Layout,这个转换过程可以看作一次读写,转换时会改变Image Memory的字节格式,会先读取Image然后再写入成新的格式。转换前需要保证Image Memory状态为Available,转换后数据会写入L2缓缓,Image Memory状态自动设置为Available,但不是Visible。

新创建的ImageMemory状态默认是Available和Visible,所以可以直接转换Layout。

Image Alias Memory

使用别名会同时将多个Image绑定到同一块Memory中,所以使用时可能其他Image已修改过Memory导致Memory状态是Undefined,不是Available。所以使用前需要增加一个VkImageMemoryBarrier保证Memory为Available。

例如Image1和Image2都绑定到同块Memory,并且都用于RenderPass渲染,使用时如下:

  • vkCmdPipelineBarrier(image = image1, oldLayout = UNDEFINED, newLayout = COLOR_ATTACHMENT_OPTIMAL, srcStageMask = COLOR_ATTACHMENT_OUTPUT, srcAccessMask = COLOR_ATTACHMENT_WRITE, dstStageMask = COLOR_ATTACHMENT_OUTPUT, dstAccessMask = COLOR_ATTACHMENT_WRITE|READ) // 1
  • vkCmdBeginRenderPass/EndRenderPass
  • vkCmdPipelineBarrier(image = image2,  …) // 与1相同
  • vkCmdBeginRenderPass/EndRenderPass

注:因为RenderPass中可以包含vkCmdPipelineBarrier,所以实际使用时可以不需要写上面两个vkCmdPipelineBarrier,这个两个vkCmdPipelineBarrier可以包含到RenderPass中

Semaphores and Fences

Semaphores和Fences可以用在vkQueueSubmit中进行同步,用于Command Queue,Host,Present中进行同步。

当前Semaphores或Fences激活(signaled)时所有的Memory状态会被设为Available。相当于添加了一个MemoryBarrier,srcStageMask = ALL_COMMANDS_BIT,srcAccessMask = MEMORY_WRITE_BIT。

当前获得(wait)Semaphores或Fences,所有的Memory状态会被设为Visible。

Host Memory

更新数据

当更新了UniformBuffer后,并不需要手动添加Barrier同步更新后的数据,当vkQueueSubmit时会自动同步,会将对应的Memory设置Avaiable和Visible。

如果更新数据在vkQueueSubmit后面则需要手动设置Barrier同步,设置如下:

  • srcStageMask = HOST
  • srcAccessMask = HOST_WRITE_BIT
  • dstStageMask = TRANSFER
  • dstAccessMask = TRANSFER_READ

读取数据

当CPU端需要读取GPU数据时,通常需要使用Fences同步,当获得(wait)Fences时会自动将Memory状态设为Visible,但这个仅限于GPU内部。所以还需要手动添加Barrier用于刷新缓存,设置dstStageMask = PIPELINE_STAGE_HOST和dstAccessMask = ACCESS_HOST_READ_BIT。这样Command执行后会将缓存刷新到CPU内存,CPU就可以读取到最新数据。

总结

Vulkan同步会将Command分成前面的和后面的两个部分,同步主要是用于控制后面的Command执行顺序,以及后面的Command读取到最新的数据。

不同的同步方式:

参考:

Yet another blog explaining Vulkan synchronization – Maister's Graphics Adventures

Vulkan Synchronization

  • 19
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值