DirectXの話 第146回

DirectX12事始め その4 15/08/29 up

DirectX12の事始めも第4回でやっと終わり。

始めるだけでこんなに時間がかかるものか?と言われるかもしれませんし、自分でもどうかと思う。

ですが、最初で躓いたらやる気がなくなるので、最初くらいはちゃんと丁寧に説明した方がいいと思うの。

というわけで最後の解説は残った部分。

まずはパイプラインステートオブジェクトとその周辺から。

シェーダですが、今回用意したシェーダは頂点カラーをそのままピクセルシェーダで出力するだけの簡単なものです。

なのでここに提示はしませんが、読み込みにはちょっと今までとは違うやり方をします。

シェーダのコンパイルは完了し、バイナリが出力されているわけですが、今までであればこのバイナリからシェーダオブジェクトが作られます。

しかしDX12ではそんな軟弱なものは存在しません。シェーダバイナリはただのバイナリファイルとして取り扱います。

必要なのは読み込んだバイナリの先頭アドレスとサイズだけ。なんと男らしい仕様。

オブジェクトが必要なら自前で用意してね♪ ということでしょう。

・290行目

// シェーダバイナリを読み込む

bool isOK = g_binVS.Read(L"bin/VSSample.cso");

isOK = isOK && g_binPS.Read(L"bin/PSSample.cso");

assert(isOK);

BinFile というクラスを自作してバイナリファイルを読み込めるようにしました。

それを使ってVisual Studioがビルドしたシェーダを読み込んでいるだけです。

さて、では本題のパイプラインステートオブジェクト、略してPSOです。

DX9まではブレンドや深度テストなどの設定はDeviceに各パラメータを直接設定する形でした。

DX10からはいくつかの項目に分類され、それぞれがオブジェクトとして管理する手法になりました。ある程度まとめられたわけです。

DX12からはその手の設定のほぼすべてがPSOとして1つにまとめられました。これ以上まとめられない所まで来てしまいました。

例えば、マテリアルAは不透明・深度テスト有効・深度書き込み有効、マテリアルBは半透明・深度テスト有効・深度書き込みなし、マテリアルCは加算合成・深度テスト有効・深度書き込みなしという状態だとします。

DX11であれば、ブレンドステートとして「不透明」・「半透明」・「加算合成」を用意、深度ステートとして「テストあり・書き込みあり」・「テストあり・書き込みなし」を用意してそれぞれの組み合わせてマテリアルを設定していました。

DX12ではこの場合、それぞれのマテリアルのPSOを作成しなければなりません。

それこそ、深度バイアスが違うとか陰面カリングが違うとか、その1つのパラメータだけが違う状態でもPSOを作らなきゃいけないわけです。

しかもシェーダもPSOの中に入ります。つまり、不透明オブジェクトだけどシェーダが違うという状態でPSOを作る必要があります。

実質、マテリアル1つに対してPSO1つが必要と言っても差し支えないでしょう。

概念は難しくないですが、管理はかなり大変じゃないかと思いますね…

PSOを作成しているコードは以下です。

・295行目

// PSOを作成

{

D3D12_INPUT_ELEMENT_DESC elementDescs[] = {

{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },

{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 1, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },

};

D3D12_RASTERIZER_DESC rasterDesc = {};

rasterDesc.FillMode = D3D12_FILL_MODE_SOLID;

rasterDesc.CullMode = D3D12_CULL_MODE_NONE;

rasterDesc.FrontCounterClockwise = false;

rasterDesc.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;

rasterDesc.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;

rasterDesc.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;

rasterDesc.DepthClipEnable = true;

rasterDesc.MultisampleEnable = false;

rasterDesc.AntialiasedLineEnable = false;

rasterDesc.ForcedSampleCount = 0;

rasterDesc.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;

D3D12_BLEND_DESC blendDesc = {};

blendDesc.AlphaToCoverageEnable = false;

blendDesc.IndependentBlendEnable = false;

blendDesc.RenderTarget[0].BlendEnable = false;

blendDesc.RenderTarget[0].LogicOpEnable = false;

blendDesc.RenderTarget[0].SrcBlend = D3D12_BLEND_ONE;

blendDesc.RenderTarget[0].DestBlend = D3D12_BLEND_ZERO;

blendDesc.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;

blendDesc.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;

blendDesc.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO;

blendDesc.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;

blendDesc.RenderTarget[0].LogicOp = D3D12_LOGIC_OP_NOOP;

blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;

D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {};

desc.InputLayout = { elementDescs, _countof(elementDescs) };

desc.pRootSignature = g_pRootSignature;

desc.VS = { reinterpret_cast<UINT8*>(g_binVS.GetBin()), g_binVS.GetBinSize() };

desc.PS = { reinterpret_cast<UINT8*>(g_binPS.GetBin()), g_binPS.GetBinSize() };

desc.RasterizerState = rasterDesc;

desc.BlendState = blendDesc;

desc.DepthStencilState.DepthEnable = FALSE;

desc.DepthStencilState.StencilEnable = FALSE;

desc.SampleMask = UINT_MAX;

desc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;

desc.NumRenderTargets = 1;

desc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;

desc.SampleDesc.Count = 1;

hr = g_pDevice->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&g_pPipelineState));

