こんにちは、今回はDirectX12でHLSL6.8を使う方法です
筆者の環境
言語 : C++20
IDE : VisualStudio2022
GPU : Nvidia Geforce RTX 3060
Driver : 555.85
まずそもそもVisualStudio2022だと、HLSL6.6までしかプロパティで選択できません。なので自力でコンパイルし、オブジェクトファイルを作成する必要があります。とりあえずHLSLのコンパイラは必要ですね。
実はDirectX12だけではHLSL6.8は使えません。DirectX AgilitySDKが必要なのです。AgilitySDKとは誤解を恐れずに言うと、マイナーアップデートみたいなものです。
DirecX11 -> 12 が大型アップデートです。設計や思想そのものは変えたくないけど、機能は追加していきたい、ということでこのAgilitySDKが必要なのです。
では必要なものを取得しましょう、今回はNuGetを使用します
NuGetを開いたら以下の二つを取得してください。
DirectX Compiler(HLSL)、略してDXCですね。
さて次はcppファイルに記述が必要です。
extern "C" { __declspec(dllexport) extern const unsigned int D3D12SDKVersion = 614; }
extern "C" { __declspec(dllexport) extern const char8_t* D3D12SDKPath = u8".\\D3D12\\"; }
AgilitySDKがそのプロジェクトにある時はそのSDKを使用し、ないときはCドライブにある共通のを使うようになるそうです。へー
...少し真面目に解説すると、この部分だけC++ではなくC言語と同じようにコンパイルしDLLエクスポートしています。なぜC++のコンパイルだとダメなのかは長くなるので、詳しくはマングリングと検索してご自分で調べてみてください。オーバーロードの仕組みとか知れて面白いです。
ちなみに、D3D12SDKVersionのところは614じゃなくても大丈夫です。取得したAgilitySDKの対応バージョンに応じて書き換えましょう。
続きましてHLSL6.8が使えるか確認します。
D3D12_FEATURE_DATA_SHADER_MODEL sm = { D3D_SHADER_MODEL_6_9 };
device_->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &sm, sizeof(sm));
ブレークポイントを打ってsmの中身を確認してください。使えるシェーダーモデルの最大バージョンが返されます。ちなみに初期化のところで6_6とかやってはいけません。どうやら使えるバージョンであるときは数値が変わらず、数値をただ丸めるようなので、最大値で初期化する必要があります。
もしここで6_8未満だった場合はドライバの確認をしてみましょう。
https://developer.nvidia.com/blog/advancing-gpu-driven-rendering-with-work-graphs-in-direct3d-12/ より551.76からHLSL6.8が使えます。
https://www.nvidia.co.jp/download/driverResults.aspx/221878/jp のサポートリストから自分のGPUがそもそも対応しているか見てみましょう。
これで一通り必要なものの取得や設定は終わりました。あとはDXCでHLSLファイルをコンパイルし取得するだけです。ですがその前にHLSLのプロパティにあるビルドから除外をはいに変更しましょう。でないと勝手に6.6以前の指定したシェーダモデルでコンパイルしようとし、コンパイルエラーを吐きます。
HLSLファイルを手動コンパイル
dxcompiler.libとdxcapi.h,d3dcompiler.hを読み込みます。
そしたら初期化です
IDxcUtils* util_;
IDxcCompiler3* dxc_;
IDxcIncludeHandler* inc_;
----------------------
HRESULT res = DxcCreateInstance(CLSID_DxcUtils, IID_PPV_ARGS(&util_));
if (FAILED(res)) return false;
res = DxcCreateInstance(CLSID_DxcCompiler, IID_PPV_ARGS(&dxc_));
if (FAILED(res)) return false;
res = util_->CreateDefaultIncludeHandler(&inc_);
if (FAILED(res)) return false;
return true;
失敗しないことを祈ります。
無事にお祈りが通じたらいよいよコンパイルです。
std::wstring str = L""//ファイルパス;
std::ifstream filecode(str, std::ios::binary);
std::string code = std::string(std::istreambuf_iterator<char>(filecode), std::istreambuf_iterator<char>());
IDxcBlobEncoding* E_blob;
HRESULT&& res = ShaderModel6_8::util_->CreateBlob(code.c_str(), code.size(), CP_UTF8, &E_blob);
if (FAILED(res)) return false;
DxcBuffer buf;
buf.Ptr = E_blob->GetBufferPointer();
buf.Size = E_blob->GetBufferSize();
buf.Encoding = DXC_CP_ACP;
IDxcResult* dres;
std::vector<LPCWSTR> args;
args.push_back(L"-E");
args.push_back(L"main");
args.push_back(L"-T");
args.push_back(L"vs_6_8")//ピクセルシェーダならps。switch caseでわけるべし
res = ShaderModel6_8::dxc_->Compile(&buf, args.data(), static_cast<UINT32>(args.size()), inc_, IID_PPV_ARGS(&dres));
if (FAILED(res)) return false;
IDxcBlob* DXC_blob;
res = dres->GetResult(&DXC_blob);
if (FAILED(res)) return false;
ID3DBlob* blob ={};//このblobを使う。関数の引数で持ってきましょう
D3DCreateBlob(DXC_blob->GetBufferSize(), &blob);
memcpy(blob->GetBufferPointer(), DXC_blob->GetBufferPointer(), DXC_blob->GetBufferSize());
E_blob->Release();
dres->Release();
DXC_blob->Release();
return true;
あとはblobを使ってPSOなりを初期化するだけです。
自作のソースコードから一部変更しながらコピペしただけなので、もしかしたらそのままコピペしたら動かないかもしれません。参考程度にしていただけたら幸いです。
間違い、不明な点、もっと効率的な方法や書き方その他諸々ございましたら気楽にコメントお願いします。初めての記事のため大変見苦しかったと思います。
それではまた