2019年11月11日 星期一

關於 ID3D12GraphicsCommandList::SetDescriptorHeaps()

關於 ID3D12GraphicsCommandList::SetDescriptorHeaps()

前言

  最近在研究 Direct3D12 ,手邊的資料有 Direct3D12 的範例與 [ 書籍 ] DirectX 12 3D 游戲開發實戰,出版商:人民郵電出版社,ISBN-13:9787115479211 ,本以為就入門來說應該很充足,但在實務上會碰到一些不知道在做什麼的 Function ,就像是 ID3D12GraphicsCommandList::SetDescriptorHeaps() ,這個 Function 不論是 [ MSDN ]ID3D12GraphicsCommandList::SetDescriptorHeaps method 或是手邊的資料都沒有說得很清楚,都使是在範例裡有寫這個步驟,但都不特別解釋,所以我就對這個 Funciton 做了研究,在此做個紀錄。

內容

  ID3D12GraphicsCommandList::SetDescriptorHeaps() 這個 Function 在介面上有些設計不良,
介面如下
void SetDescriptorHeaps(
  UINT                 NumDescriptorHeaps,
  ID3D12DescriptorHeap * const *ppDescriptorHeaps
);

介面看起來會讓人很直覺得可以輸入很多個 ID3D12DescriptorHeap ,但是事實上是輸入的 DescriptorHeap 是有限制的!每個 ID3D12DescriptorHeap 在 Create 的時候會需要 D3D12_DESCRIPTOR_HEAP_TYPE,定義如下
typedef enum D3D12_DESCRIPTOR_HEAP_TYPE
{
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,    // Constant buffer/Shader resource/Unordered access views
    D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,        // Samplers
    D3D12_DESCRIPTOR_HEAP_TYPE_RTV,            // Render target view
    D3D12_DESCRIPTOR_HEAP_TYPE_DSV,            // Depth stencil view
    D3D12_DESCRIPTOR_HEAP_TYPE_NUM_TYPES       // Simply the number of descriptor heap types
} D3D12_DESCRIPTOR_HEAP_TYPE;

這個 Type 會決定 DescriptorHeap 的型態, Create 完後不能更改,而在 ID3D12GraphicsCommandList::SetDescriptorHeaps() 輸入的每個 DescriptorHeap 必須是不同的  D3D12_DESCRIPTOR_HEAP_TYPE ,加上這個 Function 其實只處理"輸入"的部分,所以D3D12_DESCRIPTOR_HEAP_TYPE_RTV 與 D3D12_DESCRIPTOR_HEAP_TYPE_DSV 就不會用到,所以實際上最多只能輸入兩個 DescriptorHeap !這些在 MSDN 上都沒有很詳述的說明,我是在輸入同一種 Type 後會出現 Debug 的錯誤訊息才知道這個 Fuction 不能輸入不同 Type 的 DescriptorHeap 。個人認為理想的介面應該如下
void SetDescriptorHeaps(
  ID3D12DescriptorHeap * pDescriptorHeapsType_CBV_SRV_UAV;
  ID3D12DescriptorHeap * pDescriptorHeapsType_SAMPLER;
);

  接著來說一下喚起的時機,如下圖
SetDescriptorHeaps() 的喚起時機

如中上方的部分說明只能接收兩種 DescriptorHeap ,接著下方的 Draw call 是指每次的繪圖會透過 SetGraphicsRootDescriptorTable() 時要注意 GPUDescriptorHandle 必須來自 SetDescriptorHeaps() 的 DescriptorHeap ,手邊的資料也都沒明說這樣的規則,這是經過多次的實驗得來的經驗。如果需要切換 DescriptorHeap 的話,要注意切換的時機要發生在 Draw call 與 Draw call 之間,不能發生在 Draw call 的裡面,舉個例來說,如果一個 Draw call 裡需要做兩次 SetGraphicsRootDescriptorTable() ,但期望來自兩個 DescriptorHeap( D3D12_DESCRIPTOR_HEAP_TYPE 是一樣的),這個時候發生需要在同一個 Draw call 裡喚起兩次 SetDescriptorHeaps(),如以下範例
ComPtr<ID3D12DescriptorHeap> m_myHeap1;
ComPtr<ID3D12DescriptorHeap> m_myHeap2;
//Init heap1 and heap2
//...
//
ID3D12DescriptorHeap* ppHeaps[] = { m_myHeap1.Get()};
m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_commandList->SetGraphicsRootDescriptorTable(0,m_myHeap1->GetGPUDescriptorHandleForHeapStart() );
ppHeaps[0] = m_myHeap2.Get();
m_commandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
m_commandList->SetGraphicsRootDescriptorTable(1,m_myHeap2->GetGPUDescriptorHandleForHeapStart() );
m_commandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_commandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
m_commandList->DrawInstanced(3, 1, 0, 0);

這個做法目前不確定是否為"正規"用法,因為這個用法在 NVIDIA GeForce GTX1050 的顯卡時可以正常工作,但在 Intel HD Graphics 630 時無法正常工作,所以不確定是否可以這樣用,
說明文件也沒說明是否可這樣用,這部分目前的解決方案是絕不在 Draw call 中切換 DescriptorHeap ,這樣可以取得較好的兼容性。

參考資料

[ MSDN ]ID3D12GraphicsCommandList::SetDescriptorHeaps method
[ 書籍 ] DirectX 12 3D 游戲開發實戰,出版商:人民郵電出版社,ISBN-13:9787115479211

沒有留言:

張貼留言