alphakaz’s blog

個人的備忘録

DirectXで余分にメモリを確保しないために

環境

DirectX12

対象者

DirectX12のチュートリアルを終えているが、GPUのリソース確保についてそれほど詳しくない人。

DirectX12のメモリアラインメントは基本64KB

DirectX12でリソースを確保する場合、いろんなチュートリアルや参考書にある通り、CreateCommittedResource()を使うのが一番手軽です。ヒープの確保とテクスチャ初期化をまとめて行ってくれます。

確かに楽です…が、ここでの落とし穴は、メモリのアラインメントが64KBであることです。つまり、64KB以下のメモリを確保しようとすると無駄が発生します。

 

たとえばR8の128x128テクスチャの大きさは単純計算すると16KBなので、これを素直にCreateCommittedResourceで確保しようとすると、48KBの隙間(パディング)が空きます。

隙間の空いたメモリ

たとえば、R8の128x128テクスチャを何も考えずに1000枚確保すると、16MBぐらいしか使っていないつもりでも実質64MBのメモリを消費しています。

自前でプロファイルをしているつもりでも、このアラインメントを考慮していない場合、潜在的にVRAMを大量に消費していたりします。

4KB Alignment

DirectXには、このアラインメントを4KBにする方法があります。これをうまく使えば、上のようなテクスチャを確保するときでも、無駄な隙間はなくなるはずです。

スッキリ詰められたメモリ

4KBにアラインメントされたテクスチャを確保する方法

この4KB Alignmentを使うにはいくつか条件があり、これらの条件をすべて満たす必要があります。

github.com

↑ CanUseSmallAlignment関数を参照

  • Bufferはダメ。Textureのみ
  • MSAAテクスチャ、レンダーターゲット、デプスステンシルはダメ
    • 特に、MSAA対応のテクスチャは4MB Alignmentになる
  • Texture2Dであること
  • テクスチャレイアウトがUNKNOWN
  • 4KB単位のタイルが16枚以下
    • R8の無圧縮テクスチャなら64x64などのサイズが1タイル

 

特に最後の条件はイメージしづらいと思うので、図示してみます。

例1-3

例4-6

VRAMは、整列した4KBを1タイルとします。ここからちょっとでもはみ出ると、はみ出た部分が1タイル扱いになり、タイルの枚数が16を超えると4KB Alignmentはできません。

4KB Alignmentのメモリを確保する

上記の条件を満たしているかチェックした上で、

  • D3D12_RESOURCE_DESC::Alignment = 4KBを指定する
  • CreatePlacedResourceでリソースを作成
    • CreateCommitedResource()はダメ。内部で暗黙的にヒープを一つ確保するが、このヒープの最低Alignmentが64KBだから
    •  

することで、ようやく4KBにAlignmentされたメモリを確保できます。

 

いや面倒

この判定自体は上記のリンクを参考にすれば可能ですが、まあ面倒です。
もっと簡単に、厳密に、スマートに判定をする方法はないのでしょうか?

あります。公式サンプルを見てみましょう。

github.com

D3D12SmallResources::CreateTextures()

textureDesc.Alignment=D3D12_SMALL_RESOURCE_PLACEMENT_ALIGNMENT

を指定した上でGetResourceAllocationInfo()に渡すと、そのtextureDescで可能なAlignment情報が返されます。*1

つまり、戻り値のinfoのAlignmentがSmallAlignmentに一致していれば、SmallAlignmentは可能というわけです。

なにせAPIに判定してもらっているのですから、安定したやり方です。

ですが、この方法には欠点があります。
デバッグレイヤーを有効にしたときに「4KB Alignment対応していないぞ」とエラーを吐きます*2

デバッグレイヤーを使ったデバッグ中にこのテクスチャ確保時のログが出続けたら邪魔で仕方がありません。一長一短です。

 

 

自分でやらずにアロケータを使う

そもそも、PlacedResourceを使って個人で汎用的なアロケータを作る事自体、コストが高いです。
車輪の再発明に無駄にカロリーを消費せず、素直にOSSのアロケータを使ったほうが無難でしょう。

ただ、上記の条件があることを知っているだけで、リソース設計に役立つと思います。

 

以下のOSSAMDが公開しているVRAMアロケータです。

CreateCommitedResourcesとほとんど同じインターフェースで、
4KBAlignmentも自動で判定してメモリを確保してくれます。

github.com

 

参考:

この記事に書いてあることはだいたい以下のリンク先に書いてあります。タイルについての具体的な話をしているページが見当たらなかったので、その部分について話したかった。

https://www.asawicki.info/news_1726_secrets_of_direct3d_12_resource_alignment

*1:GetResourceAllocationInfo()関数から戻ってきた値は、自前でVRAMの使用量をプロファイリングするのに役立ちます。実際に消費されているメモリ量が入っています。

*2:最新のAPIだと直っていてほしい