1. 程式人生 > >Vulkan Cookbook 第四章 2 為緩衝區分配和繫結記憶體物件

Vulkan Cookbook 第四章 2 為緩衝區分配和繫結記憶體物件

為緩衝區分配和繫結記憶體物件

譯者注:示例程式碼點選此處

在Vulkan中緩衝區沒有自己的記憶體。為了能夠在我們的應用程式中使用緩衝區並在裡面儲存資料,需要分配一個記憶體物件並將其繫結都一個緩衝區。

怎麼做...

1.獲取建立邏輯裝置的物理裝置控制代碼。將其儲存在名為physical_device的VkPhysicalDevice型別變數中。
2.建立名為physical_device_memory_properties的VkPhysicalDeviceMemoryProperties型別變數。
3.呼叫vkGetPhysicalDeviceMemoryProperties( physical_device, &physical_device_memory_properties ),為其提供物理裝置控制代碼和指向VkPhysicalDeviceMemoryProperties變數的指標。此呼叫將儲存物理的記憶體引數(堆數,大小和型別)。
4.獲取從物理裝置建立的邏輯裝置控制代碼。將控制代碼儲存在名為logical_device的VkDevice型別變數中。
5.獲取由名為buffer的VkBuffer型別變量表示的已建立緩衝區控制代碼。
6.建立名為memory_requirements的VkMemoryRequirements型別變數。
7.獲取需要用於緩衝區的記憶體引數。通過呼叫vkGetBufferMemoryRequirements( logical_device, buffer, &memory_requirements ),並在第一個引數中提供邏輯裝置控制代碼,在第二個引數中提供建立的緩衝區控制代碼,以及第三個引數指向memory_requirements變數的指標。
8.建立一個名為memory_object的VkDeviceMemory型別變數,它將表示建立的緩衝區的記憶體物件,併為其分配VK_NULL_HANDLE值。
9.建立名為memory_properties的VkMemoryPropertyFlagBits型別變數。儲存額外選擇。
10.遍歷由physical_device_memory_properties變數的memoryTypeCount成員表示的可用物理裝置的記憶體型別。對於遍歷的每個成員請執行一下步驟:
    1.確保memory_requirements的memoryTypeBits設定了由名為type變量表示的標記。
    2.確保memory_properties變數在physical_device_memory_properties變數的memoryTypes陣列的索引型別處具有記憶體型別propertyFlags成員相同的位。
    3.如果第一點和第二點不成立,則繼續迭代迴圈。
    4.建立名為buffer_memory_allocate_info的VkMemoryAllocateInfo型別變數,併為其成員分配以下值:
      ·sType為VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO
      ·pNext為nullptr
      ·allocationSize為memory_requirements.size
      ·memoryTypeIndex為type
    5.呼叫vkAllocateMemory( logical_device, &buffer_memory_allocate_info, nullptr, &memory_object ),為此提供邏輯裝置控制代碼、指向buffer_memory_allocate_info變數的指標、nullptr值以及指向memory_object變數的指標。
    6.通過檢查呼叫返回的值是否等於VK_SUCCESS確保呼叫成功,並停止迭代迴圈。
11.通過檢查memory_object變數是否不等於VK_NULL_HANDLE,確保迴圈內的記憶體物件分配成功。
12.呼叫vkBindBufferMemory( logical_device, buffer, memory_object, 0),為其提供邏輯logical_device、buffer、memory_object和0值。
13.檢查返回值等於VK_SUCCESS,確保呼叫成功。

這個怎麼運作...

要為緩衝區(或一般的記憶體物件)分配記憶體物件,我們需要知道給定物理裝置上可用的記憶體型別,以及有多少記憶體。這是通過呼叫vkGetPhysicalDeviceMemoryProperties()函式來完成的如下所示:

VkPhysicalDeviceMemoryProperties physical_device_memory_properties; 
vkGetPhysicalDeviceMemoryProperties( physical_device, &physical_device_memory_properties );