assert(SUCCEEDED(hr));

}

DX11で言うところのラスラライザステートブレンドステートを設定しています。

また、頂点入力レイアウトもここです。

ああ、RenderTargetのフォーマットまで。なんて面倒くさいんだ!

D3D12_GRAPHICS_PIPELINE_STATE_DESC::PrimitiveTopologyType は三角形かラインか、という情報を入れます。

これは三角形リストや三角形ストリップなどの情報とは違いますので注意してください。

例えば、ラインリストやラインストリップを描画したい場合はここに D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE を指定します。

PSOは確実に管理をどうしようかと考える事になるオブジェクトです。

管理手法のセオリーはおいおい確立されていくものと思いますが、あまり考えたくはないですね…

PSOの設定は ID3D12GraphicsCommandList::SetPipelineState() 命令で設定可能ですが、今回は切り替えることがないためコマンドリストリセット時に設定してそのままにしています。

・530行目

// コマンドリストをリセット

hr = g_pCommandList->Reset(g_pCommandAllocator, g_pPipelineState);

assert(SUCCEEDED(hr));

次は頂点バッファの作成。

今回は頂点座標と頂点カラーを別々のバッファにしています。

作り方はどちらも同じなので、頂点座標の方だけ提示します。

・348行目

// 頂点座標を作成する

{

float positions[] = {

0.0f, 0.5f, 0.0f,

-0.5f, -0.5f, 0.0f,

0.5f, -0.5f, 0.0f,

};

D3D12_HEAP_PROPERTIES prop = {};

prop.Type = D3D12_HEAP_TYPE_UPLOAD;

prop.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;

prop.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;

prop.CreationNodeMask = 1;

prop.VisibleNodeMask = 1;

D3D12_RESOURCE_DESC desc = {};

desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;

desc.Alignment = 0;

desc.Width = sizeof(positions);

desc.Height = 1;

desc.DepthOrArraySize = 1;

desc.MipLevels = 1;

desc.Format = DXGI_FORMAT_UNKNOWN;

desc.SampleDesc.Count = 1;

desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;

desc.Flags = D3D12_RESOURCE_FLAG_NONE;

hr = g_pDevice->CreateCommittedResource(

&prop, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&g_pVBPosition));

assert(SUCCEEDED(hr));

// バッファにコピー

UINT8* p;

hr = g_pVBPosition->Map(0, nullptr, reinterpret_cast<void**>(&p));

assert(SUCCEEDED(hr));

memcpy(p, positions, sizeof(positions));

g_pVBPosition->Unmap(0, nullptr);

// Viewの初期化

g_vbPositionView.BufferLocation = g_pVBPosition->GetGPUVirtualAddress();

g_vbPositionView.SizeInBytes = sizeof(positions);

g_vbPositionView.StrideInBytes = sizeof(float) * 3;

}

ヒープタイプはやっぱりUPLOADにしてます。CPUからもGPUからもアクセスできるヒープタイプですね。

本来、この手の変更しないバッファに関してはDEFAULTを利用してVRAM上に確保すべきですが、今回はコピーの手間を省くためにUPLOADにしています。

そのうちちゃんとした実装を試したいと思います。

他の注目点としては D3D12_VERTEX_BUFFER_VIEW というオブジェクトが存在することです。ただの構造体ですが。

DX11までは頂点バッファにはViewがなかったのでちょっと新鮮。とはいえ、こちらは頂点バッファと1対1で持っておけばいいので管理に苦労することはないでしょう。

で、実際の描画部分。

・568行目

D3D12_VERTEX_BUFFER_VIEW views[] = { g_vbPositionView, g_vbColorView };

g_pCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

g_pCommandList->IASetVertexBuffers(0, _countof(views), views);

g_pCommandList->DrawInstanced(3, 1, 0, 0);

今回はインデックスバッファを使っていないので頂点バッファのViewを指定してトポロジを指定してそのまま描画。

DrawInstanced() を使っていますが、描画命令はこれと DrawIndexedInstanced() のみになりました。

インスタンシングしないならインスタンス数を1にすればいいだけだし、ということでしょう。

というわけで、三角形を1つ表示するだけのサンプルで4回も引っ張りました。

わかってる方にとっては何を今更って感じだったかもしれませんが、特に今回は自分があまり理解していなかったので理解を深めるために細かく解説しました。

個人的には、DX12はよっぽどのことがない限りいじらなくていいと思っているのですが、よっぽどのことがある方もいらっしゃるでしょうから自分が迷った部分で無駄に迷わないようになっていただければ書いた意味があるというものです。

というわけで次回は全くの未定ですが、最低でもテクスチャを使って描画、インデックスバッファ使って描画、深度バッファを有効化くらいは早めにやりたい。

今年中にはコンピュートパイプラインを使ってなにかやりたい。

しかし果てしなく未定。いや、C++書きたくないもんで…