简体   繁体   中英

Taking advantage of multithreading with openGL

Talking in context of a game based on openGL renderer :

Lets assume there are two threads :

  1. that updates the gameLogic and physics etc. for the in game objects

  2. that 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/cocurrency

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?

The usual approach is that the simulation thread after completing a game step commits the state into an intermediary buffer and then signals the renderer thread. Since OpenGL executes asynchronously the render thread should complete rather quickly, thereby releasing the intermediary buffer for the next state.

You shouldn't render directly from the game state anyway, since what the renderer needs to do its works and what the simulation produces not always are the same things. So some mapping may be necessary anyway.

This is quite a general question you're asking. If you ask 10 different people, you'll probably get 10 different answers. In the past I implemented something similar, and here's what I did (after a long series of optimisation cycles).

Your model-update loop which runs on a background thread should look something like this:

while(true)
{
  updateAllModels()
}

As you said, this will cause an issue when the GL thread kicks in, since it may very well render a view based on a model which is half way through being rendered, which can cause UI glitches at the best case.

The straight-forward way for dealing with this would be synchronising the update:

while (true)
{
  synchronized(...)
  {
    updateAllModels();
  }
}

Where the object you synchronize with here is the same object you'll use to synchronize the drawing method.

Now we have an improved method which won't cause glitches in the UI, but the overall rendering will probably take a very severe performance hit, since all rendering needs to wait until all model updates are finished, or vise versa - the models update will need to wait until all drawing is finished.

Now, lets think for a moment - what do we really need to be synchronizing?

In my app (a space game), when updating the models, I needed to calculate vectors, check for collisions and update all the object's positions, rotations, scale, etc.

Out of all these things, the only things the view cares about is the position, rotation, scale and a few other small considerations which the UI needs to know in order to correctly render the game world. The rendering process doesn't care about a game object's vector, the AI code, collision tests, etc. Considering this, I altered my update code to look something like this:

while (true)
{
  synchronized(...)
  {
     updateVisibleChanges(); // sets all visible changes - positions, rotations, etc
  }

  updateInvisibleChanges(); // alters vectors, AI calculations, collision tests, etc  
}

Same as before, we're synchronising the update and the draw methods, but this time, the critical section is much smaller than before. Essentially, the only things which should be set in the updateVisibleChanges method are things which pertain to the position, rotation, scale, etc of the objects which should be rendered. All other calculations (which are usually the most exhaustive ones) are performed afterwards, and do not stop the rendering from occurring.

An added bonus from this method - when you're performing your invisible changes, you can be sure that all objects are in the position they need to be (which is very useful for accurate collision tests). For example, in the method before the last one, object A moves, then object A tests a collision against object B which hasn't moved yet. It is possible that had object B moved before object A tested a collision, there would be a different result.

Of course, the last example I showed isn't perfect - you will still need to hang the rendering method and/or the updateVisible method to avoid clashes, but I fear that this will always be a problem, and the key is minimizing the amount of work you're doing in either thread sensitive method.

Hope this helps :)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM