1. 程式人生 > >Vulkan Cookbook 第四章 11 對映、更新和取消對映主機可見記憶體

Vulkan Cookbook 第四章 11 對映、更新和取消對映主機可見記憶體

對映、更新和取消對映主機可見記憶體

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

對於渲染期間使用的影象和緩衝區,建議繫結位於影象硬體(裝置本地記憶體)上的記憶體。這會產生最好的表現。但我們不能直接訪問這樣的記憶體,需要使用中間(暫存)資源來調節GPU(裝置)和CPU(主機)之間的資料傳輸。

另一方面,暫存資源需要使用主機可見的記憶體。要將資料上傳到此類記憶體或從中讀取資料,我們需要對其進行對映。

怎麼做...

1.獲取已建立的邏輯裝置控制代碼並將其儲存在名為logical_device的VkDevice型別變數中。
2.選擇在具有VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT屬性的記憶體型別上分配的記憶體物件。將記憶體物件的控制代碼儲存在名為memory_object的VkDeviceMemory型別變數中。
3.選擇應對映和更新的記憶體區域。將記憶體物件的記憶體開頭的偏移量(以位元組為單位)儲存在名為offset的VkDeviceSize型別變數中。
4.選擇要複製到記憶體物件的選定區域的資料大小。使用名為data_size的VkDeviceSize型別變量表示資料大小。
5.準備應該複製到記憶體物件的資料。並使用它來初始名為data的void*型別的變數。
6.建立一個名為pointer的void *型別型變數。它將包含指向對映記憶體範圍的指標。
7.呼叫vkMapMemory( logical_device, memory_object, offset, data_size, 0, &local_pointer )進行記憶體對映,提供邏輯裝置控制代碼和記憶體物件的控制代碼,偏離記憶體的起始點和我們想要對映的區域的大小(以位元組為單位),0值和指向local_pointer變數的指標。
8.檢查返回的值是否等於VK_SUCCESS來確保呼叫成功。
9.將準備好的資料複製到指標變數指向的記憶體中。它可以通過以下呼叫完成:std::memcpy( local_pointer, data, data_size )。
10.建立一個名為memory_ranges的std::vector<VkMappedMemoryRange>型別變數。向每個元素初始化一下值:
    ·sType為VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE
    ·pNext為nullptr
    ·memory為memory_object
    ·offset為每個範圍的偏移量
    ·size為每個範圍的尺寸
11.通知驅動程式記憶體的哪些部分已更改。通過呼叫vkFlushMappedMemoryRanges( logical_device, static_cast<uint32_t>(memory_ranges.size()), &memory_ranges[0] )來完成此操作,為其提供logical_device變數、要更改記憶體的範圍數量(memory_ranges向量中的元素數量)和指向memory_ranges向量的第一個元素的指標。
12.確保呼叫返回VK_SUCCESS值重新整理成功。
13.要取消對映記憶體,請呼叫vkUnmapMemory( logical_device, memory_object )。

這個怎麼運作...

對映記憶體是將資料上載到Vulkan資源的最簡單方法。在對映期間我們指定應該對映哪個記憶體部分(從記憶體物件的開頭偏移和對映範圍的大小):

VkResult result;
void * local_pointer;
result = vkMapMemory( logical_device, memory_object, offset, data_size, 0, &local_pointer ); 
if( VK_SUCCESS != result ) {
  std::cout << "Could not map memory object." << std::endl;
  return false; 
}

對映為我們提供了指向所請求的記憶體部分的指標。我們可以使用這個指標,就像在典型的C++應用程式中使用的其他指標一樣。是否從這樣的記憶體寫入或讀取資料沒有限制。下面將資料從應用程式賦值到記憶體物件:

std::memcpy( local_pointer, data, data_size );

當我們更新對映的記憶體範圍時,需要通知驅動程式記憶體內容已被修改,上傳的資料可能不會立即對提交給佇列的其他操作可見。通知CPU(主機)執行的記憶體資料修改稱為重新整理。為此我們準備了一個更新的記憶體範圍列表,不需要覆蓋整個對映記憶體:


std::vector<VkMappedMemoryRange> memory_ranges = { 
  {
  VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 
  nullptr,
  memory_object,
  offset,
  data_size
  } 
};

vkFlushMappedMemoryRanges( logical_device, static_cast<uint32_t>(memory_ranges.size()), &memory_ranges[0] ); 
if( VK_SUCCESS != result ) {
  std::cout << "Could not flush mapped memory." << std::endl;
  return false; 
}

當我們完成處理對映記憶體之後,可以取消對映它。記憶體對映不應該影響我們應用程式的效能,可以在我們的應用程式的整個生命週期中保留獲取的指標。但是我們應該在關閉應用程式並銷燬所有資源之釋放它(unmap):

if( unmap ) {
  vkUnmapMemory( logical_device, memory_object );
} else if( nullptr != pointer ) { 
  *pointer = local_pointer;
}

return true;