Tuesday, August 18, 2015

c++ - Taking advantage of multithreading between game loop and openGL


Talking in context of a game based on openGL renderer :


Let's assume there are two threads :




  1. Updates the game logic and physics etc. for the in game objects




  2. Makes openGL draw calls for each game object based on data in the game objects (that thread 1 keeps updating)





Unless you have two copies of each game object in the current state of the game you'll have to pause Thread 1 while Thread 2 makes the draw calls otherwise the game objects will get updated in the middle of a draw call for that object, which is undesirable!


But stopping thread 1 to safely make draw calls from thread 2 kills the whole purpose of multithreading/concurrency


Is there a better approach for this other than using hundreds or thousands or sync objects/fences so that the multicore architecture can be exploited for performance?


I know I can still use multithreading for loading texture and compiling shaders for the objects which are yet to be the part of the current game state but how do I do it for the active/visible objects without causing conflict with draw and update?


What if I use separate sync lock in each of the game object? This way any thread would only block on one object (ideal case) rather than for the whole update/draw cycle! But how costly is taking locks on each object (game may have a thousand objects)?



Answer



The approach you've described, using locks, would be very inefficient and most likely slower than using a single thread. The other approach of keeping copies of data in each thread would probably work well "speed-wise", but with a prohibitive memory cost and code complexity to keep the copies in sync.


There are several alternative approaches to this, one popular solution for multi-threaded rendering is by using a double-buffer of commands. This consists of running the renderer back-end in a separate thread, where all draw calls and communication with the rendering API are performed. The front-end thread that runs the game logic communicates with the back-end renderer via a command buffer (double-buffered). With this setup, you only have one synchronazation point at the completion of a frame. While the front-end is filling one buffer with render commands, the back-end is consuming the other. If both threads are well balanced, none should starve. This approach is suboptimal however, since it introduces latency in the frames rendered, plus, the OpenGL driver is likely to be already doing this in its own process, so performance gains would have to be carefully measured. It also only uses two cores, at best. This approach has been used in several successful games though, such as Doom 3 and Quake 3



More scalable approaches that make better use of multi-core CPUs are the ones based on independent tasks, where you fire an asynchronous request that gets serviced in a secondary thread, while the thread that fired the request continues with some other work. The task should ideally have no dependencies with the other threads, to avoid locks (also avoid shared/global data like the plague!). A task-based architectures is more usable in localized parts of a game, such as computing animations, AI pathfinding, procedural generation, dynamic loading of scene props, etc. Games are naturally event-full, most kinds of events are async, so it is easy to make them run in separate threads.


Finally, I recommend reading:



No comments:

Post a Comment

Simple past, Present perfect Past perfect

Can you tell me which form of the following sentences is the correct one please? Imagine two friends discussing the gym... I was in a good s...