i hate FPS limiters x.x. Average is too far away from the target

Jul 12, 2010 at 11:48am
closed account (236Rko23)

SOLUTION: http://www.cplusplus.com/forum/general/26035/#msg138980

hi there, i have this little huge problem. I'm making some stuff with sdl and i was making a class to act as a fps limiter. but using the common example with SDL_Getticks() and SDL_Delay() in 60fps ended up averaging 57-58 fps, which is too far away from the target for my taste. I managed to make somethime a little more precise but i'm still not satisfied. Also googled but without success...T_T
F*** i spent like a week trying to get this to work.....my brain is gonna explode if i continue like this...

this is what i've done so far...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
class fps_wait{
	private:
	average *ms_avg;
	average *ms_avg2;
	average *diff_avg;
	average *diff_avg2;
	average *diff_avg3;
	double ms;  // 1000/60 = 16.6666[...]
	double fps;		// 60
	double skipped;
	double starttick;
	double nexttick;
	double tmp;
	double tmp2;
	double tmp3;
	double adap_delay;
	double sum;
	char *asd;
	public:
	double lasttick;

	fps_wait(Uint32 fps_in = 60,Uint32 fps_pres = 1){
		init(fps_in, fps_pres);
	}
	~fps_wait(){
		delete ms_avg; delete ms_avg2; delete diff_avg; delete diff_avg2; delete diff_avg3;
		delete asd;
	}
	void init(Uint32 fps_in,Uint32 fps_pres){
		if (fps_in < 1){
			ms = 1000/60;
			fps = 60;}
		else{
			ms = double(1000)/double(fps_in);
			fps = fps_in;}
		starttick = SDL_GetTicks();
		nexttick = 0;
		tmp = 0; tmp2=0; tmp3=0;
		ms_avg = new average(10*ms*fps_pres,true,ms,false);//fps*fps_pres);
		ms_avg2 = new average(10*ms/2*fps_pres,true,ms,false);//fps*fps_pres);
		diff_avg = new average(20,true,0,false);
		diff_avg2 = new average(10,true,0,false);
		diff_avg3 = new average(5,true,0,false);
		lasttick = starttick - ms;
		adap_delay = 0;
		sum = 0;
		skipped =0;
		asd = new char(255); 
		
	}
	void Wait_Next(){
			tmp = SDL_GetTicks();
				
		ms_avg->add(tmp - lasttick);	// average 
		
		nexttick = (((tmp/ms)+1)*ms);
		nexttick = nexttick - tmp;
		lasttick = tmp;			// updates the last position
		

		adap_delay = (ms - ms_avg->davg); // diference between what-should-be and the average miliseconds between each read
		

		sum += decimals(nexttick+adap_delay); // Acumulates those decimals that would be lost because SDL_Delay uses ms so 1.1 ms would acumulate 1ms every 10 1.1ms delays, then when it reaches 1 it adds it to the tota delay
		// decimals() returns only the decimals, like 123.4567 would return 0.4567
		if (sum>=1){adap_delay--;sum--;}
		if (sum<=-1){adap_delay++;sum++;}
		
		sprintf (asd, "tmp - lasttick = %f (%1.2f) | FPS = %f (%1.2f) | delay = %f | sum = %f\n", ms_avg->davg, double(ms), 1000/ms_avg->davg, fps, nexttick+adap_delay, sum); cout<<asd;
		
		if ( nexttick+adap_delay > 0 ){
			SDL_Delay (nexttick+adap_delay); // next + correction
		}else{
			skipped=nexttick+adap_delay;
		}
	}

	double GetAvgFPS(){
		return 1000/ms_avg->davg;
	}
	void Change_FPS_Wait(Uint32 fps_in,Uint32 fps_pres = 1){
		delete ms_avg; delete ms_avg2; delete diff_avg; delete diff_avg2; delete diff_avg3;
		init(fps_in, fps_pres);
	}


};


has anyone had this problem? any better solution? maybe this is a bit noob but i'm learning by myself so i don't have a teacher to ask, or that would teach me all the technic.

btw, if i simulate some "load" (aka SDL_Delay(rand()%10)) it misses the acuracy even if it still has to wait 6.6666ms to reach the 16.6666 for the 60 fps...
Last edited on Jul 13, 2010 at 8:12pm
Jul 12, 2010 at 1:29pm
Are you running under Windows? All yield functions have a granularity of 16 ms (100/6==16.(6)).
Also, why do you need to get it to exactly 60 fps? While I'm at it, why are you limiting the framerate?
Jul 12, 2010 at 2:47pm
Since SDL_Delay will rarely sleep for exactly the time you tell it to, you need to take account any errors to keep your frame rate stable. Also, note that integer division always results in another integer, therefore 1000/60=16.

