Are there DirectX guidelines for binding and unbinding resources between draw calls?

3.5k views Asked by At

All DirectX books and tutorials strongly recommend reducing resource allocations between draw calls to a minimum – yet I can’t find any guidelines that get more into details. Reviewing a lot of sample code found in the web, I have concluded that programmers have completely different coding principles regarding this subject. Some even set and unset

VS/PS 
VS/PS ResourceViews
RasterizerStage 
DepthStencilState
PrimitiveTopology
... 

before and after every draw call (although the setup remains unchanged), and others don’t.

I guess that's a bit overdone...

From my own experiments I have found that the only resources I have to bind on every draw call are the ShaderResourceViews (to VS and PS in my case). This requirement may be caused by the use of compute shaders since I bind/unbind UAVs to buffers that are bound to VS / PS later on.

I have lost many hours of work before I detected that this rebinding was necessary. And I guess that many coders aren’t sure either and prefer to unbind and rebind a “little too much” instead of running into a similar trap.

Question 1: Are there at least some rules of thumb regarding this problem?

Question 2: Is it possible that my ShaderResourceViews bound to VS/PS are unbound by the driver/DirectX core because I bind UAVs to the same buffers before the CS dispatch call (I don’t unbind the SRVs myself)?

Question 3: I don't even set VS/PS to null before I use the compute shaders. Works w/o problems yet I feel constantly unsure whether or not I'm digging my next trap using such a "lazy" approach.

2

There are 2 answers

3
galop1n On

Answer 1 : less is better.

Answer 2 : it is the opposite, you have to unbind a view before you bind the resource with a different kind of view. You should enable the debug layer to catch errors like this.

Answer 3 : that's fine.

2
mrvux On

You want to have as less overhead, but also while avoiding invalid pipeline state. That's why some people unbind everything (try to prevent as much), it depends on uses cases, and of course you can balance this a bit.

To balance this you can pre allocate a specific resource to a slot, depending on resource type, since you have a different number of slots, different rules can apply

1/Samplers and States

You have 16 slots, and generally 4-5 samplers you use 90% of the time (linear/point/anisotropic/shadow).

So on application startup create those states and bind them to each shader stage you need (try not to start at zero slot, since they would easily be overriden by mistake). Create a shader header file with mapping SamplerState -> slot, and use it in your shaders, so any slot update is reflected automatically.

Reuse this as much as possible, and only bind custom samplers.

For standard states (Blend/Depth/Rasterizer), building a small collection of common states on application startup and bind as needed is common practice.

An easy way to minimize Render State binding at low cost, you can build a stack, so you set a default state, and if a shader needs a more specific state, it can push new state to the stack, once it's done, pop last state and apply it again to the pipeline.

2/Constant Buffers

You have 14 slots, which is quite a lot, it's pretty rare (at least in my use cases) to use all of them, specially now you can also use buffers/structuredbuffers as well.

One simple common case is setting a reserved slots for camera (with all data that you need, view/projection/viewprojection, plus their inverses since you might need that too.

Bind it to (all if needed) shader stage slots, and only thing you have to do is update your cbuffer every frame, it's ready to use anywhere.

3/Shader stages

You pretty much never need to unbind Compute Shader, since it's fully separated from the pipeline.

On the other side, for pipeline stage, instead of unbinding, a reasonably good practice is to set all the ones you need and set to null the ones you don't.

If you don't follow this as example and render a shadow map (depth buffer only), a pixel shader might still be bound.

If you forget to unset a Geometry Shader that you previously used, you might end up with invalid layout combination and your object will not render (error will only show up in runtime debug mode).

So setting the full shader stage adds little overhead, but the safety trade off is very far from negligible.

In your use case (using only VS/PS and CS to build), you can safely ignore that.

4/Uavs-RenderTargets-DepthStencil

For write resources, always unset when you done with unit of work. Within the same routine you can optimize inside, but at the end of your render/compute shader function, set your output back to null, since pipeline will not allow anything to be rebound as ShaderResource while it's on output.

Not unsetting a write resource at the end of your function is recipe for disaster.

5/ShaderResourceView

This is very situational, but idea is to minimize while also avoiding runtime warnings (which can be harmless, but then hide important messages).

One eventual thing is to reset to null all shader resource inputs at the beginning of the frame, to avoid a buffer still bound in VS to be set as UAV in CS for example, this costs you 6 pipeline calls per frame, but it's generally worth it.

If you have enough spare registers and some constant resources you can also of course set those in some reserved slots and bind those once and for all.

6/IA related resources

For this one, you need to set the right data to draw your geometry, so everytime you bind it it's pretty reasonable to set InputLayout/Topology . You can of course organize your draw calls to minimize switches.

I find Topology to be rather critical to be set properly, since invalid topology (for example, using Triangle List with a pipeline including tesselation), will draw nothing and give you a runtime warning, but it's very common that on AMD card it will just crash your driver, so better to avoid that as it becomes rather hard to debug.

Generally never really unbinding vertex/index buffers (since just overwriting them and Input layout tells how to fetch anyway). Only exception to this rule if in the case those buffers are generated in compute/stream out, to avoid the above mentioned runtime warning.