常量变量的打包规则
打包规则决定了在存储数据时的数据排列紧密程度。 HLSL 针对 VS 输出数据、GS 输入和输出数据以及 PS 输入和输出数据强制实施打包规则。 (由于 IA 阶段无法解包数据,因此不会打包 VS 输入数据。)
HLSL 打包规则类似于使用 Visual Studio 执行 #pragma pack 4,即,将数据打包成 4 字节边界。 此外,HLSL 会打包数据,使其不会超越 16 字节边界。 变量将打包成给定的四分量矢量,直到变量跨越 4 矢量边界;下一个变量将回弹到下一个四分量矢量。
每个结构强制下一个变量从下一个四分量矢量开始。 这有时会为结构数组生成填充。 任何结构的最终大小始终按照 sizeof(四分量矢量)均匀划分。
默认情况下,数组不会在 HLSL 中打包。 为了避免强制着色器在偏移计算方面产生 ALU 开销,数组中的每个元素将存储在四分量矢量中。 请注意,可以使用强制转换来实现数组打包(并造成寻址计算)。
下面是结构及其相应打包大小的示例(假设:float1 占用 4 个字节):
// 2 x 16byte elements
cbuffer IE
{
float4 Val1;
float2 Val2; // starts a new vector
float2 Val3;
};
// 3 x 16byte elements
cbuffer IE
{
float2 Val1;
float4 Val2; // starts a new vector
float2 Val3; // starts a new vector
};
// 1 x 16byte elements
cbuffer IE
{
float1 Val1;
float1 Val2;
float2 Val3;
};
// 1 x 16byte elements
cbuffer IE
{
float1 Val1;
float2 Val2;
float1 Val3;
};
// 2 x 16byte elements
cbuffer IE
{
float1 Val1;
float1 Val1;
float1 Val1;
float2 Val2; // starts a new vector
};
// 1 x 16byte elements
cbuffer IE
{
float3 Val1;
float1 Val2;
};
// 1 x 16byte elements
cbuffer IE
{
float1 Val1;
float3 Val2;
};
// 2 x 16byte elements
cbuffer IE
{
float1 Val1;
float1 Val1;
float3 Val2; // starts a new vector
};
// 3 x 16byte elements
cbuffer IE
{
float1 Val1;
struct {
float4 SVal1; // starts a new vector
float1 SVal2; // starts a new vector
} Val2;
};
// 3 x 16byte elements
cbuffer IE
{
float1 Val1;
struct {
float1 SVal1; // starts a new vector
float4 SVal2; // starts a new vector
} Val2;
};
// 3 x 16byte elements
cbuffer IE
{
struct {
float4 SVal1;
float1 SVal2; // starts a new vector
} Val1;
float1 Val2;
};
更激进的打包方式
可以以更激进的方式打包数组,如下所示。 假设你想要从常量缓冲区访问如下所示的数组:
// Original array: not efficiently packed.
float2 myArray[32];
在常量缓冲区中,上述声明将占用 32 个 4 元素矢量。 这是因为,每个数组元素将放置在其中一个矢量的开头。 现在,如果你希望这些值紧密打包在常量缓冲区中(没有任何空格),并且仍希望以 float2[32]
数组的形式访问着色器中的数组,则可以如下所示编写代码:
float4 packedArrayInCBuffer[16];
// shader uses myArray here:
static const float2 myArray[32] = (float2[32])packedArrayInCBuffer;
更紧密的打包方式是一种折衷方法,这样就无需为寻址计算添加额外的着色器指令。