DirectX12を勉強してみた (描画周り)

#注意!
この記事の内容は古いものとなっています。新しいものを書いていますので、そちらにお願いします。
[DirectX12で3D描画](http://qiita.com/em7dfggbcadd9/items/a73b79ef9cd949de8926)

***

[前回](http://qiita.com/em7dfggbcadd9/items/483c60fa066f10f510d7)に引き続きDirectX12です．今回は描画周りをやっていきたいと思います．例によって間違いを多く含む恐れがあるのでご了承ください．
DirectX12では，
Ⅰ. CommandListにコマンドを積む
Ⅱ. CommandListをClose
Ⅲ. CommandQueueにCommandlistを渡して実行
Ⅳ. CommandAllocatorをReset
Ⅴ. CommandListをReset
という順序で描画を行います．CommandListをCloseしたときにコマンドがハードウエアネイティブに変換されてCommandAllocatorに蓄えられます．そしてCommandListをCommandQueueに渡して実行します．DirextX12のコマンドは遅延実行なので，CommandQueueを抜けてきたとしても描画が終わっているとは限りません．描画が終わったらCommandAllocatorをResetします．この時バインドされたすべてのCommandListがCloseされている必要があります．また，描画が行われておらず，GPUから参照されていない必要もあります．その後CommandListをResetします．この時，一つのCommandAllocatorをバインドする必要があります(PSOもバインドできますが，こちらはしなくても大丈夫です)．

# 1. ViewPort，ScissorRectの設定
ヴューポートとシザー矩形の設定をします．

```cpp:
Viewport.TopLeftX = 0.f; 
Viewport.TopLeftY = 0.f;
Viewport.Width    = (FLOAT)m_iWindowWidth;
Viewport.Height   = (FLOAT)m_iWindowHeight;
Viewport.MinDepth = 0.f;
Viewport.MaxDepth = 1.f;

RectScissor.top    = 0;
RectScissor.left   = 0;
RectScissor.right  = m_iWindowWidth;
RectScissor.bottom = m_iWindowHeight;
```
# 2. 描画終了待ち関数
DirectX12からはコマンドがすべて遅延実行になったため，コマンドを実行してもそれが終了したかわかりません．そして，CommandAllocatorはコマンドの実行中に参照されるので，描画が終了するまでResetしてはいけません．なので，Ⅲ.の実行が終了するのを待つ関数が必要になります．ここでCommandQueueと一緒に作成したFenceを使用します．

```cpp:WaitForPreviousFrame
int D3D12Manager::WaitForPreviousFrame(){
	HRESULT hr;
	
	const UINT64 fence = Frames;
	hr = pComandQueue->Signal(pQueueFence, fence);
	if(FAILED(hr)){
		return -1; 
	}
	++Frames;

	if (pQueueFence->GetCompletedValue() < fence){
		hr = pQueueFence->SetEventOnCompletion(fence, handleFenceEvent);
		if(FAILED(hr)){
			return -1; 
		}
		WaitForSingleObject(handleFenceEvent, INFINITE);
	}
	return 0;
}
```
CommandQueueの終了したコマンドの数がFrameより少なかったら終了を待つという処理です．

# 3. ポリゴンデータの準備
頂点バッファへの書き込み，インデックスバッファへの書き込み，定数バッファへの書き込みを行っています．毎フレーム呼び出してしまっていますが，今回は頂点，インデックスは変わらないので一回でいいはずです．

```cpp:SetPlaneData
int D3D12Manager::SetPlaneData(){
	HRESULT hr;
	void *Mapped;

	//定数バッファの書き込み
	ConstantBufferData Buf;
	XMMATRIX Projection = XMMatrixPerspectiveFovLH(
		XMConvertToRadians(60.0f),
		(float)m_iWindowWidth / (float)m_iWindowHeight,
		1.0f,
		20.0f);

	XMVECTORF32 eyePosition   = { 0.0f,  0.0f, -3.0f, 0.0f};
	XMVECTORF32 FocusPosition = { 0.0f,  0.0f,  0.0f, 0.0f};
	XMVECTORF32 upDirection   = { 0.0f,  1.0f,  0.0f, 0.0f};
	XMMATRIX View = XMMatrixLookAtLH(eyePosition, FocusPosition, upDirection);
	XMStoreFloat4(&Buf.EyePos, eyePosition);

	XMMATRIX World;
	FLOAT rotate = 0.f;//(FLOAT)(XM_PI * (Frames % 1600)) / 800.0f;
	World = XMMatrixRotationY(rotate);

	XMStoreFloat4x4(&Buf.World, XMMatrixTranspose(World));

	XMMATRIX WVP = XMMatrixTranspose(World * View * Projection);
	XMStoreFloat4x4(&Buf.WVP, WVP);

	hr = pConstantBuffer->Map(0, nullptr, &Mapped);
	if(SUCCEEDED(hr)){
		CopyMemory(Mapped, &Buf, sizeof(Buf));
		pConstantBuffer->Unmap(0, nullptr);
		Mapped = nullptr;
	}else{
		return -1;
	}

	return 0;
}
```

# 4. 描画コマンドの設定
データの設定ができたので，描画コマンドを設定します．

```cpp:PopulateCommandList
int D3D12Manager::SetResourceBarrier(D3D12_RESOURCE_STATES BeforeState, D3D12_RESOURCE_STATES AfterState){
	D3D12_RESOURCE_BARRIER ResourceBarrier;
	ZeroMemory(&ResourceBarrier, sizeof(ResourceBarrier));
	ResourceBarrier.Type  = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	ResourceBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	ResourceBarrier.Transition.pResource   = pRenderTarget[RTVIdx];
	ResourceBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	ResourceBarrier.Transition.StateBefore = BeforeState;
	ResourceBarrier.Transition.StateAfter  = AfterState;

	pCommandList->ResourceBarrier(1, &ResourceBarrier);
	return 0;
}

int D3D12Manager::PopulateCommandList(){
	HRESULT hr;
	
	FLOAT ClearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f};

	SetResourceBarrier(D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);

	pCommandList->ClearDepthStencilView(handleDSV, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
	pCommandList->ClearRenderTargetView(handleRTV[RTVIdx], ClearColor, 0, nullptr);

	pCommandList->SetGraphicsRootSignature(pRootSignature);
	pCommandList->SetPipelineState(pPipelineState);
	pCommandList->SetGraphicsRootConstantBufferView(0, pConstantBuffer->GetGPUVirtualAddress());

	pCommandList->RSSetViewports(1, &Viewport);
	pCommandList->RSSetScissorRects(1, &RectScissor);

	pCommandList->OMSetRenderTargets(1, &handleRTV[RTVIdx], TRUE, &handleDSV);

	pCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
	pCommandList->IASetVertexBuffers(0, 1, &VertexView);
	pCommandList->IASetIndexBuffer(&IndexView);

	pCommandList->DrawIndexedInstanced(6, 1, 0, 0, 0);

	SetResourceBarrier(D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);

	hr = pCommandList->Close();
	if(FAILED(hr)){
		return -1;
	}

	return 0;
}
```

# 5. Present
描画の終了待ちをしてPresentをすると画面にポリゴンが映されます．

```cpp:
int D3D12Manager::Render(){
	HRESULT hr;

	SetPlaneData();

	PopulateCommandList();

	ID3D12CommandList *const ppCommandList = pCommandList;
	pComandQueue->ExecuteCommandLists(1, &ppCommandList);

	WaitForPreviousFrame();

	hr = pCmdAllocator->Reset();
	if(FAILED(hr)){
		return -1; 
	}
	
	hr = pCommandList->Reset(pCmdAllocator, nullptr);
	if(FAILED(hr)){
		return -1; 
	}

	hr = pSwapChain->Present(1, 0);
	if(FAILED(hr)){
		return -1;
	}

	RTVIdx = pSwapChain->GetCurrentBackBufferIndex();

	return 0;
}
```
これでやっとポリゴンが１枚描画されました．長かった......恐るべしDirectX12．何はともあれこのようにして描画を行います．
このプログラムではコマンドを実行して待機を繰り返していますが，CommandListはResetするときに今までとは別のCommandAllocatorをバインドしてもいいので，CommandAllocatorやGPUから参照されるバッファ類を2つ以上作って，コマンドの実行をしたら終了を待たずに，別の空いているCommandAllocatorをバインドして次のコマンドの実行をするというふうにすると良い？



今回はこれで終了したいと思います．まだまだ勉強不足なので頑張りたいと思います．間違いなど有りましたら教えていただけるとありがたいです(お手柔らかにお願いしますm(_ _)m)．

[次回](http://qiita.com/em7dfggbcadd9/items/b5a9b71a6ae29d86da50)はテクスチャの描画をやってみたいと思います．