Vulkan同步机制:fence的特点和使用


基本介绍

是什么

根据spec的描述:

Fences are a synchronization primitive that can be used to insert a dependency from a queue to the host.

fence是在device与host之间同步,将device完成任务的消息通知给host,注意,这里是单向同步的,即从device向host

何时使用fence

简单讲,需要GPU向CPU报告一些事情,来编排CPU上任务的执行。其最通俗的使用逻辑如下:

当我们想提交一个要执行的任务,可以将一个fence绑定到这个任务中,当该任务执行完毕,这个fence就会被触发signaled;而在CPU(host)端,可以等待这个fence被signaled,在等待的过程中,CPU不会做任何事情,当发现GPU上的任务执行结束后,发现fence被signal了,才会继续执行其他操作。

在vulkan-tutorial中给出了一个例子说明——screenshot

伪代码如下:

// record command buffer with the transfer
VkCommandBuffer A = ... 
// create the fence
VkFence F = ... 

// enqueue A, start work immediately, signal F when done
vkQueueSubmit(work: A, fence: F)
// blocks execution until A has finished executing
vkWaitForFence(F)
// can't run until the transfer has finished
save_screenshot_to_disk() 

注意:让CPU不干活,一直在等待不是一个好主意

VkFence长啥样

在vulkan中,fence是通过VkFence表示的。

// Provided by VK_VERSION_1_0
VK_DEFINE_NON_DISPATCHABLE_HANDLE(VkFence)

在vulkan中,这是一个别名,如下:

#define VK_DEFINE_NON_DISPATCHABLE_HANDLE(object) typedef struct object##_T *object;

即,VkFence是一个pointer,指向struct vkFence_T。

相关操作

创建和销毁

创建fence

VkResult vkCreateFence(
    VkDevice device,
    const VkFenceCreateInfo* pCreateInfo,
    const VkAllocationCallbacks* pAllocator,
    VkFence* pFence
);

其中:

  • pCreateInfo是创建一个fence所需要的信息;
  • pFence是创建成功后,返回的指向fence的pointer;

那么创建一个fence,需要什么信息呢?

typedef struct VkFenceCreateInfo {
    VkStructureType sType;
    const void* pNext;
    VkFenceCreateFlags flags;
} VkFenceCreateInfo;

typedef enum VkFenceCreateFlagBits {
    VK_FENCE_CREATE_SIGNALED_BIT = 0x00000001,
} VkFenceCreateFlagBits;

其中在创建fence时需要指定VkFenceCreateFlags,用来表明该fence的初始状态是什么?signaled还是unsignaled,默认情况下是unsignaled的。

在创建fence时,可以指定该fence中的payload信息可以为外部读取,因此需要为信息被读取指定一个VkExportFenceCreateInfo,放到pNext成员变量中。

销毁fence

void vkDestroyFence(
    VkDevice device,
    VkFence fence,
    const VkAllocationCallbacks* pAllocator
);

其中,fence是要销毁的对象。在执行该函数时,所有queue submmission commands必须都已经完成了执行。

状态管理

Fence有两种状态:

  • signaled;
  • unsignaled;

其用于同步各种commands,就是通过对自身状态的改变实现的。

signal a fence

当vkQueueSubmit任务完成时

当command buffer被提交到对应的queue之后,即执行vkQueueSubmit2vkQueueSubmit之后,fence可以被signaled。

Queue submission commands define a set of queue operations to be executed by the underlying physical device, including synchronization with semaphores and fences.

VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(
    VkQueue queue,
    uint32_t submitCount,
    const VkSubmitInfo* pSubmits,
    VkFence fence);

VKAPI_ATTR和VKAPI_CALL是一些macro,参考这里

在这里,fence是可选的,一旦这次所有被提交的command buffers被执行完成,该fence就会被signaled

当特定的event发生时

通过在vkQueueSubmit中使用fence,可以提交一个fence,还有其他的方式可以使得fence被signaled。

fence可以与event建立联系,当在device或者display上发生了特定的event时,也可以signal a fence,这需要注册一个event发生时的处理函数,即vkRegisterDeviceEventEXT

在device上,

VkResult vkRegisterDeviceEventEXT(
    VkDevice device,
    const VkDeviceEventInfoEXT* pDeviceEventInfo,
    const VkAllocationCallbacks* pAllocator,
    VkFence* pFence
);

