This topic is 5025 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

## Recommended Posts

I decided that some of the things I've been trying to do with my engine are a pain in the ass without multithreading, so I went and took some time today (about 2 hours or so) to implement it. Short story: Multithreading definitely eased some performance bottlenecks in certain areas, but it's kind of tricky to make sure that you don't totally fuck something up. There was a very, very slight performance loss from the initial setup (I went from about 550fps average to about 530), but it was an overall improvement (frame rate never dips below 400fps now, whereas it used to drop dowwn as low as 200 during normal rendering, and to fractions when loading things). Long story: There's one thing to consider when using D3D in a multithreaded environment that's more important than anything else: If you try to create or destroy a resource while D3D has a chance to access it, you're probably going to make your program blow up. Fortunately, it's fairly easy to resolve this. Here's what I did:

//-----------------------------------
//-----------------------------------
{
bool Locked = false;
GraphicsEngine.RequestLock();
while(!Locked)
{
if(GraphicsEngine.GetRenderState(RS_PAUSED))
return;
Sleep(0); // Give the other thread time to respond.
}
}

//-----------------------------------
//-----------------------------------
{
if(GraphicsEngine.IsStateLocked(RS_PAUSED)) // We can't unlock at this point; most likely we're still loading.
return;

bool Locked = true;
GraphicsEngine.RequestUnlock();
while(Locked)
{
if(!GraphicsEngine.GetRenderState(RS_PAUSED))
return;
Sleep(0); // Give the other thread time to respond.
}
}


Is in the main engine. Here's the thread that actually does the rendering:
//-----------------------------------
// GraphicsProc()
// Handles rendering.
//-----------------------------------
DWORD WINAPI GraphicsProc(void* Obj)
{
assert(Obj);
{
if(GraphicsEngine.GetRenderState(RS_SHUTDOWN))
return 0;

if(!GraphicsEngine.GetRenderState(RS_PAUSED))
{
if(GraphicsEngine.IsLocked())
GraphicsEngine.SetRenderState(RS_PAUSED, true);
}
else
{
if(!GraphicsEngine.IsLocked())
GraphicsEngine.SetRenderState(RS_PAUSED, false);
}

if(GraphicsEngine.GetRenderState(RS_PAUSED))
continue;

// Begin rendering
RSLT rslt = GraphicsEngine.BeginScene();
if(rslt != SUCCESS)
{
PostQuitMessage(0);
break;
}

//...snipped drawing routines.

// Render the scene.
rslt = GraphicsEngine.EndScene();
if(rslt != SUCCESS)
{
PostQuitMessage(0);
break;
}
Sleep(0);
}
return 0;
}



##### Share on other sites
First of all, great post [grin]. Now you have me wanting to implement a thread interface in my project. Must...resist...more...features...

I've never really messed with multi-threading, so in your opinion, was it easy to implement? If it only took you 2 hours, it can't be too bad.

Also, how many different threads are you going to make? Right now, you have:

If you ever added a networking interface, you would need a separate thread for that, as well (soley for collecting + processing messages). It could get *very* complicated, where you have a separate thread for everything (ie graphics, sound, networking, input, physics).

It would be cool to have someone write an article about multithreading, and how it can be used in game engines. It could cover a lot of material, I believe.

Our coding styles are shockingly similar...we even use the same function header comment. The only difference I see is that I almost always use HRESULTs.

##### Share on other sites
I note that you have a local boolean in both of those functions which is never used except as a means of keeping a loop going forever. Why not just do while(true) :). Or did you make a mistake while pasting the code?

##### Share on other sites
Quote:
 Original post by circlesoftI've never really messed with multi-threading, so in your opinion, was it easy to implement? If it only took you 2 hours, it can't be too bad.

It was pretty easy, but my Engine layout was already well structured to the point where I didn't have to change a whole lot; if you're directly calling D3D functions from your app, you'll have a hell of a lot more work than I do. I use a rendering queue system (objects never directly draw themselves), and I use a resource manager that controls the creation of all d3d resources (textures, shaders, buffers, etc.) if you do any of this on your own, outside of a centralized location, it will be very, very hard to do this right.

