Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » fps and timer stuff

Credits go to Derezo, Erkle, Evert, flares, FMC, gering, ImLeftFooted, jamal, Marco Radaelli, Matt Smith, Nunca Ese, Oscar Giner, Rampage, tobing, Trezker, and umperio for helping out!
This thread is locked; no one can reply to it. rss feed Print
 1   2   3 
fps and timer stuff
lucaz
Member #4,194
January 2004

I tested a game sketch with a powerful PC, and it was completly unplayeable because it rans too fast!.

1-How can force the game to run no more than 60fps?
2-Installing more than one timer will slow the program too much?

Rampage
Member #3,035
December 2002
avatar

1. It's in the FAQ

2. I think timers are implemented in a thread, so they shouldn't slow down your system too much.

-R

Nunca Ese
Member #5,262
November 2004
avatar

1.-
Define timer and time rate:

// fps counter
#define FPS_RATE BPS_TO_TIMER(60)
volatile unsigned int fpsTimer = 0;
void FPS_FUNC() {
  fpsTimer++;
} END_OF_FUNCTION(FPS_FUNC);

Use the timer to run the stuff you want 60 times per second:

1 // Reset vars
2 unsigned int lastFrame = 0;
3 fpsTimer = 0;
4 
5 // Install timers
6 install_int_ex(SEC_FUNC, SEC_RATE);
7 install_int_ex(FPS_FUNC, FPS_RATE);
8 
9 bool run = true;
10 do {
11 
12 if(lastFrame < fpsTimer || lastFrame == UINT_MAX) {
13 lastFrame = fpsTimer;
14 
15 // STUFF (logic + render + input?)
16 run = do_stuff();
17
18 }
19 
20 yield_timeslice(); // Save CPU (timer need it)
21 
22 } while(run);
23 
24 // Remove timers
25 remove_int(SEC_FUNC);
26 remove_int(FPS_FUNC);
27...

umperio
Member #3,474
April 2003
avatar

Isn't better to let all the logic update at 60 fps and render at the best speed the computer can? I think human eye can still feel the difference between 60fps and 75fps, so if I can run at 75fps why not?

flares
Member #3,463
April 2003
avatar

i agree with umperio

[nonnus29]Plus the api is crap ... I'd rather chew broken glass then code with those.

FMC
Member #4,431
March 2004
avatar

Umperio said:

Isn't better to let all the logic update at 60 fps and render at the best speed the computer can? I think human eye can still feel the difference between 60fps and 75fps, so if I can run at 75fps why not?

I'm not sure of this but if you only update the logic 60 times per second there is no point (and no graphical improvement) in reblitting the same image over and over... better use this time to yield to the OS or whatever

[FMC Studios] - [Caries Field] - [Ctris] - [Pman] - [Chess for allegroites]
Written laws are like spiders' webs, and will, like them, only entangle and hold the poor and weak, while the rich and powerful will easily break through them. -Anacharsis
Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover. -Mark Twain

Marco Radaelli
Member #3,028
December 2002
avatar

Mithrandir said:

I'm not sure of this but if you only update the logic 60 times per second there is no point (and no graphical improvment) in reblitting the same image over and over... better use this time to yield to the OS or whatever

Well if there's no logic update there's no changes between two frames, so yielding would be nice, but I would replace yield_timeslice() with rest(1)

Nunca Ese
Member #5,262
November 2004
avatar

Yea, Marco and Mithrandir got the answer. No sense in drawing if no logic changes.

For sprite animation its better if you know that you will display 60fps and no more than that, so you can set your animations to the movement rate you want, and test them the same rate that they will be drawn in every computer (every computer that can do your 60fps) IMO.

But it depends on the game. You can also set considerations about the game time if you are making a real time game (time lost due to drawing, ...).

You can read this article from Javier Arévalo (Commandos saga) for nice example.

---
rest(1) didnt work for me, so i moved to yield.

Derezo
Member #1,666
April 2001
avatar

Quote:

No sense in drawing if no logic changes.

There's no down side, from a performance or visual perspective. Use page flipping.

I would highly not recommend using rest(1). On a windows machine, this means it's going to rest for approximately 10ms (and do nothing in that time). If the machine is a slower one, this time is valuable and can lead to loss of logic cycles and/or loss of video frames.

The best solution is to use page flipping with a lock on the fps of your logic. You wont get tearing, and your game will run at a decent speed on all systems. On slower systems, you'll get choppy video but not choppy or slow logic.

"He who controls the stuffing controls the Universe"

Erkle
Member #3,493
May 2003
avatar

If you rerender the same screen again(not just blit or flip it) it may still be rendering when the next logic step should have been running. This leads to small skips in the game that make fast moving objects look jerky.

If the writing above has offended you, you've read it wrong.....fool.
And if you read that wrong it means 'All of the above is opinion and is only to be treated as such.'

lucaz
Member #4,194
January 2004

I changed your example a little, but I don't understand how it works :-/

 while(!key[KEY_ESC]) {
    if(lastFrame >= fpsTimer && lastFrame != UINT_MAX)) {
       yield_timeslice();
       continue;
    }
    lastFrame = fpsTimer;
    // stuff
 }

jamal
Member #3,326
March 2003
avatar

you can lock the fps this way :

1 volatile int speed_counter = 0;
2 
3 void increment_speed_counter()
4 {
5 speed_counter++;
6 }
7 
8 END_OF_FUNCTION(increment_speed_counter)
9 
10 void play_the_game()
11 {
12 LOCK_VARIABLE(speed_counter);
13 LOCK_FUNCTION(increment_speed_counter);
14 
15 install_int_ex(increment_speed_counter, BPS_TO_TIMER(60));
16 
17 while (!game_over) {
18 while (speed_counter > 0) {
19 update_game_logic();
20 speed_counter--;
21 }
22 
23 update_display();
24 // no need to begin a cycle logic drawing if speed_counter=0
25 while(speed_counter == 0){/*lock the FPS*/}
26 }
27 }

I didn't know about the yield_timeslice function, but don't understand what it does, so i will continue with this stuff instead

gering
Member #4,101
December 2003
avatar

Quote:

I would highly not recommend using rest(1). On a windows machine, this means it's going to rest for approximately 10ms (and do nothing in that time). If the machine is a slower one, this time is valuable and can lead to loss of logic cycles and/or loss of video frames.

On a windows machine you schould use Sleep() and timeBeginPeriod(). Don't forget timeEndPeriod().

__________________________________________________________
[ FreeGameDevLibs ]

tobing
Member #5,213
November 2004
avatar

On Windows rest() is implemented using Sleep().

And why should rest(1) wait for 10ms? My measurements indicate that rest(1) waits fpr 1ms average - slight variations possible, so that's not too exact. You have approx. 1000 rest(1) calls within a second.

FMC
Member #4,431
March 2004
avatar

Gering said:

On a windows machine you schould use Sleep()

This is what, AFAIK, yield_timeslice(); does under windows.

Tobing, when people say that rest waits for 10ms it's because (i don't know where i read it) allegro's(or it's windows's fault?) implementation of the timer isn't precise to the millisecond.

BTW what Marco is saying is valid for a WIP (i don't recall which one) where yield_timeslice, under windows, didn't work as expected.

[FMC Studios] - [Caries Field] - [Ctris] - [Pman] - [Chess for allegroites]
Written laws are like spiders' webs, and will, like them, only entangle and hold the poor and weak, while the rich and powerful will easily break through them. -Anacharsis
Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover. -Mark Twain

jamal
Member #3,326
March 2003
avatar

I don't see why use Rest would resolve the problem because, regardless of
the computer speed, sometimes you'll have to rest 10 ms, sometimes 0, ...
I think the only thing you have to do is to look at if logic has to be implemented, so just do nothing while timer_counter==0
just add one line after the drawing should do the trick :

while (!game_over) {
            while (speed_counter > 0) {
               update_game_logic();
               speed_counter--;
            }

            update_display();
     
            while(speed_counter == 0){/*do nothing*/}
         }

Nunca Ese
Member #5,262
November 2004
avatar

lucaz said:

I changed your example a little, but I don't understand how it works :-/
while(!key[KEY_ESC]) {
if(lastFrame < fpsTimer && lastFrame != UINT_MAX)) {
yield_timeslice();
continue;
}
lastFrame = fpsTimer;
// stuff
}

Not the right way. You should use:

while(!key[KEY_ESC]) {
    if(lastFrame >= fpsTimer && lastFrame != UINT_MAX)) {
       lastFrame = fpsTimer;
       // stuff
       
    }
    yield_timeslice();
}

Explanation:

1// Dont stop till user press key_esc
2while(!key[KEY_ESC]) {
3 // First time lastframe = 0 and fpstimer = 0
4 // (60 ticks per second = fps rate you have set for the fpstimer)
5 // Every tick fpsTimer goes ++
6 // so lastframe will be fpstimer - 1
7 // in that case you enter the if sentence to do the stuff
8 // this situation takes place 60 times in a second, so you paint 60frames
9 // If computer is slow and cannot paint 60 frames, pending frames are discarded
10 // lastframe would be < fpstimer 'n' times (where n is the frames lost)
11 // the UINT_MAX condition is to avoid uint overflow (fpstimer would be 0 and
12 // lastframe would be equal to UINT_MAX, so the '<' condition wouldn't work
13 // So the condition must be to paint 60 times
14 if(lastFrame < fpsTimer && lastFrame != UINT_MAX)) {
15 lastFrame = fpsTimer;
16 // stuff
17
18 }
19 // this is needed to prevent cpu from tax death
20 yield_timeslice();
21}

Using draw out of the if condition for painting more than 60 times can be used to scroll, page flipping or this kind of stuff they say, but u really dont need it (maybe my way make the minimum requiriments higher). In my game (in progress 40%) input phase includes logic and scroll calculation, so i dont need to paint the same frame (no tick, no changes). This will probably change when i add the AI, so ai can use the free time to improve its performance.

@lucaz
You can use:

(lastframe < fpstimer) {
    lastframe = fpstimer;
    // stuff
}
or
(fpstimer > 0) {
    fpstimer = 0;
    // stuff
}

I think you can control time better using the first solution.

jamal said:

while (speed_counter > 0) {
update_game_logic();
speed_counter--;
}

If computer is slow at a certain moment speed_counter will grow a lot. If cpu gets free later, cpu will try to do this amount of counter that couldn't do, so you will have a really fast moment on the game. (Dont know if i have explained it well; i mean, that you have to control lost frames in a moment, and no extend them to later; if so, later will have more than 60fps, what can be a bad effect for animations).

jamal said:

// yield vs
while(speed_counter == 0){/*lock the FPS*/}

are you sure this works? this will block the cpu to max usage, so even the timer wont be take in consideration: speed_counter will be 0 forever. IMO. Think i tried it the first time.

jamal
Member #3,326
March 2003
avatar

Quote:

Nunca Ese said:

jamal said:

while (speed_counter > 0) {
update_game_logic();
speed_counter--;
}

If computer is slow at a certain moment speed_counter will grow a lot. If cpu gets free later, cpu will try to do this amount of counter that couldn't do, so you will have a really fast moment on the game. (Dont know if i have explained it well; i mean, that you have to control lost frames in a moment, and no extend them to later; if so, later will have more than 60fps, what can be a bad effect for animations).

I know about this but I don't think it's a good solution to have the logic and the drawing dependant. Imagine you have a computer playing your game at 30 fps. Another computer will play it at 60 fps (which is what you want). if logic and drawing are dependant, the game will be two times slower in the first computer ( I mean here the moving of characters,...), so this won't be the same game. In the other hand ("my way") the game will always be the same, exept a bad drawing if the computer is too slow.

Quote:

Nunca Ese said :
jamal said:
// yield vs
while(speed_counter == 0){/*lock the FPS*/}

are you sure this works? this will block the cpu to max usage, so even the timer wont be take in consideration: speed_counter will be 0 forever. IMO. Think i tried it the first time.

I don't think so because the incrementation of speed_counter is done via a thread so is independant of the main loop

[EDIT] *lock the FPS* means do nothing as I corrected it the second post [/EDIT]

tobing
Member #5,213
November 2004
avatar

The timer precision is given by a certain constant (forgot the name in the moment), and back in Windows 3.1 the accuracy of a timer was very low, something like 18 updates with a second or so. All that has improved, so the timer accuracy should be at least 0.1ms if not better (Windows XP, I do not know about any of the other systems).

I have never used any other function than rest() resp. Sleep() to give time back to the cpu, so I don't know any of the other functions mentioned above - timeBeginPeriod() or yield_timeslice().

This form of waiting:

while(speed_counter == 0){/*lock the FPS*/}

is something I do not like, because it uses 100% cpu to do just nothing. It is not nice, and uses a lot of resources on a notebook, which I believe it shouldn't. So I would use something like

while(speed_counter == 0){rest(1);}

to wait without wasting resources like cpu usage.

Nunca Ese
Member #5,262
November 2004
avatar

jamal said:

Imagine you have a computer playing your game at 30 fps. Another computer will play it at 60 fps (which is what you want). if logic and drawing are dependant, the game will be two times slower in the first computer

This only happens if computer is too slow, which is the same performance in your case. Its normal that it will be two times slower, thats in the case that cpu and video card do the stuff at half the speed from the minimum requirements.
If computer is fast enough to run 60fps it will (thats the concept of minimum requirements).

jamal said:

In the other hand ("my way") the game will always be the same, exept a bad drawing if the computer is too slow.

Hope the comment above applies to this sentence. Mine will have reduced performance only if it cannot draw 60fps, which is equal to "slow computer".

