SDL C Framerate understanding/help.

Started by
5 comments, last by rip-off 14 years, 6 months ago
I am tring to learn and understand SDL and C. So far I managed to get this far with being able to take a 192x128 image with 32x48 sprites in it. Layed out in a 4x4 grid. Then on button press move the character around the screen. What I don't understand atm is the proper way to limit framerate. I have seen people on the forums metion this delta time using sdl.getticks etc... Atm my program is running way to fast. Its running an a AMD Athlon 64 X2 Dual Core 5400+ 2.61 Ghz, ATI Radeon HD 4670. Would someone on the forums explain to me how to add a framerate limiter to my game. I would like to understand it better. Here is what I have so far.

#include <SDL/SDL.h>
#include <SDL/SDL_image.h>


SDL_Surface *screen;
SDL_Event event;
int frame;
int maxframes;
int startanim;

//The frames per second
const int framesSecond = 20;



// Character Struct.
typedef struct{
	int x, y; // Sprite Position
	int scrollx, scrolly;
	int xVel, yVel;
	int dir;
}sakuratype;
sakuratype sakura;


// Draw Background.
background(char* string)
{
SDL_Surface *bkg; 
SDL_Rect src, dest;
 
 bkg = IMG_Load(string);
 if (bkg == NULL) {
 printf("Unable to load background.\n");
 return 1;
}
src.x = 0;
src.y = 0;
src.w = bkg->w;
src.h = bkg->h;
dest.x = 0;
dest.y = 0;
dest.w = bkg->w;
dest.h = bkg->h;
 
SDL_BlitSurface(bkg, &src, screen, &dest);
SDL_FreeSurface(bkg);
}


// Draw Sprite
drawsprite(char* string, int row)
{

SDL_Surface *bkg; 
SDL_Rect src, dest;

 bkg = IMG_Load(string);
 if (bkg == NULL) {
 printf("Unable to load sprite.\n");
 return 1;
}

src.x = frame * 32;
src.y = row * 48;
src.w = 32;
src.h = 48;
dest.x = sakura.x;
dest.y = sakura.y;
dest.w = 32;
dest.h = 48;

SDL_BlitSurface(bkg, &src, screen, &dest);
SDL_Flip(screen);
}

// Move Sakura
movesakura()
{
 if( event.type == SDL_KEYDOWN ) {

  switch( event.key.keysym.sym )
        {
			case SDLK_UP: sakura.yVel -= 32; sakura.dir = 3; break;
            case SDLK_DOWN: sakura.yVel += 32; sakura.dir = 0; break;
            case SDLK_LEFT: sakura.xVel -= 32; sakura.dir = 1; break;
            case SDLK_RIGHT: sakura.xVel += 32; sakura.dir = 2; break;  
			
		}
	}
	//If a key was released
    else if( event.type == SDL_KEYUP )
    {
        //Adjust the velocity
        switch( event.key.keysym.sym )
        {
            case SDLK_UP: sakura.yVel += 32; break;
            case SDLK_DOWN: sakura.yVel -= 32; break;
            case SDLK_LEFT: sakura.xVel += 32; break;
            case SDLK_RIGHT: sakura.xVel -= 32; break;
        }
    }
}

showsakura()
{
sakura.x += sakura.xVel;
sakura.y += sakura.yVel;
drawsprite("Graphics/000-sakura.bmp", sakura.dir);
}

// Main
int main(int argc, char *argv[])
{

/* Initialize SDL's video system and check for errors. */
if (SDL_Init(SDL_INIT_VIDEO) != 0) {
printf("Unable to initialize SDL: %s\n", SDL_GetError());
return 1;
}
/* Make sure SDL_Quit gets called when the program exits! */
atexit(SDL_Quit);

/* Set video mode */
screen = SDL_SetVideoMode(800,600,16,0);
if (screen == NULL) {
printf("Unable to set video mode: %s\n", SDL_GetError());
return 1;
}
screen = SDL_GetVideoSurface();


sakura.x = 0;
sakura.y = 0;
sakura.dir = 0;
frame = 0;
maxframes = 3;
startanim = 0;
int quit;
quit = 0;
Uint8 *keystate;


while( quit == 0 ) {



	while( SDL_PollEvent( &event ) )
	{

	movesakura();

	if( event.type == SDL_QUIT )
            {
                //Quit the program
                quit = 1;
            }
	}

	background("TestBkg.bmp");
	showsakura();

/* Ask SDL to update the entire screen. */
	SDL_Flip(screen);

}

return 0;
}


Advertisement
There are two general ideas. One is to scale the amount of "physics" (movement, acceleration etc) by the amount of time that has passed. On slow machines, we should increase the amount moved and on faster machines we should reduce it. So if we expect each frame to take about 10 milliseconds on our development machine, if the game is taking 20 milliseconds then we move the objects twice as fast. If the game takes 5 milliseconds then we halve the speed. We can measure the amount of time between frames by using a variable to store the previous value of SDL_GetTicks().

