SDL: evdev: Batch mouse axis updates until SYN_REPORT

From 574db63c8e3ae602e265bf29f6fd8706e6b593c6 Mon Sep 17 00:00:00 2001
From: Cameron Gutman <[EMAIL REDACTED]>
Date: Sun, 31 Jul 2022 12:36:11 -0500
Subject: [PATCH] evdev: Batch mouse axis updates until SYN_REPORT

This is necessary for consistent position reports with absolute mice
and improves application performance with relative mice by cutting the
number of reported mouse motion events roughly in half.
---
 src/core/linux/SDL_evdev.c | 44 +++++++++++++++++++++++++++++---------
 1 file changed, 34 insertions(+), 10 deletions(-)

diff --git a/src/core/linux/SDL_evdev.c b/src/core/linux/SDL_evdev.c
index 208b3c1f161..bb940ce2b76 100644
--- a/src/core/linux/SDL_evdev.c
+++ b/src/core/linux/SDL_evdev.c
@@ -96,8 +96,12 @@ typedef struct SDL_evdevlist_item
 
     } * touchscreen_data;
 
+    /* Mouse state */
     SDL_bool high_res_wheel;
     SDL_bool high_res_hwheel;
+    SDL_bool relative_mouse;
+    int mouse_x, mouse_y;
+    int mouse_wheel, mouse_hwheel;
 
     struct SDL_evdevlist_item *next;
 } SDL_evdevlist_item;
@@ -361,16 +365,20 @@ SDL_EVDEV_Poll(void)
                             if (item->touchscreen_data->max_slots != 1)
                                 break;
                             item->touchscreen_data->slots[0].x = events[i].value;
-                        } else
-                            SDL_SendMouseMotion(mouse->focus, mouse->mouseID, SDL_FALSE, events[i].value, mouse->y);
+                        } else if (!item->relative_mouse) {
+                            /* FIXME: Normalize to input device's reported input range (EVIOCGABS) */
+                            item->mouse_x = events[i].value;
+                        }
                         break;
                     case ABS_Y:
                         if (item->is_touchscreen) {
                             if (item->touchscreen_data->max_slots != 1)
                                 break;
                             item->touchscreen_data->slots[0].y = events[i].value;
-                        } else
-                            SDL_SendMouseMotion(mouse->focus, mouse->mouseID, SDL_FALSE, mouse->x, events[i].value);
+                        } else if (!item->relative_mouse) {
+                            /* FIXME: Normalize to input device's reported input range (EVIOCGABS) */
+                            item->mouse_y = events[i].value;
+                        }
                         break;
                     default:
                         break;
@@ -379,26 +387,28 @@ SDL_EVDEV_Poll(void)
                 case EV_REL:
                     switch(events[i].code) {
                     case REL_X:
-                        SDL_SendMouseMotion(mouse->focus, mouse->mouseID, SDL_TRUE, events[i].value, 0);
+                        if (item->relative_mouse)
+                            item->mouse_x += events[i].value;
                         break;
                     case REL_Y:
-                        SDL_SendMouseMotion(mouse->focus, mouse->mouseID, SDL_TRUE, 0, events[i].value);
+                        if (item->relative_mouse)
+                            item->mouse_y += events[i].value;
                         break;
                     case REL_WHEEL:
                         if (!item->high_res_wheel)
-                            SDL_SendMouseWheel(mouse->focus, mouse->mouseID, 0, events[i].value, SDL_MOUSEWHEEL_NORMAL);
+                            item->mouse_wheel += events[i].value;
                         break;
                     case REL_WHEEL_HI_RES:
                         SDL_assert(item->high_res_wheel);
-                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, 0, events[i].value / 120.0f, SDL_MOUSEWHEEL_NORMAL);
+                        item->mouse_wheel += events[i].value;
                         break;
                     case REL_HWHEEL:
                         if (!item->high_res_hwheel)
-                            SDL_SendMouseWheel(mouse->focus, mouse->mouseID, events[i].value, 0, SDL_MOUSEWHEEL_NORMAL);
+                            item->mouse_hwheel += events[i].value;
                         break;
                     case REL_HWHEEL_HI_RES:
                         SDL_assert(item->high_res_hwheel);
-                        SDL_SendMouseWheel(mouse->focus, mouse->mouseID, events[i].value / 120.0f, 0, SDL_MOUSEWHEEL_NORMAL);
+                        item->mouse_hwheel += events[i].value;
                         break;
                     default:
                         break;
@@ -407,6 +417,19 @@ SDL_EVDEV_Poll(void)
                 case EV_SYN:
                     switch (events[i].code) {
                     case SYN_REPORT:
+                        /* Send mouse axis changes together to ensure consistency and reduce event processing overhead */
+                        if (item->mouse_x != 0 || item->mouse_y != 0) {
+                            SDL_SendMouseMotion(mouse->focus, mouse->mouseID, item->relative_mouse, item->mouse_x, item->mouse_y);
+                            item->mouse_x = item->mouse_y = 0;
+                        }
+                        if (item->mouse_wheel != 0 || item->mouse_hwheel != 0) {
+                            SDL_SendMouseWheel(mouse->focus, mouse->mouseID,
+                                               item->mouse_hwheel / (item->high_res_hwheel ? 120.0f : 1.0f),
+                                               item->mouse_wheel / (item->high_res_wheel ? 120.0f : 1.0f),
+                                               SDL_MOUSEWHEEL_NORMAL);
+                            item->mouse_wheel = item->mouse_hwheel = 0;
+                        }
+
                         if (!item->is_touchscreen) /* FIXME: temp hack */
                             break;
 
@@ -760,6 +783,7 @@ SDL_EVDEV_device_added(const char *dev_path, int udev_class)
     }
 
     if (ioctl(item->fd, EVIOCGBIT(EV_REL, sizeof(relbit)), relbit) >= 0) {
+        item->relative_mouse = test_bit(REL_X, relbit) && test_bit(REL_Y, relbit);
         item->high_res_wheel = test_bit(REL_WHEEL_HI_RES, relbit);
         item->high_res_hwheel = test_bit(REL_HWHEEL_HI_RES, relbit);
     }