SSE介绍
在学习3D游戏编程大师技巧的时候,就了解到,可是使用一种称之为“单指令,多数据(SIMD)”的技术来编写3D数学库。通过这样的方法,可以将我们经常使用的诸如向量计算,矩阵变换等操作加快很多倍。这次,在学习3D引擎开发的时候,也用到了这个技术。SIMD是一种技术的名称,而并不是具体的工具。实现这种技术,不同的CPU厂商推出了不同的技术,像MMX, 3DNow!, SSE, SSE2, SSE3...。由于我的计算机上使用的是Intel的处理器,它支持MMX,SSE,SSE2,所以在这里我使用SSE的指令来进行。如果你使用的是AMD处理器,并且支持!3DNow!的话,不用担心,虽然他们是不同的厂商,但是他们的指令使用的是同一个标准,所以依然能够使用这里的代码。
SSE支持
为了能够使用SSE指令,来加快向量计算的速度。我们需要知道我们的CPU和操作系统是否支持SSE指令代码。对于这样的查询,我们可以在汇编代码中使用如下的代码来获取CPU特性表示符:
mov eax , 1
CPUID
mov flag, edx
这样,flag中就保存了我们CPU的特性值了。然后只要解析这个特性值就能够判断我们的计算机是否支持上面提到的那些指令集 了。但是,在这里,我并不使用这种方法。如果读者希望使用这样的方法的话,可以自行上网上进行有关资料的查询。
在这里,我使用MSDN网站上提供的一个示例代码来完成这个判断。
想要使用的同学只要简单的下载,编译运行,就能够知道你的计算机是否支持SSE了。
我在此基础上,进行了修改,以下是ZFXEngine中用来判断用户计算机是否支持SSE的代码:
<span style="font-family:Microsoft YaHei;">#include"ZFX3D.h" #include<iostream> #include<stdio.h> #include<fstream> #include"cpuid.h" using namespace ZFXEngine ; using namespace std ; /** * Global variant */ bool g_bSSE = false ; //Check if the operate system support sse void expand(int avail, int mask, ofstream* pOut) { char buffer[64]; if (mask & _CPU_FEATURE_MMX) { sprintf(buffer,"\t%s\t_CPU_FEATURE_MMX\n", avail & _CPU_FEATURE_MMX ? "yes" : "no"); (*pOut)<<buffer; } if (mask & _CPU_FEATURE_SSE) { sprintf(buffer,"\t%s\t_CPU_FEATURE_SSE\n", avail & _CPU_FEATURE_SSE ? "yes" : "no"); (*pOut)<<buffer; } if (mask & _CPU_FEATURE_SSE2) { sprintf(buffer,"\t%s\t_CPU_FEATURE_SSE2\n", avail & _CPU_FEATURE_SSE2 ? "yes" : "no"); (*pOut)<<buffer; } if (mask & _CPU_FEATURE_3DNOW) { sprintf(buffer,"\t%s\t_CPU_FEATURE_3DNOW\n", avail & _CPU_FEATURE_3DNOW ? "yes" : "no"); (*pOut)<<buffer; } } //--------------------------------------------------------------------------------------------- bool ZFX3DInitCPU(void) { _p_info info; _cpuid(&info); ofstream out ; out.open("ZFXEngine_CPU_Info.log"); char buffer[64]; sprintf(buffer,"v_name:\t\t%s\n", info.v_name); out<<buffer; sprintf(buffer,"model:\t\t%s\n", info.model_name); out<<buffer; sprintf(buffer,"family:\t\t%d\n", info.family); out<<buffer; sprintf(buffer,"model:\t\t%d\n", info.model); out<<buffer; sprintf(buffer,"stepping:\t%d\n", info.stepping); out<<buffer; sprintf(buffer,"feature:\t%08x\n", info.feature); out<<buffer; expand(info.feature, info.checks, &out); sprintf(buffer,"os_support:\t%08x\n", info.os_support); out<<buffer; expand(info.os_support, info.checks,&out); sprintf(buffer,"checks:\t\t%08x\n", info.checks); out<<buffer; if((info.feature & _CPU_FEATURE_SSE) &&(info.os_support & _CPU_FEATURE_SSE)) g_bSSE = true ; else g_bSSE = false ; out.close(); return g_bSSE ; }// end for ZFX3DInitCPU</span>
用户只需要调用ZFX3DInitCPU()就可以知道是否支持SSE了,并且这个函数会将用户的CPU信息打印出来,如下所示:
<span style="font-family:Microsoft YaHei;">v_name: GenuineIntel model: INTEL Pentium-III family: 6 model: 10 stepping: 9 feature: 00000007 yes _CPU_FEATURE_MMX yes _CPU_FEATURE_SSE yes _CPU_FEATURE_SSE2 no _CPU_FEATURE_3DNOW os_support: 00000007 yes _CPU_FEATURE_MMX yes _CPU_FEATURE_SSE yes _CPU_FEATURE_SSE2 no _CPU_FEATURE_3DNOW checks: 0000000f </span>
ZFXVector实现
如下是ZFXVector的头文件:
<span style="font-family:Microsoft YaHei;">/** * Define ZFXVector */ class _declspec(dllexport) ZFXVector { public: float x, y, z, w ; public: ZFXVector(void){ x = 0 , y = 0 , z = 0, w = 1.0f ;} ZFXVector(float _x, float _y, float _z) :x(_x), y(_y), z(_z), w(1.0) { } ~ZFXVector(){} public: inline void set(float _x, float _y, float _z, float _w = 1.0f); inline float getLength(void); inline float getSqrtLength(void) const ; inline void negate(void); inline void normalize(void); inline float angleWith(ZFXVector& v); inline void difference(const ZFXVector& u, const ZFXVector&v); void operator +=(const ZFXVector &v); void operator -=(const ZFXVector &v); void operator *=(float f); void operator /=(float f); float operator *(const ZFXVector &v) const ; ZFXVector operator *(float f) const ; ZFXVector operator * (const ZFXMatrix &m) const ; ZFXVector operator + (const ZFXVector &v) const ; ZFXVector operator - (const ZFXVector &v) const ; inline void cross(const ZFXVector &u, const ZFXVector& v); }; // end for ZFXVector</span>
如下是该类的实现文件:
<span style="font-family:Microsoft YaHei;">#include"ZFX3D.h" #include<cmath> using namespace ZFXEngine ; extern bool g_bSSE ; float _fabs(float f) { if(f < 0.0f) return -f ; return f ; }// end for _fabs inline void ZFXVector::set(float _x, float _y, float _z, float _w) { x = _x ; y = _y ; z = _z ; w = _w ; }// end for set void ZFXVector::operator+=(const ZFXVector& v) { x += v.x ; y += v.y ; z += v.z ; }// end for += ZFXVector ZFXVector::operator+(const ZFXVector& v) const { return ZFXVector(x + v.x, y + v.y, z+ v.z); }// end for + void ZFXVector::operator -=(const ZFXVector& v) { x -= v.x ; y -= v.y ; z -= v.z ; }// end for -= ZFXVector ZFXVector::operator -(const ZFXVector& v) const { return ZFXVector(x - v.x, y - v.y, z - v.z); }// end for - void ZFXVector::operator *=(float f) { x *= f ; y *= f ; z *= f ; }// end for *= void ZFXVector::operator /= (float f) { x /= f ; y /= f ; z /= f ; }// end for /= ZFXVector ZFXVector::operator *(float f) const { return ZFXVector(x * f, y * f, z * f) ; }// end for * float ZFXVector::operator*(const ZFXVector& v) const { return (x * v.x + y * v.y + z * v.z); }// end for * inline float ZFXVector::getSqrtLength(void) const { return (x * x + y * y + z * z) ; }// end for getSqrLength inline void ZFXVector::negate(void) { x = -x ; y = -y ; z = -z ; }// end for negate inline void ZFXVector::difference(const ZFXVector&v1, const ZFXVector&v2) { x = v2.x - v1.x ; y = v2.y - v1.y ; z = v2.z - v1.z ; w = 1.0f ; }// end for difference inline float ZFXVector::angleWith(ZFXVector& v) { return (float)acos(((*this) * v )/(this->getLength() * v.getLength())); }// end for angleWith inline float ZFXVector::getLength(void) { float f = 0.0f ; if(!g_bSSE) { f = (float)sqrt(x*x + y*y + z*z); } else { float *pf = &f ; w = 0.0f; _asm { mov ecx , pf ; point to the result mov esi , this ; copy the pointer of this to esi movups xmm0, [esi] ; copy the this vector to xmm0 mulps xmm0, xmm0 ; multiply all the component movaps xmm1, xmm0 ; copy result to xmm1 shufps xmm1, xmm1, 4Eh; shuffle : f1, f0, f3, f2 addps xmm0, xmm1 ; movaps xmm1, xmm0 ; copy the xmm0 to xmm1 shufps xmm1, xmm1, 11h; addps xmm0, xmm1 sqrtss xmm0, xmm0 ; sqrt the first element movss [ecx], xmm0 ; copy the first element to the result }// end for _asm w = 1.0f ; } return f ; }// end for getLength inline void ZFXVector::normalize(void) { if(x == 0 && y == 0 && z == 0) return ; if(!g_bSSE) { float f = (float)sqrt(x*x + y*y + z*z); x /= f; y /= f; z /= f; } else { w = 0.0f ; _asm { mov esi , this ; copy the pointer of this to esi movups xmm0, [esi] ; copy the this vector to xmm0 movaps xmm2, xmm0 mulps xmm0, xmm0 ; multiply all the component movaps xmm1, xmm0 ; copy result to xmm1 shufps xmm1, xmm1, 4Eh; shuffle : f1, f0, f3, f2 addps xmm0, xmm1 ; movaps xmm1, xmm0 ; copy the xmm0 to xmm1 shufps xmm1, xmm1, 11h; addps xmm0, xmm1 rsqrtps xmm0, xmm0 ; mulps xmm2, xmm0 ; multiply the inverse of squre root movups [esi], xmm2 }// end for _asm w = 1.0f; }// end if...else... }// end for normalize inline void ZFXVector::cross(const ZFXVector& v, const ZFXVector& u) { if(!g_bSSE) { x = v.y * u.z - v.z * u.y ; y = v.z * u.x - v.x * u.z ; z = v.x * u.y - v.y * u.x ; w = 1.0f; } else { _asm { mov esi , v mov edi , u movups xmm0, [esi] movups xmm1, [edi] movaps xmm2, xmm0 movaps xmm3, xmm1 shufps xmm0, xmm0, 0xC9 shufps xmm1, xmm1, 0xD2 mulps xmm0, xmm1 shufps xmm2, xmm2, 0xD2 shufps xmm3, xmm3, 0xC9 mulps xmm2, xmm3 subps xmm0, xmm2 mov esi, this movups [esi], xmm0 }// end for _asm w = 1.0f ; }// end if...else... }// end for cross ZFXVector ZFXVector::operator*(const ZFXMatrix& m) const { ZFXVector vcResult ; if(!g_bSSE) { vcResult.x = x* m._11 + y * m._21 + z * m._31 + w * m._41 ; vcResult.y = x* m._12 + y * m._22 + z * m._32 + w * m._42 ; vcResult.z = x* m._13 + y * m._23 + z * m._33 + w * m._43 ; vcResult.w = x* m._14 + y * m._24 + z * m._34 + w * m._44 ; } else { float *ptrRet = (float*)&vcResult ; ZFXVector s ; s.set(m._11, m._12, m._13, m._14); ZFXVector t ; t.set(m._21, m._22, m._23, m._24); ZFXVector u ; u.set(m._31, m._32, m._33, m._34); ZFXVector v ; v.set(m._41, m._42, m._43, m._44); float* ps = (float*)&s ; float* pt = (float*)&t ; float* pu = (float*)&u ; float* pv = (float*)&v ; __asm { mov esi, this movups xmm0, [esi] movaps xmm1, xmm0 movaps xmm2, xmm0 movaps xmm3, xmm0 shufps xmm0, xmm2, 0x00 shufps xmm1, xmm2, 0x55 shufps xmm2, xmm2, 0xAA shufps xmm3, xmm3, 0xFF mov edx, ps movups xmm4, [edx] mov edx, pt movups xmm5, [edx] mov edx, pu movups xmm6, [edx] mov edx, pv movups xmm7, [edx] mulps xmm0, xmm4 mulps xmm1, xmm5 mulps xmm2, xmm6 mulps xmm3, xmm7 addps xmm0, xmm1 addps xmm0, xmm2 addps xmm0, xmm3 mov edx, ptrRet ; movups [edx], xmm0 ; }// end for _asm }// end if...else... //homo it if(vcResult.w != 1.0f && vcResult.w != 0.0f) { vcResult.x /= vcResult.w ; vcResult.y /= vcResult.w ; vcResult.z /= vcResult.w ; vcResult.w = 1.0f ; } return vcResult ; }// end for *</span>
在这里需要注意几点:
1.并不是所有的操作都要用SSE来完成。我们要知道,将数据从一般的CPU寄存器移动到专用的SSE寄存器和将SSE计算的结果移到CPU寄存器中的开销是比较大的。如果你的运算的开销没有移动的开销来的大,比如只是简单的进行一次加法,那么就不适合使用SSE来完成。你应该使用SSE完成那些稍微复杂的,需要优化的操作函数。
2.关于SSE的指令操作,需要掌握哪些指令是用于对齐数据的,哪些数据是用于package数据的,同时要掌握shuf指令的含义。
3.进行函数编写的时候,你可以完全按照数学上的定义来编写向量,但是由于计算机的精度有限,我们往往只是使用近似的算法来编写如normalize和getlength这样的函数,使用近似的思想能够提高函数的运算速度,并且在精度允许的范围内,存在一定误差,这种误差对于图形来说基本上可以忽略。
好了,以上是今天的笔记。