標籤:

vulkan中的同步和緩存控制

Introduction

這部分內容主要是vulkan的spec的第六章部分的一些閱讀筆記。

對於同一個資源的訪問之間的同步是AP應負責的內容之一,vulkan中一共提供了如下四種同步機制:

  1. Fence
  2. Semaphore
  3. Event
  4. Barrier

用以同步host/device之間,queues之間,queue submissions之間,以及一個單獨的command buffer的commands之間的同步。

Fence

首先我們介紹最簡單的Fence。一句話總結,Fence提供了一種粗粒度的,從Device向Host單向傳遞信息的機制。Host可以使用Fence來查詢通過vkQueueSubmit/vkQueueBindSparse所提交的操作是否完成。簡言之,在vkQueueSubmit/vkQueueBindSparse的時候,可以附加帶上一個Fence對象。之後就可以使用這個對象來查詢之前提交的狀態了。例如:

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

的原型中,最後一個參數可以是一個有效的fence對象,當然,也可以指定為VK_NULL_HANDLE,標明不需要Fence。有趣的是,在vkQueueSubmit的時候,如果給定一個有效的fence對象,但是不提交任何信息,即submitCount為0,那麼同樣也可以算作一次成功的提交,等待之前所有提交到queue的任務都完成後,這個fence也就signaled了。這種使用方式提供了一種機制,可以讓我們查詢一個queue現在到底忙不忙。

Fence本身只有兩種狀態,unsignaled或者signaled,大致可以認為fence是觸發態還是未觸發態。當使用vkCreateFence創建fence對象的時候,如果在標誌位上填充了VkFenceCreateFlagBits(3)的VK_FENCE_CREATE_SIGNALED_BIT,那麼創建出來的fence就是signaled狀態,否則都是unsignaled狀態的。銷毀一個fence對象需要使用vkDestroyFence(3)。

伴隨著vkQueueSubmit/vkQueueBindSparse一起提交的fence對象,可以使用vkGetFenceStatus(3)來查詢fence的狀態。注意vkGetFenceStatus是非阻塞的,如果fence處於signaled狀態,這個API返回VK_SUCCESS,否則,立即返回VK_NOT_READY。

當然,fence被觸發到signaled狀態,必須存在一種方法,將之轉回到unsignaled狀態,這個功能由vkResetFences(3)完成,這個API一次可以將多個fence對象轉到unsignaled狀態。這個API結合VK_FENCE_CREATE_SIGNALED_BIT位,可以達到一種類似於C中do {} while;的效果,即loop的代碼有著一致的表現:loop開始之前,所有的fence都創建位signaled狀態,每次loop開始的時候,所用到的fence都由這個API轉到unsignaled狀態,伴隨著submit提交過去。

等待一個fence,除了使用vkGetFenceStatus輪詢之外,還有一個API vkWaitForFences(3)提供了阻塞式地查詢方法。這個API可以等待一組fence對象,直到其中至少一個,或者所有的fence都處於signaled狀態,或者超時(時間限制由參數給出),才會返回。如果超時的時間設置為0,則這個API簡單地看一下是否滿足前兩個條件,然後根據情況選擇返回VK_SUCCESS,或者(雖然沒有任何等待)VK_TIMEOUT。

簡而言之,對於一個fence對象,Device會將其從unsignaled轉到signaled狀態,告訴Host一些工作已經完成。所以fence使用在Host/Device之間的,且是一種比較粗粒度的同步機制。

Semaphore

VkSemaphore(3)用以同步不同的queue之間,或者同一個queue不同的submission之間的執行順序。類似於fence,semaphore也有signaled和unsignaled的狀態之分。然而由於在queue之間或者內部做同步都是device自己控制,所以一個semaphore的初始狀態也就不重要了。所以,vkCreateSemaphore(3)就簡單地不用任何額外參數創建一個semaphore對象,然後vkDestroySemaphore(3)可以用來銷毀一個semaphore對象。不同於fence,沒有重置或者等待semaphore的api,因為semaphore只對device有效。

在device上使用semaphore的最典型的場景,就是通過vkQueueSubmit提交command buffer時候,所需要的參數VkSubmitInfo(3)中的控制,

typedef struct VkSubmitInfo {n VkStructureType sType;n const void* pNext;n uint32_t waitSemaphoreCount;n const VkSemaphore* pWaitSemaphores;n const VkPipelineStageFlags* pWaitDstStageMask;n uint32_t commandBufferCount;n const VkCommandBuffer* pCommandBuffers;n uint32_t signalSemaphoreCount;n const VkSemaphore* pSignalSemaphores;n} VkSubmitInfo;n

可以看出,在VkSubmitInfo(3),實際上存在三組array結構,他們分別是

  • 由waitSemaphoreCount決定長度,pWaitSemaphores決定起始地址的wait semaphore數組
  • 由waitSemaphoreCount決定長度,pWaitDstStageMask決定起始地址的semaphore等待階段的數組

  • 由signalSemaphoreCount決定長度,pSignalSemaphores決定起始地址的signal semaphore數組

通過VkSubmitInfo(3)中的這三組數組,可以達到如下效果:所提交的command buffer將在執行到每個semaphore等待階段時候,檢查並等待每個對應的wait semaphore數組中的semaphore是否被signal, 且等到command buffer執行完畢以後,將所有signal semaphore數組中的semaphore都signal起來。

VkSubmitInfo(3)通過這種方式,實際上提供了一種非常靈活的同步queue之間或者queue內部不同command buffer之間的方法,通過組合使用semaphore,AP可以顯式地致命不同command buffer之間的資源依賴關係,從而可以讓driver在遵守這個依賴關係的前提下,最大程度地並行化,以提高GPU的利用效率。

類似於Fence, Semaphore也是一種較粗粒度的同步機制。在大多數時候這兩個同步機制已經足夠使用了。然而,如果需要在更細的粒度上控制同步,就需要使用接下來介紹的Event了。

未完待續

推薦閱讀:

Dota 2 現已支持 Vulkan API,使用起來究竟怎麼樣?
為什麼部分 PC 遊戲開發者更喜歡 DirectX 而非 OpenGL ?

TAG:Vulkan |