我正在使用DirectXMath(或XNAMath)库(在Windows SDK的DirectXMath.h头文件中定义),因为它看起来非常高效,并提供物理和渲染所需的一切。但是我发现它非常冗长(使用XMStoreFloatX和XMLoadFloatX到处都很累)。
我试图让它更容易操作,并提出了在分配操作符/转换操作符中隐藏存储/加载的想法。由于这两个都是成员函数,我想出了这个代码作为例子:
struct Vector2F : public DirectX::XMFLOAT2 {
inline Vector2F() : DirectX::XMFLOAT2() {};
inline Vector2F(float x, float y) : DirectX::XMFLOAT2(x, y) {};
inline Vector2F(float const * pArray) : DirectX::XMFLOAT2(pArray) {};
inline Vector2F(DirectX::XMVECTOR vector) {
DirectX::XMStoreFloat2(this, vector);
}
inline Vector2F& __vectorcall operator= (DirectX::XMVECTOR vector) {
DirectX::XMStoreFloat2(this, vector);
return *this;
}
inline __vectorcall operator DirectX::XMVECTOR() {
return DirectX::XMLoadFloat2(this);
}
};
正如您所看到的,它复制了XMFLOAT2的公共接口,并添加了构造函数,赋值运算符和XMVECTOR的转换,这是DirectXMath用于计算的SIMD类型。我打算为DirectXMath提供的每个存储结构执行此操作。
性能是数学图书馆的一个非常重要的因素,因此我的问题是:这种继承的性能影响是什么?与正常使用库相比,是否有任何其他代码生成(当然假设完全优化)?
直觉上我会说生成的代码应该与我在没有这些便利运算符的情况下使用详细变体时完全相同,因为我基本上只是重命名结构和函数。但也许有些方面我不知道?
P.S。我有点担心赋值运算符的返回类型,因为它添加了额外的代码。省略引用返回以优化它是否是一个好主意?
答案 0 :(得分:2)
如果您发现DirectXMath对您的口味有点过于冗长,请查看SimpleMath中的DirectX Tool Kit。特别是Vector2
类:
struct Vector2 : public XMFLOAT2
{
Vector2() : XMFLOAT2(0.f, 0.f) {}
explicit Vector2(float x) : XMFLOAT2( x, x ) {}
Vector2(float _x, float _y) : XMFLOAT2(_x, _y) {}
explicit Vector2(_In_reads_(2) const float *pArray) : XMFLOAT2(pArray) {}
Vector2(FXMVECTOR V) { XMStoreFloat2( this, V ); }
Vector2(const XMFLOAT2& V) { this->x = V.x; this->y = V.y; }
explicit Vector2(const XMVECTORF32& F) { this->x = F.f[0]; this->y = F.f[1]; }
operator XMVECTOR() const { return XMLoadFloat2( this ); }
// Comparison operators
bool operator == ( const Vector2& V ) const;
bool operator != ( const Vector2& V ) const;
// Assignment operators
Vector2& operator= (const Vector2& V) { x = V.x; y = V.y; return *this; }
Vector2& operator= (const XMFLOAT2& V) { x = V.x; y = V.y; return *this; }
Vector2& operator= (const XMVECTORF32& F) { x = F.f[0]; y = F.f[1]; return *this; }
Vector2& operator+= (const Vector2& V);
Vector2& operator-= (const Vector2& V);
Vector2& operator*= (const Vector2& V);
Vector2& operator*= (float S);
Vector2& operator/= (float S);
// Unary operators
Vector2 operator+ () const { return *this; }
Vector2 operator- () const { return Vector2(-x, -y); }
// Vector operations
bool InBounds( const Vector2& Bounds ) const;
float Length() const;
float LengthSquared() const;
float Dot( const Vector2& V ) const;
void Cross( const Vector2& V, Vector2& result ) const;
Vector2 Cross( const Vector2& V ) const;
void Normalize();
void Normalize( Vector2& result ) const;
void Clamp( const Vector2& vmin, const Vector2& vmax );
void Clamp( const Vector2& vmin, const Vector2& vmax, Vector2& result ) const;
// Static functions
static float Distance( const Vector2& v1, const Vector2& v2 );
static float DistanceSquared( const Vector2& v1, const Vector2& v2 );
static void Min( const Vector2& v1, const Vector2& v2, Vector2& result );
static Vector2 Min( const Vector2& v1, const Vector2& v2 );
static void Max( const Vector2& v1, const Vector2& v2, Vector2& result );
static Vector2 Max( const Vector2& v1, const Vector2& v2 );
static void Lerp( const Vector2& v1, const Vector2& v2, float t, Vector2& result );
static Vector2 Lerp( const Vector2& v1, const Vector2& v2, float t );
static void SmoothStep( const Vector2& v1, const Vector2& v2, float t, Vector2& result );
static Vector2 SmoothStep( const Vector2& v1, const Vector2& v2, float t );
static void Barycentric( const Vector2& v1, const Vector2& v2, const Vector2& v3, float f, float g, Vector2& result );
static Vector2 Barycentric( const Vector2& v1, const Vector2& v2, const Vector2& v3, float f, float g );
static void CatmullRom( const Vector2& v1, const Vector2& v2, const Vector2& v3, const Vector2& v4, float t, Vector2& result );
static Vector2 CatmullRom( const Vector2& v1, const Vector2& v2, const Vector2& v3, const Vector2& v4, float t );
static void Hermite( const Vector2& v1, const Vector2& t1, const Vector2& v2, const Vector2& t2, float t, Vector2& result );
static Vector2 Hermite( const Vector2& v1, const Vector2& t1, const Vector2& v2, const Vector2& t2, float t );
static void Reflect( const Vector2& ivec, const Vector2& nvec, Vector2& result );
static Vector2 Reflect( const Vector2& ivec, const Vector2& nvec );
static void Refract( const Vector2& ivec, const Vector2& nvec, float refractionIndex, Vector2& result );
static Vector2 Refract( const Vector2& ivec, const Vector2& nvec, float refractionIndex );
static void Transform( const Vector2& v, const Quaternion& quat, Vector2& result );
static Vector2 Transform( const Vector2& v, const Quaternion& quat );
static void Transform( const Vector2& v, const Matrix& m, Vector2& result );
static Vector2 Transform( const Vector2& v, const Matrix& m );
static void Transform( _In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector2* resultArray );
static void Transform( const Vector2& v, const Matrix& m, Vector4& result );
static void Transform( _In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector4* resultArray );
static void TransformNormal( const Vector2& v, const Matrix& m, Vector2& result );
static Vector2 TransformNormal( const Vector2& v, const Matrix& m );
static void TransformNormal( _In_reads_(count) const Vector2* varray, size_t count, const Matrix& m, _Out_writes_(count) Vector2* resultArray );
// Constants
static const Vector2 Zero;
static const Vector2 One;
static const Vector2 UnitX;
static const Vector2 UnitY;
};
// Binary operators
Vector2 operator+ (const Vector2& V1, const Vector2& V2);
Vector2 operator- (const Vector2& V1, const Vector2& V2);
Vector2 operator* (const Vector2& V1, const Vector2& V2);
Vector2 operator* (const Vector2& V, float S);
Vector2 operator/ (const Vector2& V1, const Vector2& V2);
Vector2 operator* (float S, const Vector2& V);
DirectXMath首先如此冗长的主要原因是程序员在“溢出内存”时非常清楚,因为这会对SIMD代码的性能产生负面影响。当我从XNAMath迁移到DirectXMath时,我考虑过添加一些类似于我用于“SimpleMath”的隐式转换,但我想确保任何这样的“C ++魔术”都是选择性的,并且对于性能敏感而言绝对不会感到惊讶开发商。 SimpleMath也有点像训练轮子,可以更容易地移植不能识别对齐的现有代码,并随着时间的推移将其变形为更加SIMD友好的东西。
SimpleMath(和你的包装器)的真正性能问题是每个函数实现必须做一个显式的Load&存储在相当少量的SIMD周围。理想情况下,在优化的代码中,它们都会被合并掉,但在调试代码中它们总是存在。对于SIMD的任何实际性能优势,您希望在每个Load& amp;和/或它们之间进行长时间的寄存器内SIMD操作。商店对。
另一个含义是传递像Vector2
或Vector2F
这样的包装器的参数永远不会特别有效。 XMVECTOR
是__m128
的typedef而非结构的全部原因,以及FXMVECTOR
,GXMVECTOR
,HXMVECTOR
和{{1}的存在是尝试优化所有可能的调用约定场景,并在最好的情况下获取in-register传递行为(如果事情不内联)。见MSDN。对于CXMVECTOR
,您可以做的最好的事情是始终如一地传递Vector2
以最小化临时和堆栈副本。