DirectX Math(2)

前回に引き続き、DirectX Mathに関して見ていきたいと思います。

XMMATRIXに関して

XMMATRIXは下記の通り宣言されています。

struct XMMATRIX;

// Fix-up for XMMATRIX parameters to pass in-register on Xbox 360, by reference otherwise
#if defined(_XM_VMX128_INTRINSICS_) && !defined(_XM_NO_INTRINSICS_)
typedef const XMMATRIX CXMMATRIX;
#else
typedef const XMMATRIX& CXMMATRIX;
#endif

#if (defined(_M_IX86) || defined(_M_AMD64) || defined(_M_ARM)) && defined(_XM_NO_INTRINSICS_)
struct XMMATRIX
#else
__declspec(align(16)) struct XMMATRIX
#endif
{
#ifdef _XM_NO_INTRINSICS_
    union
    {
        XMVECTOR r[4];
        struct
        {
            float _11, _12, _13, _14;
            float _21, _22, _23, _24;
            float _31, _32, _33, _34;
            float _41, _42, _43, _44;
        };
        float m[4][4];
    };
#else
    XMVECTOR r[4];
#endif

    XMMATRIX() {}
    XMMATRIX(FXMVECTOR R0, FXMVECTOR R1, FXMVECTOR R2, GXMVECTOR R3)
    { r[0] = R0; r[1] = R1; r[2] = R2; r[3] = R3; }

    XMMATRIX(
             float m00, float m01, float m02, float m03,
             float m10, float m11, float m12, float m13,
             float m20, float m21, float m22, float m23,
             float m30, float m31, float m32, float m33);

    explicit XMMATRIX(_In_reads_(16) const float *pArray);

#ifdef _XM_NO_INTRINSICS_
    float       operator() (size_t Row, size_t Column) const
    { return m[Row][Column]; }
    float&      operator() (size_t Row, size_t Column) 
    { return m[Row][Column]; }
#endif

    XMMATRIX&   operator= (const XMMATRIX& M) 
    { r[0] = M.r[0]; r[1] = M.r[1]; r[2] = M.r[2]; r[3] = M.r[3]; return *this; }

    XMMATRIX    operator+ () const 
    { return *this; }
    XMMATRIX    operator- () const;

    XMMATRIX&   operator+= (CXMMATRIX M);
    XMMATRIX&   operator-= (CXMMATRIX M);
    XMMATRIX&   operator*= (CXMMATRIX M);
    XMMATRIX&   operator*= (float S);
    XMMATRIX&   operator/= (float S);

    XMMATRIX    operator+ (CXMMATRIX M) const;
    XMMATRIX    operator- (CXMMATRIX M) const;
    XMMATRIX    operator* (CXMMATRIX M) const;
    XMMATRIX    operator* (float S) const;
    XMMATRIX    operator/ (float S) const;

    friend XMMATRIX operator* (float S, CXMMATRIX M);
};

マトリクスにもレジスタ渡し用のtypedefのCXMMATRIXが定義されています。不思議なのは、NEONとxbox360は__m128のインスタンス4つまでレジスタ渡しできるはずなのですが、ここではNEON用の宣言が参照渡しになっています。記述漏れか意図したものかは良くわかりません。XMMATRIX自体は4要素のXMVECTORで宣言されています。コンストラクタがいくつか用意されていますので、初期化はそれほど難しくないと思われますが、各要素個別のアクセスは、_XM_NO_INTRINSICS_を定義して、SIMD演算を無効にしない限りできません。

XMMATRIXの3.03における変更点

3.03以前では、XMMATRIXは共用体で宣言されていて、もともとは以下の様だったようです。

union
{
        XMVECTOR r[4];
        struct
        {
            FLOAT _11, _12, _13, _14;
            FLOAT _21, _22, _23, _24;
            FLOAT _31, _32, _33, _34;
            FLOAT _41, _42, _43, _44;
        };
        FLOAT m[4][4];
};

今回の3.03における変更で、これらの共用体宣言がなくなりました。SSEに関して言えば、アドレスのalignmentは16Byteで良いはずなので、このオブジェクト自身が16Byte alignを守れば、この共用体宣言が性能を低下させることは無いはずです。ARM-V8のドキュメントを見る限り、NEON側もalignment指定子は128bitまでなので、この共用体宣言で何かが損なわれることはなさそうです。キャッシュラインを考慮すれば、オブジェクト自身に関しては、32Byte/64Byte alignが望ましいケースもあるかもしれませんが、API側がそこまで考慮する必要も無いと思います。あとは、このオブジェクトがレジスタ上に確保されていた場合、個々の要素へのアクセスを連続して行うと、partial register stall等で性能低下を招くことが考えられます。個々の要素へのアクセスを、このオブジェクトで行うのは、極力避けるようにという意味を込めた削除かもしれません。

XMMATRIXの個々の要素へのアクセス

XMMATRIXのメンバーであるXMVECTORは__m128なので、直接各要素にアクセスが出来ません。現在考えられる方法として、

  • XMVECTOR32Fの共用体メンバーを介す
  • XMVectorGetZ()などの、アクセス関数を使う

があります。
XMVECTOR32Fを介す方法はメモリを介してアクセスするということになりそうです。XMVectorGetZ()は下記のように宣言されています。

#define XM_PERMUTE_PS( v, c ) _mm_shuffle_ps( v, v, c )

// Return the Z component in an FPU register. 
inline float XMVectorGetZ(FXMVECTOR V)
{
#if defined(_XM_NO_INTRINSICS_)
    return V.vector4_f32[2];
#elif defined(_XM_ARM_NEON_INTRINSICS_)
    return vgetq_lane_f32(V, 2);
#elif defined(_XM_SSE_INTRINSICS_)
    XMVECTOR vTemp = XM_PERMUTE_PS(V,_MM_SHUFFLE(2,2,2,2));
    return _mm_cvtss_f32(vTemp);
#else // _XM_VMX128_INTRINSICS_
#endif // _XM_VMX128_INTRINSICS_
}

NEON版は手元で試す環境がないので割愛します。SSE版はシャッフルして抽出するように書いてあります。これらはintrinsic functionなので、コンパイラが場合に応じて最適化するはずです。 実際にこの両者のアクセスを行うコードを書いて、win32上で試してみました。しかし通常のfloatオブジェクトもXMMレジスタを使って演算していたので、アクセス関数を使った場合も、共用体メンバーを使った場合も、shufpsで各要素を取り出して、そのまま演算に利用されていました。共用体メンバーがshufpsで抽出されるのは少し予想外でした。コンパイル時に、/arch:IA32を明示的に指定すれば、アクセス関数を用いた場合は、shufpsで指定要素を抽出しメモリを介してfldされ、共用体メンバーを用いれば、オブジェクト全体をメモリにストアし、該当要素をfldで取得していました。いまさら/arch:IA32を指定するケースは稀だと考えるなら、各要素に対するアクセスは、どちらを使っても同じような結果になりそうです。

ベクトルとマトリクスの積

DirectXMathには当然ですが、ベクトルとマトリクスの積を計算する関数が用意されています。D3DXVec4Transformと異なり、結果を戻り値で受け取ります。

XMVECTOR XMVector4Transform(XMVECTOR V,XMMATRIX M);

演算のイメージですが、ベクトルをrowに取るように考えるのが、DirectXの場合は一番自然に見えます。このように考えると、XMVector4Transform()の引数の順序が、そのまま演算の順序となり、XMMATRIXに格納される4つのXMVECTORメンバーは各々のrowを表し、メモリ格納イメージもrow-orientedのイメージとなります。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中