DirectX 12 Agility SDKを使ったGPU UPLOADについて


# Agility SDK って？
皆さん、Agility SDKってご存知でしょうか。
DirectXの実験的な実装を、提供しているDirectX12の拡張SDKのような
モノとなっています。
追加でインストール対応しなければならず、一般的な環境とは言えないのですが
今後実装されるだろう機能などが検証できるので、今回は
GPUへの直接データアップロードについて調査してみました。

# そもそも何故ここに来たのか
ことの発端は、趣味でちまちまゲームを作る準備を楽しんでいた所、
コマンドキューにコピー専用のキューがあるなら、描画キューとは別スレッドで
新たにコピーキューを作って転送したほうが、早いんじゃない？と
思い、実装してみてPIXで確認したところ以下のような動作になっていました。

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658418/df13662d-bf43-ba6f-7b59-b33ac50ed723.png)

なんと、CopyTextureRegion（テクスチャをシステムメモリからGPUメモリへコピー）を
行っている間、描画キューがwait状態になっているのです。

資料しらべたり、色々やってみたんですが
個人的には解決できず、そういうモノなのかなと思っていました。
（すいません。もし並行して実行できる方法ご存知の方がいれば
　教えていただけると助かります・・）


# これまでの GPU UPLOAD
現状は、GPUにテクスチャ等のデータを転送する際、DirectX12では
DescriptorHeapをDEFAULTフラグで作成したものと、
UPLOADフラグで作成したものを２つ用意し、
UPLOADフラグで作成した領域(SYSTEM MEMORY上)にCPUでコピーを行い、
その後、コマンドリストで、UPLOADフラグ作成したDescriptorHeapから
DEFAULTフラグ作成したDescriptorHeap(GPU MEMORY上)にGPUで
コピーを行うという手順でした。

# 今回やってみるGPU UPLAOD
そこへ、Agility SDKのアップデートの中にGPUへのアップロードの際、
コピーキューを使わずに、直接アップロードできるといったことが記載されてました。
https://devblogs.microsoft.com/directx/preview-agility-sdk-1-710-0/

そこでふむふむと調べ続けてみると、どうもvulkanではそれはすでに対応してる
らしいのですが、directx12ではagility sdkを使えば一応できるよと。
https://gpuopen.com/learn/using-d3d12-heap-type-gpu-upload/

DescriptorHeap作成時のフラグをGPU_UPLOADという新たなフラグで作成し、
MapしてからCPUから直接そこへコピーすれば、GPUメモリへコピー
されちゃいますというのがこの方法です。

# Resizable BAR

ただ、この機能を使うにはもう一段階ハードルがあり、ビデオカードが対応している
必要があるのはもちろんなんですが、マザーボードでの
Resizable BAR(base address register)を有効にする必要があります。
https://mogalabo.com/resizable-bar/

近くにあるPCをいくつか調べましたが、デフォルトで有効になっているのは
なかったので、おそらく手動で有効にしないと使えないと思います。
マザーボードのbiosからResizable BARという項目を探してenableにします。
![rbar_bios.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658418/ca9339e7-a723-74bb-5894-c5af1f533186.jpeg)

こちらは有効になっているかどうかはnvidiaのコントロールパネルから
ヘルプのシステム情報から確認できます。

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658418/3e5b68f8-bac2-072f-50ed-dd0a74843965.png)


リサイズ可能なベースアドレスが、いいえになっている所が
![スクリーンショット 2023-07-27 100300.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78134/f854b87e-31e8-e354-dd21-94aac91f18d7.png)

はい、になっていればOKです。
![スクリーンショット 2023-07-27 101546.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78134/cfb2ee6d-08d3-74a6-504d-80b87e93b681.png)

# Agility SDKのインストール

Agility SDKはNu Get Packageという形式で提供されており、
VisualStudioのNu Get Packageの管理コンソールからインストールを行う必要があります。
https://devblogs.microsoft.com/directx/directx12agility/

道のりが長い・・・。
VisualStudioのメニューから、
ツール -> Nu Getパッケージマネージャー -> パッケージマネージャーコンソール
の順に選ぶと、画面下部にPM>と表示されたプロンプトが出ますので

PM> NuGet\Install-Package Microsoft.Direct3D.D3D12 -Version 1.711.3-preview

と入力することで、現在のプロジェクトにAgility SDKがインストールされます。
GPU Upload Heapsについては 1.710.0-previewでも対応していますが
現状最新が、1.711.3-previewだったので、最新を入れてみました。

注意点として、**これはPC全体にAgility SDKがインストールされたわけではなく
現在のプロジェクトにだけ適用されているという状態です。**

# Agility SDKを有効化する