One possible implementation is this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class FrameScheduler
{
  public:
    FrameScheduler(double fps) {setFPS(fps);}
 
    void setFPS(double newFPS)
    {
      frame=0;
      lastFrame=firstTick=SDL_GetTicks();
      fps=newFPS;
      msPerFrame=1000.0/fps;
    }

    void reset() {setFPS(fps);}

    void waitNextFrame()
    {
      frame++;
      const uint now=SDL_GetTicks();
      if (lastFrame-now>10000)reset(); //this is so standby mode or hibernation doesn't send the program into a frenzy
      lastFrame=now;
      const uint targetTick=firstTick+frame*msPerFrame; //tick count we want to reach (might have passed already if we're behind schedule)
      if (now<targetTick)SDL_Delay(targetTick-now);
    }

  private:
    uint frame;
    uint firstTick;
    uint lastFrame;
    double fps,msPerFrame;
};


Also, why do you need to get it to exactly 60 fps? While I'm at it, why are you limiting the framerate?

So to not unnecessarily waste precious CPU time and all the problems it brings (more heat dissipation => more energy consumption, lower battery life, greatly increased fan noise).
Jul 12, 2010 at 2:59pm
So to not unnecessarily waste precious CPU time
But why exactly 60? Why not 30, for instance?

EDIT:
//this is so standby mode or hibernation doesn't send the program into a frenzy
It doesn't make sense to account for that. Nobody would keep a game running during stand by, and could mess things up during a more common situation, such as the program getting very little CPU time.
Last edited on Jul 12, 2010 at 3:06pm
Jul 12, 2010 at 3:06pm
60 fps is perceived as very smooth, while 30 fps definitely isn't.
~50 fps might be enough already, but 60 fps still ain't a bad value to aim for.

Still, depends on the type of game/application. For some a lower frame rate might not make much of a difference.
Last edited on Jul 12, 2010 at 3:08pm
Jul 12, 2010 at 3:13pm
The smoothness depends on how constant the framerate is, not how high it is (except at very low framerates). Video is rarely shot at framerates higher than 30, but it looks smooth, doesn't it?

And besides, that doesn't answer my question.
using the common example with SDL_Getticks() and SDL_Delay() in 60fps ended up averaging 57-58 fps, which is too far away from the target
Jul 12, 2010 at 3:30pm
but it looks smooth, doesn't it?

Not really. I was actually thinking of TV when I said that. It's not very smooth at all, which becomes quite apparent when objects are moving across the screen and for pans.

When aiming for 60 fps, 57-58 fps indicates that there might be something wrong with the frame scheduling, which might or might not cause choppiness. In any case, if I tell the scheduler I want 60 fps and it goes and produces 57, it's a bug that can easily be avoided.
Jul 12, 2010 at 4:21pm
That's not... unsmoothness.
An object moving at a constant angular velocity relative to a camera filming at a constant framerate would appear to move the exact same distance in each frame. To a subjective observer, this would look smooth. The exception is when an object travels too much distance between each frame. Say, one or two lengths of the object.
An animation only appears choppy if an object that's supposedly moving at a constant rate appears to randomly skip points in the trajectory.
Jul 12, 2010 at 4:37pm
Is a perfectly constant 1 fps still smooth for you? If not, what do you call it if not unsmooth?
The fact remains that 30 fps is much more <insert your word here> than 60 fps.
Jul 12, 2010 at 5:32pm
Like I said,
The exception is when an object travels too much distance between each frame. Say, one or two lengths of the object.


If not, what do you call it if not unsmooth?
I would say "gapfull", but it doesn't really matter because doubling the framerate doesn't fill that many gaps. Okay, an object that moves 1/4 of its length would certainly see some improvement, but it won't make a difference for slow objects (because their movement already looked fine at lower framerates) or for too fast objects (because the still look blurs that cross the screen). And there are ways to improve the look of fast objects without adding frames, such as filling the space between frames with motion blur. If done right, it can look very natural, rather than some sort of infinitesimal stroboscopy of real life.
Jul 13, 2010 at 6:45am
closed account (236Rko23)
Since SDL_Delay will rarely sleep for exactly the time you tell it to, you need to take account any errors to keep your frame rate stable. Also, note that integer division always results in another integer, therefore 1000/60=16.

