Bug 96982

Summary: xf86-input-libinput: Rough and lopsided mouse movement in games/apps that reposition mouse
Product: Wayland Reporter: Danni H <dannihfoss>
Component: libinputAssignee: Wayland bug list <wayland-bugs>
Status: RESOLVED NOTOURBUG QA Contact:
Severity: normal    
Priority: medium CC: peter.hutterer
Version: 1.3.0   
Hardware: All   
OS: Linux (All)   
Whiteboard:
i915 platform: i915 features:
Attachments: listprops.txt
evemu.txt

Description Danni H 2016-07-18 23:26:26 UTC
Created attachment 125136 [details]
listprops.txt

When using xf86-input-libinput as the input driver in X11, the mouse movement is very stair-steppy and biased toward moving up and to the left. While it looks like this issue has already been reported as bug #83674 and marked invalid, it actually affects a wide range of applications, not just Clutter.

This issue does not occur when using xf86-input-evdev as the input driver but I'd really prefer to use libinput as the acceleration curve feels much more natural than evdev.

My understanding of this issue is this: Many games and applications implement mouse movement ("mouselook") by constantly repositioning the mouse cursor in the center of the window or screen and then track the distance the mouse has moved away from this center to determine movement.

The problem is that when the mouse is getting repositioned, it looks like the libinput driver is chopping off the fractional portion of the position, meaning that if the mouse cursor's X position is at, say, 160.8, it becomes 160.0. The evdev driver preserves the fractional, so a coordinate of 160.8 stays at 160.8 - moreover, if the mouse is at X coordinate 159.8 and the cursor is moved to 160, the position is now at 160.8, not 160.0.

The reason for preserving the fractional is to account for very slow mouse movements. If the mouse is moved to the right or down a fractional amount, this movement is lost, since moving the mouse to X coordinate 160.1 will place it back at 160.0 - this means that a significant amount of effort is needed to move the mouse down or to the right without it "eating" the input. Conversely, if moving the mouse ever so slightly to the left - say from X coordinate 160.0 to 159.99, it will suddenly snap over to 159.0, making movement very lopsided going up and toward the left.

I understand that constantly repositioning the mouse cursor is not the ideal solution. However, many games do this and most will probably never be fixed to do it the right way (e.g. ask the window system to trap the cursor inside a box and get raw mouse input). This is especially true of indie developers, who release a large number of games to make a living and do not have the time or money to continuously maintain their older games, nor do they have the means to make the source of those games available for others to fix these issues.

Applications with this issue off the top of my head:

- SLADE, in 3D editing mode
- Slime Rancher
- Joy Exhibition (itch.io)
- Probably just about any Unity3D-powered game now that I think about it

If the game window hides the mouse cursor, the effect can be more closely observed by opening an always-on-top window above it (such as Yakuake), which should make the mouse cursor reappear while still having it be trapped.

I do not know where exactly in the stack this issue occurs, so I would appreciate any help with triaging this issue. If I know what part of the stack is responsible for the issue I can at least patch my own system.
Comment 1 Danni H 2016-07-18 23:27:53 UTC
Created attachment 125137 [details]
evemu.txt

