devinacker / bsnes-plus

debug-oriented fork of bsnes
http://bsnes.revenant1.net
327 stars 94 forks source link

Sync video on non-60Hz displays #315

Open Selicre opened 2 years ago

Selicre commented 2 years ago

My monitor is set to 120Hz. The emulator assumes that the refresh rate is set to 60Hz and tries to paint a frame every vsync, which makes the game run at >60fps. Audio sync works, but is very jittery.

I would check if sufficient time has elapsed since the last frame, and only then advance the emulation.

devinacker commented 2 years ago

Right now, the recommended (and default) setting is to have audio sync enabled and video sync disabled, which limits the emulation speed to something that doesn't depend on your monitor's refresh rate.

At some point, mainline bsnes redid the video/audio sync system to handle vsync in a better/more flexible way, but it needs to be backported at some point.

Selicre commented 2 years ago

Unfortunately, only audio sync jitters to the point where just playing the game is uncomfortable. I've managed to fix it by adding this timer to Interface::video_refresh:

  typedef std::chrono::duration<double> fsec;
  auto now = std::chrono::steady_clock::now();
  fsec frameTime = now - lastTimePoint;
  fsec target(0.016);
  while (frameTime < target) {
    // video.refresh() and everything else goes here
    now = std::chrono::steady_clock::now();
    frameTime = now - lastTimePoint;
  }

However, it has an odd bug where the emulator won't close properly if it's running. I'm not sure why that's the case, so I can't really PR that.

devinacker commented 2 years ago

That's also going to break the speedup key and emulation speed settings. A proper solution is going to require something better than just jamming a busy loop into GUI code like that.

carmiker commented 2 years ago

The solution that works for me is to figure out the screen's refresh rate and divide the core refresh rate by it as well as collect remainders to determine when to skip or run an extra frame. I do it something like this:

int frames_to_run = 0;
int collector = 0;
while (1) {
    frames_to_run = core_refresh / screen_refresh;
    collector += core_refresh % screen_refresh;

    if (collector >= core_refresh) {
        ++frames_to_run;
        collector -= core_refresh;
    }

    for (int i = 0; i < frames_to_to_run; ++i)
        execute_emulation_frame();
}

If you want to do fast forward you can just add to the number of frames you want to run. You will also need to resample audio, which should be happening anyway so that you can use vsync and still have smooth audio. bsnes, in my experience, lends itself well to libsamplerate.