2007/10/31
Direct3D 10 ShaderModel 4.0 半透明ソート補足
元の記事エントリ
・Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う
少々わかりにくかったかもしれませんので補足します。
実際のシーンのレンダリング時に、フレームバッファの各ピクセル単位で
描画履歴を全部保存しています。実際に書き込まれた色とZ深度、そして
そのピクセルに何回描いたか。
これを浮動少数 float4 ×2~4 に encode して詰め込み、最大 6回分
格納します。
最後に encode された情報を全部 decode して、順番にピクセルごとに
blend しなおすわけです。
これらの処理は各ピクセルごとに独立しています。回数判定もピクセル
単位なので、ポリゴンの重なりや Draw 命令の回数等とは関連しないし
影響を受けることもありません。
またこの処理は Hardware の Blend 機能だけで実現する必要があります。

32bit float には演算によって 8bit ×3個まで値が格納できます。
IEEE754 の単精度の仮数部は 23bit ですが実際は 24bit の情報量が
あるためです。
3回以上値が加算されてしまうと、デコードするときに不要な情報が混ざり
ノイズとなります。Blend 演算だけで必要なピクセルの選択も行わなければ
なりません。
これは浮動少数単精度の制限を使います。3回以上描き込まれたピクセルの
値は仮数部の精度の範囲外に押し込み、必要な値だけ残るようにします。
まとめると
・float に複数の値を正確に畳み込んで、あとから取り出す手段
・Blend 機能だけで値を encode する手段
・Blend 機能だけで履歴(回数)による値の選択と切捨てを行わせる手段
が必要となるわけです。
AlphaToCoverage との違いは次のとおりでしょうか。
・粒状感が出ない
・ブレンドアルゴリズムを任意に持てる (SrcAlpha+InvSrcAlpha 以外も可能)
・最後に Shader でブレンドするので、Hardware Blend 機能より高度な
演算も実装できる
MRT を増やして追加パラメータを格納すれば、ソート後のブレンド時に
ピクセルごとに演算手法の選択もできるかもしれません。
またアルゴリズム的に大変応用が利くので、フレームバッファに情報を
蓄積していく技としてさまざまな手法に活用することができます。
Z剥がしの方法とは違い、シーンのレンダリングは一度で済むのでそこそこの
速度で動作しています。
欠点は
・現在最大6回までしか保持できない
・浮動少数演算の丸め対策で現状 7bit 精度になっている(6 layerのみ)
・保存する分だけメモリと 帯域を かなり 使う
・並べ替えなど、動的分岐の多い重いシェーダーが走る
などなど。
・Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う
少々わかりにくかったかもしれませんので補足します。
実際のシーンのレンダリング時に、フレームバッファの各ピクセル単位で
描画履歴を全部保存しています。実際に書き込まれた色とZ深度、そして
そのピクセルに何回描いたか。
これを浮動少数 float4 ×2~4 に encode して詰め込み、最大 6回分
格納します。
最後に encode された情報を全部 decode して、順番にピクセルごとに
blend しなおすわけです。
これらの処理は各ピクセルごとに独立しています。回数判定もピクセル
単位なので、ポリゴンの重なりや Draw 命令の回数等とは関連しないし
影響を受けることもありません。
またこの処理は Hardware の Blend 機能だけで実現する必要があります。

32bit float には演算によって 8bit ×3個まで値が格納できます。
IEEE754 の単精度の仮数部は 23bit ですが実際は 24bit の情報量が
あるためです。
3回以上値が加算されてしまうと、デコードするときに不要な情報が混ざり
ノイズとなります。Blend 演算だけで必要なピクセルの選択も行わなければ
なりません。
これは浮動少数単精度の制限を使います。3回以上描き込まれたピクセルの
値は仮数部の精度の範囲外に押し込み、必要な値だけ残るようにします。
まとめると
・float に複数の値を正確に畳み込んで、あとから取り出す手段
・Blend 機能だけで値を encode する手段
・Blend 機能だけで履歴(回数)による値の選択と切捨てを行わせる手段
が必要となるわけです。
AlphaToCoverage との違いは次のとおりでしょうか。
・粒状感が出ない
・ブレンドアルゴリズムを任意に持てる (SrcAlpha+InvSrcAlpha 以外も可能)
・最後に Shader でブレンドするので、Hardware Blend 機能より高度な
演算も実装できる
MRT を増やして追加パラメータを格納すれば、ソート後のブレンド時に
ピクセルごとに演算手法の選択もできるかもしれません。
またアルゴリズム的に大変応用が利くので、フレームバッファに情報を
蓄積していく技としてさまざまな手法に活用することができます。
Z剥がしの方法とは違い、シーンのレンダリングは一度で済むのでそこそこの
速度で動作しています。
欠点は
・現在最大6回までしか保持できない
・浮動少数演算の丸め対策で現状 7bit 精度になっている(6 layerのみ)
・保存する分だけメモリと 帯域を かなり 使う
・並べ替えなど、動的分岐の多い重いシェーダーが走る
などなど。
2007/10/28
Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う
追記 2007/11/02: Direct3D 10 ShaderModel4.0 Stencil Routed A-Buffer とお詫び も見てください。
半透明描画時に、ピクセル単位でソートを行うシェーダーを考案しました。

とりあえずデモプログラムをご覧ください。(ソース付)
実行には DirectX10 環境 (Vista+GPU) と
DirectX SDK November 2007 の Runtime が必要です。
・wheelhandle_ss10t.zip
ピクセル単位でソートしているので、順番が正しく保たれるだけでなく
半透明ポリゴンが交差しても矛盾しません。
こちらがソートありで

ソートなしにするとこうなります。

ソートしないと、teapot のオブジェクト間だけでなく、自分自身の取っ手や
ふたの重なりにも矛盾が生じていることがわかるかと思います。
●特徴
この手法の特徴は本当にソートしてブレンドしていることと、GPU だけで処理が
完結していることです。CPU は何もしていません。
またシーンのレンダリングは一回で済み、レイヤー単位でレンダリングしなおす
必要はありません。
●欠点
ピクセル単位で最大 layer 数(重なり回数)制限があります。
2種類のシェーダーを作成しています。それぞれ pixel 単位で 3枚までのものと、
6枚までです。
MultiRenderTarget かつ 128bit pixel を使っているため、それなりにメモリと
帯域を消費します。速度についてはまだ未評価です。
今の方法では格納できる値に制限があるため、ソート時に参照する Z の精度に
限界があります。14~16bit なので、交差面の境界が荒くなる可能性があります。
●ソートが必要な理由
Direct3D 10 では GeometryShader の導入によって、より自由度が高まりました。
ポリゴンの動的な追加削除が可能で、また StreamOutput を使ってジオメトリ
の更新も GPU だけでできるようになっています。
反面ジオメトリの決定や描画において CPU を介さないということは、CPU に
よる半透明ソートができなくなることも意味しています。
これを解決する手段として、ソートが不要な加算半透明だけ用いる方法があります。
また Direct3D では、MultiSample + AlphaToCoverage を使う方法が有望と
されています。これはアルファブレンドの代わりに高密度の点描を行い平均化
するものです。
今回のデモ ss10 のアルゴリズムは全く異なるアプローチで、本当にピクセルを
ソートしてしまいました。
● 3 layer シェーダー
・8bit のフルカラー画素値を用いることができます。
・半透明描画の重なりはピクセルごとに 3枚までです。
・透明度(Alpha)は各ピクセル単位で独立した値を持つことができます。
・3枚を超える重なりは、最後に描画された 3pixel が用いられます。Z 値を
考慮した選択ではないので、重なりが多いと不定の色に見えることがあります。
・MRT が 2枚なので 6 layer より効率はよくなっています。
・ソート時の Z 精度が 16bit あるためで 6 layer より実用的です。
● 6 layer シェーダー
・R G B A 各チャンネルは 7bit カラーに制限されます。
・その代わり半透明描画の重なりはピクセルごとに 6枚まで対応可能です。
・透明度(Alpha)は各ピクセル単位で独立した値を持ちます。
・6枚を超える重なりは、最初に描画した 3pixel と最後に描画された 3pixel
が用いられます。6枚を超えると 3layer 同様に不定の色に見えることがあります。
・MRT が 4枚必要で、それなりにメモリを帯域を消費します。
・ソート時の Z 値が 14bit なので、交差したポリゴンの境界などの精度が
3layer に劣ります。
●原理とアイデア
MRT (MultiRenderTarget) と Blend 機能だけを使い、レンダリングした
ピクセルの蓄積を行っていることが最大の特徴です。
レンダリング時に、直前の結果を即時反映させることができるのは
RenderTarget か DepthStencil しかありません。これらの機能の組み合わせ
だけで、描画したピクセルの選択と判定、蓄積のためのエンコードを実現する
必要があります。いくら Shader に自由度があっても、同じパスで出力を
受け取ることができないからです。
なお今回は DepthStencil は使用していません。
Direct3D 10.0 (ShaderModel4.0) では、128bit 浮動少数フォーマット
R32G32B32A32_FLOAT による Blend が可能です。これを利用しています。
下記の表は、CPU で Pixel 値のシミュレーションを行ったものです。
0x10 から順番に 0x11, 0x12 ~ とカラー値を同じピクセルに重ねていきます。
pixel ~ が書き込まれたフレームバッファの画素値、decode ~ がデコード
して取り出したカラーを表しています。
デコード結果を見ると、Blend 機能を使った積和演算のみでも、最初の 3値と
最後の 3値が正しく保持されていることがわかります。
アルゴリズム U と D のどちらかを使えば 3 layer が実現可能で、両方組み
合わせることで 6layer が実現できます。
このように、制限があるのは演算アルゴリズム上の問題なので、MRT 数を
増やしたとしても単純にレイヤー数が増えるわけではありません。
画素の割り当て下記のとおり。
●浮動少数演算の丸め問題
Blend の演算機能と浮動小数値の特性を使って、ピクセルの蓄積と適切な
値の選択を行っています。このとき非常に厄介な問題が、浮動少数演算時の
丸め処理です。
入力 3 値まではこの問題が発生しないため、当初実現できたのは 3 layer
のみでした。3 値を越えると桁あふれによって追い出された bit が丸め込まれ、
上位 bit に影響を与える可能性があります。
例えば
Algorithm-U: C B A
と入力された段階で D を入力すると A が切り捨てられます。このとき A の
値の重さによって上位 D C B に影響が出ます。例えばすべて A B C D が
すべて 0xff だった場合、あふれた 0xff の繰り上がりの 1 によって
上位すべて 0 になってしまいます。影響は無視できません。
この問題を回避するために、失われたピクセル値が何であったのか推測する
必要があります。もし丸め込まれていたら decode 時に減算すればいいわけです。
捨てられたピクセルは情報として保持されていませんが、同時に Algorithm-D
を用いることによって、6 値までなら失われた値を相互に補完することが
できそうです。
step3
Algorithm-U: C B A
Algorithm-D: C B A
step4
Algorithm-U: D C B
Algorithm-D: C B A
step5
Algorithm-U: E D C
Algorithm-D: C B A
step6
Algorithm-U: F E D
Algorithm-D: C B A
この場合、step5 なら U の捨てられた値は D の B で参照でき、step6 なら
D の C で参照することができます。
同じように Algorithm-D でも入力値を捨てたことによるまるめこみが入ります
が、Algorithm-U から求めることができます。
一見うまくいきそうですが、双方同時にまるめこみが発生した場合に残念ながら
適切な値をとることができませんでした。おそらく実現できるのは 5 layer
までと考えられます。U と D で反転した値を入力しておくことで、同値の
ずれを割り出すことができます。
この問題を解決ができなかったので、6 layer シェーダーでは値を 7bit に
小さくすることで、とりあえず桁上がりが発生しないようにしています。
8bit フルに格納できなかったのが残念です。
●今後に向けて
Direct3D 10.1 / ShaderModel4.1 では、Blend 機能が大幅に拡張されます。
特に MRT 単位で独立した Blend パラメータを設定でき、また UNORM/SNORM
フォーマットでの Blend も可能となります。
これにより、今よりも実装も実現もずっと楽になると考えられます。
layer を増やせるかもしれません。
とりあえず実現が目標だったので・・速度最適化などはほとんど行っていません。
特に並べ替えの実装はあまりに手抜きなので、まだまだ改善の余地は多く
残されていると思います。
シミュレーションのあと実際にシェーダーとして実装したところ、予想と異なる
挙動がありました。CPU と GPU による浮動少数演算の違いだと考えられます。
ss10 ではごまかしが入っているので・・改善しないと。
layer オーバー時に、Stencil を使って最前面の pixel だけ選択できないか
も考えていました。Stencil Test の結果と Depth Test の結果の組み合わせ
によって Pixel を捨てるかどうか自由に決められればできそうです。
現状は Stencil Test の結果だけで決まり、Depth Test との組み合わせは
Stencil の更新方法の選択のみなのでうまくいきませんでした。
半透明描画時に、ピクセル単位でソートを行うシェーダーを考案しました。

とりあえずデモプログラムをご覧ください。(ソース付)
実行には DirectX10 環境 (Vista+GPU) と
DirectX SDK November 2007 の Runtime が必要です。
・wheelhandle_ss10t.zip
[SPACE] pause 一時停止 [3] 3 layer mode [6] 6 layer mode [S] sort ありなし切り替え [U] + teapot 追加 [D] - teapot 削除
ピクセル単位でソートしているので、順番が正しく保たれるだけでなく
半透明ポリゴンが交差しても矛盾しません。
こちらがソートありで

ソートなしにするとこうなります。

ソートしないと、teapot のオブジェクト間だけでなく、自分自身の取っ手や
ふたの重なりにも矛盾が生じていることがわかるかと思います。
●特徴
この手法の特徴は本当にソートしてブレンドしていることと、GPU だけで処理が
完結していることです。CPU は何もしていません。
またシーンのレンダリングは一回で済み、レイヤー単位でレンダリングしなおす
必要はありません。
●欠点
ピクセル単位で最大 layer 数(重なり回数)制限があります。
2種類のシェーダーを作成しています。それぞれ pixel 単位で 3枚までのものと、
6枚までです。
MultiRenderTarget かつ 128bit pixel を使っているため、それなりにメモリと
帯域を消費します。速度についてはまだ未評価です。
今の方法では格納できる値に制限があるため、ソート時に参照する Z の精度に
限界があります。14~16bit なので、交差面の境界が荒くなる可能性があります。
●ソートが必要な理由
Direct3D 10 では GeometryShader の導入によって、より自由度が高まりました。
ポリゴンの動的な追加削除が可能で、また StreamOutput を使ってジオメトリ
の更新も GPU だけでできるようになっています。
反面ジオメトリの決定や描画において CPU を介さないということは、CPU に
よる半透明ソートができなくなることも意味しています。
これを解決する手段として、ソートが不要な加算半透明だけ用いる方法があります。
また Direct3D では、MultiSample + AlphaToCoverage を使う方法が有望と
されています。これはアルファブレンドの代わりに高密度の点描を行い平均化
するものです。
今回のデモ ss10 のアルゴリズムは全く異なるアプローチで、本当にピクセルを
ソートしてしまいました。
● 3 layer シェーダー
・8bit のフルカラー画素値を用いることができます。
・半透明描画の重なりはピクセルごとに 3枚までです。
・透明度(Alpha)は各ピクセル単位で独立した値を持つことができます。
・3枚を超える重なりは、最後に描画された 3pixel が用いられます。Z 値を
考慮した選択ではないので、重なりが多いと不定の色に見えることがあります。
・MRT が 2枚なので 6 layer より効率はよくなっています。
・ソート時の Z 精度が 16bit あるためで 6 layer より実用的です。
● 6 layer シェーダー
・R G B A 各チャンネルは 7bit カラーに制限されます。
・その代わり半透明描画の重なりはピクセルごとに 6枚まで対応可能です。
・透明度(Alpha)は各ピクセル単位で独立した値を持ちます。
・6枚を超える重なりは、最初に描画した 3pixel と最後に描画された 3pixel
が用いられます。6枚を超えると 3layer 同様に不定の色に見えることがあります。
・MRT が 4枚必要で、それなりにメモリを帯域を消費します。
・ソート時の Z 値が 14bit なので、交差したポリゴンの境界などの精度が
3layer に劣ります。
●原理とアイデア
MRT (MultiRenderTarget) と Blend 機能だけを使い、レンダリングした
ピクセルの蓄積を行っていることが最大の特徴です。
レンダリング時に、直前の結果を即時反映させることができるのは
RenderTarget か DepthStencil しかありません。これらの機能の組み合わせ
だけで、描画したピクセルの選択と判定、蓄積のためのエンコードを実現する
必要があります。いくら Shader に自由度があっても、同じパスで出力を
受け取ることができないからです。
なお今回は DepthStencil は使用していません。
Direct3D 10.0 (ShaderModel4.0) では、128bit 浮動少数フォーマット
R32G32B32A32_FLOAT による Blend が可能です。これを利用しています。
RTc= RTc + Pc * RTa RTa= RTa * Pa RTc = RenderTarget color RTa = RenderTarget alpha Pc = Input Pixel color Pa = Input Pixel alpha (U:2^8, D:2^-8)
下記の表は、CPU で Pixel 値のシミュレーションを行ったものです。
0x10 から順番に 0x11, 0x12 ~ とカラー値を同じピクセルに重ねていきます。
pixel ~ が書き込まれたフレームバッファの画素値、decode ~ がデコード
して取り出したカラーを表しています。
step 0 pixel U: hex=41800000 float=16 pixel D: hex=41800000 float=16 decode U: 10 decode D: 10 step 1 pixel U: hex=45888000 float=4368 pixel D: hex=41808800 float=16.0664 decode U: 11 10 decode D: 11 10 step 2 pixel U: hex=49908880 float=1.18402e+006 pixel D: hex=41808890 float=16.0667 decode U: 12 11 10 decode D: 12 11 10 step 3 pixel U: hex=4d989088 float=3.19951e+008 pixel D: hex=41808891 float=16.0667 decode U: 13 12 11 00 decode D: 20 12 11 10 step 4 pixel U: hex=51a09891 float=8.62193e+010 pixel D: hex=41808891 float=16.0667 decode U: 14 13 12 20 00 decode D: 00 20 12 11 10 step 5 pixel U: hex=55a8a099 float=2.3176e+013 pixel D: hex=41808891 float=16.0667 decode U: 15 14 13 20 00 00 decode D: 00 00 20 12 11 10 step 6 pixel U: hex=59b0a8a1 float=6.21563e+015 pixel D: hex=41808891 float=16.0667 decode U: 16 15 14 20 00 00 00 decode D: 00 00 00 20 12 11 10 step 7 pixel U: hex=5db8b0a9 float=1.66354e+018 pixel D: hex=41808891 float=16.0667 decode U: 17 16 15 20 00 00 00 00 decode D: 00 00 00 00 20 12 11 10
デコード結果を見ると、Blend 機能を使った積和演算のみでも、最初の 3値と
最後の 3値が正しく保持されていることがわかります。
アルゴリズム U と D のどちらかを使えば 3 layer が実現可能で、両方組み
合わせることで 6layer が実現できます。
このように、制限があるのは演算アルゴリズム上の問題なので、MRT 数を
増やしたとしても単純にレイヤー数が増えるわけではありません。
画素の割り当て下記のとおり。
Algorithm-U / MRT0,1 MRT-0 X Y Z W ----------------------------- Pixel0-2 R G B ExpU ----------------------------- MRT-1 X Y Z W ----------------------------- Pixel0-2 ZH ZL A ExpU ----------------------------- Algorithm-D / MRT2,3 MRT-2 X Y Z W ----------------------------- Pixel3-5 R G B ExpD ----------------------------- MRT-3 X Y Z W ----------------------------- Pixel3-5 ZH ZL A ExpD -----------------------------
●浮動少数演算の丸め問題
Blend の演算機能と浮動小数値の特性を使って、ピクセルの蓄積と適切な
値の選択を行っています。このとき非常に厄介な問題が、浮動少数演算時の
丸め処理です。
入力 3 値まではこの問題が発生しないため、当初実現できたのは 3 layer
のみでした。3 値を越えると桁あふれによって追い出された bit が丸め込まれ、
上位 bit に影響を与える可能性があります。
例えば
Algorithm-U: C B A
と入力された段階で D を入力すると A が切り捨てられます。このとき A の
値の重さによって上位 D C B に影響が出ます。例えばすべて A B C D が
すべて 0xff だった場合、あふれた 0xff の繰り上がりの 1 によって
上位すべて 0 になってしまいます。影響は無視できません。
この問題を回避するために、失われたピクセル値が何であったのか推測する
必要があります。もし丸め込まれていたら decode 時に減算すればいいわけです。
捨てられたピクセルは情報として保持されていませんが、同時に Algorithm-D
を用いることによって、6 値までなら失われた値を相互に補完することが
できそうです。
step3
Algorithm-U: C B A
Algorithm-D: C B A
step4
Algorithm-U: D C B
Algorithm-D: C B A
step5
Algorithm-U: E D C
Algorithm-D: C B A
step6
Algorithm-U: F E D
Algorithm-D: C B A
この場合、step5 なら U の捨てられた値は D の B で参照でき、step6 なら
D の C で参照することができます。
同じように Algorithm-D でも入力値を捨てたことによるまるめこみが入ります
が、Algorithm-U から求めることができます。
一見うまくいきそうですが、双方同時にまるめこみが発生した場合に残念ながら
適切な値をとることができませんでした。おそらく実現できるのは 5 layer
までと考えられます。U と D で反転した値を入力しておくことで、同値の
ずれを割り出すことができます。
この問題を解決ができなかったので、6 layer シェーダーでは値を 7bit に
小さくすることで、とりあえず桁上がりが発生しないようにしています。
8bit フルに格納できなかったのが残念です。
●今後に向けて
Direct3D 10.1 / ShaderModel4.1 では、Blend 機能が大幅に拡張されます。
特に MRT 単位で独立した Blend パラメータを設定でき、また UNORM/SNORM
フォーマットでの Blend も可能となります。
これにより、今よりも実装も実現もずっと楽になると考えられます。
layer を増やせるかもしれません。
とりあえず実現が目標だったので・・速度最適化などはほとんど行っていません。
特に並べ替えの実装はあまりに手抜きなので、まだまだ改善の余地は多く
残されていると思います。
シミュレーションのあと実際にシェーダーとして実装したところ、予想と異なる
挙動がありました。CPU と GPU による浮動少数演算の違いだと考えられます。
ss10 ではごまかしが入っているので・・改善しないと。
layer オーバー時に、Stencil を使って最前面の pixel だけ選択できないか
も考えていました。Stencil Test の結果と Depth Test の結果の組み合わせ
によって Pixel を捨てるかどうか自由に決められればできそうです。
現状は Stencil Test の結果だけで決まり、Depth Test との組み合わせは
Stencil の更新方法の選択のみなのでうまくいきませんでした。
今回の DirectX SDK November 2007 では、ReleaseNote を見ただけでは
Graphics 周りだとほど大きな更新がなかったように見えます。
ところがこの更新は、一般の開発者にとっては非常に大きな、とても
意味のあるものとなりそうです。
マニュアルが大幅に刷新されています。
これまでいくつか指摘してきたマニュアルの不備や不具合なども修正され、
説明不足だった点もかなり手が入っているようです。
Direct3D 10.1 など開発や更新自体がひと段落し、マニュアルや解説の
方にも手が回るようなってきたのでしょうか。
と思ったら、build 番号もかなり増えています。
・DirectX SDK Version 一覧
マニュアルの更新によって、Direct3D 10.1 の機能もかなり見えてきました。
Shader の説明でも、4_1 で追加された機能がわかりやすく表記されています。
D3D 10.1 / ShaderModel 4.1 の新機能といえば TextureCubeArray がありますが
他に Texture のサンプリング命令として Gather() があるようです。
これは一度のサンプリングで、テクスチャの 4ピクセルの値をまとめて参照
することができます。4 つのピクセルの R だけが、一度に xyzw として
読み出されるわけです。
ATI の RADEON X1900 系の機能に精通している方ならすぐに思い当たる
ものがあるかと思います。
かつて ATI が、NVIDIA ShadowMap に対抗して用意した ShadowBuffer 用の
サンプリングアクセラレータ機能が、まさに Gather() そのものでしょう。
4pixel 同時にサンプリングしてもハード的にコストがかからないので、
これはかなりよい機能だと思います。
実はこちらのエントリで DXGI_FORMAT の詳細を調べたときに、
機能フラグとして D3D10_FORMAT_SUPPORT_SHADER_GATHER が
存在していることを発見しました。このときはマニュアルやヘッダを検索
しても他に GATHER は出てこないので意味がわかりませんでした。
おそらく、このフラグがついているフォーマットだけが Gather() 命令で
利用可能なのだと思われます。(10.0 では無し)
・Direct3D 10 DXGI_FORMAT の機能対応一覧
・Direct3D 10.0 DXGI_FORMAT_SUPPORT
こちらで調べているような アセンブラ命令の定義の意味も、
マニュアルに乗るようになりました。
他に個人的に大きなトピックとして、DDS の詳細項目の追加があります。
DDS は比較的古いフォーマットです。Direct3D8 で FourCC が拡張されて
Direct3D9 のフォーマットも全部格納できるようになりました。
・DDS Texture format memo
ですが、Direct3D10 の全フォーマットを格納することができませんでした。
今回 Direct3D 10 のフォーマットに対応すべく拡張方法が定義されました。
基本的には先頭の DDSURFACE ヘッダ 128 byte は互換性があります。
その直後に 20byte の DDS_HEADER_DXT10 ヘッダが追加された形となるようです。
つまりヘッダは合計 148byte 、少々中途半端です。この追加ヘッダの存在を
識別する方法がまだ不明です。また DDS_HEADER のうち未使用となった
dwCaps がちょうど 20byte あるので、ここに DDS_HEADER_DXT10 が収まるの
だったらしっくりきます。
ヘッダの種別は dwMagic で区別するようにみえますが、この具体的な値が
書かれていないようです。
とにかくこれでようやく TextureArray も dds ファイルとして格納できる
ようになりそうです。
付属の dxtex.exe (DirectX Texture Tool) はまだ D3D10 フォーマットに
対応していないようです。
Graphics 周りだとほど大きな更新がなかったように見えます。
ところがこの更新は、一般の開発者にとっては非常に大きな、とても
意味のあるものとなりそうです。
マニュアルが大幅に刷新されています。
これまでいくつか指摘してきたマニュアルの不備や不具合なども修正され、
説明不足だった点もかなり手が入っているようです。
Direct3D 10.1 など開発や更新自体がひと段落し、マニュアルや解説の
方にも手が回るようなってきたのでしょうか。
と思ったら、build 番号もかなり増えています。
・DirectX SDK Version 一覧
マニュアルの更新によって、Direct3D 10.1 の機能もかなり見えてきました。
Shader の説明でも、4_1 で追加された機能がわかりやすく表記されています。
D3D 10.1 / ShaderModel 4.1 の新機能といえば TextureCubeArray がありますが
他に Texture のサンプリング命令として Gather() があるようです。
これは一度のサンプリングで、テクスチャの 4ピクセルの値をまとめて参照
することができます。4 つのピクセルの R だけが、一度に xyzw として
読み出されるわけです。
ATI の RADEON X1900 系の機能に精通している方ならすぐに思い当たる
ものがあるかと思います。
かつて ATI が、NVIDIA ShadowMap に対抗して用意した ShadowBuffer 用の
サンプリングアクセラレータ機能が、まさに Gather() そのものでしょう。
4pixel 同時にサンプリングしてもハード的にコストがかからないので、
これはかなりよい機能だと思います。
実はこちらのエントリで DXGI_FORMAT の詳細を調べたときに、
機能フラグとして D3D10_FORMAT_SUPPORT_SHADER_GATHER が
存在していることを発見しました。このときはマニュアルやヘッダを検索
しても他に GATHER は出てこないので意味がわかりませんでした。
おそらく、このフラグがついているフォーマットだけが Gather() 命令で
利用可能なのだと思われます。(10.0 では無し)
・Direct3D 10 DXGI_FORMAT の機能対応一覧
・Direct3D 10.0 DXGI_FORMAT_SUPPORT
こちらで調べているような アセンブラ命令の定義の意味も、
マニュアルに乗るようになりました。
他に個人的に大きなトピックとして、DDS の詳細項目の追加があります。
DDS は比較的古いフォーマットです。Direct3D8 で FourCC が拡張されて
Direct3D9 のフォーマットも全部格納できるようになりました。
・DDS Texture format memo
ですが、Direct3D10 の全フォーマットを格納することができませんでした。
今回 Direct3D 10 のフォーマットに対応すべく拡張方法が定義されました。
基本的には先頭の DDSURFACE ヘッダ 128 byte は互換性があります。
その直後に 20byte の DDS_HEADER_DXT10 ヘッダが追加された形となるようです。
つまりヘッダは合計 148byte 、少々中途半端です。この追加ヘッダの存在を
識別する方法がまだ不明です。また DDS_HEADER のうち未使用となった
dwCaps がちょうど 20byte あるので、ここに DDS_HEADER_DXT10 が収まるの
だったらしっくりきます。
ヘッダの種別は dwMagic で区別するようにみえますが、この具体的な値が
書かれていないようです。
とにかくこれでようやく TextureArray も dds ファイルとして格納できる
ようになりそうです。
付属の dxtex.exe (DirectX Texture Tool) はまだ D3D10 フォーマットに
対応していないようです。
2007/10/27
DirectX SDK November2007
2007/10/17
64bit 環境へ移行
昨日の「64bit 開発設定のメモ」で
Executable files: に DXSDK の bin\x64 を path を追加しましたが、
よく考えると x86 (32bit) 上で実行する分には不要なものでした。
訂正させていただきます。
実際に Vista x64 をメイン PC にインストールしました。
現在 Windows Vista x86 (32bit) が入っているので 2つ目の HDD に
入れてみます。
HDD 1 - C: Vista x86 (32bit)
HDD 0 - D: Vista x64 (64bit) install 予定
OS が起動した状態から install できなかったので、いったん DVD から
boot します。
インストール予定の HDD 0 側はあらかじめ 100G ほどの空きを作って
おきました。特に HDD を初期化しなくても新規相当で install 可能で、
もとからあったフォルダやファイルなどもそのまま残ります。
install 後、とりあえず Vista x86 と x64 の dual boot となります。
Windows Vista では、どちらで起動してもシステムが入っている方が
C: ドライブに割り当てられます。
x86 (32bit) で起動すると x64 側ドライブ(HDD 0)が D:
x64 (64bit) で起動すると x86 側ドライブ(HDD 1)が D:
システムドライブが固定されるため、それぞれツールなどをインストール
しても混乱せず、混用されないのでこの方が便利です。
アプリケーションは OS 毎に個別のインストールになりますが、
作成したデータなどは共有したいところです。
起動する OS によってフォルダのドライブが異なるため、データは一意に
アクセスしづらくなります。
たとえば x86 で C:\DATA にデータが格納してあった場合、x64 で起動
すると D:\DATA になります。
そこで、両方で共通にアクセスする可能性のあるフォルダは
symbolic link を作りました。
コマンドプロンプトから下記のように実行します。
mklink /d C:\DATA D:\DATA
これで x64 側でも C:\DATA が作られます。実際のデータは D:\DATA に
格納されますが、使ってる分には完全に C:\DATA でアクセスできます。
mklink 実行時に権限が無いといわれることがあります。その場合は
コマンドプロンプトを管理者権限で起動しておきます。スタートメニュー
から起動するとき、右ボタンの「管理者として実行」を選択します。
以前だったらこうなることを想定して、普段から仮想ドライブを作って
おいたりしなければいけないところです。Vista の symbolic link の
方がフォルダ単位で柔軟に対応できるし慣れてるので扱いが楽です。
64bit 環境でも 32bit のアプリケーションがそのまま動作するので、
今のところは特に不具合もなく使えています。普段使ってるツールも
動くし乗り換えてもいいかもしれません。まだ1日しか使ってないけど。
x64 専用アプリに切り替えなくても、たとえばコンパイラの設定も
x86_amd64 (32bit 用 64bit コンパイラ) のままでも動きます。
Program Files も2種類できていました。
実行するアプリケーションによっていくつかの環境変数が切り替わる
ようになっています。
・MSDN 64-Bit Windows WOW64 Implementation Details
たとえば環境変数 ProgramFiles は、64bit アプリでは
C:\Program Files を指していますが 32bit アプリケーションでは
ProgramFiles は C:\Program Files (x86) になっています。
同じように PROCESSOR_ARCHITECTURE も必要に応じて AMD64 や x86
になります。
急に乗り換えたのは Maya plug-in を x64 に対応させるためでした。
そろそろメモリが厳しいということで、グラフィックデザイナーの環境が
64bit に移行しつつあります。
Executable files: に DXSDK の bin\x64 を path を追加しましたが、
よく考えると x86 (32bit) 上で実行する分には不要なものでした。
訂正させていただきます。
実際に Vista x64 をメイン PC にインストールしました。
現在 Windows Vista x86 (32bit) が入っているので 2つ目の HDD に
入れてみます。
HDD 1 - C: Vista x86 (32bit)
HDD 0 - D: Vista x64 (64bit) install 予定
OS が起動した状態から install できなかったので、いったん DVD から
boot します。
インストール予定の HDD 0 側はあらかじめ 100G ほどの空きを作って
おきました。特に HDD を初期化しなくても新規相当で install 可能で、
もとからあったフォルダやファイルなどもそのまま残ります。
install 後、とりあえず Vista x86 と x64 の dual boot となります。
Windows Vista では、どちらで起動してもシステムが入っている方が
C: ドライブに割り当てられます。
x86 (32bit) で起動すると x64 側ドライブ(HDD 0)が D:
x64 (64bit) で起動すると x86 側ドライブ(HDD 1)が D:
システムドライブが固定されるため、それぞれツールなどをインストール
しても混乱せず、混用されないのでこの方が便利です。
アプリケーションは OS 毎に個別のインストールになりますが、
作成したデータなどは共有したいところです。
起動する OS によってフォルダのドライブが異なるため、データは一意に
アクセスしづらくなります。
たとえば x86 で C:\DATA にデータが格納してあった場合、x64 で起動
すると D:\DATA になります。
そこで、両方で共通にアクセスする可能性のあるフォルダは
symbolic link を作りました。
コマンドプロンプトから下記のように実行します。
mklink /d C:\DATA D:\DATA
これで x64 側でも C:\DATA が作られます。実際のデータは D:\DATA に
格納されますが、使ってる分には完全に C:\DATA でアクセスできます。
mklink 実行時に権限が無いといわれることがあります。その場合は
コマンドプロンプトを管理者権限で起動しておきます。スタートメニュー
から起動するとき、右ボタンの「管理者として実行」を選択します。
以前だったらこうなることを想定して、普段から仮想ドライブを作って
おいたりしなければいけないところです。Vista の symbolic link の
方がフォルダ単位で柔軟に対応できるし慣れてるので扱いが楽です。
64bit 環境でも 32bit のアプリケーションがそのまま動作するので、
今のところは特に不具合もなく使えています。普段使ってるツールも
動くし乗り換えてもいいかもしれません。まだ1日しか使ってないけど。
x64 専用アプリに切り替えなくても、たとえばコンパイラの設定も
x86_amd64 (32bit 用 64bit コンパイラ) のままでも動きます。
Program Files も2種類できていました。
C:\Program Files C:\Program Files (x86)
実行するアプリケーションによっていくつかの環境変数が切り替わる
ようになっています。
・MSDN 64-Bit Windows WOW64 Implementation Details
たとえば環境変数 ProgramFiles は、64bit アプリでは
C:\Program Files を指していますが 32bit アプリケーションでは
ProgramFiles は C:\Program Files (x86) になっています。
同じように PROCESSOR_ARCHITECTURE も必要に応じて AMD64 や x86
になります。
急に乗り換えたのは Maya plug-in を x64 に対応させるためでした。
そろそろメモリが厳しいということで、グラフィックデザイナーの環境が
64bit に移行しつつあります。
2007/10/16
64bit 開発設定のメモ
Windows で x64 用の build 環境を作る時のメモです。
●VisualStudio 2005 IDE
・MSDN 方法 : Visual C++ プロジェクトを 64 ビット プラットフォーム用に設定する
構成マネージャで新しい Platform を追加するだけでいいようです。
WindowsCE の CPU 追加と同じような感じ。
その後 Options の Projects and Solutions → VC++ Directories でも
Platform を選択してフォルダなどの確認や追加をします。
32bit 版 Vista で作業しているので、クロス開発用バイナリ x86_amd64 が
選択されていました。
DirectX SDK 用に下記の設定を追加します。必ず一番上にしておきます。
●コマンドライン用
・MSDN 方法 : 64 ビットの Visual C++ ツールセットをコマンド ラインから有効にする
vcvarsall.bat の中を見ると各環境用の個別のファイルを呼び出して
いるだけでした。それぞれ専用の環境変数設定が用意されています。
環境設定用 bat ファイル
ひとつの Makefile で各環境用のバイナリを作るなら、これらの設定も
Makefile に記述することになるでしょう。
x64 の場合 lib のそれぞれのパスに lib\amd64 を追加するだけで、
または DirectX SDK のように x86 を x64 に置き換えます。
binary path の場合は上記のようにクロスコンパイルを含めた 3 種類の
構成があります。x86_amd64 では x86 用 bin も必要なので、その前に
x86_amd64 用パスを追加します。
あとはコンパイルオプションの変更など。
ほぼ同じ指定が可能で -arch:SSE2 は不要。
__asm ははじかれたけど、intrin.h は大丈夫そうです。
●VisualStudio 2005 IDE
・MSDN 方法 : Visual C++ プロジェクトを 64 ビット プラットフォーム用に設定する
構成マネージャで新しい Platform を追加するだけでいいようです。
WindowsCE の CPU 追加と同じような感じ。
その後 Options の Projects and Solutions → VC++ Directories でも
Platform を選択してフォルダなどの確認や追加をします。
32bit 版 Vista で作業しているので、クロス開発用バイナリ x86_amd64 が
選択されていました。
DirectX SDK 用に下記の設定を追加します。必ず一番上にしておきます。
Executable files: $(DXSDK_DIR)Utilities\Bin\x64Inclue files: $(DXSDK_DIR)Include Library files: $(DXSDK_DIR)Lib\x64
●コマンドライン用
・MSDN 方法 : 64 ビットの Visual C++ ツールセットをコマンド ラインから有効にする
vcvarsall.bat の中を見ると各環境用の個別のファイルを呼び出して
いるだけでした。それぞれ専用の環境変数設定が用意されています。
x86 32bit 用 amd64 64bit 用 x86_amd64 32bit 上で 64bit バイナリの作成 (クロスコンパイル)
環境設定用 bat ファイル
x86 VC\bin\vcvars32.bat adm64 VC\bin\amd64\vcvarsamd64.bat x86_adm64 VC\bin\x86_amd64\vcvarsx86_amd64.bat
ひとつの Makefile で各環境用のバイナリを作るなら、これらの設定も
Makefile に記述することになるでしょう。
x64 の場合 lib のそれぞれのパスに lib\amd64 を追加するだけで、
または DirectX SDK のように x86 を x64 に置き換えます。
binary path の場合は上記のようにクロスコンパイルを含めた 3 種類の
構成があります。x86_amd64 では x86 用 bin も必要なので、その前に
x86_amd64 用パスを追加します。
x86 C:\Program Files\Microsoft Visual Studio 8\VC\bin amd64 C:\Program Files\Microsoft Visual Studio 8\VC\bin\amd64 x86_amd64 C:\Program Files\Microsoft Visual Studio 8\VC\bin\x86_amd64 C:\Program Files\Microsoft Visual Studio 8\VC\bin
あとはコンパイルオプションの変更など。
ほぼ同じ指定が可能で -arch:SSE2 は不要。
__asm ははじかれたけど、intrin.h は大丈夫そうです。
2007/10/14
Direct3D 10 DXGI_FORMAT の機能対応一覧
DXGI_FORMAT_R11G11B10_FLOAT は RenderTarget として使えましたが、
DXGI_FORMAT_R9G9B9E5_SHAREDEXP はレンダリングできませんでした。
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験(2)
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験
また Direct3D10/DirectX10 では Pixel 形式も頂点形式もインデックスも
統合されています。BC1 (DXT1) は画像圧縮用フォーマットなので、
これは頂点形式に使えるのでしょうか。
フォーマット Type 毎に対応している機能や、どの用途に使えるのか
ID3D10Device::CheckFormatSupport() で調べることができます。
各フォーマットの機能一覧を作成してみました。
・Direct3D 10.0 DXGI_FORMAT 機能対応一覧
表の BF は CreateBuffer() で作成できるかどうか、
1D ~ 3D はそれぞれ CreateTexture1D() ~ 3D() で作成できるかどうか、
Texture として使えるかどうかを意味しています。
VB, IB, SO は、VertexBuffer, IndexBuffer, StreamOutput や
InputLayout の定義に使えるかどうかです。これを見ると、やはり
BC1~BC5 (DXT) や、R8G8_B8G8/G8R8_G8B8 (YUV) などの圧縮形式は
画像以外に使えないことがわかります。
R9G9B9E5_SHAREDEXP はだめですが、R11G11B10_FLOAT は頂点でも
使用できるようです。
IndexBuffer に使えるのは
R32_UINT (32bit), R16_UINT (16bit) しかありませんでした。
SL, SS, SC は、シェーダー中のリソースアクセス手段を示しています。
SL は Sampler を使わないで直接 Load() 命令が使えるかどうか、
SS は Sampler 経由の Sample() 命令が使えるかどうかです。
同じように SC は SampleCmp() 系命令に対応しています。わかりやすく
言えば、SC が付いたフォーマットはハードウエアシャドウマップ
(NVIDIA ShadowMap) に使えるということです。
MI は MipMap です。ほとんどすべてのフォーマットが使用可能に
なっています。MA は Mipmap の自動生成対応かどうか。
RT が付いたものは RenderTarget に使えます。整数形式を含めて
レンダリング可能な形式は結構多いようです。ただし Blend 可能な
なのは BL が付いたものだけです。Blend 対応フォーマットはそれほど
多くありませんが、32bit FLOAT もしっかり blend 対応になっている
など D3D9 世代と比べたらかなりの贅沢さです。
DS は DepthStencil 用フォーマットです。DepthStencil はまだまだ
特殊なフォーマットであることがわかります。
MT が付いたものはさらに MultiSample 対応になります。
DI (Display) は単なる RenderTarget ではなく、FrontBuffer として
使用可能なフォーマットを意味しているようです。
その他の機能シンボルの意味は下記のとおりです。
表を見ていると 1bit 形式の DXGI_FORMAT_R1_UNORM はかなり特殊な
フォーマットであることがわかります。2D Texture にしか使えず
Mipmap もありません。RenderTarget もだめ。
またこのフォーマット機能表を確認しておくと、Direct3D 10.1 の
機能拡張の意味が見えてきます。
まず block-compressed (BC?) texture へのレンダリングができる
ようになるとのこと。
さらに Blend 対応の拡張があります。10.0 だと下記のものだけですが、
これが DirectX 10.1 ではすべての UNORM、SNORM で使えるように
なるそうです。
DXGI_FORMAT_R9G9B9E5_SHAREDEXP はレンダリングできませんでした。
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験(2)
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験
また Direct3D10/DirectX10 では Pixel 形式も頂点形式もインデックスも
統合されています。BC1 (DXT1) は画像圧縮用フォーマットなので、
これは頂点形式に使えるのでしょうか。
フォーマット Type 毎に対応している機能や、どの用途に使えるのか
ID3D10Device::CheckFormatSupport() で調べることができます。
各フォーマットの機能一覧を作成してみました。
・Direct3D 10.0 DXGI_FORMAT 機能対応一覧
表の BF は CreateBuffer() で作成できるかどうか、
1D ~ 3D はそれぞれ CreateTexture1D() ~ 3D() で作成できるかどうか、
Texture として使えるかどうかを意味しています。
BF = ID3D10Buffer 1D = ID3DTexture1D 2D = ID3DTexture2D 3D = ID3DTexture3D
VB, IB, SO は、VertexBuffer, IndexBuffer, StreamOutput や
InputLayout の定義に使えるかどうかです。これを見ると、やはり
BC1~BC5 (DXT) や、R8G8_B8G8/G8R8_G8B8 (YUV) などの圧縮形式は
画像以外に使えないことがわかります。
R9G9B9E5_SHAREDEXP はだめですが、R11G11B10_FLOAT は頂点でも
使用できるようです。
IndexBuffer に使えるのは
R32_UINT (32bit), R16_UINT (16bit) しかありませんでした。
VB = InputLayout (VertexBuffer) IB = IndexBuffer SO = StreamOutput
SL, SS, SC は、シェーダー中のリソースアクセス手段を示しています。
SL は Sampler を使わないで直接 Load() 命令が使えるかどうか、
SS は Sampler 経由の Sample() 命令が使えるかどうかです。
同じように SC は SampleCmp() 系命令に対応しています。わかりやすく
言えば、SC が付いたフォーマットはハードウエアシャドウマップ
(NVIDIA ShadowMap) に使えるということです。
SL = Load() SS = Sample() / SampleGrad() / SampleLevel() SC = SampleCmp() / SampleCmpLevelZero()
MI は MipMap です。ほとんどすべてのフォーマットが使用可能に
なっています。MA は Mipmap の自動生成対応かどうか。
MI = MipMap MA = MipMap 自動生成
RT が付いたものは RenderTarget に使えます。整数形式を含めて
レンダリング可能な形式は結構多いようです。ただし Blend 可能な
なのは BL が付いたものだけです。Blend 対応フォーマットはそれほど
多くありませんが、32bit FLOAT もしっかり blend 対応になっている
など D3D9 世代と比べたらかなりの贅沢さです。
DS は DepthStencil 用フォーマットです。DepthStencil はまだまだ
特殊なフォーマットであることがわかります。
MT が付いたものはさらに MultiSample 対応になります。
DI (Display) は単なる RenderTarget ではなく、FrontBuffer として
使用可能なフォーマットを意味しているようです。
RT = RenderTarget BL = Blend 対応 DS = DepthStencil MT = MultiSample RenderTarget DI = Display
その他の機能シンボルの意味は下記のとおりです。
CL = CPU で Lock (Map)できる CW = 他の format に cast できる MR = MultiSample Resolution 対応 ML = Multisample テクスチャを Load() 可能
表を見ていると 1bit 形式の DXGI_FORMAT_R1_UNORM はかなり特殊な
フォーマットであることがわかります。2D Texture にしか使えず
Mipmap もありません。RenderTarget もだめ。
またこのフォーマット機能表を確認しておくと、Direct3D 10.1 の
機能拡張の意味が見えてきます。
まず block-compressed (BC?) texture へのレンダリングができる
ようになるとのこと。
さらに Blend 対応の拡張があります。10.0 だと下記のものだけですが、
これが DirectX 10.1 ではすべての UNORM、SNORM で使えるように
なるそうです。
R32G32B32A32_FLOAT R32G32_FLOAT R32_FLOAT R16G16B16A16_FLOAT R16G16_FLOAT R16_FLOAT R11G11B10_FLOAT R10G10B10A2_UNORM R8G8B8A8_UNORM R8G8B8A8_UNORM_SRGB R8G8_UNORM R8_UNORM A8_UNORM
D3D10/DX10 の新しいテクスチャフォーマットの続きです。
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験
DXGI_FORMAT_R11G11B10_FLOAT の 11bit / 10bit FLOAT は、
指数部が 16F と同じだったので 16F との相互変換は比較的容易です。
符号を落として下位bit を切り詰めるだけで十分かもしれません。
実際に相互変換してみました。
まず DXGI_FORMAT_R11G11B10_FLOAT への変換です。16F に変換した後
下位 bit の切り落としを行っています。符号が無いので入力が負数の
場合は 0 にクランプしています。
次に DXGI_FORMAT_R11G11B10_FLOAT から 32F に変換してみます。
同じように一旦 16F を経由しています。
D3DXFLOAT16 のメンバはマニュアルでは WORD Value となってますが、
実際には小文字の value でした。しかも protected だったので
上の例では cast でごまかしています。
DXGI_FORMAT_R11G11B10_FLOAT と同様、Direct3D10/DirectX10 でもう
1つ追加された新フォーマットがあります。
DXGI_FORMAT_R9G9B9E5_SHAREDEXP
こちらも調べてみました。5bit の exponent を持つことから、
R11G11B10_FLOAT 同様により広い範囲をカバーできます。ただし共有
されているため極端にスケールの異なる component を持つことは
できないでしょう。
FLOAT のグループではないことと、フォーマット名に E の成分も記載
されていることから、exponent の演算はシェーダーを併用するのでは
ないかと予想していました。
ところが実際に試すと E5 の成分がきちんと反映された値が返ってきます。
またシェーダーでは E5 の値を直接読み取ることができず、w は存在
しませんでした。(w を読み取ろうとすると 1.0 固定となる)
また RenderTarget にはできません。
R9G9B9E5_SHAREDEXP の各 bit の意味は下記の通りです。
指数部は 5bit なので、16F や R11G11B10_FLOAT と同じように 15
が 0 の offset 15 と仮定します。このとき R9G9B9 を 0 にすると
真っ黒になりました。
上のように R G B すべての bit を 1 にするとほぼ (1.0, 1.0, 1.0)
相当になっています。ただ厳密に一致せず、それぞれわずかに 1.0
より小さな値になります。
この結果からわかるのは、指数部は 16F や R11G11B10_FLOAT ほぼ同じ
内容で合っているのではないかということ。
また R9G9B9 は IEEE754 等の浮動少数の仮数に近いものではなく、
9bit の値がそのまま格納されていることです。511 が厳密に 1.0 に
ならないため、R8G8B8_UNORM のような UNORM 変換 1/511 倍ではなく
1/512 倍している可能性があります。
次に 0xc0040201 を入れてみたところ完全に 1.0 に一致しました。
これは上記のように各 component が 1 で指数は 24 になっています。
offset は 15 なので 24-15 = 9、つまり 1 * (2^9) = 512 相当に
なります。簡単に書けば 1<<9 です。
同様に 0x84020100 でも (1.0, 1.0, 1.0) になります。こちらは
最上位に合わせており次のようになります。
16-15 = 1、256 * (2^1) = 512 です。
よって offset 15 の exponent とみなすよりも、offset 16 で正規化
し、最上位 bit を隠していない浮動少数とみなした方がしっくりくる
かもしれません。
16F も R11G11B10_FLOAT も R9G9B9E5_SHAREDEXP も指数部は全く同じ
5bit となっていました。それぞれ exponent の機能はほぼ同一なので、
この仕様はもしかしたらハードウエア的な都合で決められたものなの
かもしれません。
・Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験
DXGI_FORMAT_R11G11B10_FLOAT の 11bit / 10bit FLOAT は、
指数部が 16F と同じだったので 16F との相互変換は比較的容易です。
符号を落として下位bit を切り詰めるだけで十分かもしれません。
実際に相互変換してみました。
まず DXGI_FORMAT_R11G11B10_FLOAT への変換です。16F に変換した後
下位 bit の切り落としを行っています。符号が無いので入力が負数の
場合は 0 にクランプしています。
DWORD F32toF11( const float* f32 ) { WORD _f16[3]; D3DXFloat32To16Array( reinterpret_cast<D3DXFLOAT16*>( _f16 ), f32, 3 ); for( int i= 0 ; i< 3 ; i++ ){ if( _f16[i] & 0x8000 ){ _f16[i]= 0; } } return (((DWORD)_f16[0] >> 4) & 0x000007ff) |(((DWORD)_f16[1] << 7) & 0x003ff800) |(((DWORD)_f16[2] << 17) & 0xffc00000); }
次に DXGI_FORMAT_R11G11B10_FLOAT から 32F に変換してみます。
同じように一旦 16F を経由しています。
void F11toF32( float* f32, DWORD f11 ) { WORD _f16[3]; _f16[0]= (WORD)((f11 << 4) & 0x7ff0); _f16[1]= (WORD)((f11 >> 7) & 0x7ff0); _f16[2]= (WORD)((f11 >>17) & 0x7fe0); D3DXFloat16To32Array( f32, reinterpret_cast<D3DXFLOAT16*>( _f16 ), 3 ); }
D3DXFLOAT16 のメンバはマニュアルでは WORD Value となってますが、
実際には小文字の value でした。しかも protected だったので
上の例では cast でごまかしています。
DXGI_FORMAT_R11G11B10_FLOAT と同様、Direct3D10/DirectX10 でもう
1つ追加された新フォーマットがあります。
DXGI_FORMAT_R9G9B9E5_SHAREDEXP
こちらも調べてみました。5bit の exponent を持つことから、
R11G11B10_FLOAT 同様により広い範囲をカバーできます。ただし共有
されているため極端にスケールの異なる component を持つことは
できないでしょう。
FLOAT のグループではないことと、フォーマット名に E の成分も記載
されていることから、exponent の演算はシェーダーを併用するのでは
ないかと予想していました。
ところが実際に試すと E5 の成分がきちんと反映された値が返ってきます。
またシェーダーでは E5 の値を直接読み取ることができず、w は存在
しませんでした。(w を読み取ろうとすると 1.0 固定となる)
また RenderTarget にはできません。
R9G9B9E5_SHAREDEXP の各 bit の意味は下記の通りです。
E5 B9 G9 R9 01111 111111111 111111111 111111111
指数部は 5bit なので、16F や R11G11B10_FLOAT と同じように 15
が 0 の offset 15 と仮定します。このとき R9G9B9 を 0 にすると
真っ黒になりました。
上のように R G B すべての bit を 1 にするとほぼ (1.0, 1.0, 1.0)
相当になっています。ただ厳密に一致せず、それぞれわずかに 1.0
より小さな値になります。
この結果からわかるのは、指数部は 16F や R11G11B10_FLOAT ほぼ同じ
内容で合っているのではないかということ。
また R9G9B9 は IEEE754 等の浮動少数の仮数に近いものではなく、
9bit の値がそのまま格納されていることです。511 が厳密に 1.0 に
ならないため、R8G8B8_UNORM のような UNORM 変換 1/511 倍ではなく
1/512 倍している可能性があります。
次に 0xc0040201 を入れてみたところ完全に 1.0 に一致しました。
// 0xc0040201 E5 B9 G9 R9 11000 000000001 000000001 000000001
これは上記のように各 component が 1 で指数は 24 になっています。
offset は 15 なので 24-15 = 9、つまり 1 * (2^9) = 512 相当に
なります。簡単に書けば 1<<9 です。
同様に 0x84020100 でも (1.0, 1.0, 1.0) になります。こちらは
最上位に合わせており次のようになります。
// 0x84020100 E5 B9 G9 R9 10000 100000000 100000000 100000000
16-15 = 1、256 * (2^1) = 512 です。
よって offset 15 の exponent とみなすよりも、offset 16 で正規化
し、最上位 bit を隠していない浮動少数とみなした方がしっくりくる
かもしれません。
16F も R11G11B10_FLOAT も R9G9B9E5_SHAREDEXP も指数部は全く同じ
5bit となっていました。それぞれ exponent の機能はほぼ同一なので、
この仕様はもしかしたらハードウエア的な都合で決められたものなの
かもしれません。
2007/10/11
Direct3D 10 DXGI_FORMAT_R11G11B10_FLOAT の実験
Direct3D10/DirectX10 ではいくつかの新しいフォーマットが追加されて
います。その 1つに DXGI_FORMAT_R11G11B10_FLOAT があります。
32bit で 3チャンネル持っていて、かつ FLOAT なのでどんな構造を
しているか興味あります。
調べてみました。
見つけた資料はこちらです。
・NVIDIA next-gen-dx10-games-develop06.pdf
各 component 毎に 5bit の exponent があると書かれているので、
RGB がそれぞれ 6e5, 6e5, 5e5 であると考えられます。
符号はありません。
実際にバッファを作って描画してみました。
その結果、0x781e03c0 がちょうど ( 1.0, 1.0, 1.0 ) であることが
わかりました。この値を 2進数に変換すると
さらに 11bit 11bit 10bit に分解すると
となります。B が上位で 10bit、残りが 11bit です。
exponent が各 5bit で、そのうち 4bit が立っています。
指数部は 5bit の 0~31 で、上記のように center の 15 が 0 であると
考えられます。この構造は fp16 と同じです。fp16 については以前
下記のページに書きました。
・DDS Texture format memo
仮数部は最上位の 1 が省略されているので全部 0 です。
DXGI_FORMAT_R11G11B10_FLOAT はテクスチャだけでなく RenderTarget
として実際にレンダリングできることも確認できました。
これはかなり使えそうなフォーマットです。
他に指数を持たない似たような型として次のフォーマットもあります。
DXGI_FORMAT_R10G10B10A2_UNORM
DXGI_FORMAT_R10G10B10A2_UINT
この 10bit 型は DirectX9 にもありました。
だけど D3D10/DX10 になって新しいのは、UNORM だけでなく整数型 UINT が
使えることと、NVIDIA のビデオカードでもちゃんと使えることです。
こちらも RenderTarget としてレンダリングできました。
います。その 1つに DXGI_FORMAT_R11G11B10_FLOAT があります。
32bit で 3チャンネル持っていて、かつ FLOAT なのでどんな構造を
しているか興味あります。
調べてみました。
見つけた資料はこちらです。
・NVIDIA next-gen-dx10-games-develop06.pdf
各 component 毎に 5bit の exponent があると書かれているので、
RGB がそれぞれ 6e5, 6e5, 5e5 であると考えられます。
符号はありません。
実際にバッファを作って描画してみました。
その結果、0x781e03c0 がちょうど ( 1.0, 1.0, 1.0 ) であることが
わかりました。この値を 2進数に変換すると
7 8 1 e 0 3 c 0 0111 1000 0001 1110 0000 0011 1100 0000
さらに 11bit 11bit 10bit に分解すると
B G R 0111100000 01111000000 01111000000
となります。B が上位で 10bit、残りが 11bit です。
exponent が各 5bit で、そのうち 4bit が立っています。
指数部は 5bit の 0~31 で、上記のように center の 15 が 0 であると
考えられます。この構造は fp16 と同じです。fp16 については以前
下記のページに書きました。
・DDS Texture format memo
仮数部は最上位の 1 が省略されているので全部 0 です。
DXGI_FORMAT_R11G11B10_FLOAT はテクスチャだけでなく RenderTarget
として実際にレンダリングできることも確認できました。
これはかなり使えそうなフォーマットです。
他に指数を持たない似たような型として次のフォーマットもあります。
DXGI_FORMAT_R10G10B10A2_UNORM
DXGI_FORMAT_R10G10B10A2_UINT
この 10bit 型は DirectX9 にもありました。
だけど D3D10/DX10 になって新しいのは、UNORM だけでなく整数型 UINT が
使えることと、NVIDIA のビデオカードでもちゃんと使えることです。
こちらも RenderTarget としてレンダリングできました。
前回書いた整数テクスチャの扱いに関して少々追加です。
レンダリングで更新する時は整数処理を行いますが、デバッグなどで
画面に描画する時は RenderTarget の 0.0~1.0 に変換する必要があると
下記エントリで書きました。
Direct3D 10 ShaderModel 4.0 で整数の世界
これを DirectX10(D3D10) の機能を使って自動化できます。
まずリソースの作成を DXGI_FORMAT_R8G8B8A8_UINT ではなく、
DXGI_FORMAT_R8G8B8A8_TYPELESS で宣言しておきます。
初期データの渡し方などは同じです。
その後、同じリソースから ShaderResourceView を 2個作成します。
こちらは型を明確にして UINT と UNORM にします。
これでシェーダーからは、iResourceViewUINT 経由でアクセスすると
0~255 の整数値として読み込むことができ、iResourceViewUNORM で
アクセスすると、従来どおり 0~1.0 の少数値で受け取ることが
できるようになります。
例えばシェーダー側では次のように宣言しておきます。
エフェクトの変数設定はこんな感じで。
受け取るシェーダー側です。
便利です。さすがに良く考えられています。
注意点は、以前のエントリ で書いたように同一のリソースを複数の View
として設定するため、リソースが握られたままになって衝突が
おきやすいことです。
・Direct3D 10 HLSL Effect/FX リソース設定のはまり
上記の PixelShader では、UV を 0.0~1.0 に補間した値で受け取って
いるためテクスチャのサイズを乗算する処理が入っています。
レンダリングで更新する時は整数処理を行いますが、デバッグなどで
画面に描画する時は RenderTarget の 0.0~1.0 に変換する必要があると
下記エントリで書きました。
Direct3D 10 ShaderModel 4.0 で整数の世界
これを DirectX10(D3D10) の機能を使って自動化できます。
まずリソースの作成を DXGI_FORMAT_R8G8B8A8_UINT ではなく、
DXGI_FORMAT_R8G8B8A8_TYPELESS で宣言しておきます。
初期データの渡し方などは同じです。
D3D10_TEXTURE2D_DESC t2ddesc; t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_TYPELESS; ~ ID3D10Texture2D* riTexture2D= NULL; iDevice->CreateTexture2D( &t2ddesc, &initdata, &riTexture2D );
その後、同じリソースから ShaderResourceView を 2個作成します。
こちらは型を明確にして UINT と UNORM にします。
// ShaderResourceView の作成 D3D10_SHADER_RESOURCE_VIEW_DESC srvdesc; srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT; // 整数アクセス srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D; srvdesc.Texture2D.MostDetailedMip= 0; srvdesc.Texture2D.MipLevels= 1; iDevice->CreateShaderResourceView( riTexture2D, &srvdesc, iResourceViewUINT ); srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UNORM; // 固定少数化 srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D; iDevice->CreateShaderResourceView( riTexture2D, &srvdesc, iResourceViewUNORM );
これでシェーダーからは、iResourceViewUINT 経由でアクセスすると
0~255 の整数値として読み込むことができ、iResourceViewUNORM で
アクセスすると、従来どおり 0~1.0 の少数値で受け取ることが
できるようになります。
例えばシェーダー側では次のように宣言しておきます。
Texture2D<uint4> InputTextureUI; Texture2D<float4> InputTextureF;
エフェクトの変数設定はこんな感じで。
iEffect->GetVariableByName( "InputTextureUI" )->AsShaderResource()-> SetResource( iTextureBufferUINT ); iEffect->GetVariableByName( "InputTextureF" )->AsShaderResource()-> SetResource( iTextureBufferUNORM );
受け取るシェーダー側です。
// PixelShader で整数としてアクセスする場合 (0~255) float4 PS_Update( noperspective float4 Pos : SV_POSITION, noperspective float2 UV : TEXCOORD ) : SV_Target { float2 pixsize; InputTexture.GetDimensions( pixsize.x, pixsize.y ); uint2 uvpos= (uint2)( UV.xy * pixsize.xy ); return InputTextureUI.Load( uint3(uvpos.xy,0) )* (1.0f/255.0f); } // 浮動少数で受け取れるので乗算が不要 (0~1.0) float4 PS_View( noperspective float4 Pos : SV_POSITION, noperspective float2 UV : TEXCOORD ) : SV_Target { float2 pixsize; InputTexture.GetDimensions( pixsize.x, pixsize.y ); uint2 uvpos= (uint2)( UV.xy * pixsize.xy ); return InputTextureF.Load( uint3(uvpos.xy,0) ); }
便利です。さすがに良く考えられています。
注意点は、以前のエントリ で書いたように同一のリソースを複数の View
として設定するため、リソースが握られたままになって衝突が
おきやすいことです。
・Direct3D 10 HLSL Effect/FX リソース設定のはまり
上記の PixelShader では、UV を 0.0~1.0 に補間した値で受け取って
いるためテクスチャのサイズを乗算する処理が入っています。
2007/10/08
Direct3D 10 ShaderModel 4.0 で整数の世界
Direct3D10/DirectX10 の ShaderModel4.0 で追加された新機能に
整数演算があります。テクスチャフォーマットにも SINT, UINT など
整数形式が追加されていて、入出力も演算も一通り整数だけの処理が
できるようになりました。
実際に試してみました。
今回使用したフォーマットは DXGI_FORMAT_R8G8B8A8_UINT です。
これまで使われてきた DXGI_FORMAT_R8G8B8A8_UNORM と違うのは、
読み書き時に 0~255 を 0.0~1.0 に変換しないことです。
直接 0~255 の数値(しかも整数)として扱うことができます。
厳密な色コードの判定ができるので、特定の色を抜く、置換する
などといったカラーキー処理がしやすくなります。
また 0/1 だけでよい 2値のフォントデータなどは、各bit に畳み込んで
おくことで効率よくデータを保持することができます。DXT1 で 1pixel
あたり 4bit なので、さらに 1/4 までデータが小さくなると考えられます。
ただし整数読み込みだとフィルタはかかりませんし、D3D10/DX10 では
DXGI_FORMAT_R1_UNORM という 1bit 形式のテクスチャも使えるので、
こちらを使ったほうが良いかもしれません。
UINT でデータを読み込む場合は、D3DX10 を使うと上記のようなコードに
なるでしょう。D3DX10_IMAGE_LOAD_INFO を使ってフォーマットを指定
しています。ところがこれ、うまく動きません。(August 2007 SDK)
UINT であっても内部的に 1/255 倍されてしまうらしく、バッファには
0 か 1 の値が書き込まれてしまいます。
自前でファイルを読み込んで Texture2D を作成すると正しく動作したので、
D3DX10 側の問題かもしれません。(違っていたらごめんなさい)
自分で作成する場合は下記のようになります。ファイルロード部分は
省いています。
レンダリングも試したので、RenderTargetView も作っておきます。
シェーダー側で整数値のままテクスチャから読み込むには Load() を
使います。(マニュアルはちょっとしたミスもあるようです。Return Value
が None になってますがこれは間違いでしょう。)
整数値テクスチャの読み込みでは Sample~() 系は使用できず、
サンプラーを通さないのでフィルタの類もかかりません。
Load() の場合、読み込みアドレスはピクセル座標で指定します。
これは一般的な UV 座標の 0~1.0f ではなく、0~imagesize-1 までの
ピクセル位置になります。そのため補間された UV から変換したり、また
画像全体を読み込む場合はあらかじめイメージサイズがわかっていなければ
なりません。画像サイズを調べるには こちら で紹介した GetDimensions() を使います。
画像全体が不要な場合、例えば任意の 128x128 pixel だけアクセスする
ような場合は、逆に 0~1.0 の UV 値と違って画像サイズを調べる必要が
ありません。
またスクリーンに対して 1対1 で転送を行う場合、PixelShader の
SV_POSITION で受け取ったスクリーン座標をそのまま渡すことができます。
Load() の引数が uint3 なのは、最後に MipLevel の指定が必要だからです。
読み込むテクスチャがどのフォーマットを返すのか、テクスチャの
宣言にも型の指定が必要です。この宣言は下記のようになります。
(なぜかマニュアルに説明がありませんでした)
整数型でレンダリングする場合は、PixelShader の戻り値も整数で宣言します。
レンダリングできました。きちんと整数のまま読み書きできています。
一般的にフレームバッファは DXGI_FORMAT_R8G8B8A8_UNORM 等の形式を
使うので、画面に描画してテストする場合は変換が必要です。
0~255 を 0.0f~1.0f にマッピングし、float4 で返します。
このへん、上記のシェーダー側の表記方法や機能、設定など、結構マニュ
アルに抜けやミスがあります。HLSL 部分のマニュアルは DirectX9 と
共有されており、D3D9 や ShaderModel 1~3 の解説もマージされています。
そのせいか、肝心の ShaderModel 4.0 の機能がわかりにくくなっています。
唯一の手がかりは実際に試すことです。1つ1つ試してエラーメッセージや
変換されたコードからから機能を類推しなければなりません。
今回、今までのシェーダー出力の調査や解析が役に立ちました。
asm 出力でテクスチャ宣言に型が埋め込んであるのを知っていなければ、
Texture2D<uint4> はもうしばらく気づかず見落としていたかもしれません。
整数演算があります。テクスチャフォーマットにも SINT, UINT など
整数形式が追加されていて、入出力も演算も一通り整数だけの処理が
できるようになりました。
実際に試してみました。
今回使用したフォーマットは DXGI_FORMAT_R8G8B8A8_UINT です。
これまで使われてきた DXGI_FORMAT_R8G8B8A8_UNORM と違うのは、
読み書き時に 0~255 を 0.0~1.0 に変換しないことです。
直接 0~255 の数値(しかも整数)として扱うことができます。
厳密な色コードの判定ができるので、特定の色を抜く、置換する
などといったカラーキー処理がしやすくなります。
また 0/1 だけでよい 2値のフォントデータなどは、各bit に畳み込んで
おくことで効率よくデータを保持することができます。DXT1 で 1pixel
あたり 4bit なので、さらに 1/4 までデータが小さくなると考えられます。
ただし整数読み込みだとフィルタはかかりませんし、D3D10/DX10 では
DXGI_FORMAT_R1_UNORM という 1bit 形式のテクスチャも使えるので、
こちらを使ったほうが良いかもしれません。
// ファイル読み込み (August2007 うまくいかない) D3DX10_IMAGE_LOAD_INFO info; memset( &info, 0, sizeof(D3DX10_IMAGE_LOAD_INFO) ); info.MipLevels= 1; info.Usage= D3D10_USAGE_DEFAULT; info.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET; info.Format= DXGI_FORMAT_R8G8B8A8_UINT; info.Filter= D3DX10_FILTER_NONE; info.MipFilter= D3DX10_FILTER_NONE; D3DX10CreateShaderResourceViewFromFile( iDevice, TEXT("rgba8.dds"), &info, NULL, &iTextureBuffer[i], NULL );
UINT でデータを読み込む場合は、D3DX10 を使うと上記のようなコードに
なるでしょう。D3DX10_IMAGE_LOAD_INFO を使ってフォーマットを指定
しています。ところがこれ、うまく動きません。(August 2007 SDK)
UINT であっても内部的に 1/255 倍されてしまうらしく、バッファには
0 か 1 の値が書き込まれてしまいます。
自前でファイルを読み込んで Texture2D を作成すると正しく動作したので、
D3DX10 側の問題かもしれません。(違っていたらごめんなさい)
自分で作成する場合は下記のようになります。ファイルロード部分は
省いています。
// Texture2D の作成, USAGE_DEFAULT なので初期データを必ず与える ID3D10Texture2D* riTexture2D= NULL; D3D10_TEXTURE2D_DESC t2ddesc; t2ddesc.Width= *width= phead->dwWidth; t2ddesc.Height= *height= phead->dwHeight; t2ddesc.MipLevels= 1; t2ddesc.ArraySize= 1; t2ddesc.Format= DXGI_FORMAT_R8G8B8A8_UINT; t2ddesc.SampleDesc.Count= 1; t2ddesc.SampleDesc.Quality= 0; t2ddesc.Usage= D3D10_USAGE_DEFAULT; t2ddesc.BindFlags= D3D10_BIND_SHADER_RESOURCE|D3D10_BIND_RENDER_TARGET; t2ddesc.CPUAccessFlags= 0; t2ddesc.MiscFlags= 0; // SysMemPitch の設定を忘れないように D3D10_SUBRESOURCE_DATA initdata; initdata.pSysMem= phead->DataBody; initdata.SysMemPitch= *width / sizeof(DWORD); initdata.SysMemSlicePitch= 0; iDevice->CreateTexture2D( &t2ddesc, &initdata, &riTexture2D ); // ShaderResourceView に変換する D3D10_SHADER_RESOURCE_VIEW_DESC srvdesc; srvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT; srvdesc.ViewDimension= D3D10_SRV_DIMENSION_TEXTURE2D; srvdesc.Texture2D.MostDetailedMip= 0; srvdesc.Texture2D.MipLevels= 1; iDevice->CreateShaderResourceView( riTexture2D, &srvdesc, iResourceView ); riTexture2D->Release();
レンダリングも試したので、RenderTargetView も作っておきます。
ID3D10Resource* riResource; iResourceView->GetResource( &riResource ); D3D10_RENDER_TARGET_VIEW_DESC rtvdesc; memset( &rtvdesc, 0, sizeof(D3D10_RENDER_TARGET_VIEW_DESC) ); rtvdesc.Format= DXGI_FORMAT_R8G8B8A8_UINT; rtvdesc.ViewDimension= D3D10_RTV_DIMENSION_TEXTURE2D; iDevice->CreateRenderTargetView( riResource, &rtvdesc, &iRenderBuffer );
シェーダー側で整数値のままテクスチャから読み込むには Load() を
使います。(マニュアルはちょっとしたミスもあるようです。Return Value
が None になってますがこれは間違いでしょう。)
整数値テクスチャの読み込みでは Sample~() 系は使用できず、
サンプラーを通さないのでフィルタの類もかかりません。
uint4 color_00= InputTexture.Load( uint3(uvpos.xy,0) );
Load() の場合、読み込みアドレスはピクセル座標で指定します。
これは一般的な UV 座標の 0~1.0f ではなく、0~imagesize-1 までの
ピクセル位置になります。そのため補間された UV から変換したり、また
画像全体を読み込む場合はあらかじめイメージサイズがわかっていなければ
なりません。画像サイズを調べるには こちら で紹介した GetDimensions() を使います。
float2 pixsize; InputTexture.GetDimensions( pixsize.x, pixsize.y );
画像全体が不要な場合、例えば任意の 128x128 pixel だけアクセスする
ような場合は、逆に 0~1.0 の UV 値と違って画像サイズを調べる必要が
ありません。
またスクリーンに対して 1対1 で転送を行う場合、PixelShader の
SV_POSITION で受け取ったスクリーン座標をそのまま渡すことができます。
Load() の引数が uint3 なのは、最後に MipLevel の指定が必要だからです。
読み込むテクスチャがどのフォーマットを返すのか、テクスチャの
宣言にも型の指定が必要です。この宣言は下記のようになります。
(なぜかマニュアルに説明がありませんでした)
Texture2D<uint4> InputTexture;
整数型でレンダリングする場合は、PixelShader の戻り値も整数で宣言します。
uint4 PS_Main( noperspective float4 Pos : SV_POSITION, noperspective float2 UV : TEXCOORD ) : SV_Target { ~
レンダリングできました。きちんと整数のまま読み書きできています。
一般的にフレームバッファは DXGI_FORMAT_R8G8B8A8_UNORM 等の形式を
使うので、画面に描画してテストする場合は変換が必要です。
0~255 を 0.0f~1.0f にマッピングし、float4 で返します。
このへん、上記のシェーダー側の表記方法や機能、設定など、結構マニュ
アルに抜けやミスがあります。HLSL 部分のマニュアルは DirectX9 と
共有されており、D3D9 や ShaderModel 1~3 の解説もマージされています。
そのせいか、肝心の ShaderModel 4.0 の機能がわかりにくくなっています。
唯一の手がかりは実際に試すことです。1つ1つ試してエラーメッセージや
変換されたコードからから機能を類推しなければなりません。
今回、今までのシェーダー出力の調査や解析が役に立ちました。
asm 出力でテクスチャ宣言に型が埋め込んであるのを知っていなければ、
Texture2D<uint4> はもうしばらく気づかず見落としていたかもしれません。
2007/10/07
Direct3D 10 HLSL Effect/FX リソース設定のはまり
●前置き
D3D10/DX10 のレンダリングは、入力と出力に同じものを指定することが
できません。
同じバッファ (Texture) を、RenderTarget と ShaderResource(Texture)
に同時に設定できず、もし設定しても入力側が強制的に 0 (NULL) 相当に
なります。少々厳密すぎる気もしますが、自己レンダリングは動作結果を
保障できないので当たり前といえば当たり前です。
非常にありがたいことに Direct3D10 では、間違って設定しても
DebugLayer が親切に教えてくれます。
ただ判定が厳密すぎるために少々問題になることもあります。
例として、テクスチャへのレンダリングを考えてみます。次のように
読み込み用と書き込み用の View を作成してあるものとします。
iTextureBuffer と iRenderBuffer は、同一の Resource Buffer を
参照していると思ってください。
下記の "InputTexture" は Effect(fx) 内で Texture2D 宣言された変数で、
入力するテクスチャを意味しています。登録用インターフェースを
iInputTexture にキャッシュしておきます。
描画時のセットアップを次のようにします。
Effect の変数に登録したパラメータは Apply() で反映されます。
これで Draw() を発行すると、Resource[0] を読み込んで Resource[1]
を更新することができます。
Resource[1] ← Resource[0]
その直後に、今度は入力と出力を入れ替えてレンダリングします。
Resource[0] ← Resource[1]
このとき、Resource[1] は前のレンダリングで RenderTarget として
登録されています。すでに Busy なので、入力テクスチャとして設定する
ことができません。
(ちなみにこれらの衝突は正確には Draw の実行タイミングで検出されます)
衝突を回避するには、先に RenderTarget のステートをクリアしておくか
別のターゲットを登録して参照をはずす必要があります。
SetResource と SetRenderTarget の順番を変えても同じで、今度は
入力テクスチャとして参照されているため RenderTarget への登録が
できなくなります。
このステート設定順の問題は以前のエントリでも触れました。
・Direct3D 10 Streamと同時Resource
また ClearState() を使うとこれらのリソース参照をいっぺんにはずすことができます。
・Direct3D 10 ClearState
ただし必要なものまで全部解除されてしまうので効率は悪くなります。
●本題
Effect(fx) を使って描画していると、このリソースの衝突がどうしても
発生してしまうことがあります。C/C++ のプログラムコード上は正しくても
うまくいかず、それだけでは原因がわからないのです。
list2 の (C) で Resource[0] を入力テクスチャとして参照し、一旦描画
しています。その後 (D) でテクスチャを変更して、別の Technique を使って
描画しようとしています。
InputTexture は iTextureBuffer[1] (Resource[1]) で置換されているし
Apply() もしているので、(E) のタイミングでは Resource[0] はフリーに
なったように見えます。
ところがシェーダーによっては (E) の SetRenderTarget() で衝突してしまう
可能性があります。
重要なのは描画に使っている Technique が異なっているということです。
ID3D10Effect 上では同一の変数&インターフェースとしてリソース登録が
できるものの、書き換えられる場所はシェーダーによって違うからです。
(1) シェーダーリソースの登録は、VS, GS, PS それぞれ別管理となっている
"Update" のシェーダーでは、InputTexture を VS と PS の両方で参照
しているかもしれません。Core API での Resource 登録は VS, GS, PS
それぞれ別なので、下記のように専用の登録 API があります。
ID3D10Device::VSSetShaderResources()
ID3D10Device::GSSetShaderResources()
ID3D10Device::PSSetShaderResources()
"Main" のシェーダーで、もし InputTexture の読み込みを PS だけで
行っているとしたら、(D) の Apply() で上書きされるのは
PSSetShaderResources() だけなのです。
そのため VSSetShaderResources() の参照が残ったままとなります。
(2) シェーダーによって、各リソース参照するスロット番号が異なっている。
"Update" の PixelShader が 2枚のテクスチャを参照しているとします。
このとき InputTexture が 0 と 1 のどちらのスロットに割り当てられる
のか予測できません。(Reflection を見るとわかる)
例えば "Update" では InputTexture は Slot 1 に割り当てられており、
"Main" の PixelShader では Slot 0 だとすると、
やはり (D) の Apply() では Slot 1 の参照は上書きされずに残って
しまうのです。
HLSL コンパイラは最適化によって、各シェーダーが本当に必要としている
リソースしかバインドしません。ID3D10Effect のステート管理も最小限の
更新だけで済むように最適化が行われているので、内部の動作をある程度
把握しておかないと、このような はまり に遭遇してしまうことになります。
個別に呼び出すとシェーダー単体は問題なく動作するし、C/C++ のコードも
問題が無いので原因がなかなか見つからないかもしれません。
注意点
・同じ Effect/Fx でも Technique や Pass が異なっていると違うシェーダー
であること
・シェーダー(Technique/Pass)ごとに Apply() しないと、厳密には各種リソース
のステートが上書きされないこと
よくあるのはこんなケースです。
1. 本来はシェーダーでテクスチャを参照している
2. デバッグのために一時的に Shader を書き換えて、固定値を出力して
動きを確認しようとする。
(例えば PS で強制的に return float4(1,0,0,1); とするなど)
3. このときテクスチャの参照が外れたので、Effect は Resource 設定の
上書きを行わずに前のステートが残ってしまう。
4. 固定値を返すように書き換えただけなのに、突然関係ないシェーダーの
描画で衝突検出が発生して驚く
Effect(fx) を使って、高レベルな API の理解だけで済ませてしまおう・・
として、思わぬはまりに遭遇することがあります。D3D10 ではある程度
Effect 内部動作や ID3D10Device 関連のステート設定についてもきちんと
把握しておく必要があるかもしれません。
D3D10/DX10 のレンダリングは、入力と出力に同じものを指定することが
できません。
同じバッファ (Texture) を、RenderTarget と ShaderResource(Texture)
に同時に設定できず、もし設定しても入力側が強制的に 0 (NULL) 相当に
なります。少々厳密すぎる気もしますが、自己レンダリングは動作結果を
保障できないので当たり前といえば当たり前です。
非常にありがたいことに Direct3D10 では、間違って設定しても
DebugLayer が親切に教えてくれます。
ただ判定が厳密すぎるために少々問題になることもあります。
例として、テクスチャへのレンダリングを考えてみます。次のように
読み込み用と書き込み用の View を作成してあるものとします。
iTextureBuffer と iRenderBuffer は、同一の Resource Buffer を
参照していると思ってください。
ID3D10ShaderResourceView* iTextureBuffer[2]; // テクスチャ ID3D10RenderTargetView* iRenderBuffer[2]; // レンダーターゲット // iTextureBuffer[0] と iRenderBuffer[0] はどちらも Resource[0] // iTextureBuffer[1] と iRenderBuffer[1] はどちらも Resource[1]
下記の "InputTexture" は Effect(fx) 内で Texture2D 宣言された変数で、
入力するテクスチャを意味しています。登録用インターフェースを
iInputTexture にキャッシュしておきます。
ID3D10EffectShaderResourceVariable* iInputTexture= iEffectModel->GetVariableByName( "InputTexture" ) ->AsShaderResource();
描画時のセットアップを次のようにします。
// list1 // 入力テクスチャの設定 iInputTexture->SetResource( iTextureBuffer[0] ); // ←入力 (A) iEffectModel->GetTechniqueByName( "Main" ) ->GetPassByIndex(0)->Apply(0); // 出力の設定 iDevice->OMSetRenderTargets( 1, &iRenderBuffer[1], NULL ); // ←出力 (B)
Effect の変数に登録したパラメータは Apply() で反映されます。
これで Draw() を発行すると、Resource[0] を読み込んで Resource[1]
を更新することができます。
Resource[1] ← Resource[0]
その直後に、今度は入力と出力を入れ替えてレンダリングします。
Resource[0] ← Resource[1]
// 入力テクスチャの設定 iInputTexture->SetResource( iTextureBuffer[1] ); iEffectModel->GetTechniqueByName( "Main" )->GetPassByIndex(0) ->Apply(0); // ←できない, (B)でBusy // 出力の設定 iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL );
このとき、Resource[1] は前のレンダリングで RenderTarget として
登録されています。すでに Busy なので、入力テクスチャとして設定する
ことができません。
(ちなみにこれらの衝突は正確には Draw の実行タイミングで検出されます)
衝突を回避するには、先に RenderTarget のステートをクリアしておくか
別のターゲットを登録して参照をはずす必要があります。
// 入力テクスチャの設定 ID3D10RenderTargetView* iRTVNull= NULL; iDevice->OMSetRenderTargets( 1, &iRTVNull, NULL ); // 参照外しのクリア iInputTexture->SetResource( iTextureBuffer[1] ); iEffectModel->GetTechniqueByName( "Main" ) ->GetPassByIndex(0)->Apply(0); // ←できる // 出力の設定 iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL );
SetResource と SetRenderTarget の順番を変えても同じで、今度は
入力テクスチャとして参照されているため RenderTarget への登録が
できなくなります。
// 出力の設定 iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL ); // ←できない, (A)でBusy // 入力テクスチャの設定 iInputTexture->SetResource( iTextureBuffer[1] ); iEffectModel->GetTechniqueByName( "Main" ) ->GetPassByIndex(0)->Apply(0);
このステート設定順の問題は以前のエントリでも触れました。
・Direct3D 10 Streamと同時Resource
また ClearState() を使うとこれらのリソース参照をいっぺんにはずすことができます。
・Direct3D 10 ClearState
ただし必要なものまで全部解除されてしまうので効率は悪くなります。
●本題
Effect(fx) を使って描画していると、このリソースの衝突がどうしても
発生してしまうことがあります。C/C++ のプログラムコード上は正しくても
うまくいかず、それだけでは原因がわからないのです。
// list2 iInputTexture->SetResource( iTextureBuffer[0] ); // (C) iEffectModel->GetTechniqueByName( "Update" ) ->GetPassByIndex(0)->Apply(0); ~描画など // 入力テクスチャの設定 iInputTexture->SetResource( iTextureBuffer[1] ); // (D) iEffectModel->GetTechniqueByName( "Main" ) ->GetPassByIndex(0)->Apply(0); // 出力の設定 iDevice->OMSetRenderTargets( 1, &iRenderBuffer[0], NULL ); // (E)
list2 の (C) で Resource[0] を入力テクスチャとして参照し、一旦描画
しています。その後 (D) でテクスチャを変更して、別の Technique を使って
描画しようとしています。
InputTexture は iTextureBuffer[1] (Resource[1]) で置換されているし
Apply() もしているので、(E) のタイミングでは Resource[0] はフリーに
なったように見えます。
ところがシェーダーによっては (E) の SetRenderTarget() で衝突してしまう
可能性があります。
重要なのは描画に使っている Technique が異なっているということです。
ID3D10Effect 上では同一の変数&インターフェースとしてリソース登録が
できるものの、書き換えられる場所はシェーダーによって違うからです。
(1) シェーダーリソースの登録は、VS, GS, PS それぞれ別管理となっている
"Update" のシェーダーでは、InputTexture を VS と PS の両方で参照
しているかもしれません。Core API での Resource 登録は VS, GS, PS
それぞれ別なので、下記のように専用の登録 API があります。
ID3D10Device::VSSetShaderResources()
ID3D10Device::GSSetShaderResources()
ID3D10Device::PSSetShaderResources()
"Main" のシェーダーで、もし InputTexture の読み込みを PS だけで
行っているとしたら、(D) の Apply() で上書きされるのは
PSSetShaderResources() だけなのです。
そのため VSSetShaderResources() の参照が残ったままとなります。
(2) シェーダーによって、各リソース参照するスロット番号が異なっている。
"Update" の PixelShader が 2枚のテクスチャを参照しているとします。
このとき InputTexture が 0 と 1 のどちらのスロットに割り当てられる
のか予測できません。(Reflection を見るとわかる)
例えば "Update" では InputTexture は Slot 1 に割り当てられており、
"Main" の PixelShader では Slot 0 だとすると、
やはり (D) の Apply() では Slot 1 の参照は上書きされずに残って
しまうのです。
HLSL コンパイラは最適化によって、各シェーダーが本当に必要としている
リソースしかバインドしません。ID3D10Effect のステート管理も最小限の
更新だけで済むように最適化が行われているので、内部の動作をある程度
把握しておかないと、このような はまり に遭遇してしまうことになります。
個別に呼び出すとシェーダー単体は問題なく動作するし、C/C++ のコードも
問題が無いので原因がなかなか見つからないかもしれません。
注意点
・同じ Effect/Fx でも Technique や Pass が異なっていると違うシェーダー
であること
・シェーダー(Technique/Pass)ごとに Apply() しないと、厳密には各種リソース
のステートが上書きされないこと
よくあるのはこんなケースです。
1. 本来はシェーダーでテクスチャを参照している
2. デバッグのために一時的に Shader を書き換えて、固定値を出力して
動きを確認しようとする。
(例えば PS で強制的に return float4(1,0,0,1); とするなど)
3. このときテクスチャの参照が外れたので、Effect は Resource 設定の
上書きを行わずに前のステートが残ってしまう。
4. 固定値を返すように書き換えただけなのに、突然関係ないシェーダーの
描画で衝突検出が発生して驚く
Effect(fx) を使って、高レベルな API の理解だけで済ませてしまおう・・
として、思わぬはまりに遭遇することがあります。D3D10 ではある程度
Effect 内部動作や ID3D10Device 関連のステート設定についてもきちんと
把握しておく必要があるかもしれません。
描画するプリミティブの形状は通常 IASetPrimitiveTopology() で
指定します。D3D10/DX10 では次の 9種類が定義されています。
ところがジオメトリシェーダーを使っている場合、
IASetPrimitiveTopology() の設定値は実際に描画する
プリミティブ形状と連動していません。
というのは、GeometryShader の出力プリミティブの種類はシェーダー
側で再定義されるからです。
PointSprite がその良い例で、入力 Topology は PointList でも
実際に描画される形状は TriangleStrip の板ポリゴンになっています。
つまりジオメトリシェーダーを使う場合の IASetPrimitiveTopology()
の設定値は、
・頂点ストリームを一度に読み進める量
・GeometryShader へ同時に入力されるパラメータ数
の決定にだけ使われると考えられます。
そこでジオメトリシェーダーへの入力頂点数にだけ注目してまとめると
次のようになります。
同時 1 頂点 POINTLIST
同時 2 頂点 LINELIST
同時 3 頂点 TRIANGLELIST
同時 4 頂点 LINELIST_ADJ
同時 6 頂点 TRIANGLELIST_ADJ
同時 5頂点が無いのが残念ですが、ほぼ 1~6 頂点までの汎用的な入力
頂点数として流用することが可能です。この考え方を使って
4頂点プリミティブや 6頂点プリミティブを定義することができます。
実際に 4角ポリゴン でモデルを作って描画してみました。
いわゆる QUADLIST 相当です。(D3DPT_QUADLIST ?)

いつものように、実行ファイル、ソース、シェーダー込みでダウンロード
できます。
モデルデータ側は四角形のあつまりなので、index list は 4つで
1ポリゴンとなります。
描画命令の発行は 同時4頂点入力の LINELIST_ADJ を使います。
無駄に凝ったことをしているので DrawIndexedInstance() になってますが、
LINELIST_ADJ 以外はごく普通の描画コードです。
DrawIndexed~() に渡す値もそのまま頂点数 (Index) の個数になっています。
もし1枚だけ描くなら 4頂点です。
VertexShader も PixelShader も通常の描画と全く同じものです。
ジオメトリシェーダーは次のとおりです。
lineadj で 4頂点入力、TriangleStrip で四角形を描画します。
これで QUADLIST の描画が可能となります。
ファイルはこちらです。
・wheelhandle_ss07t.zip
指定します。D3D10/DX10 では次の 9種類が定義されています。
D3D10_PRIMITIVE_TOPOLOGY_POINTLIST // 点 D3D10_PRIMITIVE_TOPOLOGY_LINELIST // 線 D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP // 連続線 D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST // 三角形 D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP // 連続三角形 D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ D3D10_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP_ADJ
ところがジオメトリシェーダーを使っている場合、
IASetPrimitiveTopology() の設定値は実際に描画する
プリミティブ形状と連動していません。
というのは、GeometryShader の出力プリミティブの種類はシェーダー
側で再定義されるからです。
PointSprite がその良い例で、入力 Topology は PointList でも
実際に描画される形状は TriangleStrip の板ポリゴンになっています。
つまりジオメトリシェーダーを使う場合の IASetPrimitiveTopology()
の設定値は、
・頂点ストリームを一度に読み進める量
・GeometryShader へ同時に入力されるパラメータ数
の決定にだけ使われると考えられます。
そこでジオメトリシェーダーへの入力頂点数にだけ注目してまとめると
次のようになります。
同時 1 頂点 POINTLIST
同時 2 頂点 LINELIST
同時 3 頂点 TRIANGLELIST
同時 4 頂点 LINELIST_ADJ
同時 6 頂点 TRIANGLELIST_ADJ
同時 5頂点が無いのが残念ですが、ほぼ 1~6 頂点までの汎用的な入力
頂点数として流用することが可能です。この考え方を使って
4頂点プリミティブや 6頂点プリミティブを定義することができます。
実際に 4角ポリゴン でモデルを作って描画してみました。
いわゆる QUADLIST 相当です。(D3DPT_QUADLIST ?)

いつものように、実行ファイル、ソース、シェーダー込みでダウンロード
できます。
モデルデータ側は四角形のあつまりなので、index list は 4つで
1ポリゴンとなります。
// torus.inc static unsigned short _Index[]= { 0, 1, 2, 3, // 四角ポリゴン1 4, 0, 3, 5, // 四角ポリゴン2 6, 4, 5, 7, // 四角ポリゴン3 8, 6, 7, 9, // 四角ポリゴン4 :
描画命令の発行は 同時4頂点入力の LINELIST_ADJ を使います。
// 描画 (ss07.cpp) iDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_LINELIST_ADJ ); iDevice->IASetInputLayout( iInputLayout ); UINT vsize= sizeof(VertexType); UINT voffset= 0; iDevice->IASetVertexBuffers( 0, 1, &iVertexBuffer, &vsize, &voffset ); iDevice->IASetIndexBuffer( iIndexBuffer, DXGI_FORMAT_R16_UINT, 0 ); iEffectModel->GetTechniqueByName( "Main" )->GetPassByIndex( 0 )->Apply( 0 ); iDevice->DrawIndexedInstanced( IndexCount, 16, 0, 0, 0 );
無駄に凝ったことをしているので DrawIndexedInstance() になってますが、
LINELIST_ADJ 以外はごく普通の描画コードです。
DrawIndexed~() に渡す値もそのまま頂点数 (Index) の個数になっています。
もし1枚だけ描くなら 4頂点です。
// 1枚だけ描く場合 iDevice->DrawIndexed( 4, 0, 0 );
VertexShader も PixelShader も通常の描画と全く同じものです。
ジオメトリシェーダーは次のとおりです。
typedef VS_OUTPUT GS_OUTPUT; [maxvertexcount(4)] void GS_Main( lineadj GS_OUTPUT In[4], inout TriangleStreamgsstream ) { gsstream.Append( In[0] ); gsstream.Append( In[1] ); gsstream.Append( In[3] ); gsstream.Append( In[2] ); gsstream.RestartStrip(); }
lineadj で 4頂点入力、TriangleStrip で四角形を描画します。
これで QUADLIST の描画が可能となります。
ファイルはこちらです。
・wheelhandle_ss07t.zip
2007/10/04
Direct3D 10 HLSL の asint/asuint/asfloat 命令
D3D10/DX10 の ShaderModel4.0 では整数演算を行うことができます。
この整数演算系の命令出力を見ていると、従来の浮動少数演算と
全く同じレジスタを使っていることがわかります。
Buffer やレジスタ、リソースアクセスは TYPELESS 宣言しなければ
型付けされますが、Shader の命令的には float だろうが int だろうが
特に区別が無いようです。
これは SSE 等の CPU 命令でも同じで、32bit ×4 の 128bit の値と
いうだけで特に動的な型情報はなさそうです。データの中身を利用する
側が便宜上区別しているだけに過ぎません。
例えば条件判定の結果によって 0.0f か 1.0f を代入したい場合、
HLSL コンパイラはこんなコードを出力します。
ここでは直後が ret なので o0 に代入されています。
上の v は float4 で宣言しています。
0x3f800000 は 32bit 浮動少数の 1.0f を bit 表現したものです。
r0.x が 0 なら o0.z もそのまま 0.0f になり、r0.x の bit が
すべて 1 なら 1.0f が代入されます。
つまり実数の 0.0f か 1.0f か選択するために整数演算の bit mask を
活用して最適化をはかっています。演算上整数データと浮動少数データの
区別がないことがわかります。
またこのことから lt 命令は結果を整数値で返し、さらに成立すれば
bit が全部 1 (-1)、不成立なら 0 を返すのだと予想できます。
(昔の basic 言語のように)
ちなみに、代入先の 0 と 1.0 を交換するとこんなコードになります。
movc は条件付転送命令のようです。
条件成立時の値を 1.0f 以外にしてみます。
条件式の結果からマスクで値を生成しているだけなので、どんな値でも
ペナルティが無いようです。
条件結果を整数で返す場合は次のようになりました。
HLSL/C言語の仕様的には条件式は -1 or 0 ではなく 1 or 0 なので、
こちらでも and 演算が入ってしまいます。
このような型を無視したデータの取り扱いを HLSL 上で行うためには
asint(), asuint(), asfloat() 命令を使います。
型変換ですがデータは変更しないので型キャストとは異なります。
キャストでは実際にデータを変換する命令に置き換わりますが、
これらの命令は実行時には何もしません。コンパイラに対して型が
変わったことを通知しているだけです。
例えば asfloat() の動作を C/C++ で書くとこんな感じです。
asfloat を使うと 1.0f を直接代入する代わりに次のように記述できます。
この as~系命令はかなり使えそうです。
例えば RGBA32F のテクスチャやレンダーターゲットで、R, G だけ
float とみなし、B, A を uint とみなして処理することもできます。
データのパックや圧縮など、開いているチャンネルの有効利用にも
応用できそうです。
以前作った迷路のシェーダーも、これを使っていればもっと判定が
簡略化できました。
・Direct3D ShaderModel4.0 Shaderで迷路作成
・Direct3D 10 ShaderModel4.0 迷路の自動探索Shader
ただ頂点データに使うなど、バーテックスシェーダーや
ジオメトリシェーダー から ピクセルシェーダー へ値がわたるときは
難しいでしょう。異なる型で補間が発生してしまうので、型を混用した
ようなデータは向いてないかもしれません。
GPU によっては浮動少数演算に特化されていて、整数専用の演算は
まだ特殊な ALU を必要としている可能性があります。演算性能を
最大限引き出すには float の方が有利なので、整数演算の多用は
パフォーマンスとの相談になってくるかもしれません。この点は
要注意です。
この整数演算系の命令出力を見ていると、従来の浮動少数演算と
全く同じレジスタを使っていることがわかります。
Buffer やレジスタ、リソースアクセスは TYPELESS 宣言しなければ
型付けされますが、Shader の命令的には float だろうが int だろうが
特に区別が無いようです。
これは SSE 等の CPU 命令でも同じで、32bit ×4 の 128bit の値と
いうだけで特に動的な型情報はなさそうです。データの中身を利用する
側が便宜上区別しているだけに過ぎません。
例えば条件判定の結果によって 0.0f か 1.0f を代入したい場合、
HLSL コンパイラはこんなコードを出力します。
// HLSL v.z= v.x < v.y ? 1.0f : 0.0f; // asm lt r0.x, v0.x, v0.y and o0.z, r0.x, l(0x3f800000)
ここでは直後が ret なので o0 に代入されています。
上の v は float4 で宣言しています。
0x3f800000 は 32bit 浮動少数の 1.0f を bit 表現したものです。
r0.x が 0 なら o0.z もそのまま 0.0f になり、r0.x の bit が
すべて 1 なら 1.0f が代入されます。
つまり実数の 0.0f か 1.0f か選択するために整数演算の bit mask を
活用して最適化をはかっています。演算上整数データと浮動少数データの
区別がないことがわかります。
またこのことから lt 命令は結果を整数値で返し、さらに成立すれば
bit が全部 1 (-1)、不成立なら 0 を返すのだと予想できます。
(昔の basic 言語のように)
ちなみに、代入先の 0 と 1.0 を交換するとこんなコードになります。
// HLSL v.z= v.x < v.y ? 0.0f : 1.0f; // asm lt r0.x, v0.x, v0.y movc o0.z, r0.x, l(0), l(1.000000)
movc は条件付転送命令のようです。
条件成立時の値を 1.0f 以外にしてみます。
// HLSL v.z= v.x < v.y ? 4095.0f : 0.0f; // asm lt r0.x, v0.x, v0.y and o0.z, r0.x, l(0x457ff000)
条件式の結果からマスクで値を生成しているだけなので、どんな値でも
ペナルティが無いようです。
条件結果を整数で返す場合は次のようになりました。
// HLSL uint c= v.x < v.y; // asm lt r0.x, v0.x, v0.y and o0.x, r0.x, l(1)
HLSL/C言語の仕様的には条件式は -1 or 0 ではなく 1 or 0 なので、
こちらでも and 演算が入ってしまいます。
このような型を無視したデータの取り扱いを HLSL 上で行うためには
asint(), asuint(), asfloat() 命令を使います。
型変換ですがデータは変更しないので型キャストとは異なります。
キャストでは実際にデータを変換する命令に置き換わりますが、
これらの命令は実行時には何もしません。コンパイラに対して型が
変わったことを通知しているだけです。
例えば asfloat() の動作を C/C++ で書くとこんな感じです。
float asfloat( int b ) { return *(float*)(&b); } // もしくは float asfloat( int b ) { union { float _f; int _i; } temp; temp._i= b; return temp._f; }
asfloat を使うと 1.0f を直接代入する代わりに次のように記述できます。
float b= asfloat( 0x3f800000 );
この as~系命令はかなり使えそうです。
例えば RGBA32F のテクスチャやレンダーターゲットで、R, G だけ
float とみなし、B, A を uint とみなして処理することもできます。
データのパックや圧縮など、開いているチャンネルの有効利用にも
応用できそうです。
以前作った迷路のシェーダーも、これを使っていればもっと判定が
簡略化できました。
・Direct3D ShaderModel4.0 Shaderで迷路作成
・Direct3D 10 ShaderModel4.0 迷路の自動探索Shader
ただ頂点データに使うなど、バーテックスシェーダーや
ジオメトリシェーダー から ピクセルシェーダー へ値がわたるときは
難しいでしょう。異なる型で補間が発生してしまうので、型を混用した
ようなデータは向いてないかもしれません。
GPU によっては浮動少数演算に特化されていて、整数専用の演算は
まだ特殊な ALU を必要としている可能性があります。演算性能を
最大限引き出すには float の方が有利なので、整数演算の多用は
パフォーマンスとの相談になってくるかもしれません。この点は
要注意です。
2007/10/03
Direct3D 10 HLSL で再帰呼び出しの展開
HLSL では関数を再帰呼び出しすることができません。
>error X3500: '_Sub0': recursive functions not yet implemented
一応 asm 命令ではシェーダープログラムのサブルーチンコールは
存在していて、call ~ ret や label 等のニモニックもあります。
今まで調べた限りでは、現在の HLSL (Shader4.0) でこれらの命令が
使われるのは、唯一 switch 文の attribute に [call] を指定した
場合だけでした。
データスタックが無いので、ローカル変数の保護など、その辺の
実装でハードルが高いのかもしれません。
とはいっても、テスト中はジオメトリシェーダー等でほんの数段で
よいから再帰的にコードを記述したくなることがあります。
シェーダー関数は基本的にすべて inline 展開されるので、指定した
数だけ勝手に inline 展開してくれれば実現可能でしょう。
将来のコンパイラで実装してほしい機能です。
([recursive(4)] _Sub0( .. ) とか、こんな感じで)
というわけで手動で再帰を展開してみます。
まず再帰関数をマクロ定義します。
必要な段数だけ定義します。
呼び出しの例
再起呼び出しの代わりに、全く同じ定義内容の別名関数を呼び出して
いるだけです。この場合 _Sub0() から呼ばれる関数は _Sub1() で
以後終了条件まで増えていきます。そのため必要な再帰の数だけ別名で
定義しておく必要が生じます。
上の例では _DEFFUNC の最後は自分自身の呼び出しをしていますが、
定数による inline 展開では、その関数が実際に呼ばれない限り HLSL
コンパイラではエラーにならないようです。
もし
のような感じで、動的なパラメータを使った呼び出しにすると最後まで
展開する必要があるためエラーになります。
このときは終端だけ専用の関数を用意します。
実際は上の例の _DEFFUNC と違って、もっと複雑な条件を持った、
もっと長い再帰関数を記述することになります。その場合いちいち行末に
「\」をつけたマクロの連続行形式で書かなければならないのが少々難点です。
>error X3500: '_Sub0': recursive functions not yet implemented
一応 asm 命令ではシェーダープログラムのサブルーチンコールは
存在していて、call ~ ret や label 等のニモニックもあります。
今まで調べた限りでは、現在の HLSL (Shader4.0) でこれらの命令が
使われるのは、唯一 switch 文の attribute に [call] を指定した
場合だけでした。
データスタックが無いので、ローカル変数の保護など、その辺の
実装でハードルが高いのかもしれません。
とはいっても、テスト中はジオメトリシェーダー等でほんの数段で
よいから再帰的にコードを記述したくなることがあります。
シェーダー関数は基本的にすべて inline 展開されるので、指定した
数だけ勝手に inline 展開してくれれば実現可能でしょう。
将来のコンパイラで実装してほしい機能です。
([recursive(4)] _Sub0( .. ) とか、こんな感じで)
というわけで手動で再帰を展開してみます。
まず再帰関数をマクロ定義します。
#define _DEFFUNC(V0,V1) \ float4 _Sub##V0( uint a, float4 col ) \ { \ if( --a > 0 ){ \ return _Sub##V1( a, col.yzwx ); \ } \ return col; \ }
必要な段数だけ定義します。
_DEFFUNC(4,4) _DEFFUNC(3,4) _DEFFUNC(2,3) _DEFFUNC(1,2) _DEFFUNC(0,1)
呼び出しの例
float4 PS_Main( PS_INPUT In ) : SV_Target { return _Sub0( 2, In.Color ); }
再起呼び出しの代わりに、全く同じ定義内容の別名関数を呼び出して
いるだけです。この場合 _Sub0() から呼ばれる関数は _Sub1() で
以後終了条件まで増えていきます。そのため必要な再帰の数だけ別名で
定義しておく必要が生じます。
上の例では _DEFFUNC の最後は自分自身の呼び出しをしていますが、
定数による inline 展開では、その関数が実際に呼ばれない限り HLSL
コンパイラではエラーにならないようです。
もし
return _Sub0( In.Level, In.Color );
のような感じで、動的なパラメータを使った呼び出しにすると最後まで
展開する必要があるためエラーになります。
このときは終端だけ専用の関数を用意します。
float4 _Sub4( uint a, float4 col ) { return col; } _DEFFUNC(3,4) _DEFFUNC(2,3) _DEFFUNC(1,2) _DEFFUNC(0,1)
実際は上の例の _DEFFUNC と違って、もっと複雑な条件を持った、
もっと長い再帰関数を記述することになります。その場合いちいち行末に
「\」をつけたマクロの連続行形式で書かなければならないのが少々難点です。
2007/10/02
Direct3D 10 Shader4.0 ジオメトリシェーダーで破壊する
GeometryShader は、Shader3.0 以前はできなかったさまざまな用途に
応用することができます。
・面(primitive)単位の処理、エッジの処理
・隣接頂点の参照
・VertexShader の代わり
・頂点(primitive)の追加
・primitive の削除
・その他いろいろ
頂点シェーダーは 1頂点単位の変換なので、そのままでは他の頂点情報の
参照ができません。また動的な追加削除は矛盾を引き起こしてしまいます。
あらかじめ面単位に分割しておいたり、隣接頂点座標を1頂点に入れて
おいたりと、さまざまな工夫と前処理が必要でした。
D3D10/DX10 で追加されたジオメトリシェーダーは、このような面(プリミ
ティブ)ごとの処理を、データ側の加工無しに行うことができます。
(topology の adjacency data は別に情報が必要)
試しにポリゴンをばらばらにするシェーダーを作ってみました。

以前 Xbox1 のゲームでも同じようなシェーダーを作成し、シェーダー
だけで任意のモデルをばらばらに破壊する表現を用いたことがあります。
当時は ShaderModel1.0 だったので、あらかじめ情報を頂点に埋め込んで
おく必要がありました。専用コンバータを用意して、共有頂点を分割したり
回転中心からの距離を求めておいたりと、専用のモデルデータに
なっています。
今回はジオメトリシェーダーのおかげで、データ自体は何もいじる必要が
ありませんでした。普通に描画するデータをそのままシェーダーに渡す
だけで簡単(?)に破壊することができます。
なお Local/World 座標での演算が必要なので、Transform 自体も
ジオメトリシェーダーで行う必要があります。
そのため VertexShader は何もせず、頂点を GeometryShader に渡して
いるだけとなっています。
データは同じですが演算量は増加します。面ごとに 3頂点分の演算が発生
するので、普通に描画するよりはかなり重くなっているはずです。
破壊については、本当ならば StreamOutput を活用すべきところですが
使っていません。単純に面法線と重力方向にローカル回転させながら
吹っ飛ばしているだけです。
・wheelhandle_ss06t.zip
いつものように ss06.exe で実際に実行することができます。
DirectX10 が走る環境と DirectX SDK August2007 Runtime が必要です。
今までと違って速度調整が入っていて、約 60fps 前後で固定するように
なっています。(追記: そのままだと速すぎるからです)
応用することができます。
・面(primitive)単位の処理、エッジの処理
・隣接頂点の参照
・VertexShader の代わり
・頂点(primitive)の追加
・primitive の削除
・その他いろいろ
頂点シェーダーは 1頂点単位の変換なので、そのままでは他の頂点情報の
参照ができません。また動的な追加削除は矛盾を引き起こしてしまいます。
あらかじめ面単位に分割しておいたり、隣接頂点座標を1頂点に入れて
おいたりと、さまざまな工夫と前処理が必要でした。
D3D10/DX10 で追加されたジオメトリシェーダーは、このような面(プリミ
ティブ)ごとの処理を、データ側の加工無しに行うことができます。
(topology の adjacency data は別に情報が必要)
試しにポリゴンをばらばらにするシェーダーを作ってみました。

以前 Xbox1 のゲームでも同じようなシェーダーを作成し、シェーダー
だけで任意のモデルをばらばらに破壊する表現を用いたことがあります。
当時は ShaderModel1.0 だったので、あらかじめ情報を頂点に埋め込んで
おく必要がありました。専用コンバータを用意して、共有頂点を分割したり
回転中心からの距離を求めておいたりと、専用のモデルデータに
なっています。
今回はジオメトリシェーダーのおかげで、データ自体は何もいじる必要が
ありませんでした。普通に描画するデータをそのままシェーダーに渡す
だけで簡単(?)に破壊することができます。
なお Local/World 座標での演算が必要なので、Transform 自体も
ジオメトリシェーダーで行う必要があります。
そのため VertexShader は何もせず、頂点を GeometryShader に渡して
いるだけとなっています。
データは同じですが演算量は増加します。面ごとに 3頂点分の演算が発生
するので、普通に描画するよりはかなり重くなっているはずです。
破壊については、本当ならば StreamOutput を活用すべきところですが
使っていません。単純に面法線と重力方向にローカル回転させながら
吹っ飛ばしているだけです。
・wheelhandle_ss06t.zip
いつものように ss06.exe で実際に実行することができます。
DirectX10 が走る環境と DirectX SDK August2007 Runtime が必要です。
今までと違って速度調整が入っていて、約 60fps 前後で固定するように
なっています。(追記: そのままだと速すぎるからです)
2007/10/01
Direct3D 10 Shader4.0 消えた abs と最適化
テクスチャから読み込んだピクセルの値が 0.1 付近であることを
判定しようとして、HLSL で当初こんなコードを書いていました。
テクスチャのピクセルサイズが不明なので、± 0.05 くらいの
誤差を許容しています。
あまりにそのまんまなので、もう少しちゃんと書こうとして次の
ように修正してみました。
どちらが複雑度が高いか比較するためにアセンブラで確認してみます。
結果は上の TestA() 3命令で、下の TestB() が 2命令に展開されています。
見てわかるとおり abs 命令がありません。lt 命令のソースオペランド
に直接絶対値指定らしき修飾子 |~| が記述されています。
Shader3.0 までは、abs は独立した命令 'abs' だったので便利に
なっています。良く考えたらもともと符号反転は出来たので、符号を
落とすだけの abs も簡単なのかもしれません。
ただし共通バイトコードでは単なる修飾子でも、ドライバレベル以降の
ネイティブコードでは独立した命令として実行される可能性があります。
NVIDIA の OpenGL 拡張命令から D3D10 Shader4.0 相当の ASM Shader
を調べてみるとしっかり ABS 命令が存在していました。
実際に比べてみます。もともと動作しているシェーダーに abs を挿入
してコンパイルし、消費する命令 slot 数が変化しないことを確認します。
比較しやすいように 300回ほどループさせて比べます。
GeForce8800GTS で走らせたところ速度差が出ました。
GeForce8800(G80) では実際には abs は個別の命令となっていて、
それぞれの絶対値演算で別の実行サイクルを消費しているように見えます。
つまり
mad r1.xyz, |v0.xyzx|, |v1.xxxx|, r1.xyzx
は本当は 3命令相当で、さらに内部で追加の temp レジスタを消費
している可能性もあります。
このことから、Direct3D 上のバイトコードで命令 slot 数やレジスタ
数をみても、実行速度や最適化の目安に過ぎないということがわかります。
また HLSL コンパイラの段階では abs をコストフリーと考えて
最適化している可能性もあります。
将来ぎりぎりの最適化を行うようになったら、この辺は要注意ですね。
さて、最初に戻って TestA と TestB の比較ですが、ge ×2 + and の
TestA よりも、abs を使った TestB の方がずっと高速でした。
数値そのものは他の処理も含んでいるのであまり意味を持たないのですが、
差が生じていることはわかります。
もしかしたら単なる命令差よりも、文脈上 B の方が消費レジスタが1つ
少ないことが原因かもしれません。
例えば
E= A*B*C*D
という演算を行う場合、一般的な CPU や Shader1.0 世代の GPU では
(1) R1= A*B
(2) R1= R1*C
(3) R1= R1*D
よりも
(4) R1= A*B
(5) R2= C*D
(6) R1= R1*R2
の方が高速です。これは最適化のテクニックとしても良く用いられます。
その理由は (1)~(3) の演算にはすべて依存関係があり、前の演算結果が
が出るまでパイプラインがストールするからです。
(4) と (5) は依存関係が全く無いので、完全に並列演算が可能となります。
アウトオブオーダーなら C,D の準備が出来次第、(4) よりも (5) を
先に実行するかもしれません。
ところが今の GPU は逆であり、パイプラインストールは完全に他の
スレッドで埋めてしまいます。よって (4)~(6) は余計な R2 を消費する
分だけ逆にスレッド並列化を阻害し、(1)~(3) よりも低速になってしまう
わけです。
TestA と TestB の関係も同じで、TestA の方が最初の2命令に依存関係が
無いので、今までの感覚で見ているとついこちら方が良いコードに見えて
しまいます。
判定しようとして、HLSL で当初こんなコードを書いていました。
テクスチャのピクセルサイズが不明なので、± 0.05 くらいの
誤差を許容しています。
int TestA( float4 color ) { return color.x > 0.05f && color.x < 0.15f; }
あまりにそのまんまなので、もう少しちゃんと書こうとして次の
ように修正してみました。
int TestB( float4 color ) { return abs( color.x - 0.1f ) < 0.05f; }
どちらが複雑度が高いか比較するためにアセンブラで確認してみます。
結果は上の TestA() 3命令で、下の TestB() が 2命令に展開されています。
// TestA ge r1.x, r0.x, l(0.050000) ge r1.y, l(0.150000), r0.x and r1.x, r1.x, r1.y // TestB add r1.x, r0.x, l(-0.100000) lt r1.x, |r1.x|, l(0.050000)
見てわかるとおり abs 命令がありません。lt 命令のソースオペランド
に直接絶対値指定らしき修飾子 |~| が記述されています。
Shader3.0 までは、abs は独立した命令 'abs' だったので便利に
なっています。良く考えたらもともと符号反転は出来たので、符号を
落とすだけの abs も簡単なのかもしれません。
ただし共通バイトコードでは単なる修飾子でも、ドライバレベル以降の
ネイティブコードでは独立した命令として実行される可能性があります。
NVIDIA の OpenGL 拡張命令から D3D10 Shader4.0 相当の ASM Shader
を調べてみるとしっかり ABS 命令が存在していました。
実際に比べてみます。もともと動作しているシェーダーに abs を挿入
してコンパイルし、消費する命令 slot 数が変化しないことを確認します。
比較しやすいように 300回ほどループさせて比べます。
// abs 無し loop ige r2.x, r1.w, l(300) breakc_nz r2.x mad r1.xyz, v0.xyzx, v1.xxxx, r1.xyzx iadd r1.w, r1.w, l(1) endloop // abs あり loop ige r2.x, r1.w, l(300) breakc_nz r2.x mad r1.xyz, |v0.xyzx|, |v1.xxxx|, r1.xyzx iadd r1.w, r1.w, l(1) endloop
GeForce8800GTS で走らせたところ速度差が出ました。
10000~10200 (usec) abs無し 14500~15400 (usec) absあり
GeForce8800(G80) では実際には abs は個別の命令となっていて、
それぞれの絶対値演算で別の実行サイクルを消費しているように見えます。
つまり
mad r1.xyz, |v0.xyzx|, |v1.xxxx|, r1.xyzx
は本当は 3命令相当で、さらに内部で追加の temp レジスタを消費
している可能性もあります。
このことから、Direct3D 上のバイトコードで命令 slot 数やレジスタ
数をみても、実行速度や最適化の目安に過ぎないということがわかります。
また HLSL コンパイラの段階では abs をコストフリーと考えて
最適化している可能性もあります。
将来ぎりぎりの最適化を行うようになったら、この辺は要注意ですね。
さて、最初に戻って TestA と TestB の比較ですが、ge ×2 + and の
TestA よりも、abs を使った TestB の方がずっと高速でした。
数値そのものは他の処理も含んでいるのであまり意味を持たないのですが、
差が生じていることはわかります。
26000 (usec) TestA 15000 (usec) TestB
もしかしたら単なる命令差よりも、文脈上 B の方が消費レジスタが1つ
少ないことが原因かもしれません。
例えば
E= A*B*C*D
という演算を行う場合、一般的な CPU や Shader1.0 世代の GPU では
(1) R1= A*B
(2) R1= R1*C
(3) R1= R1*D
よりも
(4) R1= A*B
(5) R2= C*D
(6) R1= R1*R2
の方が高速です。これは最適化のテクニックとしても良く用いられます。
その理由は (1)~(3) の演算にはすべて依存関係があり、前の演算結果が
が出るまでパイプラインがストールするからです。
(4) と (5) は依存関係が全く無いので、完全に並列演算が可能となります。
アウトオブオーダーなら C,D の準備が出来次第、(4) よりも (5) を
先に実行するかもしれません。
ところが今の GPU は逆であり、パイプラインストールは完全に他の
スレッドで埋めてしまいます。よって (4)~(6) は余計な R2 を消費する
分だけ逆にスレッド並列化を阻害し、(1)~(3) よりも低速になってしまう
わけです。
TestA と TestB の関係も同じで、TestA の方が最初の2命令に依存関係が
無いので、今までの感覚で見ているとついこちら方が良いコードに見えて
しまいます。