kleiner C++ Wrapper für SSE
-
habe mal zum spass einen kleinen wrapper für xmm intrinsics geschrieben, der ihre nutzung etwas erleichtern sollte. auch das alignment problem auf dem freestore ist dabei gelöst. die nutzung als lokale variable auf dem stack sollte ohne einschränkung möglich sein.
EDIT: gegenstandslos
die new und delete operatoren müssen auch in allen klassen, die diese klasse als member benutzen, überladen werden, daher hab ich das ganze in ein makro verpackt. es ist auch (noch) nicht die gesamte funktionalität der intrinsics implementiert, falls interesse besteht, werde ich es noch ein bisschen ausbauen.
anregungen und konstruktive kritik (auch stilfragen) sind erwünscht.
das ganze scheint soweit mit msvc 7.1 und dem intel c++ compiler zu funktionieren. mit dev-cpp (g++ 3.3.1) funktioniert es bisher nicht, da dieser offenbar die ausrichtung bei temporären objekten nicht beachtet (und __alignof__ funktioniert dort auch nicht richtig, daher der parameter im makro); kann sein, dass das mit einer aktuellen version anders ist.
-
hi,
sieht sehr interessant aus! Keine Frage, aber glaubst du da fehlen nicht paar sachen?
DotProduct und CrossProduct, sowie längenberechnungen und distanzberechnungen? Normalisierungen? Ohne das ist dein vector einfach nur ein kleines bisschen +-*/ und sonst nichts großartiges für den GameDev bereich.
Das solltest du noch ändern, aber ansonsten: Interessantes projekt
Was jedoch auch noch ein mangel ist, das diese klasse kein Template ist und auf float ausgerichtet ist. Bei der Physik würd sich die genauigkeit von double und der Speed von SSE sicherlich bezahlt machen.
-
der zweck ist nicht, eine vektorklasse, wie sie z.b. für 3d gebraucht wird, zu implementieren, sondern eine abstraktion für sse-basierte intrisics bereitzustellen. da es dabei nat. auch immer um geschwindigkeit geht, werden nur solche operationen bereitgestellt, die sich direkt in einzelne assembler instruktionen umsetzen lassen. sonst könnte es leicht passiern, das in klassen, die darauf basieren, eine operation eine langsame kombination nicht primitiver basisoperationen benutzt, nur weil es evtl. an dieser stelle simpler erscheint. aus deisem grunde handelt es sich auch nicht um eine template klasse - sse kennt eben nur diesen einen datentyp. was noch fehlt, sind operationen, die nur auf dem ersten float wert arbeiten, also _ss instruktionen, um in berechnungen den zeitaufwendigen wechsel zur fpu zu vermeiden. ein skalarprodukt zu implementieren wäre leicht möglich, würde aber von dem eines 3d vektors abweichen. der könnte dann z.b. so implementiert werden (entwurf):
EDIT: gegenstandslos
-
gibt es schon ein update?
-
Sehr interessant;
da könnte ich bestimmt mal was von gebrauchen..-Kannst Du den "Zeitaufwendigen wechsel zur FPU" mal näher erklären?
-Du benutzt für den Zugriff auf __m128 eine union(Ok, mache ich auch
),
was spricht dagegen direkt auf die Elemente des Typs __m128 zuzugreifen, der auch als union definiert ist ( Bsp.: foo.m128i_i32[0] = 10).@unreg. SSE unterstützt nur single precision values; double precision gibts erst ab SSE2 und das ist dann wieder ein anderer Typ und andere Befehle.
Gruß
Michael
-
jetzt gehts auch mit gcc (nur das shuffle template compiliert dort nicht aus mir unverständlichen gründen)
der direkte zugriff über .f[ .. ] etc. ist wenigstens bei gcc nicht möglich, da dort __m128 ein typedef auf den builtin typ ist. allerdings hat die klasse ja jetzt eigene [] operatoren.
new/delete sind jetzt global überladen - das ist bequemer. damit es keine probleme gibt, sollte die datei in allen übersetzungseinheiten includiert werden - da alles andere in einem eigenen namespace liegt, sollte es keine konflikte geben. falls man allerdings klassen für eine bibliothek schreibt und dann nur diese klassen beim user bekannt machen will, müsste man sie trotzdem nochmal klassenweise (für alle klassen, die direkt oder indirekt einen xmm typ enthalten) überladen.#ifndef SSEINTRIN_H_INCLUDED #define SSEINTRIN_H_INCLUDED #include <cstdlib> #include <new> #include <cassert> #include <xmmintrin.h> #if defined _MSC_VER || defined __ICL #pragma warning( disable : 4290 ) // C++ exception specification ignored except to indicate a function is not __declspec(nothrow) #define inline __forceinline #elif defined __GNUC__ #define inline __attribute__ ((always_inline)) inline #endif // implement operator new/delete that returns memory suitable aligned // for SIMD vector types inline void* operator new(std::size_t size) throw (std::bad_alloc) { char* allocated = static_cast< char* > ( std::malloc(size + sizeof( char* ) + 0xf ) ); if( allocated == NULL ) throw std::bad_alloc(); char* aligned = reinterpret_cast< char* > ( ( size_t( allocated ) + sizeof( char* ) + 0xf ) & ~0xf ); *reinterpret_cast< char** > ( aligned - sizeof( char* ) ) = allocated; return aligned; } inline void* operator new(std::size_t size, const std::nothrow_t&) throw() { char* allocated = static_cast< char* > ( std::malloc(size + sizeof( char* ) + 0xf ) ); if( allocated == NULL ) return NULL; char* aligned = reinterpret_cast< char* > ( ( size_t( allocated ) + sizeof( char* ) + 0xf ) & ~0xf ); *reinterpret_cast< char** > ( aligned - sizeof( char* ) ) = allocated; return aligned; } inline void* operator new[](std::size_t size) throw (std::bad_alloc) { return operator new( size ); } inline void* operator new[](std::size_t size, const std::nothrow_t&) throw() { return operator new( size, std::nothrow ); } inline void operator delete(void* ptr) throw() { if( ptr != NULL ) std::free( *reinterpret_cast< char** > ( static_cast< char* >( ptr ) - sizeof( char* ) ) ); } inline void operator delete(void* ptr, const std::nothrow_t&) throw() { operator delete( ptr ); } inline void operator delete[](void* ptr) throw() { operator delete( ptr ); } inline void operator delete[](void* ptr, const std::nothrow_t&) throw() { operator delete( ptr ); } namespace SSE { union Xmm4f_uint_init { unsigned int u[ 4 ]; __m128 v; }; union Xmm4f_float_init { float f[ 4 ]; __m128 v; }; namespace { const Xmm4f_uint_init not_mask = { ~0u, ~0u, ~0u, ~0u }; const Xmm4f_uint_init sign_mask = { ( ~0u >> 1 ) + 1, ( ~0u >> 1 ) + 1, ( ~0u >> 1 ) + 1, ( ~0u >> 1 ) + 1 }; } // namespace struct Xmm4f { public: union { float f[4]; __m128 v; }; public: Xmm4f() { }; Xmm4f(float x): v( _mm_set1_ps( x ) ) { } Xmm4f(float a, float b, float c, float d) : v( _mm_set_ps( d, c, b, a ) ) { } Xmm4f(__m128 r): v( r ) { } float& operator[](std::size_t i) { assert( i < 4 ); return f[ i ]; } const float& operator[](std::size_t i)const { assert( i < 4 ); return f[ i ]; } operator __m128()const { return v; } // returns value of first element, faster than [] if vector isn't used otherwise operator float()const { float f; _mm_store_ss( &f, v ); return f; } Xmm4f operator +()const { return v; } Xmm4f operator -()const { return _mm_xor_ps( v, sign_mask.v ); } Xmm4f operator ~()const { return _mm_xor_ps( v, not_mask.v ); } Xmm4f& operator +=(float x) { v = _mm_add_ps( v, _mm_set1_ps( x ) ); return *this; } Xmm4f& operator -=(float x) { v = _mm_sub_ps( v, _mm_set1_ps( x ) ); return *this; } Xmm4f& operator *=(float x) { v = _mm_mul_ps( v, _mm_set1_ps( x ) ); return *this; } Xmm4f& operator /=(float x) { v = _mm_div_ps( v, _mm_set1_ps( x ) ); return *this; } Xmm4f& operator +=(const Xmm4f& r) { v = _mm_add_ps( v, r.v ); return *this; } Xmm4f& operator -=(const Xmm4f& r) { v = _mm_sub_ps( v, r.v ); return *this; } Xmm4f& operator *=(const Xmm4f& r) { v = _mm_mul_ps( v, r.v ); return *this; } Xmm4f& operator /=(const Xmm4f& r) { v = _mm_div_ps( v, r.v ); return *this; } Xmm4f& operator ^=(const Xmm4f& r) { v = _mm_xor_ps( v, r.v ); return *this; } Xmm4f& operator |=(const Xmm4f& r) { v = _mm_or_ps( v, r.v ); return *this; } Xmm4f& operator &=(const Xmm4f& r) { v = _mm_and_ps( v, r.v ); return *this; } // returns bitmask with each bit set if corresponding pairs of elements are unequal: // int(l[0]!=r[0])|int(l[1]!=r[1])<<1| // int(l[2]!=r[2])<<2|int(l[3]!=r[3])<<3 int operator!=(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmpneq_ps( v, r.v ) ); } // WARNING: in general ( a == b ) == !( a != b ) is NOT true !!! // the following is always true: ( a == b ) + ( a != b ) == 0xf // use !( a != b ) to quickly check if all pairs are equal int operator==(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmpeq_ps( v, r.v ) ); } // all following operators function in the same manner int operator<(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmplt_ps( v, r.v ) ); } int operator<=(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmple_ps( v, r.v ) ); } int operator>(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmpgt_ps( v, r.v ) ); } int operator>=(const Xmm4f& r)const { return _mm_movemask_ps( _mm_cmpge_ps( v, r.v ) ); } }; inline Xmm4f operator+(const Xmm4f& l, const Xmm4f& r) { return _mm_add_ps( l, r ); } inline Xmm4f operator-(const Xmm4f& l, const Xmm4f& r) { return _mm_sub_ps( l, r ); } inline Xmm4f operator*(const Xmm4f& l, const Xmm4f& r) { return _mm_mul_ps( l, r ); } inline Xmm4f operator/(const Xmm4f& l, const Xmm4f& r) { return _mm_div_ps( l, r ); } inline Xmm4f operator^(const Xmm4f& l, const Xmm4f& r) { return _mm_xor_ps( l, r ); } inline Xmm4f operator|(const Xmm4f& l, const Xmm4f& r) { return _mm_or_ps( l, r ); } inline Xmm4f operator&(const Xmm4f& l, const Xmm4f& r) { return _mm_and_ps( l, r ); } inline Xmm4f andnot(const Xmm4f& l, const Xmm4f& r) { return _mm_andnot_ps( l, r ); } inline Xmm4f abs(const Xmm4f& r) { return _mm_andnot_ps( r, SSE::sign_mask.v ); } inline Xmm4f sqrt(const Xmm4f& r) { return _mm_sqrt_ps( r ); } // return ( 1/r[0],1/r[1],1/r[2],1/r[3] ) inline Xmm4f rcp(const Xmm4f& r) { return _mm_rcp_ps( r ); } // return ( 1/sqrt(r[0]),1/sqrt(r[1]),1/sqrt(r[0]),1/sqrt(r[1]) ) inline Xmm4f rsqrt(const Xmm4f& r) { return _mm_rsqrt_ps( r ); } // return vector of minimum of each pair of corresponding elements inline Xmm4f min(const Xmm4f& l, const Xmm4f& r) { return _mm_min_ps( l, r ); } inline Xmm4f max(const Xmm4f& l, const Xmm4f& r) { return _mm_max_ps( l, r ); } // return ( l[x], l[y], r[z], r[w] ) template< int x, int y, int z, int w > inline Xmm4f shuffle(const Xmm4f& l, const Xmm4f& r) { return _mm_shuffle_ps( l.v, r.v, _MM_SHUFFLE( w, z, y, x ) ); } template< > inline Xmm4f shuffle< 0, 1, 0, 1 >(const Xmm4f& l, const Xmm4f& r) { return _mm_movelh_ps( l.v, r.v ); } template< > inline Xmm4f shuffle< 2, 3, 2, 3 >(const Xmm4f& l, const Xmm4f& r) { return _mm_movehl_ps( r.v, l.v ); } template< int x, int y, int z, int w > inline Xmm4f shuffle(const Xmm4f& l) { return _mm_shuffle_ps( l.v, l.v, _MM_SHUFFLE( w, z, y, x ) ); } template< > inline Xmm4f shuffle< 0, 1, 0, 1 >(const Xmm4f& l) { return _mm_movelh_ps( l.v, l.v ); } template< > inline Xmm4f shuffle< 2, 3, 2, 3 >(const Xmm4f& l) { return _mm_movehl_ps( l.v, l.v ); } template< > inline Xmm4f shuffle< 0, 1, 2, 3 >(const Xmm4f& l) { return l; } // return ( l[0], r[0], l[1], r[1] ) inline Xmm4f unpacklo(const Xmm4f& l, const Xmm4f& r) { return _mm_unpacklo_ps( l, r ); } // return ( l[2], r[2], l[3], r[3] ) inline Xmm4f unpackhi(const Xmm4f& l, const Xmm4f& r) { return _mm_unpackhi_ps( l, r ); } // return ( l[0], l[0], l[1], l[1] ) inline Xmm4f unpacklo(const Xmm4f& l) { return _mm_unpacklo_ps( l, l ); } // return ( l[2], l[2], l[3], l[3] ) inline Xmm4f unpackhi(const Xmm4f& l) { return _mm_unpackhi_ps( l, l ); } // the following funtions only change the first element [0] // all other elements are passed through from the left argument inline Xmm4f addss(const Xmm4f& l, const Xmm4f& r) { return _mm_add_ss( l, r ); } inline Xmm4f subss(const Xmm4f& l, const Xmm4f& r) { return _mm_sub_ss( l, r ); } inline Xmm4f mulss(const Xmm4f& l, const Xmm4f& r) { return _mm_mul_ss( l, r ); } inline Xmm4f divss(const Xmm4f& l, const Xmm4f& r) { return _mm_div_ss( l, r ); } inline Xmm4f sqrtss(const Xmm4f& r) { return _mm_sqrt_ss( r ); } inline Xmm4f rcpss(const Xmm4f& r) { return _mm_rcp_ss( r ); } inline Xmm4f rsqrtss(const Xmm4f& r) { return _mm_rsqrt_ss( r ); } inline Xmm4f minss(const Xmm4f& l, const Xmm4f& r) { return _mm_min_ss( l, r ); } inline Xmm4f maxss(const Xmm4f& l, const Xmm4f& r) { return _mm_max_ss( l, r ); } inline bool cmpeqss(const Xmm4f& l, const Xmm4f& r) { return _mm_comieq_ss( l, r ) != 0; } // l[0] == r[0] inline bool cmpneqss(const Xmm4f& l, const Xmm4f& r) { return _mm_comineq_ss( l, r ) != 0; }// l[0] != r[0] inline bool cmpltss(const Xmm4f& l, const Xmm4f& r) { return _mm_comilt_ss( l, r ) != 0; } // l[0] < r[0] inline bool cmpless(const Xmm4f& l, const Xmm4f& r) { return _mm_comile_ss( l, r ) != 0; } // l[0] <= r[0] inline bool cmpgess(const Xmm4f& l, const Xmm4f& r) { return _mm_comige_ss( l, r ) != 0; } // l[0] >= r[0] inline bool cmpgtss(const Xmm4f& l, const Xmm4f& r) { return _mm_comigt_ss( l, r ) != 0; } // l[0] > r[0] // return ( r[0], l[1], l[2], l[3] ) inline Xmm4f movess(const Xmm4f& l, const Xmm4f& r) { return _mm_move_ss( l, r ); } } // namespace SSE #undef inline #endif // #ifndef SSEINTRIN_H_INCLUDED
-
@Kyon: der wechsel ist aufwendig, weil er immer nur über den speicher erfolgen kann. die fpu kann nicht direkt aus einem xmm register heraus laden. das ist nat. ungünstig im falle von temporären objekten, die nach der optimierung sonst nur als registervariablen existieren würden - daher auch der float cast operator, dort wird wirklich nur das in den speicher transferiert, was gebraucht wird.
generell sollte eine rechnung immer an einem platz ausgeführt werden, entweder ganz über sse, oder ganz in der fpu. ein wechsel is so gut wie nie sinnvoll (wenn man nicht gerade mal transzendente funktionen braucht).
-
@camper
Deine New und Delete Operatoren hab' ich dann direkt schon eingebaut.Nach dem 1001ten Absturz, weil der MSCompiler mal wieder den __m128i Typ nicht ausgelegt hat, wird es etwas nervend...
Ich hab natürlich Deinen Namen dabei gesetzt...
Gruß
Michael
-
kleine änderung, jetzt funktioniert shuffle auch mit gcc. irgendwie funktioniert es dort nur, wenn ich auf v direkt zugreife, obwohl ja ein cast operator da ist; ich denke das ist ein compilerfehler, bei funktionen, die keine template funktionen sind, geht es ja schliesslich. leider krieg ich die syntax, um jede instanz des funktions-templates friend zu machen, nicht auf die reihe, also sind die daten jetzt public.
hier mal eine einigermassen funktionsfähige 3dklasse#ifndef VECTOR3D_H_INCLUDED #define VECTOR3D_H_INCLUDED #include <cstdlib> #include <cassert> #include "SSEIntrin.h" class Vector3d { public: SSE::Xmm4f v; Vector3d() { } Vector3d(const SSE::Xmm4f& r): v( r ) { } Vector3d(float x, float y, float z): v( x, y, z, 1.0f ) { } float& operator[](std::size_t i) { assert( i < 3 ); return v[ i ]; } const float& operator[](std::size_t i)const { assert( i < 3 ); return v[ i ]; } Vector3d operator +()const { return v; } Vector3d operator -()const { return -v; } Vector3d& operator*=(float k) { v *= k; return *this; } Vector3d& operator/=(float k) { v /= k; return *this; } float length()const { SSE::Xmm4f tmp1( v * v ); SSE::Xmm4f tmp2; tmp2 = SSE::shuffle< 2, 3, 2, 3 >( tmp1, tmp2 ); // resolves to movehl // [2] and [3] of tmp2 are actually undefined, which is why the default constructor // doesnt initialize anything, since it couldnt be optimized away tmp2 = addss( tmp2, tmp1 ); // now holds v[0]+v[2] tmp1 = SSE::shuffle< 1, 0, 0, 0 >( tmp1 ); tmp2 = addss( tmp2, tmp1 ); tmp2 = sqrtss( tmp2 ); return tmp2; } float sqrLength()const { SSE::Xmm4f tmp1( v * v ); SSE::Xmm4f tmp2; tmp2 = SSE::shuffle< 2, 3, 2, 3 >( tmp1, tmp2 ); tmp2 = addss( tmp2, tmp1 ); tmp1 = SSE::shuffle< 1, 0, 0, 0 >( tmp1 ); tmp2 = addss( tmp2, tmp1 ); return tmp2; } Vector3d& normalize() { SSE::Xmm4f tmp1( v * v ); SSE::Xmm4f tmp2; tmp2 = SSE::shuffle< 2, 3, 2, 3>( tmp1, tmp2 ); tmp2 = addss( tmp2, tmp1 ); tmp1 = SSE::shuffle< 1, 0, 0, 0>( tmp1 ); tmp2 = addss( tmp2, tmp1 ); tmp2 = rsqrtss( tmp2 ); tmp2 = SSE::shuffle< 0, 0, 0, 1>( tmp2 ); v *= tmp2; return *this; } }; inline Vector3d operator+(const Vector3d& l, const Vector3d& r) { return l.v + r.v; } inline Vector3d operator-(const Vector3d& l, const Vector3d& r) { return l.v - r.v; } inline float operator*(const Vector3d& l, const Vector3d& r) { SSE::Xmm4f tmp1( l.v * r.v ); SSE::Xmm4f tmp2; tmp2 = SSE::shuffle< 2, 3, 2, 3 >( tmp1, tmp2 ); tmp2 = addss( tmp2, tmp1 ); tmp1 = SSE::shuffle< 1, 0, 0, 0 >( tmp1 ); tmp2 = addss( tmp2, tmp1 ); return tmp2; } inline Vector3d vectorp(const Vector3d& l, const Vector3d& r) { SSE::Xmm4f tmp1( SSE::shuffle< 2, 0, 1, 3 >( l.v ) ); SSE::Xmm4f tmp2( SSE::shuffle< 1, 2, 0, 3 >( r.v ) ); SSE::Xmm4f tmp3( tmp1 * tmp2 ); tmp1 = SSE::shuffle< 2, 0, 1, 3 >( tmp1 ); tmp2 = SSE::shuffle< 1, 2, 0, 1 >( tmp2 ); tmp1 *= tmp2; tmp1 -= tmp3; return tmp1; } inline bool operator==(const Vector3d& l, const Vector3d& r) { return !( ( l.v != r.v ) & 7 ); // mask unused vector element } inline bool operator!=(const Vector3d& l, const Vector3d& r) { return ( l.v != r.v ) & 7; } #endif // #ifndef VECTOR3D_H_INCLUDED
auf die verwendung der 4. koordinate hab ich verzichtet, weil das zusätzlichen code erfordert. im zusammenhang mit matrixoperationen muss man dann davon ausgehen, dass diese koordinate 1 sein soll, unabhängig davon, was tatsächlich drin steht - das sollte auch dort code sparen.
*= rsqrt(.. ist übrigens deutlich schneller als /= sqrt(..
da rsqrt genauso schnell wie eine divison ist, allerdings ist es auch deutlich ungenauer (allerdings genauer als der workaround in dem anderen thread), man vergleiche einfach mal vec.normalize() mit vec /= vec.length().im übrigen würde mich interessieren, ob das alles hier bei jemandem einen messbaren vorteil bringt