Understanding Buffer, Texture, and Sampler "registers"

Started by
8 comments, last by Tim Coolman 11 years, 5 months ago
I am trying to better understand the limitations implied by the register keyword for HLSL buffers, textures, and samplers. I will explain my understanding of it, then pose a couple questions. Any corrections, verification, or clarification on this topic is much appreciated.

Let's take constant buffers for example. The first parameter of VSSetConstantBuffers is Start Slot, which I believe corresponds to the register defined for a cbuffer in HLSL. So, if you have:

[source lang="cpp"]cbuffer MyConstantBuffer : register(b2)
{
...
};[/source]

Then in order to supply an ID3D11Buffer resource for that cbuffer, then you would call:

[source lang="cpp"]myContext->VSSetConstantBuffer(2, 1, & myBufferResource);[/source]

Where "2" is the slot/register number indicated by "b2" in HLSL. In the MSDN documentation for VSSetConstantBuffer, the Start Slot parameter can be from 0 to D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT - 1. That would be 0 to 13 on my machine. So I assume that means I am limited to 14 cbuffers in my HLSL code, with registers ranging from "b0" to "b13". As I understand it, these registers can't be reused for multiple, "incompatible" constant buffers.

But is it also correct to assume that the scope of this limitation is per compiled shader? Currently I have all of my vertex and pixel shaders in a single file. So when the shaders are compiled, they are sharing the same configuration of constant buffers, textures, and samplers. With this setup I'm limited to 14 total constant buffers between all shaders. But if I separated out all my shaders to different files with their own constant buffer declarations, I could have 14 constant buffers PER shader since they would be completely separated from each other at compile time. Is this correct?

Then when I call VSSetConstantBuffer, I will provide the Start Slot number specific to the shader I'm about to use.

I hope this has made sense. Let me know if I am misunderstanding this at all. At first I thought that D3D11 programs were actually limited to 14 total constant buffers, but I found this hard to believe for large scale projects.
Advertisement
Looks all good to me; you've pretty much got it nailed.

Direct3D has need of instancing, but we do not. We have plenty of glVertexAttrib calls.

Good questions Tim, and ones I would like to know the answers to too.

Have you tried defining 2 constant buffers with the same register on the HLSL side?
My guess would be that you can, and then so long as you only set one of them from the C++ side and then only use one of them on your shader side you should be fine.

For example, imagine your game is a car racing game. In your code you might draw the roads first and then the cars in a separate Draw call.

So, when you draw the roads you could have a RoadParams specific constant buffer assigned to B0 and then set the appropriate Pixel Shader etc.

Once complete you could then assign a completely different CarParams constant buffer to B0, and set a different Pixel Shader (One that uses these params and draws car pixels instead of road pixels).

I'd imagine that should work fine, but I've not tried it.

Thanks
Ben

Have you tried defining 2 constant buffers with the same register on the HLSL side?


I have not tried this. I thought I read on another forum post that for registers to be reused they have to be "compatible" in some way. But I understand what you mean. If you have two different constant buffers defined with the same register slot in HLSL, maybe it is okay to use one or the other from a given shader as long as that slot has been provided with appropriate data from C++ first.

Anyone else know the answer? I won't know for sure until I have time to set up an experiment.

Thanks,
Tim
You can bind up to 14 constant buffers per pipeline stage (and up to 128 texture buffers per pipeline stage). See the remarks here: http://msdn.microsoft.com/en-us/library/bb509581(VS.85).aspx .

You can bind up to 14 constant buffers per pipeline stage (and up to 128 texture buffers per pipeline stage).


I understand that. The thing I'm still unsure about is whether you can use the same register number for multiple cbuffer structures in the same HLSL file, as long as you don't use more than one of those in a given pipeline stage.

For example, can you do this...
[source lang="cpp"]cbuffer BufferForVertexShader : register(b0)
{
float variable1;
float variable2;
uint variable3;
};

cbuffer BufferForPixelShader : register(b0)
{
float4x4 variable1;
float4 variable2;
int variable3;
int variable4;
};[/source]
... as long as you only access one cbuffer from either pipeline stage and have the proper buffer bound to slot 0 for each shader? Or does this cause compilation problems within the same HLSL file and require you to separate them out into different files?

Thanks.
You can, yes, though it's probably better to just use an include file for common structs and define the shader-specific buffers in each shader's HLSL file. Or are you planning to put all of the shaders into one file?

(Note: By "you can" I mean that I tested compiling a vertex and pixel shader with two cbuffers both assigned to register(b0) where only one of those cbuffers is used and that I received no compile errors using the built-in shader compilation with VS 2012 and the Windows 8 SDK; you might have different results if you compile using an older version of fxc even though it should in theory work fine. It's not super hard to test so if you are using an older fxc, just try it and see what happens.).
The way resources work is that the compiler binds them to a slot whenever a shader actually uses that resource. Any other resources are stripped out of the compiled shader. When I say "resource", I mean texture, buffer, constant buffer, or sampler. This means that a particular shader file can have multiple resources explicitly assigned to the same slot, and any shader within that file will compile as long as it only uses one of those resources. Here's an example with textures:

Texture2D TextureA : register(t0);
Texture2D TextureB : register(t0);
SamplerState SamplerA : register(s0);

float4 ThisShaderUsesTextureA(in float2 texcoord : TEXCOORD) : SV_Target0
{
return TextureA.Sample(SamplerA, texcoord);
}

float4 ThisShaderUsesTextureB(in float2 texcoord : TEXCOORD) : SV_Target0
{
return TextureB.Sample(SamplerA, texcoord);
}


In that case both shaders will compile, and each one will expect a texture bound to slot 0. They could be the same texture or different textures, it doesn't matter as some texture is there that matches the specified type (Texture2D in this case). The only time you would have a problem would be if you had a shader that tried to use two resources assigned to the same slot:


Texture2D TextureA : register(t0);
Texture2D TextureB : register(t0);
SamplerState SamplerA : register(s0);

float4 ThisShaderWontCompile(in float2 texcoord : TEXCOORD) : SV_Target0
{
return TextureA.Sample(SamplerA, texcoord) + TextureB.Sample(SamplerA, texcoord);
}


My example uses textures, but it's the same for constant buffers. When a shader uses a value from a constant buffer, that buffer is bound to its slot. So you're okay with having two constant buffers assigned to the same register, as long as any shader doesn't try to access both constant buffers.
Thanks for the clarification MJP, that's what I was trying to say in my post above.
Thanks to both MikeBMcL and MJP for your input. This really helps clarify things for me. Always helps to understand a little bit better how things work, even the things that happen "behind the scenes".

This topic is closed to new replies.

Advertisement