GPU有一個命令隊列,CPU經過Direct3D API將命令提交到隊列裏來使用命令列表(command lists),以下圖。當一套命令(a set of commands)已經被提交到命令隊列,他們不會被GPU馬上執行,理解這一點很是重要。因爲GPU極可能忙着處理以前插入的命令,因此它們會待在隊列裏直到GPU準備好處理它們。node
若是命令隊列空了,沒有任何工做可作,GPU就會處於空閒狀態;另外一方面,若是命令隊列太滿,CPU在某個時刻必須停下來等着GPU追上來。這兩種狀況都不是咱們但願看到的;對於高性能要求的應用,好比遊戲,目標是同時保持CPU和GPU的處於繁忙狀態以使得可以充分利用硬件資源的優點。數組
在Direct3D12中,命令隊列由接口ID3D12CommandQueue來表示。它是經過填充D3D12_COMMAND_QUEUE_DESC結構來描述隊列,而後調用ID3D12Device::CreateCommandQueue來建立的。在本書中,咱們經過如下方式來建立咱們的命令隊列:安全
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue; D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; ThrowIfFailed(md3dDevice->CreateCommandQueue( &queueDesc, IID_PPV_ARGS(&mCommandQueue)));
其中IID_PPV_ARGS這個幫助宏(helper macro)的定義以下函數
#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
其中__uuidof(**(ppType))求值爲(**(ppType))的COM接口ID,在上面的代碼中是ID3D12CommandQueue。IID_PPV_ARGS_Helper函數實質上將ppType強制轉換爲void **。 咱們在本書中使用了這個宏,這是由於許多Direct3D 12 API調用都要求有一個參數,即咱們正在建立的接口的COM ID,並使用void **類型。性能
這個接口中的一個主要函數是ExecuteCommandLists方法,它將命令列表(command lists)中的命令(commands)添加到到命令隊列(command queue):優化
void ID3D12CommandQueue::ExecuteCommandLists( // Number of commands lists in the array UINT Count, // Pointer to the first element in an array of command lists ID3D12CommandList *const *ppCommandLists);
命令列表(command lists)將會從ppCommandLists的第一個數組元素開始順序執行ui
正如上面的方法聲明所暗示的,圖形的命令列表(a command list for graphics)由ID3D12GraphicsCommandList接口表示,該接口繼承自ID3D12CommandList接口。 ID3D12GraphicsCommandList接口有許多方法能夠將命令添加到命令列表中。 例如,如下代碼添加了設置視口(set the viewport),清除渲染目標視圖(clear the render target view)和發出繪製調用(issue a draw call)的命令:spa
// mCommandList pointer to ID3D12CommandList mCommandList->RSSetViewports(1, &mScreenViewport); mCommandList->ClearRenderTargetView(mBackBufferView, Colors::LightSteelBlue, 0, nullptr); mCommandList->DrawIndexedInstanced(36, 1, 0, 0, 0);
這些方法的名稱暗示命令是當即執行的,但實際並非。上面的代碼只是將命令添加到命令列表中。 ExecuteCommandLists方法將命令添加到命令隊列,GPU處理來自隊列的命令。 在咱們閱讀本書的過程當中,咱們將瞭解ID3D12GraphicsCommandList支持的各類命令。 當咱們完成向命令列表添加命令時,咱們必須經過調用ID3D12GraphicsCommandList :: Close方法來代表咱們已完成命令錄製(finished recording commands)。3d
// Done recording commands. mCommandList->Close();
命令列表在被傳遞給ID3D12CommandQueue :: ExecuteCommandLists以前,必須先被關閉。指針
與命令列表相關聯的是一個名爲ID3D12CommandAllocator的內存支持類。 當命令被記錄到命令列表中時,它們實際上將存儲在相關的命令分配器(command allocator)中。 當經過ID3D12CommandQueue :: ExecuteCommandLists執行命令列表時,命令隊列將引用分配器中的命令(commands)。 從ID3D12Device建立命令分配器的代碼以下:
HRESULT ID3D12Device::CreateCommandAllocator( D3D12_COMMAND_LIST_TYPE type, REFIID riid, void **ppCommandAllocator);
參數解釋以下
1.type:可與此分配器關聯的命令列表類型。咱們在本書中使用的兩種常見類型是:
第一種:D3D12_COMMAND_LIST_TYPE_DIRECT:存儲會被GPU直接執行的命令列表(到目前爲止咱們已經描述的命令列表的類型)。
第二種:D3D12_COMMAND_LIST_TYPE_BUNDLE:指定命令列表的捆綁包。構建(building)命令列表時會產生一些CPU開銷,所以Direct3D 12提供了一種優化,容許咱們將一系列命令記錄到所謂的bundle中。記錄捆綁後,驅動程序將預處理命令以優化其在渲染過程當中的執行。所以,應在初始化時記錄捆綁。若是分析顯示構建特定命令列表須要花費大量時間,則應將bundle的使用視爲必要的優化。 Direct3D 12繪圖API已經很是高效,所以您不須要常用捆綁包,只有在您能夠經過它們取得立竿見影的性能時才應該使用它們;也就是說,默認狀況下不要使用它們。咱們在本書中不使用bundle;有關更多詳細信息,請參閱DirectX 12文檔。
2.riid:咱們要建立的ID3D12CommandAllocator接口的COM ID。
3.ppCommandAllocator:輸出的指向被建立的命令分配器的指針。
命令列表也是用ID3D12Device中的方法來建立的
HRESULT ID3D12Device::CreateCommandList( UINT nodeMask, D3D12_COMMAND_LIST_TYPE type, ID3D12CommandAllocator *pCommandAllocator, ID3D12PipelineState *pInitialState, REFIID riid, void **ppCommandList);
參數解釋以下
1.nodeMask:單GPU系統設置爲0。 不然,nodeMask 標識與該命令列表相關聯的物理GPU。 在本書中,咱們假設單GPU系統。
2.命令列表的類型:_COMMAND_LIST_TYPE_DIRECT或D3D12_COMMAND_LIST_TYPE_BUNDLE。
3.pCommandAllocator:與建立的命令列表關聯的分配器。 命令分配器類型必須與命令列表類型匹配。
4.pInitialState:指定命令列表的初始管道狀態(pipeline state)。 對於bundle而言,這能夠爲null,而且在特殊狀況下,執行命令列表以進行初始化而且不包含任何繪製命令。 咱們將在第6章討論ID3D12PipelineState。
5.riid:咱們想要建立的ID3D12CommandList接口的COM ID。
6.ppCommandList:輸出指向被建立的命令列表的指針。
你能夠建立多個命令列表,並關聯到同一個分配器上,但不能同時爲這些命令列表錄製命令。 也就是說,除了咱們將要錄製命令的列表以外,其餘命令列表必須被關閉。 所以,來自給定命令列表的全部命令將連續地添加到分配器。 請注意,建立或重置命令列表時,它處於「打開」(open)狀態。 所以,若是咱們嘗試使用相同的分配器在一行(a row)中建立兩個命令列表,咱們將收到錯誤:
D3D12 ERROR: ID3D12CommandList::{Create,Reset}CommandList: The command allocator is currently in-use by another command list.
在咱們調用了ID3D12CommandQueue :: ExecuteCommandList(C)以後,經過調用ID3D12CommandList :: Reset方法重用C的內部存儲器來記錄一組新命令是安全的。 此方法的參數與ID3D12Device :: CreateCommandList中的相應參數相同:
HRESULT ID3D12CommandList::Reset( ID3D12CommandAllocator *pAllocator, ID3D12PipelineState *pInitialState);
此方法將命令列表設置爲和剛剛建立時相同的狀態,但容許咱們重用內部內存並避免取消分配舊命令列表並分配新命令列表。 請注意,重置命令列表不會影響命令隊列中的命令,由於關聯的命令分配器仍然具備命令隊列引用的內存中的命令。
在咱們將完整幀的渲染命令提交給GPU以後,咱們但願重用命令分配器中的內存來錄製下一幀的渲染命令。 ID3D12CommandAllocator :: Reset方法可用於此目的:
HRESULT ID3D12CommandAllocator::Reset(void);
這個想法相似於調用std :: vector :: clear,它將向量的大小調整爲零,但保持當前容量相同。 可是,由於命令隊列可能在分配器中引用數據,因此在咱們肯定GPU已完成執行分配器中的全部命令以前,不得重置命令分配器。 如何執行此操做將在下一節中介紹。