Taking Advantage of High-Definition Mouse Movement

A standard computer mouse returns data at 400 dots per inch (DPI), whereas a high-definition mouse generates data at 800 DPI or greater. This makes input from a high-definition mouse much more precise than that from a standard mouse. However, high-definition data cannot be obtained through the standard WM_MOUSEMOVE messages. In general, games will benefit from high-definition mouse devices but games that obtain mouse data using just WM_MOUSEMOVE won't be able to access the full, unfiltered resolution of the mouse.

A number of companies are manufacturing high-definition mouse devices, such as Microsoft and Logitech. With the increasing popularity of high-resolution mouse devices, it is important that developers understand how to use the information generated by these devices optimally. This article focuses on the best way to optimize the performance of high-definition mouse input in a game like a first-person shooter.

Retrieving Mouse Movement Data

Here are the three primary methods to retrieve mouse data:

There are advantages and disadvantages to each method, depending on how the data will be used.

WM_MOUSEMOVE

The simplest method of reading mouse movement data is through WM_MOUSEMOVE messages. The following is an example of how to read mouse movement data from the WM_MOUSEMOVE message:

case WM_MOUSEMOVE:
{
    int xPosAbsolute = GET_X_PARAM(lParam); 
    int yPosAbsolute = GET_Y_PARAM(lParam);
    // ...
    break;
}

The primary disadvantage to data from WM_MOUSEMOVE is that it is limited to the screen resolution. This means that if you move the mouse slightly — but not enough to cause the pointer to move to the next pixel — then no WM_MOUSEMOVE message is generated. So, using this method to read mouse movement negates the benefits of high-definition input.

The advantage to WM_MOUSEMOVE, however, is that Windows applies pointer acceleration (also known as ballistics) to the raw mouse data, which makes the mouse pointer behave as customers expect. This makes WM_MOUSEMOVE the preferred option for pointer control (over WM_INPUT or DirectInput), since it results in more natural behavior for users. While WM_MOUSEMOVE is ideal for moving mouse pointers, it is not so good for moving a first-person camera, since the high-definition precision will be lost.

For more info about WM_MOUSEMOVE, see WM_MOUSEMOVE.

WM_INPUT

The second method of obtaining mouse data is to read WM_INPUT messages. Processing WM_INPUT messages is more complicated than processing WM_MOUSEMOVE messages, but WM_INPUT messages are read directly from the Human Interface Device (HID) stack and reflect high-definition results.

To read mouse movement data from the WM_INPUT message, the device must first be registered; the following code provides an example of this:

// you can #include <hidusage.h> for these defines
#ifndef HID_USAGE_PAGE_GENERIC
#define HID_USAGE_PAGE_GENERIC         ((USHORT) 0x01)
#endif
#ifndef HID_USAGE_GENERIC_MOUSE
#define HID_USAGE_GENERIC_MOUSE        ((USHORT) 0x02)
#endif

RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = HID_USAGE_PAGE_GENERIC; 
Rid[0].usUsage = HID_USAGE_GENERIC_MOUSE; 
Rid[0].dwFlags = RIDEV_INPUTSINK;   
Rid[0].hwndTarget = hWnd;
RegisterRawInputDevices(Rid, 1, sizeof(Rid[0]));

The following code handles WM_INPUT messages in the application's WinProc handler:

case WM_INPUT: 
{
    UINT dwSize = sizeof(RAWINPUT);
    static BYTE lpb[sizeof(RAWINPUT)];

    GetRawInputData((HRAWINPUT)lParam, RID_INPUT, lpb, &dwSize, sizeof(RAWINPUTHEADER));

    RAWINPUT* raw = (RAWINPUT*)lpb;

    if (raw->header.dwType == RIM_TYPEMOUSE) 
    {
        int xPosRelative = raw->data.mouse.lLastX;
        int yPosRelative = raw->data.mouse.lLastY;
    } 
    break;
}

The advantage to using WM_INPUT is that your game receives raw data from the mouse at the lowest level possible.

The disadvantage is that WM_INPUT has no ballistics applied to its data, so if you want to drive a cursor with this data, extra effort will be required to make the cursor behave like it does in Windows. For more information about applying pointer ballistics, see Pointer ballistics for Windows XP.

For more info about WM_INPUT, see About raw input.

DirectInput

DirectInput is a set of API calls that abstracts input devices on the system. Internally, DirectInput creates a second thread to read WM_INPUT data, and using the DirectInput APIs will add more overhead than simply reading WM_INPUT directly. DirectInput is only useful for reading data from DirectInput joysticks; however, if you only need to support controllers for Windows, use XInput instead. Overall, using DirectInput offers no advantages when reading data from mouse or keyboard devices, and the use of DirectInput in these scenarios is discouraged.

Compare the complexity of using DirectInput, shown in the following code, to the methods previously described. The following set of calls are needed to create a DirectInput mouse:

LPDIRECTINPUT8 pDI;
LPDIRECTINPUTDEVICE8 pMouse;

hr = DirectInput8Create(GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (VOID**)&pDI, NULL);
if(FAILED(hr))
    return hr;

hr = pDI->CreateDevice(GUID_SysMouse, &pMouse, NULL);
if(FAILED(hr))
    return hr;

hr = pMouse->SetDataFormat(&c_dfDIMouse2);
if(FAILED(hr))
    return hr;

hr = pMouse->SetCooperativeLevel(hWnd, DISCL_NONEXCLUSIVE | DISCL_FOREGROUND);
if(FAILED(hr))
    return hr;

if(!bImmediate)
{
    DIPROPDWORD dipdw;
    dipdw.diph.dwSize       = sizeof(DIPROPDWORD);
    dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
    dipdw.diph.dwObj        = 0;
    dipdw.diph.dwHow        = DIPH_DEVICE;
    dipdw.dwData            = 16; // Arbitrary buffer size

    if(FAILED(hr = pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph)))
        return hr;
}

pMouse->Acquire();

And then the DirectInput mouse device can be read each frame:

DIMOUSESTATE2 dims2; 
ZeroMemory(&dims2, sizeof(dims2));

hr = pMouse->GetDeviceState(sizeof(DIMOUSESTATE2), &dims2);
if(FAILED(hr)) 
{
    hr = pMouse->Acquire();
    while(hr == DIERR_INPUTLOST) 
        hr = pMouse->Acquire();

    return S_OK; 
}

int xPosRelative = dims2.lX;
int yPosRelative = dims2.lY;

Summary

Overall, the best method to receive high-definition mouse movement data is WM_INPUT. If your users are just moving a mouse pointer, then consider using WM_MOUSEMOVE to avoid needing to perform pointer ballistics. Both of these window messages will work well even if the mouse isn't a high-definition mouse. By supporting high definition, Windows games can offer more precise control to users.