Multithreading vs. v-sync - any alternative?

Started by
4 comments, last by don 17 years, 9 months ago
The way I currently have my engine designed, the bulk of the engine runs in the main thread, and the renderer runs in a separate thread. I know there are many complications with multithreading the renderer, but I'm trying to accomplish one main thing: separating my main game loop speed from the monitor's v-sync rate. I need my game to be able to run with v-sync enabled, and still allow my input and physics to run at a fixed 100Hz rate regardless of the renderer refresh rate. However, I've run into yet another complication, using D3D in a separate thread. D3D refuses to allow certain API calls to be made in any thread other than the one containing the main window message loop, which in my case is part of the game loop in the main thread. One thing I've done in the past is converted these API calls in the renderer thread to messages sent to the main thread, where they are received and the API called from there. Very messy. I'm trying to avoid this. I have no great desire to make this work with multithreading, I just need some way for my game loop speed to be independent of v-sync delays. My question is, is there another way to pull this off, one that doesn't use multithreading? I would imagine all of the single-threaded games out there that have fixed timesteps and allow v-sync to be enabled have some way of separating the two. I just don't know how a single thread can prevent v-sync from slowing everything else down. Any ideas?
Advertisement
You can use the D3DPRESENT_DONOTWAIT flag (might have it a bit wrong, from memory :)).

When you call present, if the GPU is waiting for VSYNC, you'll get a WASSTILLDRAWING error from the present call. This allows you to perform extra updates while waiting for VSYNC. If you encounter this case, you'd simply skip all drawing that frame, and only perform a logic update, and then try the Present the next frame.

Since D3D let's the CPU be around 3 frames ahead of what's drawn, theres no real "time frame" in which you have to call Present, so if it get's a bit delayed, you wouldn't skip a frame.

This isn't a perfect solution, and might not give you an exact 100Hz rate on the logic update, but it's simple, and it's a start. At any rate, a multithreaded solution would probably work out better, I think.

Hope this helps :).
Sirob Yes.» - status: Work-O-Rama.
I've seen a commercial game use a multithreaded system where one thread, the renderer owned the device and other threads were using the device in "recording mode". All API calls were done through a batch rendering system, where each frame a command buffer was built up and the renderer sent these commands to DirectX as soon as there was time to do so. That way the renderer was sync'ed with vsync, the main loop was running independently and the other helper rendering threads were recording commands, building dynamic vertex buffers and performing physics calculations.

If you want top performance, multi-threading is the way to go. It allows you to run things in parallel and utilize the maximum CPU power. Running everything in one thread can lead to CPU stalling because the GPU can't keep up (or you have vsync enabled and it must purposely wait). I don't know, maybe there are tricks you can do with a single thread, but I'm just speaking from experience. The only problem with multi-threading is that it's a major pain to get all the synchronization right.
deathkrushPS3/Xbox360 Graphics Programmer, Mass Media.Completed Projects: Stuntman Ignition (PS3), Saints Row 2 (PS3), Darksiders(PS3, 360)
You might find the Coding For Multiple Cores GDC presentation from Microsoft interesting. Its got a XB360/Vista bias, but still got useful info.

The big thing is to insulate D3D into a single thread, you can use D3DCREATE_MULTITHREADED but its generally considered bad practice.

If you're having to use MT access into the renderer then you might want to reconsider the architecture for your application. I find the Model-Viewer design pattern compelling in the past.

As for getting some sort of single-threaded approach... you've descibed a 100hz and 75hz (or whatever) modes - thus you want to set up your core loop to run at 100hz, and then whenever 1.33 cycles (yes, I know thats impossible!) have elapsed you call the renderer. Maybe call your render operation on 3 in every 4 iterations for example...

hth
Jack

<hr align="left" width="25%" />
Jack Hoxley <small>[</small><small> Forum FAQ | Revised FAQ | MVP Profile | Developer Journal ]</small>

Concerning the issue of D3D refusing to allow create/reset/release calls from any but the main thread, I found something interesting. Thanks, jollyjeffers, for the link to the "Coding For Multiple Cores" lecture, it was very informative. Towards the end of the slides, it contains the following brief comment:

Quote:CreateDevice/Reset might have a side-effect on the calling thread's affinity with software vertex processing enabled


While I only have a single core CPU, this got me to thinking, as those were the exact D3D APIs that would crash in the past if I called them from my renderer thread rather than my main thread. I went back and looked at my old test demos, and sure enough they were set to software vertex processing. I switched them to hardware vertex processing and removed the thread messaging hacks I had used to skirt the problem previously, recompiled and ran them. It ran just fine.

Aside from how great this is, to find the cause that prevented me from using D3D in a separate thread, I still don't know WHY this problem exists. I don't have a multi-core CPU, and D3D is only ever created and used in a single thread. So why does software vertex processing cause it to fail, while hardware vertex processing works? Does anyone have an idea what's going on here?


And along similar lines, the DirectX documentation for CreateDevice() has the following comment:

Quote:This method should not be run during the handling of WM_CREATE. An application should never pass a window handle to Direct3D while handling WM_CREATE. Any call to create, release, or reset the device must be done using the same thread as the window procedure of the focus window.


...Which is a bit confusing. Is it strictly talking about the case of using D3D during WM_CREATE (which I don't do), or more generally commenting that, again, D3D must only be used from the main thread containing the window procedure?

Any clarification on these two issues would be great!
CreateDevice/Reset/Release can all cause a display mode change. If this occurs, the OS will broadcast a message to all top-level windows in the system that the display has changed. If the thread in your app that called CreateDevice/Reset/Release isn't the same as the thread that is executing your WndProc, any D3D calls that you make in response to these system messages in your WndProc can cause a deadlock.

As far as calling CreateDevice in WM_CREATE goes, I would think this has to do with D3D needing to subclass your focus window.

See the section in the SDK docs on "Multithreading Issues"

This topic is closed to new replies.

Advertisement