Recently I've been working on some SDL wrappers for my own use and I've come with a problem I've been having since I first started working with the SDL library.
See, like many others I've been using a timer akin to this one http://lazyfoo.net/SDL_tutorials/lesson14/index.php to regulate my framerate and the movement is never smooth. It doesn't seem like a double buffering or vsync problem, rather the movement is smooth but skips and stutters periodically (actually it has a certain pulse and rhythm to it). Whenever framerate regulation is turn off the pulse dissapears - and of course, everything becomes unusable) so I am mostly sure it has to do with it.
I've put together a little application consisting only of the timer and a red square moving around it. I'll put the code at the end of this post. The code is garbage and does not use any kind of sprite or anything: just know that it stutters the same no matter how much or little stress I put into it (say, add more moving things or just update part of the screen). I've tried with different framerate settings and the pulse always appears (just with a different "tempo"). Needless to say I've tried this on several machines (linux machines, all of them).
Anyway, I've been reading and seen other people having this problem, but no real answer. I, for one, have about the delta timing tecniques and will gladly try it but my issue here is with the timer itself on this very simple application. Why does it stutter?. Is it a problem with the SDL_GetTicks precision and accuracy?. What can I do to improve it?.
So, here's the code for those who want to try it:
#include <SDL/SDL.h>
class fps_control
{
private:
bool apply;
Uint32 ticks_frame_count;
Uint32 ticks_frame_end;
Uint32 ticks_frame_begin;
Uint32 diff;
unsigned int frame_count; //Visible to the outside
unsigned int frame_count_inner; //Keeps count until a second has passed, then overwrites former to give the elapsed frames in a second.
unsigned int frame_length;
unsigned int desired_framerate;
private:
void calculate_frame_length()
{
this->frame_length=1000 / this->desired_framerate;
}
public:
fps_control(unsigned int p_f=30):desired_framerate(p_f), apply(true), ticks_frame_count(0), ticks_frame_end(0), ticks_frame_begin(0), diff(0), frame_count(0), frame_count_inner(0), frame_length(0)
{
this->calculate_frame_length();
}
unsigned int get_frame_count() const {return this->frame_count;}
unsigned int get_desired_framerate() const {return this->desired_framerate;}
void framerate_increase(){this->set_framerate(this->desired_framerate+1);}
void framerate_decrease(){this->set_framerate(this->desired_framerate+1);}
void set_framerate(unsigned int p_param)
{
this->desired_framerate=p_param;
this->calculate_frame_length();
}
void toggle_apply() {this->apply=!this->apply;}
void init()
{
//Call this once before starting, to set the beginning frame count to the initial values.
this->ticks_frame_count=SDL_GetTicks();
this->ticks_frame_begin=this->ticks_frame_count;
this->frame_count_inner=0;
this->frame_count=0;
}
void turn()
{
//Call this when all drawing and logic is done.
if(!this->apply) return; //Only apply when asked for.
//Ask for time before drawing and logic to calculate the difference. Add a frame.
this->ticks_frame_end=SDL_GetTicks();
this->frame_count_inner++;
//Whenever a second has passed, update the visible frame count.
if( (this->ticks_frame_end - this->ticks_frame_count) > 1000)
{
this->frame_count=this->frame_count_inner;
this->frame_count_inner=0;
this->ticks_frame_count=SDL_GetTicks();
}
//Calculate difference and apply delay when needed.
this->diff=this->ticks_frame_end - this->ticks_frame_begin;
if(this->diff < this->frame_length) SDL_Delay(this->frame_length-this->diff);
//Get the beginning time and start again.
this->ticks_frame_begin=SDL_GetTicks();
}
};
class Box
{
private:
SDL_Rect position;
int movement_x;
int movement_y;
public:
Box():movement_x(4), movement_y(4)
{
this->position.x=100;
this->position.y=100;
this->position.w=30;
this->position.h=30;
}
SDL_Rect get_position() {return this->position;}
void move_around()
{
//Won't touch the edges, but doesn't really matter.
if(this->position.x<=0 || this->position.x>=800-this->position.w) this->movement_x=-this->movement_x;
if(this->position.y<=0 || this->position.y>=600-this->position.h) this->movement_y=-this->movement_y;
this->position.x+=this->movement_x;
this->position.y+=this->movement_y;
}
};
bool init_sdl(){return SDL_Init( SDL_INIT_VIDEO ) >= 0;}
void quit_sdl(){SDL_Quit();}
void fill_screen(SDL_Surface * screen)
{
SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,0,0,0));
}
void update_screen(SDL_Surface * screen, Box& box)
{
SDL_Rect b=box.get_position();
SDL_FillRect(screen, &b, SDL_MapRGB(screen->format, 200, 20, 20));
SDL_Flip(screen);
}
int get_input()
{
SDL_Event event;
while(SDL_PollEvent(&event))
{
if(event.type==SDL_QUIT) return 1;
else if(event.type==SDL_KEYDOWN)
{
switch(event.key.keysym.sym)
{
case SDLK_ESCAPE: return 1; break;
case SDLK_SPACE: return 2; break;
case SDLK_UP: return 3; break;
case SDLK_DOWN: return 4; break;
default: break;
}
}
}
return 0;
}
int main(int argc, char **argv)
{
if(!init_sdl())
{
return 1;
}
else
{
//Init things...
// SDL_Surface * screen=SDL_SetVideoMode(800, 600, 16, SDL_DOUBLEBUF | SDL_HWSURFACE); /*SDL_SWSURFACE | SDL_ANYFORMAT);*/
SDL_Surface * screen=SDL_SetVideoMode(800, 600, 16, SDL_SWSURFACE | SDL_ANYFORMAT);
Box box=Box();
fps_control fps=fps_control(60); //Framerate is set to 60.
bool run=true;
int input=0;
SDL_FillRect(screen, NULL, SDL_MapRGB(screen->format,255,0,0));
//Main loop
fps.init();
while(run)
{
input=get_input();
switch(input)
{
case 1: run=false; break;
case 2: fps.toggle_apply(); break;
case 3: fps.framerate_increase(); break;
case 4: fps.framerate_decrease(); break;
default: break;
}
box.move_around();
fill_screen(screen);
update_screen(screen, box);
fps.turn();
}
quit_sdl();
}
}
It is self contained (and again, pure garbage) so just link it with the SDL and try it... Do you see any stuttering pulse here?.
I'll try and apply the delta timing to see if the problem goes away. I just want to know why this happens in such simple applications. Thanks a lot.
Edit: One last thing, I know I probably shouldn't go with SDL_Delay at all (something about the system sleeping for at least the asked value) but I am really puzzled about this behaviour on seemingly powerful machines.
Edit: Corrected a bit on "set_framerate", thanks to Yno for the comments on it.
Edit 2: Since I changed all code to work with a delta time value instead of a set framerate everything has been going much better. I still get some periodic slowdowns (this time each forty something seconds) but I can blame those on the system since the framerate and slowdowns vary wildly depending on the Linux Desktop I am using (say Gnome, Unity, Gnome2d...).
Your class fps_control simply doesn't do the job, it is completely buggy. The framerate_decrease()
function is wrong:
void framerate_decrease(){this->set_framerate(this->desired_framerate+1);}
It should be -1
here. calculate_frame_length()
should be called everytime your desired FPS is changed, that is, into set_framerate()
:
void set_framerate (unsigned int p_param) {
desired_framerate = p_param;
calculate_frame_length ();
}
Also be careful with your division in calculcate_frame_length()
, you might divide by zero:
void framerate_decrease() {
if (desired_framerate > 1)
set_framerate (desired_framerate - 1);
}
Apart from these issues, your class looks uselessly complicated. If you want to restrict your framerate, you have to understand how it is computed. The usual way to go about it is to compute the amount of time required by your frames using SDL_GetTicks()
:
int t;
while (run) {
t = SDL_GetTicks ();
// ...
t = SDL_GetTicks () - t;
}
At the end of the loop, t
is the number of ms your frame took to complete. 1000 / t
is thus your frames per second. If your framerate is too high (that is, t is too small), you have to fill the gap between your current frame time and the desired frame time with a call to SDL_Delay()
. Say FPS
is your FPS limit, the time each frame should take is 1000 / FPS
.
int t;
while (run) {
t = SDL_GetTicks ();
// ...
t = SDL_GetTicks () - t;
// if the framerate is too high
if (t < 1000 / FPS) {
// compute the difference to have a total frame time of 1000 / FPS
SDL_Delay ((1000 / FPS) - t);
}
}
Hope this helps.