日別アーカイブ: 2009年10月8日

Direct3D 11 / DirectX 11 UnorderedAccessView と RenderTarget の関係

DirectX10 で大きく仕様が変わったのは、より汎用化を進めるためでした。
つまり専用の機能が無くなったということ。
ライティングのための機能、マテリアルの設定などといった目的別の API は
完全に姿を消しています。

では CPU のように完全に何にでも使える、単純でフラットな空間が得られたのかと言えば必ずしも
そうではなく、特にリソースとバッファの扱いはかえって複雑になった印象を受けます。

汎用化や何にでも使えることが、そのまま単純化に繋がるとは限らないためです。
複数の機能の要素をマージしたこと、目的別の機能の代わりにより抽象的な用語や仕様が
増えたことがその要因といえるかもしれません。

DirectX11 はさらに CompteShader が使えるようになり、書き込み可能なバッファが
追加されています。

以下 FEATURE_LEVEL 11.0、 ShaderModel 5.0 を対象としています。

●リソース

Direct3D 11 では View も ShaderObject も種類が増えており何を見て良いのか
わからなくなります。
でもリソースを作る手段は二つだけ。CreateBuffer() か CreateTexture~() です。
Constant も Buffer だし、Vertex や Index もただの Buffer です。

● View

リソースをどう扱うか、それを決めるのが View です。
同じリソースを複数の View を通して見ることが可能で、テクスチャとして参照したり
レンダリングしたり、データとして読み書きするなど流用が可能となります。

View は次の 4種類だけです。

・読み込みのための View、テクスチャやバッファなど
  ShaderResourceView (SRV)

・Merger による書き込みのための View
  RenderTargetView (RTV)
  DepthStencilView (DSV)

・読み書きのための View
  UnorderedAccessView (UAV)

VertexBuffer/IndexBuffer/ConstantBuffer は View を用いずに設定します。

●タイプとシェーダーオブジェクト

データをアクセスする方法を決定するのが、バッファ作成時の細かい指定と
DXGI フォーマットタイプです。

Texture
Buffer
ByteAdddressBuffer
StructuredBuffer
AppendSturctutedBuffer
ConsumeStructuredBuffer
RWTexture
RWBuffer
RWSturcturedBuffer
RWByteAddressBuffer

これらのシェーダーオブジェクトは、シェーダーがアクセスする方法を決定しています。
シェーダー側の宣言だけではだめで、リソース生成時にもいろいろとフラグを指定
しておく必要があります。

● UnorderedAccessView

RW がついているものと AppendSturctutedBuffer/ConsumeStructuredBuffer は
書き込み可能なシェーダーオブジェクトです。
これらは UnorderedAccessView (UAV) を使います。

UAV が使えるのは PixelShader と CompteShader だけ。
Compte Shader は Output Merger が無いので、出力のために必ず何らかの UAV を
用いることになります。

実際に使ってみると、出力はすべて UnorderedAccessView に相当することがわかってきます。
つまり RenderTarget も出力先アドレスが固定された UnorderedAccessView 相当だということです。

UAV の設定 API は次の通り

CompteShader → CSSetUnorderedAccessViews()
PixelShader  → OMSetRenderTargetsAndUnorderedAccessViews()

OMSetRenderTargetsAndUnorderedAccessViews() で一見何のために存在する
パラメータなのかわかりにくいのが UAVStartSlot です。
これは RenderTarget との衝突を避けるためのものでした。

例えば RenderTarget を 2 枚設定した場合は、UnorderedAccessView は必ず
番号 2 から始めなければなりません。

下記のようなリソースを登録する場合

RenderTarget x2
UnorderedAccessView x3

OMSetRenderTargetsAndUnorderedAccessViews() には次のようなパラメータを
渡します。

NumViews = 2          // RTV (RenderTargetView) の個数
UAVStartSlot = 2      // UAV (UnorderedAccessView) の開始番号 == NumViews
NumUAVs = 3           // UAV の個数

※ August 2009 のマニュアル OMSetRenderTargetsAndUnorderedAccessViews()
の引数は若干間違いがあります。ヘッダファイルを見た方が正しい宣言です。

シェーダーレジスタはこのように割り当てられます。

o0 = RenderTarget
o1 = RenderTarget
u2 = UnorderedAccessView
u3 = UnorderedAccessView
u4 = UnorderedAccessView

つまり o レジスタと u レジスタは同時に同じ番号を使うことが出来ず、
使用可能な View はあわせて 8 個まで。この両者ほぼ同じ扱いです。

UnorderedAccessView は読み書き可能なバッファです。
よく考えると RenderTarget や DepthBuffer と同じこと。
UAV は読み込んだあとにブレンドしたり、判定した結果をまた書き戻すことが出来るわけです。
そのパス内で更新できる OM をプログラムできるなら、いろいろ面白いことが出来そうです。

実際試してみたらエラーで、RW で読み込めるのは sngle コンポーネントに限ると言われます。
時間がなかったので、このあたりは後ほどまた詳しく調べてみます。

昔わからないと書いた CSSetUnorderedAccessViews() の最後のパラメータは
Append/Consume buffer に関するものでした。
やはり当時のサンプルの使い方が間違っていたのだと思われます。

関連エントリ
DirectX 11 / Direct3D 11 と RADEON HD 5870 の caps
Direct3D11/DirectX11 (7) テセレータの流れの基本部分
その他 Direct3D 11 関連