The other is to aim for a fixed time step. That is, every time X milliseconds have passed, we choose update the physics once.

Something like this:
// Aiming for a fixed rate of approximately 60 times a second// Every time this number of milliseconds elapses, we run one physics update()const Uint32 PhysicsUpdateTime = 1000 / 60;struct Character{   int x, y;   int vx, vy;};int main(int, char **){    initialisation()    Character character = { 0, 0, 1, 1};    // Timestamp, in milliseconds, when we last updated physics    Uint32 lastUpdateTime = SDL_GetTicks();        bool running = true;    while(running)    {        checkInput();        Uint32 currentTime = SDL_GetTicks();        // The number of milliseconds since the last time we were here        Uint32 difference = currentTime  - lastUpdateTime;        // Note that we use a "while" loop here        // For example, if something happened that caused our game to run slow        // temporarily, then this will "catch up" the physics.        // Basically, we update multiple times.        //        // However, this approach has downsides. Consider if the slow down isn't        // temporary. Maybe it lasts 5 or 10 seconds - that isn't temporary when        // you are thinking in milliseconds!        //        // We could spend more and more time playing catch up        // Instead, if we are running too slow we could consider merely dropping        // physics updates. Not entirely accurate, but the player might not notice =)        while(difference >= PhysicsUpdateTime)        {            difference -= PhysicsUpdateTime;            updatePhysics(character);            lastUpdateTime = currentTime;        }        draw(character);        updateScreen();    }    cleanup();}

A more complete discussion may be found here, but if your "physics" are simple enough (as they appear to be) then this method will suffice.

On an unrelated note, there are some performance issues in your code. Your code loads images every frame, which is extremely inefficient. Consider loading the images once and storing them for the duration of the game.
A simple way of limiting the frame rate could be:

int FRAMERATE;FRAMERATE = 60;int startTimer;int endTimer;int deltaTime;while ( gameRunning ){    //start of game loop        // record the start time of the game loop.    startTimer = SDL_GetTicks();         // do game update stuff here    /*            GAME LOOP UPDATE STUFF    */        // do frame rate limiting stuff here    endTimer = SDL_GetTicks();        deltaTime = endTimer - startTimer;        if ( deltaTime < ( 1000 / FRAMERATE )    {        SDL_Delay( ( 1000 / FRAMERATE ) - deltaTime );     }}


This method will only add in an SDL_Delay() call at the end of the loop based on how long the loop took to run. The FRAMERATE is set by you (in this case I just set it to 60); the SDL_Delay() call shown above will have the game loop wait the 'extra' time in order to give a constant FRAMERATE frame rate.

There is also something called independent frame rate movement. This is similar to the case I showed above. In this scheme, objects may move like so:

object.move( deltaTime * objectVelocity );


In this case the object's velocity is multiplied by the update time, deltaTime. deltaTime is defined similar to as above: the time between the last frame's movement. So what we have is a time x velocity = distance. In this case, no matter how fast the user's pc is (corresponding to a low deltaTime) the object will still move at a constant velocity, objectVelocity. This would look similar to a user on s slower pc (corresponding to a low deltaTime) since it is multiplied by the constant velocity, objectVelocity.

Hopefully, I've explained this well enough to convey the methods.

Also, please check out the following links for a better understanding:

Lazy Foo Lesson on Timers
Lazy Foo Lesson on Frame Rate Regulation
Lazy Foo Lesson on frame rate independent movement
Gaffer on Games: Fix Your Timestep!

The above Lazy Foo links use C++, but you can easily translate the ideas to a C frame work.

Edit: oops, rip-off beat me to it!
Something like SDL_Delay() is a really brittle solution. For example, SDL_Delay() typically has a granularity of about 10 milliseconds - this is defined by the operating system scheduler, not SDL. This means that if you sleep for 1 millisecond, you might end up sleeping for *at least* 10 milliseconds. So, if your total frame time exceeds 6 milliseconds you will probably miss your 60 FPS deadline. Not too bad a problem - until you are running vsynced and drop down to 30 FPS!

There is a trade off though, SDL_Delay() means that your game no longer hogs all the processor time, which is nice for smaller games running on battery devices such as laptops and netbooks.

A hybrid approach can work, using a delay and a timestep aware physics. You calculate the amount of milliseconds remaining at the end of a game loop. If it exceeds 10 milliseconds, you might use SDL_Delay() for a bit. Otherwise just run the loop again. Of course, writing something like this couples your code tightly to the value of 10 milliseconds, which is an implementation defined value, making your code less portable and more likely to break in future.
rip-off, good points as usual.

Quote:Original post by rip-off
Something like SDL_Delay() is a really brittle solution. For example, SDL_Delay() typically has a granularity of about 10 milliseconds - this is defined by the operating system scheduler, not SDL. This means that if you sleep for 1 millisecond, you might end up sleeping for *at least* 10 milliseconds. So, if your total frame time exceeds 6 milliseconds you will probably miss your 60 FPS deadline. Not too bad a problem - until you are running vsynced and drop down to 30 FPS!


I had some problems with SDL_Delay and VSync, as you outlined above.

But, I opted for the hybrid approach since saving batteries on laptop devices, etc., is good for the user of the program.

Interesting point about tying code to an 'implementation defined value'. I think that I will leave this frame rate SDL_Delay() stuff as an option to the user, so that it can be turned off and allow for just the frame rate independent physics.

I just typed in the quick-n-dirty SDL_Delay() approach since it is easiest to implement without a lot of code rewriting as a first approach. But, as you point out it really is a brittle solution. I did spend some time looking for a way to implement a better resolution timer than SDL's, but I gave up since my hybrid approach seems to work well enough for me.

I am sure that the OP will listen to yr good advice since it advocates a more robust solution.
Quote:Original post by rip-off
Something like SDL_Delay() is a really brittle solution. For example, SDL_Delay() typically has a granularity of about 10 milliseconds - this is defined by the operating system scheduler, not SDL. This means that if you sleep for 1 millisecond, you might end up sleeping for *at least* 10 milliseconds. So, if your total frame time exceeds 6 milliseconds you will probably miss your 60 FPS deadline. Not too bad a problem - until you are running vsynced and drop down to 30 FPS!

There is a trade off though, SDL_Delay() means that your game no longer hogs all the processor time, which is nice for smaller games running on battery devices such as laptops and netbooks.

A hybrid approach can work, using a delay and a timestep aware physics. You calculate the amount of milliseconds remaining at the end of a game loop. If it exceeds 10 milliseconds, you might use SDL_Delay() for a bit. Otherwise just run the loop again. Of course, writing something like this couples your code tightly to the value of 10 milliseconds, which is an implementation defined value, making your code less portable and more likely to break in future.


Thanks for info rip-off.

I have a similar problem and asked a few hours ago in a iphone forum.
Can you read it and tell me your opinion?
As signal_ says, battery is important on a embedded device (or the iphone)
What do you think?
http://www.idevgames.com/forum/showthread.php?t=18297

Thanks a lot.

I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhauser gate. All those moments will be lost in time, like tears in rain. Time to die.
Ricardo, it is impolite to "hijack" Fujitaka's thread to get an answer to your question. It would probably be better to have started your own thread on gamedev.net, if you want the advice of the people who frequent this site. That said, someone might find this useful so I'll reply anyway.

As I said, if you want to use SDL_Delay() you should be aware that you can experience some rather unstable frame times. I don't know what the scheduler granularity is on the iPhone, it might be higher than the common value I've heard for desktops (again, approximately 10 milliseconds).

My advice would be to only call SDL_Delay() when there are more than X milliseconds to sleep, and to Delay() for less than the full value. Still use a fixed timestep however, to maintain an even logical framerate. You can determine a good value for X through trial and error.

E.g:
// TODO: tweak me!const Uint32 DELAY_GRANULARITY_ESTIMATE = 10;int main(int, char **){    initialisation()    Character character = { 0, 0, 1, 1};    Uint32 lastUpdateTime = SDL_GetTicks();    Uint32 previousFrameTime = SDL_GetTicks();        bool running = true;    while(running)    {        checkInput();        Uint32 currentTime = SDL_GetTicks();        Uint32 difference = currentTime  - lastUpdateTime;        while(difference >= PhysicsUpdateTime)        {            difference -= PhysicsUpdateTime;            updatePhysics(character);            lastUpdateTime = currentTime;        }        draw(character);        updateScreen();        /*          Measure the time between the previous and next frames.          If it exceeds the granularity threshold, then sleep for          some amount of time.        */        Uint32 currentFrameTime = SDL_GetTicks();        Uint32 ticks = currentFrameTime - previousFrameTime        if(ticks  > DELAY_GRANULARITY_ESTIMATE)        {            SDL_Delay(ticks - DELAY_GRANULARITY_ESTIMATE);        }        previousFrameTime = currentFrameTime;    }    cleanup();}

Based on the granularity being around 10ms, and your figures from that other thread, you should be sleeping for between 12 and 20 ish milliseconds, still saving some battery life but hopefully not quite as at the mercy of the scheduler as you were. We still don't have any real guarantees though.

Let me know if that helps, I haven't tested any of the above code.

This topic is closed to new replies.

Advertisement