17
\$\begingroup\$

How does one separate game logic and rendering? I know there seem to already be questions on here asking exactly that but the answers are not satisfactory to me.

From what I understand so far the point of separating them into different threads is so that game logic can start running for the next tick immediately instead of waiting for the next vsync where rendering finally returns from the swapbuffer call its been blocking on.

But specifically what data structures are used to prevent race conditions between the game logic thread and the rendering thread. Presumably the rendering thread needs access to various variables to figure out what to draw, but game logic could be updating these same variables.

Is there a de facto standard technique for handling this problem. Maybe like copy the data needed by the rendering thread after every execution of the game logic. Whatever the solution is will the overhead of synchronization or whatever be less than just running everything single threaded?

\$\endgroup\$
5
  • 1
    \$\begingroup\$ I hate to just spam a link, but I think this is a very good read and it should answer all your questions: altdevblogaday.com/2011/07/03/threading-and-your-game-loop \$\endgroup\$
    – Roy T.
    Jul 1, 2012 at 14:18
  • \$\begingroup\$ Another link: software.intel.com/en-us/articles/… \$\endgroup\$ Jul 1, 2012 at 21:04
  • 1
    \$\begingroup\$ Those links give the typical end result that one would like, but don't elaborate on how to do it. Would you copy the entire scene graph each frame or something else? The discussions are too high level and vague. \$\endgroup\$
    – user782220
    Jul 2, 2012 at 6:37
  • \$\begingroup\$ I thought the links were fairly explicit about how much state is copied in each case. eg. (from 1st link) "A batch contains all the information necessary to draw a frame, but does not contain any other game state." or (from 2nd link) "Data still needs to be shared however, but now instead of each system accessing a common data location to say, get position or orientation data, each system has its own copy" (See especially 3.2.2 - State Manager) \$\endgroup\$
    – DMGregory
    Dec 4, 2013 at 2:32
  • \$\begingroup\$ Whoever wrote that Intel article doesn't seem to know that top-level threading is a very bad idea. Nobody does something that stupid. Suddenly the whole application has to communicate through specialized channels and there are locks and/or huge coordinated state exchanges everywhere. Not to mention that there's no telling when the data sent will be processed so it's extremely hard to reason about what the code does. It's much easier to copy the relevant scene data (immutable as ref.counted pointers, mutable - by value) at one point and let the subsystem sort it out however it wants. \$\endgroup\$
    – snake5
    Nov 29, 2015 at 8:07

5 Answers 5

2
\$\begingroup\$

What I have generally seen to handle logic/render thread communication is to triple buffer your data. This way the render thread has say bucket 0 it reads from. The logic thread uses bucket 1 as it's input source for the next frame and writes the frame data to bucket 2.

At sync points, the indices of what each of the three buckets mean are swapped so that the next frame's data is given to the render thread and the logic thread can continue forward.

But there isn't necessarily a reason to split rendering & logic into their respective threads. You can in fact keep the game loop serial and decoupling your render frame rate from the logic step using interpolation. To take advantage of multi-core processors using this kind of setup is where you would have a thread pool that operates on groups of tasks. These tasks can be simply things such as rather than iterate a list of objects from 0 to 100, you iterate the list in 5 buckets of 20 across 5 threads effectively increasing your performance but not over complicating the main loop.

\$\endgroup\$
1
\$\begingroup\$

I've been working on the same thing. The additional concern is that OpenGL (and to my knowledge, OpenAL), and a number of other hardware interfaces, are effectively state machines that do not get along well with being called by multiple threads. I don't think their behavior is even defined, and for LWJGL (possibly also JOGL) it often throws an exception.

What I ended up doing was creating a sequence of threads that implemented a specific interface, and loading them onto a control object's stack. When that object got a signal to shut down the game, it would run through each thread, call an implemented ceaseOperations() method, and wait for them to close before closing itself. Universal data that could be relevant to rendering sound, graphics, or any other data is kept in a sequence of objects that are volatile, or universally available to all threads but never kept in thread memory. There's a slight performance penalty there, but used properly, it has allowed me to flexibly assign audio to one thread, graphics to another, physics to yet another, and so forth without tying them into the traditional (and dreaded) "game loop."

So as a rule, all OpenGL calls go through the Graphics thread, all OpenAL through the Audio thread, all input through the Input thread, and all that the organizing control thread needs to worry about is thread management. Game state is held in the GameState class, which they can all take a look at as they need to. If I ever decide that, say, JOAL has gotten dated and I want to use the new edition of JavaSound instead, I just implement a different thread for Audio.

