A simple game loop runs somewhat like this:
MSG msg;
while (running){
if (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
try{
onIdle();
}
catch(std::exception& e){
onError(e.what());
close();
}
}
(taken from this question)
I’m using windows as an example here for the sake of the example, it could be any platform. Correct me if I’m mistaken, but such a loop would use 100% of a cpu/core (uses 50% of one core on my computer) since it’s always checking for the state of the running variable.
My question is, would it be better (performance-wise) to implement the game loop using the OS’ (in the example Windows) timer functions, setting the required interval according to the desired number of ticks of game logic wanted per second? I ask this because I assume the timer functions use the CPU’s RTC interrupts.
Typically, a game will keep drawing new frames all the time even when the user does not do anything. This would normally happen where you have your onIdle() call. If your game only updates the window/screen when the user presses a button or such, or sporadically in between, then
MSGWaitForMultipleObjectsis a good option.However, in a continuous-animation game, you would normally not want to block or sleep in the render thread at all if you can help it, instead you want to render at maximum speed and rely on vertical sync as a throttle. The reason for that is that timing, blocking, and sleeping is unprecise at best, and unreliable at worst, and it will quite possibly add disturbing artefacts to your animations.
What you normally want to do is push everything belonging to a frame to the graphics API (most likely OpenGL since you said “any platform”) as fast as you can, signal a worker thread to start doing the game logic and physics etc, and then block on SwapBuffers.
All timers, timeouts, and sleep are limited by the scheduler’s resolution, which is 15ms under Windows (can be set to 1ms using
timeBeginPeriod). At 60fps, a frame is 16.666ms, so blocking for 15ms is catastrophic, but even 1ms is still a considerable time. There is nothing you can do to get a better resolution (this is considerably better under Linux).Sleep, or any blocking function that has a timeout, guarantees that your process sleeps for at least as long as you asked for (in fact, on Posix systems, it may sleep less if an interrupt occurred). It does not give you a guarantee that your thread will run as soon as the time is up, or any other guarantees.Sleep(0) under Windows is even worse. The documentation says “the thread will relinquish the remainder of its time slice but remain ready. Note that a ready thread is not guaranteed to run immediately”. Reality has it that it works kind of ok most of the time, blocking anywhere from “not at all” to 5-10ms, but on occasions I’ve seen Sleep(0) block for 100ms too, which is a desaster when it happens.
Using
QueryPerformanceCounteris asking for trouble — just don’t do it. On some systems (Windows 7 with a recent CPU) it will work just fine because it uses a reliable HPET, but on many other systems you will see all kinds of strange “time travel” or “reverse time” effects which nobody can explain or debug. That is because on those systems, the result of QPC reads the TSC counter which depends on the CPU’s frequency. CPU frequency is not constant, and TSC values on multicore/multiprocessor systems need not be consistent.Then there is the synchronization issue. Two separate timers, even when ticking at the same frequency, will necessarily become unsynchronized (because there is no such thing as “same”). There is a timer in your graphics card doing the vertical sync already. Using this one for synchronisation will mean everything will work, using a different one will mean things will eventually be out of sync. Being out of sync may have no effect at all, may draw a half finished frame, or may block for one full frame, or whatever.
While missing a frame is usually not that much of a big deal (if it’s just one and happens rarely), in this case it is something you can totally avoid in the first place.