Frame Rate Demos

By Pete
[email protected]

Introduction

For real time games, you will want to specify the frame rate at which it runs. Unfortunately, there is a huge range of PC hardware and software combinations out there, so you can't assume that everyone will experience your creation at the same speed as it is on your machine. Fortunately, Allegro makes it very easy to do this correctly. It's in the FAQ, but it is surprising how many games don't bother to implement it.

Example code

The code here uses a simple bouncing circle to illustrate the point. Such simple animation should run OK on all but the oldest PCs. I have includes a pretend delay to simulate the effect of doing lots of processing, which will slow down your maximum frame rate. In real life, this would be moving objects, collision detection, AI, and so on. Another chunk of processing time is taken up by composing the screen; drawing backgrounds, objects and characters. It's sometimes useful to split these two activities up, as I will demonstrate later.

The code is written in C, it will compile under DJGPP/DOS using the latest Allegro. I've also checked it under MinGW/WindowsMe. Feel free to use and modify it, subject to the usual Allegro "std_disclaimer.h"
It should also work on other platforms, let me know if not.

Example 0 (t0.c)

This is just the skeleton, with no control. On my system, it achieves 200 fps - yours might be faster or slower. Use the +/- keys on the keypad to introduce a pretend processing delay. You'll see that the frame rate is very dependent on that, as explained in the following diagram. Time is on the horizontal axis.

Slow computer(Slow computer)
Fast computer(Fast computer)
Key

For the fast computer, processing the logic and drawing the graphics just takes less time, so the frame rate goes up.

 

Example 1 (t1.c) Simple Delay

Suppose we want to cut our frame rate right down to 25 fps. Assuming the processing takes no time at all, we just introduce a delay of 1/25 sec, i.e.. 40 ms. we can then allow for the processing time by tweaking the 40 down a bit, until it looks about right. If the processing per frame really is very short compared to the frame rate, this method is OK. In general though, there are three problems:

  1. Any PC with a different speed to yours will play differently (more so, the bigger proportion of the 40 ms is processing time)
  2. If you're running windows / Linux, then other running processes can change your effective speed.
  3. If the amount of stuff you need to do each frame changes, so will the speed. For example, you'd see a slowdown if a lot of enemies come into view at the same time. (If anyone remembers Knight Lore on the ZX Spectrum - groundbreaking game, terrible slowdown problems!)

And there's a further difficulty - as you develop the game, the amount of processing may change. You add more features, or do things more efficiently, or switch between debug (no optimization) and release (optimized) versions. You'll need to keep altering that delay.

Example 2 (t2.c) Vsync control

This is a not recommended, but the VGA does have a vertical interrupt every 50-70ish frames per second, and you can synchronize on this. Just call
vsync();
every frame. I think Allegro simulates the vsync for modes that don't have it. Anyway, it's very crude, as you have to stick to one rate. I would say, don't so this.

Example 3 (t3.c) Ticker control

This is a much better way. You use Allegro's timer facility to set an event every 40 ms. you then do your frame and wait for the event to occur. In the following diagram, the ticker is shown as the black line on the top. If a tick has not occurred, the program will simply wait. So, as long as the total processing does not take more than 40ms, the program will have the correct frame rate regardless of what computer it runs on.

Slow computer(Slow computer)
Fast computer(Fast computer)
Key

In more detail, you need a variable to signal that the event has occurred, anta function to be called, by the system, which updates it.
volatile int ticks;
void ticker(void)
{
   ++ticks;
} END_OF_FUNCTION(ticker);

The volatile tells the compiler that this variable might change at any time in your main program. The END_OF_FUNCTION is a bit of mechanics that lets us lock the function in memory (see the FAQ/Allegro manual for why this is needed).
Next, you need to do that locking, only once, at the start of your program
LOCK_FUNCTION(ticker);
LOCK_VARIABLE(ticks);

Now, start a timer running at 25 fps.
install_int_ex(ticker, BPS_TO_TIMER(25));
You can also do this once, at the start of the program. It doesn't do much harm to leave it running, but make sure you don't keep calling it, or you can run in to problems. The number of timers is limited, so to be super correct, use install_int_ex just before you need it and use remove_int when finished.
The final bit goes in the main loop; it just waits for the event to occur
while (ticks<1)
{
    yield_timeslice();
}
ticks=0;

In other words, wait in an endless loop for ticker to increment ticks. Then reset ticks. The loop continues, and time elapses, until it gets back here.
If you try example 3, you will see that you can alter the pretend delay quite a lot, and the program will stay steady on 25 fps. If you exceed 40 ms (or so) on the delay, the rate will drop. This corresponds to the PC going flat out; there’s too much processing for it to achieve this frame rate.

Example 4 (t4.c) Ticker control with skipping

As a small refinement, you will often find that actually composing an image and blitting it to the screen is the slowest part of the processing. If you don't do this, you can 'catch up' on frames, at the expense of making it jerky. In the following diagram, the graphics time has gone up so that a complete cycle of logic and graphics takes too long. If the tick has already occurred when the program starts to draw the graphics, it skips this stage and does two logic blocks in a row.

Skip computer
Key

Example 4 splits the delay in two, with a fixed portion (25 ms) for the graphics. The extra line
if (ticks==0)
{
    /* draw graphics */
...
}

only does the graphics if we haven't already exceeded our 40 ms. Try the program; you'll see it going jerky as you pass 40 ms. In the limit, there's only enough time to do the logic, and the graphics never get updated.
You can go to town on this, for example drawing more elaborate explosions if the PC is fast enough. Only bother if you've really got nothing better to do!

Example 5 (tx.c) Predictable frames

Another special case is when you can draw frame N without needing to go through previous frames. You can't do it in a game, because what happens must depend on what the player's been doing up to that point. But for a title page, you might have some rotating things, scrolling text, something of that nature. In that case, you can use a ticker as before, but run your loop at full speed, potentially redrawing the same image many times. In the example, you'll see a rotating cross. The image for frame N is the cross, rotated by N "Allegro degrees" - in other words, it rotates a full circle every 256 frames. I could draw frame 99, say, without drawing any others. As you increase the delay, the cross rotates at the same speed, but it is jerkier. For example, if the real frame rate is only 5 fps, it only draws every fifth frame = (25/5). The result: fast and slow PCs see the same thing, but the faster one sees smoother graphics.

Conclusion

The purpose of this demo is to show that you can do frame rates properly without much effort. The result will be that your game can be enjoyed by more people because it always runs at the right speed. Dell is selling a server at the moment with multiple Intel Itanium processors in it. At the moment it costs 56000 US dollars, but I can predict it won't be long before we all have one. And, it will run Allegro! So plan for the future now.

Cheers,
Pete