One possible implementation is this:[...]


thanks for the idea but when i tryed it, it throwed about 61FPS (same as mine), but it will actualy (being 16.66ms for 60fps) do two frames together, so the visible frame rate would be exactly the half, but the cpu utilization would be the same as 60.

yours looks like this
0 now: 203389 | last: 203389 | diff: 0 | DiffAvg: 16.343000 | FPS: 61.188276
0 now: 203422 | last: 203389 | diff: 33 | DiffAvg: 16.359500 | FPS: 61.126563
0 now: 203422 | last: 203422 | diff: 0 | DiffAvg: 16.342500 | FPS: 61.190148
0 now: 203455 | last: 203422 | diff: 33 | DiffAvg: 16.359000 | FPS: 61.128431
0 now: 203455 | last: 203455 | diff: 0 | DiffAvg: 16.342500 | FPS: 61.190148
0 now: 203488 | last: 203455 | diff: 33 | DiffAvg: 16.359000 | FPS: 61.128431
0 now: 203488 | last: 203488 | diff: 0 | DiffAvg: 16.341500 | FPS: 61.193893
0 now: 203489 | last: 203488 | diff: 1 | DiffAvg: 16.342000 | FPS: 61.192021
0 now: 203522 | last: 203489 | diff: 33 | DiffAvg: 16.342000 | FPS: 61.192021
0 now: 203522 | last: 203522 | diff: 0 | DiffAvg: 16.342000 | FPS: 61.192021


mine looks like this
0 now: 44769 | last: 44752 | diff: 17 | DiffAvg: 16.332500 | FPS: 61.227614
0 now: 44786 | last: 44769 | diff: 17 | DiffAvg: 16.332000 | FPS: 61.229488
0 now: 44801 | last: 44786 | diff: 15 | DiffAvg: 16.332000 | FPS: 61.229488
0 now: 44818 | last: 44801 | diff: 17 | DiffAvg: 16.332000 | FPS: 61.229488
0 now: 44835 | last: 44818 | diff: 17 | DiffAvg: 16.332000 | FPS: 61.229488
0 now: 44853 | last: 44835 | diff: 18 | DiffAvg: 16.332000 | FPS: 61.229488
0 now: 44868 | last: 44853 | diff: 15 | DiffAvg: 16.331000 | FPS: 61.233237
0 now: 44883 | last: 44868 | diff: 15 | DiffAvg: 16.331000 | FPS: 61.233237
0 now: 44900 | last: 44883 | diff: 17 | DiffAvg: 16.331500 | FPS: 61.231363
0 now: 44917 | last: 44900 | diff: 17 | DiffAvg: 16.331500 | FPS: 61.231363
0 now: 44934 | last: 44917 | diff: 17 | DiffAvg: 16.331500 | FPS: 61.231363
0 now: 44952 | last: 44934 | diff: 18 | DiffAvg: 16.332000 | FPS: 61.229488


(diif is the diference between each frame, just in case)
(the 0 at the begining is the SDL_Delay(X); between each frame)

and it has one more killer bug. If you add some delay between each frame, it goes nuts....
0 now: 216268 | last: 216235 | diff: 33 | DiffAvg: 16.316500 | FPS: 61.287654
0 now: 216268 | last: 216268 | diff: 0 | DiffAvg: 16.316500 | FPS: 61.287654
...
1 now: 240362 | last: 240360 | diff: 2 | DiffAvg: 1.590000 | FPS: 628.930818 CPU at 100%
1 now: 240363 | last: 240362 | diff: 1 | DiffAvg: 1.590000 | FPS: 628.930818
1 now: 240364 | last: 240363 | diff: 1 | DiffAvg: 1.589500 | FPS: 629.128657
1 now: 240365 | last: 240364 | diff: 1 | DiffAvg: 1.589500 | FPS: 629.128657
...
10 now: 282052 | last: 282041 | diff: 11 | DiffAvg: 10.223000 | FPS: 97.818644
10 now: 282062 | last: 282052 | diff: 10 | DiffAvg: 10.222500 | FPS: 97.823429
10 now: 282072 | last: 282062 | diff: 10 | DiffAvg: 10.222500 | FPS: 97.823429
...
16 now: 342575 | last: 342559 | diff: 16 | DiffAvg: 16.198500 | FPS: 61.734111
16 now: 342591 | last: 342575 | diff: 16 | DiffAvg: 16.198500 | FPS: 61.734111
16 now: 342607 | last: 342591 | diff: 16 | DiffAvg: 16.198500 | FPS: 61.734111



