(What I'm going to describe here is pretty simple, however I felt the need to write this article because I'm still seeing games with jumpy mouse pointers as the frame rate goes south.)
Traditionally the way to draw the mouse pointer in games as has been to synchronize it with frame buffer flips. You render the scene, draw the mouse pointer, and then flip. This was fine for many 2D games that weren't graphically taxing the system and could keep their frame rates above 25 frames per second. Lately though graphics processing has gotten a lot more intensive (especially with 3D) and even games that can usually keep a high frame rate have periods where they will drop below 15. The mouse pointer then becomes jumpy, hard to control and makes the game feel slow and unresponsive.
The solution to this problem is to desynchronize the mouse drawing from the buffer flips, and draw the mouse asynchronously when ever the user moves the mouse. This means updating the mouse on the front buffer while at the same time rendering on the back buffer. Here's a operating, graphics API neutral description of how this is done:
- Set up a separate thread that gets activated whenever the mouse moves.
- When this thread gets activated first restore whatever was under the mouse pointer previously, save the rectangle where you are going to place the mouse pointer, and then draw the mouse pointer. This is all done on the front buffer.
- Make sure on frame flips that you also do a save and draw on the back buffer.
[size="5"] Multitasking in Windows:
I see multitasking as a necessary evil. Sure it can be useful and make some things easier, but it also brings with it a whole nasty class of bugs. (race conditions, re-entrancy issues, etc.) These can occur seemingly at random and can be very hard to debug. Also, I frankly don't trust Microsoft's code to always handle multitasking correctly. How many times have you seen Windows 95/98 grind to a halt as it gets stuck on the Win16Lock? Still if you want to handle asynchronous events you got to multitask. For the most part DirectX seems to handle multitasking OK. However there was one function that I had to help out (I'll get to it later) and there maybe others that have problems that I'm not using.
I'm using two threads. One for mouse drawing and messaging, one for everything else. I also have two mutexs. One I use to protect the mouse drawing routines from reentrancy, the other I use to flip back and forth from windows message retrieval and the main game loop.
Here's a code snip:
hTMain=GetCurrentThread();
hMThreadFlip=CreateMutex(NULL,TRUE,NULL);
hMMouseDraw=CreateMutex(NULL,FALSE,NULL);
hTLoop=CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)MainLoopStart,NULL,NULL,&lpThreadId);
while(1)
{
if (fThreadReleased)
{
if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
if (msg.message==WM_QUIT) return(msg.wParam);
DispatchMessage(&msg);
if (fEnding) continue;
}
ReleaseMutex(hMThreadFlip);
}
else
{
while (PeekMessage(&msg,NULL,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE))
{
DispatchMessage(&msg);
}
}
fThreadReleased=(MsgWaitForMultipleObjects(1,&hMThreadFlip,FALSE,INFINITE,QS_MOUSEMOVE)==WAIT_OBJECT_0) ? TRUE : FALSE;
}
Here's snip of the mouse drawing code.
Draw on WM_MOUSEMOVE code:
case WM_MOUSEMOVE:
if (WaitForSingleObject(hMMouseDraw,0)==WAIT_OBJECT_0)
{
DrawMouseOnFront();
ReleaseMutex(hMMouseDraw);
}
WaitForSingleObject(hMMouseDraw,INFINITE);
// Draw Mouse.
DrawMouseOnBack();
// Do Flip.
FlipScreen();
ReleaseMutex(hMMouseDraw);
One I thing I had to do was place mutexs (blocking calls to DrawMouseOnFront()) around all calls to Surface->GetDC, Surface->ReleaseDC. It appears the GetDC, ReleaseDC functions can't handle multitasking correctly. Before I did this I was getting crashing in DirectX.
Using this method makes the mouse pointer nice and smooth no matter the frame rate. It can also make your game appear faster because when your keyed in on the mouse you don't notice other jerkiness. The mouse feels responsive so the game feels responsive.
Questions? Comments? you can email me at [email="bgantt@onegames.com"]bgantt@onegames.com[/email]