Clayman's Graphics Corner

DirectX,Shader & Game Engine Programming

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

A Simple Wrapper of DirectX Math

关于DirectX Math
DirectX Math最初叫做XNA Math,是一个跨平台的C++数学库,全SIMD指令优化,目前的版本是3.03,支持x86,x64和arm平台,用于代替DX 9/10中的数学库。

为什么使用DirectX Math
如果你有足够的时间,精力和知识,并且坚定的认为自己写的数学库才是最好,那么可以忽略此文:)。根据gamasutra技术控做的细介,基本上很难再比DXM的实现好了,并且它是完全跨平台的,只包含.h和.inl文件,就算是OpenGL程序也可以用,最后,DXM非常全面,实现了大部分常见的3D计算,甚至还包括了一个简单的碰撞检测库。

 正确使用DXM
        首先,用 #define _XM_NO_INTRINSICS_ 宏可以取消SIMD加速,生成普通指令,除非希望兼容非常,非常老的CPU,否则不应该使用该宏。虽然DXM对SIMD指令做了封装,你还是需要了解一点相关知识(请仔细阅读DXM文档)。DXM定义了大量常见的数据类型,但只为XMVECTOR 和XMMATRIX定义了各种数学操作。DXM所有的计算都基于XMVECTOR 和XMMATRIX。对x86平台,XMVECTOR的定义如下:
typedef __m128 XMVECTOR;

         而XMMATRIX则是一组(4个) XMVECTOR。由于__m128在内存中需要严格的16 byte对齐,因此并不建议直接在程序中使用这两种类型保存数据,比如

Class Camera
{
      XMVECTOR position;
      XMMATRIX viewMatrix;
}

         上面的代码基本上不会成功运行,32位下new并不保证16byte对齐,而且由于类成员布局的关系,其中的XMVECTOR也不一定是16byte对齐,访问以上数据,程序可能直接崩溃。因此,DXM定义了大量用来储存数据的类型,比如XMFLOAT4,XMFLOAT3,XMFLOAT4X4等:

Class Camera
{
    XMFLOAT3 position;
    XMFLOAT4X4 viewMatrix;
}

         这些类型大部分只是float数组而已,因此不用考虑对齐问题,在需要计算时,再把他们转换为于XMVECTOR 或者XMMATRIX,比如:

XMFLOAT3 position;
XMVECTOR myVector = XMLoadFloat3(position)   //load to mmx register
//perform calculate with SIMD …..
//
XMStoreFloat4(&position,myVector);           //save to memory

         显然,这种load/store模式确实很麻烦,简单计算两个向量的和也需要编写4,5行代码,于是DX开发组的程序写了一个名为SimpleMath的简单wrapper包含在DirectX Tool Kit中,为普通类型添加计算功能。然而,SimpleMath只是简单的包装了load,store而已:

struct Vector2 : public XMFLOAT2
{
    //......other member function
    float Dot( const Vector2& V ) const
    float LengthSquared() const;
    //…..other member function
}


inline float Vector2::Dot( const Vector2& V ) const
{
    using namespace DirectX;
    XMVECTOR v1 = XMLoadFloat2( this );
    XMVECTOR v2 = XMLoadFloat2( &V );
    XMVECTOR X = XMVector2Dot( v1, v2 );
    return XMVectorGetX( X );
}
 

         这样的实现对于Float4来说也许有意义,但对于Float2来说就太笨重了,把2个Float2复制到mmx寄存器相加以后再保存到普通内存位置也许比直接用非SIMD指令相加还慢!此外对于SIMD计算来说,应该尽量减少load和unload,尽量把数据保存在寄存器中,比如在计算前把数据load为XMVECTOR,执行完一系列计算,最后再保存到普通位置,而不是每执行一次计算,就load/store一次

case 1:
Load to XMVECTOR
perform calc 1
perform calc 1
..
Perform calc n
Store to Float4

 
case 2
Load to XMVECTOR
Perform calc 1
Store to Float4
Load to XMVECTOR
Perform calc 2
Store to Float4
…

两种不同写法,如果编译器没有特别优化的情况下,效率相差会很明显。因此,好的数学库还要有正确的使用方法,才会得到最好的性能。

 

FlameMath
         FlameMath保留了SimpleMath的接口,但改进了一部分性能问题,替换了部分函数的实现,对于简单类型和轻量级计算,直接用普通指令,对于matrix等复杂计算,再使用SIMD。

inline float Float2::Dot( const Float2& V ) const
{
   return (x * V.x + y * V.y);
}

       但是,FlameMath仍然不能避免不同计算间反复load/store的问题。因此,最佳方案应该是使用Float3,Float4等类型保存数据,对于轻量级简单计算,直接用FloatX的成员函数进行,对于需要连续进行的复杂计算,则应该把数据load为XMVECTOR等类型,直接用DXM函数计算,最后再store到FloatX中。FloatMath中的FloatX对应DXM和SimpleMath中的标量数据类型,Vector4和Matrix4x4则相当于XMVECTOR 和XMMATRIX。所有DXM中的函数以SimdMath类静态函数的方式提供。

Simpe Usage:
Float2 v1;
Float2 v2;
Float2 result = v1 + v2;
//done

 
Advance usage
Float2 f1;
Float2 f2;
Vector4 v1 = SimdMath.LoadFloat2(f1);
Vector4 v2 = SimdMath.LoadFloat2(f2);
Vector4 v3 = SimdMath. Vector2Normalize (v1);
Vector4 v4 = SimdMath. Vector2Normalize (v2);
Vector4 v5 = SimdMath. Vector2AngleBetweenNormals(v3,v4);
Float result;
SimdMath. StoreFloat(&result,v5);
//done

 

ps:FlameMath所有代码都未经测试,使用时只需要包含FlameMath.h即可:)
https://files.cnblogs.com/clayman/FlameMath.rar

posted on 2013-05-01 14:56  clayman  阅读(2671)  评论(1编辑  收藏  举报