I just started learning game programming, I choosed to use a fixed-timestep game loop since it's straight forward and easier to understand.
Basically like this:
while (game_is_running) {
input();
update();
render();
}
When VSync is enabled, it runs at the speed of my display, but I want to limit it to run as fast as 60 FPS even on higher refresh rate monitors, so I added frame skip and fps limiter:
static void Main()
{
long frame_count = 0;
long tick_frame_end;
Stopwatch stopwatch = new Stopwatch();
WaitForVSync();
stopwatch.Start(); // hoping to get in sync with display
while (game_is_running) {
frame_count++;
tick_frame_end = GetEndTick(frame_count);
if (stopwatch.ElapsedTicks > tick_frame_end) { // falling behind
continue; // skip this frame
}
input();
update(tick_frame_end);
// render(); // render here when VSync is enabled
while (stopwatch.ElapsedTicks < tick_frame_end) {
// Busy waiting, in real code I used Spinwait to avoid 100% cpu usage
}
render(); // I moved it here because I turned off VSync and I want render to happen at a constant rate
}
}
static long GetEndTick(long frame_count) {
return (long)((double)frame_count * Stopwatch.Frequency / 60); // Target FPS: 60
}
When VSync is off, it can run at a steady 30, 60, 120 (or any value in between) FPS. I was happy with the result, but as soon as I turn VSync back on (and set FPS to 60), I noticed there's a lot frame skipping.
I can run a steady 120FPS when VSync is off with not a single frame dropped, yet when running 60FPS VSync on I have a notably number of frames dropped. It took me quiet a while to nail down the cause of this:
My monitor which I thought was running at 60Hz is actually running at about 59.920Hz (tested with https://www.displayhz.com/)
As time goes on, my internal frame time and monitor frame time would become more and more out of sync, causing many unexpected frame drops. This completely breaks my GetEndTick()
function, which basically breaks everything: the frame skipping logic, the fps limiter, and most importantly update(tick_frame_end)
is broken to.
So the questions are:
1. How can I get the end time of current frame?
In my code above, the end time is calculated by the assumption that there's 60 even frames in every second. If that's true, my internal frame time may not align perfectly to the display frame time, but would still be in-sync which is useful enough.
(Everything was calculated based on the time stamp of frame end, the idea is I generate the image based on the time of frame end, which is just moment before next frame getting displayed on screen.)
I tried to record the time at beginning of the loop (or right after last frame render) and add 1 frame worth of time to it - Doesn't work as the recorded time isn't reliable due to system load, background processes, and many other things. Also I'll need a new method to determine which frame is current frame.
2. If (1.) isn't possible, how can I fix my game loop?
I have read the infamous Fix Your Timestep! by Glenn Fiedler, yet it's about game physics not graphic, and doesn't really provide me an answer to the first question.