From d9159fefdd9e8850e550dd47cc59d7da999a2380 Mon Sep 17 00:00:00 2001 From: lizzie Date: Mon, 14 Sep 2026 03:26:22 +0200 Subject: [PATCH] [input] Fix mouse drift on Qt/SDL frontends (#4352) Fixes regression from #4229 Mouse wasn't updated periodically to prevent "infinite drift". Timer for polling is now controlled by frontend directly, this should reduce latency considerably and wasted cycles spinwaiting, it also removes an extra thread which is always welcome. - [x] I have read and followed the [Contribution Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/CONTRIBUTING.md#code-contributions). - [x] I have read and followed the [AI Policy](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/AI.md) - [x] I have read and followed the [Coding Guidelines](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/policies/Coding.md) to the best of my ability. ------------------- Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4352 Reviewed-by: CamilleLaVey Reviewed-by: MaranBr --- src/input_common/drivers/mouse.cpp | 107 +++++++++----------- src/input_common/drivers/mouse.h | 7 +- src/yuzu/bootmanager.cpp | 33 +++--- src/yuzu/bootmanager.h | 2 + src/yuzu_cmd/emu_window/emu_window_sdl3.cpp | 6 ++ src/yuzu_cmd/emu_window/emu_window_sdl3.h | 3 + 6 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 4444f0ac3b..4ac259d03b 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -4,17 +4,16 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include +#include #include #include #include "common/param_package.h" #include "common/settings.h" -#include "common/thread.h" +#include "common/steady_clock.h" #include "input_common/drivers/mouse.h" namespace InputCommon { -constexpr int update_time = 10; constexpr float default_panning_sensitivity = 0.0010f; constexpr float default_stick_sensitivity = 0.0006f; constexpr float default_deadzone_counterweight = 0.01f; @@ -74,33 +73,31 @@ Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) last_motion_change = {}; } -void Mouse::UpdateStickInput() { - if (!IsMousePanningEnabled()) { - return; - } - - const float length = last_mouse_change.Length(); +void Mouse::UpdateStickInput(Common::SteadyClock::time_point timestamp) { + if (IsMousePanningEnabled()) { + const float length = last_mouse_change.Length(); - // Prevent input from exceeding the max range (1.0f) too much, - // but allow some room to make it easier to sustain - if (length > maximum_stick_range) { - last_mouse_change /= length; - last_mouse_change *= maximum_stick_range; - } + // Prevent input from exceeding the max range (1.0f) too much, + // but allow some room to make it easier to sustain + if (length > maximum_stick_range) { + last_mouse_change /= length; + last_mouse_change *= maximum_stick_range; + } - SetAxis(identifier, mouse_axis_x, last_mouse_change[0]); - SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]); + SetAxis(identifier, mouse_axis_x, last_mouse_change[0]); + SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]); - // Decay input over time - const float clamped_length = (std::min)(1.0f, length); - const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); - const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; - const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); - const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay); - last_mouse_change *= clamped_decay; + // Decay input over time + const float clamped_length = (std::min)(1.0f, length); + const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); + const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; + const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); + const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay); + last_mouse_change *= clamped_decay; + } } -void Mouse::UpdateMotionInput() { +void Mouse::UpdateMotionInput(Common::SteadyClock::time_point timestamp) { const float sensitivity = IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity; @@ -114,23 +111,21 @@ void Mouse::UpdateMotionInput() { last_motion_change[1] = last_motion_change[1] * multiplier; } - const BasicMotion motion_data{ - .gyro_x = last_motion_change[0] * sensitivity, - .gyro_y = last_motion_change[1] * sensitivity, - .gyro_z = last_motion_change[2] * sensitivity, - .accel_x = 0, - .accel_y = 0, - .accel_z = 0, - .delta_timestamp = update_time * 1000, - }; - if (IsMousePanningEnabled()) { last_motion_change[0] = 0; last_motion_change[1] = 0; } last_motion_change[2] = 0; - SetMotion(motion_identifier, 0, motion_data); + SetMotion(motion_identifier, 0, BasicMotion{ + .gyro_x = last_motion_change[0] * sensitivity, + .gyro_y = last_motion_change[1] * sensitivity, + .gyro_z = last_motion_change[2] * sensitivity, + .accel_x = 0, + .accel_y = 0, + .accel_z = 0, + .delta_timestamp = u64(std::chrono::duration_cast(timestamp - last_notify_timestamp).count()), + }); } void Mouse::Move(int x, int y, int center_x, int center_y) { @@ -149,29 +144,27 @@ void Mouse::Move(int x, int y, int center_x, int center_y) { last_mouse_change /= length; last_mouse_change *= deadzone_cw; } - return; - } - - if (button_pressed) { - const auto mouse_move = Common::Vec(x, y) - mouse_origin; - const float x_sensitivity = - Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; - const float y_sensitivity = - Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; - SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity); - SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity); - - last_motion_change = { - float(-mouse_move[1]) * x_sensitivity, - float(-mouse_move[0]) * y_sensitivity, - last_motion_change[2], - }; + } else { + if (button_pressed) { + const auto mouse_move = Common::Vec(x, y) - mouse_origin; + const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; + const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; + SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity); + SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity); + last_motion_change = { + float(-mouse_move[1]) * x_sensitivity, + float(-mouse_move[0]) * y_sensitivity, + last_motion_change[2], + }; + } } } void Mouse::NotifyChanged() { - UpdateStickInput(); - UpdateMotionInput(); + auto const timestamp = Common::SteadyClock::Now(); + UpdateStickInput(timestamp); + UpdateMotionInput(timestamp); + last_notify_timestamp = timestamp; } void Mouse::MouseMove(f32 touch_x, f32 touch_y) { @@ -223,8 +216,8 @@ void Mouse::MouseWheelChange(int x, int y) { wheel_position[0] += x; wheel_position[1] += y; last_motion_change[2] += static_cast(y); - SetAxis(identifier, wheel_axis_x, static_cast(wheel_position[0])); - SetAxis(identifier, wheel_axis_y, static_cast(wheel_position[1])); + SetAxis(identifier, wheel_axis_x, f32(wheel_position[0])); + SetAxis(identifier, wheel_axis_y, f32(wheel_position[1])); } void Mouse::ReleaseAllButtons() { diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 25f75109b9..1f7cb7e966 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -7,7 +7,9 @@ #pragma once #include +#include +#include "common/steady_clock.h" #include "common/polyfill_thread.h" #include "common/vector_math.h" #include "input_common/input_engine.h" @@ -101,8 +103,8 @@ public: Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: - void UpdateStickInput(); - void UpdateMotionInput(); + void UpdateStickInput(Common::SteadyClock::time_point timestamp); + void UpdateMotionInput(Common::SteadyClock::time_point timestamp); bool IsMousePanningEnabled(); Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; @@ -112,6 +114,7 @@ private: Common::Vec last_mouse_change; Common::Vec last_motion_change; Common::Vec wheel_position; + Common::SteadyClock::time_point last_notify_timestamp{}; bool button_pressed = false; }; diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index f9c0233901..308db3621b 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -73,6 +73,7 @@ class QPaintEngine; class QSurface; constexpr int default_mouse_constrain_timeout = 10; +constexpr int default_mouse_update_timeout = 5; class RenderWidget : public QWidget { public: @@ -138,6 +139,10 @@ GRenderWindow::GRenderWindow(MainWindow* parent, mouse_constrain_timer.setInterval(default_mouse_constrain_timeout); connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse); + + mouse_update_timer.setInterval(default_mouse_update_timeout); + connect(&mouse_update_timer, &QTimer::timeout, this, &GRenderWindow::UpdateMouse); + mouse_update_timer.start(); } void GRenderWindow::ExecuteProgram(std::size_t program_index) { @@ -487,7 +492,6 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) { input_subsystem->GetMouse()->PressMouseButton(button); input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), button); input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, button); - input_subsystem->GetMouse()->NotifyChanged(); emit MouseActivity(); } @@ -508,7 +512,6 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { input_subsystem->GetMouse()->MouseMove(touch_x, touch_y); input_subsystem->GetMouse()->TouchMove(touch_x, touch_y); input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y); - input_subsystem->GetMouse()->NotifyChanged(); // Center mouse for mouse panning if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { @@ -518,8 +521,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { // Constrain mouse for mouse emulation with mouse panning if (Settings::values.mouse_panning && Settings::values.mouse_enabled) { const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y); - QCursor::setPos(mapToGlobal( - QPoint{static_cast(clamped_mouse_x), static_cast(clamped_mouse_y)})); + QCursor::setPos(mapToGlobal(QPoint{int(clamped_mouse_x), int(clamped_mouse_y)})); } mouse_constrain_timer.stop(); @@ -534,16 +536,10 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { const auto button = QtButtonToMouseButton(event->button()); input_subsystem->GetMouse()->ReleaseButton(button); - input_subsystem->GetMouse()->NotifyChanged(); } void GRenderWindow::ConstrainMouse() { - if (QtCommon::emu_thread == nullptr || !Settings::values.mouse_panning) { - mouse_constrain_timer.stop(); - return; - } - - if (!this->isActiveWindow()) { + if (QtCommon::emu_thread == nullptr || Settings::values.mouse_panning || !this->isActiveWindow()) { mouse_constrain_timer.stop(); return; } @@ -552,22 +548,22 @@ void GRenderWindow::ConstrainMouse() { const auto pos = mapFromGlobal(QCursor::pos()); const int new_pos_x = std::clamp(pos.x(), 0, width()); const int new_pos_y = std::clamp(pos.y(), 0, height()); - QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y})); - return; + } else { + const int center_x = width() / 2; + const int center_y = height() / 2; + QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); } +} - const int center_x = width() / 2; - const int center_y = height() / 2; - - QCursor::setPos(mapToGlobal(QPoint{center_x, center_y})); +void GRenderWindow::UpdateMouse() { + input_subsystem->GetMouse()->NotifyChanged(); // required to reset mouse once it's no longer moved } void GRenderWindow::wheelEvent(QWheelEvent* event) { const int x = event->angleDelta().x(); const int y = event->angleDelta().y(); input_subsystem->GetMouse()->MouseWheelChange(x, y); - input_subsystem->GetMouse()->NotifyChanged(); } void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { @@ -716,7 +712,6 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { input_subsystem->GetKeyboard()->ReleaseAllKeys(); input_subsystem->GetTouchScreen()->ReleaseAllTouch(); input_subsystem->GetMouse()->ReleaseAllButtons(); - input_subsystem->GetMouse()->NotifyChanged(); } void GRenderWindow::resizeEvent(QResizeEvent* event) { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 1ed61a8191..bb18bbb3bb 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -144,6 +144,7 @@ private: void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); void ConstrainMouse(); + void UpdateMouse(); void RequestCameraCapture(); void OnCameraCapture(int requestId, const QImage& img); @@ -184,6 +185,7 @@ private: #endif QTimer mouse_constrain_timer; + QTimer mouse_update_timer; protected: void showEvent(QShowEvent* event) override; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp index cff2292bb2..950e001f81 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp @@ -37,10 +37,16 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co SDL_SetWindowTitle(this_->render_window, title.c_str()); return 2000; }, this); + mouse_timer = SDL_AddTimer(100, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 { + auto* this_ = (EmuWindow_SDL3*)userdata; + this_->input_subsystem->GetMouse()->NotifyChanged(); + return 100; + }, this); } EmuWindow_SDL3::~EmuWindow_SDL3() { SDL_RemoveTimer(titlebar_timer); + SDL_RemoveTimer(mouse_timer); system.HIDCore().UnloadInputDevices(); input_subsystem->Shutdown(); SDL_Quit(); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.h b/src/yuzu_cmd/emu_window/emu_window_sdl3.h index bf63b65554..ffabaeb143 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl3.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.h @@ -84,6 +84,9 @@ protected: /// Periodic changer of titlebar (independent of event loop) SDL_TimerID titlebar_timer; + // Mouse resetter once it + SDL_TimerID mouse_timer; + /// Is the window still open? bool is_open = true;