Constant buffer for shaders in SharpDX

Started by
7 comments, last by myvraccount 8 years, 12 months ago

I'm trying to shade based on the directions of normal vectors, but they need to be relative to the camera, so I must give a parameter to the shaders that holds a vector from the point being viewed to the camera, so I can compare that to the surface normal to get the angle between them.

The problem is that all I'm sending right now as a parameter in the constant buffer is a matrix to transform an object through worlds space into view space, so I can draw it in the correct place. I want to add a vector to the list of things to send, but I can't find a function in the constant buffer that allows me to adequately set a list of parameters like that. Currently I'm using a function that just lets me use a matrix and that's all.

Does anyone know of a way to send arbitrary information like this? And also, how do I receive it in the shader? Is it sufficient just to declare variables within the shader that match those types/sizes and are defined in the same order?

Advertisement

I usually use the Effects Framework support, which I prefer but its a slightly different way of doing things if you are using straight shaders.

Under the covers, I believe all that is really happening is that the bytes that make up whatever data you are sending to the GPU get marshalled to a native byte array and uploaded to the constant buffer. Usually you define a C# struct with [StructLayout(LayoutKind.Sequential)] or LayoutKind.Explicit, and set that up to match the structure of your cbuffer. Then you update your struct with any changes, do a UpdateSubresource, then set the Buffer to your shader.

I personally use SlimDX, but SharpDX is pretty much the same.

Another useful thing I often do when I'm having trouble is to install dotPeek and go look at the source of the method that I'm are currently using to get an idea how it works under the covers.

Eric Richards

SlimDX tutorials - http://www.richardssoftware.net/

Twitter - @EricRichards22

Well I'll have to look into that, but I don't remember seeing a function for setting the type to anything like that. There's a function (I don't remember what it's called, I think updateResource or something), and it's used for putting a matrix into the constant buffer. It overwrites everything in the buffer with just the one matrix though. There are several overloads though, but they didn't look like what I want, hence the reason for my question in the first place.

In HLSL, you declare a constant buffer and define the layout of data within that constant buffer. So if you were to have this in your shader:

cbuffer MyConstants
{
    float4x4 ViewTransform;
    float3 ObjectPosition;
}
Your shader would then expect a constant buffer to be bound that has the same layout. This means it would expect the buffer to start with 16 floats for the matrix, followed immediately by 3 floats for object position.

The idea here is that you specify your data layout in HLSL, and then in your application you would make sure to fill up your constant buffer resource with data using the same layout. There are many ways to do this, but probably the simplest is to do what Eric recommended in his post: make a matching C# struct with the same data layout, fill it with appropriate values, and then use either MapSubresource or UpdateResource to fill the buffer resource with the contents of your struct. For the simple constant buffer in my example, making a matching C# struct should be pretty simple:

[StructLayout(LayoutKind.Sequential)]
public struct MyConstants
{
    public Matrix ViewTransform;
    public Vector3 ObjectPosition;
}
The one thing you really need to watch out for is that the packing rules for constant buffers can be a little weird. Most notably, if a vector type crosses a 16-byte boundary, then it will move the vector so that it sits on the next 16-byte alignment. As an example, let's add another float3 to our constant buffer layout:

cbuffer MyConstants
{
    float4x4 ViewTransform;
    float3 ObjectPosition;
    float3 LightPosition;
}
So the float4x4 is 64 bytes in size, and a float3 is 12 bytes. So you might think that "LightPosition" would be located at an offset of (64+12) = 76 bytes. However, this is not the case due to the alignment rule that I just mentioned. Since "LightPosition" would straddle a 16-byte boundary, it will give moved up 4 bytes so that it's located at an offset of 80 bytes. To make sure that our C# struct has the same data layout, LayoutKind.Explicit can be used:

[StructLayout(LayoutKind.Explicit)]
public struct MyConstants
{
    [FieldOffset(0)]
    public Matrix ViewTransform;

    [FieldOffset(64)]
    public Vector3 ObjectPosition;

    [FieldOffset(80)]
    public Vector3 LightPosition;
} 
As an alternative, you can also just insert padding variables into your struct:

[StructLayout(LayoutKind.Sequential)]
public struct MyConstants
{
    public Matrix ViewTransform;
    public Vector3 ObjectPosition;
    public Uint32 Padding;
    public Vector3 LightPosition;
} 

Well I'll have to look into that, but I don't remember seeing a function for setting the type to anything like that. There's a function (I don't remember what it's called, I think updateResource or something), and it's used for putting a matrix into the constant buffer. It overwrites everything in the buffer with just the one matrix though. There are several overloads though, but they didn't look like what I want, hence the reason for my question in the first place.


The generic version of UpdateSubresource can take any type, including your own custom struct type.

To add to your post MJP:

I'm using SharpDX and I noticed that I couldn't put variables in my cbuffer that are not multiples of 16 bytes. I once wanted to pass a Vector2 to a cBuffer, and it just wouldn't let me so I had to use a Vector4 instead, leaving half of it unused

You can put variables (basically any struct as mentioned earlier) that are not a multiple of 16 bytes, but cbuffer requires to have it's total size to be a multiple of 16 bytes (so yes, if you use single variable for cbuffer then you lose data)

I normally use this (considering T is a struct that you can know size of) to construct cbuffer size:


size = ((Marshal.SizeOf(typeof(T)) + 15) / 16) * 16;

I think I've encountered the 4 byte boundery problem before, so I just use Vector4 instead of Vector3. Thanks for the advice. I'll try it that way.

That seems to have fixed the problem, thanks!

This topic is closed to new replies.

Advertisement