I found the engine’s main input loop and disabled everything on the input loop other than the sdl plugin, now im pretty certain nothing else in the engine is messing with the direct input device events, but it’s hard to be absolutely sure, so im crossing that possibility out for now.
I’ve noticed a new effect though. Specifically that event inputs almost never get dropped if you are just doing one thing, like pulling the trigger. It’s when you combine a trigger pull with a 3 axis joystick movement plus throttle that the events stop coming really consistently. So im back to “buffer overflow” thinking.
From further research, every directinput device has it’s own buffer of some length, and SDL2 is essentially acting as a middle man buffer, moving items from the directinput buffer into it’s own queue. I’ve found this in the SDL source code which is probably the real issue here:
32 is probably just way too low for a 3 axis stick with multiple buttons at a device polling rate. If SDL is running at a high polling rate and a game is running a 60 on a typical joystick with 3 axes, a throttle, a hat, and a user mashing several buttons that queue fills up the directinput buffer will overflow the buffer in just 3-5 polls, and if the internal poll rate on the device is even remotely high then it’s not hard to see how that would break pretty much immediately. The default USB polling rate for most of these directinput devices seems to be the usb default of 125hz with mice for example going up 1000+ so i don’t understand how this even works reliably for 60 fps games that might dip to begin with, from what i can see in the source sdl’s mouse support has a much bigger buffer? Definetly needs expanding for joysticks.
So, i rebuilt sdl2 from source with the directinput buffering disabled and it actually fixes the issue completely, though it generates the new issue that button presses quicker than a frame are missed by the game, which isnt great either but is arguably better than the alternative. I also tried increasing the size of the directinput buffer, but sdl is using a statically allocated array for that so i could only get it from 32 to 167 before it hit the static alloc memory limit, which did noticably improve the result but without a guarantee that inputs wont get dropped it’s not enough.
Next i tried forcing sdl to use both buffered and immediate mode events at the same time and that solved the problem for me completely. All my game code does (at least, it should) use state aware bindings so having an accidental doubled up “button down” before a “button release” is never an issue, since a user can sometimes have two buttons bound to the same thing anyways and the game already needs handling for that. The function i modified is below, i just added that one line inside the buffering side of the if statement. The order in there is important or it doesnt work, i guess the event queue add order requires it.
void
SDL_DINPUT_JoystickUpdate(SDL_Joystick * joystick)
{
HRESULT result;
result = IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
if (result == DIERR_INPUTLOST || result == DIERR_NOTACQUIRED) {
IDirectInputDevice8_Acquire(joystick->hwdata->InputDevice);
IDirectInputDevice8_Poll(joystick->hwdata->InputDevice);
}
if (joystick->hwdata->buffered) {
UpdateDINPUTJoystickState_Polled(joystick);
UpdateDINPUTJoystickState_Buffered(joystick);
} else {
UpdateDINPUTJoystickState_Polled(joystick);
}
}
So unless i find other problems, which honestly isn’t all that unlikely (we will see when this hits the testers) this issue is solved for my project.
But, on SDL’s end. There’s no reason to ignore changes in joystick state when it’s right there to check, and if performance from checking both paths is the concern then SDL could do something like check if the buffer was at capacity and if it was, only then do the state comparison checks to produce events when it gets pumped and if it detects a different state than the last pull. This would be a big improvement on my hacky solution where im just using polls and buffers at the same time, and work for every game, not just a game written with button state kept in mind. Also that default directinput buffer of 32 really just should be 100x bigger if there’s not a good reason not to. The alternative is just allowing some events on some standard joystick hardware to get dropped at standard framerates which is never desirable.