SDL: [Windows] Toggling SetRelativeMouseMode can cause incorrect SDL_MOUSEMOTION occasionally

I am working on a project where it is important to seamlessly move the mouse in and out of the window. While in the window, we wish for the mouse to be in relative mode to benefit from raw input and mouse capture. This means we are toggling the relative mouse state when approaching the window border (note that this is just to describe our use case and does not contribute to the reproducibility of the issue).

Our expectation is that when switching relative mode on / off, there should be no resultant SDL_MOUSEMOTION generated that was not a result of user input. On rare occasions this is not the case.


using System;
using System.Diagnostics;
using SDL2;

namespace SDL2IsolatedRelativeModeTest
{
    class Program
    {
        static bool relative;

        static void Main(string[] args)
        {
            SDL.SDL_Init(SDL.SDL_INIT_VIDEO);
            SDL.SDL_SetHint(SDL.SDL_HINT_EVENT_LOGGING, "2");

            SDL.SDL_CreateWindow("test window", 100, 100, 500, 500, SDL.SDL_WindowFlags.SDL_WINDOW_RESIZABLE);

            var stopwatch = new Stopwatch();
            stopwatch.Start();

            while (stopwatch.ElapsedMilliseconds < 10000)
            {
                while (SDL.SDL_PollEvent(out var e) > 0)
                {
                    if (e.type == SDL.SDL_EventType.SDL_MOUSEMOTION)
                    {
                        // switch relative mode every time an input event is received.
                        relative = !relative;
                        SDL.SDL_SetRelativeMouseMode(relative ? SDL.SDL_bool.SDL_TRUE : SDL.SDL_bool.SDL_FALSE);
                        Console.WriteLine($"Relative mode {relative}");
                    }
                }
            }
        }
    }
}

Doing small circles with the mouse away from the centre of the window works fine most of the time (and potentially forever if the toggle rate is lower) but can fail with rapid toggles. I have not yet ascertained whether the rate of toggle is important, or whether this can always occur at a low reproduction change, but based on loose testing with our game it looks to be the second of these cases.

https://user-images.githubusercontent.com/191335/110595947-07d57400-81c2-11eb-8c0f-5efb88edfbc0.mp4

Note the high delta suddenly appearing in the log output:


Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5669 windowid=1 which=0 state=0 x=424 y=436 xrel=1 yrel=2)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5676 windowid=1 which=0 state=0 x=424 y=438 xrel=0 yrel=2)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5685 windowid=1 which=0 state=0 x=424 y=439 xrel=0 yrel=1)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5701 windowid=1 which=0 state=0 x=424 y=440 xrel=0 yrel=1)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5719 windowid=1 which=0 state=0 x=423 y=440 xrel=-1 yrel=0)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5724 windowid=1 which=0 state=0 x=422 y=441 xrel=-1 yrel=1)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5734 windowid=1 which=0 state=0 x=420 y=441 xrel=-2 yrel=0)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5741 windowid=1 which=0 state=0 x=417 y=441 xrel=-3 yrel=0)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5749 windowid=1 which=0 state=0 x=415 y=441 xrel=-2 yrel=0)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5758 windowid=1 which=0 state=0 x=412 y=441 xrel=-3 yrel=0)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5765 windowid=1 which=0 state=0 x=410 y=441 xrel=-2 yrel=0)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5774 windowid=1 which=0 state=0 x=407 y=439 xrel=-3 yrel=-2)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5778 windowid=1 which=0 state=0 x=249 y=237 xrel=-158 yrel=-202)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5782 windowid=1 which=0 state=0 x=247 y=236 xrel=-2 yrel=-1)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5792 windowid=1 which=0 state=0 x=246 y=233 xrel=-1 yrel=-3)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5797 windowid=1 which=0 state=0 x=245 y=230 xrel=-1 yrel=-3)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5808 windowid=1 which=0 state=0 x=244 y=228 xrel=-1 yrel=-2)
Relative mode True
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5812 windowid=1 which=0 state=0 x=244 y=226 xrel=0 yrel=-2)
Relative mode False
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=5820 windowid=1 which=0 state=0 x=244 y=224 xrel=0 yrel=-2)

