DirectX12 で画面クリアまでの最小サンプル実装

DirectX12 を使っての画面クリアまでの処理について、先日の内容をより洗練させることができました。前回のものは手探り状態だったのもあって無駄な部分が紛れ込んでいました。具体的には、実は RootSignature はもっと簡単でよかったこと、 ID3D12DescriptorHeap は1つでも実装上問題なかったことです。
多くの部分で繰り返しとなりますが、改めて画面クリアまでの実装をチュートリアル的に確認していきたいと思います。
本内容は既に最新環境では正常にコンパイルできません。ご注意ください 2015/5

注意事項

現時点において DirectX12 の部分は実装途中となっています。正式版では大きく変更される可能性があります。よってここの情報は 2015/04 現在の限定された環境でのみ動作するという点をご理解ください。

なおMSDNドキュメントですら不十分な状況で手探りで動作させたため、以下の内容は間違っていることが多く含まれる可能性があります。その点にはご注意ください。


2015/04/11 従来実装と違いアロケーター1つで処理できるようにできたため該当部分だけコード例を修正しました。正しい内容は、GitHubのコードリポジトリをご参照ください。

必要なもの

  • VisualStudio 2015 CTP6
  • VisualStudio Tools for Windows10 Technical Preview
  • Windows10 Technical Preview 10041

d3d12_clear

とりあえず上記のように画面のクリア処理がうまく動くようになりました。結構手間取ったので喜びもなおさらです!

DirectX12 プログラミング開始

通常の Win32 アプリケーションを作成します。ウィンドウの作成関連は割愛して、DirectX12 部分だけ抜粋していきます。

デバイスとスワップチェインを作る

まずは ID3D12Device を作成します。以下のような感じで作成でき、 DirectX11 とそんなに変わりません。

D3D12CreateDevice(
  nullptr, 
  D3D_DRIVER_TYPE_HARDWARE, 
  D3D12_CREATE_DEVICE_DEBUG, 
  D3D_FEATURE_LEVEL_11_1, 
  D3D12_SDK_VERSION, 
  __uuidof(ID3D12Device), 
  (void**)&gpDevice
  );

スワップチェインの作成はドキュメント記載の方法ではうまくきませんでした。ドキュメントでは D3D12Device から QueryInterface で IDXGIDevice2 がとれるらしいのですが。
またスワップチェインの作成にあたり、 ID3D12CommandQueue が必要になります。適当に値を設定して作成します。スワップチェインの設定パラメーターも適当です。

IDXGIFactory2* dxgiFactory = nullptr;
CreateDXGIFactory2( 
  0, 
  __uuidof(IDXGIFactory2),
  (void**)&dxgiFactory);

D3D12_COMMAND_QUEUE_DESC descCommandQueue;
ZeroMemory(&descCommandQueue, sizeof(descCommandQueue) );
descCommandQueue.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
descCommandQueue.Priority = 0;
descCommandQueue.Flags = D3D12_COMMAND_QUEUE_NONE;
gpDevice->CreateCommandQueue( 
  &descCommandQueue, 
  __uuidof(ID3D12CommandQueue),
  (void**)&pCommandQueue);

DXGI_SWAP_CHAIN_DESC swapChainDesc;
ZeroMemory(&swapChainDesc, sizeof(swapChainDesc));
swapChainDesc.BufferCount = 1;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.OutputWindow = gHwnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
swapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

IDXGISwapChain* pSwapChain = nullptr;
dxgiFactory->CreateSwapChain( 
  pCommandQueue, 
  &swapChainDesc, 
  &pSwapChain);

ID3D12PipelineState を作成する

ここが難関でした。 D3D12_GRAPHICS_PIPELINE_STATE_DESC に値を設定して作成するのですが、設定すべき値がわからないものが多かったです。さらには設定する内容が多いのと、準備が必要だったりと手間がかかりました。

必要だったものをあげると以下のような感じでした。

  • VertexShaderのシェーダーバイナリ
  • InputLayoutの情報
  • ID3D12RootSignatureオブジェクト

ID3D12RootSignature はひとまず以下のようにして生成させてしまいます。D3D12_ROOT_SIGNATURE 構造体初期化したあとでシリアライズし、そのバイナリをデバイスに渡して生成するという流れでした。

ID3D12RootSignature* pRootSignature = nullptr;
D3D12_ROOT_SIGNATURE rootSignature = D3D12_ROOT_SIGNATURE();
rootSignature.Flags = D3D12_ROOT_SIGNATURE_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
ID3DBlob* pRootSigBlob = nullptr;
ID3DBlob* pErrorBlob = nullptr;
D3D12SerializeRootSignature(
  &rootSignature, 
  D3D_ROOT_SIGNATURE_V1, 
  &pRootSigBlob, 
  &pErrorBlob);

gpDevice->CreateRootSignature(
  1, 
  pRootSigBlob->GetBufferPointer(), 
  pRootSigBlob->GetBufferSize(), 
  __uuidof(ID3D12RootSignature), 
  (void**)&pRootSignature);

これが出来ればあとは D3D12_GRAPHICS_PIPELINE_STATE_DESC に値を設定して ID3D12PipelineState を作成出来ると思います。

コマンドリスト( ID3D12GraphicsCommandList ) を作成

