以前は VS2015 CTP6 と Windows10 10041 で画面クリアのサンプルを作りましたが、既に今現在はそのコードはビルド不可能な状態になってしまいましたので、再度作り直すことにしました。本当に色々とあってようやく画面クリアまで出来るようになりました。
注意事項
現時点において DirectX12 の部分は実装途中となっています。正式版では大きく変更される可能性があります。よってここの情報は 2015/05 現在の限定された環境でのみ動作するという点をご理解ください。
必要なもの
- VisualStudio 2015 RC
- Windows10 Insider Preview 10074
- AMD RADEON の新しいドライバ(15.200.1023.0)
ここで新しいドライバというのが大事です。つい最近Windows Update で提供されました。それまではドライバの不具合で DirectX12 の初期化が正常に行えませんでした。
DirectX12 プログラミング
通常の Win32 アプリケーションを作成します。ウィンドウの作成関連は割愛して、DirectX12 部分だけ抜粋していきます。
初期化処理
まずはコードを先に示します。画面クリアするのに必要になるものだけを生成していますが、コード量は多めです。
BOOL InitializeD3D12(HWND hWnd)
{
UINT flagsDXGI = 0;
ID3D12Debug* debug = nullptr;
HRESULT hr;
#if _DEBUG
D3D12GetDebugInterface(IID_PPV_ARGS(&debug));
if (debug) {
debug->EnableDebugLayer();
debug->Release();
}
flagsDXGI |= DXGI_CREATE_FACTORY_DEBUG;
#endif
hr = CreateDXGIFactory2(flagsDXGI, IID_PPV_ARGS(dxgiFactory.ReleaseAndGetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateDXGIFactory2\n");
return FALSE;
}
ComPtr adapter;
hr = dxgiFactory->EnumAdapters(0, adapter.GetAddressOf());
if (FAILED(hr)) {
return FALSE;
}
// デバイス生成.
// D3D12は 最低でも D3D_FEATURE_LEVEL_11_0 を要求するようだ.
hr = D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(dxDevice.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed D3D12CreateDevice\n");
return FALSE;
}
// コマンドアロケータを生成.
hr = dxDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(cmdAllocator.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateCommandAllocator\n");
return FALSE;
}
// コマンドキューを生成.
D3D12_COMMAND_QUEUE_DESC descCommandQueue;
ZeroMemory(&descCommandQueue, sizeof(descCommandQueue));
descCommandQueue.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
descCommandQueue.Priority = 0;
descCommandQueue.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
hr = dxDevice->CreateCommandQueue(&descCommandQueue, IID_PPV_ARGS(cmdQueue.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateCommandQueue\n");
return FALSE;
}
// コマンドキュー用のフェンスを準備.
hFenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
hr = dxDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(queueFence.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateFence\n");
return FALSE;
}
// スワップチェインを生成.
DXGI_SWAP_CHAIN_DESC descSwapChain;
ZeroMemory(&descSwapChain, sizeof(descSwapChain));
descSwapChain.BufferCount = 2;
descSwapChain.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
descSwapChain.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
descSwapChain.OutputWindow = gHwnd;
descSwapChain.SampleDesc.Count = 1;
descSwapChain.Windowed = TRUE;
descSwapChain.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
descSwapChain.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
hr = dxgiFactory->CreateSwapChain(cmdQueue.Get(), &descSwapChain, (IDXGISwapChain**)swapChain.GetAddressOf());
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateSwapChain\n");
return FALSE;
}
// コマンドリストの作成.
hr = dxDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAllocator.Get(), nullptr, IID_PPV_ARGS(cmdList.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateCommandList\n");
return FALSE;
}
// ディスクリプタヒープ(RenderTarget用)の作成.
D3D12_DESCRIPTOR_HEAP_DESC descHeap;
ZeroMemory(&descHeap, sizeof(descHeap));
descHeap.NumDescriptors = 2;
descHeap.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
descHeap.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = dxDevice->CreateDescriptorHeap(&descHeap, IID_PPV_ARGS(descriptorHeapRTV.GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed CreateDescriptorHeap\n");
return FALSE;
}
// レンダーターゲット(プライマリ用)の作成.
UINT strideHandleBytes = dxDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
for (UINT i = 0;i < descSwapChain.BufferCount;++i) {
hr = swapChain->GetBuffer(i, IID_PPV_ARGS(renderTarget[i].GetAddressOf()));
if (FAILED(hr)) {
OutputDebugString(L"Failed swapChain->GetBuffer\n");
return FALSE;
}
handleRTV[i] = descriptorHeapRTV->GetCPUDescriptorHandleForHeapStart();
handleRTV[i].ptr += i*strideHandleBytes;
dxDevice->CreateRenderTargetView(renderTarget[i].Get(), nullptr, handleRTV[i]);
}
return TRUE;
}
このコードについて簡単に説明していきます。
最初にデバッグ情報を多く出してくれるようにするための設定をしています。DXGIにも DXGI_CREATE_FACTORY_DEBUG フラグを渡していますし、 D3D12 もデバッグ情報を出してくれるようにするために D3D12GetDebugInterface でインターフェースを取得して有効化しています。従来は CreateDevice でフラグを渡す、でしたが今回から変わったようです。
また、本来ならばアダプタを列挙して処理が必要になるのかもしれませんが、ここでは最初のアダプタに対して D3D12CreateDevice をするようにしています。コメントにあるように DirectX12 のデバイスは最低でも D3D_FEATURE_LEVEL_11_0 の機能レベルを要求するようです。
その後、コマンドアロケーター、コマンドキューを作成したあと、スワップチェインを作成します。ここでは2つのバックバッファチェインを持つようにして初期化をしています。また カレントのバックバッファインデックスを取得するために、DXGI の IDXGISwapChain3 で作成することにしました。
さらにその後、コマンドリストを作成し、レンダーターゲットのディスクリプタを格納するためのオブジェクトを作成していきます。ディスクリプタヒープには先ほどのスワップチェインのバックバッファ数分だけのレンダーターゲットビューを準備しておきます。このとき、ディスクリプタヒープのポインタのインクリメントサイズがデバイスから取得できます。
描画処理
初期化のコードが終わったので、画面クリアの描画ループについて説明します。基本的にはクリアのコマンドをコマンドキューに設定して(積んで)、実行をキックする、その後コマンド完了を待って画面に描画、という感じです。
ただし、クリアの際には描画先のリソースをリソースバリア処理(関数)を用いて状態を変更していく必要があります(SetResourceBarrier関数)。これから扱うリソースがどの状態になるのか、正しく変更されるか、といったためにこんな仕組みになっているのだろうと予想しています。バッファの扱いが柔軟になったため故だと思います。
描画に関しては以下のようなコードとなっています。
void OnRender() {
static int count = 0;
float clearColor[4] = { 0,1.0f,1.0f,0 };
D3D12_VIEWPORT viewport;
viewport.TopLeftX = 0; viewport.TopLeftY = 0;
viewport.Width = 640;
viewport.Height = 480;
viewport.MinDepth = 0;
viewport.MaxDepth = 1;
int targetIndex = swapChain->GetCurrentBackBufferIndex();
SetResourceBarrier(
cmdList.Get(),
renderTarget[targetIndex].Get(),
D3D12_RESOURCE_STATE_PRESENT,
D3D12_RESOURCE_STATE_RENDER_TARGET);
// レンダーターゲットのクリア処理.
clearColor[0] = (float)(0.5f * sin(count*0.05f) + 0.5f);
clearColor[1] = (float)(0.5f * sin(count*0.10f) + 0.5f);
cmdList->RSSetViewports(1, &viewport);
cmdList->ClearRenderTargetView(handleRTV[targetIndex], clearColor, NULL, 0);
// Presentする前の準備.
SetResourceBarrier(
cmdList.Get(),
renderTarget[targetIndex].Get(),
D3D12_RESOURCE_STATE_RENDER_TARGET,
D3D12_RESOURCE_STATE_PRESENT);
cmdList->Close();
// 積んだコマンドの実行.
ID3D12CommandList* pCommandList = cmdList.Get();
cmdQueue->ExecuteCommandLists(1, &pCommandList);
swapChain->Present(1, 0);
WaitForCommandQueue(cmdQueue.Get());
cmdAllocator->Reset();
cmdList->Reset(cmdAllocator.Get(), nullptr);
count++;
}
まとめ
ようやく現在の状況での正常に動くコードを作成できました。とはいってもまだ画面クリアだけですが、最初の1歩を再び歩み出せたと思います。従来のコードからは変更箇所は多いですが、考え方そのものは変わっていないなと思います。
ソースコードについて
今回のソースコードの全貌は GitHub に上げてあります。気になる方はそちらを参照していただければと思います(https://github.com/techlabxe/testD3D12 の HelloDX12 )。
