Sol's Graphics for Beginners

(ch09.cpp)

(prebuilt win32 exe)

09 - Frame-Rate Independence

(Last revised 9. August 2005)

Feel free to make a copy of the project, or continue from the last one.

The game's speed depends on how many iterations we perform per second. To be more precise, the speed depends on the "physics" iterations we perform every second. In traditional graphics applications, most of the cpu power will be spent rendering the graphics, so we can speed up the game by calculating several physics iterations per rendering iteration.

On the other hand, there's no point in updating the graphics if the physics haven't been updated, since nothing on screen has changed. We can save some CPU power by detecting this case and call the system's 'sleep()' function. In SDL, the 'sleep()' is called SDL_Delay().

First we add one new constant and one new variable (add near beginning of the file):

// Physics iterations per second
#define PHYSICSFPS 100

// Last iteration's tick value
int gLastTick;

The PHYSICSFPS is the number of "physics" iterations we wish to do, per second.

In order to know how many times we should iterate the physics, we need to know how much time has passed. Hence, we're storing the tick value of the last iteration.

Next, add the following line to the init() function (as the last thing there):

gLastTick = SDL_GetTicks();

Finally, here's the beginning of the render() function:

void render()
{   
  // Ask SDL for the time in milliseconds
  int tick = SDL_GetTicks();

  if (tick <= gLastTick) 
  {
    SDL_Delay(1);
    return;
  }

  while (gLastTick < tick)
  {
    if (gKeyLeft) gXMov -= THRUST;
    if (gKeyRight) gXMov += THRUST;
    if (gKeyUp) gYMov -= THRUST;
    if (gKeyDown) gYMov += THRUST;

    gXMov *= SLOWDOWN;
    gYMov *= SLOWDOWN;

    gXPos += gXMov;
    gYPos += gYMov;

    gLastTick += 1000 / PHYSICSFPS;
  }

  // Lock surface if needed
  if (SDL_MUSTLOCK(gScreen))
    if (SDL_LockSurface(gScreen) < 0) 
      return;

What we've done here is to first check whether we're still in the same tick as before, and if so, we'll just ask the system to sleep for a while, and return. This will effectively lock the frame rate to be, at maximum, the same as PHYSICSFPS. The additional gain from this is that we're not using 100% of the CPU power all the time on fast computers.

Next, we've wrapped all of the "physics" code inside a while loop. On each iteration, we're adding to the gLastTick. This loop will run, on average, PHYSICSFPS times per second.

Compile and run. You should notice that the ball moves at a different speed (very likely a much more sensible speed than before).

This solution is not perfect, but it's much better than just locking to the frame rate. Some things that you should be aware of:

  • The granularity of SDL_GetTicks() may be something else than 1. That is, if the system updates the tick count every 100 milliseconds, you're locked to 10 FPS. Most modern systems have a sensible granularity of ticks, but there are some (e.g. symbian, win9x) where this may be a problem.
  • At very low framerates (say, under 10 FPS) the controls start to become very inaccurate.
  • If you pause the application somehow (say, break into debugger, or go to pause mode, should you implement one), the next time the physics loop is run, it may run for a long time.
  • After running for 25 days or so, SDL_GetTicks() will wrap around from a very large number to a very large negative one, practically causing the physics loop to stop. This is most likely only a concern for some kind of persistent network game, but it's good to know.

It's possible to think of workarounds for all of the above.

Next we'll look into adding 10 - Joystick Support.

Having problems? Improvement ideas? Just want to discuss this tutorial? Try the forums!

Any comments etc. can be emailed to me.