Skip to content

Commit 35cc58e

Browse files
committed
wayland: Run cursor animations on a thread
If the main event handling thread runs slowly, so will cursor animations. Use a dedicated thread for cursor surface events, so that animations will always run at a consistent rate.
1 parent 776d11a commit 35cc58e

File tree

5 files changed

+242
-24
lines changed

5 files changed

+242
-24
lines changed

src/video/wayland/SDL_waylandevents.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2356,6 +2356,8 @@ static const struct wl_keyboard_listener keyboard_listener = {
23562356

23572357
static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event)
23582358
{
2359+
Wayland_SeatDestroyCursorFrameCallback(seat);
2360+
23592361
// End any active gestures.
23602362
if (seat->pointer.gesture_focus) {
23612363
SDL_SendPinch(SDL_EVENT_PINCH_END, 0, seat->pointer.gesture_focus->sdlwindow, 0.0f);
@@ -2388,10 +2390,6 @@ static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event)
23882390
zwp_pointer_gesture_pinch_v1_destroy(seat->pointer.gesture_pinch);
23892391
}
23902392

2391-
if (seat->pointer.cursor_state.frame_callback) {
2392-
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
2393-
}
2394-
23952393
if (seat->pointer.cursor_state.surface) {
23962394
wl_surface_destroy(seat->pointer.cursor_state.surface);
23972395
}

src/video/wayland/SDL_waylandevents_c.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,10 @@ typedef struct SDL_WaylandSeat
184184

185185
// Animation state for cursors
186186
void *cursor_handle;
187+
188+
// The cursor animation thread lock must be held when modifying this.
187189
struct wl_callback *frame_callback;
190+
188191
Uint64 last_frame_callback_time_ms;
189192
Uint32 current_frame_time_ms;
190193
int current_frame;

src/video/wayland/SDL_waylandmouse.c

Lines changed: 233 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,12 @@
2323

2424
#ifdef SDL_VIDEO_DRIVER_WAYLAND
2525

26+
#include <errno.h>
27+
2628
#include "../SDL_sysvideo.h"
2729
#include "../SDL_video_c.h"
2830

31+
#include "../../core/unix/SDL_poll.h"
2932
#include "../../events/SDL_mouse_c.h"
3033
#include "SDL_waylandvideo.h"
3134
#include "../SDL_pixels_c.h"
@@ -305,6 +308,164 @@ static struct wl_buffer *Wayland_SeatGetCursorFrame(SDL_WaylandSeat *seat, int f
305308
return NULL;
306309
}
307310

