DirectX Mathについて
DirectX Mathは、Xbox Mathライブラリ、ひいてはXNA Mathライブラリに端を発する、ベクトル、マトリクス演算用ライブラリです(ただし、いずれも4要素、4×4要素まで)。
もともとDirextXには、ベクトル、マトリクス演算用の関数群が付帯していましたが、Windows RTではこれらは使えないそうです(確認したわけではありません)。したがって、Metro Styleのアプリケーションをつくり、Windows RT上での動作を想定するならば、現状ではDirectX Mathを使うのが良いと思われます。DirectX Mathは、Windows SDKに付帯されるようで、VisualStudio2012 RCでは、特別なライブラリをリンクしなくてもコンパイルできました。またこの演算ライブラリはDirectX本体からは独立しているようなので、これからはDirectXのアプリケーション以外でも、気軽に使えるvecmath演算ライブラリという位置づけになるのかもしれません。
DirectX Math 3.03
手始めに、VisualStudio2012 RCに同梱されている、DirectX Math 3.03における変更点について調べてみました。DirectX Mathのバージョンは、実は地味に進化しているようで、Windows8 Developer Previewのときは3.00で、それからCustomer Previewで3.02になり、今回のRelease Previewで3.03になったようです。番号は地味な変化ですが、互換性は無いようです。
今回の変更点は、
- DirectX::XMMATRIXの、個々のマトリクス要素に対する直接アクセスが出来なくなった。
- Floatベクトル型のコンストラクタのうち、ポインタを引数に取るものにexplicit宣言が追加された。
だそうです。
互換性がなくなったことは容易に想像がつきます。ここらあたりを含めて、DirectX Mathに関して勉強してみたいと思います。
DirectX Mathの動作条件
DirectX Mathの動作条件は、
- Window(x86/x64)の場合はSSE/SSE2命令をサポートしていること
- Windows on(ARM)では、ARM-NEON命令をサポートしていること
- 上記に当てはまらない場合はコンパイル時に、_XM_NO_INTRINSICS_を宣言すること
となっています。大切なのは、コンパイル時に決定しなければならないので、CPUに応じて動的に変更することは、簡単に出来ません。ただ、IA版はSSE2、ARM版はARM-NEONということで、比較的新しいプロセッサに対応するのは、それほど難しいことではないと思います。また念のためCPUが上記命令をサポートしているかは下記の関数で調べられます。
if (! DirectX::XMVerifyCPUSupport()) { MessageBoxA( NULL, "Unsupported hardaware detected." , NULL , MB_ICONEXCLAMATION | MB_OK ); return 1; }
ベクター型について
まず基本となる、float4要素のベクター型について見てみたいと思います。
XMVECTOR | IA上では __m128, ARM上では __n128のtypedefで宣言されている 他の型からの代入等は宣言されていない |
XMVECTORF32 | XMVECTORとfloatの配列の共用体宣言 デフォルトコンストラクタ以外はコンストラクタが宣言されていない |
XMFLOAT4 | float変数4つから成る構造体 アラインメント等は一切設定されていない 比較的自由に扱えるが、XMVECTORに直接代入する術がない |
ほかにもいろいろ型がありますが、とりあえず基本になるのはこの3つです。XMVECTOR型は完全にSIMD演算命令のソース、デスティネーションになる事を想定した型です。構造体を宣言することもなく、単なる128bit型のtypdefとして宣言されていますので、float演算に限ったものでもありません。XMVECTORF32型は__m128を、32bit float4要素で扱う時に適した型です。16byteアラインがとられ、__m128としてSIMD演算に使用することもできますし、unionで宣言されたfloat配列を介して、個々の要素にアクセスすることも出来ます。XMFLAOT4は単なるfloat型のメンバー4つから成る構造体で、float型としての参照、代入は自由に行えますし、アラインメントの制限もないので、パッキングされたデータにも使えます。ただし、XMVECTOR型に代入するには明示的に関数を呼び出す必要があり、下記の関数で呼び出すことで、XMVECTOR型とのやり取りが出来ます。
VOID XMStoreFloat4(XMFLOAT4 *pDestination, XMVECTOR V); XMVECTOR XMLoadFloat4(const XMFLOAT4 *pSource);
XMFLOAT4Aという型もあります。これは16byteアラインメントの取られたXMFLOAT4型で、下記の関数を用いることで、若干高速にXMVECTOR型へのロード、ストアが出来ます。
VOID XMStoreFloat4A(XMFLOAT4A *pDestination, XMVECTOR V); XMVECTOR XMLoadFloat4A(const XMFLOAT4A *pSource);
XMVECTOR型の初期化について
上記で説明したとおり、XMVECTORは__m128/__n128そのもので、他の型からの代入等は宣言されていません。加えて、XMVECTORF32はXMVECTORとfloatの配列の共用体宣言で、デフォルトコンストラクタ以外宣言されてません。では簡単にXMVECTOR型を初期化する方法は無いでしょうか。現状で考えられそうなのは、
XMVECTOR v; XMVECTORF32 f32Wrk = {1.0f, 1.0f, 1.0f, 1.0f}; v = f32Wrk;
もしくは、XMVECTORF32型の、float配列メンバーを個々に初期化して代入するかです。いずれにせよXMVECTORF32型の、f[]メンバーを介しての初期化です。しかし、XMVECTORは単なる__m128型なので、SSEの組み込み関数を直接かけば、もっと間単にかけるケースもあると思います。
static const float fArr[4] = {1.0f, 2.0f, 3.0f, 4.0f}; XMVECTOR v; v = _mm_loadu_ps(fArr);
SSE命令に詳しい方は、こっちの方が楽に記述できるかもしれません。しかし、Windows RTでの互換性を考えると使わないほうが賢明かもしれません。
XMVECTORに関連する3つのtypdef
XMVECTORには、主に関数の引数に使用する3つのtypedefがあり、DirectX Mathの関数でも使用されています。
- FXMVECTOR
- GXMVECTOR
- CXMVECTOR
これら、はいずれもconst XMVECTORを関数に引数で渡す際に、効率的な渡し方を提供するものです。いずれもconst修飾子がつけれられ、違いは、参照渡しにするか、値渡しにするかの違いです。これらはコンパイルする際のプラットフォームで変化します。
- win32プラットフォームでは、_fastcallの呼び出し規約を適用した関数は、3つまでは、__m128型のインスタンスを、レジスタを介して渡すことが出来ます。そのため、3つまでは引数を値渡しでレジスタにコピーすることが望ましいです。ただし、4つ目以降で値渡しを使うと、値の参照のみの場合は無駄なコピーとなります。つまり、引数の値を参照するだけの場合ならば、3つ目までは値渡し、4つめ以降は参照渡しが望ましい形になります。
- ARMの NEON をサポートしたプラットフォームと、 xbox360 では、_m128を4つまでレジスタに積み込むことができるので、4つ目までは値渡し、以後は参照渡しが望ましいと思われます。
- x64プラットフォームでは、_m128型のレジスタ渡しがサポートされていないので、すべてのインスタンスを、参照渡しで渡すのが望ましいと思われます。
これらの差異を吸収するために、この三つのtypedefがあり、1~3番目の引数にFXMVECTORを使用し、4番目の引数にGXMVECTORを用い、5番目以降の引数にCXMVECTORを用いて関数を記述しておくことで、上記の差異をプラットフォーム別に宣言された、typedefで吸収することが出来ます。
ためしに、32bit版windowsで、下記の関数をコールしたものの、呼び出し部分を下記に示します。
XMVECTOR XMVectorHermiteV(FXMVECTOR Position0, FXMVECTOR Tangent0, FXMVECTOR Position1, GXMVECTOR Tangent1, CXMVECTOR T); XMVECTOR v1, v2, v3, v4, v5; XMVECTOR v = XMVectorHermiteV(v1, v2, v3, v4, v5); cmp byte ptr [ebp-281h],0 jne wmain+109h (0E40D9h) push 0E41F1h call __RTC_UninitUse (0E10BEh) add esp,4 cmp byte ptr [ebp-275h],0 jne wmain+11Fh (0E40EFh) push 0E41EEh call __RTC_UninitUse (0E10BEh) add esp,4 cmp byte ptr [ebp-269h],0 jne wmain+135h (0E4105h) push 0E41EBh call __RTC_UninitUse (0E10BEh) add esp,4 lea eax,[v5] push eax lea ecx,[v4] push ecx movaps xmm2,xmmword ptr [v3] movaps xmm1,xmmword ptr [v2] movaps xmm0,xmmword ptr [v1] call DirectX::XMVectorHermiteV (0E11FEh) add esp,8 movaps xmmword ptr [ebp-260h],xmm0 movaps xmm0,xmmword ptr [ebp-260h] movaps xmmword ptr [v],xmm0
debugでコンパイルしましたが、確かにv1~v3がレジスタに格納されているようです。XMVECTOR型はアラインメントを取っているので、moveapsが使われています。ちなみに、XMVectorHermiteV()はinlineで宣言されており、__fastcallは宣言されていませんでした。コンパイル時は/Gdでコンパイルされています。releaseでコンパイルすると、もちろんinline展開されます。
こちらはx64でコンパイルした結果です。アドレスを渡しています。
XMVECTOR v1, v2, v3, v4, v5; XMVECTOR v= XMVectorHermiteV(v1,v2,v3,v4,v5); lea rax,[rsp+1A0h] mov qword ptr [rsp+20h],rax lea r9,[rsp+170h] lea r8,[rsp+140h] lea rdx,[rsp+110h] lea rcx,[rsp+0E0h] call DirectX::XMVectorHermiteV (013F431028h) movaps xmmword ptr [rsp+1D0h],xmm0 movaps xmm0,xmmword ptr [rsp+1D0h] movaps xmmword ptr [rsp+1C0h],xmm0
ベクトル型に関連する、3.03における変更点
下記クラスのポインタを引数にとるコンストラクタに、explicit宣言が追加されたようです。
- DirectX::XMFLOAT2
- DirectX::XMFLOAT2A
- DirectX::XMFLOAT3
- DirectX::XMFLOAT3A
- DirectX::XMFLOAT4
- DirectX::XMFLOAT4A
これらのクラスは直接SIMD演算とは関連がないクラスでした。つまり今回のexplicit宣言の導入は、SIMD利用の最適化というよりは、単にプログラマの不用意な記述によるミスを避けるためと思われます。
長くなってきたので、XMMATRIXについては次回
Visual Studio 2012 RC をインストールすしてから Visual Studio 2010 を使用すると、
LNK1123 という、COFFが壊れてる旨のエラーが出ました。フルビルドしても同様のエラーが出ます。
私の場合はVS2010 にSPをインストールしたら直りました。
同様の問題が、下記のフォーラムで報告されています。念のため。
http://social.msdn.microsoft.com/Forums/da-DK/vssetup/thread/d10adba0-e082-494a-bb16-2bfc039faa80