Texture Streaming synchronization

Started by
7 comments, last by ongamex92 10 years, 2 months ago

I have a separate thread that I use for loading texture data. It fills out a "PixelData" buffer with pixel data. With my OpenGL code, I can use "fences" to synchronize the upload to the card. I call glFenceSync to get a synchronization object, then tell the driver to upload my PixelData to the card via glTexSubImage2D. This call doesn't block; I can check the state of the synchronization object, and when it's completed I know that the texture is available to me.

How does one do this with DirectX? Right now, my DX texture updating code just calls ID3D11DeviceContext::Map, then a memcpy, and then an Unmap. DirectX is threadsafe, though, right? So I could actually make these three calls from within the thread, and just check with a mutex before using the texture... right?

Advertisement

DirectX is thread-safe for resource allocation, as long as the device sets DriverConcurrentCreates to TRUE. You can read this for more info: http://msdn.microsoft.com/en-us/library/windows/desktop/ff476893(v=vs.85).aspx

Note that this only means resource creation is thread-safe, calling device context methods from multiple threads still isn't thread safe. However you can just have your streaming thread create the texture resource, and pass the new texture data through the D3D11_SUBRESOURCE_DATA structure.

By calling ID3D11DeviceContext::UpdateSubresource? (or perhaps better by creating a second resource and calling ID3D11DeviceContext::CopyResource or ::CopySubresourceRegion)? Those functions do talk about being asynchronous calls. But then I'm back to the same point of needing a synchronization object like the fences in OpenGL to know when the copy has succeeded.

I've been researching it while typing out this post. It looks like I want to do a ID3D11Device::CreateQuery call with D3D11_QUERY_EVENT...? (It also looks like I was doing my call to glFenceSync out of order; it should be after the asynchronous call to upload the data). I call CreateQuery, then CopyResource, and then Context->End(Query). I can then check the state of the query with Context->GetData.

Does that sound right? I'm going to bed anyway so I'll just give it a shot tomorrow.

If you want to know when the GPU has finished a draw operation, you can use events, yes.

However, this isn't required for updating resources. The update call will complete immediately, copying the supplied data into an internal temporary buffer if required. The actual update will occur asynchronously in the background. Any draw-calls submitted before the update call will use the old data, any draw-calls submitted after the update will use the new data (stalling automatically, if necessary to avoid a race condition). Everything should just work(tm) without the need for any events.

I'm intentionally trying to circumvent the "stalling automatically" part.

Thanks guys!

Simply call ID3D11Device::CreateTexture2D() and fill in the pInitialData parameter on the background thread. The same applies to any other ID3D11Device function.

Once you have done that just hand the resulting ID3D11Texture2D * over to the main thread somehow.

That should be all you need to do.

Any draw-calls submitted before the update call will use the old data, any draw-calls submitted after the update will use the new data (stalling automatically, if necessary to avoid a race condition).

I'm intentionally trying to circumvent the "stalling automatically" part.

Thanks guys!

I don't know if this is possible. If the texture update is performed as a command in the internal command buffer, then yep, your idea of inserting an event right after the update call would work -- when the event is ready, you'd know that the update is complete.
However, I don't think that's how it works.
I don't know if there is a way to tell when an update is actually complete.

Is this actually a performance problem for you though? In normal usage, your command buffer will have about a frame's worth of latency in it, which should be enough time for any sensible update to complete (if you're doing a 16ms memcpy, then something is wrong).
To be extra sure, you could simply wait a frame after your update call before using the updated resource. In normal usage, no update is going to take 2 frames to complete.

Simply call ID3D11Device::CreateTexture2D() and fill in the pInitialData parameter on the background thread. The same applies to any other ID3D11Device function.

Once you have done that just hand the resulting ID3D11Texture2D * over to the main thread somehow.

That should be all you need to do.

Yes, this is what I was originally suggesting. It's possible to update the contents of already-created textures (as long as they're not creating with D3D11_USAGE_IMMUTABLE), but (IMO) the simplest way to do it is to simply create a new resource and give it the data. This is how we handle our texture streaming for the Windows version of our engine at work.

If you need to update an existing texture, then the two ways you mentioned (UpdateSubresource or Map a STAGING texture and then use CopyResource or CopySubresourceRegion) are the two ways of doing it. I recall reading from a GDC presentation a few years ago that both Nvidia and AMD recommended using a poll of STAGING textures for frequent texture updates, although I haven't actually profiled vs. UpdateSubresource. Like Hodgman mentioned there isn't any explicit synchronization required in either case. You just need to either provide the data to UpdateSubresource or call CopyResource, and the driver handles asynchronously updating the destination texture in such a way that it the data is available for any subsequent commands.

http://msdn.microsoft.com/en-us/library/windows/hardware/ff568288(v=vs.85).aspx

This topic is closed to new replies.

Advertisement