テクスチャフォーマットとHLSL記述の対応

調べたことが、ある程度まとまってきたので忘れる前にメモメモ...


以前DXGI_FORMAT_R8G8B8A8_UNORMの「UNORM」って何だろうって思ってた件は、「Windows DirectX Graphics Documentation」ヘルプの「DXGI」−「Reference」−「Enumerations」−「DXGI_FORMAT」ページの「Remarks」欄「Format Modifiers」表にある「_UNORM」の説明を読んでたら、どうやら8ビットの0〜255値を0.0f〜1.0fの浮動小数値と解釈するようなフォーマットのように見えてきた。
つまり、”VRAMやテクスチャファイル上では8ビット値で保存するけど、計算するときの数値としては0.0f〜1.0fのつもり”って形式かなと。
「Unsigned NORMalized integer」って名前らしいけど、0〜1ってのが「normalized」で8ビットってあたりが「unsigned integer」かな?(^^;


でも、よくよく考えたらPixel ShaderのHLSLは普通にfloat型でテクスチャデータを読み出して計算してRenderTargetへ投げてるような気がするので

  • HLSLでfloatって書いたのをビデオカードのドライバが適切に8ビット型に読みかえてる
  • シェーダの前後で誰か(GPUの他ブロック)が変換している

のどちらかじゃないとつじつまが合わない気がする。


  • ってことで、探してきた状況証拠その1
    • PIX先生でデバッグすると小数点数値で表示する

ただ、これは単にPIX先生が偉大なだけかもしれない(内部で変換してから表示してくれている可能性)

.cpp側でfinal pass用のPSSetShaderResources(aRViews)を呼んでいるときに、aRViews[0] : g_pTexRenderRV11(g_pTexRender11)はDXGI_FORMAT_R32G32B32A32_FLOAT、aRViews[2] : g_apTexBloomRV11[i](g_apTexBloom11[i])はDXGI_FORMAT_R8G8B8A8_UNORM指定でBufferを作ってる。ようするに2つのテクスチャの片方がHDRフォーマットで片方がSDRフォーマットになってる気がする。

それで、HLSL側では

Texture2D tex : register( t0 );
Texture2D bloom : register( t2 );

てな感じで両方ともfloatになってて特に区別してない。

ソースのどこかで指定しているのを見逃していないと仮定すると、誰かが”シェーダの前後でfloatと固有フォーマットの変換”をやってないとおかしい(片方はSDR、もう片方はHDRで格納フォーマットが違うのに、HLSL上では同じfloatを使って特に変換などせず計算しているため)。逆に変換をやっていると仮定するとつじつまが合う。


  • 考察

で、結局のところ確実な証拠は見つかってないわけだけど。てか、こ〜ゆ〜話はヘルプに載ってるんだろうか…
「HLSL」−「Reference」−「Shader Models」−「Shader Model 4」−「Texture Object」−「Sample」ページの「Return Value」欄に至っては「(戻り値は)DXGI_FORMATの一覧にある形式だよ(意訳)」とか言っててDirectXのサンプルコードとつじつまあってないし。そもそも「HLSL」−「Reference」−「HLSL Intrinsic Functions」−「tex2D(s, t) 」ページの「Type Description」表だと思いっきりretはfloat4になってるわけだし。同じ機能と思われる命令間で戻り値を変える必然性は無いと思う。
ちなみに「DXGI」−「Programming Guide」−「Hardware Support for Direct3D 11 Formats」ページに表があるんだけど、Format Target #10ってのが「Shader sample (any filter)」なので、この表の#10列で「X」になってるフォーマットは自動変換してくれるのかもしれない。わかんないけど。

まぁとにかく仮に誰かが変換してくれるとして、その”誰か”は実在しそうかどうかって話なんだけど、5870の場合、ゴトウさんの記事(http://pc.watch.impress.co.jp/docs/column/kaigai/20090924_317309.html)によるとThread Processorの手前にはTexture Unitがいて後ろにはRender Back-Endがいることになってる。ちょうどこいつらはVRAMにアクセスする機能ブロックだし、変換機能が載っていてもおかしくはないよね。
仮にそうだとするとSDRテクスチャをreadするときにTexture UnitがUNORMからfloatへ変換してwriteするときにRender Back-EndがfloatからUNORMへ変換してるってことになる。本当かどうかは確かめるすべが分からないけど、そうなっていてもおかしくない気はする。
(余談だけど、16ものThread Processorから一斉にread/writeして処理をさばき切れるのかが気になるなぁ。命令は4回分1セットで発射されるみたいだから64個分も一気にreadやらwriteしなきゃいけないけど。writeはレイテンシが大きくてもどんどん次の処理を進めれば良いけど、readはレイテンシが大きすぎるとシェーダがやれることなくなりそうな気がする。最初にテクスチャreadしておいて、テクスチャを使わない計算を実行しながら待つにしても限度があるよね…
って、考えてたらゴトウさんの記事によると8 contextまで覚えていてくれるみたいなので、Pixel Shader以外のテクスチャアクセスしない他のシェーダを7種類走らせていればレイテンシを隠せるかも。あと、命令順はドライバ側で並べ替えるらしいので気にする必要がなさそう)



とりあえず、HLSL側でフォーマットの心配をしなくて良さそうなのはうれしい。SDRでもHDRでも同じように扱えるってことだよね。HDRToneMappingCS11サンプルはそうなってるように見えるし。

ところで、OpenCLにはread_imagefとwrite_imagefって専用の変換機能付きread/write命令があるみたい。コンパイル済みコードを逆アセンブルしたら何か分かるかもね。普通のSAMPLE命令しか無かったら変換はGPU側でやってるって証拠になるかもしれないし。いや、ダメかな。ドライバで読みかえてる可能性を否定できないか。

[2011/01/13 00:15]追記
Direct3D 10」−「Programming Guide」−「Resources」−「Data Conversion Rules」ページに詳しい変換ルールが載っているみたい。
けど、"いつ"・"誰が"変換するのかは書いてない気がする…

[2011/01/19 23:50 追記]
ちょうど今読んでる書籍*1にそれらしき記述を発見した。シェーダには変換済みの値が来ると思ってほぼ間違いないかと。

<原文>
This may look a little bit weird since you're expecting the R, G, and B values to be byte values. But since you sample them from a texture, the pixel shader automatically converts the color bytes into float values(which are in the range of 0 to 1 for each color channel).<スーパー意訳タイム>
あなたはRGB値がバイトデータだと思っているから、これはちょっとおかしく見えるかもしれない。しかしテクスチャから値を読み出したら、Pixel Shaderが自動的にバイト値からfloat値(各RGBチャネルごとに0〜1の範囲)へ変換してくれるのだ。

*1:Carl Granberg「Character Animation With Direct3D」Charles River Media(2009/04/21) p.261