Jump to content
  • Advertisement
simco50

DX11 Urho3D Graphics abstraction

Recommended Posts

Hello,

I've stumbled upon Urho3D engine and found that it has a really nice and easy to read code structure.
I think the graphics abstraction looks really interesting and I like the idea of how it defers pipeline state changes until just before the draw call to resolve redundant state changes.
This is done by saving the state changes (blendEnabled/SRV changes/RTV changes) in member variables and just before the draw, apply the actual state changes using the graphics context.
It looks something like this (pseudo):

void PrepareDraw()
{
  if(renderTargetsDirty)
  {
      pD3D11DeviceContext->OMSetRenderTarget(mCurrentRenderTargets);
      renderTargetsDirty = false
  }
  if(texturesDirty)
  {
      pD3D11DeviceContext->PSSetShaderResourceView(..., mCurrentSRVs);
      texturesDirty = false
  }
  ....
  //Some more state changes
}

This all looked like a great design at first but I've found that there is one big issue with this which I don't really understand how it is solved in their case and how I would tackle it.
I'll explain it by example, imagine I have two rendertargets: my backbuffer RT and an offscreen RT.

Say I want to render my backbuffer to the offscreen RT and then back to the backbuffer (Just for the sake of the example).
You would do something like this:

//Render to the offscreen RT
pGraphics->SetRenderTarget(pOffscreenRT->GetRTV());
pGraphics->SetTexture(diffuseSlot, pDefaultRT->GetSRV())
pGraphics->DrawQuad()

pGraphics->SetTexture(diffuseSlot, nullptr); //Remove the default RT from input

//Render to the default (screen) RT
pGraphics->SetRenderTarget(nullptr); //Default RT
pGraphics->SetTexture(diffuseSlot, pOffscreenRT->GetSRV())
pGraphics->DrawQuad();

The problem here is that the second time the application loop comes around, the offscreen rendertarget is still bound as input ShaderResourceView when it gets set as a RenderTargetView because in Urho3D, the state of the RenderTargetView will always be changed before the ShaderResourceViews (see top code snippet) even when I set the SRV to nullptr before using it as a RTV like above causing errors because a resource can't be bound to both input and rendertarget.

What is usually the solution to this?

 

Thanks!

Share this post


Link to post
Share on other sites
Advertisement
Posted (edited)

I deal with a similar issue in my graphics library. My API is not exactly like what you've posted (it's closer to Vulkan/D3D12 with multiple resources being bound as a single set), but there's a similar problem that needs to be tackled in the Direct3D11 backend.

In my case, I just keep track of all SRVs and UAVs that are currently bound, and check for invalid combinations when a new SRV or UAV is bound. For example, if you try to bind an SRV for Texture A to the context, then I'll check if Texture A has any bound UAV's. If it does, then those are removed. Then, the SRV is bound to the context and added to the map that tracks SRV state. Same process when a UAV is bound.

A bit hand-wavey (I could elaborate if it doesn't make sense), but overall it's not a very complicated system. Urho3D might be doing something more clever than I am.

Edited by mellinoe

Share this post


Link to post
Share on other sites

That sounds pretty straight forward and I've thought of doing that as well.
Although:

7 hours ago, mellinoe said:

If it does, then those are removed

The downside of this, is that the graphics system would be doing "magic" just to make it work behind the back of the user while it might not have been intended behavior. It wouldn't point out when the user made an actual mistake, it would just solve it by unbinding the problematic resource while the user might not realize that.

I wonder if there's a cleaner solution for this.

Share this post


Link to post
Share on other sites
Posted (edited)
45 minutes ago, simco50 said:

The downside of this, is that the graphics system would be doing "magic" just to make it work behind the back of the user while it might not have been intended behavior.

I just treat this as a quirk of the Direct3D11 backend's state tracking behavior. It's not legal for users to bind incompatible resources to the pipeline through my library's regular API, and they would get a descriptive error message if they did that.

Nevertheless the D3D11 context might get into a state where there are resource conflicts after a sequence of legal operations, because unused resources aren't removed. I could probably aggressively purge those unused resources, but it would be unnecessary most of the time.

 

EDIT: To clarify, in response to this:

Quote

It wouldn't point out when the user made an actual mistake, it would just solve it by unbinding the problematic resource while the user might not realize that.

I'm only talking about the cases where the user hasn't made a mistake. If they try to actually do something illegal (e.g. bind a Texture as both read and read-write), then you should catch that separately and give them a descriptive error.

Edited by mellinoe

Share this post


Link to post
Share on other sites

That sounds like the right thing to do.
What I do now, is before a conflict, flush the staged states (submit the state changes to the context with PrepareDraw() in my case)
That works but I wonder if there's a smarter solution for this.

Share this post


Link to post
Share on other sites

I'm no longer actively involved in the development of this library, but thought I'd chime in..

When a rendertarget is set such that it's still also being bound as a SRV, what should happen is that the runtime cleans up the still bound SRV, and if you have the debug device flag enabled, you see this as output log spam.

(D3D11 WARNING: ID3D11DeviceContext::OMSetRenderTargets[AndUnorderedAccessViews]: Forcing VS shader resource slot 0 to NULL)

That's why the D3D11 Graphics::PrepareDraw() in Urho3D sets rendertargets first, then textures.

The situation also easily happens also in Urho3D's own rendering, when pingponged postprocesses are involved.

In case you see an actual rendering bug resulting from this (ie. black screen) please raise it in Urho3D's issue tracker.

Share this post


Link to post
Share on other sites

Setting render targets is very rare compared to setting textures, so I take the heavy-handed approach of unbinding all SRV's when setting new render-targets (and marking the SRV bindings as dirty so the next draw will rebind any that are required). In non-shipping builds, I also implement my own version of the D3D runtime warnings -- when binding a SRV, I check to see if it's already bound as a RTV/DSV/UAV, and assert if so.

Share this post


Link to post
Share on other sites
20 hours ago, cadaver said:

That's why the D3D11 Graphics::PrepareDraw() in Urho3D sets rendertargets first, then textures.

Yeah, that's what I saw. I actually had no idea DirectX solves it by itself and the warning doesn't really break anything.

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

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!