Coal, Glitchy Update Loops, and Waiting

Image licensed under CC Share Alike, found here

I’ve been fighting a glitchy update loop on Windows that I don’t see on the equivalent update loop running on Linux. After a fair bit of digging, I narrowed it down to a call to Sleep in the Windows main loop. Eliminating the Sleep made the frame update buttery smooth like it is on Linux, but it shot my CPU usage to 100%, and the computer’s fans roar like a vacuum cleaner.

Some research into this issue got me a good answer: a smooth framerate, a cool CPU, and quiet fans. Before the solution is revealed though, I wanted to bring some game programmer dark wisdom into the light where it may shrivel to a crisp and blow away.

Game Programmer Dark Side Wisdom

The most common advice people seem to give on the web is that it’s fine to run the CPU at 100%, let the fans blow, what other use is your computer any way, especially when you have a game going? A cowed minority mentions that they’re on a laptop, and grudgingly it’s admitted that maybe an option for laptop users to run at less than 100% would be an admissible but sad concession.

CPU at 100% is not fine

Forget that! Anyone who thinks running at max power is a good idea should re-watch The Day After Tomorrow, and the compare and contrast the weather report on your evening news. News flash, it’s very likely that the energy you consume to run your game at 100% came from burning something.

Game Programmer Light Side Wisdom

Luckily, the right answer does exist, and thanks to Christian Weis for working it out. The code debugged is posted here for posterity. This version is way stripped down from the original post, but accomplishes what I want; CPU loading is very light, the fans aren’t going, and I’ve got no stuttering. I’ve calculated the wait delay I want in my main loop based on the time remaining to achieve my desired framerate (in my case, 60Hz).

// due to: http://www.gamedev.net/community/forums/topic.asp?topic_id=445787&whichpage=2&#2960603
// allows sleep at fine granularity with no stuttering, and not pegging CPU to 100%
void MySleep( float Seconds )
{
    static HANDLE Timer = CreateWaitableTimer( NULL, FALSE, NULL );

    // Determine time to wait.
    LARGE_INTEGER WaitTime;
    WaitTime.QuadPart = (LONGLONG)(Seconds * -10000000);
    if ( WaitTime.QuadPart >= 0 )
        return;

    // Give up the rest of the frame.
    if ( !SetWaitableTimer( Timer, &WaitTime, 0, NULL, NULL, FALSE ) )
        return;

    DWORD Result = MsgWaitForMultipleObjects
    (
        1,
        &Timer,
        FALSE,
        INFINITE,
        QS_ALLINPUT
    );
}

Consequences!

Please consider adding this or something like it to your main loop. Minimally your fans are going to run less, and your workspace will be a little quieter.

I ask you all to consider carefully the consequences of the choices you make every day, even while you code.

  • That shader is really gorgeous, and I’m personally first in line for the latest and greatest, but could you do it in a lighter way?
  • Do you really need a full rebuild?
  • Would a better search algorithm burn less coal?
  • It’s cool that some ancient clunker still runs and can serve files, but if you turned it off could you buy a newer-faster-bigger server with the power you saved in one year?
  • Did you turn off your monitor before you went home?

Seriously, we’re all in this together.

Post to Twitter Post to Delicious Post to Facebook

  • meshula
    Important note, the technique related in this post relates to Windows XP.

    @Repi mentioned on twitter:
    Yes, Sleep() seems to be much more accurate now on Vista/Win7. Probably related to the HPET

    All this complexity might not be necessary now...
  • Using http://www.geisswerks.com/ryan/FAQS/timing.html trick did made sleep much more precise on XP for me:

    "timeBeginPeriod(1) once, Sleep(1) will actually sleep for 1-2 milliseconds, Sleep(2)
    for 2-3, and so on (instead of sleeping in increments as high as 10-15 ms)."
  • admin
    Charles Bloom is trying WaitableTimers in this post.

    http://cbloomrants.blogspot.com/2009/03/03-02-0...

    Here's his comment. I'm not sure what he means by "doesn't work", since it does work for me... I'll have to give his modification a try.

    ----
    Mmm.. I take that back, maybe WaitableTimer is better. But the code sample on meshula doesn't work. You have to use the priority boost trick :



    int oldpri = GetThreadPriority( GetCurrentThread() );
    SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );

    WaitForSingleObject(Timer,INFINITE);

    SetThreadPriority( GetCurrentThread(), oldpri );


    and that actually seems pretty reliable so far.
  • admin
  • admin
    The code as published by Christian never terminates - it hangs. The routine MsgWaitForMultipleObjects is only going to wait for messages on the timer, so I don't think any other messages will come in; therefore it seemed reasonable to me to wait on the timer, and simply exit at that pont. I don't really care if Sleep exits early, the next time through the main loop will compensate for the time.

    Do you have more information about where it might be necessary to propagate messages? Like I said, I believe messages can only come from the timer; my program is behaving correctly - mouse and keyboard work, as do various window operations.
  • ugasoft
    sorry, why your "MySleep" is different from the "MySleep" posted by the author Christian Weis?

    He checks the "Result" value and he propagates the messages he doesn't handle.

    Are there any reason?
  • Markus
    Good post! Love the blog.
blog comments powered by Disqus

Bad Behavior has blocked 801 access attempts in the last 7 days.