Sign in to follow this  
RonHiler

shader constant buffers in D3D9

Recommended Posts

Hey guys, I have this bit of code in my DrawSubMesh routine, the purpose of which is to fill in the shader constant buffers. This works great for the D3D10, 10.1, and 11 renderers. And now I have to marry in functionality for the D3D9 renderer. And I have to admit, I'm floundering a bit here. This is the code I have:


Material *CurrentMaterial;
void **ConstantBuffer;
D3D11_MAPPED_SUBRESOURCE Buffer;
Matrix *BufferData;

CurrentMaterial = GraphicsMgr.GetMaterialPointer(&MaterialName);
ConstantBuffer = CurrentMaterial->GetConstantBufferPointer(ENZ_SHADER_VERTEX, "cbPerObject");
MapBuffer(Renderer, Interface, *ConstantBuffer, &Buffer);
BufferData = static_cast<Matrix*>(Buffer.pData);
BufferData[0] = *World;
UnmapBuffer(Renderer, Interface, *ConstantBuffer);

ConstantBuffer = CurrentMaterial->GetConstantBufferPointer(ENZ_SHADER_VERTEX, "cbPerViewport");
MapBuffer(Renderer, Interface, *ConstantBuffer, &Buffer);
BufferData = static_cast<Matrix*>(Buffer.pData);
BufferData[0] = *View;
BufferData[1] = *Projection;
UnmapBuffer(Renderer, Interface, *ConstantBuffer);




where my vertex shader constants are simply:


cbuffer cbPerObject
{
float4x4 WorldMatrix;
};

cbuffer cbPerViewport
{
float4x4 ViewMatrix;
float4x4 ProjectionMatrix;
};




So I guess my question is, what is a good approach for code that will handle all four rendering APIs? I have wrapper classes for each of the APIs, so I can certainly put any API specific code in it's own function (MapBuffer and UnmapBuffer in the given code end up in the wrapper functions, for example).

I guess DX9 requires use of the SetVertexShaderConstantF function (presuming I want to set the vertex shader constants). But then, could I use the names of the buffer (e.g. "cbPerObject") somehow, the same way as is done for the other renderers?

Anyway, as I said, I'm floundering a bit on this one, so any advice is appreciated. Even just nudges in the right direction would be very helpful.

Ron

[Edited by - RonHiler on December 4, 2010 11:35:56 AM]

Share this post


Link to post
Share on other sites
I wrote something similar at work. The problem you'll run into is that constants in D3D9 are stripped if they're not used in a shader, and the compiler will try to pack all used constants into adjacent constant registers. So if your "constant buffer" is like this:

float4 someConstant;
float4 someOtherConstant;

and you only use "someOtherConstant", then "someConstant" will be stripped and "someOtherConstant" will get mapped to c0. This means you can't just set all of your constants with one call to SetVertexShaderConstantF/SetPixelShaderConstantF and then leave them there.

What I ended up doing was I just manually assigned constants to constant registers, using a macro that evaluated to "register : c(#)" for D3D9. If you do that then you can explicitly specify the exact layout of the entire constant buffer. So the pre-processed code would look something like this:

// Per-frames:
{
float4x4 View : register(c0);
float4x4 Proj : register(c4);
float3 EyePos : register(c8);
}

// Per-object:
{
float4x4 World : register(c9);
float3 Color : register(c12);
}

You can do the register mapping manually if you want, or if you have a shader build pipeline you can do it automatically by just counting the number of registers required for each constant. Then at runtime you just need the starting register for each constant buffer, and the size and you can set the whole thing in one call. You can get the starting register using reflection at runtime or build time, or you if you're assigning the registers automatically then you know them implicitly. As for giving the constant buffers a name...I think you should be able to just wrap all of them in a struct and it should work okay. Then in the constant table you can just look for the name of the struct, and the starting register of the first child parameter.

[Edited by - MJP on December 4, 2010 3:54:24 PM]

Share this post


Link to post
Share on other sites
Interesting! Thanks Matt. I can see I'm going to have to rewrite the existing routines for DX10+ to support this methodology, but I figured I would have to do that anyway. At least this way there is a linkage between the two very different systems, and I can marry them together in a common interface (at this place in the code, the class doesn't (and shouldn't) know which renderer is active (that doesn't happen until the code path gets into the API wrapper manager class), so it needs commonality). Very cool.

I'll get to work on it. Thanks very much, that was just the idea thread I needed :)

Share this post


Link to post
Share on other sites
Just a quick follow up question.

The vs_4_0 specification doesn't have c# registers, according to this page:

http://msdn.microsoft.com/en-us/library/ff471380(VS.85).aspx

As opposed to the vs_3_0 spec, which does:

http://msdn.microsoft.com/en-us/library/bb172963(v=VS.85).aspx

I presume the r# register is equivalent? And if so, it looks like the min number went from 256 to 4096 registers.

Is that all correct? I can't seem to really get a straight answer from the docs, and the register modifier seems to take anything I put in, even if it's garbage, heh.

Share this post


Link to post
Share on other sites
No, r registers are your temporary registers that are allocated and used when a shader runs. SM4.0 doesn't really have an exact equivalent for the c register, since constant buffers are resources and thus aren't mapped directly to a flat register set (which is what you had in SM3.0).

Instead constant buffers are mapped to "slots", similar to how you'd map a texture or a sampler to a slot. These slots are cb registers, and there's 15 of them. So basically you have 15 slots to which you can bind an entire constant buffer, and that slot maps directly to the slot you use when calling VSSetConstantBuffers (or the equivalent for other shader stages). If you want you can explicitly map a constant buffer to a slot using the " : register(cb0)" syntax.

One other thing to be aware of is packing and alignment within a constant buffer. This is done with the " : packoffset(c0)" syntax:

cbuffer PSConstants : register(cb0)
{
float3 LightDirWS : packoffset(c0);
float3 LightColor : packoffset(c1);
float3 CameraPosWS : packoffset(c2);
}


Each c# is essentially a float4 register, so in the case above the three constants within the constant buffer will have 16-byte alignment. But you can also use this syntax to tightly pack constants, like this:

cbuffer PSConstants : register(cb0)
{
float3 LightDirWS : packoffset(c0);
float3 LightColor : packoffset(c1);
float3 CameraPosWS : packoffset(c2);
float Exposure : packoffset(c2.w);
}


See this for the default packing rules.

Share this post


Link to post
Share on other sites
Okay, thanks again Matt. I believe I understand. I will use cb# (to a max of 15)on groups of constants when I am using VS_4_0, and c# (to a max of 255) on individual constants when I am using VS_3_0.

And I take it the StartRegister paramter of SetPixelShaderConstantF (and its ilk) will not care which of those I have used, and long as I give it the right number (corresponding to the # value in the HLSL code).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this