Register
handmade.network»Forums»How to read joystick data from Raw Input
30 posts
How to read joystick data from Raw Input
1 week, 2 days ago Edited by BernFeth on Feb. 24, 2021, 1:28 p.m. Reason: Updated code
Hey guys,

I'm trying to implement raw input following this and msdn.

The controller is recognized, most of the buttons as well but not the stick values.

Can someone help me? The code is as follows:

*EDIT: The code has been updated (read next post).*

Before entering the game loop (initialization):
1
2
3
4
5
6
7
RAWINPUTDEVICE DevicesToRegister[1] = {};
DevicesToRegister[0].dwFlags = RIDEV_INPUTSINK;
DevicesToRegister[0].usUsagePage = 1;
DevicesToRegister[0].usUsage = 4; // * Joystick
DevicesToRegister[0].hwndTarget = Window;

RegisterRawInputDevices(DevicesToRegister, ArrayCount(DevicesToRegister), sizeof(DevicesToRegister[0]));


In wndproc:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
case WM_INPUT:
{
    // TODO: Determine allocation size
    temp_memory InputMemory = BeginTempMemory(&GlobalMemory.TranArena,
                                                Megabytes(2));

    // ! Ask for raw input buffer size
    u32 RawInputBufferSize = 0;
    if (GetRawInputData((HRAWINPUT)LParam,
                        RID_INPUT,
                        0,
                        &RawInputBufferSize,
                        sizeof(RAWINPUTHEADER)) == 0)
    {
        if (RawInputBufferSize > 0)
        {
            char *RawInputBuffer = (char *)PushSize(InputMemory.Arena,
                                                    RawInputBufferSize);
            *RawInputBuffer = {};
            i32 BytesCopied = GetRawInputData((HRAWINPUT)LParam,
                                                RID_INPUT,
                                                RawInputBuffer,
                                                &RawInputBufferSize,
                                                sizeof(RAWINPUTHEADER));
            if (BytesCopied > 0)
            {
                RAWINPUT *RawInput = (RAWINPUT *)RawInputBuffer;
                if (RawInput->header.dwType == 2) // ! Joystick
                {
                    u32 PreparsedDataBufferSize = 0;

                    // ! Ask for Preparsed Data buffer size
                    GetRawInputDeviceInfoA(RawInput->header.hDevice,
                                            RIDI_PREPARSEDDATA,
                                            0,
                                            &PreparsedDataBufferSize);

                    if (PreparsedDataBufferSize > 0)
                    {
                        PHIDP_PREPARSED_DATA PreparsedData =
                            (PHIDP_PREPARSED_DATA)PushSize(InputMemory.Arena,
                                                            PreparsedDataBufferSize);

                        // ! Get Preparsed data
                        if (GetRawInputDeviceInfoA(RawInput->header.hDevice,
                                                    RIDI_PREPARSEDDATA,
                                                    PreparsedData,
                                                    &PreparsedDataBufferSize) != -1)
                        {
                            // ! Get device capabilities
                            HIDP_CAPS Caps;
                            if (HidP_GetCaps(PreparsedData,
                                                &Caps) == HIDP_STATUS_SUCCESS)
                            {
                                PHIDP_BUTTON_CAPS ButtonCaps =
                                    (PHIDP_BUTTON_CAPS)PushArray(InputMemory.Arena,
                                                                    HIDP_BUTTON_CAPS,
                                                                    Caps.NumberInputButtonCaps);
                                *ButtonCaps = {};

                                USHORT ButtonCapsLength = Caps.NumberInputButtonCaps;

                                // ! Get Button capabilities
                                HidP_GetButtonCaps(HidP_Input,
                                                    ButtonCaps,
                                                    &ButtonCapsLength,
                                                    PreparsedData);

                                u32 NumberOfButtons = ButtonCaps->Range.UsageMax - ButtonCaps->Range.UsageMin + 1;

                                ULONG UsageLength = NumberOfButtons * sizeof(USAGE);
                                PUSAGE Usage = (PUSAGE)PushSize(InputMemory.Arena,
                                                                UsageLength);
                                *Usage = {};

                                HidP_GetUsages(HidP_Input,
                                                ButtonCaps->UsagePage,
                                                0,
                                                Usage,
                                                &UsageLength,
                                                PreparsedData,
                                                (PCHAR)RawInput->data.hid.bRawData,
                                                RawInput->data.hid.dwSizeHid);

                                // ! Allocate space for button array
                                b32 *ButtonStates = PushArray(InputMemory.Arena,
                                                                b32,
                                                                NumberOfButtons);

                                // ! Fill it with buttons data
                                for (u32 i = 0; i < UsageLength; i++)
                                {
                                    i32 ButtonIndex = Usage[i] - ButtonCaps->Range.UsageMin;
                                    ButtonStates[ButtonIndex] = true;

                                    {
                                        char OutputBuffer[256] = {};
                                        sprintf_s(OutputBuffer,
                                                    sizeof(OutputBuffer),
                                                    "Button %d: %d",
                                                    i,
                                                    ButtonStates[i]);

                                        OutputDebugStringA(OutputBuffer);
                                    }
                                }

                                // ! Allocate space for value caps array
                                PHIDP_VALUE_CAPS ValueCaps =
                                    (PHIDP_VALUE_CAPS)PushArray(InputMemory.Arena,
                                                                HIDP_VALUE_CAPS,
                                                                Caps.NumberInputValueCaps);
                                *ValueCaps = {};

                                USHORT ValueCapsLength = Caps.NumberInputValueCaps;

                                // ! Get Value caps data
                                HidP_GetValueCaps(HidP_Input,
                                                    ValueCaps,
                                                    &ValueCapsLength,
                                                    PreparsedData);

                                // ! Fill it
                                for (u32 i = 0; i < Caps.NumberInputValueCaps; i++)
                                {
                                    ULONG Value = 0;
                                    HidP_GetUsageValue(HidP_Input,
                                                        ValueCaps[i].LinkUsagePage,
                                                        0,
                                                        ValueCaps[i].Range.UsageMin,
                                                        &Value,
                                                        PreparsedData,
                                                        (PCHAR)RawInput->data.hid.bRawData,
                                                        RawInput->data.hid.dwSizeHid);

                                    {
                                        HIDP_VALUE_CAPS vc = ValueCaps[i];

                                        char OutputBuffer[Kilobytes(2)] = {};

                                        sprintf_s(OutputBuffer,
                                                    sizeof(OutputBuffer),
                                                    "UsageMin: %hu - UsageMax: %hu\n - DataIndexMin: %hu - DataIndexMax: %hu - Value: %lu\n\n\n",
                                                    vc.Range.UsageMin,
                                                    vc.Range.UsageMax,
                                                    vc.Range.DataIndexMin,
                                                    vc.Range.DataIndexMax,
                                                    Value);

                                        OutputDebugStringA(OutputBuffer);
                                    }

                                    switch (ValueCaps[i].Range.UsageMin)
                                    {
                                    case 0x30:
                                    {
                                        LONG LAxisX = (LONG)Value - 128;

                                        r32 NormalizedX = 0;
                                        if (LAxisX < 0)
                                        {
                                            NormalizedX = LAxisX / 128;
                                        }
                                        else
                                        {
                                            NormalizedX = LAxisX / 127;
                                        }

                                        GlobalInput.Controllers[1].LeftAxisX = NormalizedX;
                                    }
                                    break;

                                    case 0x31:
                                    {
                                        LONG LAxisY = (LONG)Value - 128;

                                        r32 NormalizedY = 0;
                                        if (LAxisY < 0)
                                        {
                                            NormalizedY = LAxisY / -128;
                                        }
                                        else
                                        {
                                            NormalizedY = LAxisY / -127;
                                        }

                                        GlobalInput.Controllers[1].LeftAxisY = NormalizedY;
                                    }
                                    break;

                                    case 53:
                                    {
                                        LONG RAxisX = (LONG)Value - 128;

                                        r32 NormalizedX = 0;
                                        if (RAxisX < 0)
                                        {
                                            NormalizedX = RAxisX / 128;
                                        }
                                        else
                                        {
                                            NormalizedX = RAxisX / 127;
                                        }

                                        GlobalInput.Controllers[1].RightAxisX = NormalizedX;
                                    }
                                    break;

                                    case 50:
                                    {
                                        LONG RAxisY = (LONG)Value - 128;

                                        r32 NormalizedY = 0;
                                        if (RAxisY < 0)
                                        {
                                            NormalizedY = RAxisY / -128;
                                        }
                                        else
                                        {
                                            NormalizedY = RAxisY / -127;
                                        }

                                        GlobalInput.Controllers[1].RightAxisY = NormalizedY;
                                    }
                                    break;
                                    }
                                }
                            }
                            else
                            {
                                // TODO: GetCaps Failed
                            }
                        }
                        else
                        {
                            // GetRawInputDeviceInfo Failed
                            DWORD Error = GetLastError();
                            LPVOID MessageBuffer;
                            FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER |
                                                FORMAT_MESSAGE_FROM_SYSTEM,
                                            0,
                                            Error,
                                            0,
                                            (LPTSTR)&MessageBuffer,
                                            0,
                                            0);

                            OutputDebugStringA("\n\n");
                            OutputDebugStringA((LPTSTR)MessageBuffer);
                            OutputDebugStringA("\n\n");
                        }
                    }
                    else
                    {
                        // TODO: Preparsed data was zero
                    }
                }
                else
                {
                    // TODO: Device was not a joystick
                }
            }
            else
            {
                // TODO: GetRawInputData failed
            }
        }
        else
        {
            // TODO: GetRawInputData did NOT fail but BufferSize was still zero
        }
    }
    else
    {
        // TODO: GetRawInputData failed
    }

    EndTempMemory(InputMemory);
}
break;
Mārtiņš Možeiko
2176 posts / 1 project
How to read joystick data from Raw Input
1 week, 1 day ago Edited by Mārtiņš Možeiko on Feb. 23, 2021, 11:16 p.m.
Did you try other axis? Maybe your gamepad stick maps to different axes that 0x30 and 0x31? Print out all values in that loop whenever you receive WM_INPUT message and see if any of them changes when you move gamepad sticks around.
30 posts
How to read joystick data from Raw Input
1 week, 1 day ago Edited by BernFeth on Feb. 24, 2021, 2:04 p.m.
Hi Martins,

