| ||
| Views: 31,308,485 | Homepage | Main | Rules/FAQ | Memberlist | Active users | Last posts | Calendar | Stats | Online users | Search | 05-12-26 06:42 AM |
| Guest: | ||
| 0 users reading SDL2's Ongoing Curse on Windows: taking a lib for granted | 1 bot |
| Main - General - SDL2's Ongoing Curse on Windows: taking a lib for granted | Hide post layouts | New reply |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 3/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
When SDL2 was released, it demanded main thread execution. The problem is its device enumeration is slow. On Winfows systems in particular, when events from devices considered as input trigger a re-enumeration this holds up the main thread and causes noticeable stutter in games and apps using SDL2.
This used to not be the case. Applications used to be mindful of enumeration being slow and at least with SDL 1 I don't think it was ever a problem. Developers implementing input themselves observed that enumeration could be slow and wrote their code async so it would not block time-critical code. With SDL2 everyone seems to have taken the library's functioning for granted. I do not know where the failure is as XInput doesn't suffer from stutter nor does raw, but I know DirectInput8 does so I guess it's probably implemented in the backend for max compatibility with devices. Either way SDL2 does not "just work" as the naive implementation will hold up the main thread on input connect/disconnect events. Why is this important? On typical systems, anything from USB power savings suspending a USB hub to less than reliable devices like UPSes, Bluetooth input devices or non-essential peripherals with flaky connection can sporadically trigger a re-enumeration, without any user intervention. If this happens mid-game, it can cause a few whole seconds worth of stutter, as many devices present as more than one input device and re-enumeration can trigger several times in a row from just one device connecting or disconnecting. Ever since the advent of SDL2, a number of smaller games and some emulators have suffered from this behavior. This includes MelonDS. Please be mindful of inviting SDL2 into your projects. It doesn't "just work". It brings its own quirks along for the ride. If essential work is time-sensitive, consider moving it off the main thread and pass the relevant information on asynchronously. If re-enumerating input devices, consider a 500ms timer. Wait for it to expire then populate the input device list. Chances are the event will fire several more times in rapid succession before the new device is all settled in. If it does, reset the timer. Maybe have a hard 1-2 second limit and force population then if it still hasn't finished so you don't fully lose out on the device. For now MelonDS continues to stutter on my main PC from sporadic d/c+reconnect courtesy of my APC UPS. APC are notorious for this. I live in an area with flaky electric power and depend on it, especially to shut down the system when I'm at work and power goes out. Hardware is too expensive to risk without it. A new UPS is also. And why? A UPS is not a gaming input device. But also, on version 1.1 I noticed that disconnecting and reconnecting the controller mid run doesn't pick it back up. It's the only emulator that does that. Is the re-enumeration event handled at all? |
| Nadia |
| ||
![]() Big melon Pile of fazils Level: 26 Posts: 142/145 EXP: 100762 Next: 1513 Since: 03-29-17 From: Denmark Last post: 24 days ago Last view: 12 hours ago |
SDL is so widespread because, at least as far as I can tell, there doesn't seem to be any other good options for cross platform gamepad input that aren't vastly more limited than what SDL provides - so it's what most similar projects end up using.
Since we require Qt Multimedia, it could be used for audio instead of SDL, but for input I really don't know. It seems like currently each emulation thread calls the relevant SDL functions to update input, which I guess means we already know it's safe to do on a different thread on all platforms we support, so gamepad input could be moved to its own thread that sends messages to the emulation threads I guess. Do you know if SDL3 is any better about this problem? |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 4/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
Posted by Nadia I've not had any experience with SDL3 titles. The hard way I even learned about this is a few years back when this sporadic stutter gradually began to creep into games. With EDuke32 - a modern open-source implementation of the late '90s Build engine - I was able to talk the devs into implementing a "-nocontroller" option that doesn't load in controller handling functionality if started with. Obviously that also meant no controller support. Which is acceptable for a '90s boomer shooter engine but not for a console emulator. I haven't heard of SDL3 majorly changing architecture in any way. I also filed a bug with them years back on the matter. Since it still exists I assume nothing was made of it. Could try to wing it and init SDL2 on a separate thread regardless and see if it does play nice. I don't know what it holds up in MelonDS' code but it causes stutter. Multimedia hasn't been an issue, not that I've noticed. When the audio is cutting off, it's because the entire emulator is stuttering not directly because of MM handling. The aformentioned EDuke32 still uses SDL2 for everything else when no longer handling controllers, and the stutter remains gone so it really is just the controller part. Other emulators have allowed me to select the type of device or input, either allowing me to disable APIs altogether or only initializing the APIs relevant to the user-configured input devices. If you could provide XInput as an alternative, it'd work for sure as that was my go-to for solving this wherever it was possible. If XInput is selected, don't init SDL2 controller handling. |
| Nadia |
| ||
![]() Big melon Pile of fazils Level: 26 Posts: 143/145 EXP: 100762 Next: 1513 Since: 03-29-17 From: Denmark Last post: 24 days ago Last view: 12 hours ago |
I noticed this in SDL_hints.h
/**
* A variable controlling whether a separate thread should be used for * handling joystick detection and raw input messages on Windows * * This variable can be set to the following values: * * - "0": A separate thread is not used (the default) * - "1": A separate thread is used for handling raw input messages */ #define SDL_HINT_JOYSTICK_THREAD "SDL_JOYSTICK_THREAD" Maybe you can try setting that ( SDL_JOYSTICK_THREAD) as an environment variable with a value of 1 and see if that maybe improves things. There are also controls for which backends SDL's gamepad code tries to use, SDL_JOYSTICK_HIDAPI and SDL_JOYSTICK_RAWINPUT are probably the relevant ones you could try to disable. |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 5/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
I haven't actually gone into the code of either the emulator or SDL. I could try to make a toy project and just shove SDL2 in there and then test some stuff with it, if I get around to it. Are you able to test this on your side? |
| Nadia |
| ||
![]() Big melon Pile of fazils Level: 26 Posts: 144/145 EXP: 100762 Next: 1513 Since: 03-29-17 From: Denmark Last post: 24 days ago Last view: 12 hours ago |
I can't reproduce the issue myself, melonDS runs perfectly fine on my Windows machine.
Those options I mentioned though can be set as environment variables, so you can simply set those and run melonDS. |
| Arisotura |
| ||
![]() Big fire melon magical melon girl Level: 64
Posts: 1129/1130 EXP: 2192710 Next: 21387 Since: 03-28-17 From: France Last post: 18 days ago Last view: 2 days ago |
Posted by Nadia yeah, this. Qt's joystick API is utterly braindead. ____________________ Kuribo64 |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 6/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
Setting SDL_JOYSTICK_THREAD to 1 does not seem to affect the issue. It's not any better or worse. The issue appears to get worse as more USB devices are connected, slowing down enumeration. I have 8 devices connected in total. The more of them I remove, the more mild the issue becomes but even then I have to say it never fully goes away. It used to be worse on my old 4 core setup before changing motherboard and CPU in the process, and while better on my 8 core CPU now it's still present.
UPDATE: I just confirmed that with SDL3 the issue is the same. |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 7/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
For the first time in 2023 I have answers. I probably shouldn't have waited this long to test this but now that I have, I have answers at last. Context: this game continuously calls SDL_PollEvent() as part of the main loop. See the following picture:
And there's more: I believe it does indeed all come down to DirectInput. Setting SDL_SetHint(SDL_HINT_DIRECTINPUT_ENABLED, "0"); should fix it. If this were a checkbox in the settings, people like myself could uncheck it.
As you can see from the picture, removing/plugging an input device(controller, mouse, etc.) causes SDL_PollEvent() to be very slow. Those hitches I mentioned happening several times in order after a single plug event were matching the stuttering EXACTLY in the game title, so what you see in that console log are 2-3 slow polling events per single device connect/dc. The polling should be async from whatever depends on its output. My guess, a thread that loops polling and places the latest obtained result in a mutex-wrapped result that the outer call just enters to fetch. The wait time just about matches the time it takes to do a full DirectInput8 EnumDevices(), and this is only speculation on my part but I rather confidently believe the blocking event is waiting for an enum under the hood. Design strategy seems to be: don't assume SDL_PollEvent() is always going to be instant. Sometimes, it can be very slow. Your thoughts? |
| CasualPokePlayer |
| ||
|
Member Normal user Level: 13 Posts: 38/38 EXP: 9091 Next: 1176 Since: 03-27-22 Last post: 24 days ago Last view: 17 days ago |
> the blocking event is waiting for an enum under the hood.
It is. Normally (unless set other via SDL_HINT_AUTO_UPDATE_JOYSTICKS) pumping SDL events implicitly calls SDL_JoystickUpdate. This proceeds to call WINDOWS_JoystickDetect, which if SDL got alerted to a joystick being added/removed, it proceeds to call various SDL_*_JoystickDetect, including SDL_DINPUT_JoystickDetect. This proceeds to call IDirectInput8_EnumDevices. melonDS probably should be setting that hint to avoid this implicit SDL_JoystickUpdate call anyways, since it already explicitly calls it (and I would doubt it's a good idea to call SDL_JoystickUpdate on multiple threads at the same time, at least with the native Windows side). Of course, this means you're just moving the blocking to the emu thread rather than the GUI thread (and even then, that might not be safe either as SDL_INIT_JOYSTICK was called on the GUI thread rather than the emu thread, so that could potentially cause issues due to the wrong thread being used). |
| Nadia |
| ||
![]() Big melon Pile of fazils Level: 26 Posts: 145/145 EXP: 100762 Next: 1513 Since: 03-29-17 From: Denmark Last post: 24 days ago Last view: 12 hours ago |
Since SDL_HINT_DIRECTINPUT_ENABLED is a hint, it should be settable as an environment variable (SDL_DIRECTINPUT_ENABLED). The proper fix here is probably for you to set it globally so all SDL apps stop having this problem.
Since this is likely a system specific problem with buggy drivers and not an actual bug in the code, I don’t really think actually implementing a workaround for it on our end is the correct thing to do here? |
| Searinox |
| ||
|
Newcomer Normal user Level: 4
Posts: 8/8 EXP: 228 Next: 51 Since: 01-30-26 Last post: 24 days ago Last view: 24 days ago |
I've run tests just now with SDL_DIRECTINPUT_ENABLED = 0. Setting the value turns the many stuttery events spanning seconds into just a single event when un/plugging a mouse. But not fully gone. This is much more bearable, though with it still having some presence I do wonder what could still be happening. The only way to fully make it go away is to omit calling SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); altogether, which for me is acceptable in a FPS title that I don't play with a controller, but not in a console emulator...
...I guess I'll go around trying to find out how to stamp out blocking for good but for now this seems to be acceptable performance. Please do consider a checkbox for DirectInput for less technically-minded users. In the meantime, I'll do with this workaround. If I find out why SDL still hitches even with DInput disabled, I'll bring it up on their GitHub. |
| Main - General - SDL2's Ongoing Curse on Windows: taking a lib for granted | Hide post layouts | New reply |
|
Page rendered in 0.050 seconds. (2048KB of memory used) MySQL - queries: 29, rows: 102/102, time: 0.026 seconds.
Acmlmboard 2.064 (2018-07-20)© 2005-2008 Acmlm, Xkeeper, blackhole89 et al. |