Hopefully you see what I'm saying, I have a few thousand lines on this project already. If you would like me to try and scrape together a sample, I'll see what I can do.

\$\endgroup\$
1
  • \$\begingroup\$ The problem you'll eventually face is this setup doesn't scale particularly well on a multi-core machine. Yes, there are aspects of a game which generally are best served in their own thread such as audio but much of the remainder of the game loop can actually be managed serially in conjunction with thread pool tasks. If your thread pool supports affinity masks, you can easily queue up say render tasks to be executed on the same thread and have your thread scheduler manage thread work queues and do work stealing as needed giving you multi-threading and multi-core support. \$\endgroup\$
    – Naros
    Dec 16, 2013 at 5:02
1
\$\begingroup\$

Usually, the logic that deals with graphics rendering passes (and their schedule, and when they're gonna run, etc) is handled by a separate thread. However that thread is already implemented (up and running) by the platform you use to develop your game loop (and game).

So in order to obtain a game loop where the game logic updates independently of the graphics refresh schedule you don't need to make extra threads, you just tap into the already existing thread for said graphics updates.

This depends on what platform you're using. For example:

  • if you're doing it in most Open GL related platforms (GLUT for C/C++, JOLG for Java, Android's OpenGL ES related Action) they will usually give you a method/function which is periodically called by the rendering thread, and which you can integrate into your game loop (without making the gameloop's iterations dependent on when that method is called). For GLUT using C, you do something like this:

    glutDisplayFunc(myFunctionForGraphicsDrawing);

    glutIdleFunc(myFunctionForUpdatingState);

  • in JavaScript, you can use Web Workers since there's no multi-threading (that you can reach programmaticaly), you can also use the "requestAnimationFrame" mechanism to get notified when a new graphics rendering will be scheduled, and do your game state updates accordingly.

Basically what you want is a mixed step game loop: you have some code that updates the game state, and which is called inside the main thread of your game, and you also want to periodically tap into (or be called back by) the already existing graphics rendering thread for heads up as to when it's time to refresh the graphics.

\$\endgroup\$
0
\$\begingroup\$

In Java there's the "synchronized" keyword, which locks variables you pass to it to make them threadsafe. In C++ you can achieve the same thing using Mutex. E.g.:

Java:

synchronized(a){
    //code using a
}

C++:

mutex a_mutex;

void f(){
    a_mutex.lock();
    //code using a
    a_mutex.unlock();
}

Locking variables makes sure they don't change while running the code following it, so variables don't get changed by your updating thread while you are rendering them (in fact they DO change, but from the standpoint of your rendering thread they don't). You have to watch out with the synchronized keyword in Java though, since it only makes sure the pointer to the variable/Object doesn't change. The attributes can still change without changing the pointer. To contemplate for this, you could copy the object yourself or call synchronized on all attributes of the object you don't want to change.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Mutexes are not necessarily the answer here because the OP would not only needs to decouple game logic and rendering but they also want to avoid any stalling of one thread's ability to move forward in it's processing regardless of where the other thread may currently be in it's processing loop. \$\endgroup\$
    – Naros
    Dec 5, 2013 at 0:26
0
\$\begingroup\$

This is an old post but it still pops up so wanted to add my 2 cents here.

First list data that should be stored in UI/display thread vs logic thread. In UI thread you might include 3d mesh, textures, light info, and a copy of position/rotation/direction data.

In game logic thread you might need game object size in 3d, bounding primitives (sphere, cube), simplified 3d mesh data (for detailed collisions for example), all attributes affecting movement/behavior, like object velocity, turn ratio, etc., and also position/rotation/direction data.

If you compare two lists you can see that only copy of position/rotation/direction data needs to be passed from logic to UI thread. You might also need some kind of correlation id to determine which game object this data belongs to.

How you do it depends on what language you're working with. In Scala you can use Software Transactional Memory, in Java/C++ some kind of locking/synchronization. I like immutable data so I tend to return new immutable object for every update. This is a bit of memory waste but with modern computers it's not such a big deal. Still if you want to lock shared data structures you can do it. Check out Exchanger class in Java, using two or more buffers can speed things up.

Before you get into sharing data between threads work out how much data you actually need to pass. If you have a octree partitioning your 3d space, and you can see 5 game objects out of 10 objects total, even if your logic needs to update all 10 you need to redraw only the 5 you're seeing. For more reading check out this blog: http://gameprogrammingpatterns.com/game-loop.html This is not about synchronization but it does show how game logic is separated from display and what challenges you need to overcome (FPS). Hope this helps,

Mark

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .