Accumulator
Accumulator

Reputation: 903

SDL/OpenGL game runs too fast on 144Hz screen; can't use vsync

This is how I handle the game loop:

while (running) {
    diff = duration_cast<milliseconds>(end - start).count();
    start = clock::now();

    dt = diff / (16.0);

    handleInput(); // get input
    update(dt); // game logic
    render(); // render game

    SDL_GL_SwapWindow(window); // swap frame buffer

    end = clock::now();
}

It is intended to be a fixed timestep game locked to 60FPS (it's a re-made emulation of an SNES game) however it runs on 144 timestep on my 144hz screen, making it far too fast. Vsync can't solve this, so what can?

Upvotes: 4

Views: 3670

Answers (2)

taylorcoreyd
taylorcoreyd

Reputation: 21

The SDL_Delay() and SDL_GetTicks() solution is less-than-desirable due to its imprecision.


We could hot-loop:

while (1)
  if stopwatch.time() < 16666 // in usec
    break
  stopwatch.reset()
  input()
  update()
  render()

But this is less-than-desirable as it'll eat up CPU cycles that could be put elsewhere or save energy.


We could act only even frames

while (1)
  if frameCount = 1
    frameCount = 0
  else
    frameCount++
    input()
    update()
    render()
  waitForVSync // aka SDL_GL_SwapWindow(window)

But this only gets us to 72 fps.


If we need to get as close to 60 FPS as possible, it may be best to use a hybrid of the above two approaches: hot-loop on even frames until we've gotten to 16.666 ms.

while (1)
  if frameCount = 1
    frameCount = 0
  else
    while stopWatch.time() < 16666 // in usec
      break
    stopwatch.reset()
    frameCount++
    input()
    update()
    render()
  waitForVSync // aka SDL_GL_SwapWindow(window)

A bonus of this: you don't have to just skip odd frames, you could use them for various things. Perhaps update on odd frames, render on evens? Apply video filters on odd frames? Lots of possibilities.

A note: you should probably be looking at frame-times overall. This solution only works when framerates are at least 120 fps. When framerates drop below 20s, and you want to be more accurate than OS threads will give you, hot-looping is your best bet.


The other alternative is to tap into OS scheduling, which tends to be more accurate than OS thread sleeping. However, accuracy is still not guaranteed for all systems.

Upvotes: 1

Teivaz
Teivaz

Reputation: 5665

Here is a quick example of how game loop can be implemented:

int32_t tickInteval = 1000/FPS; // frequency in Hz to period in ms
uint32_t lastUpdateTime = 0;
int32_t deltaTime = 0;
while (running) { // running condition
    uint32_t currentTime = SDL_GetTicks();
    deltaTime = currentTime - lastUpdateTime;

    int32_t timeToSleep = tickInteval - deltaTime;
    if(timeToSleep > 0)
    {
        SDL_Delay(timeToSleep); // energy saving
    }

    update(deltaTime); // game logic
    lastUpdateTime = currentTime;
}

I would recommend to look close on this topic.


UPD.
One might be concerned with uint32_t overflow. And yes it will overflow. After almost two months of uninterrupted run of the game (49.7 days to be precise). What will happen then? currentTime will be a very small positive integer, lastUpdateTime will be a very large positive integer. But the subtraction of two will not overflow no matter what. Moreover if the difference does not fit into int32_t it will be wrapped around modulo of UINT_MAX + 1 resulting in a small positive integer that will be the exact number of ticks these two values different (with regard to unsigned overflow of one).

Upvotes: 4

Related Questions