接下來,我們需要知道給定緩衝區需要多少記憶體空間(記憶體可能需要大於緩衝區的大小)以及與之相容的記憶體型別。所有這些都存在VkMemoryRequirements型別的變數中。

VkMemoryRequirements memory_requirements;
vkGetBufferMemoryRequirements( logical_device, buffer, &memory_requirements );

接下來我們需要檢查哪個記憶體型別對應緩衝區的記憶體要求:

memory_object = VK_NULL_HANDLE;
for( uint32_t type = 0; type < physical_device_memory_properties.memoryTypeCount; ++type ) {
    if( (memory_requirements.memoryTypeBits & (1 << type)) &&
      ((physical_device_memory_properties.memoryTypes[type].propertyFlags & memory_properties) == memory_properties) ) {

        VkMemoryAllocateInfo buffer_memory_allocate_info = {
            VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,   // VkStructureType    sType
            nullptr,                                  // const void       * pNext
            memory_requirements.size,                 // VkDeviceSize       allocationSize
            type                                      // uint32_t           memoryTypeIndex
        };

        VkResult result = vkAllocateMemory( logical_device, &buffer_memory_allocate_info, nullptr, &memory_object );
        if( VK_SUCCESS == result ) {
            break;
        }
    }
}

在這裡,我們迭代所有可用的記憶體型別,並檢查給定的型別是否可以用於我們的緩衝區。我們還可以請求一些需要滿足的額外記憶體屬性。 例如如果想直接從我們的應用程式(從CPU)上傳資料,則必須支援記憶體對映在這種情況下,我們需要使用主機可見(host-visible)的記憶體型別。

譯者注:要注意的是緩衝區(buffer)是通過邏輯裝置級函式建立的,我們通過邏輯裝置再次詢問緩衝區的記憶體需求,然後要跟特定物理裝置所支援的記憶體屬性相匹配。通過上面的程式碼和解釋分析來看,該物理裝置必須是建立了該緩衝區的邏輯裝置的物理裝置。既然該物理裝置必然是建立特定邏輯裝置的那個物理裝置,我之前想可能可以直接通過邏輯裝置獲取記憶體相關的屬性,然後再和緩衝區的記憶體需求做比較,後來發現貌似並不可以,看來只能通過物理裝置獲取記憶體的相關屬性再和buffer需求進行比較,這樣做應該是為了效能考慮。因為我們不需要每次繫結緩衝區的時候都去呼叫查詢記憶體屬性,而是在獲取初始化物理裝置的時候就儲存了其記憶體屬性的相關資訊。

當我們找到合適的記憶體型別時,可以使用它來分配記憶體物件,停止迴圈之後確保正確分配記憶體,接下來將它繫結到我們的緩衝區:

if( VK_NULL_HANDLE == memory_object ) {
  std::cout << "Could not allocate memory for a buffer." << std::endl; 
  return false;
}

VkResult result = vkBindBufferMemory( logical_device, buffer, memory_object, 0 ); 
if( VK_SUCCESS != result ) {
  std::cout << "Could not bind memory object to a buffer." << std::endl;
  return false; 
}
return true;

在繫結期間,我們制定記憶體偏移量以及其他引數。這准許我們繫結不在記憶體物件開頭的記憶體的一部分。可以(並且應該)使用offset引數將記憶體物件的多個獨立部分繫結到多個緩衝區。
從現在開始,緩衝區可以在我們的應用程式中使用。

還有更多...

此節內容顯示如何將記憶體物件分配和繫結到緩衝區。但一般來說,我們不應該為每個緩衝區使用單獨的記憶體物件。應該分配更大的記憶體物件,並將它們的一部分用於多個緩衝區。

在本文中,我們還通過呼叫vkGetPhysicalDeviceMemoryProperties()函式獲取了物理裝置可用記憶體型別的引數。但總的來說,為了提高應用程式的效能,不需要每次想要分配記憶體物件時都呼叫它。在我們選擇了將用於邏輯裝置的物理裝置(請參閱第一章例項與裝置中的建立邏輯裝置),我們只需要呼叫此函式一次。