Urho3D Graphics abstraction

Started by
7 comments, last by simco50 6 years, 1 month ago

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!

Advertisement

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.

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.

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.

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.

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.

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.

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.

This topic is closed to new replies.

Advertisement