What i mean is that its better using speed_counter = 0; instead of speed_counter--; if you are not going to consider the lost frames.

If computer is slow at a certain moment (54 fps):
your code will do spped_counter to reach 6 (value) (60 ticks - 54 times)
When computer restores its normal speed your code will have speed_counter = 6, so next time it will try to do 66fps, what is not they right vehaviour (you want the computer to run 60fps all the time, not more than that).
6 frames is not a problem, but imagine the case that antivirus suddenly starts to scan (slow computer) or p2p running, or any kind of background program, (downloading tasks, etc.) that can make your game to lose 60 frames (this would make your game to try 120fps next second, animations at double speed). Frames sould be discarded, so speed_counter should be set to 0, losing the 6 frames.

The other point is a behaviour for the 6 frames (look at the javier arevalos' article (link some messages above), there they have a good way to control (using interpolation) the lost frames):

// Easy but not very useful way
if(speed_counter > 0) {
   speed_counter--;
    // losts
   while(speed_counter > 0) {
     // frame lost, do X
     speed_counter--;
   }
}

tobing said:

I have never used any other function than rest() resp. Sleep() to give time back to the cpu, so I don't know any of the other functions mentioned above - timeBeginPeriod() or yield_timeslice().

Neither
- while(speed_counter == 0){/*lock the FPS*/}
nor
- while(speed_counter == 0){rest(1);}
worked for me. speed_counter was always 0 on the first case, and dont remember what, but bad behaviour with rest()
yield_timeslice() gives up the rest of the current scheduler timeslice. The problem is that timeslice size changes from so version, so its like rest(variable) IMO.

Maybe the problem with rest is:
***** start manual info

void rest(unsigned int time);

This function waits for the specified number of milliseconds.

Passing 0 as parameter will not wait, but just yield. This can be useful in order to "play nice" with other processes. Other values will cause CPU time to be dropped on most platforms. This will look better to users, and also does things like saving battery power and making fans less noisy.

Note that calling this inside your active game loop is a bad idea, as you never know when the OS will give you the CPU back, so you could end up missing the vertical retrace and skipping frames. On the other hand, on multitasking operating systems it is good form to give up the CPU for a while if you will not be using it.
****** end manual info

Evert
Member #794
November 2000
avatar

Quote:

Its normal that it will be two times slower, thats in the case that cpu and video card do the stuff at half the speed from the minimum requirements.

Having your game run at half the speed it is meant to run because the computer can't draw all frames is poor design.
It completely changes the dynamics of the game if what was a fast-paced jump'n run game suddenly runs at half speed. Imagine what would happen if you tried to run an RTS over a network where not all computers have the same speed.

It's more important that the logic runs at a constant rate on all computers (in sofar as that is possible) than that you don't skip animation frames because otherwise the animation will be less smooth.

Quote:

Neither
- while(speed_counter == 0){/*lock the FPS*/}
nor
- while(speed_counter == 0){rest(1);}
worked for me. speed_counter was always 0 on the first case

Did you remember to declare speed_counter as volatile?

Quote:

The problem is that timeslice size changes from so version, so its like rest(variable) IMO.

I'm not sure what you mean?

Quote:

Note that calling this inside your active game loop is a bad idea, as you never know when the OS will give you the CPU back, so you could end up missing the vertical retrace and skipping frames.

Look at the code. They're not calling it from the active (meaning logic) loop.
The way I do this myself is to run the logic until it is up to date, then draw a frame if that is nescessary, otherwise just yield/rest.

FMC
Member #4,431
March 2004
avatar

jamal said:

I don't see why use Rest would resolve the problem because, regardless of
the computer speed, sometimes you'll have to rest 10 ms, sometimes 0, ...

I never said to use rest to keep framerate constant; what i was suggesting was to use yield_timeslice to play nice with multitasking. Since in a previous allegro WIP there was a bug that didn't make yield_timeslice work under windows, Marco introduced rest, but only as a mean to yield, not for fps.

[FMC Studios] - [Caries Field] - [Ctris] - [Pman] - [Chess for allegroites]
Written laws are like spiders' webs, and will, like them, only entangle and hold the poor and weak, while the rich and powerful will easily break through them. -Anacharsis
Twenty years from now you will be more disappointed by the things that you didn't do than by the ones you did do. So throw off the bowlines. Sail away from the safe harbor. Catch the trade winds in your sails. Explore. Dream. Discover. -Mark Twain

tobing
Member #5,213
November 2004
avatar

This is my game loop as I use it currently:

1//----------------------------------------------------------------------------
2 
3static volatile int timer_counter = 0; // set to 60 times per second - desired fps
4 
5static void timerCounterUpdater()
6{
7 ++timer_counter;
8}
9 
10static volatile int fps_counter = 0; // set to 1 per second - count fps
11 
12static void fpsCounterUpdater()
13{
14 ++fps_counter;
15}
16 
17static volatile int perf_counter = 0; // set to 1000 per second - measure time used by a certain code fragment
18 
19static void perfCounterUpdater()
20{
21 ++perf_counter;
22}
23 
24//----------------------------------------------------------------------------
25 
26struct FPSInfo
27{
28 int logic;
29 int displayed;
30 int iteration;
31 int advance_cputime;
32 int display_cputime;
33 int show_cputime;
34 int perf_counter_;
35};
36 
37 
38//----------------------------------------------------------------------------
39 
40// frame counter
41static FPSInfo cur = { 0, 0, 0, 0, 0, 0, 0 };
42static FPSInfo displayed = { 0, 0, 0, 0, 0, 0, 0 };
43 
44 
45 
46void GameLoop::loop()
47{
48 while(true)
49 {
50 bool needsRefresh = false;
51 cur_skip = 0;
52 BITMAP*const draw_buffer = display_engine.draw_buffer(); // what I draw onto
53 while( timer_counter > 0 )
54 {
55 if( key[KEY_ESC] )
56 exit(0);
57 
58 // handle user input
59 
60 // Book keeping
61 ++cur.logic;
62 --timer_counter;
63 ++cur_skip;
64 cur.advance_cputime -= perf_counter;
65 advance_game_time(); // advance logic game time
66 cur.advance_cputime += perf_counter;
67 if( cur_skip >= max_skip ) // max_skip is constant, e.g. 3 - max 3 frames may be skipped before the logical fps goes down
68 {
69 timer_counter = 0;
70 break;
71 }
72 needsRefresh = true;
73 }
74 if( needsRefresh )
75 {
76 cur.display_cputime -= perf_counter;
77 // draw screen
78 {
79 acquire_bitmap( draw_buffer );
80 clear_bitmap( draw_buffer );
81 // do the drawing here
82 release_bitmap( draw_buffer );
83 }
84 cur.display_cputime += perf_counter;
85 
86 cur.show_cputime -= perf_counter;
87 // Display double_buffer
88 needsRefresh = false;
89 display_engine.display_draw_buffer();
90 cur.displayed++;
91 cur.show_cputime += perf_counter;
92 }
93 else
94 {
95 if( rest_dur_ms > 0 )
96 rest( rest_dur_ms ); // rest for given amount of milliseconds if non-zero
97 }
98 cur.iteration++;
99 if( fps_counter > 0 )
100 {
101 displayed = cur;
102 displayed.perf_counter_ = perf_counter;
103 cur.logic = cur.iteration = cur.displayed = 0;
104 cur.advance_cputime = cur.display_cputime = cur.show_cputime = 0;
105 fps_counter = 0;
106 perf_counter = 0;
107 }
108 }
109}

including performance measurements. The basic structure is from the german book I had recommended some days ago, the important things I added are rest() and performance measure.

Feel free to comment on this code, I would be glad to improve it where I can.

jamal
Member #3,326
March 2003
avatar

Quote:

Mithrandir said :
I never said to use rest to keep framerate constant; what i was suggesting was to use yield_timeslice to play nice with multitasking. Since in a previous allegro WIP there was a bug that didn't make yield_timeslice work under windows, Marco introduced rest, but only as a mean to yield, not for fps.

sorry Mithrandir I misunderstood you :)

Nunca Ese :

I think Evert explained it well : Logic has to run at a constant rate on every computer.

Matt Smith
Member #783
November 2000

Quote:

Isn't better to let all the logic update at 60 fps and render at the best speed the computer can? I think human eye can still feel the difference between 60fps and 75fps, so if I can run at 75fps why not?

One reason why not is beat frequencies. Your example would give an artifact at 15 Hz, which is acceptable IMO, but an extreme example would be logic at 60Hz with display at 61Hz, which in a slow scroll would produce a visible jerkiness at 1Hz intevals. Other less obvious artifacts can cause trouble at the epilepsy-inducing frequencies (around 3-7Hz) which are uncomfortable for most viewers.

The best policy would probably be to allow only certain fixed frequencies for displaying. You could even make it explicit by coding each display freq as follows (this is for the 4:5 relationship of 60 and 75Hz)

     loop:update_logic();
         update_screen();  //with vsync
         update_logic();
         update_screen();
         update_logic();
         update_screen();
         update_logic();
         update_screen();
         update_screen();
         jmp loop

 1   2   3 


Go to: