-
|
|
Multiple instances of ContentManager on multiple threads
|
As far as I have read many beliefs ( example) on ContentManager internals and it's thread-safety, I'm still concerned about it's proper use in mult-threaded environment.
Understanding XNA Framework Performance gives few hints in that matter: "ContentManager is not thread safe, but it's ok to have multiple instances, but only one per thread".
Armed with Refactor I dug into the demonic depths of XNA (in matter of fact, it's a great piece of software, worth studying). As far as ContentManager caching is one of this not-always-wanted mechanisms, which can't be disabled or overrun without loads of reflection tweaks, I was looking for potential multi-thread and multi-instance issues and... found nothing.
Well, disposing IDisposable objects obtained by ContentManager.Load<>() leads to unintended results, as ContentManager caches all loaded resources using a Dictionary<string, object> and after the disposal, future Load<>() invocations would return an invalid, disposed, cached objects. You can't miss the fact that ContentManager has Unload() method, which empties the cache, but there is no possibility for getting rid of certain resources. This pains because the more complex game designs doesn't load all the resources in Game.LoadContent() as it is initially unknown what resources will be used. It is common that game entity templates or game entities themselves decide what to load.
Of course, graphics device can enter the "lost state", which will annihilate all the resources which are located in non-local video and AGP aperature memory, so there is always a possibility of having to deal with LoadContent() for the second time. This is obviously a reason for the phrase "...you can load all non-graphics resources here." in variouse places of XNA documentation. It's hard too keep track of all those COM-calls so I didn't manage to find IDirect3DDevice9::Reset() call, but I believe such an exists. Having many instances of ContentManager distributed through the game logic components and game entities would probably solved the case, but I'm not perfectly sure whether XNA was designed to play it that way.
I'd be glad for some comments. I need them for my thesis. I'd be glad for both reliable links and simple thoughts/suggestions.
PS Sorry to say this, but the forum's editor sucks so hard, that I desire to cout.flush() my entrails.
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
I can't relay any authoritative ContentManager knowledge (yet, anyways), but I do know that in XNA 2.0 the DeviceResourceManager inside GraphicDevice also contains a cache of loaded resources, and there appears to be proper locking performed around all access to that collection. There are examples of loading content from another thread while showing an animated loading screen, but I don't know the limits to the multi-thread content access.
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
Indeed. GraphicsDeviceManager is a very handy brick in Game <-> GraphicsDevice relation. It holds GraphicsDevice which remembers it's resources. It is somehow necessary, as in DirectX 9.0c every resource created as D3DPOOL_DEFAULT needs to be manully destroyed before the device Reset() can be done. It is actually done in GraphicsDevice.Reset(), where DeviceResourceManager.ReleaseAllDefaultPoolResource() kills them (they are recreated afterwards). Disposing resources involves automatic detachment from DeviceResourceManager.
I don't see any locking mechanisms in, for example, GraphicsDevice.CreateTexture(), so it could be convenient to assume that the "thread-safety" of XNA comes out of D3DCREATE_MULTITHREADED flag, but there is no excuse for Xbox (which ignores this flag).
I don't know then, what should I really do with the resources in Game.LoadContent() in case this method is called for the second time. And it's a mystery for me, how secondary work threads can create graphics resources without hesitation. In DX9 you have to schedule every call to be performed on the main thread. But I assume that I can do that, as well as SetData<>(). But am I really limited to a single ContentManager instance per thread in that case?
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
Maerius:I don't see any locking mechanisms in, for example, GraphicsDevice.CreateTexture(), so it could be convenient to assume that the "thread-safety" of XNA comes out of D3DCREATE_MULTITHREADED flag, but there is no excuse for Xbox (which ignores this flag).
Not sure what method you mean by GraphicsDevice.CreateTexture, but the locking I was referencing shows up at the very bottom of Texture2D.CreateTexture(), where graphicsDevice.Resources.AddTrackedObject() is called. Inside that call is a simple lock around the dictionary that tracks those resources... that's all, and I assume not nearly what you're looking for. Eager to hear what you discover!
|
|
-
-
- (13069)
-
Team XNA
-
Posts
8,589
|
Re: Multiple instances of ContentManager on multiple threads
|
You don't need to worry about LoadContent being called more than once. Since 2.0, the framework takes care of lost devices for you (barring a few corner cases if you are using dynamic resources to cache data for long periods of time).
The ContentManager is very customizable. If you want to change its lifespan management / unload policy, you have all the hooks you need to implement whatever other policy you like.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
Shawn Hargreaves on ContentManager: Yep. You have right, I was wrong - you can load assets without caching, managing it's lifespan freely. ContentManager can surely act as a singleton through the entire game. ContentReader class mislead me. I'm really ashamed that I didn't find this solution myself (that's why I came here with my fears and disbeliefs).
Bo Jordan: Of course, I wanted to refer to Texture2D.CreateTexture(). You're right. I see it now. But, as you could predict, it's not what I wanted put on fire. Before DeviceResourceManager.AddTrackedObject() call, there is a call to IDirect3DDevice9::CreateTexture():
| internal protected unsafe void CreateTexture(GraphicsDevice graphicsDevice, int width, int height, int numberLevels, uint modopt(IsLong) usage, _D3DPOOL pool, SurfaceFormat format) |
| { |
| // ... |
| |
| ref IDirect3DTexture9* modopt(IsExplicitlyDereferenced) pinned texturePtrRef = (ref IDirect3DTexture9* modopt(IsExplicitlyDereferenced)) &this.pComPtr; |
| uint num3 = usage & 0x3ffffffd; |
| int num2 = *(((int*) graphicsDevice.pComPtr)) + 0x5c; |
| |
| // IDirect3DDevice9::CreateTexture() |
| int num = *num2[0](graphicsDevice.pComPtr, width, height, numberLevels, num3, _ddformat, pool, texturePtrRef, 0); |
| |
| if (num < 0) |
| { |
| throw Helpers.GetExceptionFromResult((uint) num); |
| } |
| this.CreateObjects(graphicsDevice); |
| graphicsDevice.Resources.AddTrackedObject(this, (void*) this.pComPtr, (uint modopt(IsLong)) pool, base._internalHandle, ref this._internalHandle); |
| } |
| |
lock() occuring in AddTrackedObject() is vital prior to proper multithread work with Dictionary<>, but the same texture creation is my concern. It just takes place on the current thread! I mean, [Understanding XNA Framework Performance] states that I can create resources and feed them with data while the other thread performs the rendering and I believe in this with all my heart, but DirectX SDK teaches that the main thread should only operate on Direct3D API. Even ContentStreaming sample use lock/unlock scheduling. Please, aid me with that and I will go away with a huge smile on my face.
|
|
-
-
- (13069)
-
Team XNA
-
Posts
8,589
|
Re: Multiple instances of ContentManager on multiple threads
|
Creating textures works fine on multiple threads. I'm not sure what makes you think this would be a problem.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
So I see true matter of my problems. I have DirectX SDK (August 2008) and I can read there:
Technical Articles -> Top Issues for Windows Titles
When profiling games, the top hotspots are often found to be related to
entering and leaving critical sections. With the prevalence of multi-core CPUs,
the use of multithreading in games has increased dramatically, and many
implementations rely on heavy use of thread synchronization. The CPU time to
take a critical section even without any contention is quite significant, and
all other forms of thread synchronization are even more expensive. So, care must
be taken to minimize the use of these primitives.
A common source of excessive synchronization in games is the use of D3DCREATE_MULTITHREADED. This flag, while making
Direct3D thread-safe for rendering from multiple threads, takes a very
conservative approach, resulting in high synchronization overhead. Games should
avoid this flag. Instead, architect the engine so that all communication with
Direct3D is from a single thread and any communication between threads is
handled directly. For more information about designing multi-threaded games, see
the article Coding For Multiple Cores on
Xbox 360 and Microsoft Windows.
Technical Articles -> Coding For Multiple Cores on Xbox 360 and Microsoft Windows
Rendering — which may include walking the scene graph or, possibly, only
calling D3D functions — often accounts for 50 percent or more of CPU time.
Therefore, moving rendering to another thread can have significant benefits. The
update thread can fill in some sort of render description buffer, which the
rendering thread can then process.
The game update thread is always one frame ahead of the render thread, which
means that it takes two frames before user actions show up on the screen.
Although this increased latency can be a problem, the increased frame rate from
splitting up the workload generally keeps the total latency acceptable.
In most cases all rendering is still done on a single thread, but it is a
different thread from the game update.
The D3DCREATE_MULTITHREADED flag is sometimes used to allow rendering on one
thread and resource creation on other threads; this flag is ignored on Xbox 360,
and you should avoid using it on Windows. On Windows, specifying this flag
forces D3D to spend a significant amount of time on synchronization, thus
slowing down the render thread.
Shawn Hargreaves: Please, tell me how does it work if you know. Sheesh, I will probably have to rewrite an entire page of my thesis.
|
|
-
-
- (13069)
-
Team XNA
-
Posts
8,589
|
Re: Multiple instances of ContentManager on multiple threads
|
You cannot apply this information from the native DirectX docs to the XNA Framework graphics API. These are not the same thing - although XNA Framework calls eventually do boil down to some number of native calls, this is far from a direct mapping especially on Xbox. The threading semantics are simply not the same in managed as in native.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Multiple instances of ContentManager on multiple threads
|
So say we all. You're the boss - you got the intel. When something exceeds the borders of our comprehension, the only thing we can trust is our faith, despite all the warnings.
Many thanks for solving my puzzle. I really appreciate your help.
|
|
|