你將在本章中學到:
l Vulkan如何管理主機和設備內存
l 在應用程序中如何有效地管理內存
l Vulkan如何使用images和buffers消費和生產數據
memory是是幾乎所有計算機系統做任何操作的基礎,也包括Vulkan。在Vulkan里,memory基本上有兩種類型:主機memory和設備memory。Vulkan操作的所有資源必須被設備內存支持,應用程序需要負責管理內存。此外,內存也被用作主機上存儲數據。Vulkan提供了讓應用程序管理內存的機會。在本章中,您將學到Vulkan用來管理內存的各種機制。
主機內存管理
每當Vulkan創建新對象時,它可能需要內存來存儲與它們相關的數據。為此,它使用主機內存,可以被CPU訪問,是通過malloc或者new調用返回的通常意義的內存。然而,除了正常的分配器,Vulkan有一些特殊的內存分配需求。其中最常見的,是它希望分配的內存被對齊。這是因為一些高性能CPU指令在對齊的內存上工作的最好。若存儲CPU端的數據被對齊,Vulkan可以無條件的使用這些高性能指令,提供實質性能優勢。
由於上述要求,Vulkan實現將使用高級分配器。但是,為了某些,甚至全部操作,它還為您的應用程序提供了替換默認分配器的機會。這是通過指定多數的設備創建函數的pAllocator參數來實現的。例如,讓我們重新看一遍vkCreateInstance() 函數,它可能是你的應用程序第一個調用的函數。原型如下:
VkResult vkCreateInstance (
const VkInstanceCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkInstance* pInstance);
pAllocator參數是一個指向VkAllocationCallbacks類型數據的指針。直到目前,我們一直設置pAllocator為nullptr,這告訴Vulkan去使用它內部提哦那個的默認內存分配器,而不是應用程序提供的內存分配器。VkAllocationCallbacks書籍結構封裝了我們提供的自定義內存分配器。這個數據結構定義如下:
typedef struct VkAllocationCallbacks {
void* pUserData;
PFN_vkAllocationFunction pfnAllocation;
PFN_vkReallocationFunction pfnReallocation;
PFN_vkFreeFunction pfnFree;
PFN_vkInternalAllocationNotification pfnInternalAllocation;
PFN_vkInternalFreeNotification pfnInternalFree;
} VkAllocationCallbacks;
你可以通過VkAllocationCallbacks的定義知道,它基本上是一些函數指針的集合和一個void* pUserData。這個指針可以供應用程序使用。它可以指向任何位置。Vulkan不會dereference它。事實上,它甚至不需要是一個指針。你可以放任何東西在哪兒,只要它放入一個指針占用的內存區。Vulkan對pUserData做的唯一的事情,就是將它傳遞到包含VkAllocationCallback指針的回調函數。
pfnAllocation,pfnReallocation和pfnFree用於通常的、對象級的內存管理。它們被定義為指向與以下聲明匹配的函數的指針:
void* VKAPI_CALL Allocation(
void* pUserData,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
void* VKAPI_CALL Reallocation(
void* pUserData,
void* pOriginal
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
void VKAPI_CALL Free(
void* pUserData,
void* pMemory);
注意,這三個函數用一個pUserData作為第一個參數,這是和VkAllocationCallbacks數據結構的pUserData是同一東西。如果你的應用程序使用數據結構來管理內存,這是放置他們的地址的好地方。用一個C ++類實現你的內存分配器(假設你在使用C++),並且把這個類的this指針放到pUserData,是一個合理的方式。
Allocation函數負責新的內存分配。size參數指定了分配多少byte。Alignment參數指定了安幾個byte進行要求的內存對齊,這是一個經常被忽視的參數。非常容易和原生的內存分配器malloc掛鈎聯系起來。如果這么做,你將會發現程序會正常運行一段時間,但是在某個函數中神奇的崩潰。如果你提供自己的allocator,,你需要重視alignment參數。
最后一個參數allocationScope,,告訴應用程序,內存分配的范圍,生命周期是什么樣的。它是VkSystemAllocationScope值中的某一個,
l VK_SYSTEM_ALLOCATION_SCOPE_COMMAND 意味着分配的內存將只生存於調用Allocation的demand中。因為只在一個command內生存,Vulkan有可能使用它做作為臨時的內存分配。
l VK_SYSTEM_ALLOCATION_SCOPE_OBJECT 內存分配和一個特定的Vulkan對象關聯。在對象被銷毀之前,分配的內存一直存在。這種類型的內存分配只發生在command創建(所有以vkCreate開頭的函數)期間。
l VK_SYSTEM_ALLOCATION_SCOPE_CACHE 意味着分配的內存和內部緩存或者VkPipelineCache對象關聯。
l VK_SYSTEM_ALLOCATION_SCOPE_DEVICE 意味着分配的內存在整個devcie中都有效。當Vulkan需要和GPU關聯的內存,而不是和一個對象關聯。比如,該內存分配器在某些blocks中分配內存,有很多對象或許都在這個block內,那么,分配的內存就不能和任何特定對象直接關聯。
l VK_SYSTEM_ALLOCATION_SCOPE_INSTANCE 意味着分配的內存在一個instance內有效。這個與VK_SYSTEM_ALLOCATION_SCOPE_DEVICE.相似。這種類型的內存分配通常是layer或者Vulkan啟動時做的,比如vkCreateInstance()、vkEnumeratePhysicalDevices()。
pfnInternalAllocation和pfnInternalFree函數指針指向Vulkan使用自帶分配器時的 備用的函數。他們和pfnAllocation、pfnInternalFree的函數簽名相同,除了pfnInternalAllocation不返回值,且pfnInternalFree不應該真的釋放內存。這些函數僅僅用來通知應用程序管理好Vulkan在使用的內存。這些函數原型如下:
void VKAPI_CALL InternalAllocationNotification(
void* pUserData,
size_t size,
VkInternalAllocationType allocationType,
VkSystemAllocationScope allocationScope);
void VKAPI_CALL InternalFreeNotification(
void* pUserData,
size_t size,
VkInternalAllocationType allocationType,
VkSystemAllocationScope allocationScope);
對於pfnInternalAllocation和pfnInternalFree提供的信息,你並不能做什么,除了做日志和跟蹤應用程序的內存使用量。這些函數指針是可選的,但如果你指定了一個,另外一個也需要指定。如果你不想用,把他們都設置為nullptr即可。
Listing2.1 展示了一個如何寫C++ class作為和Vulkan內存分配回調函數匹配的allocator的例子。因為這些回調函數被Vulkan通過C函數指針調用,所以這些回調函數被聲明為該class靜態成員函數,然而,真的實現函數被聲明為非靜態成員函數。
class allocator
{
public:
// Operator that allows an instance of this class to be used as a
// VkAllocationCallbacks structure
inline operator VkAllocationCallbacks() const
{
VkAllocationCallbacks result;
result.pUserData = (void*)this;
result.pfnAllocation = &Allocation;
result.pfnReallocation = &Reallocation;
result.pfnFree = &Free;
result.pfnInternalAllocation = nullptr;
result.pfnInternalFree = nullptr;
return result;
};
private:
// Declare the allocator callbacks as static member functions.
static void* VKAPI_CALL Allocation(
void* pUserData,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
static void* VKAPI_CALL Reallocation(
void* pUserData,
void* pOriginal,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
static void VKAPI_CALL Free(
void* pUserData,
void* pMemory);
// Now declare the nonstatic member functions that will actually
perform
// the allocations.
void* Allocation(
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
void* Reallocation(
void* pOriginal,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope);
void Free(
void* pMemory);
};
在Listing2.2 中展示了該類的實現。它把Vulkan的內存分配函數映射到符合POSIX標准的aligned_malloc函數。注意,這個allocator幾乎不會比Vulkan內部大多的默認分配器好,這只是作為一個用自己的代碼寫回調函數的例子。
void* allocator::Allocation(
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope)
{
return aligned_malloc(size, alignment);
}
void* VKAPI_CALL allocator::Allocation(
void* pUserData,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope)
{
return static_cast<allocator*>(pUserData)->Allocation(size,
alignment,
allocationScope);
}
void* allocator::Reallocation(
void* pOriginal,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope)
{
return aligned_realloc(pOriginal, size, alignment);
}
void* VKAPI_CALL allocator::Reallocation(
void* pUserData,
void* pOriginal,
size_t size,
size_t alignment,
VkSystemAllocationScope allocationScope)
{
return static_cast<allocator*>(pUserData)->Reallocation(pOriginal,
size,
alignment,
allocationScope);
}
void allocator::Free(
void* pMemory)
{
aligned_free(pMemory);
}
void VKAPI_CALL allocator::Free(
void* pUserData,
void* pMemory)
{
return static_cast<allocator*>(pUserData)->Free(pMemory);
}
在Listing2.2中我們可以看到,靜態成員函數內部,可以簡單的把pUserData參數靜態類型轉換回該類的一個實例對象,並調用非靜態成員函數。因為非靜態和靜態函數在同一個編譯單元內,非靜態函數很有可能被內聯了,以致這種實現是很高效的。