VisualStudio 2015 RC になって従来のコードが動かなくなってしまったので、その対応第2弾です。今回は立方体を回転させるサンプルまで復活できたのでそのお話になります。
注意事項
現時点において DirectX12 の部分は実装途中となっています。正式版では大きく変更される可能性があります。よってここの情報は 2015/05 現在の限定された環境でのみ動作するという点をご理解ください。
必要なもの
- VisualStudio 2015 RC
- Windows10 Insider Preview 10074
- AMD RADEON の新しいドライバ(15.200.1023.0)
ほか、以前に説明した内容はこちらを参照してください。
DirectX12プログラミング
通常の Win32 アプリケーションを作成します。ウィンドウの作成関連は割愛して、DirectX12 部分だけ抜粋していきます。また、 DirectX12 についても前回までの内容については省略させていただきます。今回説明する内容で出来るのは上図で示したようなサンプルになります。
デプスバッファの作成
3Dを描画する際に、前後関係を正しくするためにデプスバッファが必要です。この作り方は以下のようなコードになります。以前ではこれらのディスクリプタの設定を楽にしてくれるヘルパークラスがありましたが、今回は含まれていないようだったので個別に設定しました。将来的には再びヘルパーが提供されるとかいう噂になってます。
ComPtr depthBuffer;
D3D12_RESOURCE_DESC descDepth;
ZeroMemory(&descDepth, sizeof(descDepth));
D3D12_HEAP_PROPERTIES heapProps;
ZeroMemory(&heapProps, sizeof(heapProps));
heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 0;
heapProps.VisibleNodeMask = 0;
descDepth.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
descDepth.Width = width;
descDepth.Height = height;
descDepth.DepthOrArraySize = 1;
descDepth.MipLevels = 0;
descDepth.Format = DXGI_FORMAT_R32_TYPELESS;
descDepth.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
descDepth.SampleDesc.Count = 1;
descDepth.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE dsvClearValue;
dsvClearValue.Format = DXGI_FORMAT_D32_FLOAT;
dsvClearValue.DepthStencil.Depth = 1.0f;
dsvClearValue.DepthStencil.Stencil = 0;
HRESULT hr = device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&descDepth,
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&dsvClearValue,
IID_PPV_ARGS(depthBuffer.ReleaseAndGetAddressOf())
);
このデプスバッファの情報を管理するためのディスクリプタヒープも作成する必要があり、その部分は以下のようになります。
D3D12_DESCRIPTOR_HEAP_DESC descDescriptorHeapDSB;
ZeroMemory(&descDescriptorHeapDSB, sizeof(descDescriptorHeapDSB));
descDescriptorHeapDSB.NumDescriptors = 1;
descDescriptorHeapDSB.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
descDescriptorHeapDSB.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
hr = dxDevice->CreateDescriptorHeap(
&descDescriptorHeapDSB,
IID_PPV_ARGS(descriptorHeapDSB.GetAddressOf()));
そしてこのディスクリプタヒープに対して、作成したデプスバッファの情報を生成する部分が以下のようになります。ビューの情報という感じなので handleDSV と名前を適当につけています。
D3D12_DEPTH_STENCIL_VIEW_DESC descDSV;
ZeroMemory(&descDSV, sizeof(descDSV));
descDSV.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
descDSV.Format = DXGI_FORMAT_D32_FLOAT;
descDSV.Texture2D.MipSlice = 0;
dxDevice->CreateDepthStencilView(
renderTargetDepth.Get(),
&descDSV,
descriptorHeapDSB->GetCPUDescriptorHandleForHeapStart()
);
handleDSV = descriptorHeapDSB->GetCPUDescriptorHandleForHeapStart();
定数バッファの作成
定数バッファもデプスバッファ同様に作成します。コードは以下のようになりますが、ほとんどの部分で似ています。
ComPtr cbBuffer;
D3D12_HEAP_PROPERTIES heapProps;
D3D12_RESOURCE_DESC descResourceVB;
ZeroMemory(&heapProps, sizeof(heapProps));
ZeroMemory(&descResourceVB, sizeof(descResourceVB));
heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 0;
heapProps.VisibleNodeMask = 0;
descResourceVB.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
descResourceVB.Width = bufferSize;
descResourceVB.Height = 1;
descResourceVB.DepthOrArraySize = 1;
descResourceVB.MipLevels = 1;
descResourceVB.Format = DXGI_FORMAT_UNKNOWN;
descResourceVB.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
descResourceVB.SampleDesc.Count = 1;
HRESULT hr;
hr = device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&descResourceVB,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(cbBuffer.GetAddressOf())
);
if (FAILED(hr)) {
OutputDebugString(L"CreateCommittedResource() failed.\n");
}
頂点バッファの作成
頂点バッファも同様に作成します。インデックスバッファもさらに同様なのでここでは省略させていただきます。
D3D12_HEAP_PROPERTIES heapProps;
D3D12_RESOURCE_DESC descResourceVB;
ZeroMemory(&heapProps, sizeof(heapProps));
ZeroMemory(&descResourceVB, sizeof(descResourceVB));
heapProps.Type = type;
heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
heapProps.CreationNodeMask = 0;
heapProps.VisibleNodeMask = 0;
descResourceVB.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
descResourceVB.Width = bufferSize;
descResourceVB.Height = 1;
descResourceVB.DepthOrArraySize = 1;
descResourceVB.MipLevels = 1;
descResourceVB.Format = DXGI_FORMAT_UNKNOWN;
descResourceVB.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
descResourceVB.SampleDesc.Count = 1;
ComPtr vertexBuffer;
HRESULT hr;
hr = device->CreateCommittedResource(
&heapProps,
D3D12_HEAP_FLAG_NONE,
&descResourceVB,
states,
nullptr,
IID_PPV_ARGS(vertexBuffer.GetAddressOf())
);
作成した頂点バッファへのアクセスであるビューを以下のようにして作成しておきます。
D3D12_VERTEX_BUFFER_VIEW vertexView;
vertexView.BufferLocation = vertexBuffer->GetGPUVirtualAddress();
vertexView.StrideInBytes = sizeof(MyVertex);
vertexView.SizeInBytes = sizeof(cubeVertices);
各バッファへの書き込み
作成した頂点バッファや定数バッファへの書き込みは Map して書き込みます。
void* mapped = nullptr;
hr = vertexBuffer->Map(0, nullptr, &mapped);
if (SUCCEEDED(hr)) {
memcpy(mapped, cubeVertices, sizeof(cubeVertices));
vertexBuffer->Unmap(0, nullptr);
}
パイプラインステートの作成および設定
個人的に一番大変だと思っている部分がこのパイプラインステートの作成および設定になります。先にコードを示しますが、今回は以下のようにして作成しました。
D3D12_GRAPHICS_PIPELINE_STATE_DESC d3d12util::CreateGraphicsPipelineStateDesc(ID3D12RootSignature * pRootSignature, const void * pBinaryVS, int vsSize, const void * pBinaryPS, int psSize, D3D12_INPUT_ELEMENT_DESC * descInputElements, int numInputElements)
{
D3D12_GRAPHICS_PIPELINE_STATE_DESC descState;
ZeroMemory(&descState, sizeof(descState));
descState.VS.pShaderBytecode = pBinaryVS;
descState.VS.BytecodeLength = vsSize;
descState.PS.pShaderBytecode = pBinaryPS;
descState.PS.BytecodeLength = psSize;
descState.SampleDesc.Count = 1;
descState.SampleMask = UINT_MAX;
descState.InputLayout.pInputElementDescs = descInputElements;
descState.InputLayout.NumElements = numInputElements;
descState.pRootSignature = pRootSignature;
descState.NumRenderTargets = 1;
descState.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
descState.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
descState.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
descState.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
descState.RasterizerState.DepthClipEnable = TRUE;
descState.RasterizerState.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
for (int i = 0;i < _countof(descState.BlendState.RenderTarget); ++i) {
descState.BlendState.RenderTarget[i].BlendEnable = FALSE;
descState.BlendState.RenderTarget[i].SrcBlend = D3D12_BLEND_ONE;
descState.BlendState.RenderTarget[i].DestBlend = D3D12_BLEND_ZERO;
descState.BlendState.RenderTarget[i].BlendOp = D3D12_BLEND_OP_ADD;
descState.BlendState.RenderTarget[i].SrcBlendAlpha = D3D12_BLEND_ONE;
descState.BlendState.RenderTarget[i].DestBlendAlpha = D3D12_BLEND_ZERO;
descState.BlendState.RenderTarget[i].BlendOpAlpha = D3D12_BLEND_OP_ADD;
descState.BlendState.RenderTarget[i].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}
descState.DepthStencilState.DepthEnable = FALSE;
return descState;
}
// ---------------------
// 今回のための頂点レイアウト.
D3D12_INPUT_ELEMENT_DESC descInputElements[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0,12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
};
// PipelineStateオブジェクトの作成.
D3D12_GRAPHICS_PIPELINE_STATE_DESC descPipelineState;
descPipelineState = d3d12util::CreateGraphicsPipelineStateDesc(
rootSignature.Get(),
gVertexShader.binaryPtr, gVertexShader.size,
gPixelShader.binaryPtr, gPixelShader.size,
descInputElements,
_countof(descInputElements));
descPipelineState.DepthStencilState.DepthEnable = TRUE;
descPipelineState.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
descPipelineState.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
descPipelineState.DSVFormat = DXGI_FORMAT_D32_FLOAT;
hr = dxDevice->CreateGraphicsPipelineState(&descPipelineState, IID_PPV_ARGS(pipelineState.GetAddressOf()));
パイプラインステートオブジェクトは描画に必要な設定の大部分を記述したものとなっています。そのため、頂点入力の情報や頂点&ピクセルシェーダーの内容や、ブレンディングの設定といったものまでを保持しています。またそれだけにとどまらず ルートシグネチャ というものをさらに要求し、この部分が今までの DirectX と違うものとなっています。初出だけに自分もまだ理解しきれていないです。
今回のサンプルではルートシグネチャは以下のようにしています。
// PipelineStateのための RootSignature の作成.
D3D12_ROOT_SIGNATURE_DESC descRootSignature;
ZeroMemory(&descRootSignature, sizeof(descRootSignature));
descRootSignature.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
D3D12_ROOT_PARAMETER rootParameters[1];
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;
descRootSignature.NumParameters = 1;
descRootSignature.pParameters = rootParameters;
ルートシグネチャはそのパイプラインでどのようなデータをどのようにセットするかを記述したものとなっています。柔軟なメモリ構成をとれるようにするためのようです。
今回では定数バッファを1つのみ使用するため、それを実現できるルートシグネチャを作成、パイプラインステートの作成としました。
描画コード
描画の設定フローについては従来のコードとほとんど変更がありません。若干引数の順序が変わった程度です。今回のようなものでは以下のようなコードになりました。
cmdList->SetGraphicsRootSignature(rootSignature.Get());
cmdList->SetPipelineState(pipelineState.Get());
cmdList->SetGraphicsRootConstantBufferView(0, constantBuffer->GetGPUVirtualAddress());
cmdList->RSSetViewports(1, &viewPort);
cmdList->RSSetScissorRects(1, &rect);
cmdList->ClearDepthStencilView(handleDSV, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
cmdList->ClearRenderTargetView(handleRTV[targetIndex], clearColor, 0, nullptr);
cmdList->OMSetRenderTargets(1, &handleRTV[targetIndex], TRUE, &handleDSV);
// 頂点データをセット.
cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmdList->IASetVertexBuffers(0, 1, &vertexView );
cmdList->IASetIndexBuffer( &indexView );
cmdList->DrawIndexedInstanced(36, 1, 0, 0, 0);
以前と違い、 IA や OM, RM といった DirectX11 のときに見慣れた接頭辞が復活しました。
まとめ
ようやく立方体を描画させるサンプルまでの復活ができました。以前から中途半端になっていたテクスチャについてを次回説明できたらと思います。
また今回、コード量も多いので全部を貼り付けるわけにもいきませんでした。前回同様、コードの全貌は GitHub のほうを参照していただければと思います。 (https://github.com/techlabxe/testD3D12 の cubeDX12 というものが今回の内容となります)。