まだ準備あります。ゲロゲロ。
https://devblogs.microsoft.com/directx/gettingstarted-dx12agility/
SDKのどのバージョンを有効化するかのフラグを
ソースに記載する必要があります。
こちらは、ソースに書くだけで大丈夫なので簡単です。
プロジェクトに含まれているcppであれば、どこでも大丈夫なはずです。

```
extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = n;}

extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = u8".\\D3D12\\"; }
```
SDKのバージョンについては、（１行目の = n の部分）
https://devblogs.microsoft.com/directx/directx12agility/
こちらのダウンロードページにあるD3D12SDKVersionの数値を入れます。
今回最新を入れたので711となります。

```
extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 711;}
```

これにて漸く下準備が完了です。ここからいよいよ実装です。
なげぇ～。



# D3D12_HEAP_TYPE_GUP_UPLOAD

細かい説明はこちらのサイトにありますがかいつまんで説明していきます。
https://microsoft.github.io/DirectX-Specs/d3d/D3D12GPUUploadHeaps.html

まず、Resizable BARの有効化、Agility SDK最新版の適用、ハードウェアの対応が
有効になっていれば、CheckFeatureSupport関数で有効な状態かを確認できます。

```C++
D3D12_FEATURE_DATA_D3D12_OPTIONS16 options16 = {};
bool GPUUploadHeapSupported = false;
if(SUCCEEDED(pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS16, &options16, sizeof(options16))))
{
    GPUUploadHeapSupported = options16.GPUUploadHeapSupported;
}
```
ここで、GPUUploadHeapSupportedがfalseなら、振り出しに戻ります・・。
NVIDIAのコントロールパネルで有効が確認できている場合は、AgilitySDKの有効化で
どこかミスっていると思います。ちなみに私もソースにバージョンを指定する対応が
できてなくて最初は有効になってませんでした。。。

ここでGPUUploadHeapSupportedでtrueが取れたらゴールが見えてきました。

D3D12DescriptorHeap作成時のフラグにD3D12_HEAP_TYPE_GUP_UPLOADを指定して
作成します。

```c++
D3D12_HEAP_PROPERTIES heapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_GPU_UPLOAD);

D3D12_RESOURCE_DESC resDesc = CD3DX12_RESOURCE_DESC::Tex2D(
	format,
	width,
	height,
	array_size,
	mip_level,
	1,
	0,
	0);

HRESULT hr = pDevice->CreateCommittedResource(
	&heapProp,
	D3D12_HEAP_FLAG_NONE,
	&resDesc,
	D3D12_RESOURCE_STATE_COMMON,
	nullptr,
	IID_PPV_ARGS(&_res));

```

最後に、MapしてからGPUへ直接書き込み処理になります。
この際、通常のUPLOADフラグで作成したDescriptorHeapの場合は
マップしたアドレスを受け取ってそこへ直接memcpyを行うのですが、
GPUへダイレクトの場合は、Map関数の引数をnull,0,nullで呼び出し、
WriteToSubresource関数でコピーを行い、
Unmap関数を呼びます。

```c++
HRESULT hr = _res->Map(0, nullptr, nullptr);
// srcDataがコピー元のテクスチャデータです。
_res->WriteToSubresource(0, NULL, srcData, 
	SrcRowPitch,
	SrcDepthPitch
);
_res->Unmap(0, nullptr);
```

おめでとうございます。これでGPUへのダイレクトコピーは完了です。
あとは、このDescHeapを使って描画をすればOKです。

# 動作確認
これらの動作をPIXで確認する際もAgility SDKを使った場合は注意が必要です。
まず、Agility SDKに対応したPIXを使わなければキャプチャできません。
PIX公式サイトからpreview版をダウンロードしてインストールしてください。
また、通常版もPCに残るので、起動してからちゃんと最新Agility SDKの
バージョンに対応しているかを確認しましょう。
今回の場合はAgility SDKの項目が 711になっていればOKです。

![pix_preview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2658418/3ee3c18a-b35a-5d26-e736-1d0835e5aab3.png)

# D3D12MemoryAllocator
AMDがOpenSourceで公開しているD3D12MemoryAllocatorというライブラリがあります。
こちら、feature-gpu-upload-heapsというブランチがあり
こちらのブランチであれば、directx12の今回のgpuへの直接アップロードに
対応したブランチとなっているので、今回のテクスチャ以外の使い方も
Test.cppのテストコードの中にあるので、このあと確認してみたいと思います。


# 最後に
長丁場お疲れ様でした。最後まで見ていただきありがとうございます。
参考になったかわかりませんが、少しでもヘルプになっていれば幸いです。
ちょっとパフォーマンス確認についてはもう少し検証しないと
調べきれていないので、完成次第更新したいと思います。