3Dテクスチャの謎

また盛大にハマってしまった。
今回はD3D11_TEX3D_UAV::WSize様とD3D11_MAPPED_SUBRESOURCE様の2名様に翻弄されてました…

やりたかったことはComputeShaderで3Dテクスチャに書き込んでそれをCPU側で確認する、ってだけの簡単なお仕事のはずだったんだけど、例によって分かりづらいヘルプとサンプルコードの無さに悩まされました。とほほ。


で、以下メモ代わりにうまくいったコードとハマりポイントを書いていく。

まずテクスチャの作成。
以下の関数はミップマップは無し。というか、ミップマップの使い方(呼び出し方法的な意味で)が分からない…

Resolutionは自分で用意した独自の型なので気にしない方向で。コードの見たまんま、(x, y, z)のピクセル数を入れるだけの構造体。

ID3D11Texture3D *createTexture3d(ID3D11Device *device, const Resolution &resolution, DXGI_FORMAT format, D3D11_USAGE usage, D3D11_BIND_FLAG bind, D3D11_CPU_ACCESS_FLAG access, D3D11_RESOURCE_MISC_FLAG misc, const D3D11_SUBRESOURCE_DATA *data)
{
    if(device == NULL){
        return NULL;
    }

    ID3D11Texture3D         *ret;
    D3D11_TEXTURE3D_DESC    desc;

    ZeroMemory(&desc, sizeof(desc));
    desc.Width = resolution.width_;
    desc.Height = resolution.height_;
    desc.Depth = resolution.depth_;
//  desc.MipLevels = 0;     // [2013/09/08 23:10追記]こっちだとミップマップをフル階層で作っちゃう設定らしい。今回はミップマップ不要なので1固定が正解
    desc.MipLevels = 1;
    desc.Format = format;
    desc.Usage = usage;
    desc.BindFlags = bind;
    desc.CPUAccessFlags = access;
    desc.MiscFlags = misc;

    device->CreateTexture3D(&desc, data, &ret);

    return ret;
}

ここは(dataをNULL指定していたので)特にハマらなかった。


次。UAV。
UAVってなんぞ?と思ったけど、どうやらシェーダからread/writeできるテクスチャ、バッファ類はUAV経由になるらしい。テクスチャとしてreadしかしないならSRVでいけるっぽい。

ID3D11UnorderedAccessView *createUav3d(ID3D11Device *device, ID3D11Texture3D *texture, DXGI_FORMAT format)
{
    if(device == NULL){
        return NULL;
    }
    if(texture == NULL){
        return NULL;
    }

    D3D11_TEXTURE3D_DESC                texture_desc;
    texture->GetDesc(&texture_desc);

    ID3D11UnorderedAccessView           *ret;
    D3D11_UNORDERED_ACCESS_VIEW_DESC    uav_desc;

    ZeroMemory(&uav_desc, sizeof(uav_desc));
    uav_desc.Format = format;
    uav_desc.ViewDimension = D3D11_UAV_DIMENSION_TEXTURE3D;
    uav_desc.Texture3D.WSize = texture_desc.Depth;
    uav_desc.Texture3D.MipSlice = 0;
    uav_desc.Texture3D.FirstWSlice = 0;

    device->CreateUnorderedAccessView(texture, &uav_desc, &ret);

    return ret;
}

で、ここでハマった。
D3D11_TEX3D_UAV::WSize。これがヘルプだと

The number of depth slices.

って書いてある。
ん?
スライスってなんぞ??
MipSliceは

The mipmap slice index.

FirstWSliceは

The zero-based index of the first depth slice to be accessed.

とか書いてある。
WSizeもミップマップ関係?とか思って最初1を指定してみた*1んだけど、これがいけなかった。

UAVのCreateは成功する。うん。成功してしまったんだ。
でも、シェーダを走らせてMap()したらZ=0の領域しかそれっぽいデータが書き込まれていないんだ。
Z>0の領域はきれいにall 0になってる。

シェーダコードとかいろいろ見なおしてみて最後にようやくWSizeがあやしいって話になったんだけど、正しい値が分からない。0なのか−1なのかと思いつつ「D3D11_UNORDERED_ACCESS_VIEW_DESC D3D11_UAV_DIMENSION_TEXTURE3D」とかでググりまくってようやくGoogleCode*2で上記のDepthを設定しているコードを発見した。*3

ということで、WSizeにはZ方向のピクセル数を指定するらしい。
意味がわからん。なんでZだけ??
てか、CreateUnorderedAccessView()の第一引数ID3D11Texture3D *だよね?
内部でGetDesc()すればわかるよね?

今さらDirectX関連の不親切っぷりにどうこう言おうと思わないけど、どうなんだろうね。