描画用のコマンドリストを作成します。これには ID3D12GraphicsCommandList を用います。 ID3D12CommandList ではない点に注意です。
コマンドリストには ID3D12CommandAllocator と ID3D12DescriptorHeap が必要になるのでこれも作ります。

gpDevice->CreateCommandAllocator(
  D3D12_COMMAND_LIST_TYPE_DIRECT, 
  __uuidof(ID3D12CommandAllocator), 
  (LPVOID*)&commandAllocator);
gpDevice->CreateCommandList(
  0, 
  D3D12_COMMAND_LIST_TYPE_DIRECT, 
  commandAllocator,
  pPipelineState, 
  __uuidof(ID3D12GraphicsCommandList), 
  (void**)&pCommandList);

ID3D12DescriptorHeap は以下のようにして生成させます。

ID3D12DescriptorHeap* pDescriptorHeap = nullptr;
D3D12_DESCRIPTOR_HEAP_DESC descHeap;
ZeroMemory(&descHeap, sizeof(descHeap));
descHeap.NumDescriptors = 1;
descHeap.Type = D3D12_RTV_DESCRIPTOR_HEAP;
descHeap.Flags = D3D12_DESCRIPTOR_HEAP_NONE;
gpDevice->CreateDescriptorHeap(
  &descHeap, 
  __uuidof(ID3D12DescriptorHeap), 
  (void**)&pDescriptorHeap );

プライマリのRenderTargetViewの準備

ここは予想付く人も多いでしょう。スワップチェインから作成していきます。以下のようにして作成します。

ID3D12Resource* pRenderTarget = nullptr;
D3D12_CPU_DESCRIPTOR_HANDLE cpuDescriptor;
pSwapChain->GetBuffer(
  0, 
  __uuidof(ID3D12Resource), 
  (void**)&pRenderTarget);
cpuDescriptor = pDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
gpDevice->CreateRenderTargetView(
  pRenderTarget, 
  NULL, 
  cpuDescriptor);

画面クリアコマンドを積んで描画する

ようやく準備が出来たのでクリアのコマンドを積んで、実行させるところまでを示します。
以下のコードを見てもらうとわかるように、クリアの前にリソースの状態を変更するためのコマンドを積む必要があります。そしてクリア関連のコマンドを設定した後、Present のためにリソースの状態を変更するコマンドを積んでます。
一通りコマンドを積んだ後は、 Close して、コマンドキューにリストを投入します。このとき ExecuteCommandList が ID3D12GraphicsCommandList をそのまま受け付けないので、CommandListCast を介して渡すようにしています。これはヘッダを探っていたら発見したものです。

D3D12_RESOURCE_BARRIER_DESC descBarrier;
ZeroMemory(&descBarrier, sizeof(descBarrier));
descBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
descBarrier.Transition.pResource = pRenderTarget;
descBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
descBarrier.Transition.StateBefore = D3D12_RESOURCE_USAGE_PRESENT;
descBarrier.Transition.StateAfter = D3D12_RESOURCE_USAGE_RENDER_TARGET;
pCommandList->ResourceBarrier(1, &descBarrier);

clearColor[0] = 0.5f * sin(count*0.05f) + 0.5f;
clearColor[1] = 0.5f * sin(count*0.10f) + 0.5f;
pCommandList->RSSetViewports(1, &viewport);
pCommandList->ClearRenderTargetView(cpuDescriptor, clearColor, NULL, 0);

descBarrier.Transition.StateBefore = D3D12_RESOURCE_USAGE_RENDER_TARGET;
descBarrier.Transition.StateAfter = D3D12_RESOURCE_USAGE_PRESENT;
pCommandList->ResourceBarrier(1, &descBarrier);

pCommandList->Close();
pCommandQueue->ExecuteCommandLists(1, CommandListCast(&pCommandList));
pSwapChain->Present(1, 0);

WaitForCommandQueue( pCommandQueue );
pCommandAllocator->Reset();
pCommandList->Reset( pCommandAllocator, nullptr );

補足

一連の処理が終わった後は処理完了をイベントにて待機します。その後は各Resetを呼び出して次回に備えます。これらのコードがなく、毎フレームループを回すと、どんどんメモリが増えていく症状が見られました。

待機のコード

上記で使っている WaitForCommandQueue の実装は以下のようになります。基本的にはフェンスを挿入して、待つだけの簡単なものです。

queueFence->Signal(0);
queueFence->SetEventOnCompletion(1, hFenceEvent);
pCommandQueue->Signal(queueFence.Get(), 1);
WaitForSingleObject(hFenceEvent, INFINITE);

まとめ

前回の内容からコード量を少し減らし、よりシンプルなコードとすることが出来ました。僅かかもしれませんが、画面クリアまでの道のりが少し短くなったと思います。まだまだ描画までの距離はありそうですが、挑戦していきたいと思います。

GitHub のほうにあげていたコードも今回のプログラムで更新しました。気になる人はそちらから全貌をどうぞ。
http://github.com/techlabxe/testD3D12


2015/04/11 従来実装と違いアロケーター1つで処理できるようにできたため該当部分だけコード例を修正しました。上記記載のコードと加筆箇所との対応が完全にとれているわけではないため、正しい全貌は、GitHubのコードリポジトリをご参照ください。

本内容は既に最新環境では正常にコンパイルできません。ご注意ください 2015/5

DirectX プログラミング
すらりんをフォローする
すらりん日記

コメント

タイトルとURLをコピーしました