DirectX HLSL Packing Rules

Started by
3 comments, last by SBD 3 years, 7 months ago

I'm currently reading the book “Introduction to 3D game programming with DirectX 12” and got a little confused with HLSL on the Lighting chapter. So, I know about HLSL packing rules, but after I created the constant buffer like this:

cbuffer cbPass : register(b2)
{
    // not perfectly padded stuff.

    int      iLightCount;
	
    Light    vLights[MAX_LIGHTS];
}

my vLights array had only the first float3 member of the Light struct copied right, other members (of the Light struct) had invalid values. After that, I thought that maybe the issue is in the padding and changed my cbuffer to this:

cbuffer cbPass : register(b2)
{
    // perfectly padded stuff.

    int      iLightCount;
	
    float3   pad2;
	
    Light    vLights[MAX_LIGHTS];
}

I also changed my C++ Light struct accordingly and now all members of the Light struct were copied correctly. I'm confused, why only the first 3-4 bytes of the struct was copied correctly without perfect padding.

Advertisement

Flone said:
I also changed my C++ Light struct accordingly and now all members of the Light struct were copied correctly. I'm confused, why only the first 3-4 bytes of the struct was copied correctly without perfect padding.

HLSL uses a 16-byte alignment, while c++ only has 4/8 bytes, depending on x86-64. So if you use the same struct in C++ that you use in HLSL, for your first version the Light-array will directly follow the light-counts in memory in C++, while the HLSL-version has 12 bytes of implicit padding.

You should change the alignment to 16-byte in c++ (pragma pack or something) when doing this kind of stuff.

Juliean said:

Flone said:
I also changed my C++ Light struct accordingly and now all members of the Light struct were copied correctly. I'm confused, why only the first 3-4 bytes of the struct was copied correctly without perfect padding.

HLSL uses a 16-byte alignment, while c++ only has 4/8 bytes, depending on x86-64. So if you use the same struct in C++ that you use in HLSL, for your first version the Light-array will directly follow the light-counts in memory in C++, while the HLSL-version has 12 bytes of implicit padding.

You should change the alignment to 16-byte in c++ (pragma pack or something) when doing this kind of stuff.

Yeah, I've read about that, and you're right it's pragma pack. Thanks, I'm sure this might be the issue.

It's actually a little more complicated than Juliean stated. Under the covers, data is stored in 16-byte, 4-component vectors. HLSL requires 4-byte packing (so that a variable will fit into a single 32-bit value/component), BUT any variable cannot cross a 16-byte boundary (go across multiple underlying 16-byte vectors that store it). In short, for the case of arrays, this means they must start on a 16-byte boundary. And of course, this means that structs in HLSL also have these padding requirements, which means when storing arrays of structs it can get even more fun.

I find the easiest thing to do is explicitly put the padding in myself in both my HLSL declarations and my C++ structs. In C++, you should be using #pragma pack(4) for your cbuffer structs.

https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules​

An example that I just had to fix in my own code, genericized…

#define MAX_DATA 256

cbuffer cbData : register( b6 )
{
	float	var1;

	uint	dataCount;

	// NOTE:  Be careful with structs (and arrays), as they will be padded to 16-byte boundary.
	//		  As it stands now, we need no struct padding.
	struct data
	{
		float4	m1;
		float4	m2;
	};

	float2	padding;	// Need to have this padding BEFORE the array of structs, as arrays will START on 16-byte boundaries

	Data data[MAX_DATA];
};

And the C++ struct:

#pragma pack( push, 4 )

#define MAX_DATA 256

struct DataConstantBuffer
{
	float  var1;

	uint32_t dataCount;

	// NOTE:  Be careful with structs (and arrays), as they will be padded to 16-byte boundary.
	//    As it stands now, we need no struct padding.
	struct Data
	{
	 	HLSLVector4 m1; // HLSLVector4 is just a class with 4 float members, x, y, z, w
 		HLSLVector4 m2;
	};

	float  padding[2]; // Need to have this padding BEFORE the array of structs, as arrays will START on 16-byte boundaries

	Data  data[MAX_DATA];
};

This topic is closed to new replies.

Advertisement