This question has the C++
tag because there are more developers working with DirectX
in C++
than there are in C#
. I don't believe this question is directly related to either language, but instead to the types used (which as I understand it are exactly the same), or DirectX
itself and how it compiles shaders. If someone working in C++
knows a better and more descriptive answer, then I would prefer that over my own answer. I understand both languages but use C#
primarily.
In an HLSL
shader, when setting up my constant buffers I ran into an issue that was rather peculiar. The original constant buffers in question were set up as follows:
cbuffer ObjectBuffer : register(b0) {
float4x4 WorldViewProjection;
float4x4 World;
float4x4 WorldInverseTranspose;
}
cbuffer ViewBuffer : register(b1) {
DirectionalLight Light;
float3 CameraPosition;
float3 CameraUp;
float2 RenderTargetSize;
}
If I swap the b0
and b1
registers around, rendering no longer works (e1). If I leave those registers alone, and swap the order between World
and WorldViewProjection
again, rendering no longer works (e2). However, simply moving the ViewBuffer
above the ObjectBuffer
in the HLSL
file without making other modifications, it works just fine.
Now, I expect the register placement is rather important and that the first register b0
requires the three properties given in that buffer, and I understand that HLSL
constant buffers are required to be in 16 byte chunks. However, this leaves me with some questions.
Given the fact that HLSL
expects constant buffers to be in 16 byte chunks;
Aren't float4x4
types the same as Matrix
types where it's essentially an array of arrays?
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ 0, 0, 0, 0 ] = 16 bytes
[ TOTAL ] = 64 bytes
Since a float
is 4 bytes on its own, this would mean a float4
is 16 bytes, and thus a float4x4
is 64 bytes. So why does the order matter if the size remained the same?
ObjectBuffer
have to be assigned to b0
in this case instead of any other b
register?I am currently working on further analysis of the problem so that I can give a more detailed and accurate answer. I will update the question and answer to reflect as much accuracy as possible as I discover more details.
The exact issue with the question above (which was unknown at the time of posting), is that the HLSL
buffers didn't match their C#
representations; thus the reordering of variables caused the shader to fail. However, I am still unsure as to why when the types are the same. I have learned of some other things along my road for an answer and have decided to post them here.
After some further research and testing, I'm still not 100% sure as to the reasoning behind this where types are all the same. Overall, I believe it may be due to the expected types in the cbuffer
and the order of the types in the struct
. In this case, if your cbuffer
expects a bool
first and then a float
, then rearranging causes issues.
cbuffer MaterialBuffer : register(b0) {
bool HasTexture;
float SpecularPower;
float4 Ambient;
...
}
// Won't work.
public struct MaterialBuffer {
public float SpecularPower;
public Vector2 padding2;
public bool HasTexture;
private bool padding0;
private short padding1;
public Color4 Ambient;
...
}
// Works.
public struct MaterialBuffer {
public bool HasTexture;
private bool padding0;
private short padding1;
public float SpecularPower;
public Vector2 padding2;
public Color4 Ambient;
...
}
I put some research effort into testing the differences in byte size of types and this didn't really seem to change much of anything but I'll post my findings for common basic types here:
1 Byte : bool, sbyte, byte
2 Bytes : short, ushort
4 Bytes : int, uint, float
8 Bytes : long, ulong, double
16 Bytes: decimal
You do have to be conscious of the basic types used to construct more complex types. Say for example you have a Vector2
with an X
property and a Y
property. If those are represented by float
types then you'll need an 8 byte padding prior to the next property unless you have some other stuff to help reach 16 bytes. However, if those are represented by double
types or decimal
types then the size is different and you'll need to be aware of that.
I was able to solve the register issue; this also corresponds to the C#
side when you set the buffers. As you set the buffers, you assign indices to those buffers and the HLSL
is expected to use the same indices.
// Buffer declarations in HLSL.
cbuffer ViewBuffer : register(b0)
cbuffer CameraBuffer : register(b1);
cbuffer MaterialBuffer : register(b2);
// Buffer assignments in C#.
context.VertexShader.SetConstantBuffer(0, viewBuffer);
context.VertexShader.SetConstantBuffer(1, cameraBuffer);
context.VertexShader.SetConstantBuffer(2, materialBuffer);
The above code will work as expected since the buffers are assigned to the correct registers. However, if we change the buffer for the camera to 8 for example, then the cbuffer
would have to be assigned to register b8
in order to work properly. The code below doesn't work for that exact reason.
cbuffer CameraBuffer : register(b1)
context.VertexShader.SetConstantBuffer(8, cameraBuffer);