I’ve already spent considerable time trying to figure out why this is happening, but thought I would open an issue in the mean time. It would seem the bad event is arriving via

https://github.com/libsdl-org/SDL/blob/3a1317ed47698c268574e1d0018edd6485f5dbc4/src/video/windows/SDL_windowsevents.c#L635

aka the non-rawinput flow. It looks to be some kind of race condition or timing issue. I am aware that this is a pain point on windows (and there are already weird workaround required for recent windows updates, although that looks specific to the alternate warp-mode) but this is one of the main issues we are facing with getting our game working as a user would expect.

Any direction or feedback would be appreciated.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 29 (12 by maintainers)

Commits related to this issue

Most upvoted comments

This is hopefully fixed by #4831. Any testing would be appreciated.

I think this is finally fixed. Can you try it now?

Either way, I still recommend using the hint to reduce load on the window system with double-warping.

Flushing pending mouse motion when we warp takes care of this. Can you try the latest code and let me know if there are other issues?

This has regressed with recent relative mode changes. I haven’t looked into this, but it does mean for the time being we can’t track upstream changes with our fork any more (as the related relative mouse code has been changed quite a lot).

On a quick tests with a similar scenario to the isolated one reported in this thread:

https://user-images.githubusercontent.com/191335/136323585-f3f06091-8923-46d0-963e-93f5084e0cdc.mp4

For reference, here’s how it looked prior to recent changes:

https://user-images.githubusercontent.com/191335/136323362-d2c7c53b-6ed7-4f49-9d48-6be40cd826e7.mp4

I’m looking at a solution for this. First of all, let me better explain the interactions at play here:

The crux of this issue looks to be that WM_MOUSEMOVE does not arrive immediately. The failing flow is:

SDL_SetRelativeMouseMode(TRUE) // application call
    // warps mouse to centre (#1)
    WIN_WarpMouse
    SetCursorPos

SDL_SetRelativeMouseMode(FALSE) // application call
    // warps mouse to true location (#2)
    WIN_WarpMouse
    SetCursorPos

// WM_MOUSEMOVE arrives for above SetCursorPos #1
INFO: SDL EVENT: SDL_MOUSEMOTION (timestamp=116167 windowid=1 which=0 state=0 x=250 y=238 xrel=-210 yrel=-200)

SDL_SetRelativeMouseMode(TRUE) // application call, before #2 MOUSEMOVE arrives

// WM_MOUSEMOVE never arrives for above SetCursorPos #2 (but why?)
// if the second event does arrive (can happen depending on timing) there is an equal relative jump back to correct position.

I have two proposed directions to fix this.

First is that we are only running into this issue due to the necessity of toggling relative mode off when the user reaches the edge of the window. This is because in our scenario, we don’t implicitly want mouse “grab” to enabled when relative mode is. Currently this is enforced:

https://github.com/libsdl-org/SDL/blob/2689e844e0ab60e48052504f87efcbd6e766b227/src/video/SDL_video.c#L2657-L2661

By changing the logic here to make mouse grab optional would remove the need for us to handle this manually via a full SetRelativeMode toggle from our end. Of course, this needs API consideration, as it may overlap with existing exposed method SDL_SetWindowGrab (already usable for this purpose, but overwritten by the next call to SDL_UpdateWindowGrab).

Second would be to remove all “warp to center” logic in the case that relative_mode_warp is not set. This includes a “custom” implementation of ClipCursor which uses a 2x2 pixel area at the center of the window (which even on blaming back to the beginning, I can’t find reasoning for its presence). I have done this and it looks to be working quite well, so will PR this solution for further discussion.