Quote:
 Also, how many different threads are you going to make? Right now, you have:(1) Engine thread(2) Rendering threadIf you ever added a networking interface, you would need a separate thread for that, as well (soley for collecting + processing messages). It could get *very* complicated, where you have a separate thread for everything (ie graphics, sound, networking, input, physics).

Not really. For networking, you're already using unreliable data (thanks UDP!), so it doesn't matter if your updates are irregular. You're only going to get a few milliseconds (at most) delay in the updates, so you're fine.

Sound is going to be on a seperate thread (hard to do streaming without it), as well as networking. The main thread will deal with physics and input, as those are the things that have no hardware support and require the most CPU time.

Quote:
 Our coding styles are shockingly similar...we even use the same function header comment. The only difference I see is that I almost always use HRESULTs.

Yeah, I'm not that fond of HRESULTs (though of course they're required for COM). I prefer using exceptions and error logging than checking for return values; that's kind of a pain in the ass, really.

Also, 9 times out of 10 if an error occurs, I either have some default behavior (if you try to load an invalid texture, it loads a default purple thing that says "INVALID TEXTURE" instead). Most other errors will stop the engine from working right, or will require falling back to some ugly behavior. If a certain feature of D3D isn't supported, I simply have a fallback that automatically picks up. Most other issues are going to be critical failures, and the program has to terminate anyway, so you might as well just log the error and terminate.

##### Share on other sites
Quote:
 Original post by WashuI note that you have a local boolean in both of those functions which is never used except as a means of keeping a loop going forever. Why not just do while(true) :). Or did you make a mistake while pasting the code?

Nope, no mistake; I use level 4 warnings and break on errors, and while(true) generates a warning under level 4 in visual studio, whereas the local booleans do not. I suppose there's a 'cleaner' way to do it (while(!GraphicsEngine.GetRenderState(RS_PAUSED))), but I wasn't quite sure if I'd need to do any checks before exiting, so I left it as is.

##### Share on other sites
I like your idea, it is neat to keep your renderthread locked when updating mutual data. But from personal experience I find multithreading is GENERALLY a bad idea in games. The cost incurred to context switch threads is not worth it unless you have a very specific application such as yours (streaming in music for one). Also synchronizing time between threads, mutex or using semaphores to control access to dual sensitive data is a nightmare even on non-realtime apps (can anyone say Managed C++?)...

And on a single-processor system (most pcs?) you can simulate threading by using access flags and repeatedly polling your WAIT state. This is much faster than using two threads as it spares you the context switch. I had an app which was polling input in a different thread from the engine from the graphics, while I managed to synchronize all three in the end (so that no undefined memory behavior occured), I found it gave me a serious performance drop, especially as I was polling one thread to generate data for another redundantly (say waiting for up arrow to be pressed to move ur ship, but polling a hundred times a second without a press--switching contexts 300 times a second without any point). On a single-threaded system it had no trouble at all. As I said you can reclaim wasted CPU cycles (that you're passing off to the other threads) by using a polling flag.

And I'm not sure but I would imagine that nextgen consoles with multiprocessing (SMP) would have their APIs/kernel autodistribute and manage their tasks (like the Solaris model that 'attaches' kernel threads to applications), so making your own multithreader may be redundant.
All that said, I'm still very impressed by the renderthread locking you have demoed.

##### Share on other sites
Direct3D can handle some multithreading sync on itself by using the D3DCREATE_MULTITHREADED flags when calling CreateDevice. I do not prevents you from using critical sections for your own code but it protected DirectX calls from locking when having a concurrent access.

##### Share on other sites
whaoh seem my reply made bugging something on the forum (got an error message). Please moderator can you delete these ugly repeated posts ? Sorry for the disturbance.

##### Share on other sites
Click post only once, not 9 times. If you get an error, go back to the post, and check before reposting.

##### Share on other sites
thanks, but I clicked only 2 times :(

• 10
• 19
• 14
• 19
• 15