1. 程式人生 > >Vulkan Cookbook 第四章 16 使用暫存緩衝區更新裝置本地記憶體繫結的影象

Vulkan Cookbook 第四章 16 使用暫存緩衝區更新裝置本地記憶體繫結的影象

使用暫存(staging)緩衝區更新裝置本地記憶體繫結的影象

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

暫存緩衝區不僅可用於在緩衝區之間傳輸資料,還可用於在影象之間傳輸資料,在這裡,我們將展示如何對映緩衝區的記憶體並將其內容複製到所需的影象。

怎麼做...

1.建立一個足夠大的暫存緩衝區來儲存要傳輸的整個資料。為緩衝區指定VK_BUFFER_USAGE_TRANSFER_SRC_BIT用法,並將其控制代碼儲存在名為staging_buffer的VkBuffer型別變數中。分配一個支援VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT屬性的記憶體物件,並將其繫結到暫存緩衝區。將記憶體物件的控制代碼儲存在名為memory_object的VkDeviceMemory型別變數中。對映記憶體並使用要傳輸到影象的資料更新其內容。取消對映記憶體。暫存緩衝區更詳細的描述在

使用暫存緩衝區更新裝置本地記憶體繫結的緩衝區內容。
2.獲取主命令緩衝區的控制代碼並使用它來初始化名為command_buffer的VkCommandBuffer型別變數。
3.開始記錄command_buffer。提供VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT標誌(請參閱第三章緩衝區和同步中的開始命令緩衝區記錄操作內容)。
4.獲取要傳輸資料的影象控制代碼,並確保使用指定的VK_IMAGE_USAGE_TRANSFER_DST_BIT建立它。使用控制代碼初始化名為destination_image的VkImage型別變數。
5.在command_buffer中記錄影象記憶體屏障。指定到目前為止使用影象的階段,併為消費階段使用VK_PIPELINE_STAGE_TRANSFER_BIT階段。使用destination_image變數,提供影象的當前訪問型別,併為新訪問使用VK_ACCESS_TRANSFER_WRITE_BIT值。指定影象的當前佈局,併為新佈局使用VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL值。提供影象的應用方面,但是忽略佇列索引,為兩者使用VK_QUEUE_FAMILY_IGNORED值(請參閱
設定影象記憶體屏障
內容)。
6.在command_buffer中,記錄從staging_buffer到destination_image的資料傳輸操作,提供VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL值作為影象佈局,緩衝區偏移量0,緩衝區行長度為0,緩衝區影象高度為0。通過提供所需的mipmap級別,基本陣列層索引和要更新的層數,指定要複製資料的影象的記憶體區域。提供影象的應用方面。指定影象的x、y和z座標(以紋素為單位)和影象大小的偏移量(請參閱將將資料從緩衝區複製到影象內容)。
7.在command_buffer中記錄另一個影象記憶體屏障。這次指定VK_PIPELINE_STAGE_TRANSFER_BIT值給生產階段並設定資料傳輸之後將使用目標影象的適當階段。在屏障中,將影象的佈局從VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL更改為適合新用法的值。為兩個佇列族設定VK_QUEUE_FAMILY_IGNORED值,並提供影象的應用方面(請參閱
置影象記憶體屏障
內容)。
8.結束命令緩衝區記錄操作,建立一個未發出訊號的圍欄並在將命令緩衝區提交到佇列期間使用它以及應該發訊號的訊號量。等待建立的fence訊號,銷燬暫存緩衝區,並釋放其記憶體物件,如使用暫存緩衝區更新裝置本地記憶體繫結的緩衝區中所述。

這個怎麼運作...

本節內容非常類似於使用暫存緩衝區更新裝置本地記憶體繫結的緩衝區,這就是為什麼只更詳細的描述差異的原因。

首先,我們建立一個暫存緩衝區,為其分配一個記憶體物件,將其繫結在緩衝區,並將其對映到要從應用程式上傳到GPU的資料上。

