DirectStorage APIの予想

公開:2020/10/13
更新:2020/10/13
4 min読了の目安(約4000字TECH技術記事

はじめに

NVIDIA に GPUで直接ストレージのIO操作を行う cuFile API の仕様が公開されているので、これをベースにDirectStorage APIを予想してみます。

cuFile API Reference Guide :: NVIDIA GPUDirect Storage Documentation

cuFile API

NVIDIA が提供するGPUメモリとストレージ間でDMA転送するためのAPIです。

Driver API

ドライバーAPIは cuFile システムを初期化するためのAPIです。

API 説明
cuFileDriverOpen cuFile システムの初期化
cuFileDriverClose cuFile システムの解放
cuFileDriverSetPollMode IO操作のポーリング利用有無の設定
cuFileDriverSetMaxDirectIOSize ドライバと通信する際の最大 IO サイズ(KB)の設定
cuFileDriverSetMaxCacheSize 内部バッファリング用の最大GPUメモリサイズの設定

IO API

基本的な入出力APIです。

API 説明
cuFileHandleRegister OS固有のファイルでスクリプタをcuFile固有ハンドルに変換
cuFileRead GPUアドレスにデータを読み込む
cuFileWrite GPUアドレスの内容をファイルに書き込む

Stream API

非同期な入出力APIです。Queueに積まれ順次処理するようです。

Operations that are enqueued with cuFile Stream APIs are FIFO ordered with respect to other work on the stream and must be completed before continuing with the next action in the stream.

API 説明
cuFileReadAsync 非同期でGPUアドレスにデータを読み込む
cuFileWriteAsync 非同期でGPUアドレスの内容をファイルに書き込む
cuStreamSynchronize ストリーム同期待ち

Batch API

複数のファイルや同じファイルの任意の場所をどこに入出力するかをまとめて定義し一括で非同期IOを発行するAPIです。こちらもQueueに積まれ順次処理しますが、バッチ内の順序は入れ替わる場合があるようです。

cuFile Batch APIs enable an arbitrary mix of read and write transactions, multiple files, and multiple locations in a file in one dispatch. Operations enqueued with cuFile Batch APIs are FIFO ordered with respect to other work on the stream, and must be completed before continuing to the next action in the stream. Individual operations in each batch might be reordered with respect to each other.

API 説明
cuFileBatchIOSubmit IO操作バッチを送信する
cuFileBatchIOGetStatus IO操作バッチの状態を取得する
cuFileBatchIOCancel IO操作バッチをキャンセルする
cuFileBatchIODestroy IO操作バッチ破棄する

DirectStorage API の予想

ストレージ処理を行うのは GPU自身なので D3D12Device を QueryInterface して IDirectStorageDevice みたいなものを取得するのが考えられます。

pD3D12Device->QueryInterface(IID_PPV_ARGS(&pStorageDevice));

IO操作に関してはゲーム用途を考えた場合、ストリーミング処理が主な用途になるので Batch API をベースとした非同期なIO操作で実装されると思います。
なにより Microsoft が推進している Sampler Feedback Streaming を利用した仮想テクスチャ(Tiled Resource Texture)ストリーミングはタイル単位で大量のIO発行する必要があるので Batch API ベースじゃないと厳しいのが大きいです。

pStorageDevice->CreateStorageQueue(IID_PPV_ARGS(&pStorageQueue));

auto handle = CreateFile(...);
pStorageDevice->CreateFile(handle, IID_PPV_ARGS(&pStorageFile));

batches[0].File = pStorageFile;
batches[0].Heap = pHeap;
batches[0].Size = 65536;
batches[0].FileOffset = 0;
batches[0].WriteOffset = 0;

pStorageDevice->CreateBatch(numBatches, &batches, IID_PPV_ARGS(&pBatch));

pStorageQueue->Submit(pBatch);

DirectStorage の出力先は ID3D12Resource ではなく ID3D12Heap になると思います。ID3D12Resource だと Reserved リソースの場合、実メモリが割り当てられていない可能性がありますし仮想テクスチャの場合、実メモリの内容が確定した後に TileMapping したほうが楽そうだしIO発行の回数を減らすにも効果的だからです。

仮想テクスチャストリーミングとDirectStorage

現在のテクスチャストリーミングがMIPレベルストリーミングが主流なのは仮想テクスチャストリーミングでは1タイル64KiB単位という小さなデータを色々な場所から大量に読み込む必要がありHDD だとランダムアクセス過多でIOパフォーマンスが最悪なのも一つの要因です。
現在のSSDであれば64KiB以上のデータであればランダムアクセスによるパフォーマンス低下はほとんど無視できるレベルなのでBatch APIベースを前提とすれば一気に実用レベルになります。

タイルデータの最適化について

現在はマッピングするテクスチャが1枚なんてことはほぼ無くてカラー/法線/スペキュラー等と複数のテクスチャが使用されています。
タイル単位で読み込む場合も当然全てのテクスチャを読み込む必要がありますが各テクスチャのタイル毎にIO発行するのは流石に無駄が多いです。
IO発行回数を減らすためには各テクスチャのタイル単位でまとめて連続したデータにする事で1回のIO発行で各テクスチャのタイルを読み込むことが可能になります。

タイル番号 カラー 法線 スペキュラー その他
0 カラー#0 法線#0 スペキュラー#0 その他#0
1 カラー#1 法線#1 スペキュラー#1 その他#1
2 カラー#2 法線#2 スペキュラー#2 その他#2
N カラー#N 法線#N スペキュラー#N その他#N

まとめ

DirectStorage の予想のはずが仮想テクスチャストリーミングなどまとまりがないない内容になりましたがこの二つは切っても切れない関係なのでとりあえず良しとします。

DirectX12 Ultimate や DirectStorage はある目的を実現するために必要になった手段に見えるので個々の機能で見るのではなく全体を見る必要があるように思います。

参考資料

cuFile API Reference Guide :: NVIDIA GPUDirect Storage Documentation
GPUDirect Storage: A Direct Path Between Storage and GPU Memory | NVIDIA Developer Blog