发信人: nhyjq(人类·静修) 
整理人: nhyjq(2003-01-05 15:33:15), 站内信件
 | 
 
 
by: 方泓
 
 介绍
 DX8中的FVF(Flexible Vertex Format Flags)是用来描述在单个数据流里交错存储的顶点内容, 它是用一个整形数(DWORD)来表示,更多关于FVF的说明,可以在DX8 SDK文档[1]中找到。
 
 从FVF的值我们就可以知道此FVF所表示的一些基本属性。如果我们在编译时间就知道了这个值,那么我们甚至在编译时间就可以知道并利用这些属性,由此我们可以做一些有趣的应用。但是如何才能在编译时间就得到这些属性呢 ? 
 
 实现
 C++中的template机制就能实现上面所说的目标,这种称为C++ Meta Programming的方法是C++ template的一种有趣而且也有一定实用价值的应用,对此有兴趣的人可以读读Todd Veldhuizen的著名文章[2]。
 
 在这里我引进一个名为TFVFProperties template类(或结构),它的template参数就是FVF值,这个类非常的单纯,就是根据此FVF值在编译时间算出一些基本属性,并将这些基本属性信息保存在里面待用,这样我们在编译时就能得到指定FVF中所包含的属性信息,我们可以利用这些属性来做一些有趣的应用。
 
 这个类的实现不算很复杂,下面我就直接将实现列在下面: 
 
 template<DWORD t_dwFVF>
 struct	TFVFProperties
 {
 BOOST_STATIC_CONSTANT(DWORD, sc_dwFVF = t_dwFVF);
 private:
 BOOST_STATIC_CONSTANT(int, _sc_nDummy = 1);
 public:
 BOOST_STATIC_CONSTANT(bool, sc_bHasPosition = ((t_dwFVF & D3DFVF_POSITION_MASK) != 0));
 BOOST_STATIC_CONSTANT(bool, sc_bHasRHW = ((t_dwFVF & D3DFVF_POSITION_MASK) == D3DFVF_XYZRHW));
 BOOST_STATIC_CONSTANT(bool, sc_bHasWeights = ((t_dwFVF & D3DFVF_POSITION_MASK) >= D3DFVF_XYZB1));
 BOOST_STATIC_CONSTANT(bool, sc_bHasMatrixIndices = ((t_dwFVF & D3DFVF_LASTBETA_UBYTE4) != 0));
 BOOST_STATIC_CONSTANT(int, sc_nWeights = sc_bHasWeights
 	? (sc_bHasMatrixIndices
 		? (t_dwFVF - D3DFVF_XYZB1 & D3DFVF_POSITION_MASK) >> 1
 		: ((t_dwFVF - D3DFVF_XYZB1 & D3DFVF_POSITION_MASK) >> 1) + 1)
 	: _sc_nDummy);
 BOOST_STATIC_CONSTANT(bool, sc_bHasNormal = ((t_dwFVF & D3DFVF_NORMAL) != 0));
 BOOST_STATIC_CONSTANT(bool, sc_bHasPointSize = ((t_dwFVF & D3DFVF_PSIZE) != 0));
 BOOST_STATIC_CONSTANT(bool, sc_bHasDiffuse = ((t_dwFVF & D3DFVF_DIFFUSE) != 0));
 BOOST_STATIC_CONSTANT(bool, sc_bHasSpecular = ((t_dwFVF & D3DFVF_SPECULAR) != 0));
 BOOST_STATIC_CONSTANT(bool, sc_bHasTexCoords = ((t_dwFVF & D3DFVF_TEXCOUNT_MASK) != 0));
 BOOST_STATIC_CONSTANT(int, sc_nTexCoords = ((t_dwFVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT));
 
 // D3DFVF_TEXCOORDSIZEx
 #define _M_DIM_TEX(mCoordIndex)\
 	(0x1432 >> (((t_dwFVF >> ((mCoordIndex - 1) * 2 + 16)) & 0x03) * 4)) & 0x0F
 BOOST_STATIC_CONSTANT(int, sc_nDimTex1 = _M_DIM_TEX(1) ? _M_DIM_TEX(1) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex2 = _M_DIM_TEX(2) ? _M_DIM_TEX(2) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex3 = _M_DIM_TEX(3) ? _M_DIM_TEX(3) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex4 = _M_DIM_TEX(4) ? _M_DIM_TEX(4) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex5 = _M_DIM_TEX(5) ? _M_DIM_TEX(5) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex6 = _M_DIM_TEX(6) ? _M_DIM_TEX(6) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex7 = _M_DIM_TEX(7) ? _M_DIM_TEX(7) : _sc_nDummy);
 BOOST_STATIC_CONSTANT(int, sc_nDimTex8 = _M_DIM_TEX(8) ? _M_DIM_TEX(8) : _sc_nDummy);
 #undef _M_DIM_TEX
 
 private:
 BOOST_STATIC_CONSTANT(bool, _sc_bIsValid = sc_bHasPosition
 	&& (sc_bHasNormal || sc_bHasDiffuse || sc_bHasSpecular || sc_bHasTexCoords)
 	&& ! (sc_bHasRHW && (sc_bHasNormal || sc_bHasWeights)));
 
 M_CAssert(sc_nTexCoords <= 8);
 M_CAssert(_sc_bIsValid);
 };
 
 
 由此类的实现可以看出,象sc_bHasPosition这样的bool值属性的获取是非常简单的,只要将FVF值与D3DFVF_POSITION_MASK相与看是否为0就可以得到了。一些属性数值的取得较为复杂一些,如sc_nWeights, sc_nDimTex* 等,不过相信只要仔细看DX8SDK的文档[1]可以得到解答,在此我不再说明。
 
 应用
 下面我们来看TFVFProperties的一个简单应用:根据FVF自动生成Vertex shader的declaration token数组,我们可以用下面这个template类来做这项工作: 
 
 template<DWORD t_dwFVF, DWORD t_dwStreamNumber>
 struct	TFVFDeclGen
 {
 	BOOST_STATIC_CONSTANT(DWORD, e_StreamNumber = t_dwStreamNumber);
 public:
 	static const DWORD	sm_adwDecl[D3DVSDE_NORMAL2 + 2 + 1];
 };
 
 template<DWORD t_dwFVF, DWORD t_dwStreamNumber>
 const DWORD	TFVFDeclGen<t_dwFVF, t_dwStreamNumber>::sm_adwDecl[] =
 {
 D3DVSD_STREAM(e_StreamNumber),
 TFVFProperties<t_dwFVF>::sc_bHasPosition ? D3DVSD_REG(D3DVSDE_POSITION, D3DVSDT_FLOAT3) : D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasWeights
 	? D3DVSD_REG(D3DVSDE_BLENDWEIGHT, (TFVFProperties<t_dwFVF>::sc_nWeights == 1)
 		? D3DVSDT_FLOAT1
 		: (TFVFProperties<t_dwFVF>::sc_nWeights == 2)
 			? D3DVSDT_FLOAT2
 			: (TFVFProperties<t_dwFVF>::sc_nWeights == 3)
 				? D3DVSDT_FLOAT3
 				: (TFVFProperties<t_dwFVF>::sc_nWeights == 4)
 					? D3DVSDT_FLOAT4
 					: D3DVSDT_FLOAT4)
 	: D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasMatrixIndices
 	? D3DVSD_REG(D3DVSDE_BLENDINDICES, D3DVSDT_UBYTE4) : D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasNormal ? D3DVSD_REG(D3DVSDE_NORMAL, D3DVSDT_FLOAT3) : D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasPointSize ? D3DVSD_REG(D3DVSDE_PSIZE, D3DVSDT_FLOAT1) : D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasDiffuse ? D3DVSD_REG(D3DVSDE_DIFFUSE, D3DVSDT_D3DCOLOR) : D3DVSD_NOP(),
 TFVFProperties<t_dwFVF>::sc_bHasSpecular ? D3DVSD_REG(D3DVSDE_SPECULAR, D3DVSDT_D3DCOLOR) : D3DVSD_NOP(),
 
 #define _M_GenTexureDecl(mN, mN_1)\
 	TFVFProperties<t_dwFVF>::sc_nTexCoords >= ##mN\
 		? D3DVSD_REG(D3DVSDE_TEXCOORD##mN_1,\
 			(D3DVSDT_FLOAT1 + TFVFProperties<t_dwFVF>::sc_nDimTex##mN - 1))\
 		: D3DVSD_NOP()
 _M_GenTexureDecl(1, 0),
 _M_GenTexureDecl(2, 1),
 _M_GenTexureDecl(3, 2),
 _M_GenTexureDecl(4, 3),
 _M_GenTexureDecl(5, 4),
 _M_GenTexureDecl(6, 5),
 _M_GenTexureDecl(7, 6),
 _M_GenTexureDecl(8, 7),
 #undef _M_GenTexureDecl
 D3DVSD_NOP(),	// D3DVSDE_POSITION2
 D3DVSD_NOP(),	// D3DVSDE_NORMAL2
 D3DVSD_END()
 };
 
 
 代码很容易理解,在这我不加解说了。这样的话我们在编译时就能求出数组的值,省了一点点运行开销,这个方法我去年底曾经在cpp3d的论坛上简单说过,也有朋友认为这数组可能是运行时才算出的,所以我还写了一个最简单的试验程序在VC6 SP5下验证了一下: 
 
 {
 ...
 const DWORD dwFVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;
 printf("%X\n%X-%X-%X-%X\n%X-%X-%X-%X\n%X-%X-%X-%X\n%X-%X-%X-%X-%X\n%X\n"
 	, TFVFDeclGen<dwFVF, 0>::m_adwDecl[0], ...);
 ...
 }
 
 
 用IDA反汇编出来的代码可以清楚的看到生成的declaration 数组: 可见在这些值并非在运行时才计算出的。 
 
 ; Segment type: Pure data
 _rdata segment para public 'DATA' use32
 assume cs:_rdata
 ;org 4070DCh
 db 0 ; 
 db 0 ; 
 db 0 ; 
 db 0 ; 
 dword_4070E0 dd 20000000h ; DATA XREF: _main+76r
 dword_4070E4 dd 40020000h ; DATA XREF: _main+6Fr
 dword_4070E8 dd 0 ; DATA XREF: _main+68r
 dword_4070EC dd 0 ; DATA XREF: _main+62r
 dword_4070F0 dd 40020003h ; DATA XREF: _main+5Br
 dword_4070F4 dd 0 ; DATA XREF: _main+54r
 dword_4070F8 dd 0 ; DATA XREF: _main+4Er
 dword_4070FC dd 0 ; DATA XREF: _main+47r
 dword_407100 dd 40010007h ; DATA XREF: _main+40r
 dword_407104 dd 0 ; DATA XREF: _main+3Ar
 dword_407108 dd 0 ; DATA XREF: _main+33r
 dword_40710C dd 0 ; DATA XREF: _main+2Cr
 dword_407110 dd 0 ; DATA XREF: _main+26r
 dword_407114 dd 0 ; DATA XREF: _main+1Fr
 dword_407118 dd 0 ; DATA XREF: _main+18r
 dword_40711C dd 0 ; DATA XREF: _main+12r
 dword_407120 dd 0 ; DATA XREF: _main+Br
 dword_407124 dd 0 ; DATA XREF: _main+5r
 dword_407128 dd 0FFFFFFFFh ; DATA XREF: _mainr
 
 
 下面我们来看看如何使用这个类的代码示例: 
 
 class	CD3DVertexShader
 {
 ....
 
 public:
 #define M_FVF2Decl(mFVF)	meta::TType2Type<TFVFDeclGen<mFVF,0> >()
 	template<typename TWrapDecl>
 	inline bool	Create(TWrapDecl, const DWORD* pVSCode)
 	{
 		return Create(TWrapDecl::TOriginalType::sm_adwDecl, pVSCode);
 	}
 
 	inline bool	Create(const DWORD* pdwDecl, const DWORD* pVSCode)
 	{
 		DWORD	dwUsage = 0L;
 		if (CD3DRenderDevice::Instance()->IsSoftwareVertexProcessing())
 			dwUsage |= D3DUSAGE_SOFTWAREPROCESSING;
 
 		// Create the vertex shader
 		return SUCCEEDED(CD3DRenderDevice::Instance()->CreateVertexShader(...);
 	}
 ...
 };
 
 
 const DWORD	c_dwFVF0 = D3DFVF_XYZ | D3DFVF_DIFFUSE;
 const DWORD	c_dwFVF1 = D3DFVF_XYZ | D3DFVF_DIFFUSE;
 const DWORD	c_dwFVF2 = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1,
 const DWORD	c_dwFVF3 = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1;
 ...
 {
 ...
 	// Create the vertex shader
 	m_VS0.Create(M_FVF2Decl(c_dwFVF0), dwSimple0VertexShader);
 	m_VS1.Create(M_FVF2Decl(c_dwFVF1), dwSimple1VertexShader);
 	m_VS2.Create(M_FVF2Decl(c_dwFVF2), dwSimple2VertexShader);
 	m_VS3.Create(M_FVF2Decl(c_dwFVF3), dwSimple3VertexShader);
 ...
 }
 
 
 这里需要提醒的是,declaration是提供给shader使用的,用来表达输入数值的秩序,所以记得你的shader代码得到输入数据要和它一致。
 
 对比D3DX中的D3DXDeclaratorFromFVF()方法,这种方法在编译时生成数组,没什么运行开销。但是需要FVF是一个编译时知道的常量。而D3DXDeclaratorFromFVF()则是运行期间生成declaration数组的,有一点点运行开销,但是FVF不必是常量。对比实际中更常用的直接定义declaration数组,好处是比较方便,不需要定义,但不能用于一些特殊情况,而且有多个Stream时就用不上了,而直接定义则有很大的灵活性,所以在实用中可以几种方法一起使用。
 
 总结
 本文介绍了一个C++ template类TFVFProperties及其实现,并介绍它的一个具体应用,生成静态Vertex Shader的declaration数组,可以在某些情况下简化编程,但要注意在使用上有一些限制。
 下一篇我将介绍应用TFVFProperties的一个更为复杂的应用。
 
 注释
 [1] Microsoft Corp., Microsoft DirectX 8.1 SDK. Microsoft Visual C++ Documentation Homepage, Microsoft Corp., 2001.
 
 [2] Todd Veldhuizen. "Template Metaprograms.", C++ Report, 7, May, pp 36-43, 1995.
 
 
  ---- ∵我是人类(♂)㊣,天蝎座
 ∴我冷静、深沉     
 My OICQ is 726556     
 欢迎来游戏开发版逛逛,我是斑竹     | 
 
 
 |