shader constant buffers in D3D9

Started by
4 comments, last by RonHiler 13 years, 4 months ago
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]
Creation is an act of sheer will
Advertisement
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]
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 :)
Creation is an act of sheer will
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.
Creation is an act of sheer will
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.
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).
Creation is an act of sheer will

This topic is closed to new replies.

Advertisement