311+
static struct CursorThreadContext
312+
{
313+
SDL_Thread *thread;
314+
struct wl_event_queue *queue;
315+
struct wl_proxy *compositor_wrapper;
316+
SDL_Mutex *lock;
317+
bool should_exit;
318+
} cursor_thread_context;
319+
320+
static void handle_cursor_thread_exit(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
321+
{
322+
wl_callback_destroy(wl_callback);
323+
cursor_thread_context.should_exit = true;
324+
}
325+
326+
static const struct wl_callback_listener cursor_thread_exit_listener = {
327+
handle_cursor_thread_exit
328+
};
329+
330+
static int SDLCALL Wayland_CursorThreadFunc(void *data)
331+
{
332+
struct wl_display *display = data;
333+
const int display_fd = WAYLAND_wl_display_get_fd(display);
334+
int ret;
335+
336+
/* The lock must be held whenever dispatching to avoid a race condition when setting
337+
* or destroying cursor frame callbacks, as adding the callback followed by setting
338+
* the listener is not an atomic operation, and the callback proxy must not be
339+
* destroyed while in the callback handler.
340+
*
341+
* Any error other than EAGAIN is fatal and causes the thread to exit.
342+
*/
343+
while (!cursor_thread_context.should_exit) {
344+
if (WAYLAND_wl_display_prepare_read_queue(display, cursor_thread_context.queue) == 0) {
345+
Sint64 timeoutNS = -1;
346+
347+
ret = WAYLAND_wl_display_flush(display);
348+
349+
if (ret < 0) {
350+
if (errno == EAGAIN) {
351+
// If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events.
352+
timeoutNS = SDL_MS_TO_NS(1);
353+
} else {
354+
WAYLAND_wl_display_cancel_read(display);
355+
return -1;
356+
}
357+
}
358+
359+
// Wait for a read/write operation to become possible.
360+
ret = SDL_IOReady(display_fd, SDL_IOR_READ, timeoutNS);
361+
362+
if (ret <= 0) {
363+
WAYLAND_wl_display_cancel_read(display);
364+
if (ret < 0) {
365+
return -1;
366+
}
367+
368+
// Nothing to read, and woke to flush; try again.
369+
continue;
370+
}
371+
372+
ret = WAYLAND_wl_display_read_events(display);
373+
if (ret == -1) {
374+
return -1;
375+
}
376+
}
377+
378+
SDL_LockMutex(cursor_thread_context.lock);
379+
ret = WAYLAND_wl_display_dispatch_queue_pending(display, cursor_thread_context.queue);
380+
SDL_UnlockMutex(cursor_thread_context.lock);
381+
382+
if (ret < 0) {
383+
return -1;
384+
}
385+
}
386+
387+
return 0;
388+
}
389+
390+
static bool Wayland_StartCursorThread(SDL_VideoData *data)
391+
{
392+
if (!cursor_thread_context.thread) {
393+
cursor_thread_context.queue = WAYLAND_wl_display_create_queue(data->display);
394+
if (!cursor_thread_context.queue) {
395+
goto cleanup;
396+
}
397+
398+
cursor_thread_context.compositor_wrapper = WAYLAND_wl_proxy_create_wrapper(data->compositor);
399+
if (!cursor_thread_context.compositor_wrapper) {
400+
goto cleanup;
401+
}
402+
WAYLAND_wl_proxy_set_queue(cursor_thread_context.compositor_wrapper, cursor_thread_context.queue);
403+
404+
cursor_thread_context.lock = SDL_CreateMutex();
405+
if (!cursor_thread_context.lock) {
406+
goto cleanup;
407+
}
408+
409+
cursor_thread_context.thread = SDL_CreateThread(Wayland_CursorThreadFunc, "wl_cursor_surface", data->display);
410+
if (!cursor_thread_context.thread) {
411+
goto cleanup;
412+
}
413+
414+
return true;
415+
}
416+
417+
cleanup:
418+
if (cursor_thread_context.lock) {
419+
SDL_DestroyMutex(cursor_thread_context.lock);
420+
}
421+
422+
if (cursor_thread_context.compositor_wrapper) {
423+
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
424+
}
425+
426+
if (cursor_thread_context.queue) {
427+
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
428+
}
429+
430+
SDL_zero(cursor_thread_context);
431+
432+
return false;
433+
}
434+
435+
static void Wayland_DestroyCursorThread(SDL_VideoData *data)
436+
{
437+
if (cursor_thread_context.thread) {
438+
// Dispatch the exit event to unblock the animation thread and signal it to exit.
439+
struct wl_proxy *display_wrapper = WAYLAND_wl_proxy_create_wrapper(data->display);
440+
WAYLAND_wl_proxy_set_queue(display_wrapper, cursor_thread_context.queue);
441+
442+
SDL_LockMutex(cursor_thread_context.lock);
443+
struct wl_callback *cb = wl_display_sync((struct wl_display *)display_wrapper);
444+
wl_callback_add_listener(cb, &cursor_thread_exit_listener, NULL);
445+
SDL_UnlockMutex(cursor_thread_context.lock);
446+
447+
WAYLAND_wl_proxy_wrapper_destroy(display_wrapper);
448+
449+
int ret = WAYLAND_wl_display_flush(data->display);
450+
if (ret == -1 && errno == EAGAIN) {
451+
// The timeout is long, but shutting down the thread requires a successful flush.
452+
ret = SDL_IOReady(WAYLAND_wl_display_get_fd(data->display), SDL_IOR_WRITE, SDL_MS_TO_NS(1000));
453+
if (ret >= 0) {
454+
ret = WAYLAND_wl_display_flush(data->display);
455+
}
456+
}
457+
458+
// Wait for the thread to return. Don't wait if the flush failed, or this can hang.
459+
if (ret >= 0) {
460+
SDL_WaitThread(cursor_thread_context.thread, NULL);
461+
}
462+
463+
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
464+
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
465+
SDL_zero(cursor_thread_context);
466+
}
467+
}
468+
308469
static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time);
309470
static const struct wl_callback_listener cursor_frame_listener = {
310471
cursor_frame_done
@@ -362,6 +523,51 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
362523
wl_surface_commit(seat->pointer.cursor_state.surface);
363524
}
364525

526+
void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat)
527+
{
528+
if (cursor_thread_context.lock) {
529+
SDL_LockMutex(cursor_thread_context.lock);
530+
}
531+
532+
seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
533+
wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
534+
535+
if (cursor_thread_context.lock) {
536+
SDL_UnlockMutex(cursor_thread_context.lock);
537+
}
538+
}
539+
540+
void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat)
541+
{
542+
if (cursor_thread_context.lock) {
543+
SDL_LockMutex(cursor_thread_context.lock);
544+
}
545+
546+
if (seat->pointer.cursor_state.frame_callback) {
547+
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
548+
seat->pointer.cursor_state.frame_callback = NULL;
549+
}
550+
551+
if (cursor_thread_context.lock) {
552+
SDL_UnlockMutex(cursor_thread_context.lock);
553+
}
554+
}
555+
556+
static void Wayland_SeatResetCursorAnimation(SDL_WaylandSeat *seat, bool lock)
557+
{
558+
if (lock && cursor_thread_context.lock) {
559+
SDL_LockMutex(cursor_thread_context.lock);
560+
}
561+
562+
seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks();
563+
seat->pointer.cursor_state.current_frame_time_ms = 0;
564+
seat->pointer.cursor_state.current_frame = 0;
565+
566+
if (lock && cursor_thread_context.lock) {
567+
SDL_UnlockMutex(cursor_thread_context.lock);
568+
}
569+
}
570+
365571
static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cdata, struct wl_cursor *cursor, int size)
366572
{
367573
Wayland_CachedSystemCursor *cache = NULL;
@@ -718,10 +924,8 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
718924
wl_list_for_each (seat, &video_data->seat_list, link)
719925
{
720926
if (seat->pointer.current_cursor == d) {
721-
if (seat->pointer.cursor_state.frame_callback) {
722-
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
723-
seat->pointer.cursor_state.frame_callback = NULL;
724-
}
927+
Wayland_SeatDestroyCursorFrameCallback(seat);
928+
725929
if (seat->pointer.cursor_state.surface) {
726930
wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0);
727931
}
@@ -851,13 +1055,17 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
8511055
int hot_y;
8521056

8531057
// Stop the frame callback for old animated cursors.
854-
if (seat->pointer.cursor_state.frame_callback && cursor_data != seat->pointer.current_cursor) {
855-
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
856-
seat->pointer.cursor_state.frame_callback = NULL;
1058+
if (cursor_data != seat->pointer.current_cursor) {
1059+
Wayland_SeatDestroyCursorFrameCallback(seat);
8571060
}
8581061

8591062
if (cursor) {
8601063
if (cursor_data == seat->pointer.current_cursor) {
1064+
// Restart the animation sequence if the cursor didn't change.
1065+
if (cursor_data->num_frames > 1) {
1066+
Wayland_SeatResetCursorAnimation(seat, true);
1067+
}
1068+
8611069
return;
8621070
}
8631071

@@ -895,21 +1103,16 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
8951103
seat->pointer.current_cursor = cursor_data;
8961104

8971105
if (!seat->pointer.cursor_state.surface) {
898-
seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
1106+
if (cursor_thread_context.compositor_wrapper) {
1107+
seat->pointer.cursor_state.surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper);
1108+
} else {
1109+
seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
1110+
}
8991111
}
9001112

9011113
struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, 0);
9021114
wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
9031115

904-
// If more than one frame is available, create a frame callback to run the animation.
905-
if (cursor_data->num_frames > 1) {
906-
seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks();
907-
seat->pointer.cursor_state.current_frame_time_ms = 0;
908-
seat->pointer.cursor_state.current_frame = 0;
909-
seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
910-
wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
911-
}
912-
9131116
// A scale value of 0 indicates that a viewport with the returned destination size should be used.
9141117
if (!scale) {
9151118
if (!seat->pointer.cursor_state.viewport) {
@@ -934,8 +1137,15 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
9341137
wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
9351138
}
9361139

1140+
// If more than one frame is available, create a frame callback to run the animation.
1141+
if (cursor_data->num_frames > 1) {
1142+
Wayland_SeatResetCursorAnimation(seat, false);
1143+
Wayland_SeatSetCursorFrameCallback(seat);
1144+
}
1145+
9371146
wl_surface_commit(seat->pointer.cursor_state.surface);
9381147
} else {
1148+
Wayland_SeatDestroyCursorFrameCallback(seat);
9391149
seat->pointer.current_cursor = NULL;
9401150
wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0);
9411151
}
@@ -1180,7 +1390,7 @@ void Wayland_RecreateCursors(void)
11801390
}
11811391
#endif // 0
11821392

1183-
void Wayland_InitMouse(void)
1393+
void Wayland_InitMouse(SDL_VideoData *data)
11841394
{
11851395
SDL_Mouse *mouse = SDL_GetMouse();
11861396

@@ -1194,6 +1404,10 @@ void Wayland_InitMouse(void)
11941404
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
11951405
mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
11961406

1407+
if (!Wayland_StartCursorThread(data)) {
1408+
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread");
1409+
}
1410+
11971411
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
11981412
while (r <= SDL_HITTEST_RESIZE_LEFT) {
11991413
switch (r) {
@@ -1248,6 +1462,7 @@ void Wayland_InitMouse(void)
12481462

12491463
void Wayland_FiniMouse(SDL_VideoData *data)
12501464
{
1465+
Wayland_DestroyCursorThread(data);
12511466
Wayland_FreeCursorThemes(data);
12521467

12531468
#ifdef SDL_USE_LIBDBUS

src/video/wayland/SDL_waylandmouse.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@
2424
#ifndef SDL_waylandmouse_h_
2525
#define SDL_waylandmouse_h_
2626

27-
extern void Wayland_InitMouse(void);
27+
extern void Wayland_InitMouse(SDL_VideoData *data);
2828
extern void Wayland_FiniMouse(SDL_VideoData *data);
2929
extern void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat);
3030
extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y);
31+
extern void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat);
32+
extern void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat);
3133
#if 0 // TODO RECONNECT: See waylandvideo.c for more information!
3234
extern void Wayland_RecreateCursors(void);
3335
#endif // 0

src/video/wayland/SDL_waylandvideo.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1452,7 +1452,7 @@ bool Wayland_VideoInit(SDL_VideoDevice *_this)
14521452

14531453
Wayland_FinalizeDisplays(data);
14541454

1455-
Wayland_InitMouse();
1455+
Wayland_InitMouse(data);
14561456
Wayland_InitKeyboard(_this);
14571457

14581458
if (data->primary_selection_device_manager) {

0 commit comments

Comments
 (0)