LoginSignup
8
7

More than 1 year has passed since last update.

DirectStorage1.1を試してみる

Last updated at Posted at 2022-12-21

DirectStorageって・・?

昨今のゲームは高精細化が進み、容量も爆増していますが、
そういった大量のデータを高速にロードするための
Microsoftが提供しているAPIです。
Windows10,11や、Xboxなどでも使えるそうです。
高速ロードは魅力的ではあるものの、他の描画技術に比べると
あまり魅力を感じてなかったのですが、ふとしたことからサンプルを見て
衝撃を受けたので、調べてみました。
なお、個人で調べたものなので、間違いなどが含まれている可能性がありますので
ご容赦ください。コメントいただければ修正いたします。

まずはどんなものか

上記のgitにあるBulkLoadDemoサンプルで試してみました。

適当なgltfファイルを28個、全部で288MBぐらいのデータですが、
これらをランダムで1024個読み込もうとしています。
(ここでは読み込めた数が1008個となっているので1024のうち
 メモリが許す範囲でロードしているみたいです)
それらをgpuに展開しています。

また、これらのgltfファイルはサンプルに含まれている
MiniArchiveというツールによって、ひとまとめにされています。
この際、gdeflateという圧縮オプションを使用することで、高速に
gpuにデータを展開しています。
gdeflateはNVIDIAによるGPUに最適化されたデータ圧縮技術だそうです。
https://forest.watch.impress.co.jp/docs/news/1454536.html

DirectStorageの本来の高速化のポイントはシステムメモリにロードしてから
GPUメモリに展開していたものを、直接GPUメモリに展開するため、
無駄が減って早くなるという原理だと思います。
更に、gdeflateで圧縮展開をgpuで行うことで早めてるという事だと
思います。
https://devblogs.microsoft.com/directx/directstorage-1-1-now-available/

HelloDirectStorage

BulkLoadDemoのデモだと、色々とコードが多いので、わかりやすい
サンプルのHelloDirectStorageから読んでみました。
このサンプルは、D3D12Deviceを初期化、DirectStorageの初期化を行って
引数で受け取ったpngのテクスチャファイルを、DirectStorageでロードし、
D3D12Resourceに展開だけするという、一見何も見た目では確認できないですが
最低限のロードのフローを実装してくれているのでとてもわかり易いです。

全体的な流れ

初期化
factoryの初期化を行います
ファイル情報取得
ファイルの実際のサイズを取得
キュー(DStorageQueue)の作成
リクエストを貯めるためのキューを作成します。
ロード先のD3D12Resourceの作成
ロード先となるCommittedResourceを作成します
リクエスト(DStorageRequest)の作成・エンキュー
ファイルとサイズ、D3D12Resourceなどをセットしたリクエストを作成します
完了待ち用のFenceの作成。キューにFenceをセット
描画完了待ちを行うのと同じ、いつものFenceです。
Submitでキューのリクエストを処理依頼
キューをサブミットすることでロードが開始されます
WaitForSingleObjectで完了待ち
完了を待ちます
エラーがなければ完了です。

初期化

initialize
    com_ptr<ID3D12Device> device;
    check_hresult(D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_12_1, IID_PPV_ARGS(&device)));

    com_ptr<IDStorageFactory> factory;
    check_hresult(DStorageGetFactory(IID_PPV_ARGS(factory.put())));

Device作成は必須です。DirectStorageで使用します。その後に
ここでは、IDStorageFactoryを生成しています。

ファイルの情報取得

get file info
    com_ptr<IDStorageFile> file;
    const wchar_t* fileToLoad = argv[1];
    HRESULT hr = factory->OpenFile(fileToLoad, IID_PPV_ARGS(file.put()));
    if (FAILED(hr)){...}

    BY_HANDLE_FILE_INFORMATION info{};
    check_hresult(file->GetFileInformation(&info));
    uint32_t fileSize = info.nFileSizeLow;

次に、ファイルサイズの情報をOpenFile()関数で作成した
IDStorageFileから取得します。
ここからがDirectStorageの処理です。

キュー(DStorageQueue)の作成

