https://bugs.winehq.org/show_bug.cgi?id=56208
Bug ID: 56208 Summary: Inconsistent mouse speed tied to frame rate and mouse polling rate Product: Wine Version: 9.0 Hardware: x86-64 OS: Linux Status: UNCONFIRMED Severity: normal Priority: P2 Component: -unknown Assignee: wine-bugs@winehq.org Reporter: korbel00@gmail.com Distribution: ---
Created attachment 75922 --> https://bugs.winehq.org/attachment.cgi?id=75922 logs and patch
In Secret World Legends the actual mouse speed depends on the current frame rate in Wine which does not seem to happen in Windows. (if it does, it's not noticeable for me)
Looking around with the camera, the mouse speeds up and slows down randomly, making the game really hard to control if possible at all. It's even worse when a fight is going on in one direction, and there nothing in the other, making it impossible to turn around. (I have ~30 FPS looking into a fight and ~300 FPS looking away, the mouse movement slows down so much that I cannot do a 180 turn)
I attempted to do some investigation and I think I could pinpoint the problem which I'll try to explain here.
The game loop does the following to control the camera (this is an assumption based on my calculations and on the logged relay messages):
``` loop { screen_movement := ZERO last_position := GetCursorPos()
SetCursorPos(CENTER_OF_SCREEN) // reset cursor
while msg = PeekMessage() { if (msg.type == WM_MOUSEMOVE) { screen_movement += msg->position - last_position last_position = msg->position } translate and dispatch msg }
convert `screen_movement` to camera movement and do some stuff } ```
My tests and calculations confirms it: - At 50FPS, it takes 2906px raw movement to do a 360 turn at 1000Hz polling rate - At 100FPS, it takes 4129px raw movement to do a 360 turn at 1000Hz polling rate
In both cases, logging out the PeekMessage values and running the algorithm above on the logs gave me approx 2080px on-screen movement which the game translated into a 360 turn.
Interestingly at low polling rates the pattern reverses: high FPS gives faster movement speed than low FPS.
For the algorithm to work, there are some assumption made: - every mouse motion must enter the message queue - there cannot be a new motion between calling GetCursorPos and SetCursorPos, and the next motion after SetCursorPos should be the center of the screen.
With wine, neither of the conditions stands: - GetCursorPos by default gives back the position from the wine server, even though the display driver may have unsent motion messages in the output buffer. - SetCursorPos will tell X11DRV_MotionNotify to ignore all these unprocessed motions using the "warp_serial" value. The ignored messages won't translate to camera movements and will get lost.
I modified the code so only the raw inputs get processed, effectively ignoring all X11DRV_MotionNotify events, and commenting out all code that attempts to sync the graphics driver position. This does not cause problems in this case because for every frame SetCursorPos sends the center of the screen both directly to the message queue and the graphics driver. It solved the issue and the mouse movements became very smooth and uniform however it is not a proper solution.
I will attach logs of me doing a 360 turn on both 50FPS and 100FPS.
To sum the raw values I used this script:
``` cat messages.log | grep map_raw_event_coords | sed -E 's/.*input (-?[[:digit:]]+).*/\1/' | awk '{ sum += $1}; END { print sum }' ```
To run the algorithm which I think the game is doing (sum the PeekMessage deltas) to calculate the total motion I used this:
``` cat messages.log | awk 'BEGIN { last = 960; sum = 0 }; { if (match($0, "peek_message WM_MOUSEMOVE: \(([[:digit:]]+) ", m)) { sum += m[1] - last; last = m[1] } else if (match($0, "X11DRV_SetCursorPos warped to ([[:digit:]]+)", m)) { last = m[1] } }; END { print sum }' ```
I will also attach the stripped down version of the relay log of the game loop and my attempt at patching wine 9.0 git version.
https://bugs.winehq.org/show_bug.cgi?id=56208
--- Comment #1 from Sandor korbel00@gmail.com --- I let the problem sink in and reread what I wrote, and I think I need to make some corrections.
there cannot be a new motion between calling GetCursorPos and SetCursorPos, and the next motion after SetCursorPos should be the center of the screen.
It's not necessary for the next motion after SetCursorPos to be the coordinates of the SetCursorPos call. What's important is that if the game calls GetCursorPos and SetCursorPos after each other, the positions must show up in the message queue after each other in the same order sometime in the future. I.e. GetCursorPos returns (X1,Y1) and then I call SetCursorPos (X2,Y2) immediately after, then I expect to receive an (X1,Y1) and a (X2,Y2) after each other using the PeekMessage call. But (X1,Y1) is not there right now.
GetCursorPos by default gives back the position from the wine server
This is incorrect but apparently it look like that in the logs. X11 does not take into account the output buffer, and sends back an "old" position (the last known position by the wine server) which is the cause of the whole issue. I think if we could somehow force X11 to send all buffered events to wine before returning the latest cursor position, there wouldn't be any ignored events after SetCursorPos and it would solve the issue. Maybe calling XSync(display, TRUE) before getting the actual cursor position would help? Not sure about that. After a quick try it didn't work out too well.
Putting this aside, I still think raw input events is the way to go and it does not make much sense to me to listen and process the X11DRV_MouseMotion events at the same time for the same events and sync the X11 position to the wine server regularly. X11DRV_MouseMotion messages should be filtered and the sync should happen in the other direction in this case.
https://bugs.winehq.org/show_bug.cgi?id=56208
Rémi Bernon rbernon@codeweavers.com changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |rbernon@codeweavers.com
--- Comment #2 from Rémi Bernon rbernon@codeweavers.com --- I think we'll need to keep the absolute positions somehow, in order for the Wine cursor position to be kept in sync with X11. Using rawinput relative input only doesn't seem realistic.
Then, my plan is find a way to listen to input separately from the windows message polling, because that simply breaks too many things, and is suboptimal anyway. I am currently investigating some idea to have a X11 client inside wineserver, which would do that very nicely.
Once this is done, and assuming that input is continuously listened on regardless of what the applications are doing, it will be possible to ditch GetCursorPos roundtrip to the X server entirely and assume that wineserver position is the reference position.
The way it currently works is broken anyway, as going to X11 only from time to time you just get an up to date position once in a while, and an outdated position every other time. But it is still required for now because of applications which use GetCursorPos but never poll their messages.
https://bugs.winehq.org/show_bug.cgi?id=56208
jacobbrett+winehqbugs@jacobbrett.id.au changed:
What |Removed |Added ---------------------------------------------------------------------------- CC| |jacobbrett+winehqbugs@jacob | |brett.id.au