VB:Tutorials:WINAPI:Timers
From GPWiki
The wiki is now hosted by GameDev.NET at wiki.gamedev.net. All gpwiki.org content has been moved to the new server. However, the GPWiki forums are still active! Come say hello.
[edit] The SituationWhen doing time-based animation, you have several options for tracking the amount of time that has passed between frames:
The first four are not very precise, and the fourth has a tendency to suddenly jump ahead a few seconds to correct itself. So what's a programmer to do? Cheat. [edit] Precision vs. AccuracyPeople often confuse precision and accuracy. Let's start with a couple of definitions:
Being able to measure time down to a nanosecond is therefore very precise. But if that nanosecond value is only updated every three seconds then it's not very accurate at all. And this is the exact problem that we run into when calculating the passage of time in our program. [edit] Comparing the MethodsYou can set the timer control to an interval of one, but it's accuracy is at best 55 ms. The timer function only returns hundredths. GetTickCount reports ms, but is also only accurate to 12-58 (depending on OS) ms, and timeGetTime's resolution seems to vary by machine (15.625 ms resolution on my development rig, about 30 ms on another). QueryPerformanceCounter is incredibly precise...but can suddenly jump ahead several seconds to correct itself, and without warning, thus making it potentially very innacurate (though there are methods around this). On the surface, you might think GetTickCount or timeGetTime might be the way to go. After all, 15.625 ms should be plenty fast, right? Even 30 ms seems good. That is, until you do a little math: 1 sec / 15.625ms = 64 FPS 1 sec / 30.000ms = 33.333 FPS Note that you might actually draw MORE than 64 FPS, but any animation will only be updated 64 FPS, which is actually noticeable. The discrepancy will cause strange hops and jumps in your animation, since you'll get numerous frames where the timers tell you that no time has passed since the last frame. If no time has passed, no changes are made and the exact same frame is drawn again. Envision this scenario: 14 ms has passed since the last frame, so the timer tells you no time has passed at all (not up to 15.625 ms yet). The next time around, Windows took a little bit and an additional 18 ms has passed. The timer tells you that 32 ms has passed since the last frame, and your animation plays catch-up. On a slower machine (IE: 30 ms resolution...or even 58), this jump becomes REALLY noticeable. Given that on a decent machine a single frame could be handled in 2 or 3 ms, we're missing out on a lot of potential smoothness. [edit] The SolutionWhat we really need is a reliable timer with 1 ms precision or so. Fortunately, that's what we have with timeGetTime. What's that? Didn't I say that timeGetTime is only accurate to 15 ms or so? Well, that's true...by default. It's actually accurate to 1 ms, but is updated much less often! Happily, we do have a way to correct this. Public Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long Public Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long These two API calls were surprisingly difficult to find out about, and they're not in the API-Guide. At the beginning of your code, you make this call: timeBeginPeriod 1
This sets the update period of timeGetTime to 1 ms. When your program is exiting, you make this call: timeEndPeriod 1
This restores the update period of timeGetTime to whatever it was when you started. Between these two calls, your calls to timeGetTime are actually accurate to 1 ms (thus matching the precision of the call), and all is well. For most applications, this is more than enough. [edit] Changes for VB.NETVB.NET exposes some new timing methods:
These are in addition to the originally available API calls. TickCount is a virtual clone of the old GetTickCount API call and returns the "amount of time in milliseconds that has passed since the last time the computer was started." Ticks gives you "the number of 100-nanosecond intervals that have elapsed since 12:00 A.M., January 1, 0001." One could also use the old favorites QueryPerformanceCounter and timeGetTime via the standard "Declare Function x Lib y" format, but we like to do things .NET-style. TickCount has millisecond resolution, while Ticks has 0.0001 millisecond resolution. One would think, therefore, that Ticks would be a wonderfully accurate way to go. But it, like every other timer except QPC, doesn't have the accuracy to match its resolution. That is to say, while Ticks specifies nanoseconds, its value is actually only updated every 15 to 30 milliseconds. Thus it really isn't any better. (Applications written for .NET 2.0 or later, should use the System.Diagnostics.Stopwatch class for timing.) The way around this pitfall in VB6 was to call timeBeginPeriod and timeEndPeriod. With those, you could specify an accuracy of 1 ms to match the 1 ms resolution of timeGetTime. Interestingly, an article at Microsoft mentions that D3DPRESENT_INTERVAL_ONE actually sets timeBeginPeriod(1) "to enhance timer resolution". The catch is, timeBeginPeriod doesn't seem to affect TickCount or Ticks accuracy at all. Our solution? We added these lines to our engine: Private Declare Function timeGetTime Lib "winmm.dll" () As Integer Private Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer Private Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Integer) As Integer That's right, we're using the old multimedia API calls for our timing, and they're working like a charm. timeBeginPeriod(1) is called in the engine's New, and timeEndPeriod(1) in the Finalize. timeGetTime is used for timing in between. I (Brother Erryn) originally created this article for my Atomic Monks site. Come by and visit! Categories: VB | Tutorial |


