vulkan同步原语Event

Event

Event是一个细粒度的同步原语,用于精确的界定管线里发生的操作。

Event用于同步提交到同一队列的不同命令,或者同步CPU和队列。Event不能用于不同队列的命令间的同步。

Event有两种状态signaled和unsignaled。

无论是设备还是主机,都可以直接操作event,不过他们是有区别的,主机可以使用vkGetEventStatus立即获取事件对象的状态,但是不能直接等待事件,而设备正好相反,不能直接获取事件对象的状态,但是能等待一个或者多个事件。

Event使用场景

在官方的博客里有这样一句话:

1
Another tool for synchronization in Vulkan is the event, which uses source stage masks and destination stage masks just like pipeline barriers, and can be quite useful when we need to specify and run parallel computation.

也就是说event一般用于并行计算中,常用于compute shader中。在渲染管线中,很少见到events。

Event与Barrier的不同

1
The key difference between events and pipeline barriers is that event barriers occur in two parts.

barrier是一次设置的,而Event则是分开在两部分设置的。

1
The first part is setting an event using vkCmdSetEvent, and the second is waiting for an event with vkCmdWaitEvents

在第一部分用vkCmdSetEvent设置event为signaled,在第二部分我们等待events时,用vkCmdWaitEvents。

而在 vkCmdSetEventvkCmdWaitEvents之间的所有的commands都不受影响。

Event的API

vkCreateEvent

创建event

1
2
3
4
5
VKAPI_ATTR VkResult VKAPI_CALL vkCreateEvent(
VkDevice device,
const VkEventCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkEvent* pEvent);

创建事件参数相当简单:

1
2
3
4
5
6
7
// Now create an event and wait for it on the GPU
VkEvent event;
VkEventCreateInfo eventInfo = {};
eventInfo.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
eventInfo.pNext = NULL;
eventInfo.flags = 0;
vkCreateEvent(info.device, &eventInfo, NULL, &event);

新创建的事件的状态是unsignaled

vkDestroyEvent

销毁event

有创建就有销毁,如下所示:

1
2
3
4
VKAPI_ATTR void VKAPI_CALL vkDestroyEvent(
VkDevice device,
VkEvent event,
const VkAllocationCallbacks* pAllocator);

vkSetEvent

改变event的状态

1
vkSetEvent(device, event);

这个一般由主机调用,用来改变event的状态

vkResetEvent

重置event状态

1
vkResetEvent(device, event);

vkGetEventStatus

获取event的状态

1
2
3
VKAPI_ATTR VkResult VKAPI_CALL vkGetEventStatus(
VkDevice device,
VkEvent event);

返回值是下面两者之一:

1
2
VK_EVENT_SET = 3,    有信号状态
VK_EVENT_RESET = 4, 重置状态

vkGetEventStatus一般是主机用来获取event的状态的

vkCmdSetEvent

将event放到commandBuffer中

1
2
3
4
VKAPI_ATTR void VKAPI_CALL vkCmdSetEvent(
VkCommandBuffer commandBuffer,
VkEvent event,
VkPipelineStageFlags stageMask);

我们看到这里有一个stageMask参数,它表示event需要等待前面的commands的那个stage完成。

a stageMask parameter that marks the stages to wait for in previous commands before signalling the event

vkCmdWaitEvents

等待事件。

1
2
3
4
5
6
7
8
9
10
11
12
VKAPI_ATTR void VKAPI_CALL vkCmdWaitEvents(
VkCommandBuffer commandBuffer,
uint32_t eventCount,
const VkEvent* pEvents,
VkPipelineStageFlags srcStageMask,
VkPipelineStageFlags dstStageMask,
uint32_t memoryBarrierCount,
const VkMemoryBarrier* pMemoryBarriers,
uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier* pBufferMemoryBarriers,
uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier* pImageMemoryBarriers);

参数行为跟Barrier完全一致,这里不再重复。

这个函数主要是设备用来等待事件的。对于主机而言,它可以直接用vkGetEventStatus来获取event的状态,但是对于设备来说,就无法调用vkGetEventStatus函数了,所以用vkCmdWaitEvents来等待一个或者多个事件。

Event使用举例

假设有下面的计算管线:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Three dispatches that don’t have conflicting resource accesses

vkCmdDispatch( 1 );
vkCmdDispatch( 2 );
vkCmdDispatch( 3 );

// 4, 5, and 6 don’t share resources with 1, 2, and 3
// No reason for them to be blocked, so set an event to wait for later

vkCmdSetEvent( A, srcStageMask = COMPUTE );
vkCmdDispatch( 4 );
vkCmdDispatch( 5 );
vkCmdDispatch( 6 );

// 7 and 8 don’t use the same resources as 4, 5, and 6. So use an event
vkCmdSetEvent( B, srcStageMask = COMPUTE );

// 7 and 8 need the results of 1, 2, and 3

// So we’ll wait for them by waiting on A
vkCmdWaitEvents( A, dstStageMask = COMPUTE );
vkCmdDispatch( 7 );

vkCmdDispatch( 8 );
// 9 uses the same resources as 4, 5, and 6 so we wait.

// Also assumed is that 9 needs nothing from 7 and 8
vkCmdWaitEvents( B, dstStageMask = COMPUTE );

vkCmdDispatch( 9 );

对应如下运行图示

eventDemo

参考资料

https://www.khronos.org/blog/understanding-vulkan-synchronization

显示 Gitment 评论