mirror of
https://github.com/libsdl-org/SDL.git
synced 2026-06-06 14:43:20 +00:00
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.
This commit is contained in:
parent
776d11a9c8
commit
35cc58e027
5 changed files with 242 additions and 24 deletions
|
|
@ -2356,6 +2356,8 @@ static const struct wl_keyboard_listener keyboard_listener = {
|
|||
|
||||
static void Wayland_SeatDestroyPointer(SDL_WaylandSeat *seat, bool send_event)
|
||||
{
|
||||
Wayland_SeatDestroyCursorFrameCallback(seat);
|
||||
|
||||
// End any active gestures.
|
||||
if (seat->pointer.gesture_focus) {
|
||||
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)
|
|||
zwp_pointer_gesture_pinch_v1_destroy(seat->pointer.gesture_pinch);
|
||||
}
|
||||
|
||||
if (seat->pointer.cursor_state.frame_callback) {
|
||||
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
|
||||
}
|
||||
|
||||
if (seat->pointer.cursor_state.surface) {
|
||||
wl_surface_destroy(seat->pointer.cursor_state.surface);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -184,7 +184,10 @@ typedef struct SDL_WaylandSeat
|
|||
|
||||
// Animation state for cursors
|
||||
void *cursor_handle;
|
||||
|
||||
// The cursor animation thread lock must be held when modifying this.
|
||||
struct wl_callback *frame_callback;
|
||||
|
||||
Uint64 last_frame_callback_time_ms;
|
||||
Uint32 current_frame_time_ms;
|
||||
int current_frame;
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@
|
|||
|
||||
#ifdef SDL_VIDEO_DRIVER_WAYLAND
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include "../SDL_sysvideo.h"
|
||||
#include "../SDL_video_c.h"
|
||||
|
||||
#include "../../core/unix/SDL_poll.h"
|
||||
#include "../../events/SDL_mouse_c.h"
|
||||
#include "SDL_waylandvideo.h"
|
||||
#include "../SDL_pixels_c.h"
|
||||
|
|
@ -305,6 +308,164 @@ static struct wl_buffer *Wayland_SeatGetCursorFrame(SDL_WaylandSeat *seat, int f
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static struct CursorThreadContext
|
||||
{
|
||||
SDL_Thread *thread;
|
||||
struct wl_event_queue *queue;
|
||||
struct wl_proxy *compositor_wrapper;
|
||||
SDL_Mutex *lock;
|
||||
bool should_exit;
|
||||
} cursor_thread_context;
|
||||
|
||||
static void handle_cursor_thread_exit(void *data, struct wl_callback *wl_callback, uint32_t callback_data)
|
||||
{
|
||||
wl_callback_destroy(wl_callback);
|
||||
cursor_thread_context.should_exit = true;
|
||||
}
|
||||
|
||||
static const struct wl_callback_listener cursor_thread_exit_listener = {
|
||||
handle_cursor_thread_exit
|
||||
};
|
||||
|
||||
static int SDLCALL Wayland_CursorThreadFunc(void *data)
|
||||
{
|
||||
struct wl_display *display = data;
|
||||
const int display_fd = WAYLAND_wl_display_get_fd(display);
|
||||
int ret;
|
||||
|
||||
/* The lock must be held whenever dispatching to avoid a race condition when setting
|
||||
* or destroying cursor frame callbacks, as adding the callback followed by setting
|
||||
* the listener is not an atomic operation, and the callback proxy must not be
|
||||
* destroyed while in the callback handler.
|
||||
*
|
||||
* Any error other than EAGAIN is fatal and causes the thread to exit.
|
||||
*/
|
||||
while (!cursor_thread_context.should_exit) {
|
||||
if (WAYLAND_wl_display_prepare_read_queue(display, cursor_thread_context.queue) == 0) {
|
||||
Sint64 timeoutNS = -1;
|
||||
|
||||
ret = WAYLAND_wl_display_flush(display);
|
||||
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN) {
|
||||
// If the flush failed with EAGAIN, don't block as not to inhibit other threads from reading events.
|
||||
timeoutNS = SDL_MS_TO_NS(1);
|
||||
} else {
|
||||
WAYLAND_wl_display_cancel_read(display);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for a read/write operation to become possible.
|
||||
ret = SDL_IOReady(display_fd, SDL_IOR_READ, timeoutNS);
|
||||
|
||||
if (ret <= 0) {
|
||||
WAYLAND_wl_display_cancel_read(display);
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Nothing to read, and woke to flush; try again.
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = WAYLAND_wl_display_read_events(display);
|
||||
if (ret == -1) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
SDL_LockMutex(cursor_thread_context.lock);
|
||||
ret = WAYLAND_wl_display_dispatch_queue_pending(display, cursor_thread_context.queue);
|
||||
SDL_UnlockMutex(cursor_thread_context.lock);
|
||||
|
||||
if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool Wayland_StartCursorThread(SDL_VideoData *data)
|
||||
{
|
||||
if (!cursor_thread_context.thread) {
|
||||
cursor_thread_context.queue = WAYLAND_wl_display_create_queue(data->display);
|
||||
if (!cursor_thread_context.queue) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cursor_thread_context.compositor_wrapper = WAYLAND_wl_proxy_create_wrapper(data->compositor);
|
||||
if (!cursor_thread_context.compositor_wrapper) {
|
||||
goto cleanup;
|
||||
}
|
||||
WAYLAND_wl_proxy_set_queue(cursor_thread_context.compositor_wrapper, cursor_thread_context.queue);
|
||||
|
||||
cursor_thread_context.lock = SDL_CreateMutex();
|
||||
if (!cursor_thread_context.lock) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
cursor_thread_context.thread = SDL_CreateThread(Wayland_CursorThreadFunc, "wl_cursor_surface", data->display);
|
||||
if (!cursor_thread_context.thread) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
if (cursor_thread_context.lock) {
|
||||
SDL_DestroyMutex(cursor_thread_context.lock);
|
||||
}
|
||||
|
||||
if (cursor_thread_context.compositor_wrapper) {
|
||||
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
|
||||
}
|
||||
|
||||
if (cursor_thread_context.queue) {
|
||||
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
|
||||
}
|
||||
|
||||
SDL_zero(cursor_thread_context);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void Wayland_DestroyCursorThread(SDL_VideoData *data)
|
||||
{
|
||||
if (cursor_thread_context.thread) {
|
||||
// Dispatch the exit event to unblock the animation thread and signal it to exit.
|
||||
struct wl_proxy *display_wrapper = WAYLAND_wl_proxy_create_wrapper(data->display);
|
||||
WAYLAND_wl_proxy_set_queue(display_wrapper, cursor_thread_context.queue);
|
||||
|
||||
SDL_LockMutex(cursor_thread_context.lock);
|
||||
struct wl_callback *cb = wl_display_sync((struct wl_display *)display_wrapper);
|
||||
wl_callback_add_listener(cb, &cursor_thread_exit_listener, NULL);
|
||||
SDL_UnlockMutex(cursor_thread_context.lock);
|
||||
|
||||
WAYLAND_wl_proxy_wrapper_destroy(display_wrapper);
|
||||
|
||||
int ret = WAYLAND_wl_display_flush(data->display);
|
||||
if (ret == -1 && errno == EAGAIN) {
|
||||
// The timeout is long, but shutting down the thread requires a successful flush.
|
||||
ret = SDL_IOReady(WAYLAND_wl_display_get_fd(data->display), SDL_IOR_WRITE, SDL_MS_TO_NS(1000));
|
||||
if (ret >= 0) {
|
||||
ret = WAYLAND_wl_display_flush(data->display);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for the thread to return. Don't wait if the flush failed, or this can hang.
|
||||
if (ret >= 0) {
|
||||
SDL_WaitThread(cursor_thread_context.thread, NULL);
|
||||
}
|
||||
|
||||
WAYLAND_wl_proxy_wrapper_destroy(cursor_thread_context.compositor_wrapper);
|
||||
WAYLAND_wl_event_queue_destroy(cursor_thread_context.queue);
|
||||
SDL_zero(cursor_thread_context);
|
||||
}
|
||||
}
|
||||
|
||||
static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time);
|
||||
static const struct wl_callback_listener cursor_frame_listener = {
|
||||
cursor_frame_done
|
||||
|
|
@ -362,6 +523,51 @@ static void cursor_frame_done(void *data, struct wl_callback *cb, uint32_t time)
|
|||
wl_surface_commit(seat->pointer.cursor_state.surface);
|
||||
}
|
||||
|
||||
void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat)
|
||||
{
|
||||
if (cursor_thread_context.lock) {
|
||||
SDL_LockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
|
||||
seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
|
||||
wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
|
||||
|
||||
if (cursor_thread_context.lock) {
|
||||
SDL_UnlockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
}
|
||||
|
||||
void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat)
|
||||
{
|
||||
if (cursor_thread_context.lock) {
|
||||
SDL_LockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
|
||||
if (seat->pointer.cursor_state.frame_callback) {
|
||||
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
|
||||
seat->pointer.cursor_state.frame_callback = NULL;
|
||||
}
|
||||
|
||||
if (cursor_thread_context.lock) {
|
||||
SDL_UnlockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
}
|
||||
|
||||
static void Wayland_SeatResetCursorAnimation(SDL_WaylandSeat *seat, bool lock)
|
||||
{
|
||||
if (lock && cursor_thread_context.lock) {
|
||||
SDL_LockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
|
||||
seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks();
|
||||
seat->pointer.cursor_state.current_frame_time_ms = 0;
|
||||
seat->pointer.cursor_state.current_frame = 0;
|
||||
|
||||
if (lock && cursor_thread_context.lock) {
|
||||
SDL_UnlockMutex(cursor_thread_context.lock);
|
||||
}
|
||||
}
|
||||
|
||||
static Wayland_CachedSystemCursor *Wayland_CacheSystemCursor(SDL_CursorData *cdata, struct wl_cursor *cursor, int size)
|
||||
{
|
||||
Wayland_CachedSystemCursor *cache = NULL;
|
||||
|
|
@ -718,10 +924,8 @@ static void Wayland_FreeCursorData(SDL_CursorData *d)
|
|||
wl_list_for_each (seat, &video_data->seat_list, link)
|
||||
{
|
||||
if (seat->pointer.current_cursor == d) {
|
||||
if (seat->pointer.cursor_state.frame_callback) {
|
||||
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
|
||||
seat->pointer.cursor_state.frame_callback = NULL;
|
||||
}
|
||||
Wayland_SeatDestroyCursorFrameCallback(seat);
|
||||
|
||||
if (seat->pointer.cursor_state.surface) {
|
||||
wl_surface_attach(seat->pointer.cursor_state.surface, NULL, 0, 0);
|
||||
}
|
||||
|
|
@ -851,13 +1055,17 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
|
|||
int hot_y;
|
||||
|
||||
// Stop the frame callback for old animated cursors.
|
||||
if (seat->pointer.cursor_state.frame_callback && cursor_data != seat->pointer.current_cursor) {
|
||||
wl_callback_destroy(seat->pointer.cursor_state.frame_callback);
|
||||
seat->pointer.cursor_state.frame_callback = NULL;
|
||||
if (cursor_data != seat->pointer.current_cursor) {
|
||||
Wayland_SeatDestroyCursorFrameCallback(seat);
|
||||
}
|
||||
|
||||
if (cursor) {
|
||||
if (cursor_data == seat->pointer.current_cursor) {
|
||||
// Restart the animation sequence if the cursor didn't change.
|
||||
if (cursor_data->num_frames > 1) {
|
||||
Wayland_SeatResetCursorAnimation(seat, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -895,21 +1103,16 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
|
|||
seat->pointer.current_cursor = cursor_data;
|
||||
|
||||
if (!seat->pointer.cursor_state.surface) {
|
||||
seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
|
||||
if (cursor_thread_context.compositor_wrapper) {
|
||||
seat->pointer.cursor_state.surface = wl_compositor_create_surface((struct wl_compositor *)cursor_thread_context.compositor_wrapper);
|
||||
} else {
|
||||
seat->pointer.cursor_state.surface = wl_compositor_create_surface(seat->display->compositor);
|
||||
}
|
||||
}
|
||||
|
||||
struct wl_buffer *buffer = Wayland_SeatGetCursorFrame(seat, 0);
|
||||
wl_surface_attach(seat->pointer.cursor_state.surface, buffer, 0, 0);
|
||||
|
||||
// If more than one frame is available, create a frame callback to run the animation.
|
||||
if (cursor_data->num_frames > 1) {
|
||||
seat->pointer.cursor_state.last_frame_callback_time_ms = SDL_GetTicks();
|
||||
seat->pointer.cursor_state.current_frame_time_ms = 0;
|
||||
seat->pointer.cursor_state.current_frame = 0;
|
||||
seat->pointer.cursor_state.frame_callback = wl_surface_frame(seat->pointer.cursor_state.surface);
|
||||
wl_callback_add_listener(seat->pointer.cursor_state.frame_callback, &cursor_frame_listener, seat);
|
||||
}
|
||||
|
||||
// A scale value of 0 indicates that a viewport with the returned destination size should be used.
|
||||
if (!scale) {
|
||||
if (!seat->pointer.cursor_state.viewport) {
|
||||
|
|
@ -934,8 +1137,15 @@ static void Wayland_SeatSetCursor(SDL_WaylandSeat *seat, SDL_Cursor *cursor)
|
|||
wl_surface_damage(seat->pointer.cursor_state.surface, 0, 0, SDL_MAX_SINT32, SDL_MAX_SINT32);
|
||||
}
|
||||
|
||||
// If more than one frame is available, create a frame callback to run the animation.
|
||||
if (cursor_data->num_frames > 1) {
|
||||
Wayland_SeatResetCursorAnimation(seat, false);
|
||||
Wayland_SeatSetCursorFrameCallback(seat);
|
||||
}
|
||||
|
||||
wl_surface_commit(seat->pointer.cursor_state.surface);
|
||||
} else {
|
||||
Wayland_SeatDestroyCursorFrameCallback(seat);
|
||||
seat->pointer.current_cursor = NULL;
|
||||
wl_pointer_set_cursor(seat->pointer.wl_pointer, seat->pointer.enter_serial, NULL, 0, 0);
|
||||
}
|
||||
|
|
@ -1180,7 +1390,7 @@ void Wayland_RecreateCursors(void)
|
|||
}
|
||||
#endif // 0
|
||||
|
||||
void Wayland_InitMouse(void)
|
||||
void Wayland_InitMouse(SDL_VideoData *data)
|
||||
{
|
||||
SDL_Mouse *mouse = SDL_GetMouse();
|
||||
|
||||
|
|
@ -1194,6 +1404,10 @@ void Wayland_InitMouse(void)
|
|||
mouse->SetRelativeMouseMode = Wayland_SetRelativeMouseMode;
|
||||
mouse->GetGlobalMouseState = Wayland_GetGlobalMouseState;
|
||||
|
||||
if (!Wayland_StartCursorThread(data)) {
|
||||
SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "wayland: Failed to start cursor animation event thread");
|
||||
}
|
||||
|
||||
SDL_HitTestResult r = SDL_HITTEST_NORMAL;
|
||||
while (r <= SDL_HITTEST_RESIZE_LEFT) {
|
||||
switch (r) {
|
||||
|
|
@ -1248,6 +1462,7 @@ void Wayland_InitMouse(void)
|
|||
|
||||
void Wayland_FiniMouse(SDL_VideoData *data)
|
||||
{
|
||||
Wayland_DestroyCursorThread(data);
|
||||
Wayland_FreeCursorThemes(data);
|
||||
|
||||
#ifdef SDL_USE_LIBDBUS
|
||||
|
|
|
|||
|
|
@ -24,10 +24,12 @@
|
|||
#ifndef SDL_waylandmouse_h_
|
||||
#define SDL_waylandmouse_h_
|
||||
|
||||
extern void Wayland_InitMouse(void);
|
||||
extern void Wayland_InitMouse(SDL_VideoData *data);
|
||||
extern void Wayland_FiniMouse(SDL_VideoData *data);
|
||||
extern void Wayland_SeatUpdateCursor(SDL_WaylandSeat *seat);
|
||||
extern void Wayland_SeatWarpMouse(SDL_WaylandSeat *seat, SDL_WindowData *window, float x, float y);
|
||||
extern void Wayland_SeatSetCursorFrameCallback(SDL_WaylandSeat *seat);
|
||||
extern void Wayland_SeatDestroyCursorFrameCallback(SDL_WaylandSeat *seat);
|
||||
#if 0 // TODO RECONNECT: See waylandvideo.c for more information!
|
||||
extern void Wayland_RecreateCursors(void);
|
||||
#endif // 0
|
||||
|
|
|
|||
|
|
@ -1452,7 +1452,7 @@ bool Wayland_VideoInit(SDL_VideoDevice *_this)
|
|||
|
||||
Wayland_FinalizeDisplays(data);
|
||||
|
||||
Wayland_InitMouse();
|
||||
Wayland_InitMouse(data);
|
||||
Wayland_InitKeyboard(_this);
|
||||
|
||||
if (data->primary_selection_device_manager) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue