3 out of 4 rated this helpful Rate this topic

Taking Advantage of High-Definition Mouse Movement

Advanced Technology Group (ATG)

April 2006

Introduction

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, un-filtered 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 such mouse input in a game like a first-person shooter.

More general information about high-definition input devices can be found on product Web sites, like Microsoft Mouse and Keyboard Hardware: High-Definition Technology.

Retrieving Mouse Movement Data

There are 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 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.

More information about WM_MOUSEMOVE can be found at 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:

    #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 = 40;
        static BYTE lpb[40];
    
        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

More information about WM_INPUT can be found at 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 the Xbox 360 controller for Windows, then 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.

Did you find this helpful?
(1500 characters remaining)
Community Content Add
Annotations FAQ
That sample is a joke.
$0Yes that sample with wild guess thing is a wild joke just like rest of the documentation.$0 $0$0 $0 $0Anyone interested in RawInput API take my advice;$0 $0 $0Event GetRawInputBuffer lets you implament RawInput outside windows message loop/callback (if you are developping modular realtime application it you dont want to mess your window code with input code) it is not working.$0 $0$0 $0 Therefore, GetRawInputBuffer is not working so don't waste your time GetRawInputData is working. GetRawInputBuffer also have a nasty bug on 32bit applications on 64bit platforms. In my case GetRawInputBuffer fills data part with zeros. (So you can not figure out with key is pressed or released) $0 $0$0 $0 $0In sort, Use GetRawInputData don't use GetRawInputBuffer.$0 $0
WM_MOUSEMOVE
GET_X_PARAM is not defined?
Look for GET_X_LPARAM in windowsx.h
Using RAW input API is too hard.
First of all, MSDN don't mentioned, that calls to GetRawInputBuffer must be from the same thread (message loop proc) from which window created.
So, for reading mouse events asynchronously, we need to create separate invisible window + thread, like directinput does.
This increase code complexity much bigger than using directinput.
Second, the article about mouse ballistic don't contain formula's (API functions) for cursor position calculation which is the same as windows does.
Instead this article talks us about fixed point arithmetics in kernel ring0.
So, if we write code for reading raw mouse data, we can't convert it to the same positions, like windows does.

Third... Examples is strange...
---begin code from example--- http://msdn.microsoft.com/en-us/library/ms645546.aspx#buffered_read
VERIFY(GetRawInputBuffer(NULL, &cbSize, /*0,
*/sizeof(RAWINPUTHEADER)) == 0);
cbSize *= 16; // this is a wild guess
---end code from example---
What the "wild guess"... this examples wroten by students?
Sorry for my english.