Win10 Fall Creators Update breaks Mouse Warping


#1

On Windows 10, apparently since the “Fall Creators Update”, relative mouse mode via warping (instead of Raw Input) is unusably broken - SDL events are created, but don’t behave like expected - https://www.youtube.com/watch?v=Yl9QMMNuTSk shows this in action: you can’t really look around with the mouse, it seems to (mostly) jerk back to the original position.
This can be reproduced (if you’re on Win10 with that update) with the SDL_HINT_MOUSE_RELATIVE_MODE_WARP hint (or the corresponding SDL_MOUSE_RELATIVE_MODE_WARP environment variable) set to 1.

The biggest problem with this is that for some people relative mouse mode via raw input fails (i.e. RegisterRawInputDevices() fails) and up to now those people didn’t even notice that because SDL falls back to warping which worked well enough.
I’m not sure what makes raw input fail; one possibility is “security” software like “Norton Internet Security” which somehow disallows raw input (see https://bugzilla.libsdl.org/show_bug.cgi?id=2368), but for Yamagi Quake II we have a user who claims to only use “Microsoft Security Essentials” (which seems to not break raw mouse input for others) and still seems to get the broken relative mouse mode (see https://github.com/yquake2/yquake2/issues/257 - I’m still waiting for more feedback there to hopefully narrow down why RegisterRawInputDevices() fails and if it really fails).

A bugreport for this already exists: https://bugzilla.libsdl.org/show_bug.cgi?id=3931
It seems like the Windows update broke SetCursorPos() and/or GetCursorPos() and this doesn’t only affect SDL2, but also SDL1.2 (>= 1.2.14 - always, not just when some environment variable is set) and the old winquake.exe from 1997 (thanks to @Eric_Wasylishen for figuring that out!).

I’m posting this here to get more awareness of this problem.
As this apparently is a regression in Windows, does anyone know any developer at Microsoft who could get this to the right people so it may get fixed on their side?


SDL_ShowCursor(SDL_DISABLE) craziness
Interest in a SDL 1.2 bugfix release?
#2

One addition to the “raw input fails” thing: For the guy who reported the problem at Yamagi Quake II, GetLastError() (called directly after RegisterRawInputDevices() returns FALSE) returns error code 87 (ERROR_INVALID_PARAMETER). No idea what to make of that, it’s not like Microsoft documents the possible errors of RegisterRawInputDevices()

Anyway, regarding the broken warping: I debugged the SDL2 relative mouse mode by warping code a bit, and it seems like the most relevant part is in SDL_PrivateSendMouseMotion() - you may wanna open this in a separate window to follow the following text in code. I’m currently at the “no idea how this could ever work anywhere” point, but maybe that’s just because I’m a bit hungry and have looked at the code too much. Will document my thoughts here, would be nice if anyone could look at it and see if it makes sense… I don’t have a definite fix either.
I added a few calls to SDL_Log() in SDL_PrivateSendMouseMotion() and found the following situation:

  • fn arg relative is false
  • fn args x and y as expected (640x480 window => center of window is 320,240 => x and y are around those values, depending on where I moved the mouse. So if I move the mouse to the right, x is something like 263)
  • (mouseID != SDL_TOUCH_MOUSEID && mouse->relative_mode_warp) is true

This means that the following things happen:

  • in line 276ff, SDL_WarpMouseInWindow(window, center_x, center_y); is called to move the cursor back to the window’s center
  • xrel and yrel (which are later set in the corresponding fields of the event) are set in lines 302 and 303, like xrel = x - mouse->last_x;
    • those values have weird values in the events, causing the broken mouse view etc
  • in lines 389/390, mouse->last_x = x; is set

Example of how this causes the problem, looking just at the X coordinate, assuming a 640x480 window (center_x is 320):
Assume you move the mouse to the right. x == 340. By default mouse->last_x == 320 (see line 282), so xrel = 20 - great, like expected so far. However, mouse->last_x = x; is set now, so it’s 340.
So if in next frame x == 340 again, xrel will be 0 (because mouse->last_x is 340) - no further movement. If you moved the mouse slightly slower, x might even be 330 and xrel will be -10 (instead of 10, like it should be if mouse->last_x == 320 as it should be after warping).

So at this point, as usual when debugging something, the question is: How could this ever work, and why does it work on Linux? I haven’t debugged on Linux to figure that out…

Ignoring the “why did it ever work” part - how can this be fixed?
Very brief testing suggests, that reordering lines 281-285 like the following helps:

        mouse->last_x = center_x;
        mouse->last_y = center_y;
        if (x == center_x && y == center_y) {
            return 0;
        }

(the first two lines were in the if()-block before)
That way xrel = x - mouse->last_x; works as intended - and after that mouse->last_* is still overridden with the actual x and y values in lines 389/390 (might be good in case this is used elsewhere and expects to have the x/y values of the last event?)

However, I’m not sure what happens if the system somehow generates two mouse events in one frame and the second is generated before the first one calls SDL_WarpMouseInWindow() - in that case overriding mouse->last_* with center_* is incorrect…
No idea if that ever happens (and often enough to be annoying, if this happens every once in a while people probably won’t even notice one frame of twice as fast movement)…


#3

My impression on Linux: The answer to “why did this ever work” is, that there are just enough internal mouse events indicating no movement (i.e. x and y coordinates are center_x and center_y), even if you’re currently moving the mouse. 1-2 per frame in my silly test program.
In that case line 281 if (x == center_x && y == center_y) is true, and mouse->last_* is reset to center_*.
It seems like on Windows (10 with that Fall Creators Update) those events are a lot more rare - maybe this has changed and causes the bug to appear?


#4

Proposing the following fix, looks a bit cleaner to me than just moving those mouse->last_x/y = center_x/y; lines up (but should behave the same):

diff -r a4477243eb20 src/events/SDL_mouse.c
--- a/src/events/SDL_mouse.c	Sun Dec 03 20:27:08 2017 -0800
+++ b/src/events/SDL_mouse.c	Wed Dec 06 17:34:32 2017 +0100
@@ -279,14 +279,17 @@
         center_x /= 2;
         center_y /= 2;
         if (x == center_x && y == center_y) {
+            /* TODO: not sure these even have to be set, I think last_x/y
+                     are not really used if warping is enabled */
             mouse->last_x = center_x;
             mouse->last_y = center_y;
             return 0;
         }
         SDL_WarpMouseInWindow(window, center_x, center_y);
-    }
 