well, what i'm doing is a tiling engine to simulate the moving background from sonic1, to test how much ghost does a lcd has( sonic1 is one of those games that if the lcd has any ghosting you'll see it with ease. That's why i wanted it to be as smooth as posible, because it'll be moving at 1 pixel at a time.

EDIT:btw using linux here, but i want it to be cross platform.
Last edited on Jul 13, 2010 at 6:49am
Jul 13, 2010 at 7:55am
Oops, I should've tested my code...
That one line needs to be:
if (now-lastFrame>10000)reset();

Otherwise it'll be always true. With that change, it's at a steady 60 fps for me.
Jul 13, 2010 at 7:54pm
closed account (236Rko23)
finally!!! it works!!! (mine one) it was such a silly mistake x.x
i was calculating the next frame position from the current one, which had all the errors from before, THAT's why it gave me 61fps.
16.666 + 16.666 = 33.333 (new method, thanks Athar.)
16 + 16 = 32 (old one)
so in the old one, the next frame would be 32 + 16.666 = 48.666 = 48 (saves 0.666 to "sum")
when it should have been 33.333 + 16.666 = 49.999 = 49 (saves 0.999 to "sum")
so i was losing about 1ms and that's why it was running faster
and the "adap_delay" was one of the errors too.
So after cleaning the code, and fixing that, now it's as precise as a swiss watch :D

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class fps_wait{
	private:
		average *ms_avg;
		double 	ms, fps, nexttick;
		Uint32 	starttick, lasttick, frames, tmp;
	public:
		void Change_FPS_Wait(double fps_in,Uint32 fps_prec=1){ delete ms_avg; init(fps_in, fps_prec);}
		fps_wait(double fps_in = 60,Uint32 fps_prec = 1){init(fps_in, fps_prec);}
		~fps_wait(){delete ms_avg;}
		void init(double fps_in,Uint32 fps_prec){
			if (fps_in < 1){ms = 1000.0/60.0;fps = 60;}
			else{ms = 1000.0/fps_in;fps = fps_in;}
			
			starttick = SDL_GetTicks();
			nexttick = tmp = sum = frames = 0;
			ms_avg = new average(fps*fps_prec,true,ms,false);
			lasttick = starttick - ms;
		}
		void Wait_Next(){
			frames++;
			tmp = SDL_GetTicks();
			ms_avg->add(tmp - lasttick);
			lasttick = tmp;
			
			nexttick = (starttick+frames*ms) - tmp;
			
			if ( nexttick > 0 ){ SDL_Delay (int(nexttick));}
			else{ starttick+=-int(nexttick);}
		}
		double GetAvgFPS(){ return 1000/ms_avg->davg;}
};


@Athar
tryed yours with the fix, and now it works flawless, but if it lags and then recovers, those next frames that were behind schedule will go all together with 0 delay instead of 16.666. (which in mine doesn't happen, fixed in the last if-else) But it's almost the same thing so thanks a lot anyway :P
Last edited on Jul 13, 2010 at 8:07pm
Jul 13, 2010 at 8:02pm
Couldn't this be solved with v-sync?
Jul 13, 2010 at 8:11pm
closed account (236Rko23)
you cant have V-Sync in windowed mode, that's the only problem, and i'm not using openGL because the GP2X doesn't support it and i want it to be able to work both in the GP2X and the OpenPandora
Jul 13, 2010 at 8:19pm
I see. I'm only (a bit) familiar with DirectX 9, and even though v-sync doesn't eliminate tearing in windowed mode, it does limit the FPS to the monitor's frequency. However, from your answer, I'm assuming SDL requires OpenGL to support v-sync.
Jul 13, 2010 at 8:32pm
I think doing it with VSync is much smarter to be honest. Not all monitors have a 60Hz refresh rate.
Jul 13, 2010 at 8:51pm
My method assumes that you're skipping the next frame rendering if you're behind schedule.
If you don't, the program will slow down when there's lag and generally run in slow motion on a slow CPU.
If your game logic is fully decoupled from rendering, none of this applies, of course.
Topic archived. No new replies allowed.