DirectX12を勉強してみた (テクスチャ周り)

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

***

[前回](http://qiita.com/em7dfggbcadd9/items/89e7af2d9254fdb557b4)では描画周りをやりました．頂点に色を持たせてポリゴンを描画しましたが，今回はテクスチャを貼ってみたいと思います．
今回は以下のメンバ変数とメソッドを追加しました．

```cpp:
//メソッド
int CreateTexture();
int CreateShaderResource();
int CreateSampler();
```
```cpp:
//変数
ID3D12DescriptorHeap	*pDescriptorHeapSRV;
ID3D12DescriptorHeap	*pDescriptorHeapSMP;
ID3D12Resource			*pTexture;
```

# 1. RootSignatureの設定
前回までは，シェーダーのレジスタにはアフィン変換などに用いる定数をバインドするだけだったので，RootSignatureはCBVの設定だけでしたが，テクスチャを貼る場合は新たにテクスチャやサンプラーなどもバインドします．なのでRootSignatureをそのように設定し直す必要があります．

```cpp:CreateRootSignature
int D3D12Manager::CreateRootSignature(){
	HRESULT hr;
	D3D12_ROOT_SIGNATURE_DESC RootSignatureDesc;
	D3D12_ROOT_PARAMETER RootParameters[3];

	ZeroMemory(&RootSignatureDesc, sizeof(RootSignatureDesc));
	ZeroMemory(RootParameters,     sizeof(RootParameters));

	D3D12_DESCRIPTOR_RANGE range[2];
	ZeroMemory(range, sizeof(range));

	range[0].NumDescriptors     = 1;
	range[0].BaseShaderRegister = 0;
	range[0].RangeType          = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
	range[0].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;

	range[1].NumDescriptors     = 1;
	range[1].BaseShaderRegister = 0;
	range[1].RangeType          = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
	range[1].OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;

	RootParameters[0].ParameterType             = D3D12_ROOT_PARAMETER_TYPE_CBV;
	RootParameters[0].ShaderVisibility          = D3D12_SHADER_VISIBILITY_ALL;
	RootParameters[0].Descriptor.ShaderRegister = 0;
	RootParameters[0].Descriptor.RegisterSpace  = 0;

	RootParameters[1].ParameterType             = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	RootParameters[1].ShaderVisibility          = D3D12_SHADER_VISIBILITY_ALL;
	RootParameters[1].DescriptorTable.NumDescriptorRanges = 1;
	RootParameters[1].DescriptorTable.pDescriptorRanges   = &range[0];

	RootParameters[2].ParameterType             = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	RootParameters[2].ShaderVisibility          = D3D12_SHADER_VISIBILITY_ALL;
	RootParameters[2].DescriptorTable.NumDescriptorRanges = 1;
	RootParameters[2].DescriptorTable.pDescriptorRanges   = &range[1];


	RootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
	RootSignatureDesc.NumParameters = _countof(RootParameters);
	RootSignatureDesc.pParameters = RootParameters;

	hr = D3D12SerializeRootSignature(&RootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &pRootSignatureBlob, &pErrorBlob);
	if(FAILED(hr)){
		return -1;
	}
	hr = pDevice->CreateRootSignature(0, pRootSignatureBlob->GetBufferPointer(), pRootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(&pRootSignature));
	if(FAILED(hr)){
		return -1;
	}
	return 0;
}
```
定数バッファは今までと同じで，新たにテクスチャとサンプラの設定を追加しました．ここの設定はパフォーマンスに影響を与えるようですが，どう書いたらベストかわかりません(←)．誰かおしえてくださいf(^^;
ともあれこれで定数バッファはシェーダのb0に，テクスチャはt0に，サンプラはs0に設定できました．

# 2. 頂点レイアウトの設定
前回で使用した頂点フォーマットでは座標と色を扱いました．今回はテクスチャを使用するため色情報は必要ありません．かわりにUV情報が必要になります．これは頂点がテクスチャ画像のどの位置に当たるのかを決めるものです．なので頂点の構造体は次のようになります．

```cpp:Vertex3D
typedef struct Vertex3D{
		XMFLOAT3 Position;
		XMFLOAT2 UV0;
	}Vertex3D;
```
PSOの頂点レイアウトも変わります．

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

	// 頂点レイアウト.
	D3D12_INPUT_ELEMENT_DESC descInputElements[] = {
		{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,  0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT,    0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
	};

//...省略
```

# 3. テクスチャの作成
続いてテクスチャを作っていきます．かつてのDirectXではテクスチャを読み込む関数的なものがあったようですが (よくわかっていない...)，DirectX12ではそのようなものはなく，画像を自分で頑張って読み込みます (まじか！Σ(ﾟдﾟ;))．とりあえずリソース作って書き込みます．

```cpp:CreateTexture
int D3D12Manager::CreateTexture(){
	HRESULT hr;
	D3D12_RESOURCE_DESC descResourceTex;
	D3D12_HEAP_PROPERTIES heapProps;
	ZeroMemory(&heapProps, sizeof(heapProps));
	ZeroMemory(&descResourceTex, sizeof(descResourceTex));

	heapProps.Type                 = D3D12_HEAP_TYPE_CUSTOM;
	heapProps.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_WRITE_BACK;
	heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_L0;
	heapProps.CreationNodeMask     = 0;
	heapProps.VisibleNodeMask      = 0;

	descResourceTex.Dimension        = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
	descResourceTex.Width            = 256;	//とりあえず今回は256x256の32bitビットマップを使うため，決め打ち
	descResourceTex.Height           = 256;
	descResourceTex.DepthOrArraySize = 1;
	descResourceTex.MipLevels        = 1;
	descResourceTex.Format           = DXGI_FORMAT_B8G8R8A8_UNORM;
	descResourceTex.Layout           = D3D12_TEXTURE_LAYOUT_UNKNOWN;
	descResourceTex.SampleDesc.Quality = 0;
	descResourceTex.SampleDesc.Count   = 1;

	hr = pDevice->CreateCommittedResource(&heapProps, D3D12_HEAP_FLAG_NONE, &descResourceTex, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&pTexture));
	if(FAILED(hr)){
		return -1;
	}

	
	std::ifstream ifs("test.bmp", std::ios_base::in | std::ios_base::binary);
	if(!ifs){
		return -1;
	}
	BYTE *dat = (BYTE*)malloc(4 * 256 * 256);
	ifs.read((char*)dat, 139);	//今回ファイルヘッダは把握しているため切り捨て
	ifs.read((char*)dat, 4  * 256 * 256);//データの読み込み
	
	//読み込んだ画像データの書き込み
	D3D12_BOX box = {0, 0, 0, 256, 256, 1};
	hr = pTexture->WriteToSubresource(0, &box, dat, 4 * 256, 4 * 256 * 256);
	free(dat);
	if(FAILED(hr)){
		return -1;
	}

	return 0;
}
```
今回は自分で用意した256x256の32bitビットマップを読み込みました．手抜きのためヘッダ情報などは取得しておらず，マジックナンバー出まくりのプログラムです．皆さんはもっと上手くやってくれますよね?f(^^;
SDKのバージョンが10.0.10240.0の時は
`heapProps.Type                 = D3D12_HEAP_TYPE_UPLOAD;
	heapProps.CPUPageProperty      = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;`
でも動いたのですが，10.0.10586.0にしたら動かなくなりました．

# 4. ShaderResourceView の作成
次にShaderResourceView の作成を行います．実際にテクスチャをシェーダーに渡すためのデスクリプタの作成です．

```cpp:
int D3D12Manager::CreateShaderResource(){
	HRESULT hr;
	D3D12_DESCRIPTOR_HEAP_DESC descHeap;

	ZeroMemory(&descHeap, sizeof(descHeap));
	descHeap.NumDescriptors = 1;
	descHeap.Type           = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	descHeap.Flags		    = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	descHeap.NodeMask       = 0;
	hr = pDevice->CreateDescriptorHeap(&descHeap, IID_PPV_ARGS(&pDescriptorHeapSRV));
	if (FAILED(hr)) {
		return hr;
	}

	UINT strideSize = pDevice->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
	D3D12_CPU_DESCRIPTOR_HANDLE handleSRV;
	D3D12_SHADER_RESOURCE_VIEW_DESC descSRV;
	ZeroMemory(&descSRV, sizeof(descSRV));

	descSRV.Format						  = DXGI_FORMAT_B8G8R8A8_UNORM;
	descSRV.ViewDimension				  = D3D12_SRV_DIMENSION_TEXTURE2D;
	descSRV.Texture2D.MipLevels			  = 1;
	descSRV.Texture2D.MostDetailedMip     = 0;
	descSRV.Texture2D.PlaneSlice          = 0;
	descSRV.Texture2D.ResourceMinLODClamp = 0.0F;
	descSRV.Shader4ComponentMapping		  = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;

	handleSRV = pDescriptorHeapSRV->GetCPUDescriptorHandleForHeapStart();
	pDevice->CreateShaderResourceView(pTexture, &descSRV, handleSRV);

	return 0;
}
```

# 5. サンプラの作成
次にテクスチャをサンプル処理するためのサンプラの作成です．テススチャをワープで描画するかミラーで描画するかなどの設定をするものです．

```cpp:CreateSampler
int D3D12Manager::CreateSampler(){
	HRESULT hr;
	D3D12_DESCRIPTOR_HEAP_DESC descHeap;

	ZeroMemory(&descHeap, sizeof(descHeap));
	descHeap.NumDescriptors = 1;
	descHeap.Type			= D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER;
	descHeap.Flags			= D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	descHeap.NodeMask       = 0;
	hr = pDevice->CreateDescriptorHeap(&descHeap, IID_PPV_ARGS(&pDescriptorHeapSMP));
	if (FAILED(hr)) {
		return hr;
	}

	D3D12_CPU_DESCRIPTOR_HANDLE handleSampler;
	D3D12_SAMPLER_DESC descSampler;
	ZeroMemory(&descSampler, sizeof(descSampler));

	descSampler.Filter			= D3D12_FILTER_MIN_MAG_MIP_POINT;
	descSampler.AddressU		= D3D12_TEXTURE_ADDRESS_MODE_WRAP;
	descSampler.AddressV		= D3D12_TEXTURE_ADDRESS_MODE_WRAP;
	descSampler.AddressW		= D3D12_TEXTURE_ADDRESS_MODE_WRAP;
	descSampler.MaxLOD			=  FLT_MAX;
	descSampler.MinLOD			= -FLT_MAX;
	descSampler.ComparisonFunc	= D3D12_COMPARISON_FUNC_NEVER;
	descSampler.MipLODBias      = 0.0F;
	descSampler.MaxAnisotropy   = 0;

	handleSampler = pDescriptorHeapSMP->GetCPUDescriptorHandleForHeapStart();
	pDevice->CreateSampler(&descSampler, handleSampler);

	return 0;
}
```

# 6. シェーダーの改造
テクスチャを描画できるようにシェーダーを改造します．書き換え箇所は，定数の追加とピクセルシェーダーです．頂点シェーダーはそのままで大丈夫です．

```cpp:
//定数場ファファの値
cbuffer cbBuffer : register(b0){
	float4x4 WVP;
};

//追加したテクスチャとサンプラ
Texture2D<float4> tex0 : register(t0);
SamplerState samp0 : register(s0);

struct VS_INPUT{
	float3 Position : POSITION;
	float2 UV0      : TEXCOORD;
};

struct PS_INPUT{//(VS_OUTPUT)
	float4 Position : SV_POSITION;
	float2 UV0      : TEXCOORD;
};



PS_INPUT VSMain(VS_INPUT input){
	PS_INPUT output;

	float4 pos4 = float4(input.Position, 1.0);
	output.Position = mul(pos4, WVP);
	output.UV0      = input.UV0;

	return output;
}


float4 PSMain(PS_INPUT input) : SV_TARGET{
	return tex0.Sample(samp0, input.UV0);//サンプリングしたテクスチャを返す
}
```

# 7. コマンドリストへの追加
描画コマンドに設定を追加します．

```cpp:PopulateCommandList

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());

//-----------------追加箇所---------------------
	ID3D12DescriptorHeap* heaps[] = { pDescriptorHeapSRV, pDescriptorHeapSMP };
	pCommandList->SetDescriptorHeaps(_countof(heaps), heaps);
	pCommandList->SetGraphicsRootDescriptorTable(1, pDescriptorHeapSRV->GetGPUDescriptorHandleForHeapStart());
	pCommandList->SetGraphicsRootDescriptorTable(2, pDescriptorHeapSMP->GetGPUDescriptorHandleForHeapStart());
//---------------------------------------------

	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;
}
```

# 8. 描画データの変更
描画データも変更します．

ポリゴンの頂点は以下のようになります．

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

//-----------------変更箇所-------------------
	Vertex3D Vertices[] = {
		{XMFLOAT3(-1.f,  1.f, 0.f), XMFLOAT2(0.f, 0.f)},
		{XMFLOAT3( 1.f,  1.f, 0.f), XMFLOAT2(1.f, 0.f)},
		{XMFLOAT3( 1.f, -1.f, 0.f), XMFLOAT2(1.f, 1.f)},
		{XMFLOAT3(-1.f, -1.f, 0.f), XMFLOAT2(0.f, 1.f)},
	};
//-------------------------------------------
	hr = pVertexBuffer->Map(0, nullptr, &Mapped);
	if (SUCCEEDED(hr)) {
		CopyMemory(Mapped, Vertices, sizeof(Vertices));
		pVertexBuffer->Unmap(0, nullptr);
		Mapped = nullptr;
	}else{
		return -1;
	}
	VertexView.BufferLocation = pVertexBuffer->GetGPUVirtualAddress();
	VertexView.StrideInBytes  = sizeof(Vertex3D);
	VertexView.SizeInBytes    = sizeof(Vertices);


	//インデックスデータをIndexBufferに書き込む
	uint16_t Index[] = {0, 1, 3, 1, 2, 3};
	hr = pIndexBuffer->Map(0, nullptr, &Mapped);
	if (SUCCEEDED(hr)) {
		CopyMemory(Mapped, Index, sizeof(Index));
		pIndexBuffer->Unmap(0, nullptr);
		Mapped = nullptr;
	}else{
		return -1;
	}
	IndexView.BufferLocation = pIndexBuffer->GetGPUVirtualAddress();
	IndexView.SizeInBytes    = sizeof(Index);
	IndexView.Format         = DXGI_FORMAT_R16_UINT;

	return 0;
}

```

# 実行結果
![directx12.png](https://qiita-image-store.s3.amazonaws.com/0/105303/2ffdf80e-e23c-2637-40c5-4ab5a0a3d8f5.png)

こんな感じで実行できました．画像は適当にペイントソフトでポチポチしました．

# おわり
毎度のことながら疲れました．f(^^;
なんとかテクスチャを貼るところまで行けました．なかなか道のりは険しそうです...