其中,pDeviceEventInfo指向了特定的event的信息,当device上该event发生后,对应的pFence就会被signaled。

同样的,在display上,也有类似的操作。

VkResult vkRegisterDisplayEventEXT(
    VkDevice device,
    VkDisplayKHR display,
    const VkDisplayEventInfoEXT* pDisplayEventInfo,
    const VkAllocationCallbacks* pAllocator,
    VkFence* pFence
);

reset a fence

根据vulkan-tutorial,因为fence控制host的执行,当使用完后,必须被reset成unsignaled。

Fences must be reset manually to put them back into the unsignaled state. This is because fences are used to control the execution of the host, and so the host gets to decide when to reset the fence.

在host中调用vkResetFences之后,fences会被unsignaled;

VkResult vkResetFences(
    VkDevice device,
    uint32_t fenceCount,
    const VkFence* pFences
);
  • fenceCount is the number of fences to reset.
  • pFences is a pointer to an array of fence handles to reset.

如果当fence已经是signaled的状态后,该函数的调用没有任何副作用。

wait for fences

当host中使用vkWaitForFences则会等待fence被signaled;

VkResult vkWaitForFences(
    VkDevice device,
    uint32_t fenceCount,
    const VkFence* pFences,
    VkBool32 waitAll,
    uint64_t timeout);

该函数是等待一系列的fences被signaled,waitAll参数指定是等全部都signaled,还是只有一个被signaled就解锁等待。

当执行完该函数后,条件被满足时,则函数阻塞等待结束,或者当等待超时后,也会结束阻塞。

query a fence

在host上还可以通过vkGetFenceStatus查询当前fence的状态,以判断是否signaled;

VkResult vkGetFenceStatus(
    VkDevice device,
    VkFence fence
);

当进行查询时,该函数是非阻塞的,即查询那个瞬时的状态,如果对应的任务仍在执行时,该函数返回的值会马上过期,不可靠。

Fence’s payload

是什么

The internal data of a fence may include a reference to any resources and pending work associated with signal or unsignal operations performed on that fence object, collectively referred to as the fence’s payload.

Fence内部有些数据信息,用来关联其所同步的执行任务,这些任务后续需要通过该fence对象进行signaled和unsignaled。关于这些数据信息统一称为 fence’s payload。

为啥要设计这个机制呢?

这些内部的信息可以在不同的fences之间、甚至其他的sync primitives之间共享,以便设计更为复杂的依赖关系。

import payload

对于一个已有的fence,如果对其import其他fence的payload时,可以选择这种import操作的性质:

  • permanent

    A permanent payload import behaves as if the target fence was destroyed, and a new fence was created with the same handle but the imported payload.

    当import是永久性的时候,则会产生破坏性的效果。

  • temporary

    If the import is temporary, the fence will be restored to its permanent state the next time that fence is passed to vkResetFences.

    即,被import的fence的原始状态可以被恢复。

另外,对于import的实现,也有两种方式:

The implementation must perform the import operation by either referencing or copying the payload referred to by the specified external fence handle.

这种import的实现方法称为handle type’s reference

  • reference transference

    这种情况下,因为是reference,对于一个已有fence的payload,可以import到多个其他的fences上,这样会有多个fences共享一份payload。

    这样会有什么问题?当这些fence其中任意一个fence出现signaling、waiting、reseting时,都必须同时对多个fences执行相同的操作,好像它们是同一个fence,这就是reference的作用。

  • copy

    这种方式,从名字上可以看出,直接copy,即便一个fence出现signaling、waiting、reseting,不会影响其他的共享payload的fences的行为,因为这些payload都是副本,彼此互不影响。

这里说的是import时的情况,export的情况也是一样的,有相同的handle type’s reference

但多个共享payload的fence用在不同的commands中时,其行为需要严格满足vulkan规范中的定义

总结

关于fence,主要是从device向host单向同步,且通过各个API来对fence的状态进行管理,总结如下:

fence使用的基本情况

参考资料

  1. https://arm-software.github.io/vulkan_best_practice_for_mobile_developers/samples/performance/wait_idle/wait_idle_tutorial.html

  2. Vulkan 1.3.235 - A Specification (with all registered Vulkan extensions) - 7.3


文章作者: alex Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 alex Li !
  目录