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:
Campbell Barton 2022-11-11 10:57:30 +11:00
parent f1646a4d5e
commit 37b256e26f
Notes: blender-bot 2023-02-14 11:01:33 +01:00
Referenced by commit bbe7183cd3, GHOST/Wayland: fix threaded event handling
Referenced by commit c2c41fb14c, Fix missing title-bar redrawing with Wayland & libdecor
Referenced by issue #100855, GHOST/Wayland: Terminal Ctrl-C closes the window while Python is running
4 changed files with 636 additions and 83 deletions

View File

@ -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);

View File

@ -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_;
};

View File

@ -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 */
/** \} */

View File

@ -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_;