Thursday, June 8, 2017

directx11 - Mapping a C++ struct to an HLSL cbuffer


Out of curiosity (and peace of the mind...) I wondered how DirectX decides which attribute from a struct corresponds to the right variable inside an HLSL cbuffer-register(x) (apart from the order/type they are declared with).


My main concern is my structs not only have member variables but 2 constructors each. So how can I be sure the constructors are not at the beginning of my struct definition (memory wise - C++ question I guess) so my variables would overflow after my cbuffer ? Additionnaly, can I only rely on declare-order/type matching ?


Is there a more precise/explicit way than doing :


struct foo {
int a;

int b;
int c;

foo () {}
foo (int a, int b, int c) : a{a}, b{b}, c{c} {}
}

D3D11_BUFFER_DESC constantBufferDesc;
...
constantBufferDesc.ByteWidth = sizeof(foo) + (16 - sizeof(foo) % 16);

hr = g_d3dDevice->CreateBuffer(...);
g_d3dDeviceContext->VSSetConstantBuffers(0, 1, &fooBuffer);
g_d3dDeviceContext->UpdateSubresource(&fooBuffer, 0, nullptr, &foo, 0, 0);

With HLSL :


cbuffer foo: register(b0) {
// constructor here ?
int a;
// constructor here ?
int b;

int c;
// constructor here ?
}

I am aware this is more a C++ question - Thanks for your insight.



Answer




Out of curiosity (and peace of the mind...) I wondered how DirectX decides which attribute from a struct corresponds to the right variable inside an HLSL cbuffer-register(x) (apart from the order/type they are declared with).



Purely through memory layout. You give D3D a pointer to a chunk of memory which you claim to be organized in a certain fashion (for example, via your cbuffer definition). It's up to you to ensure that organization is actually true.




My main concern is my structs not only have member variables but 2 constructors each. So how can I be sure the constructors are not at the beginning of my struct definition



In C++, member functions (such as constructors) do not take up space in the memory layout of an instance of a structure type. In your example, sizeof(foo) is going to be 3 * sizeof(int), so it will map correctly to the foo structure in HLSL. So you don't need to worry about this.


Most of the time.




One potential issue would be if you have any virtual functions define in your type. Although it is implementation-defined how the virtual methods are handled, the far-and-away most common implementation is to stuff a pointer to the type's virtual table at the start of an instance of a type. This will offset everything by sizeof(void*) and render your structures mismatched.


Another potential issue is structure padding; compilers can and will insert dummy data between structure members (padding) to ensure the data is appropriately aligned. Generally you don't need to worry too much about this, as the types you are likely to use in your structures are those that usually end up tightly-packed once default compiler alignment comes into play. However if you have specialized alignment needs for either your CPU-side or GPU-side structures, and have padding in your type as a result, this is something you need to be aware of.


Thus, as long as you don't have any virtual methods defined in your structure types that you're mapping to GPU buffers, and you don't have any weird padding going on, you have nothing to worry about.


No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...