安全性、速度、並行性の3つのゴールにフォーカスしたシステムプログラミング言語Rust1でDirectX陣営のLow-overhead APIとして新たに(けっこう前に)策定されたDirectX12を動かします。(とりあえずメモ的な)
Rustはまだまだ初心者なので間違ってたりもっとよくできたりする点があるかもしれませんが暖かい目で見てやってください。
環境
rustcはバージョン1.9.0を使用します。あと、たぶんWindows SDKが入ってるなら入ってると思いますがDebugging Tools for Windows(windbg)も入れておきましょう。D言語を一緒に入れてる人はパスの関係でコマンドラインでwindbgを起動するとD言語付属のやつが起動してしまうかもしれないのでファイル名を変えておくといいと思います。
追記: rustcはMSVC ABIのほうを使用すると安心かと思います。GNU ABI(MinGW環境)でももしかしたら動いてくれるかもしれませんが、そもそもMinGWにはd3d12.libが同梱されていないのでちょっと準備が大変かと思います。
Crates
今回使用するクレートは以下の通りです。
[dependencies]
winapi = "0.2.7"
kernel32-sys = "0.2.2"
user32-sys = "0.2.0"
d3d12-sys = "0.2.0"
dxgi-sys = "0.2.0"
dxguid-sys = "0.2.0"
d3dcompiler-sys = "0.2.0"
widestring = "0.2.0"
D3DCompilerがワイド文字列を要求してくるのでwidestringがないとめちゃくちゃつらいです(たしか標準ライブラリだけでもできなくはないはずですけど)
引っかかり点
コード本体はほとんどC++と同じように書けるのですが、いくつか引っかかり点があるので書いておきます。
D3D12_GRAPHICS_PIPELINE_DESC::RTVFormats
最初はRTVFormats: [DXGI_FORMAT_R8G8B8A8_UNORM; 8]
で一括して初期化していたのですが、どうやら使われないRTVFormats
の要素(インデックス番号>=NumRenderTargets
となるような要素)は絶対にDXGI_FORMAT_UNKNOWN
を指定しないとダメらしいです。地道に初期化しましょう。
頂点入力レイアウトの要素のSemanticNameについて(というか、すべての文字列指定についての注意)
明示的にstatic領域に置くようにしないとダメですID3D12Device::CreateGraphicsPipelineState
から制御が返るまでに文字列オブジェクトが生存している必要があるので、べた書きじゃなくて一回ローカル変数を経由するようにする必要があります。普通に
D3D12_INPUT_ELEMENT_DESC { SemanticName: CString::new("POSITION").unwrap().as_ptr(), .. (省略) }
でいいんじゃないかと思いますが、CString::new("POSITION").unwrap()
が一時オブジェクトなのでこの式が終了すると文字列含めオブジェクトが解放されるみたいでメモリ領域が破壊されます(運が良ければ生きています。たぶん)。正しくはこう
let sn_pos = CString::new("POSITION").unwrap();
...
D3D12_INPUT_ELEMENT_DESC { SemanticName: sn_pos.as_ptr(), .. (省略) }
あるいはコメントで教えていただきましたが、as_ptr
の代わりにinto_raw
を使うと直接書けます(メモリリークの心配がありますが、アプリケーション終了時にOSか言語処理系が使用済みメモリを回収してくれるはず(たぶん)なのでここではそこまで神経質になる必要もないかなと思います)。
D3D12_INPUT_ELEMENT_DESC { SemanticName: CString::new("POSITION").unwrap().into_raw(), .. (省略) }
C/C++/DだとCString::newの引数は(この書き方だと)そのままstatic領域に格納されたはずなので引っかかりやすい点だと思います。