Sign in to follow this  
Gumgo

Frame rate timing

Recommended Posts

I have a few questions about how to do frame rate timing in games.

I have two threads, a main logic thread and a graphics thread (I realize that task based systems are more scalable and better overall but if I keep scrapping what I have I'll never get anywhere). Essentially the way it works is the main logic thread updates objects, then sends these updates to the graphics thread in a queue. I want the logic thread to run at a fixed rate and the graphics thread to run "adaptively". Since I'm not doing interpolation, 60fps for the logic would be ideal.

Here is some pseudocode for my logic game loop:
[code]
baseTime = GET_TIME();
lagTime = 0;
FRAME_TIME_MS = (1000 / 60);
MAX_LAG_MS = 500;

frames = 0;

while (!done) {
frameTime = baseTime + lagTime + frames * FRAME_TIME_MS;
targetTime = frameTime + FRAME_TIME_MS;

RCV_GRAPHICS_THREAD_MESSAGES();
if (no graphics messages have been received for, say, 15 updates) {
// pause so the graphics update queue doesn't keep building up
WAIT( ... );
continue;
}

// run the logic
// send relevant updates to graphics thread
// push frame commit
SEND_RENDER_MSG_TO_GRAPHICS_THREAD(frameTime);

// get the time at the end of the frame
actualTime = GET_TIME();
if (actualTime < targetTime)
// go idle if we have extra time
WAIT( targetTime - actualTime );
else if (actualTime - targetTime > MAX_LAG_MS)
// if we've lagged too long, add to our allowed lag time so we don't keep building up
lagTime += actualTime - (targetTime + MAX_LAG_MS);
++frames;
}
[/code]
And graphics:
[code]
FRAME_PADDING = 50

while (!done) {
RCV_MAIN_THREAD_MESSAGES();
while (more messages) {
process message
if (msg == RENDER) {
if (GET_TIME() > msg.frameTime - FRAME_PADDING)
// only render if we're "caught up"; otherwise, drop the frame (means we fell behind, hence low FPS)
render();
// alert the main thread that the previous frame has been processed
// if the main thread doesn't receive these alerts, it should temporarily freeze so that updates don't build up
SEND_MSG_TO_MAIN_THREAD();
}
}
}
[/code]

You'll notice that I'm basing frame timings off of a base (game start) time instead of the previous frame. This seems like it would be more accurate when trying to synchronize things such as audio for example, since the nth frame timing will always be n*FRAME_TIME_MS into the game and does not depend on the frames that came before. If the game freezes, however, this would be an issue, which is why I introduced lagTime, which adjusts the base time frames are calculated from if the game pauses for too long.

Anyway, my here are my questions:
0) Do you see any problems with this approach? :)

1) What is the best timer to use for the purpose of frame rate timing? I'm running Windows.

2) Obviously since 60fps is 16.6666666666666... ms, I can't represent it exactly, so FRAME_TIME_MS won't be quite right for 60fps. Is this worth worrying about? If so, how is it generally solved?

3) Right now I have WAIT( ... ) in place of a pause when I have extra time left over. What should I use for this? Sleep( ms ) won't work as it's not accurate enough. I could use "spinlock", but that would mean my logic thread is ALWAYS using 100% CPU, and I've seen games that don't seem to do this.

Share this post


Link to post
Share on other sites
1) AFAIK, the performance timer (QueryPerformanceTimer/Frequency).
2) Don't limit yourself to integer millisecond amounts. I used doubles for time.
3) You can use a spinlock with Sleep(0). Sleep(0) only gives up the rest of your time-slice, instead of waiting for a certain number of ms, which keeps it fairly accurate, but still gives the OS enough time so you don't 100% thrash the CPU.

Share this post


Link to post
Share on other sites
Thanks, I will give your suggestions a try!
One more thing concerning 1... I've read about QueryPerformanceTimer having some problems, especially on multicore and systems that use Cool'n'Quiet, such as sometimes counting "backwards". Is this an issue I should be concerned about?

Share this post


Link to post
Share on other sites
[quote name='Gumgo' timestamp='1302723920' post='4798101']
Thanks, I will give your suggestions a try!
One more thing concerning 1... I've read about QueryPerformanceTimer having some problems, especially on multicore and systems that use Cool'n'Quiet, such as sometimes counting "backwards". Is this an issue I should be concerned about?
[/quote]

I've never experienced any problems and I've no idea how much this happens. However, you can set up a control timer to detect sudden jumps in the timer using a lower resolution but more secure timer like GetTickCount (I'm assuming it is, because that's what I do :P).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this