Win32 Game Loop For Fixed Frame Rate

Started by
15 comments, last by MGB 16 years, 6 months ago
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);
	}
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.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
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.
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.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

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);	}
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	}

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.

Fruny: Ftagn! Ia! Ia! std::time_put_byname! Mglui naflftagn std::codecvt eY'ha-nthlei!,char,mbstate_t>

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);
Anthony Umfer
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.

"Those who would give up essential liberty to purchase a little temporary safety deserve neither liberty nor safety." --Benjamin Franklin

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]

This topic is closed to new replies.

Advertisement