2007/10/04
Direct3D 10 HLSL の asint/asuint/asfloat 命令
Posted by: oga
at 20:21
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 の方が有利なので、整数演算の多用は
パフォーマンスとの相談になってくるかもしれません。この点は
要注意です。