3

Background: I have written a gameboy emulator in F#/.net. In the gameboy, the vblank (~fps) is ~60 Hz and is tied to the game speed, it's therefore important that the emulator runs as close to 60 fps as possible.

Since a modern computer can run my emulator a lot faster than 60 fps I need to slow down the emulator. My current approach for this is to calculate the time between two VBlanks and then wait the remaining amount of time for that vblank period.

The problem comes with how to wait without busy looping the cpu. Since I typically need to wait a couple of ms (sometimes more, sometimes less), the built in Thread.sleep function is not a good choice, since unless you specify 0 wait time, it will sleep at least ~15 ms, which is way to long (and inaccurate). My current approach is to use sleep(0) which really is just a fancy spinlock (where other threads may run, but you still max out the cpu).

What's the proper way to solve this? I was thinking about waiting for a semaphore that's released from a timer, but can a timer provide the time resolution needed? And isn't that just a fancy sleep anyway?

Edit: This was tagged as duplicate of What Thread sleep method is most precise: Monitor.Wait vs System.Timer vs DispatchTimer vs Threading.Timer but I think this is less question of precision, and more to find an appropiate solution for a tight game loop.

Community
  • 1
  • 1
monoceres
  • 4,722
  • 4
  • 38
  • 63
  • No, timer is not a fancy sleep. High precision/multimedia timers would be the way to go to handle this properly without issues – Sami Kuhmonen May 30 '16 at 13:29
  • Possible duplicate of [What Thread sleep method is most precise: Monitor.Wait vs System.Timer vs DispatchTimer vs Threading.Timer](http://stackoverflow.com/questions/10609718/what-thread-sleep-method-is-most-precise-monitor-wait-vs-system-timer-vs-dispat) – ivan_pozdeev May 30 '16 at 13:47
  • Google "NtSetTimerResolution". – Hans Passant May 30 '16 at 14:27

1 Answers1

1

As you've noted, the default system timer isn't quite fast enough - 15.6ms is able to do 60 FPS, but not "constant time per frame" or anywhere close to that.

One solution is using a busy-loop, but yes, for such long wait times, this is a huge waste (funny how one approach is "way too short" while the other is "way too long" :)).

Another option is to change the system timer using timeBeginPeriod (https://msdn.microsoft.com/en-us/library/windows/apps/dd757624(v=vs.85).aspx) - you'll need to use P/Invokes to access this API, but it will allow you to sleep with much greater accuracy. Or better, use a Timer set to ~16.7ms.

If you don't want to mess with that, just make a timer (System.Threading.Timer, not the windows forms one) and set it to 15ms. While this will not give you exactly 60 FPS, it should average at about 64 updates per second, which should be close enough to not be readily noticeable. Since the timer callbacks are detached from the "sleep" (there's no real sleep, but you're not doing CPU work while waiting for the timer to fire), it will not tend to skip or double frames like the windows forms one does. Do note that this implicitly involves multi-threading, so make sure to use synchronization properly.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • Ah, I did not know about timeBeginPeriod, will investigate. Regarding the timer, do you think it will be better to continiously fire of the timer and release a semaphore which is awaited in the vblank function or create a new timer for every new vblank? Another thing that's worth mentioned is that I'm currently porting the emulator to ios/android using xamarin/pcl where I think I can use usleep to achieve shorter sleep times. – monoceres May 30 '16 at 14:23
  • @monoceres You need to use the timer thread - if `Wait` has to wait, it will again use the system timer quantum, which means you'll tend to skip frames, and since the last quantum you had was the timer itself, you will tend to miss more or less *every time*. Instead of having a vblank function, just have your game loop run one step at a time from the timer - it may feel weird, but that's how event-based programming does stuff :) Even `Timer` isn't portable though, AFAIK - for example, I seem to recall that on some nix systems (including Mac OS X IIRC), sleep will take *at most* the wait time. – Luaan May 30 '16 at 14:31
  • @monoceres ... so you may very well fire the timer callbacks much faster than intended (e.g. with no delay at all) on a different system. This is one of the things that simply isn't standardized, and you'll likely need a different approach on each of your platforms :) – Luaan May 30 '16 at 14:33
  • Ah, I see. Running my emulator for single blank period inside the callback may be a little trickier than just lowering the timer quantum. The emulator is actually already run in discrete steps, however it's the cpu that is stepped and the vblank just happens to trigger when enough cycles have been processed. You can see the vblank timing code here: https://github.com/rotmoset/fungbc/blob/1c72a80f743e184c3163cd4a9de7df4eb04c309c/src/emulator/Gpu.fs#L372 – monoceres May 30 '16 at 14:45
  • Anyways, thanks for the info, I think I know how to proceed now, accepting your answer.. :) – monoceres May 30 '16 at 14:48
  • @monoceres Ah, you already have the emulator working, I see :D Quite impressive, I'll have to take a closer look later, it looks like fun. Maybe you could exploit the await-like system in F# to give you a way to "return to timer on vblank" - basically, the timer callback will call a delegate (if available) which would be a "continuation" of your code after the vblank instruction. You'd just need an atomic switch of the delegate to ensure both threads interact well, but that's trivial for references. So your code would appear synchronous, while actually being bound by the timer. – Luaan May 30 '16 at 14:59
  • @monoceres It would still be a non-trivial rewrite, but you'd get a lot of practice with F#'s builders :P And most of the time, you'd really only empower types - no "big" changes. This would be even easier if you avoided mutability, of course, but oh well :D – Luaan May 30 '16 at 15:01
  • 1
    I did try to avoid mutability, but that's easier said than done in an emulator :D Especially since it was my first big F# project, I think I would have done things a lot smarter if I were to redo it, especially with agents and possible async workflows. Speaking of builders, that's the one thing I never seems to wrap my head around in F#, just as I think I understand, something comes along that throws me completely of course.. :) – monoceres May 30 '16 at 15:22
  • 1
    Pulled off an old trick instead.. :) https://github.com/rotmoset/fungbc/blob/master/src/winforms/GameboyWindow.fs#L23 – monoceres May 31 '16 at 07:49
  • @monoceres Ouch :D But yeah, for something like an emulator, it's probably justified. Though I expect there might be differences in how different versions of Windows treat the function exactly - it's unsupported and undocumented, after all. – Luaan May 31 '16 at 07:58