DirectX11 HLSL打包(packing)格式和“pad”变量的必要性

来源:互联网 发布:抢魅族手机用什么软件 编辑:程序博客网 时间:2024/05/17 04:18

HLSL打包(packing)格式和“pad”变量的必要性

1. C++复制缓存

定义了HLSL结构体后,我们就可以像以下代码一样初始化常量缓冲:

cbuffer cbPerFrame{    DirectionalLight gDirLight;    PointLight gPointLight;    SpotLight gSpotLight;    float3 gEyePosW;};

在应用程序中初始化对应的灯光结构体,我们想在一次调用中将灯光实例设置到effect变量中,而不是单独地设置每个成员变量。可以用以下函数将一个结构实例设置到一个effect变量实例上:

ID3DX11EffectVariable::SetRawValue(void *pData,    UINT Offset,UINT Count);// 调用示例:DirectionalLight  mDirLight;mfxDirLight->SetRawValue(&mDirLight, 0, sizeof(mDirLight));

但是,由于这个函数只是简单地复制原始字节,因此如果你不仔细的话会导致很难找到错误,这里所说的要仔细对待的东西是指C++的打包(packing)规则与HLSL不同。

2. HLSL打包

HLSL打包规则一:禁止拆分

在HLSL中,当元素打包到要给4D矢量中时会发生结构填充(padding),根据HLSL的规则,一个元素无法拆分到两个4D矢量中。考虑下面的例子:

//HLSLstruct S{    float3 Pos;    float3 Dir;};

如果将这个数据打包在4D矢量中,你可能以为会是这样:

vector1:(Pos.x,Pos.y,Pos.z,Dir.x)vector2:(Dir.y,Dir.z,empty,empty)

但是,将元素dir打包在两个不同的4D矢量中违反了HLSL的规则,是不被允许的——一个元素不可以跨过一个4D矢量的范围,它只能这样被打包:

vector 1: (Pos.x, Pos.y, Pos.z,empty) vector 2: (Dir.x, Dir.y, Dir.z,empty) 

现在假设我们对应的C++结构如下所示:

//  C++struct S{    XMFLOAT3 Pos;    XMFLOAT3 Dir;};

如果我们不注意打包规则,只是盲目地调用并复制数据,我们就会得到错误的结果,这是不被HLSL允许的:

vector1:(Pos.x,Pos.y,Pos.z,Dir.x)vector2:(Dir.y,Dir.z,empty,empty)

3. 使用“pad”变量解决打包问题

HLSL打包规则二:允许不拆分插空

所以我们必须基于HLSL打包规则定义C++结构,才能让元素正确地复制到HLSL结构中。我们要使用一个“pad”变量解决上述问题。让我们看几个HLSL打包的例子。

第一个例子。有下列一个结构:

// HLSLstruct S{    float3 v;    float s;    float2 p;    float3 q;} ;

这个结构会有空隙,数据会被打包到3个4D矢量中:

vector1:(v.x,v.y,v.z,s)vector2:(p.x,p.y,empty,empty)vector3:(q.x,q.y,q.z,empty)

我们可以将标量放在第1个矢量的第4个分量中。但是,我们无法在第2个矢量中匹配q的全部分量,所以需要为它分配单独的矢量。(这时候就需要定义pack变量补上空位)

最后一个例子,结构:

struct S{    float2  u;    float2  v;    float  a0;    float  a1;    float  a2;} ;

会这样被打包:

vector1:(u.x,u.y,v.x,v.y)vector2:(a0,a1,a2,empty)

HLSL打包规则三:数组的处理方法例外,有所不同。SDK文档中这样说明:“数组中的每个元素都存储在一个4个分量的矢量中。”

所以,若你有一个float2的数组:

float2 TexOffsets[8];

你可能会认为如上面的例子所说,两个float2会包装在一个float4中。但是,数组是例外,上面的代码等同于:

float4 TexOffsets[8];

所以,你需要在C++中定义一个8个XMFLOAT4的数组,而不是8个XMFLOAT2的数组,这样才能正常运行。我们实际上只需要一个float2数组,所以每个元素浪费了两个float的空间。SDK文档指出你可以使用转换和额外的地址计算指令提高内存的使用效率:

float4  array[4];static float2  aggressivePackArray[8]  = (float2[8])array;

4. 打包格式规则总结

  • 禁止拆分
  • 允许不拆分插空
  • 数组的处理方法例外,有所不同。SDK文档中这样说明:“数组中的每个元素都存储在一个4个分量的矢量中。”
0 0