VkBuffer staging_buffer;
if( !CreateBuffer( logical_device, data_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, *staging_buffer ) ) {
  return false;
}

VkDeviceMemory memory_object;
if( !AllocateAndBindMemoryObjectToBuffer( physical_device, logical_device, *staging_buffer, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, *memory_object ) ) {
  return false;
}

if( !MapUpdateAndUnmapHostVisibleMemory( logical_device, *memory_object, 0, data_size, data, true, nullptr ) ) {
  return false;
}

接下來,我們開始命令緩衝區記錄,併為目標影象設定一個屏障,以便它可以用作資料資料的目標。我們還記錄資料傳輸操作:

if( !BeginCommandBufferRecordingOperation( command_buffer, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, nullptr ) ) {
  return false;
}

SetImageMemoryBarrier( command_buffer, destination_image_generating_stages, VK_PIPELINE_STAGE_TRANSFER_BIT,
{
  {
    destination_image,                        // VkImage            Image
    destination_image_current_access,         // VkAccessFlags      CurrentAccess
    VK_ACCESS_TRANSFER_WRITE_BIT,             // VkAccessFlags      NewAccess
    destination_image_current_layout,         // VkImageLayout      CurrentLayout
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,     // VkImageLayout      NewLayout
    VK_QUEUE_FAMILY_IGNORED,                  // uint32_t           CurrentQueueFamily
    VK_QUEUE_FAMILY_IGNORED,                  // uint32_t           NewQueueFamily
    destination_image_aspect                  // VkImageAspectFlags Aspect
} } );

CopyDataFromBufferToImage( command_buffer, *staging_buffer, destination_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
{
  {
    0,                                        // VkDeviceSize               bufferOffset
    0,                                        // uint32_t                   bufferRowLength
    0,                                        // uint32_t                   bufferImageHeight
    destination_image_subresource,            // VkImageSubresourceLayers   imageSubresource
    destination_image_offset,                 // VkOffset3D                 imageOffset
    destination_image_size,                   // VkExtent3D                 imageExtent
} } );

接下來,我們記錄另一個屏障,將影象的使用從複製操作的目標更改為接下來將使用影象的目的的屏障。我們還結束了命令緩衝區記錄操作:

SetImageMemoryBarrier( command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, destination_image_consuming_stages,
{
  {
    destination_image,                        // VkImage            Image
    VK_ACCESS_TRANSFER_WRITE_BIT,             // VkAccessFlags      CurrentAccess
    destination_image_new_access,             // VkAccessFlags      NewAccess
    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,     // VkImageLayout      CurrentLayout
    destination_image_new_layout,             // VkImageLayout      NewLayout
    VK_QUEUE_FAMILY_IGNORED,                  // uint32_t           CurrentQueueFamily
    VK_QUEUE_FAMILY_IGNORED,                  // uint32_t           NewQueueFamily
    destination_image_aspect                  // VkImageAspectFlags Aspect
} } );

if( !EndCommandBufferRecordingOperation( command_buffer ) ) {
  return false;
}

之後,我們建立一個圍欄(fence)並向佇列提交一個命令緩衝區。然後我們等待圍欄通知我們可以安全地刪除緩衝區以及記憶體物件的那一刻:

VkFence fence;
if( !CreateFence( logical_device, false, fence ) ) {
  return false; 
}

if( !SubmitCommandBuffersToQueue( queue, {}, { command_buffer }, signal_semaphores, fence ) ) { 
  return false;
}

if( !WaitForFences( logical_device, { fence }, VK_FALSE, 500000000 ) ) { 
  return false;
}

DestroyBuffer( logical_device, staging_buffer ); 
FreeMemoryObject( logical_device, memory_object );

return true;

如果將現有緩衝區重用為暫存資源,我們不需要圍欄,因為緩衝區將存活更長時間,可能在應用程式的整個生命週期中。這樣我們可以避免頻繁和不必要的建立和刪除,以及記憶體物件的分配和釋放。