Thanks for answering.

As you've suggested, printing it out has helped me figure out the axis.

I have updated my first post to reflect the current state of the code.

It works with a new controller I bought this year (generic pc usb controller).

New controller name (obtained with GetRawInputDeviceInfo):
HID#VID_0810&PID_0001#6&302de5d3&0&0000

(I'm not sure how much of that is relevant to share)

Now, I have an old controller (similar generic pc usb controller) that is giving me trouble:
HID#VID_0810&PID_0003#8&399ecf6f&1&0000


It fails when trying to get Preparsed data in GetRawInputDeviceInfo (First post, second block of code: Line 45):
998 (0x3E6) - Invalid access to memory location.



The thing is this old controller has bad contact (usb cable connection physically damaged) and the other controller I bought does not fail in this manner. But note that I can still see the bad contact controller in debugger, it is recognized but it consistently fails when trying to get the preparsed data.

About bad contact controller:
- Is it right to assume GetRawInputDeviceInfo fails with "Invalid access to memory location" due to bad contact?
- Can this be determined for sure?

Generic questions about raw input:
- Is there a way to force mouse data to be absolute?
- How is mouse relative data intended to be used?
- Is is correct to say that you register a "class of devices" instead of a single device (meaning that registering keyboard gets you messages from all available keyboards)?

Lastly, about Human Interface Devices, does it make sense to not use raw input at all, and instead just deal with HID?
Mārtiņš Možeiko
2176 posts / 1 project
How to read joystick data from Raw Input
1 week, 1 day ago Edited by Mārtiņš Možeiko on Feb. 24, 2021, 5:26 p.m.
I'd be very surprised if GetRawInputDeviceInfo fails because of bad usb cable. The failure probably would have happened earlier on usb handshake.
I would say that error comes from wrong API usage. Are you sure you're passing correctly allocated memory for it (and RawInputBuffer before)? The size is enough? And does your PushSize returns memory aligned at least at 8 bytes?

You cannot force mouse data to be absolute. It sends whatever it sends. Typically mouse is sending relative data, as that is what it measures in hardware - relative movement. OS converts to absolute values based on user preference (speed & acceleration in control panel).
Most likely if you would ask laptop or external touchpad for raw input, it'll provide absolute values, as that is what it measures.

So typically you want to use raw input for mouse only when you have ingame movement/rotation for 3d stuff. Not for 2D GUI elements when you need to mouse cursor around. As for cursor movement you really want to use same settings as OS is doing for smoother speed & acceleration. Otherwise mouse movement will feel a bit off, weird and different from what user expects.

30 posts
How to read joystick data from Raw Input
1 week, 1 day ago
Wow, you were on point!

PushSize was not returning aligned memory, that has fixed the issue!

Thanks a lot Martins!