ZFXEngine开发笔记-SSE版本的ZFXVector

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这样的函数,使用近似的思想能够提高函数的运算速度,并且在精度允许的范围内,存在一定误差,这种误差对于图形来说基本上可以忽略。


             好了,以上是今天的笔记。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值