シェーダ側は(書き方が分かれば)単純で。

RWTexture3D<float4> g_result;

とか書いておいて

    float4      value;
    uint3       address;

    g_result[address] = value;

てな感じ。
g_resultがu0レジスタにわりあたってID3D11DeviceContext::CSSetUnorderedAccessViews()で配列0番目に指定したUAVとひもづけられる。
このあたりも大丈夫だった。



で、話はこれで終わらなくてMap()でもらえるD3D11_MAPPED_SUBRESOURCEにも罠がっ。
ヘルプによると
RowPitch

The row pitch, or width, or physical size (in bytes) of the data.

DepthPitch

The depth pitch, or width, or physical size (in bytes)of the data.

って書いてある。

これを読んで、テクスチャはピクセル、バッファはバイトの単位かなぁとか思っちゃいました。
ますよね?ね?

3DテクスチャをMap()したら普通にバイト単位で返ってきました。
えぇ。バイト単位で返ってきましたとも。

特にアドレス調整の詰め物とかもなくて全部アドレスが連続してるのでバイト単位にする必要が無いのに。てか、じゃぁバイト単位じゃないときってどんな時?
てか、どこ見て単位を判別すればおk?
GetDesc()した値と見比べないとダメ?

とりあえずバイト単位ってことで進めるけど、他の環境で同じ動きになるのか不安だなぁ。



[2013/05/28 22:30追記]
今読んでいる書籍*4になんと3D UAVの説明が書いてあった!

「2.2 Resources in Detail」の「Texture3D unordered access view.」によるとFirstWSliceからWSize分だけの領域がUAV経由で見える領域になるらしい。単位はピクセル
図で書くとこういうことに。(図中オレンジ部が指定領域。書籍にも同じような図がある。Figure 2.43参照)

つまりテクスチャ全体にアクセスする場合(大半の場合はこれに該当するかと)は、FirstWSlice=0、WSize=Z方向のpixel数になるみたい。
ってこんなもん、ヘルプから読み取れるかー!!(#ノー_ー)ノ彡┻━┻


同書籍を読んだ感触から自分が得られた知見としては、どうやらResource Viewというのはデータベース用語でいうところの「ビュー」に近い概念のように思える。
データベースのビューは実体の表からSQLで「一部を切り出して」見るための特殊イテレータ的な感触がする。

iter->value

とかやるとイテレータ側で表の一部だけを抜き出してくれる的な。UAVはデータベース用語でいうとカーソルみたいな。
で、DirectXのResource Viewもresourceの一部だけを抜き出すことができようになっているようだ。その辺りの「一部」の指定にdescriptorを使う模様。ヘルプに書かれている「slice」ってのがこの「一部」の指定に使うんだね。
上記図に書いたオレンジ部のうち、1pixel分の薄い板が「slice」で、1pixxelの薄いsliceがWSize個連続で固まってUAVとして見えるってことか。
ヘルプのWSizeに書かれている

The number of depth slices

ってのはそういう意味なんだね。
せめて「width×height×1pixelの薄い板をsliceと呼ぶ。sliceの塊をUAVとして見せる」みたいな説明があるだけでも違うのに。
わかりづらい…ヘルプにも図入りで説明載せて欲しいよねー(*´・ω・)(・ω・`*)ネー
しかも、ぶっちゃけ全領域にアクセスしたい場合はこの仕組み自体がいらないよね。やれやれ。┐( ̄ヘ ̄)┌

ところで、この書籍、まだ1/3しか読んでないんだけどDirectXの説明があまりに詳しすぎてヤバい。ヘルプに書かれていない詳しい話がかなり書かれている。正直この手の書籍は前半部は「ヘルプとか数学の解説書読めばわかるよね」って感じで期待してないんだけど、予想を良い意味で裏切られて超うれしい誤算だった。
「著者さんいったい何者!?もしかしてMicrosoftの中の人?」とか思ってググってみたら、3名のうち、2名はXNA/DirectXのMVP認定を受けた方で超納得。
「そもそも公式ヘルプにこの書籍のレベルでちゃんと書いてくれていれば済むのにね」と思いつつ、今後もこの書籍はマイ参考書籍になりそうな予感。

*1:だって、D3D11_TEX2D_SRV::MipLevelsとかは1にするじゃん?

*2:http://gshow.googlecode.com/svn-history/r348/trunk/GSBaseClass/GSD3DLib/GSTexture3D.cpp

*3:DirectXのサンプルには3Dテクスチャに書き込むものが見当たらず…

*4:Jason Zink, Matt Pettineo, Jack Hoxley「Practical Rendering and Computation With Direct3D 11」A K Peters/CRC Press(July 27, 2011) p.101