Fix T100855: Input while Blender is unresponsive exits under Wayland
Consume events in a thread to prevent Wayland's event buffer from overflowing Waylands internal buffer and closing the connection. From a users perspective this seemed like a crash. Details: - This is a workaround for a known bug in Wayland [0]. Threaded event handling has been if-defed so it can be removed when it's no longer needed. - GTK & QT use threaded event handling to avoid this problem (SDL on the other hand doesn't). - The complexity and number of locks needed to handle events in a separate thread is a significant down-side, but as far as I can see this is necessary. - Re-connecting to the Wayland server is possible but not practical as the OpenGL context is lost and as far as I can tell it's not possible to keep it active (see: D16492). [0]: https://gitlab.freedesktop.org/wayland/wayland/-/issues/159
This commit is contained in:
parent
f1646a4d5e
commit
37b256e26f
Notes:
blender-bot
2023-02-14 11:01:33 +01:00
Referenced by commitbbe7183cd3
, GHOST/Wayland: fix threaded event handling Referenced by commitc2c41fb14c
, Fix missing title-bar redrawing with Wayland & libdecor Referenced by issue #100855, GHOST/Wayland: Terminal Ctrl-C closes the window while Python is running
|
@ -77,6 +77,10 @@
|
|||
/* Logging, use `ghost.wl.*` prefix. */
|
||||
#include "CLG_log.h"
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
# include <pthread.h>
|
||||
#endif
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
static bool use_libdecor = true;
|
||||
# ifdef WITH_GHOST_WAYLAND_DYNLOAD
|
||||
|
@ -86,6 +90,12 @@ static bool has_libdecor = true;
|
|||
# endif
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Forward Declarations
|
||||
*
|
||||
* Control local functionality, compositors specific workarounds.
|
||||
* \{ */
|
||||
|
||||
static void keyboard_handle_key_repeat_cancel(struct GWL_Seat *seat);
|
||||
|
||||
static void output_handle_done(void *data, struct wl_output *wl_output);
|
||||
|
@ -105,6 +115,19 @@ static int gwl_registry_handler_interface_slot_from_string(const char *interface
|
|||
static const struct GWL_RegistryHandler *gwl_registry_handler_from_interface_slot(
|
||||
int interface_slot);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
static void gwl_display_event_thread_destroy(GWL_Display *display);
|
||||
|
||||
static void ghost_wl_display_lock_without_input(struct wl_display *wl_display,
|
||||
std::mutex *server_mutex);
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
/** In nearly all cases use `pushEvent_maybe_pending`
|
||||
* at least when called from WAYLAND callbacks. */
|
||||
#define pushEvent DONT_USE
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Workaround Compositor Specific Bugs
|
||||
* \{ */
|
||||
|
@ -835,6 +858,26 @@ struct GWL_Display {
|
|||
|
||||
struct zwp_pointer_constraints_v1 *wp_pointer_constraints = nullptr;
|
||||
struct zwp_pointer_gestures_v1 *wp_pointer_gestures = nullptr;
|
||||
|
||||
/* Threaded event handling. */
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/**
|
||||
* Run a thread that consumes events in the background.
|
||||
* Use `pthread` because `std::thread` leaks memory.
|
||||
*/
|
||||
pthread_t events_pthread = 0;
|
||||
/** Use to exit the event reading loop. */
|
||||
bool events_pthread_is_active = false;
|
||||
|
||||
/**
|
||||
* Events added from the event reading thread.
|
||||
* Added into the main event queue when on #GHOST_SystemWayland::processEvents.
|
||||
*/
|
||||
std::vector<GHOST_IEvent *> events_pending;
|
||||
/** Guard against multiple threads accessing `events_pending` at once. */
|
||||
std::mutex events_pending_mutex;
|
||||
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -845,6 +888,13 @@ struct GWL_Display {
|
|||
*/
|
||||
static void gwl_display_destroy(GWL_Display *display)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
if (display->events_pthread) {
|
||||
ghost_wl_display_lock_without_input(display->wl_display, display->system->server_mutex);
|
||||
display->events_pthread_is_active = false;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* For typical WAYLAND use this will always be set.
|
||||
* However when WAYLAND isn't running, this will early-exit and be null. */
|
||||
if (display->wl_registry) {
|
||||
|
@ -875,6 +925,13 @@ static void gwl_display_destroy(GWL_Display *display)
|
|||
::eglTerminate(eglGetDisplay(EGLNativeDisplayType(display->wl_display)));
|
||||
}
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
if (display->events_pthread) {
|
||||
gwl_display_event_thread_destroy(display);
|
||||
display->system->server_mutex->unlock();
|
||||
}
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
if (display->wl_display) {
|
||||
wl_display_disconnect(display->wl_display);
|
||||
}
|
||||
|
@ -1557,6 +1614,64 @@ static int ghost_wl_display_event_pump(struct wl_display *wl_display)
|
|||
return err;
|
||||
}
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
|
||||
static void ghost_wl_display_lock_without_input(struct wl_display *wl_display,
|
||||
std::mutex *server_mutex)
|
||||
{
|
||||
const int fd = wl_display_get_fd(wl_display);
|
||||
int state;
|
||||
do {
|
||||
state = file_descriptor_is_io_ready(fd, GWL_IOR_READ | GWL_IOR_NO_RETRY, 0);
|
||||
/* Re-check `state` with a lock held, needed to avoid holding the lock. */
|
||||
if (state == 0) {
|
||||
server_mutex->lock();
|
||||
state = file_descriptor_is_io_ready(fd, GWL_IOR_READ | GWL_IOR_NO_RETRY, 0);
|
||||
if (state == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (state == 0);
|
||||
}
|
||||
|
||||
static int ghost_wl_display_event_pump_from_thread(struct wl_display *wl_display,
|
||||
const int fd,
|
||||
std::mutex *server_mutex)
|
||||
{
|
||||
/* Based on SDL's `Wayland_PumpEvents`. */
|
||||
server_mutex->lock();
|
||||
int err = 0;
|
||||
if (wl_display_prepare_read(wl_display) == 0) {
|
||||
/* Use #GWL_IOR_NO_RETRY to ensure #SIGINT will break us out of our wait. */
|
||||
if (file_descriptor_is_io_ready(fd, GWL_IOR_READ | GWL_IOR_NO_RETRY, 0) > 0) {
|
||||
err = wl_display_read_events(wl_display);
|
||||
}
|
||||
else {
|
||||
wl_display_cancel_read(wl_display);
|
||||
}
|
||||
}
|
||||
else {
|
||||
int state;
|
||||
do {
|
||||
server_mutex->unlock();
|
||||
/* Wait for input (unlocked, so as not to block other threads). */
|
||||
state = file_descriptor_is_io_ready(fd, GWL_IOR_READ | GWL_IOR_NO_RETRY, INT32_MAX);
|
||||
server_mutex->lock();
|
||||
/* Re-check `state` with a lock held, needed to avoid holding the lock. */
|
||||
if (state > 0) {
|
||||
state = file_descriptor_is_io_ready(fd, GWL_IOR_READ | GWL_IOR_NO_RETRY, 0);
|
||||
if (state > 0) {
|
||||
err = wl_display_dispatch_pending(wl_display);
|
||||
}
|
||||
}
|
||||
} while (state > 0);
|
||||
}
|
||||
server_mutex->unlock();
|
||||
|
||||
return err;
|
||||
}
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
static size_t ghost_wl_shm_format_as_size(enum wl_shm_format format)
|
||||
{
|
||||
switch (format) {
|
||||
|
@ -1663,7 +1778,7 @@ static void keyboard_depressed_state_push_events_from_change(
|
|||
for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
|
||||
for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d < 0; d++) {
|
||||
const GHOST_TKey gkey = GHOST_KEY_MODIFIER_FROM_INDEX(i);
|
||||
seat->system->pushEvent(
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventKey(system->getMilliSeconds(), GHOST_kEventKeyUp, win, gkey, false));
|
||||
|
||||
CLOG_INFO(LOG, 2, "modifier (%d) up", i);
|
||||
|
@ -1673,7 +1788,7 @@ static void keyboard_depressed_state_push_events_from_change(
|
|||
for (int i = 0; i < GHOST_KEY_MODIFIER_NUM; i++) {
|
||||
for (int d = seat->key_depressed.mods[i] - key_depressed_prev.mods[i]; d > 0; d--) {
|
||||
const GHOST_TKey gkey = GHOST_KEY_MODIFIER_FROM_INDEX(i);
|
||||
seat->system->pushEvent(
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventKey(system->getMilliSeconds(), GHOST_kEventKeyDown, win, gkey, false));
|
||||
CLOG_INFO(LOG, 2, "modifier (%d) down", i);
|
||||
}
|
||||
|
@ -1721,12 +1836,13 @@ static void relative_pointer_handle_relative_motion_impl(GWL_Seat *seat,
|
|||
bounds.clampPoint(UNPACK2(seat->pointer.xy));
|
||||
}
|
||||
#endif
|
||||
seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
static void relative_pointer_handle_relative_motion(
|
||||
|
@ -1784,7 +1900,7 @@ static void dnd_events(const GWL_Seat *const seat, const GHOST_TEventType event)
|
|||
const uint64_t time = seat->system->getMilliSeconds();
|
||||
for (size_t i = 0; i < ARRAY_SIZE(ghost_wl_mime_preference_order_type); i++) {
|
||||
const GHOST_TDragnDropTypes type = ghost_wl_mime_preference_order_type[i];
|
||||
seat->system->pushEvent(
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventDragnDrop(time, event, type, win, UNPACK2(event_xy), nullptr));
|
||||
}
|
||||
}
|
||||
|
@ -2247,13 +2363,13 @@ static void data_device_handle_drop(void *data, struct wl_data_device * /*wl_dat
|
|||
|
||||
CLOG_INFO(LOG, 2, "drop_read_uris_fn file_count=%d", flist->count);
|
||||
const wl_fixed_t scale = win->scale();
|
||||
system->pushEvent(new GHOST_EventDragnDrop(system->getMilliSeconds(),
|
||||
GHOST_kEventDraggingDropDone,
|
||||
GHOST_kDragnDropTypeFilenames,
|
||||
win,
|
||||
wl_fixed_to_int(scale * xy[0]),
|
||||
wl_fixed_to_int(scale * xy[1]),
|
||||
flist));
|
||||
system->pushEvent_maybe_pending(new GHOST_EventDragnDrop(system->getMilliSeconds(),
|
||||
GHOST_kEventDraggingDropDone,
|
||||
GHOST_kDragnDropTypeFilenames,
|
||||
win,
|
||||
wl_fixed_to_int(scale * xy[0]),
|
||||
wl_fixed_to_int(scale * xy[1]),
|
||||
flist));
|
||||
}
|
||||
else if (ELEM(mime_receive, ghost_wl_mime_text_plain, ghost_wl_mime_text_utf8)) {
|
||||
/* TODO: enable use of internal functions 'txt_insert_buf' and
|
||||
|
@ -2461,12 +2577,13 @@ static void pointer_handle_enter(void *data,
|
|||
win->setCursorShape(win->getCursorShape());
|
||||
|
||||
const wl_fixed_t scale = win->scale();
|
||||
seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
static void pointer_handle_leave(void *data,
|
||||
|
@ -2500,12 +2617,13 @@ static void pointer_handle_motion(void *data,
|
|||
CLOG_INFO(LOG, 2, "motion");
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
const wl_fixed_t scale = win->scale();
|
||||
seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->pointer.xy[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
else {
|
||||
CLOG_INFO(LOG, 2, "motion (skipped)");
|
||||
|
@ -2562,7 +2680,7 @@ static void pointer_handle_button(void *data,
|
|||
|
||||
if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(new GHOST_EventButton(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventButton(
|
||||
seat->system->getMilliSeconds(), etype, win, ebutton, GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
}
|
||||
|
@ -2614,7 +2732,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/)
|
|||
if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
const int32_t discrete = seat->pointer_scroll.discrete_xy[1];
|
||||
seat->system->pushEvent(new GHOST_EventWheel(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventWheel(
|
||||
seat->system->getMilliSeconds(), win, std::signbit(discrete) ? +1 : -1));
|
||||
}
|
||||
seat->pointer_scroll.discrete_xy[0] = 0;
|
||||
|
@ -2625,7 +2743,7 @@ static void pointer_handle_frame(void *data, struct wl_pointer * /*wl_pointer*/)
|
|||
if (wl_surface *wl_surface_focus = seat->pointer.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
const wl_fixed_t scale = win->scale();
|
||||
seat->system->pushEvent(new GHOST_EventTrackpad(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventTrackpad(
|
||||
seat->system->getMilliSeconds(),
|
||||
win,
|
||||
GHOST_kTrackpadEventScroll,
|
||||
|
@ -2830,25 +2948,27 @@ static void gesture_pinch_handle_update(void *data,
|
|||
wl_fixed_to_int(win_scale * seat->pointer.xy[1]),
|
||||
};
|
||||
if (scale_as_delta_px) {
|
||||
seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(),
|
||||
win,
|
||||
GHOST_kTrackpadEventMagnify,
|
||||
event_xy[0],
|
||||
event_xy[1],
|
||||
scale_as_delta_px,
|
||||
0,
|
||||
false));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventTrackpad(seat->system->getMilliSeconds(),
|
||||
win,
|
||||
GHOST_kTrackpadEventMagnify,
|
||||
event_xy[0],
|
||||
event_xy[1],
|
||||
scale_as_delta_px,
|
||||
0,
|
||||
false));
|
||||
}
|
||||
|
||||
if (rotation_as_delta_px) {
|
||||
seat->system->pushEvent(new GHOST_EventTrackpad(seat->system->getMilliSeconds(),
|
||||
win,
|
||||
GHOST_kTrackpadEventRotate,
|
||||
event_xy[0],
|
||||
event_xy[1],
|
||||
rotation_as_delta_px,
|
||||
0,
|
||||
false));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventTrackpad(seat->system->getMilliSeconds(),
|
||||
win,
|
||||
GHOST_kTrackpadEventRotate,
|
||||
event_xy[0],
|
||||
event_xy[1],
|
||||
rotation_as_delta_px,
|
||||
0,
|
||||
false));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3144,7 +3264,7 @@ static void tablet_tool_handle_down(void *data,
|
|||
|
||||
if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(new GHOST_EventButton(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventButton(
|
||||
seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data));
|
||||
}
|
||||
}
|
||||
|
@ -3162,7 +3282,7 @@ static void tablet_tool_handle_up(void *data, struct zwp_tablet_tool_v2 * /*zwp_
|
|||
|
||||
if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(new GHOST_EventButton(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventButton(
|
||||
seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data));
|
||||
}
|
||||
}
|
||||
|
@ -3247,7 +3367,8 @@ static void tablet_tool_handle_wheel(void *data,
|
|||
GWL_Seat *seat = tablet_tool->seat;
|
||||
if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(new GHOST_EventWheel(seat->system->getMilliSeconds(), win, clicks));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventWheel(seat->system->getMilliSeconds(), win, clicks));
|
||||
}
|
||||
}
|
||||
static void tablet_tool_handle_button(void *data,
|
||||
|
@ -3289,7 +3410,7 @@ static void tablet_tool_handle_button(void *data,
|
|||
|
||||
if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(new GHOST_EventButton(
|
||||
seat->system->pushEvent_maybe_pending(new GHOST_EventButton(
|
||||
seat->system->getMilliSeconds(), etype, win, ebutton, tablet_tool->data));
|
||||
}
|
||||
}
|
||||
|
@ -3306,12 +3427,13 @@ static void tablet_tool_handle_frame(void *data,
|
|||
if (wl_surface *wl_surface_focus = seat->tablet.wl_surface_window) {
|
||||
GHOST_WindowWayland *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
const wl_fixed_t scale = win->scale();
|
||||
seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->tablet.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->tablet.xy[1]),
|
||||
tablet_tool->data));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
win,
|
||||
wl_fixed_to_int(scale * seat->tablet.xy[0]),
|
||||
wl_fixed_to_int(scale * seat->tablet.xy[1]),
|
||||
tablet_tool->data));
|
||||
if (tablet_tool->proximity == false) {
|
||||
win->setCursorShape(win->getCursorShape());
|
||||
}
|
||||
|
@ -3624,6 +3746,9 @@ static xkb_keysym_t xkb_state_key_get_one_sym_without_modifiers(
|
|||
|
||||
static void keyboard_handle_key_repeat_cancel(GWL_Seat *seat)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
|
||||
#endif
|
||||
GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
|
||||
delete static_cast<GWL_KeyRepeatPlayload *>(seat->key_repeat.timer->getUserData());
|
||||
seat->system->removeTimer(seat->key_repeat.timer);
|
||||
|
@ -3637,6 +3762,9 @@ static void keyboard_handle_key_repeat_cancel(GWL_Seat *seat)
|
|||
*/
|
||||
static void keyboard_handle_key_repeat_reset(GWL_Seat *seat, const bool use_delay)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
|
||||
#endif
|
||||
GHOST_ASSERT(seat->key_repeat.timer != nullptr, "Caller much check for timer");
|
||||
GHOST_SystemWayland *system = seat->system;
|
||||
GHOST_ITimerTask *timer = seat->key_repeat.timer;
|
||||
|
@ -3722,12 +3850,18 @@ static void keyboard_handle_key(void *data,
|
|||
break;
|
||||
}
|
||||
case RESET: {
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
|
||||
#endif
|
||||
/* The payload will be added again. */
|
||||
seat->system->removeTimer(seat->key_repeat.timer);
|
||||
seat->key_repeat.timer = nullptr;
|
||||
break;
|
||||
}
|
||||
case CANCEL: {
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_timer_guard{*seat->system->timer_mutex};
|
||||
#endif
|
||||
delete key_repeat_payload;
|
||||
key_repeat_payload = nullptr;
|
||||
|
||||
|
@ -3750,7 +3884,7 @@ static void keyboard_handle_key(void *data,
|
|||
|
||||
if (wl_surface *wl_surface_focus = seat->keyboard.wl_surface_window) {
|
||||
GHOST_IWindow *win = ghost_wl_surface_user_data(wl_surface_focus);
|
||||
seat->system->pushEvent(
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventKey(seat->system->getMilliSeconds(), etype, win, gkey, false, utf8_buf));
|
||||
}
|
||||
|
||||
|
@ -3778,12 +3912,12 @@ static void keyboard_handle_key(void *data,
|
|||
/* Calculate this value every time in case modifier keys are pressed. */
|
||||
char utf8_buf[sizeof(GHOST_TEventKeyData::utf8_buf)] = {'\0'};
|
||||
xkb_state_key_get_utf8(seat->xkb_state, payload->key_code, utf8_buf, sizeof(utf8_buf));
|
||||
system->pushEvent(new GHOST_EventKey(system->getMilliSeconds(),
|
||||
GHOST_kEventKeyDown,
|
||||
win,
|
||||
payload->key_data.gkey,
|
||||
true,
|
||||
utf8_buf));
|
||||
system->pushEvent_maybe_pending(new GHOST_EventKey(system->getMilliSeconds(),
|
||||
GHOST_kEventKeyDown,
|
||||
win,
|
||||
payload->key_data.gkey,
|
||||
true,
|
||||
utf8_buf));
|
||||
}
|
||||
};
|
||||
seat->key_repeat.timer = seat->system->installTimer(
|
||||
|
@ -5097,6 +5231,44 @@ static const struct wl_registry_listener registry_listener = {
|
|||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name Event Thread
|
||||
* \{ */
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
|
||||
static void *gwl_display_event_thread_fn(void *display_voidp)
|
||||
{
|
||||
GWL_Display *display = static_cast<GWL_Display *>(display_voidp);
|
||||
const int fd = wl_display_get_fd(display->wl_display);
|
||||
while (display->events_pthread_is_active) {
|
||||
/* Wait for an event, this thread is dedicated to event handling. */
|
||||
if (ghost_wl_display_event_pump_from_thread(
|
||||
display->wl_display, fd, display->system->server_mutex) == -1) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Event reading thread. */
|
||||
static void gwl_display_event_thread_create(GWL_Display *display)
|
||||
{
|
||||
GHOST_ASSERT(display->events_pthread == 0, "Only call once");
|
||||
display->events_pthread_is_active = true;
|
||||
pthread_create(&display->events_pthread, nullptr, gwl_display_event_thread_fn, display);
|
||||
pthread_detach(display->events_pthread);
|
||||
}
|
||||
|
||||
static void gwl_display_event_thread_destroy(GWL_Display *display)
|
||||
{
|
||||
pthread_cancel(display->events_pthread);
|
||||
}
|
||||
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
/** \name GHOST Implementation
|
||||
*
|
||||
|
@ -5108,6 +5280,12 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background)
|
|||
{
|
||||
wl_log_set_handler_client(ghost_wayland_log_handler);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
server_mutex = new std::mutex;
|
||||
timer_mutex = new std::mutex;
|
||||
main_thread_id = std::this_thread::get_id();
|
||||
#endif
|
||||
|
||||
display_->system = this;
|
||||
/* Connect to the Wayland server. */
|
||||
display_->wl_display = wl_display_connect(nullptr);
|
||||
|
@ -5188,11 +5366,20 @@ GHOST_SystemWayland::GHOST_SystemWayland(bool background)
|
|||
throw std::runtime_error("Wayland: unable to access xdg_shell!");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
gwl_display_event_thread_create(display_);
|
||||
#endif
|
||||
}
|
||||
|
||||
GHOST_SystemWayland::~GHOST_SystemWayland()
|
||||
{
|
||||
gwl_display_destroy(display_);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
delete server_mutex;
|
||||
delete timer_mutex;
|
||||
#endif
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::init()
|
||||
|
@ -5209,12 +5396,53 @@ GHOST_TSuccess GHOST_SystemWayland::init()
|
|||
return GHOST_kFailure;
|
||||
}
|
||||
|
||||
#undef pushEvent
|
||||
|
||||
bool GHOST_SystemWayland::processEvents(bool waitForEvent)
|
||||
{
|
||||
bool any_processed = false;
|
||||
|
||||
if (getTimerManager()->fireTimers(getMilliSeconds())) {
|
||||
any_processed = true;
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
if (UNLIKELY(has_pending_actions_for_window.exchange(false))) {
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
for (GHOST_IWindow *iwin : getWindowManager()->getWindows()) {
|
||||
GHOST_WindowWayland *win = static_cast<GHOST_WindowWayland *>(iwin);
|
||||
win->pending_actions_handle();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock{display_->events_pending_mutex};
|
||||
for (GHOST_IEvent *event : display_->events_pending) {
|
||||
|
||||
/* Perform actions that aren't handled in a thread. */
|
||||
switch (event->getType()) {
|
||||
case GHOST_kEventWindowActivate: {
|
||||
getWindowManager()->setActiveWindow(event->getWindow());
|
||||
break;
|
||||
}
|
||||
case GHOST_kEventWindowDeactivate: {
|
||||
getWindowManager()->setWindowInactive(event->getWindow());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
pushEvent(event);
|
||||
}
|
||||
display_->events_pending.clear();
|
||||
}
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_timer_guard{*display_->system->timer_mutex};
|
||||
#endif
|
||||
if (getTimerManager()->fireTimers(getMilliSeconds())) {
|
||||
any_processed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef WITH_INPUT_NDOF
|
||||
|
@ -5227,14 +5455,28 @@ bool GHOST_SystemWayland::processEvents(bool waitForEvent)
|
|||
#endif /* WITH_INPUT_NDOF */
|
||||
|
||||
if (waitForEvent) {
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
if (wl_display_dispatch(display_->wl_display) == -1) {
|
||||
ghost_wl_display_report_error(display_->wl_display);
|
||||
}
|
||||
}
|
||||
else {
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/* NOTE: this works, but as the events are being read in a thread,
|
||||
* this could be removed and event handling still works.. */
|
||||
if (server_mutex->try_lock()) {
|
||||
if (ghost_wl_display_event_pump(display_->wl_display) == -1) {
|
||||
ghost_wl_display_report_error(display_->wl_display);
|
||||
}
|
||||
server_mutex->unlock();
|
||||
}
|
||||
#else
|
||||
if (ghost_wl_display_event_pump(display_->wl_display) == -1) {
|
||||
ghost_wl_display_report_error(display_->wl_display);
|
||||
}
|
||||
#endif /* !USE_EVENT_BACKGROUND_THREAD */
|
||||
}
|
||||
|
||||
if (getEventManager()->getNumEvents() > 0) {
|
||||
|
@ -5251,6 +5493,10 @@ bool GHOST_SystemWayland::setConsoleWindowState(GHOST_TConsoleWindowState /*acti
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::getModifierKeys(GHOST_ModifierKeys &keys) const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -5442,6 +5688,10 @@ static char *system_clipboard_get(GWL_Display *display)
|
|||
|
||||
char *GHOST_SystemWayland::getClipboard(bool selection) const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
char *data = nullptr;
|
||||
if (selection) {
|
||||
data = system_clipboard_get_primary_selection(display_);
|
||||
|
@ -5522,6 +5772,10 @@ static void system_clipboard_put(GWL_Display *display, const char *buffer)
|
|||
|
||||
void GHOST_SystemWayland::putClipboard(const char *buffer, bool selection) const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
if (selection) {
|
||||
system_clipboard_put_primary_selection(display_, buffer);
|
||||
}
|
||||
|
@ -5574,6 +5828,10 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPositionClientRelative(const GHOST_
|
|||
int32_t &x,
|
||||
int32_t &y) const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -5590,6 +5848,10 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo
|
|||
const int32_t x,
|
||||
const int32_t y)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -5600,6 +5862,10 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorPositionClientRelative(GHOST_IWindo
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -5618,6 +5884,10 @@ GHOST_TSuccess GHOST_SystemWayland::getCursorPosition(int32_t &x, int32_t &y) co
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::setCursorPosition(const int32_t x, const int32_t y)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -5707,6 +5977,10 @@ static GHOST_Context *createOffscreenContext_impl(GHOST_SystemWayland *system,
|
|||
|
||||
GHOST_IContext *GHOST_SystemWayland::createOffscreenContext(GHOST_GLSettings /*glSettings*/)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*server_mutex};
|
||||
#endif
|
||||
|
||||
/* Create new off-screen window. */
|
||||
wl_surface *wl_surface = wl_compositor_create_surface(wl_compositor());
|
||||
wl_egl_window *egl_window = wl_surface ? wl_egl_window_create(wl_surface, 1, 1) : nullptr;
|
||||
|
@ -5972,6 +6246,7 @@ static bool cursor_is_software(const GHOST_TGrabCursorMode mode, const bool use_
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::cursor_shape_set(const GHOST_TStandardCursor shape)
|
||||
{
|
||||
/* No need to lock (caller must lock). */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -6033,6 +6308,7 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(uint8_t *bitmap,
|
|||
const int hotY,
|
||||
const bool /*canInvertColor*/)
|
||||
{
|
||||
/* Caller needs to lock `server_mutex`. */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -6103,6 +6379,7 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_shape_custom_set(uint8_t *bitmap,
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::cursor_bitmap_get(GHOST_CursorBitmapRef *bitmap)
|
||||
{
|
||||
/* Caller must lock `server_mutex`. */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -6129,6 +6406,7 @@ GHOST_TSuccess GHOST_SystemWayland::cursor_bitmap_get(GHOST_CursorBitmapRef *bit
|
|||
|
||||
GHOST_TSuccess GHOST_SystemWayland::cursor_visibility_set(const bool visible)
|
||||
{
|
||||
/* Caller must lock `server_mutex`. */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return GHOST_kFailure;
|
||||
|
@ -6153,6 +6431,7 @@ bool GHOST_SystemWayland::supportsWindowPosition()
|
|||
|
||||
bool GHOST_SystemWayland::cursor_grab_use_software_display_get(const GHOST_TGrabCursorMode mode)
|
||||
{
|
||||
/* Caller must lock `server_mutex`. */
|
||||
GWL_Seat *seat = gwl_display_seat_active_get(display_);
|
||||
if (UNLIKELY(!seat)) {
|
||||
return false;
|
||||
|
@ -6346,6 +6625,18 @@ GHOST_WindowWayland *ghost_wl_surface_user_data(struct wl_surface *wl_surface)
|
|||
* Functionality only used for the WAYLAND implementation.
|
||||
* \{ */
|
||||
|
||||
GHOST_TSuccess GHOST_SystemWayland::pushEvent_maybe_pending(GHOST_IEvent *event)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
if (main_thread_id != std::this_thread::get_id()) {
|
||||
std::lock_guard lock{display_->events_pending_mutex};
|
||||
display_->events_pending.push_back(event);
|
||||
return GHOST_kSuccess;
|
||||
}
|
||||
#endif
|
||||
return pushEvent(event);
|
||||
}
|
||||
|
||||
void GHOST_SystemWayland::seat_active_set(const struct GWL_Seat *seat)
|
||||
{
|
||||
gwl_display_seat_active_set(display_, seat);
|
||||
|
@ -6482,12 +6773,13 @@ bool GHOST_SystemWayland::window_cursor_grab_set(const GHOST_TGrabCursorMode mod
|
|||
#endif
|
||||
|
||||
if (xy_motion_create_event) {
|
||||
seat->system->pushEvent(new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
ghost_wl_surface_user_data(wl_surface),
|
||||
wl_fixed_to_int(scale * xy_motion[0]),
|
||||
wl_fixed_to_int(scale * xy_motion[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
seat->system->pushEvent_maybe_pending(
|
||||
new GHOST_EventCursor(seat->system->getMilliSeconds(),
|
||||
GHOST_kEventCursorMove,
|
||||
ghost_wl_surface_user_data(wl_surface),
|
||||
wl_fixed_to_int(scale * xy_motion[0]),
|
||||
wl_fixed_to_int(scale * xy_motion[1]),
|
||||
GHOST_TABLET_DATA_NONE));
|
||||
}
|
||||
|
||||
zwp_locked_pointer_v1_destroy(seat->wp_locked_pointer);
|
||||
|
|
|
@ -26,6 +26,11 @@
|
|||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
# include <atomic>
|
||||
# include <thread>
|
||||
#endif
|
||||
|
||||
class GHOST_WindowWayland;
|
||||
|
||||
bool ghost_wl_output_own(const struct wl_output *wl_output);
|
||||
|
@ -180,6 +185,12 @@ class GHOST_SystemWayland : public GHOST_System {
|
|||
|
||||
/* WAYLAND utility functions. */
|
||||
|
||||
/**
|
||||
* Push an event, with support for calling from a thread.
|
||||
* NOTE: only needed for `USE_EVENT_BACKGROUND_THREAD`.
|
||||
*/
|
||||
GHOST_TSuccess pushEvent_maybe_pending(GHOST_IEvent *event);
|
||||
|
||||
/** Set this seat to be active. */
|
||||
void seat_active_set(const struct GWL_Seat *seat);
|
||||
|
||||
|
@ -198,6 +209,21 @@ class GHOST_SystemWayland : public GHOST_System {
|
|||
static bool use_libdecor_runtime();
|
||||
#endif
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/* NOTE: allocate mutex so `const` functions can lock the mutex. */
|
||||
|
||||
/** Lock to prevent #wl_display_dispatch / #wl_display_roundtrip / #wl_display_flush
|
||||
* from running at the same time. */
|
||||
std::mutex *server_mutex = nullptr;
|
||||
|
||||
/** Threads must lock this before manipulating timers. */
|
||||
std::mutex *timer_mutex = nullptr;
|
||||
|
||||
std::thread::id main_thread_id;
|
||||
|
||||
std::atomic<bool> has_pending_actions_for_window = false;
|
||||
#endif
|
||||
|
||||
private:
|
||||
struct GWL_Display *display_;
|
||||
};
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
#include <xdg-decoration-unstable-v1-client-protocol.h>
|
||||
#include <xdg-shell-client-protocol.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
/* Logging, use `ghost.wl.*` prefix. */
|
||||
#include "CLG_log.h"
|
||||
|
||||
|
@ -117,11 +119,27 @@ struct GWL_Window {
|
|||
GWL_WindowFrame frame;
|
||||
GWL_WindowFrame frame_pending;
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/**
|
||||
* Needed so calls such as #GHOST_Window::setClientSize
|
||||
* doesn't conflict with WAYLAND callbacks that may run in a thread.
|
||||
*/
|
||||
std::mutex frame_pending_mutex;
|
||||
#endif
|
||||
|
||||
wl_egl_window *egl_window = nullptr;
|
||||
|
||||
std::string title;
|
||||
|
||||
bool is_dialog = false;
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/**
|
||||
* These pending actions can't be performed when WAYLAND handlers are running from a thread.
|
||||
* Postpone their execution until the main thread can handle them.
|
||||
*/
|
||||
std::atomic<bool> pending_actions[3];
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
};
|
||||
|
||||
static void gwl_window_title_set(GWL_Window *win, const char *title)
|
||||
|
@ -255,11 +273,59 @@ static void gwl_window_frame_pending_size_set(GWL_Window *win)
|
|||
|
||||
static void gwl_window_frame_update_from_pending(GWL_Window *win);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
|
||||
enum eGWL_PendingWindowActions {
|
||||
PENDING_FRAME_CONFIGURE = 0,
|
||||
PENDING_EGL_RESIZE,
|
||||
# ifdef GHOST_OPENGL_ALPHA
|
||||
PENDING_OPAQUE_SET,
|
||||
# endif
|
||||
PENDING_SWAP_BUFFERS,
|
||||
PENDING_SCALE_UPDATE,
|
||||
};
|
||||
# define PENDING_NUM (PENDING_SWAP_BUFFERS + 1)
|
||||
|
||||
static void gwl_window_pending_actions_tag(GWL_Window *win, enum eGWL_PendingWindowActions type)
|
||||
{
|
||||
win->pending_actions[int(type)].store(true);
|
||||
win->ghost_system->has_pending_actions_for_window.store(true);
|
||||
}
|
||||
|
||||
static void gwl_window_pending_actions_handle(GWL_Window *win)
|
||||
{
|
||||
if (win->pending_actions[PENDING_FRAME_CONFIGURE].exchange(false)) {
|
||||
gwl_window_frame_update_from_pending(win);
|
||||
}
|
||||
if (win->pending_actions[PENDING_EGL_RESIZE].exchange(false)) {
|
||||
wl_egl_window_resize(win->egl_window, UNPACK2(win->frame.size), 0, 0);
|
||||
}
|
||||
# ifdef GHOST_OPENGL_ALPHA
|
||||
if (win->pending_actions[PENDING_OPAQUE_SET].exchange(false)) {
|
||||
win->ghost_window->setOpaque();
|
||||
}
|
||||
# endif
|
||||
if (win->pending_actions[PENDING_SCALE_UPDATE].exchange(false)) {
|
||||
win->ghost_window->outputs_changed_update_scale();
|
||||
}
|
||||
if (win->pending_actions[PENDING_SWAP_BUFFERS].exchange(false)) {
|
||||
win->ghost_window->swapBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
/**
|
||||
* Update the window's #GWL_WindowFrame
|
||||
*/
|
||||
static void gwl_window_frame_update_from_pending(GWL_Window *win)
|
||||
static void gwl_window_frame_update_from_pending_lockfree(GWL_Window *win)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
|
||||
"Only from main thread!");
|
||||
|
||||
#endif
|
||||
|
||||
if (win->frame_pending.size[0] != 0 && win->frame_pending.size[1] != 0) {
|
||||
if ((win->frame.size[0] != win->frame_pending.size[0]) ||
|
||||
(win->frame.size[1] != win->frame_pending.size[1])) {
|
||||
|
@ -284,6 +350,14 @@ static void gwl_window_frame_update_from_pending(GWL_Window *win)
|
|||
win->frame_pending.size[1] = 0;
|
||||
}
|
||||
|
||||
static void gwl_window_frame_update_from_pending(GWL_Window *win)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_frame_guard{win->frame_pending_mutex};
|
||||
#endif
|
||||
gwl_window_frame_update_from_pending_lockfree(win);
|
||||
}
|
||||
|
||||
/** \} */
|
||||
|
||||
/* -------------------------------------------------------------------- */
|
||||
|
@ -363,6 +437,11 @@ static void xdg_toplevel_handle_configure(void *data,
|
|||
CLOG_INFO(LOG, 2, "configure (size=[%d, %d])", width, height);
|
||||
|
||||
GWL_Window *win = static_cast<GWL_Window *>(data);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_frame_guard{win->frame_pending_mutex};
|
||||
#endif
|
||||
|
||||
win->frame_pending.size[0] = win->scale * width;
|
||||
win->frame_pending.size[1] = win->scale * height;
|
||||
|
||||
|
@ -421,6 +500,10 @@ static void frame_handle_configure(struct libdecor_frame *frame,
|
|||
{
|
||||
CLOG_INFO(LOG, 2, "configure");
|
||||
|
||||
# ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_frame_guard{static_cast<GWL_Window *>(data)->frame_pending_mutex};
|
||||
# endif
|
||||
|
||||
GWL_WindowFrame *frame_pending = &static_cast<GWL_Window *>(data)->frame_pending;
|
||||
|
||||
/* Set the size. */
|
||||
|
@ -462,7 +545,19 @@ static void frame_handle_configure(struct libdecor_frame *frame,
|
|||
|
||||
{
|
||||
GWL_Window *win = static_cast<GWL_Window *>(data);
|
||||
gwl_window_frame_update_from_pending(win);
|
||||
# ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
GHOST_SystemWayland *system = win->ghost_system;
|
||||
const bool is_main_thread = system->main_thread_id == std::this_thread::get_id();
|
||||
if (!is_main_thread) {
|
||||
/* NOTE(@campbellbarton): this only gets one redraw,
|
||||
* I could not find a case where this causes problems. */
|
||||
gwl_window_pending_actions_tag(win, PENDING_FRAME_CONFIGURE);
|
||||
}
|
||||
else
|
||||
# endif
|
||||
{
|
||||
gwl_window_frame_update_from_pending_lockfree(win);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -481,7 +576,11 @@ static void frame_handle_commit(struct libdecor_frame * /*frame*/, void *data)
|
|||
|
||||
GWL_Window *win = static_cast<GWL_Window *>(data);
|
||||
|
||||
# ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
gwl_window_pending_actions_tag(win, PENDING_SWAP_BUFFERS);
|
||||
# else
|
||||
win->ghost_window->swapBuffers();
|
||||
# endif
|
||||
}
|
||||
|
||||
static struct libdecor_frame_interface libdecor_frame_iface = {
|
||||
|
@ -542,7 +641,19 @@ static void xdg_surface_handle_configure(void *data,
|
|||
}
|
||||
CLOG_INFO(LOG, 2, "configure");
|
||||
|
||||
gwl_window_frame_update_from_pending(win);
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
GHOST_SystemWayland *system = win->ghost_system;
|
||||
const bool is_main_thread = system->main_thread_id == std::this_thread::get_id();
|
||||
if (!is_main_thread) {
|
||||
/* NOTE(@campbellbarton): this only gets one redraw,
|
||||
* I could not find a case where this causes problems. */
|
||||
gwl_window_pending_actions_tag(win, PENDING_FRAME_CONFIGURE);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
gwl_window_frame_update_from_pending(win);
|
||||
}
|
||||
|
||||
xdg_surface_ack_configure(xdg_surface, serial);
|
||||
}
|
||||
|
@ -632,6 +743,10 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
|
|||
system_(system),
|
||||
window_(new GWL_Window)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system->server_mutex};
|
||||
#endif
|
||||
|
||||
/* Globally store pointer to window manager. */
|
||||
if (!window_manager) {
|
||||
window_manager = system_->getWindowManager();
|
||||
|
@ -773,8 +888,20 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system,
|
|||
setSwapInterval(0);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::swapBuffers()
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
GHOST_ASSERT(system_->main_thread_id == std::this_thread::get_id(), "Only from main thread!");
|
||||
#endif
|
||||
return GHOST_Window::swapBuffers();
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
|
||||
GHOST_Rect bounds_buf;
|
||||
GHOST_Rect *bounds = nullptr;
|
||||
if (m_cursorGrab == GHOST_kGrabWrap) {
|
||||
|
@ -804,6 +931,9 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCursorShape(GHOST_TStandardCursor s
|
|||
|
||||
bool GHOST_WindowWayland::getCursorGrabUseSoftwareDisplay()
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
return system_->cursor_grab_use_software_display_get(m_cursorGrab);
|
||||
}
|
||||
|
||||
|
@ -815,11 +945,17 @@ GHOST_TSuccess GHOST_WindowWayland::setWindowCustomCursorShape(
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::getCursorBitmap(GHOST_CursorBitmapRef *bitmap)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
return system_->cursor_bitmap_get(bitmap);
|
||||
}
|
||||
|
||||
void GHOST_WindowWayland::setTitle(const char *title)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
gwl_window_title_set(window_, title);
|
||||
}
|
||||
|
||||
|
@ -850,6 +986,11 @@ GHOST_TSuccess GHOST_WindowWayland::setClientHeight(const uint32_t height)
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setClientSize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
|
||||
#endif
|
||||
|
||||
window_->frame_pending.size[0] = width;
|
||||
window_->frame_pending.size[1] = height;
|
||||
|
||||
|
@ -878,6 +1019,10 @@ void GHOST_WindowWayland::clientToScreen(int32_t inX,
|
|||
|
||||
GHOST_WindowWayland::~GHOST_WindowWayland()
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
|
||||
releaseNativeHandles();
|
||||
|
||||
wl_egl_window_destroy(window_->egl_window);
|
||||
|
@ -915,16 +1060,25 @@ uint16_t GHOST_WindowWayland::getDPIHint()
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
return system_->cursor_visibility_set(visible);
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::setState(GHOST_TWindowState state)
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
return gwl_window_state_set(window_, state) ? GHOST_kSuccess : GHOST_kFailure;
|
||||
}
|
||||
|
||||
GHOST_TWindowState GHOST_WindowWayland::getState() const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
return gwl_window_state_get(window_);
|
||||
}
|
||||
|
||||
|
@ -940,6 +1094,10 @@ GHOST_TSuccess GHOST_WindowWayland::setOrder(GHOST_TWindowOrder /*order*/)
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
if (use_libdecor) {
|
||||
libdecor_frame_set_fullscreen(window_->libdecor->frame, nullptr);
|
||||
|
@ -955,6 +1113,10 @@ GHOST_TSuccess GHOST_WindowWayland::beginFullScreen() const
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::endFullScreen() const
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_server_guard{*system_->server_mutex};
|
||||
#endif
|
||||
|
||||
#ifdef WITH_GHOST_WAYLAND_LIBDECOR
|
||||
if (use_libdecor) {
|
||||
libdecor_frame_unset_fullscreen(window_->libdecor->frame);
|
||||
|
@ -1072,33 +1234,55 @@ const std::vector<GWL_Output *> &GHOST_WindowWayland::outputs()
|
|||
|
||||
GHOST_TSuccess GHOST_WindowWayland::close()
|
||||
{
|
||||
return system_->pushEvent(
|
||||
return system_->pushEvent_maybe_pending(
|
||||
new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowClose, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::activate()
|
||||
{
|
||||
if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
|
||||
return GHOST_kFailure;
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
|
||||
if (is_main_thread)
|
||||
#endif
|
||||
{
|
||||
if (system_->getWindowManager()->setActiveWindow(this) == GHOST_kFailure) {
|
||||
return GHOST_kFailure;
|
||||
}
|
||||
}
|
||||
return system_->pushEvent(
|
||||
return system_->pushEvent_maybe_pending(
|
||||
new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowActivate, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::deactivate()
|
||||
{
|
||||
system_->getWindowManager()->setWindowInactive(this);
|
||||
return system_->pushEvent(
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/* Actual activation is handled when processing pending events. */
|
||||
const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
|
||||
if (is_main_thread)
|
||||
#endif
|
||||
{
|
||||
system_->getWindowManager()->setWindowInactive(this);
|
||||
}
|
||||
return system_->pushEvent_maybe_pending(
|
||||
new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowDeactivate, this));
|
||||
}
|
||||
|
||||
GHOST_TSuccess GHOST_WindowWayland::notify_size()
|
||||
{
|
||||
#ifdef GHOST_OPENGL_ALPHA
|
||||
setOpaque();
|
||||
# ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
/* Actual activation is handled when processing pending events. */
|
||||
const bool is_main_thread = system_->main_thread_id == std::this_thread::get_id();
|
||||
if (!is_main_thread) {
|
||||
gwl_window_pending_actions_tag(window_, PENDING_OPAQUE_SET);
|
||||
}
|
||||
# endif
|
||||
{
|
||||
setOpaque();
|
||||
}
|
||||
#endif
|
||||
|
||||
return system_->pushEvent(
|
||||
return system_->pushEvent_maybe_pending(
|
||||
new GHOST_Event(system_->getMilliSeconds(), GHOST_kEventWindowSize, this));
|
||||
}
|
||||
|
||||
|
@ -1115,6 +1299,13 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size()
|
|||
*/
|
||||
bool GHOST_WindowWayland::outputs_changed_update_scale()
|
||||
{
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
if (system_->main_thread_id != std::this_thread::get_id()) {
|
||||
gwl_window_pending_actions_tag(window_, PENDING_SCALE_UPDATE);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
wl_fixed_t scale_fractional_next = 0;
|
||||
const int scale_next = outputs_max_scale_or_default(outputs(), 0, &scale_fractional_next);
|
||||
if (UNLIKELY(scale_next == 0)) {
|
||||
|
@ -1129,6 +1320,10 @@ bool GHOST_WindowWayland::outputs_changed_update_scale()
|
|||
window_->scale = scale_next;
|
||||
wl_surface_set_buffer_scale(window_->wl_surface, scale_next);
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
std::lock_guard lock_frame_guard{window_->frame_pending_mutex};
|
||||
#endif
|
||||
|
||||
/* It's important to resize the window immediately, to avoid the window changing size
|
||||
* and flickering in a constant feedback loop (in some bases). */
|
||||
|
||||
|
@ -1183,4 +1378,19 @@ bool GHOST_WindowWayland::outputs_leave(GWL_Output *output)
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
|
||||
void GHOST_WindowWayland::pending_actions_handle()
|
||||
{
|
||||
/* Caller must lock `server_mutex`, while individual actions could lock,
|
||||
* it's simpler to lock once when handling all window actions. */
|
||||
GWL_Window *win = window_;
|
||||
GHOST_ASSERT(win->ghost_system->main_thread_id == std::this_thread::get_id(),
|
||||
"Run from main thread!");
|
||||
|
||||
gwl_window_pending_actions_handle(win);
|
||||
}
|
||||
|
||||
#endif /* USE_EVENT_BACKGROUND_THREAD */
|
||||
|
||||
/** \} */
|
||||
|
|
|
@ -14,6 +14,25 @@
|
|||
|
||||
#include <wayland-util.h> /* For #wl_fixed_t */
|
||||
|
||||
/**
|
||||
* Define to workaround for a bug/limitation in WAYLAND, see: T100855 & upstream report:
|
||||
* https://gitlab.freedesktop.org/wayland/wayland/-/issues/159
|
||||
*
|
||||
* Consume events from WAYLAND in a thread, this is needed because overflowing the event queue
|
||||
* causes a fatal error (more than `sizeof(wl_buffer.data)` at the time of writing).
|
||||
*
|
||||
* Solve this using a thread that handles events, locking must be performed as follows:
|
||||
*
|
||||
* - Lock #GWL_Display.server_mutex to prevent wl_display_dispatch / wl_display_roundtrip
|
||||
* running from multiple threads at once.
|
||||
* GHOST functions that communicate with WAYLAND must use this lock to to be thread safe.
|
||||
*
|
||||
* - Lock #GWL_Display.timer_mutex when WAYLAND callbacks manipulate timers.
|
||||
*
|
||||
* - Lock #GWL_Display.events_pending_mutex before manipulating #GWL_Display.events_pending.
|
||||
*/
|
||||
#define USE_EVENT_BACKGROUND_THREAD
|
||||
|
||||
class GHOST_SystemWayland;
|
||||
|
||||
struct GWL_Output;
|
||||
|
@ -40,6 +59,8 @@ class GHOST_WindowWayland : public GHOST_Window {
|
|||
|
||||
/* Ghost API */
|
||||
|
||||
GHOST_TSuccess swapBuffers() override;
|
||||
|
||||
uint16_t getDPIHint() override;
|
||||
|
||||
GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override;
|
||||
|
@ -116,6 +137,10 @@ class GHOST_WindowWayland : public GHOST_Window {
|
|||
|
||||
bool outputs_changed_update_scale();
|
||||
|
||||
#ifdef USE_EVENT_BACKGROUND_THREAD
|
||||
void pending_actions_handle();
|
||||
#endif
|
||||
|
||||
private:
|
||||
GHOST_SystemWayland *system_;
|
||||
struct GWL_Window *window_;
|
||||
|
|
Loading…
Reference in New Issue