create queue
    DSTORAGE_QUEUE_DESC queueDesc{};
    queueDesc.Capacity = DSTORAGE_MAX_QUEUE_CAPACITY;
    queueDesc.Priority = DSTORAGE_PRIORITY_NORMAL;
    queueDesc.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
    queueDesc.Device = device.get();
    com_ptr<IDStorageQueue> queue;
    check_hresult(factory->CreateQueue(&queueDesc, IID_PPV_ARGS(queue.put())));

最初に作成したデバイスを渡して、
DirectStorageQueueを作成します。

ロード先のD3D12Resourceの作成

create resource
    D3D12_RESOURCE_DESC bufferDesc = {};
    bufferDesc.Width = fileSize;
    ...

    com_ptr<ID3D12Resource> bufferResource;
    check_hresult(device->CreateCommittedResource(
        &bufferHeapProps,
        D3D12_HEAP_FLAG_NONE,
        &bufferDesc,
        D3D12_RESOURCE_STATE_COMMON,
        nullptr,
        IID_PPV_ARGS(bufferResource.put())));

このあたりは、いつものリソース作成と変わらないので端折っています。
ただ、初期ステートがD3D12_RESOURCE_STATE_COMMONにする必要があるみたいです。

リクエストの作成

create request
    DSTORAGE_REQUEST request = {};
    request.Options.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
    request.Options.DestinationType = DSTORAGE_REQUEST_DESTINATION_BUFFER;
    request.Source.File.Source = file.get();
    request.Source.File.Offset = 0;
    request.Source.File.Size = fileSize;
    request.UncompressedSize = fileSize;
    request.Destination.Buffer.Resource = bufferResource.get();
    request.Destination.Buffer.Offset = 0;
    request.Destination.Buffer.Size = request.Source.File.Size;

    queue->EnqueueRequest(&request);

ファイル、ファイルサイズや、リソースをパラメーターにセットして
キューにリクエストをいれます。
ポイントとしてはDestinationTypeが、ここではDSTORAGE_REQUEST_DESTINATION_BUFFER
となっていますが、これはID3D12Resourceのバッファとしてリクエストするという
意味合いです。他にも、以下があるようです。
詳細は別記事で調査したいと思います。

DSTORAGE_REQUEST_DESTINATION_MEMORY // block of memory
DSTORAGE_REQUEST_DESTINATION_TEXTURE_REGION // texture
DSTORAGE_REQUEST_DESTINATION_MULTIPLE_SUBRESOURCES // subresources
DSTORAGE_REQUEST_DESTINATION_TILES // tile

完了待ち用のFenceを作成

create fence
    com_ptr<ID3D12Fence> fence;
    check_hresult(device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence.put())));

    ScopedHandle fenceEvent(CreateEvent(nullptr, FALSE, FALSE, nullptr));
    constexpr uint64_t fenceValue = 1;
    check_hresult(fence->SetEventOnCompletion(fenceValue, fenceEvent.get()));
    queue->EnqueueSignal(fence.get(), fenceValue);

ここはいつもの描画待ち用のFenceと変わりないので端折ります。
最後に、キューにフェンスをセットします。

Submitでキューのリクエストを処理

exec
    queue->Submit();

この命令で、DirectStorageのロード展開処理が行われます。

WaitForSingleObjectで完了待ち

wait finish
    WaitForSingleObject(fenceEvent.get(), INFINITE);

    DSTORAGE_ERROR_RECORD errorRecord{};
    queue->RetrieveErrorRecord(&errorRecord);
    if (FAILED(errorRecord.FirstFailure.HResult))
    {
        ...
    }

最後は完了をまち、結果のエラーがないかを確認すればOKです。
シンプルなサンプルで助かります。

おわりに

サンプルを追ってみただけではありますが、
思ってたより簡単に使えそうです。
次回は、BulkLoadDemoのサンプルを追ったり、パフォーマンス比較など
できればいいかなと思います。

GameEngineDevのアドベントカレンダーに初めて参加してみました。
自作ゲームエンジンに組み込んでみてはいかがでしょうか。
なお、gdeflateの圧縮、展開用のソースも公開されているようなので
他のプラットフォームのgpuでも同じように対応することは可能かと思います。

また、進みは激オソですがYoutubeチャンネルもやっているので
良ければどうぞ。DirectStorage関係も動画を作れたら作ってみたいと
思います。

8
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
7