Not at all relevant, because evemu does not record any mouse reposition events, but is listed here for completeness as it's under "required information for triage". This is simply me attempting to move the mouse in a circle.
Comment 2 Peter Hutterer 2016-07-21 00:58:09 UTC
get libinput from git and build it with the gtk+-devel package installed at configure time. That gives you the tools/event-gui binary we use for testing. When you run this tool (as root) can you reproduce the weird mouse movement? If not then the issue is somewhere above libinput.
Comment 3 Danni H 2016-07-21 01:45:06 UTC
Mouse movement inside tools/event-gui is fine, but it's also not repositioning the mouse cursor (plus it looks like it's reading raw mouse input rather than the window system's mouse position). Would this mean the issue itself is inside xf86-input-libinput?
Comment 4 Peter Hutterer 2016-07-21 05:21:37 UTC
(In reply to Danni H from comment #3)
> Mouse movement inside tools/event-gui is fine, but it's also not
> repositioning the mouse cursor (plus it looks like it's reading raw mouse
> input rather than the window system's mouse position). Would this mean the
> issue itself is inside xf86-input-libinput?

yeah, that tool takes the output from libinput directly. so if the tool behaves fine but X doesn't then the issue is somewhere higher than libinput. what versions of everything do you have? xserver, libinput, xf86-input-libinput, ...
Comment 5 Danni H 2016-07-21 23:07:33 UTC
Installed packages are from the Arch Linux repos:

libinput 1.3.3-1
xf86-input-libinput 0.19.0-1
xorg-server 1.18.3-2
linux 4.6.3-1

Any other package versions you're looking for?

The issue's been around since I initially started using xf86-input-libinput.
Comment 6 Peter Hutterer 2016-08-04 23:17:23 UTC
ok, confirmed with JoyExhibition (the only one I could get for free, SLADE doesn't currently compile on F24). I'll look into that
Comment 7 Peter Hutterer 2016-08-05 00:29:16 UTC
This is a bug in Unity3D, the libinput driver passes on the events correctly but because we do pointer acceleration inside libinput, slow motion is mostly subpixel motion. We also have a different accel methods, so subpixel motion is more common. In evdev, the driver itself does not usually handle subpixel motion, coordinates submitted to the server are always in whole integers (see below though).

The odd thing about unity3d here is that it continuously sends WarpPointer requests (and to which the server replies with a core Motion event). unity3d does this that even while the mouse isn't moving. Note that core motion events do not contain subpixel information and the server buffers that internally until a full pixel motion has been reached. Also note that internally the server only deals with absolute coordinates once the immediate event has been dealt with (i.e. scaled into screen coordinates and/or accelerated). The server picks the correct device to warp, so normally you'd have a sequence like this:

* pointer is at 100.0/100.0
* mouse moves by 0.2/0.0 pixels
* mouse moves by 0.5/0.0 pixels
* mouse moves by 0.4/0.0  pixels
* server sends motion event for 101/100, position internally is 101.1/100

unity3d keeps sending WarpPointer requests and by definition they move the mouse onto the given position. Since WarpPointer is a core request it doesn't do subpixel. So now you get this sequence:

* pointer is at 100.0/100.0
* mouse moves by 0.2/0.0 pixels
* mouse moves by 0.5/0.0 pixels
* WarpPointer resets to 100/100
* mouse moves by 0.4/0.0  pixels
* WarpPointer resets to 100/100
* mouse moves by 0.8/0.0  pixels
* WarpPointer resets to 100/100
* mouse moves by 0.8/0.0  pixels
* mouse moves by 0.6/0.0  pixels
* server sends motion event for 101/100, position internally is 101.4/100
* WarpPointer resets to 100/100

As you can see, some events are swallowed because the continuous WarpPointer requests simply undo any internal buffering the server does. This can be reproduced with the synaptics driver which also does acceleration and submits subpixel motion and the evdev driver when the Option Resolution is set to something higher than the mouse resolution (and thus most motion events are subpixel motion).

So I'm going to have to punt this to Unity3D, it's broken with all drivers that send subpixel motion.
Comment 8 Danni H 2016-08-05 00:53:54 UTC
I have several questions still:

You mention that subpixel movement is buffered inside the server. Would this make it an X.org Server bug, then? Does WarpPointer take integer or float arguments for the destination coordinates? Will this bug remain after the move to Wayland? That is probably what concerns me the most right now.

> The odd thing about unity3d here is that it continuously sends WarpPointer
> requests (and to which the server replies with a core Motion event). unity3d
> does this that even while the mouse isn't moving.

I believe this is also fairly standard practice among applications that use this method of mouse movement (and why I don't think this is strictly Unity3D's fault - see SLADE also has this issue). Additionally, if the application were to reposition the mouse pointer only if the mouse were moving, it would still result in the fractional becoming lost, so movement would still be somewhat lopsided.

If I propose a patch, will it be considered for inclusion? Or, is there some workaround that can be applied here to the hundreds of Unity3D games that will basically never receive an update to fix the issue?
Comment 9 Danni H 2016-08-05 01:06:35 UTC
Correct me if I'm wrong but it looks like WarpPointer takes ints for the destination coordinates:

https://tronche.com/gui/x/xlib/input/XWarpPointer.html

So presumably the fix would be inside the server's implementation of WarpPointer?
Comment 10 Peter Hutterer 2016-08-05 01:26:41 UTC
(In reply to Danni H from comment #8)
> You mention that subpixel movement is buffered inside the server. Would this
> make it an X.org Server bug, then? Does WarpPointer take integer or float
> arguments for the destination coordinates? Will this bug remain after the
> move to Wayland? That is probably what concerns me the most right now.

WarpPointer predates subpixel coordinates by about 20 years :)
so no, it only takes integer coordinates. There is an XIWarpPointer request that takes subpixel coordinates but that requires switching to XI2 (which is only... 8 years old?) we cannot change the WarpPointer protocol request itself because that's ABI and has been for 20 years. 

Either way, since this is a protocol restriction it will not affect Wayland clients directly but likely still be an issue when XWayland is in play.

> > The odd thing about unity3d here is that it continuously sends WarpPointer
> > requests (and to which the server replies with a core Motion event). unity3d
> > does this that even while the mouse isn't moving.
> 
> I believe this is also fairly standard practice among applications that use
> this method of mouse movement (and why I don't think this is strictly
> Unity3D's fault - see SLADE also has this issue). Additionally, if the
> application were to reposition the mouse pointer only if the mouse were
> moving, it would still result in the fractional becoming lost, so movement
> would still be somewhat lopsided.

yeah, I agree, it'd still not be right but slightly less of an issue. The thing is: you still have this issue with evdev whenever pointer acceleration causes subpixel motion (I'd have to figure out when exactly this happens), it's just not as obvious.

You can somewhat work around this by using the libinput "flat" acceleration profile. In that mode no pointer acceleration is performed by libinput and motion is sent as-is to the server (which also doesn't accelerate). So you only get full-pixel motion and many people claim it provides a more natural feel for games anyway (ymmv).

> If I propose a patch, will it be considered for inclusion? Or, is there some
> workaround that can be applied here to the hundreds of Unity3D games that
> will basically never receive an update to fix the issue?

tbh, I don't know *how* to fix this (short of the above workaround). I contemplated the idea of making the xserver aware about within-pixel warp requests so that it retains the subpixel data when we're warping from 100.4 to 100. That would be relatively easy but it would cause odd and inconsistent behaviour once we go beyond the first pixel so that's a no-go. And short of that hack I cannot come up with a satisfying solution.
Comment 11 Danni H 2016-08-05 01:58:28 UTC
Hmm, what sort of odd and inconsistent behavior? Do you have an example? My understanding is that moving the mouse 5.5 pixels from 100 to 105.5 would result in mouse pointer integral at 105 with 0.5 leftover, with the mouse moving another 0.5 the next frame to bring it to 106. Is there a corner case I'm not aware of?

If this isn't a satisfactory workaround, I think at least we should add 0.5 to the destination coordinates when warping the pointer - this would at least center the mouse pointer in sub-pixel space around the target pixel, such that it would take an equal amount of movement in any direction to move to the next pixel. It wouldn't be as smooth as evdev but it would at least be less lopsided and it wouldn't feel quite so much like pushing a bus up a hill.
Comment 12 Peter Hutterer 2016-08-05 02:48:55 UTC
my thought about in-pixel warping would've caused a warp to 100 to behave differently dependent on the distance:
* from 100.3 it would warp to 100.3
* from 100.9 it would warp to 100.9
* from 101.1 it would warp to 100.0
* from 110.9 it would warp to 100.0

because once you get outside of the single pixel range, you need to reset subpixel information. This wouldn't be a problem for large warps because relying on subpixel there doesn't matter anyway. but it would be problematic for exactly your use-case where we sometimes hit a pixel, sometimes not. it would require some magic threshold of "keep the subpixel if warping less than 10 pixels" or something like that but that sounds a bit too magic.

a 0.5 base position is an interesting idea though it will need special casing for screen edges because a half-pixel would trigger a move to the next screen. I don't think there should be any ill effects for anything else.
it would be something worth trying if you have the time.
Comment 13 Danni H 2016-08-07 16:14:57 UTC
Hi, where would be a good place to discuss the implementation of a possible fix for this issue?

I'm not really sure why you would need to reset subpixel information when traveling large distances. Wouldn't one simply be able to do something like this:

xPos += floor(destinationX) - floor(sourceX);

This would move the cursor while preserving the fractional because it would only ever adjust the mouse position by some number of whole pixels.
Comment 14 Peter Hutterer 2016-08-08 02:50:53 UTC
(In reply to Danni H from comment #13)
> Hi, where would be a good place to discuss the implementation of a possible
> fix for this issue?

best is the xorg-devel list, this is core X server stuff.

> I'm not really sure why you would need to reset subpixel information when
> traveling large distances. Wouldn't one simply be able to do something like
> this:
> 
> xPos += floor(destinationX) - floor(sourceX);
> 
> This would move the cursor while preserving the fractional because it would
> only ever adjust the mouse position by some number of whole pixels.

well, that's the bit up for discussion. Because we're only dealing with subpixel here I think we should be fine with the 0.5 offset but my gut tells me warping a request of 0/0 to 0.9/0.9 because that's the leftover is wrong. There are a few places where subpixels matter (iirc pointer barries for example) and you may different behaviour depending on whether there's a remainder or not.

Use of freedesktop.org services, including Bugzilla, is subject to our Code of Conduct. How we collect and use information is described in our Privacy Policy.