• Advertisement
Sign in to follow this  

Win32 Game Loop For Fixed Frame Rate

This topic is 3793 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I'm trying my hand at Win32 OpenGL programming and can't seem to get my game loop to work properly. It seems to only draw the first frame and then never draw again unless I move the window. Any ideas?

	DWORD target = (DWORD)(1000 / targetFrameRate);

	while (!done)
	{
		DWORD starting_point = GetTickCount();

		if (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT || 
				msg.message == WM_DESTROY || 
				msg.message == WM_CLOSE)
			{
				Exit();
				break;
			}

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		Update((float)(GetTickCount() - starting_point) / 1000.0f);

		Draw();
		SwapBuffers(deviceContext);

		while ((GetTickCount() - starting_point) < target);
	}

Share this post


Link to post
Share on other sites
Advertisement
There are a few things wrong:
  1. This line:
        Update((float)(GetTickCount() - starting_point) / 1000.0f); 
    passes in only the time spent processing up to 1 message and does not include the time spent in the spin lock.
  2. You should not process only 1 message per frame. That can lead to problems. What happens if two messages are generated every frame? You need to process all the messages every frame.
  3. You should only exit when WM_QUIT is received. WM_DESTROY and WM_CLOSE are part of cleaning up and you need to let them be processed normally.

Share this post


Link to post
Share on other sites
Thanks for those tips. I'm still getting used to not only Win32 programming, but making game loops and things. I've been using XNA for a long time, but I've always wanted to make a full game engine in C++, so I've started dabbling.

