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.