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

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

时间: 2024-08-26 21:49:45

ZFXEngine开发笔记-SSE版本的ZFXVector的相关文章

ZFXEngine开发笔记之3D模型格式支持(1)

作者:i_dovelemon 来源:CSDN 日期:2014 / 9 / 17 主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation (文中以红色中字标示的文字,是整个文章的注意项,请读者留心) 引言 在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景.当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式.每一个建模软件都有自己的文件格式,不

WCF开发笔记 高版本.Net的坑

高版本.Net的坑 开发环境 .Net 4.5.1Visual Studio 2015RCWindows 8.1SQL Server 2005 sp1 运行环境 .Net 4.0 enWindows Server 2003SQL Server 2005 sp1 现象 WCF客户端,开发环境测试正常.发布到运行环境后,提示Type 'System.Threading.Tasks.Task`1[XMRBI.ERoad.Contracts.ServiceResponse]' cannot be ser

ZFXEngine开发笔记之Bump Mapping(2)

作者:i_dovelemon 日期: 2014 / 9 / 13 来源 : CSDN 主题 :Bump Mapping, Tangent Space, Normal Map, Height Map 引言 在上篇文章中,我讲述了如何根据高度图来创建法线图.并且承诺在后面会讲述3D几何学中的重要工具Tangent Space的相关知识.今天,就在这里,向大家讲述如何接下来的工作部分. Tangent Space 我们知道,在前篇文章中,讲述的法线图中的法线是在纹理图的空间中也就是Tangent Sp

ZFXEngine开发笔记之Omni Light

作者:i_dovelemon 日期:2014 / 9 / 5 来源:CSDN博客 主题:Per-pixel lighting, Omni light, early-z, Multi-pass, Assembly Shader 引言 在第一人称射击游戏中,经常需要点光源来完成光照计算.所以,这篇文章中将会讲述如何创建这个点光源,并且如何设计让引擎可以支持多光源渲染. Omni light介绍 所谓的Omni light,就是指点光源.不过在这里,我们进行点光源的光照计算的时候,没有用到顶点的法线来

ZFXEngine开发笔记- GPU硬件架构

作者:i_dovelemon 日期:2014 / 8 / 31 来源: CSDN博客 文章: GPU硬件架构 引言 在3D图形学中,可编程渲染管道的出现,无疑是一种创举.在下面的文章中,会向大家简要的介绍现如今的可编程渲染管道中最重要的Vertex Shader和Pixel Shader的硬件架构以及如何使用汇编语言来编写Shader. Vertex Shader 在硬件上,Vertex Shader中所有的运算都在一个名为vertex arithmetic logic unit(ALU)中进行

ZFXEngine开发笔记之Bump Mapping(1)

作者:i_dovelemon 日期:2014 / 9 / 7 来源:CSDN博客 主题:Bump Mapping, Tangent Space, Normal Map, Height Map 引言 我们知道,在真实世界中,任何物体的表面都不是非常光滑的,这种粗糙的表面效果,在光源的照射下会有明暗的感觉.那么,如何在3D图形中模拟这种效果了?是通过建模的方法,为模型的表面建造出粗糙的表面?这种方法很难做出真实的粗糙感出来,毕竟表面的粗糙程度是很细微的效果,很难用建模的方式建模出来.所以,只能用其他

ZFXEngine开发笔记之Shadow Volume

作者:i_dovelemon 来源:CSDN 日期:2014 / 10 / 20 主题: Shadow Volume 引言 游戏中,往往有很多的光影效果.想要营造出很好的光影效果,物体在光源照射下的阴影就必不可少.本节内容就向大家讲述如何构建阴影. Shadow Volume 构建阴影的方法有很多,常用的两种方法是Shadow Volume和Shadow mapping.本片博文将向大家讲述使用Shadow Volume来构建阴影的方法. Shadow Volume的算法是由Frank Crow

Android开发笔记(一百零三)地图与定位SDK

集成地图SDK 国内常用的地图SDK就是百度和高德了,二者的用法大同小异,可按照官网上的开发指南一步步来.下面是我在集成地图SDK时遇到的问题说明: 1.点击基本地图功能选项,不能打开地图,弹出"key验证出错!请在AndroidManifest.xml文件中检查key设置的"的红色字提示.查看日志提示"galaxy lib host missing meta-data,make sure you know the right way to integrate galaxy&

【Kinect开发笔记之(一)】初识Kinect

一.Kinect简介 Kinect是微软在2010年6月14日对XBOX360体感周边外设正式发布的名字.它是一种3D体感摄影机(开发代号"Project Natal"),同时它导入了即时动态捕捉.影像辨识.麦克风输入.语音辨识.社群互动等功能. 二.Kinect分类 Kinect for Xbox 360:该版本设计之初就是为了Xbox 360定制的,并未考虑其他的平台.从微软授权角度而言,它无法用于商业开发. Kinect for Windows : 固件上做了升级,支持"