-    if (relative) {
+        xrel = x - center_x;
+        yrel = y - center_y;
+    } else if (relative) {
         if (mouse->relative_mode) {
             x = GetScaledMouseDelta(mouse->relative_speed_scale, x, &mouse->scale_accum_x);
             y = GetScaledMouseDelta(mouse->relative_speed_scale, y, &mouse->scale_accum_y);


#5

Damn, I just noticed: Maybe those “internal mouse events indicating no movement” aren’t just random - maybe they are the result of the call to SDL_WarpMouseInWindow(window, center_x, center_y);!
If we can (could?) rely on SDL_WarpMouseInWindow() generating an OS mouse motion event, the current implementation of mouse warping in SDL_PrivateSendMouseMotion() is correct - and even handles the “the system somehow generates two mouse events in one frame and the second is generated before the first one calls SDL_WarpMouseInWindow()” case (which is not handled properly anymore after my suggested fix).

On Win10 with Fall Creators Update, I only get one “event without movement”, after moving the mouse, i.e. when I don’t move it anymore (or maybe if I move it slowly enough for one frame without noticable movement)…
I briefly tried the same thing on a VM with Windows XP, and there indeed I get on of those events (in SDL_PrivateSendMouseMotion() ) after each normal event, i.e. after SDL_WarpMouseInWindow() has been called.

Which suggests that with that Win10 update, the behavior of SetCursorPos() (which is called by SDL_WarpMouseInWindow()) has changed significantly, in that it doesn’t generate a mouse event anymore.

UPDATE: A friend just confirmed that on Win10 1607 (which is before fall creators update) SetCursorPos() still seems to generate events. I’d love to report this bug, but https://developer.microsoft.com/en-us/windows/platform/bugs says “Ability to add new bugs is coming soon!” - FFS!


#6

I thought it’d be nice to have a minimal testcase reproducing this without SDL and let Visual Studio generate a minimal Win32 application (just a window basically), and added event handling there to look for WM_MOUSEMOVE events and call SetCursorPos() if you press enter…
For some reason, in that case the error doesn’t occur! I get an event for every call to SetCursorPos(), while in SDL I definitely don’t…

So while I’m reasonably certain that this bug exists, it’s not trivial to reproduce without SDL.

It would be great if someone else who has actual experience with Windows development (I’m really more of a Linux guy) could debug this further (I fear that Microsoft will be less eager to fix it if to reproduce the issue they need a big library like SDL…) - OTOH, even with a neat small repro I have no idea how to report this… but maybe experienced Windows developers know that as well!


#7

I haven’t actually tried anything, but it’s possible that SetCursorPos needs to be called at a high rate to cause this bug. Maybe the WM_MOUSEMOVE events are being merged and/or there’s a synchronization issue in Windows.


#8

I haven’t caught up on the discussion fully, but why should WM_MOUSEMOVE events even matter in a SetCursorPos/GetCursorPos-based relative motion implementation?

i.e. what should happen is we call GetCursorPos every frame, measure the change since the last time we called it and post an SDL event to the app with that, and also call SetCursorPos to re-center the mouse. Any WM_MOUSEMOVE events while in relative mode should then be ignored.


#9

@Jacob_Lifshay: This pushed me in the right direction: After I added a Sleep(10); in the main loop, I could reproduce the issue. (UPDATE: should have mentioned: I do a SetCursorPos() whenever a WM_MOUSEEVENT arrives now, not just when enter is pressed, so this is kinda like what SDL does)
See code at: https://gist.github.com/DanielGibson/b5b033c67b9137f0280af9fc53352c68
Without the Sleep(10) in line 71 about every 2nd-5th mouse move event is at 320,240 (which is the coordinate I warp to), so it seems less broken. With the sleep it basically never happens (only when I stop moving the mouse).
(The Sleep(10) is meant to emulate rendering and whatever else games do within a frame - and this sleeps for 10ms, and I tested for several seconds, so it’s not even “you get the 320,240 event whenever a new frame starts”, but really almost never)

@Eric_Wasylishen: SDL2 doesn’t use GetCursorPos() - it uses SetCursorPos() + mouse events.
In SDL we don’t know when one frame starts/ends (especially when not using OpenGL or SDL2 accelerated 2D rendering where you swap at the end of the frame), so there is no proper time to call GetCursorPos()
No idea what this means for winquake, you’d have to debug it to see what happens there…


#10

SDL2 doesn’t use GetCursorPos() - it uses SetCursorPos() + mouse events.

I just want to point out the commit message from SDL1.2 that switched from WM_MOUSEMOVE to GetCursorPos (back in 2008) and the rationale given there (it blames WM_MOUSEMOVE for spurious/unreliable events): https://hg.libsdl.org/SDL/rev/a6f635e5eaa6
Of course, the fact that SDL1.2 (and winquake) mouse input are failing on the Fall Creators update as well, and they both use GetCursorPos, doesn’t support the idea that GetCursorPos is more reliable.

In SDL we don’t know when one frame starts/ends (especially when not using OpenGL or SDL2 accelerated 2D rendering where you swap at the end of the frame), so there is no proper time to call GetCursorPos()…

PumpEvents would be the right place for this, wouldn’t it? The SDL docs say “gathers all the pending input information from devices and places it in the event queue”.

Anyway sorry for derailing this - I need to go and actually test code with your latest findings! Probably won’t get a chance until this weekend.


#11

It also says

The second problem is that the
"directx" driver does not work at all in combination with OpenGL meaning
that you can’t use DirectInput if your application also uses OpenGL.

Maybe it really was like this with DirectX5 or something, but at least with recent DirectX versions it seems to be possible to use DX for Input, incl. Mouse, and OpenGL for rendering (I see some DirectInput 8 mouse interface thing in the C4 game engine which renders with OpenGL).
So… no idea.

At least in the SDL2 case I wouldn’t care about WM_MOUSEMOVE being a bit wonky, as long as it’s not completely broken (like it is right now on Win10 FCU) - after all it’s just a fallback for people with broken systems that for some reason don’t support raw mouse input.

(Maybe it would be nice if there was a way to tell users that raw input doesn’t work on their systems and that SDL falls back to warping, which even assuming that it works correctly is less cool because you get system’s mouse acceleration)


#12

Yeah, maybe a SDL_IsRawInputSupported or similar would make sense.


#13

I have a fix now that I’m satisfied with, see https://bugzilla.libsdl.org/show_bug.cgi?id=3931#c18
In short, I check the windows version (once, saved in a static variable) and if it’s Win10 FCU or newer, a workaround is enabled.
The workaround just generates an additional mouse event with the coordinates of the center of the window whenever a windows WM_MOUSEMOVE event comes in (if warped relative mode is enabled).


#14

And the workaround has been merged https://hg.libsdl.org/SDL/rev/c70cf178aacb \o/

(This is still a Windows bug, and if anyone has a way to report it to MS, please do!)


#15

Thanks for finding the mouse issues on Win10 FCU are related to SDL_MOUSE_RELATIVE_MODE_WARP=1, and the workaround. I’ve spent some time trying to confirm the issue coming in on my users and will try the fix soon.


#16

Turns out you can report Win10 bugs with the “Feedback Hub” App that comes with it.
Not as good as a proper bugtracker, apparently not even readable via browser, but better than nothing…
I think it allows you to vote on bugs, and if you’re on Win10 the following URL should open the bugreport in Feedback Hub: https://aka.ms/W2icb9
Maybe if some people vote there this gets fixed faster :-)


#17

Sorry for being a noob at this, how do I implement the workaround?


#18

you need to build SDL2 yourself from the latest code you can get from https://hg.libsdl.org/SDL/


#19

Oh… this introduced alot of acceleration into my mouse. Any way I can undo this?