HLSLでは関数の外に変数を値付きで記述ができますが、この宣言だけでは見た目に反して変数から正常に値を読み出すことができず、エラーも出ません。
C++側から値を変更するつもりが無いならstaticをつけることで定数になり、意図通り動作します。(constではないです)
staticを付けなかった場合、NSIGHT等で見ると暗黙のコンスタントバッファが生成されることがわかります。ところが肝心の値が代入されず、0で埋まっています。なぜ文法的に許容されるか不可解に思えるところですが、今は非推奨となったD3DX11Effectを使うと、暗黙のコンスタントバッファに宣言通りの値を代入してくれます。(そして、D3DX11Effect::Applyを呼ぶと内部でVSSetConstantBuffers、PSSetConstantBuffers等の呼び出しまでしてくれます)
コードをモダン化するためにD3DX11Effectを取り除く作業中に分かり難いバグに遭遇したら、暗黙のコンスタントバッファがないか確認してみると良いかもしれません。
Wednesday, June 28, 2017
Monday, February 27, 2017
[DX11] DXGI_FORMAT_R32_TYPELESSのTYPELESSとは?
CreateTexture2DでDXGI_FORMAT_D32_FLOAT型のデプスバッファを作る時に以下のようなエラーが発生する場合、DXGI_FORMAT_D32_FLOATの代わりにDXGI_FORMAT_R32_TYPELESSを指定してエラーを回避できます。
おそらく、DSVとSRVをそれぞれD32、R32として作るのでCreateTexture2Dではテクスチャを型が未定のストレージと規定して齟齬が起こらないようにする意図と思われます。
D3D11 ERROR: ID3D11Device::CreateTexture2D: The format (0x28, D32_FLOAT) cannot be bound as a ShaderResource or cast to a format that could be bound as a ShaderResource. Therefore this format cannot support D3D11_BIND_SHADER_RESOURCE. Specifiying the format R32_TYPELESS instead would solve this issue. [ STATE_CREATION ERROR #92: CREATETEXTURE2D_UNSUPPORTEDFORMAT]
つまり、デプスバッファとしてレンダリングに使った後、SRVとしてシェーダから参照させたいならCreateTexture2Dの時はDXGI_FORMAT_R32_TYPELESSにしなければいけません。そして、そのテクスチャからSRVとDSVを作る場合、CreateShaderResourceViewにはDXGI_FORMAT_R32_FLOAT、CreateDepthStencilViewにはDXGI_FORMAT_D32_FLOATをそれぞれ指定します。
おそらく、DSVとSRVをそれぞれD32、R32として作るのでCreateTexture2Dではテクスチャを型が未定のストレージと規定して齟齬が起こらないようにする意図と思われます。
Thursday, February 2, 2017
6年前のNVIDIA Direct3D SDK 11を今の環境でコンパイルする
NVIDIAの"Direct3D SDK 11"はIsland11を始めとした良質なサンプルのソースコードが入っているので時々いじってみたくなります。しかし古いサンプルなので、Visual Studio 2015とWindows 10でコンパイル+実行するには一手間掛ける必要があります。
- https://developer.nvidia.com/dx11-samplesからDirect3D SDK 11をダウンロードします。一行目の緑色の"here"をクリックします。
- D3DXが使われているので、DirectX SDKが必要です。https://www.microsoft.com/en-us/download/confirmation.aspx?id=6812 から手に入れます。
- Windows 10でDirectX SDKを入れようとすると"S1023"エラーが発生します。https://support.microsoft.com/en-us/help/2728613/-s1023-error-when-you-install-the-directx-sdk-june-2010 に解決策があります。インストール前にMicrosoft Visual C++ 2010 x86 RedistributableとMicrosoft Visual C++ 2010 x64 Redistributableを消せば良いようです。
- Samples_2008.sln を開きます。Visual Studio 2015用にコンバートする過程でWarningが出ますが、特に問題はなさそうです。
ソリューション内の各プロジェクトに以下の変更を行います。
- *.fx, *.hlslはプロジェクトから除外しておきます。(Removeで除外、あるいはExcluded From Build設定)VS2015はこれらがプロジェクトにあるとコンパイルしようとしますが、シェーダーバイナリを使うプロジェクトがないのでその必要はありません。
- ConfigrationをDebugにするとd3dx11effectsd.libが無いと言われてビルドできません。'd'の無いd3dx11effects.libはNVIDIA Corporation\NVIDIA Direct3D SDK 11\Libの下にあり、Releaseにしておくとリンクできます。参考:https://www.gamedev.net/topic/602742-getting-nvidia-sdk-samples-to-compile/
- Additional Include Directoriesに;%DXSDK_DIR%\Includeを追加
- Additional Library Directoriesに;%DXSDK_DIR%\Lib\x86を追加
- __vsnwprintfが無いと言われるので、Additional Dependenciesにlegacy_stdio_definitions.libを追加
- Treat Warnings As ErrorsはNoにしておきます。DXGI_*系のマクロがWindowsSDKに取り込まれた結果、再定義されたと警告が出るようです。又は、C4005警告をDisable Specific Warningsに入れてもいいのですが、これ以外にも警告が多いので一括で許可したほうが簡単です。
- TerrainTessellation.cppで、powfとstd::maxのコンパイルが出来ないので、#include <algorithm>を追加
ただ、プロジェクトの数が多いので、一つ一つ設定するのは大変です。そこで、少々乱暴ですが以下のようにします。
- C:\ProgramData\NVIDIA Corporation\NVIDIA Direct3D SDK 11\Include\DXUT\Core にDirectX SDKのヘッダファイルを入れてしまう。
- C:\ProgramData\NVIDIA Corporation\NVIDIA Direct3D SDK 11\Lib にも同様に C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Lib\x86 からライブラリファイルを持ってくる。
- NVIDIA Direct3D SDK 11\Include\DXUT\Core\DXUT.hに、以下の行を追加する。
#pragma comment(lib,"legacy_stdio_definitions.lib")
#pragma warning(disable:4005)
これで、大部分のプロジェクトはHLSLファイルを除外するだけでビルドに成功するようになります。
更に、Debugでビルドするには、d3dx11effectsd.lib、dxutd.lib、dxutoptd.libといった、いくつかのライブラリが存在しておらずリンクできません。これは、DirectX SDKのSamplesフォルダに含まれるいくつかのプロジェクトをDebugでビルドしたものです。ビルドしたlibファイルをNVIDIA SDKの下のLibフォルダに置けばリンクできるのですが、NVIDIAの開発者がファイル名をリネームして使っているようです。
更に、Debugでビルドするには、d3dx11effectsd.lib、dxutd.lib、dxutoptd.libといった、いくつかのライブラリが存在しておらずリンクできません。これは、DirectX SDKのSamplesフォルダに含まれるいくつかのプロジェクトをDebugでビルドしたものです。ビルドしたlibファイルをNVIDIA SDKの下のLibフォルダに置けばリンクできるのですが、NVIDIAの開発者がファイル名をリネームして使っているようです。
- Samples\C++\DXUT11\Core\DXUT_2010.slnからDXUT.libが出来ますが、DXUTd.libにリネームします。
- Samples\C++\DXUT11\Core\DXUTOpt_2010.slnからDXUTOpt.libが出来ますが、DXUTOptd.libにリネームします。
- Effects11_2010.slnをビルドしようとすると、static void* __cdecl operator new(size_t s, CDataBlockStore &pAllocator) という関数がERROR C2323"non-member operator new and delete functions may not be declared inline"というエラーになります。これは、staticをinlineに変更するとコンパイルできます。Effects11.libというファイルが出来ますが、d3dx11effectsd.libにリネームします。
Sunday, January 8, 2017
[Vulkan] VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICが便利
VulkanのUniform Bufferには、VK_DESCRIPTOR_TYPE_UNIFORM_BUFFERとVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICの二種類がありますが、"DYNAMIC"はオフセットをコマンドバッファに一緒に積めるので便利です。
DirectX12やVulkanのような低レベルAPIでは、Constant buffer/Uniform buffer用に大きなGPU側バッファをまず確保しておいて、自前のアロケータで割り当てながら使うことになると思います。どのDraw Callがどのメモリ領域をConstant buffer/Uniform bufferとして使うかは毎フレーム変化するしかないので、Draw Call毎にGPUアドレスを渡すことになります。
それは、VK_DESCRIPTOR_TYPE_UNIFORM_BUFFERを使っても可能なのですが、GPUアドレスはdescriptor setに書き込んで渡す必要があるので、uniform bufferを使う回数分vkAllocateDescriptorSetsとvkUpdateDescriptorSetsでdescritor setも作ってあげる必要があります。
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICを使うと、descritor setは1個だけ、大きなGPUバッファの先頭アドレスを書き込んだものを用意するだけで済みます。その代わりdraw call毎にvkCmdBindDescriptorSetsでUniform bufferをバインドするときにオフセットを指定します。複数のdescriptor setを管理する手間が省けるので便利です。
NVIDIAのドキュメントによると、NVIDIAのハードウェアで"Uniform Buffer Dynamic Binding"は速いのだそうです。
https://developer.nvidia.com/vulkan-shader-resource-binding
推測に過ぎませんが、NVIDIAのハードウェアではDirectX12の"Root Constant"が"Uniform Buffer Dynamic Binding"に相当するのかもしれません。
DX12 Do's And Don'tsのRoot Signaturesの項で"sit in the root"でバインドしたコンスタント/CBVが特にPixel Shaderで速いとされています。
https://developer.nvidia.com/dx12-dos-and-donts
SetGraphicsRootConstantBufferViewもDescriptor Heapを介さずにコマンドリストに直接GPUアドレスを乗せるという部分も、descriptor set1個を使いまわせるVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICに似ています。
DirectX12やVulkanのような低レベルAPIでは、Constant buffer/Uniform buffer用に大きなGPU側バッファをまず確保しておいて、自前のアロケータで割り当てながら使うことになると思います。どのDraw Callがどのメモリ領域をConstant buffer/Uniform bufferとして使うかは毎フレーム変化するしかないので、Draw Call毎にGPUアドレスを渡すことになります。
それは、VK_DESCRIPTOR_TYPE_UNIFORM_BUFFERを使っても可能なのですが、GPUアドレスはdescriptor setに書き込んで渡す必要があるので、uniform bufferを使う回数分vkAllocateDescriptorSetsとvkUpdateDescriptorSetsでdescritor setも作ってあげる必要があります。
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICを使うと、descritor setは1個だけ、大きなGPUバッファの先頭アドレスを書き込んだものを用意するだけで済みます。その代わりdraw call毎にvkCmdBindDescriptorSetsでUniform bufferをバインドするときにオフセットを指定します。複数のdescriptor setを管理する手間が省けるので便利です。
NVIDIAのドキュメントによると、NVIDIAのハードウェアで"Uniform Buffer Dynamic Binding"は速いのだそうです。
https://developer.nvidia.com/vulkan-shader-resource-binding
推測に過ぎませんが、NVIDIAのハードウェアではDirectX12の"Root Constant"が"Uniform Buffer Dynamic Binding"に相当するのかもしれません。
DX12 Do's And Don'tsのRoot Signaturesの項で"sit in the root"でバインドしたコンスタント/CBVが特にPixel Shaderで速いとされています。
https://developer.nvidia.com/dx12-dos-and-donts
SetGraphicsRootConstantBufferViewもDescriptor Heapを介さずにコマンドリストに直接GPUアドレスを乗せるという部分も、descriptor set1個を使いまわせるVK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMICに似ています。
Wednesday, November 2, 2016
Vertex Array Object(VAO)を使わない理由
OpenGL ES 3.xではVertex Array Object(VAO)が導入され、APIコールが減って効率的ということになっています。ところが、コーディングの特殊さによるVAOの弊害が多く、多分実行速度も変わらない気がしたので、VAOを使わなくてもいい理由を書いてみようと思いました。(ちなみに、Unreal Engine 4もVAOを使っていないようです)プラットフォームは主にAndroidを想定しています。
1. VAOが頂点バッファと結びつくのが困る
頂点バッファをシェーダに結びつけるいわゆる"Input Layout"はAPI毎に格納されるオブジェクトが違います。DX11ではID3D11InputLayout、DX12やVulkanではPSO(pipeline state object)、ES 3.xはVAOに格納されます。
そのうちVAOだけの厄介な点は特定の頂点バッファと結びつく仕様になっていることです。実務でもありそうな例として、モンスターA、B、Cを、Gバッファ生成シェーダー、シャドウマップ生成シェーダ、光学迷彩シェーダで描画するとします。頂点バッファから送るのはGバッファは全情報、シャドウマップは座標だけ送れば十分、光学迷彩はテクスチャマッピングを省くことにします。
そのうちVAOだけの厄介な点は特定の頂点バッファと結びつく仕様になっていることです。実務でもありそうな例として、モンスターA、B、Cを、Gバッファ生成シェーダー、シャドウマップ生成シェーダ、光学迷彩シェーダで描画するとします。頂点バッファから送るのはGバッファは全情報、シャドウマップは座標だけ送れば十分、光学迷彩はテクスチャマッピングを省くことにします。
VAOを使って必要な情報のみ必要なシェーダに送るためには、モンスター3種とシェーダー3種、3x3=9個のVAOが必要になります。さすがにこのような煩雑なことはしたくないので、実際にはモンスター毎に1つずつVAOを作って、それぞれ頂点の全情報を送るように設定し、あとはドライバレベルでの最適化に託す、という書き方になりそうです。
2. わかりにくいバグを作りやすい
経験上、描画が終わったらglBindVertexArray(0)でVAOのバインドを忘れず解除すべきです。なぜなら、バインドしっぱなしにすると他の描画モジュールがバインド中のVAOを書き換えることが出来てしまい、どこでどう地雷を踏んでいるかわからない難解なバグとなります。
描画の為のglBindBufferやglVertexAttribPointerのみならず、glBindBufferの周辺はVAOを書き換える可能性に気を使います。例えばインデックスバッファを生成したり書き換えたりする時にglBindBufferを呼びますが、これがよそのVAOを壊してしまうかもしれません。
3. VAOでAPIコールを減らしても(多分)速くならない
VAOを採用する動機は、なんとなく速そうという期待ではないでしょうか。しかし、本当に速くなるでしょうか。VAOが無いES2.0は、頂点バッファを切り替える度にglBindBufferとglVertexAttribPointerの複数回に及ぶコールで毎回”Input Layout"に該当する情報をドライバに伝えます。それが省ける点はVAOが有利に見えます。
OpenGLのドライバの実装を想像してみます。GLも内部でPSOのような物を持っているはずです。PSOの生成は重い処理なので、一回作ったらハッシュ値などで探せるようにして実体をキャッシュしておくでしょう。他のステートが決まらないとPSOが確定できないので、ハッシュ値の計算もPSOの生成もすぐには行われず、ドローコール(glDrawElements等)のタイミングで行われるはずです。
こう考えると、glVertexAttribPointerにしろ、glBindVertexArrayにしろ、ハッシュ値を計算するための元データの提供に過ぎず、バッファのバインドを除いてCPU内で完結しています。VAOを使ったほうがAPIの呼び出しは減るかもしれませんが、無理にVAOを使って得られるものがあるわけではなさそうです。
別の視点から見てみると、また、現状多くのゲームが互換性のためにES2.0で実装されています。ドライバ開発者の立場としても、VAOありきで最適化できないと思われます。
従来はES2.0をベースとして上位機種はES3.x採用という戦略がありました。今後は上位機種はVulkanで互換性のためにES2.0、という組み合わせが増えると思います。対応機種を狭める上に将来的にVulkanに置き換えられる運命にある、ES3.xの必要性が薄れてきました。
現在のようにAPIが移行期にある中では特定APIに依存しないように抽象化を試みることも多いと思いますが、VAOはその特殊さゆえに抽象化がとても難しいです。ES2.0のglVertexAttribPointerも非常に変則的ですが、使うシェーダが決まるまで頂点レイアウトの決定を保留できる分、VAOよりは抽象化はしやすいと言えるかもしれません。
4. ES2.0がまだまだ現役、そしてVulkanの登場
従来はES2.0をベースとして上位機種はES3.x採用という戦略がありました。今後は上位機種はVulkanで互換性のためにES2.0、という組み合わせが増えると思います。対応機種を狭める上に将来的にVulkanに置き換えられる運命にある、ES3.xの必要性が薄れてきました。
現在のようにAPIが移行期にある中では特定APIに依存しないように抽象化を試みることも多いと思いますが、VAOはその特殊さゆえに抽象化がとても難しいです。ES2.0のglVertexAttribPointerも非常に変則的ですが、使うシェーダが決まるまで頂点レイアウトの決定を保留できる分、VAOよりは抽象化はしやすいと言えるかもしれません。
Saturday, September 3, 2016
[DX12] VBV、IBVはAPI呼出し後すぐ破棄してもOK
頂点バッファの定義で、例えば以下のように2つの変数を定義して使うのを見かけると思います。
頂点バッファはID3D12ResourceとD3D12_VERTEX_BUFFER_VIEW、インデックスバッファはID3D12ResourceとD3D12_INDEX_BUFFER_VIEWが必要です。一つのリソースに2つの宣言が必要で煩雑なのですが、実はVIEWの宣言は省略してもよさそうです。
DirectX11ではSRVやDSVなど、GPUから参照するリソースに付加情報を付けてバインドする構造をViewと呼んでいました。 DirectX12になってD3D12_VERTEX_BUFFER_VIEW(VBV)やD3D12_INDEX_BUFFER_VIEW(IBV)という構造体がいわゆるViewの一種に加わりましたが、Viewの概念が若干変わったようです。DX11ではViewはその実体がGPU側にあるのかCPU側にあるのかは隠蔽されていました。それが、DX12ではGPU側なのかCPU側なのかは明確に区別されます。
この辺は、マイクロソフトのYouTubeのチャンネル「Microsoft DirectX 12 and Graphics Education」に登録されている「Resource Binding in DirectX 12 (pt.1)」に詳しいです。
8:30付近で出る表と13:00の解説によると、CBV、SRV、UAV、SAMPLERはGPUに配置され、それ以外のIBV、VBV、SOV、RTV、DSVはCPU側にのみ存在し、更にはドライバもVIEW(descriptor)の複製をコマンドリストに積むのでVIEWへの参照を保持しないとのことです。
MicrosoftのDirectX-Graphics-Samplesサンプル中、D3D12HelloWorldソリューションでは頂点バッファのID3D12Resourceに加えてD3D12_VERTEX_BUFFER_VIEWやD3D12_INDEX_BUFFER_VIEWを保持していますが、1つのバッファに対して2つの変数を管理するのは二度手間に感じます。
この二度手間を解決するため、上の動画から得られた事実を利用します。アプリケーションはID3D12Resourceのみ保持し、VIEWはスタック上に毎回作ることにします。例えばインデックスバッファをコマンドリストに積むためにこんな関数を作れば、今後D3D12_INDEX_BUFFER_VIEW構造体の存在を忘れてしまうことができます。
同じDirectX-Graphics-SamplesでもMiniEngineの中では、スタック上に作ったD3D12_VERTEX_BUFFER_VIEWやD3D12_INDEX_BUFFER_VIEWを直接コマンドリストに流すようになっているようです。
ところで、D3D12_INDEX_BUFFER_VIEWとD3D12_VERTEX_BUFFER_VIEWは構造体でしたが、RTVやDSVはID3D12DescriptorHeapの形をとっています。これは実に奇妙に見えます。なぜなら、同じID3D12DescriptorHeapを使うSRVやCBVの場合、DescriptorがGPUメモリ上に存在し、シェーダーから参照されるため、プログラマはフェンスを駆使してID3D12DescriptorHeapの寿命管理を行わなければいけません。ところが、RTVやDSVはAPI呼び出し後はコマンドリストが実行前でもID3D12DescriptorHeapを破棄してもRTVやDSVを書き換えても構わないという事です。
内部の実装がも異なるオブジェクトが同じID3D12DescriptorHeapの形を取っているため、混乱の元になりそうです。
ともかく、この事実を利用して管理するオブジェクトを減らすことができます。以下はID3D12DescriptorHeapをスタック上に作ってOMSetRenderTargetsした後バッファをクリアする例です。ComPtrなのでID3D12DescriptorHeapは関数を抜けると消滅します。
残念ながら以下のコードはDebug Layerがエラーを出します。コマンドリスト実行終了前にID3D12DescriptorHeapを解放していないか検出するためにDebug Layerが参照しているようです。Debug Layerを外せば問題無さそうですが、まともにデバッグできないのでやめたほうがよさそうです。ただし、ID3D12DescriptorHeapを使わないVBVやIBVは前述の通りMiniEngineでもスタックに作って即破棄いるのでAPI呼出し後の破棄が合法と見て間違いなさそうです。
使い捨てにするとDebug Layerでエラーが出ますが、ID3D12DescriptorHeapをそれぞれ1個だけ作って毎回書き換えるように使うのはエラーにはなりません。上の動画の13:00に大丈夫と書いてあります。(もちろんSRVやCBVでこれをやるとアウトです)
DX12はアプリケーションがローレベルを意識するAPIなので、RTVやDSVもD3D12_VERTEX_BUFFER_VIEWのようにただの構造体にしてもよかったのではないでしょうか。そうすればGPU側はID3D12DescriptorHeap、CPU側はただの構造体、という風に住み分けができて分かりやすくなると思うからです。この辺は、おそらくはDX11時代のID3D11RenderTargetViewやID3D11DepthStencilView等の歴史的な経緯も関係がありそうです。
頂点バッファはID3D12ResourceとD3D12_VERTEX_BUFFER_VIEW、インデックスバッファはID3D12ResourceとD3D12_INDEX_BUFFER_VIEWが必要です。一つのリソースに2つの宣言が必要で煩雑なのですが、実はVIEWの宣言は省略してもよさそうです。
DirectX11ではSRVやDSVなど、GPUから参照するリソースに付加情報を付けてバインドする構造をViewと呼んでいました。 DirectX12になってD3D12_VERTEX_BUFFER_VIEW(VBV)やD3D12_INDEX_BUFFER_VIEW(IBV)という構造体がいわゆるViewの一種に加わりましたが、Viewの概念が若干変わったようです。DX11ではViewはその実体がGPU側にあるのかCPU側にあるのかは隠蔽されていました。それが、DX12ではGPU側なのかCPU側なのかは明確に区別されます。
この辺は、マイクロソフトのYouTubeのチャンネル「Microsoft DirectX 12 and Graphics Education」に登録されている「Resource Binding in DirectX 12 (pt.1)」に詳しいです。
8:30付近で出る表と13:00の解説によると、CBV、SRV、UAV、SAMPLERはGPUに配置され、それ以外のIBV、VBV、SOV、RTV、DSVはCPU側にのみ存在し、更にはドライバもVIEW(descriptor)の複製をコマンドリストに積むのでVIEWへの参照を保持しないとのことです。
MicrosoftのDirectX-Graphics-Samplesサンプル中、D3D12HelloWorldソリューションでは頂点バッファのID3D12Resourceに加えてD3D12_VERTEX_BUFFER_VIEWやD3D12_INDEX_BUFFER_VIEWを保持していますが、1つのバッファに対して2つの変数を管理するのは二度手間に感じます。
この二度手間を解決するため、上の動画から得られた事実を利用します。アプリケーションはID3D12Resourceのみ保持し、VIEWはスタック上に毎回作ることにします。例えばインデックスバッファをコマンドリストに積むためにこんな関数を作れば、今後D3D12_INDEX_BUFFER_VIEW構造体の存在を忘れてしまうことができます。
同じDirectX-Graphics-SamplesでもMiniEngineの中では、スタック上に作ったD3D12_VERTEX_BUFFER_VIEWやD3D12_INDEX_BUFFER_VIEWを直接コマンドリストに流すようになっているようです。
ところで、D3D12_INDEX_BUFFER_VIEWとD3D12_VERTEX_BUFFER_VIEWは構造体でしたが、RTVやDSVはID3D12DescriptorHeapの形をとっています。これは実に奇妙に見えます。なぜなら、同じID3D12DescriptorHeapを使うSRVやCBVの場合、DescriptorがGPUメモリ上に存在し、シェーダーから参照されるため、プログラマはフェンスを駆使してID3D12DescriptorHeapの寿命管理を行わなければいけません。ところが、RTVやDSVはAPI呼び出し後はコマンドリストが実行前でも
内部の実装がも異なるオブジェクトが同じID3D12DescriptorHeapの形を取っているため、混乱の元になりそうです。
残念ながら以下のコードはDebug Layerがエラーを出します。コマンドリスト実行終了前にID3D12DescriptorHeapを解放していないか検出するためにDebug Layerが参照しているようです。Debug Layerを外せば問題無さそうですが、まともにデバッグできないのでやめたほうがよさそうです。ただし、ID3D12DescriptorHeapを使わないVBVやIBVは前述の通りMiniEngineでもスタックに作って即破棄いるのでAPI呼出し後の破棄が合法と見て間違いなさそうです。
使い捨てにするとDebug Layerでエラーが出ますが、ID3D12DescriptorHeapをそれぞれ1個だけ作って毎回書き換えるように使うのはエラーにはなりません。上の動画の13:00に大丈夫と書いてあります。(もちろんSRVやCBVでこれをやるとアウトです)
DX12はアプリケーションがローレベルを意識するAPIなので、RTVやDSVもD3D12_VERTEX_BUFFER_VIEWのようにただの構造体にしてもよかったのではないでしょうか。そうすればGPU側はID3D12DescriptorHeap、CPU側はただの構造体、という風に住み分けができて分かりやすくなると思うからです。この辺は、おそらくはDX11時代のID3D11RenderTargetViewやID3D11DepthStencilView等の歴史的な経緯も関係がありそうです。
Saturday, August 27, 2016
[DX12] HLSLにRoot Signatureを定義する
HLSLにRoot Signatureをattributeとして書く方法は、MSDNに詳しい説明があります。
Specifying Root Signatures in HLSL
Vertex ShaderとPixel Shaderの両方に同じ定義をするために#defineで文字列を定義します。文字列1つで短く書けて、C++側で構造体と列挙体を駆使して書くより圧倒的に読みやすくで直感的です。
文字列の全体が、D3D12_ROOT_SIGNATURE_DESCに該当します。
StaticSamplerが、D3D12_STATIC_SAMPLER_DESCに該当する記述になります。フィルタリングの方法なども書けますが、レジスタ以外は省略可能です。嬉しいのはHLSL内でサンプラーの定義から使用まで完結することです。C++側から何もする必要がありません。
今回は書いていませんが、RootFlagsがD3D12_ROOT_SIGNATURE_FLAGSに該当します。ハマりそうな点としては、ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUTを書き忘れるとおそらく頂点入力が出来なくなるので気をつけます。今回は頂点はシェーダー内で生成しているので不要です。
RootFlagsとStaticSamplerを除いたそれ以外が、D3D12_ROOT_PARAMETERの配列と同等になります。この並びがC++側から参照するインデックス、つまりSetGraphicsRootConstantBufferViewやSetGraphicsRootDescriptorTableの引数RootParameterIndexに該当します。
こうしてHLSLのattributeに書いたRoot Signatureはシェーダのバイナリに含まれます。MSDNではfxcを使っていますが、D3DCompileFromFile等でID3DBlobを生成している場合にもD3DGetBlobPartでRoot Signatureを取り出せます。Vertex ShaderとPixel Shaderのどちらから取り出しても構いません。
D3DGetBlobPartにD3D_BLOB_ROOT_SIGNATUREを渡すことでシェーダーバイナリからroot signatureを取り出します。
これは、以前はD3D12SerializeRootSignatureで作っていたroot signatureのバイナリを置き換えるもので、どちらも最後はCreateRootSignatureでID3D12RootSignatureを生成します。
ところで、MSDNによるとDX11でもRoot Signature入りのシェーダを問題なく使えるようで、Root Signatureを単に無視すると書いてあります。別の視点から見ると、DX12でのみ必要だったコードをC++から追い出して、プラットフォームの差を吸収するのにも役立ってくれそうです。
Specifying Root Signatures in HLSL
MSDNのサンプルは機能紹介のためか「全部入り」の複雑なものになっていますが、ここではもっとシンプルなHLSLにRoot Signatureを書いてみました。キューブマップを空とみなして画面全体にレンダリングしています。
文字列の全体が、D3D12_ROOT_SIGNATURE_DESCに該当します。
StaticSamplerが、D3D12_STATIC_SAMPLER_DESCに該当する記述になります。フィルタリングの方法なども書けますが、レジスタ以外は省略可能です。嬉しいのはHLSL内でサンプラーの定義から使用まで完結することです。C++側から何もする必要がありません。
RootFlagsとStaticSamplerを除いたそれ以外が、D3D12_ROOT_PARAMETERの配列と同等になります。この並びがC++側から参照するインデックス、つまりSetGraphicsRootConstantBufferViewやSetGraphicsRootDescriptorTableの引数RootParameterIndexに該当します。
こうしてHLSLのattributeに書いたRoot Signatureはシェーダのバイナリに含まれます。MSDNではfxcを使っていますが、D3DCompileFromFile等でID3DBlobを生成している場合にもD3DGetBlobPartでRoot Signatureを取り出せます。Vertex ShaderとPixel Shaderのどちらから取り出しても構いません。
ところで、MSDNによるとDX11でもRoot Signature入りのシェーダを問題なく使えるようで、Root Signatureを単に無視すると書いてあります。別の視点から見ると、DX12でのみ必要だったコードをC++から追い出して、プラットフォームの差を吸収するのにも役立ってくれそうです。
Subscribe to:
Posts (Atom)