3
\$\begingroup\$

Here's my game loop:

uint64 target_fps = 60; 
  uint64 ticks_per_s = SDL_GetPerformanceFrequency(); 
  uint64 target_ticks_per_f = ticks_per_s/target_fps - (ticks_per_s/1000); //aim for (target - 1ms) to err on side of > 60fps 

  uint64 ticks_per_cur_f = 0; 
  uint64 t_last_render = 0; 
  uint64 t_last_update = SDL_GetPerformanceCounter()-target_ticks_per_f; 
  int max_updates = 3; 
  int updates = max_updates-1; 

  while(!done) 
  { 
    while( 
      t_last_update > SDL_GetPerformanceCounter() - target_ticks_per_f && 
      t_last_render > SDL_GetPerformanceCounter() - target_ticks_per_f 
    ) 
    { 
      SDL_Delay(1); 
    } 

    //update 
    updates = 0; 
    while(updates < max_updates && t_last_update < SDL_GetPerformanceCounter() - target_ticks_per_f) 
    { 
      t_last_update += target_ticks_per_f; 
      updates++; 
      pollEvents(); //possibly sets done = true 
      simData(); 
    } 
    if(updates >= max_updates) t_last_update = SDL_GetPerformanceCounter(); 

    if(t_last_render < SDL_GetPerformanceCounter() - target_ticks_per_f) 
    { 
      ticks_per_cur_f = SDL_GetPerformanceCounter()-t_last_render; 
      t_last_render = SDL_GetPerformanceCounter(); 
      render(); 
      SDL_GL_SwapWindow(window); 
      glFinish(); 
    } 
  } 

It's a fixed timestep (to simplify/make consistent any updates).

It essentially just does this:

  • Wait (SDL_Delay()) until enough time has passed requiring either an update or a render
  • While "last update" was > 16ms ago, update() and add 16ms to when "last update" was (maxing out at 3 updates assuming it got really far behind)
  • If "last render" was > 16ms ago, render (and set "last render" to "now")
  • Repeat

Even when I do nothing in update() or render(), it fluctuates anywhere between 58FPS and 62FPS. Why would this be? I understand that SDL_Delay only has a fidelity of 1ms, but I even set "target ticks" to be 1ms less than necessary to try to account for this.

Any ideas?

Thanks!

\$\endgroup\$

2 Answers 2

7
\$\begingroup\$

SDL_Delay() doesn't really have fidelity of any sort; it puts your main thread to sleep, and once your thread is sleeping it's up to the OS to decide when to wake you up again, which could happen at any time. It certainly won't happen on a reliable schedule, or be guaranteed to be "within 1ms" of your nominated time or anything of the sort. It's this unpredictability, I believe, that's leading to the variation you're seeing.

If you want to hit a solid frame rate by locking up your main CPU thread, you either need to spinlock (don't put the thread to sleep; instead just spin inside that 'while' loop until enough time has passed; effectively, just comment out the call to SDL_Delay()), or else turn on vsync so that swapping will delay until vblank.

\$\endgroup\$
-2
\$\begingroup\$

In my understanding you are trying to implement an accurate fps cap. I dealt with this problem yesterday and came up a solution I posted over at stackoverflow below a related question:

How can I implement an accurate (but variable) FPS limit/cap in my OpenGL application?

The most important part is the following code snippet displaying a sample gameloop:

//FPS limit
unsigned int FPS = 120;

//double holding clocktime on last measurement
double clock = 0;

while (cont) {
    //double holding difference between clocktimes
    double deltaticks;

    //double holding the clocktime in this new frame
    double newclock;

    //do stuff, update stuff, render stuff...
    
    //measure clocktime of this frame
    //this function can be replaced by any function returning the time in ms
    //for example clock() from <time.h>
    newclock = SDL_GetTicks();

    //calculate clockticks missing until the next loop should be
    //done to achieve an avg framerate of FPS 
    // 1000 / 120 makes 8.333... ticks per frame
    deltaticks = 1000 / FPS - (newclock - clock);

    /* if there is an integral number of ticks missing then wait the
    remaining time
    SDL_Delay takes an integer of ms to delay the program like most delay
    functions do and can be replaced by any delay function */
    if (floor(deltaticks) > 0)
        SDL_Delay(deltaticks);

    //the clock measurement is now shifted forward in time by the amount
    //SDL_Delay waited and the fractional part that was not considered yet
    //aka deltaticks
    the fractional part is considered in the next frame
    if (deltaticks < -30) {
        /*dont try to compensate more than 30ms(a few frames) behind the
        framerate
        //when the limit is higher than the possible avg fps deltaticks
        would keep sinking without this 30ms limitation
        this ensures the fps even if the real possible fps is
        macroscopically inconsitent.*/
        clock = newclock - 30;
    } else {
        clock = newclock + deltaticks;
    }

    /* deltaticks can be negative when a frame took longer than it should
    have or the measured time the frame took was zero
    the next frame then won't be delayed by so long to compensate for the
    previous frame taking longer. */


    //do some more stuff, swap buffers for example:
    SDL_RendererPresent(renderer); //this is SDLs swap buffers function
}

With this method your fps won't fluctuate anymore, it will be exactly 60fps. From what I understand my method works similarly to yours except I only call SDL_Delay once per frame and use doubles for variables like your target_ticks_per_f because the ms required for each frame to achieve an avg fps is not an integer. I suggest you visit the post i referenced because I make an attempt to explain more details there. I hope this helps you and I hope that it is not frowned upon to reference my own post like this.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Umm, this does not look very good to me. SDL_Delay is very inaccurate, and so is SDL_GetTicks. \$\endgroup\$
    – user35344
    Sep 22, 2016 at 18:58
  • \$\begingroup\$ SDL_GetTicks can only measure integers of milliseconds, yes, but because it is called only once per gameloop we take every single tick into account. SDL_Delay can also only delay by integers of ms, yes, but the fractional part of the target_ticks_per_f is carried over to the next frame so nothing gets lost. Just try it out, it works just beautifully for me :) \$\endgroup\$
    – neop
    Sep 22, 2016 at 19:10
  • 1
    \$\begingroup\$ That's perhaps on your system. Have you actually measured the FPS? And I think you are wat underestimating potential error. Calling something once per frame/more often has also nothing to do with this really. What's important is fetching the current time as accurately as possible. I know this because I used to have code similar to yours, so I don't need to try it out. I removed SDL_Delay, and switched to SDL_GetPerformanceCounter. \$\endgroup\$
    – user35344
    Sep 22, 2016 at 19:15
  • 1
    \$\begingroup\$ The issue is not the fact that SDL_Delay can't take in doubles/floats. The issue is that there is no guarantee for how long it will actually halt the thread, which is pounted out by the other answer. \$\endgroup\$
    – user35344
    Sep 22, 2016 at 19:16
  • 1
    \$\begingroup\$ I used to have what you've suggested, and while it worked on my high performance macbook, it did not work on my low end android. I was finding SDL_Delay to wait up to 10x longer than requested. Also, an "average" of 60fps still looks very choppy if every other frame is 59/61, so should not be strived for. (screen still ends up refreshing at 30fps in such a case) \$\endgroup\$
    – Phildo
    Sep 22, 2016 at 19:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .