DirectX11 With Windows SDK完整目录html
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。数组
尽管打包规则并不复杂,可是稍不留意就可能会致使由于打包规则的不理解而产生的数据错位问题。布局
下面会使用大量的例子来进行描述,并对有争议的部分使用图形调试器来反汇编着色器代码加以验证。调试
若C++结构体和HLSL常量缓冲区以下:code
// cpp struct S1 { XMFLOAT3 p1; XMFLOAT3 p2; }; // HLSL cbuffer C1 { float4 v1; float4 v2; }
则最终C1
两个向量接收到的数据以下:
(p1.x, p1.y, p1.z, p2.x)
(p2.y, p2.z, empty, empty)
htm
// cpp struct S1 { XMFLOAT3 p1; XMFLOAT3 p2; }; // HLSL cbuffer C1 { float3 v1; float4 v2; }
v1
将被单独打包成一个4D向量,确保常量缓冲区的内存按128位对齐。对象
C1
的内存布局为:blog
(v1.x, v1.y, v1.z, empty)
(v2.x, v2.y, v2.z, v2.w)
内存
这时用S1
结构体的数据再传输给C1
,结果以下:get
(p1.x, p1.y, p1.z, p2.x)
(p2.y, p2.z, empty, empty)
// HLSL cbuffer C1 { float2 v1; float4 v2; float2 v3; }
v2
没法拆分来填充v1
的空位,而是单独起一行向量,这样C1
的内存布局为:
(v1.x, v1.y, empty, empty)
(v2.x, v2.y, v2.z, v2.w)
(v3.x, v3.y, empty, empty)
// HLSL cbuffer C1 { float v1; float2 v2; float v3; float2 v4; float v5; }
C1
的内存布局为:
(v1.x, v2.x, v2.y, v3.x)
(v4.x, v4.y, v5.x, empty)
打包顺序是从最上面的变量开始往下的。
// HLSL cbuffer C1 { float2 v1; float v2; float3 v3; }
C1
的内存布局为:
(v1.x, v1.y, v2.x, empty)
(v3.x, v3.y, v3.z, empty)
经过几个例子来进行观察
// HLSL struct S1 { float2 p1; float3 p2; float p3; }; cbuffer C1 { float v1; S1 v2; float3 v3; }
C1
的内存布局为:
(v1.x, empty, empty, empty)
(v2.p1.x, v2.p1.y, empty, empty)
(v2.p2.x, v2.p2.y, v2.p2.z, v2.p3.x)
(v3.x, v3.y, v3.z, empty)
// HLSL struct S1 { float4 p1; float p2; }; cbuffer C1 { S1 v1; float2 v2; }
C1
的内存布局为:
(v1.p1.x, v1.p1.y, v1.p1.z, v1.p1.w)
(v1.p2.x, v2.x, v2.y, empty)
因此,结构体常量前面的全部常量都会被打包成4D向量,但结构体常量的最后一个成员可能会和后续的常量打包成4D向量。
数组中的每个元素都会独自打包,但对于最后一个元素来讲若是后续的变量不是数组、结构体且还有空缺,则能够进行打包操做。
// HLSL cbuffer C1 { float v1[4]; }
C1
的内存布局为:
(v1[0].x, empty, empty, empty)
(v1[1].x, empty, empty, empty)
(v1[2].x, empty, empty, empty)
(v1[3].x, empty, empty, empty)
能够看到,一个本应该是16字节的数组变量实际上变成了64字节的4个4D向量,形成内存的大量浪费。若是真的要使用这种数组,下面的声明方式经过强制转换,能够保证没有空间浪费(C++不容许这么作):
// HLSL cbuffer C1 { float4 v1; } static float packArray[4] = (float[4])v1;
// HLSL cbuffer C1 { float2 v1[4]; float2 v2; }
C1
的内存布局实际上为:
(v1[0].x, v1[0].y, empty, empty)
(v1[1].x, v1[1].y, empty, empty)
(v1[2].x, v1[2].y, empty, empty)
(v1[3].x, v1[3].y, v2.x, v2.y)
// HLSL struct S1 { float p1; int p2; }; cbuffer C1 { S1 v1[4]; float v2; float3 v3; }
C1
的内存布局实际上为:
(v1[0].p1, v1[0].p2, empty, empty)
(v1[1].p1, v1[1].p2, empty, empty)
(v1[2].p1, v1[2].p2, empty, empty)
(v1[3].p1, v1[3].p2, v2.x, empty)
(v3.x, v3.y, v3.z, empty)
// HLSL struct S1 { float p1; int p2; }; cbuffer C1 { float v1[2]; S1 v2; }
C1
的内存布局为:
(v1[0].x, empty, empty, empty)
(v1[1].x, empty, empty, empty)
(v2.p1, v2.p2, empty, empty)
首先确保着色器、常量缓冲区都已经绑定到渲染管线,若是该常量缓冲区被绑定到像素着色阶段,就应该在图形调试器中对像素着色器代码进行调试。
开启反汇编后,找到以前所放的常量缓冲区:
在这些样例中已经确保了常量缓冲区前面的全部值都已经打包好(16字节对齐)。
这里v1
的偏移值为2416
,而后能够看到结构体对象v2
内的p1
偏移值为2432
,说明v1
单独被打包成16字节向量。而后p1
没法和p2
打包,因此p1
单独打包成16字节。
而后p2
和p3
被打包,由于v3
的偏移值为2464
,p2
的偏移值为2448
。
因此内存布局以下:
(v1.x, empty, empty, empty)
(v2.p1.x, v2.p1.y, empty, empty)
(v2.p2.x, v2.p2.y, v2.p2.z, v2.p3.x)
(v3.x, v3.y, v3.z, empty)
v1
的偏移值为2416
,p1
构成单独的4D向量,p2
会和后续的v2
打包成新的4D向量,而不是单独打包。
因此内存布局以下:
(v1.p1.x, v1.p1.y, v1.p1.z, v1.p1.w)
(v1.p2.x, v2.x, v2.y, empty)
若是数组的每一个元素都单独打包的话,理论上这个数组所占字节数为64
,但这里数组的最后一个float2
和下面的float2
打包成一个4D向量。
因此内存布局以下:
(v1[0].x, v1[0].y, empty, empty)
(v1[1].x, v1[1].y, empty, empty)
(v1[2].x, v1[2].y, empty, empty)
(v1[3].x, v1[3].y, v2.x, v2.y)
能够看到结构体数组的每一个结构体元素都被单独打包成1个4D向量,但数组最后一个元素跟v2
打包到一块儿。
内存布局以下:
(v1[0].p1, v1[0].p2, empty, empty)
(v1[1].p1, v1[1].p2, empty, empty)
(v1[2].p1, v1[2].p2, empty, empty)
(v1[3].p1, v1[3].p2, v2.x, empty)
(v3.x, v3.y, v3.z, empty)
由于数组的后面是结构体,而结构体要求前面的全部变量都要先打包好,因此数组的2个元素分别单独打包。
内存布局以下:
(v1[0].x, empty, empty, empty)
(v1[1].x, empty, empty, empty)
(v2.p1, v2.p2, empty, empty)
在一次进行图形调试的时候,发现本来设置平行光和点光灯的数目为1,到了图形调试器却变成了点光灯和聚光灯的数目为1
C++代码以下:
struct CBNeverChange { DirectionalLight dirLight[10]; // 已按16字节对齐 PointLight pointLight[10]; // 已按16字节对齐 SpotLight spotLight[10]; // 已按16字节对齐 int numDirLight; int numPointLight; int numSpotLight; int pad; }; // ... mCBNeverChange.numDirLight = 1; mCBNeverChange.numPointLight = 1; mCBNeverChange.numSpotLight = 0;
HLSL代码以下:
cbuffer CBNeverChange : register(b3) { DirectionalLight gDirLight[10]; PointLight gPointLight[10]; SpotLight gSpotLight[10]; int gNumDirLight; int gNumPointLight; int gNumSpotLight; }
在图形调试器查看C++提供的字节数据,能够看到最后四个32位的传入是没有问题的
通过一番折腾,翻到像素着色器的反编译,发现里面有常量缓冲区数据偏移信息:
仔细比对的话能够发现从gNumDirLight
开始的字节偏移量出现了不是我想要的结果,本应该是2400
的值,结果倒是2396
,致使本来赋给gNumDirLight
和gNumPointLight
为1的值,却赋给了gNumPointLight
和gNumSpotLight
。这也是我为何要写出这篇文章的缘由。
首先从新总结以前的打包规则:
1. C++中的结构体数据是以字节流的形式传输给HLSL的;
2. HLSL常量缓冲区中的向量不容许拆分;
3. HLSL常量缓冲区中多个相邻的变量如有空缺则优先打包进同一个4D向量中;
4. HLSL常量缓冲区中,结构体常量前面的全部常量都会被打包成4D向量,内部也进行打包操做,但结构体的最后一个成员可能会和后续的常量打包成4D向量;
5. 数组中的每个元素都会独自打包,但对于最后一个元素来讲若是后续的变量不是数组、结构体且还有空缺,则能够进行打包操做。
因此避免出现潜在问题的办法以下:
1. 若要使用数组,数组的类型最好能按16字节对齐
2. 结构体的总大小也须要按16字节对齐。
DirectX11 With Windows SDK完整目录
欢迎加入QQ群: 727623616 能够一块儿探讨DX11,以及有什么问题也能够在这里汇报。