Any ideas why my loop only redraws things when I move the window? Here's what I've changed my loop to (I realized I didn't even need to check the message there because my WndProc function handles all of those anyway):

DWORD target = (DWORD)(1000 / targetFrameRate);

while (!done)
{
DWORD starting_point = GetTickCount();

while (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

Update((float)(GetTickCount() - starting_point) / 1000.0f);

Draw();
SwapBuffers(deviceContext);

while ((GetTickCount() - starting_point) < target);
}


My default value for targetFrameRate (which is an int) is 60. 'target' then computes to 16. So it's not getting stuck in that second loop. I'm running out of ideas.

Share this post


Link to post
Share on other sites
Because you use PM_REMOVE. PeekMessage with PM_REMOVE behaves just like GetMessage (blocks until a message is received).

Try PM_NOREMOVE, but then you also need to use GetMessage to actively remove the message from the queue.

Share this post


Link to post
Share on other sites
Hm. Ok. Can anyone show me what a good fixed-rate game loop would look like? This is what mine currently has evolved to, but it's pretty glitchy so I'm sure this isn't what it should be:

DWORD target = (DWORD)(1000 / targetFrameRate);

while (!done)
{
DWORD starting_point = GetTickCount();

do
{
if (PeekMessage(&msg, windowHandle, 0, 0, PM_NOREMOVE))
{
GetMessage(&msg, windowHandle, 0, 0);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
} while ((GetTickCount() - starting_point) <= target);

Update(1.0f / (float)targetFrameRate);

Draw();
SwapBuffers(deviceContext);
}

Share this post


Link to post
Share on other sites
I don't believe that PeekMessage blocks. From MSDN:
"Unlike GetMessage, the PeekMessage function does not wait for a message to be posted before returning."

Here's my windows game loop:


if (GAME.OneTimeInit(hWnd, hDC))
{
do // Restart game
{
GAME.Reset();
GAME.EnterMenuState();

do // Main loop
{
// Do Windows housekeeping: process any waiting windows messages.
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
GAME.ExitApp();
exitCode = msg.wParam;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
else if (GAME.IsAppActive()) // No windows messages waiting: do our own stuff.
{
GAME.Run();

if (gKeys[VK_ESCAPE]) // ESC signalled a quit
{
// GAME.HandleEscapeKey();
}
}
else
{
WaitMessage(); // Don't eat all the bloody CPU when minimised!
}

} while (GAME.IsGameRunning());

} while (GAME.IsAppRunning());
}



I.e. I leave it to the game object to sort out timing stuff.

Then the Game main loop:


mElapsedGameTimeMs = MeasureElapsedTime();

// Execute a number of fixed time step physics iterations for the real elapsed time.
int loopCount = 0;
while (mElapsedGameTimeMs >= KTickTimeMs && loopCount < KMaxPhysicsIterationCount)
{
WORLD().Update(KTickTimeMs);
mCollisionManager.CheckCollisions();
mElapsedGameTimeMs -= KTickTimeMs;
loopCount++;
}

if (KTickTimeMs*loopCount > 0)
{
// Update even if particleLevel is 0, to flush existing particles.
PARTICLES().Update(KTickTimeMs*loopCount);
}

mSceneManager.Update(KTickTimeMs*loopCount, mCamera.WorldPosn(), InterfaceMode());

SOUNDSYS().Update();

// Handle any accumulated time debt:
if (mElapsedGameTimeMs >= KTickTimeMs)
{
// Physics running slower than real-time:
if (!mNetworkGame)
{
// Discard time debt, resulting in game 'falling behind' real-time.
mElapsedGameTimeMs = 0;
}
else
{
// Network synch problems!! Kick/slow others down/what?!
}
}

// Render if a physics update occurred, else no point (until we do interpolation that is)
// if (loopCount > 0 || mTimeCompression==0)
{
Render();
SwapBuffers(mHDC); // Swap screen buffers
}


Share this post


Link to post
Share on other sites
Looks like you're right.

I was always under the impression that PeekMessage will block if the flag PM_REMOVE is used (and not block with any other combination).

My bad.

Share this post


Link to post
Share on other sites
Here's me:
#define MAX_FPS 85
#define MIN_DT (1000 / (MAX_FPS))

do {
static unsigned lastTime;
unsigned time, dt;

if (!active) {
Sleep(5);
else {
Sleep(0);
}

do {
while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
time = timeGetTime();
if (!lastTime) {
lastTime = time;
}
dt = time - lastTime;
} while (dt < MIN_DT);
lastTime = time;

DoStuff(dt);
} while (1);

Share this post


Link to post
Share on other sites
Instead of looping doing nothing and wasting cycles other processes could be using, figure out how many ms you have to kill and Sleep() them. Otherwise you're just spinning your wheels for nothing.

Also, if you're going to use Sleep() you need to make it more accurate with timeBeginPeriod(2). Also I would drop the GetTickCount() for timeGetTime() as it will now have 2ms resolution which is, if I recall right a lot better then GetTickCount() which is like 10-15ms? The two was just an example btw, I currently use 1 but someone said this was bad. But even 5ms should be good for you.

Even a Sleep(0) is something you should do if you have no time, it gives a tiny bit back to the OS, the rest of your current timeslice if I do recall correctly.

Share this post


Link to post
Share on other sites
That's definitely working a lot nicer. Very smooth movement of my objects as opposed to the glitchy movement before. Unfortunately now when I hit the little X close button, it freezes. I guess I can't win. :)

Edit: And it freezes when I move the window. And if I minimize it. Is my game loop ruining my window messages?

done = false;
MSG msg;

timeBeginPeriod(2);

DWORD target = (DWORD)(1000 / targetFrameRate);

while (!done)
{
DWORD startingPoint = timeGetTime();

while (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
{
done = true;
break;
}

TranslateMessage(&msg);
DispatchMessage(&msg);
}

DWORD sleepLength = target - (timeGetTime() - startingPoint);
Sleep((sleepLength > 0) ? sleepLength : 0);

Update(1.0f / (float)targetFrameRate);

Draw();
SwapBuffers(deviceContext);
}



[Edited by - NickGravelyn on September 17, 2007 1:17:23 PM]

Share this post


Link to post
Share on other sites
Doubtful, I don't see anything wrong in my quick glance. Don't forget to use timeEndPeriod(2) (or whatever the Begin's value was) to reset it.

Post your window processing code, might be a bug in there.

Share this post


Link to post
Share on other sites
When you get the WM_QUIT message you should not draw any more. Everything has shut down by that point. Actually, you probably should stop drawing after DestroyWindow is called (in the WM_CLOSE handler).

Share this post


Link to post
Share on other sites
Here's my message handler:

LRESULT CALLBACK Game::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(WM_QUIT);
break;

case WM_CLOSE:
case WM_QUIT:
Exit();
return 0;

case WM_KEYDOWN:
case WM_KEYUP:
setKeyboardKeyState((int)wParam, (message == WM_KEYDOWN));
return 0;

default:
return DefWindowProc(hWnd, message, wParam, lParam);
}

return 0;
}


and my current game loop:

done = false;
MSG msg;

timeBeginPeriod(2);

DWORD target = (DWORD)(1000 / targetFrameRate);

while (!done)
{
DWORD startingPoint = timeGetTime();

while (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

if (done)
break;

DWORD sleepLength = target - (timeGetTime() - startingPoint);
Sleep((sleepLength > 0) ? sleepLength : 0);

Update(1.0f / (float)targetFrameRate);

Draw();
SwapBuffers(deviceContext);
}

Share this post


Link to post
Share on other sites
Another one fixed frame rate example (that I'm use in my programs):


int fix_fps = 30;
float fFPS;
int sleep = 0;
static LARGE_INTEGER base;
static LARGE_INTEGER freq;
QueryPerformanceFrequency((LARGE_INTEGER*)&freq);
QueryPerformanceCounter((LARGE_INTEGER*)&base);
while (!done)
{


if (PeekMessage(&msg, windowHandle, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT ||
msg.message == WM_DESTROY ||
msg.message == WM_CLOSE)
{
Exit();
break;
}

TranslateMessage(&msg);
DispatchMessage(&msg);
}
static LARGE_INTEGER cur;
QueryPerformanceCounter(&cur);

// calculate fps
if(cur.QuadPart>=base.QuadPart)
{

fFPS = (float)((double)freq.QuadPart/(double)(cur.QuadPart-base.QuadPart));
}
else
{

fFPS = (float)((double)freq.QuadPart/(double)(base.QuadPart-cur.QuadPart));
}

if(fix_fps > 0.0f)
{
if((int)fFPS < fix_fps )
sleep--;
else if((int)fFPS > fix_fps )
sleep++;

// sleep cant be negative
if(sleep < 0) sleep = 0;

// sleep
Sleep(sleep);
}
Draw();
SwapBuffers(deviceContext);


}


Share this post


Link to post
Share on other sites
You treat WM_QUIT and WM_CLOSE as the same, they are not:
WM_CLOSE

Quit doesn't return at all.

As per PostQuitMessage() I just use 0. WM_QUIT isn't what you're ment to use ;-)

Hopefully that fixes it.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement