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,以及有什麼問題也能夠在這裏彙報。