Skip to main content
gamedev.net
🔒 Locked

Independent physics and graphics loops

Started by viller 9 replies 2.5k views
Original Post
viller
viller
I'm sorry if this is dumb, I'm a newb. Let's say you want separate independent loops for physics and graphics. How do you do that? With threads? Or server - client between them?
dascandy
dascandy
If a tree falls in the forest but you make 0FPS while it happens - does it still fall smoothly?
Necator
Necator
A simple approach is to render when possible while performing simulation/physics at a certain frequency. Threads are also quite possible to use, but this also requires all threading issues to be taken into account. Reading & writing the same data from different threads needs to be properly handled.

A simple example of a single loop, running simulation/physics at a fixed interval.
void update(float deltaTime){    static float deltaTime2 = 0.0f;    deltaTime2 += deltaTime;    while (deltaTime2 > TICK_TIME)    {        simulationTick();        deltaTime2 -= TICK_TIME;    }    render(deltaTime);}
sybixsus
sybixsus
Neither with threads nor with server-client, generally speaking. There may be solutions which involve those but they're not necessary.

All you need to do is write your loop such that the physics is run with a fixed timestep, and the rendering is done as often as possible. In practice this will mean that the physics is done once per frame unless the machine is struggling to keep up, in which case you may have to run more than one phyics update. The graphics updates will be run more than once per frame, unless the machine is struggling to keep up in which case you'll only run it once.

Since your graphics will be updating more often than the physics in many cases, you'll need to "tween" or interpolate all visual properties ( eg: position, orientation, scale, color, animation, etc. ) between the last physics update and the one before. This will keep everything silky smooth.

That's the theory, but I know my explanation is going to be tricky, and you may not really "get" it until you see some sample code, and I think this page explains it better than I'll ever manage :

http://gafferongames.com/game-physics/fix-your-timestep/
bzroom
bzroom
There's many ways to do this. You could use a revolving data set and a lock between the graphics and physics.

template <typename T>struct RevolvingData{  void LockFront(bool locked)   { /* wait for Revolve to exit then lock front data */ }  void LockBack(bool locked)  { /* wait for Revolve to exit then lock back data */ }  void Revolve() { /*wait until both sides are unlocked                     then toggle swapped*/ }  T &Front() { return swapped ? Data[0] : Data[1]; }    T &Back() { return swapped ? Data[1] : Data[0]; }private:  T Data[2];  bool Locked[2];  bool Swapped;};


Basically the rendering locks its side of the data and then reads it with no worries of it changing, unlocking it when it's done. The physics does exactly the same thing on its side, the only difference is that the physics will call Revolve when it's done computing the physics.

I'm not sure if i got this exactly right, can someone else please critique my suggestion and or elaborate? I was a bit rushed when i wrote this.

Thanks.

Edit: I think a popular extension of this idea is to use multiple data sets for the side that is running faster, lets say our physics runs twice as fast as the graphics (lolz) and is on the front side. We'd want to have a Front and a LastFront. So when physics is done it revolves Front and LastFront.. and then when ever rendering (back) gets around to it, it'll revolve LastFront and Back. This will dramatically reduce the times that physics is waiting on rendering, and it'll allow it to behave like the other guys are suggesting, ie running physics and rendering at less syncronized rates. With this setup the threads will only block in the rare chance that both graphics and physics are trying to revolve the LastFront data set.

So basically the code I pasted is a very novice introductory stage to revolving data. With the simple addition of another dataset you can remove a lot of locking.

[Edited by - bzroom on March 27, 2009 3:10:24 PM]
Prune
Prune
A revolving data set is the way to go, but don't use a simple locking system. Here's the problem. Computation of different shared entities will in general complete in a different order in a multicore system. The renderer should be unblocked whenever there is -any- new computed data ready for that frame to upload to VRAM. Blocking on a given lock is silly if it could use the time to read from another ready buffer. Use semaphores for signalling and explicit waiting (don't directly call WaitForSingleObject since it will do an expensive kernel call even if there will be no wait; use atomic operation to check a user-mode flag first). Swap the data buffers with an atomic operation. Which buffer is ready is pushed onto an atomic lock-free queue.
I'm working on input queues as well, organizing work as tasks for the thread pool. PhysX fits nicely in this since it let's you use your own schdeuling and threads to execute PhysX tasks. PhysX also uses a double buffered shared data.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)
Matias Goldberg
Matias Goldberg
Quote:
Original post by viller
Let's say you want separate independent loops for physics and graphics. How do you do that? With threads? Or server - client between them?

For a newbie, that was the most clever question I've ever heard. You're starting straight in the right way (IMHO)

Already posted, but now as a linky http://gafferongames.com/game-physics/fix-your-timestep/

Apart from that link, separating physics, logic and graphics is not easy. I think it's because we're use to think what we see is what the CPU actually sees. And we code with that in mind.

Thinking with a great abstraction is tough, but can be rewarding after seeing the results.

You may want to look into rendersplit concept

Indeed separating them allows you to thread the engine in a highly scalable way. But threads aren't necessary.

At some point you will need to syncronize the data from physics and graphics, because they aren't 100% independent. Otherwise something like dascandy described would happen, a 0FPS world.

You should really look into splitting physics & graphics, but don't go into threading yet. It's an advanced topic for a newbie.

Basically your single-threaded, independent physics-graphics code should reduce to this:

while( stillRunning ){   if( shouldDoLogic )        updateLogic( 1/25 ); //Locked at 25FPS   updateGraphics( timeSinceLastGraphicsUpdate );}


The link provided at the beggning goes much deep inside and explains how to make it. But from a high level view, it reduces to that

Cheers
Dark Sylinc
Sik_the_hedgehog
Sik_the_hedgehog
Quote:
Original post by Necator
    static float deltaTime2 = 0.0f;    deltaTime2 += deltaTime;

Why not just assign deltaTime to deltaTime2? =P

EDIT: looks like the source tag breaks the quote tag o_O I thought the snippet removed string was just to save space.
Don't pay much attention to "the hedgehog" in my nick, it's just because "Sik" was already taken =/ By the way, Sik is pronounced like seek, not like sick.
bzroom
bzroom
Because it's static, it will only be set to zero the first time, after that it will increment the existing value by deltaTime.

Most people would name deltaTime2 -> accumulator. (and statics are a bad idea)
viller
viller
Wow what great replies! Thanks everyone. That link about fixed timestep was great because of the other nice game physics tutorials.
Prune
Prune
Read the timing section of the PhysX API and take your cue from there if you're doing your own physics. A fixed timestep is used for stability, and the simulation is made to match real-time by executing the right number of these timesteps per frame.
"But who prays for Satan? Who, in eighteen centuries, has had the common humanity to pray for the one sinner that needed it most?" --Mark Twain

~~~~~~~~~~~~~~~Looking for a high-performance, easy to use, and lightweight math library? http://www.cmldev.net/ (note: I'm not associated with that project; just a user)

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.