diff --git a/platform/linuxbsd/os_linuxbsd.cpp b/platform/linuxbsd/os_linuxbsd.cpp index 4e87dd42ddf..85846335f77 100644 --- a/platform/linuxbsd/os_linuxbsd.cpp +++ b/platform/linuxbsd/os_linuxbsd.cpp @@ -127,14 +127,6 @@ void OS_LinuxBSD::alert(const String &p_alert, const String &p_title) { } } -int OS_LinuxBSD::get_low_processor_usage_mode_sleep_usec() const { - if (DisplayServer::get_singleton() == nullptr || DisplayServer::get_singleton()->get_name() != "Wayland" || is_in_low_processor_usage_mode()) { - return OS::get_low_processor_usage_mode_sleep_usec(); - } - - return 500; // Roughly 2000 FPS, improves frame time when emulating VSync. -} - void OS_LinuxBSD::initialize() { crash_handler.initialize(); diff --git a/platform/linuxbsd/os_linuxbsd.h b/platform/linuxbsd/os_linuxbsd.h index d4ffc0e9b44..9084061eb96 100644 --- a/platform/linuxbsd/os_linuxbsd.h +++ b/platform/linuxbsd/os_linuxbsd.h @@ -127,8 +127,6 @@ public: virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override; - virtual int get_low_processor_usage_mode_sleep_usec() const override; - virtual bool _check_internal_feature_support(const String &p_feature) override; void run(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.cpp b/platform/linuxbsd/wayland/display_server_wayland.cpp index 85bbfe546a1..c957dea32d4 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.cpp +++ b/platform/linuxbsd/wayland/display_server_wayland.cpp @@ -867,11 +867,11 @@ bool DisplayServerWayland::window_is_focused(WindowID p_window_id) const { } bool DisplayServerWayland::window_can_draw(DisplayServer::WindowID p_window_id) const { - return frame; + return !suspended; } bool DisplayServerWayland::can_any_window_draw() const { - return frame; + return !suspended; } void DisplayServerWayland::window_set_ime_active(const bool p_active, DisplayServer::WindowID p_window_id) { @@ -1143,7 +1143,32 @@ void DisplayServerWayland::process_events() { wayland_thread.keyboard_echo_keys(); - frame = wayland_thread.get_reset_frame(); + if (!suspended) { + if (emulate_vsync) { + // Due to various reasons, we manually handle display synchronization by + // waiting for a frame event (request to draw) or, if available, the actual + // window's suspend status. When a window is suspended, we can avoid drawing + // altogether, either because the compositor told us that we don't need to or + // because the pace of the frame events became unreliable. + bool frame = wayland_thread.wait_frame_suspend_ms(1000); + if (!frame) { + suspended = true; + } + } else { + if (wayland_thread.is_suspended()) { + suspended = true; + } + } + + if (suspended) { + DEBUG_LOG_WAYLAND("Window suspended."); + } + } else { + if (wayland_thread.get_reset_frame()) { + // At last, a sign of life! We're no longer suspended. + suspended = false; + } + } wayland_thread.mutex.unlock(); diff --git a/platform/linuxbsd/wayland/display_server_wayland.h b/platform/linuxbsd/wayland/display_server_wayland.h index d4da80a55f8..e42967eb7b5 100644 --- a/platform/linuxbsd/wayland/display_server_wayland.h +++ b/platform/linuxbsd/wayland/display_server_wayland.h @@ -117,7 +117,7 @@ class DisplayServerWayland : public DisplayServer { Context context; - bool frame = false; + bool suspended = false; bool emulate_vsync = false; String rendering_driver; diff --git a/platform/linuxbsd/wayland/wayland_thread.cpp b/platform/linuxbsd/wayland/wayland_thread.cpp index ae1d96a3b1b..ebb21722e27 100644 --- a/platform/linuxbsd/wayland/wayland_thread.cpp +++ b/platform/linuxbsd/wayland/wayland_thread.cpp @@ -469,7 +469,7 @@ void WaylandThread::_wl_registry_on_global(void *data, struct wl_registry *wl_re } if (strcmp(interface, xdg_wm_base_interface.name) == 0) { - registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(5, (int)version))); + registry->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, name, &xdg_wm_base_interface, MAX(2, MIN(6, (int)version))); registry->xdg_wm_base_name = name; xdg_wm_base_add_listener(registry->xdg_wm_base, &xdg_wm_base_listener, nullptr); @@ -1063,9 +1063,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * WindowState *ws = (WindowState *)data; ERR_FAIL_NULL(ws); - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; uint32_t *state = nullptr; wl_array_for_each(state, states) { @@ -1078,6 +1079,10 @@ void WaylandThread::_xdg_toplevel_on_configure(void *data, struct xdg_toplevel * ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } break; + case XDG_TOPLEVEL_STATE_SUSPENDED: { + ws->suspended = true; + } break; + default: { // We don't care about the other states (for now). } break; @@ -1176,9 +1181,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st libdecor_window_state window_state = LIBDECOR_WINDOW_STATE_NONE; - // Expect the window to be in windowed mode. The mode will get overridden if - // the compositor reports otherwise. + // Expect the window to be in a plain state. It will get properly set if the + // compositor reports otherwise below. ws->mode = DisplayServer::WINDOW_MODE_WINDOWED; + ws->suspended = false; if (libdecor_configuration_get_window_state(configuration, &window_state)) { if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { @@ -1188,6 +1194,10 @@ void WaylandThread::libdecor_frame_on_configure(struct libdecor_frame *frame, st if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) { ws->mode = DisplayServer::WINDOW_MODE_FULLSCREEN; } + + if (window_state & LIBDECOR_WINDOW_STATE_SUSPENDED) { + ws->suspended = true; + } } window_state_update_size(ws, width, height); @@ -3872,6 +3882,102 @@ bool WaylandThread::get_reset_frame() { return old_frame; } +// Dispatches events until a frame event is received, a window is reported as +// suspended or the timeout expires. +bool WaylandThread::wait_frame_suspend_ms(int p_timeout) { + if (main_window.suspended) { + // The window is suspended! The compositor is telling us _explicitly_ that we + // don't need to draw, without letting us guess through the frame event's + // timing and stuff like that. Our job here is done. + return false; + } + + if (frame) { + // We already have a frame! Probably it got there while the caller locked :D + frame = false; + return true; + } + + struct pollfd poll_fd; + poll_fd.fd = wl_display_get_fd(wl_display); + poll_fd.events = POLLIN | POLLHUP; + + int begin_ms = OS::get_singleton()->get_ticks_msec(); + int remaining_ms = p_timeout; + + while (remaining_ms > 0) { + // Empty the event queue while it's full. + while (wl_display_prepare_read(wl_display) != 0) { + if (wl_display_dispatch_pending(wl_display) == -1) { + // Oh no. We'll check and handle any display error below. + break; + } + + if (main_window.suspended) { + return false; + } + + if (frame) { + // We had a frame event in the queue :D + frame = false; + return true; + } + } + + int werror = wl_display_get_error(wl_display); + + if (werror) { + if (werror == EPROTO) { + struct wl_interface *wl_interface = nullptr; + uint32_t id = 0; + + int error_code = wl_display_get_protocol_error(wl_display, (const struct wl_interface **)&wl_interface, &id); + CRASH_NOW_MSG(vformat("Wayland protocol error %d on interface %s@%d.", error_code, wl_interface ? wl_interface->name : "unknown", id)); + } else { + CRASH_NOW_MSG(vformat("Wayland client error code %d.", werror)); + } + } + + wl_display_flush(wl_display); + + // Wait for the event file descriptor to have new data. + poll(&poll_fd, 1, remaining_ms); + + if (poll_fd.revents | POLLIN) { + // Load the queues with fresh new data. + wl_display_read_events(wl_display); + } else { + // Oh well... Stop signaling that we want to read. + wl_display_cancel_read(wl_display); + + // We've got no new events :( + // We won't even bother with checking the frame flag. + return false; + } + + // Let's try dispatching now... + wl_display_dispatch_pending(wl_display); + + if (main_window.suspended) { + return false; + } + + if (frame) { + frame = false; + return true; + } + + remaining_ms -= OS::get_singleton()->get_ticks_msec() - begin_ms; + } + + DEBUG_LOG_WAYLAND_THREAD("Frame timeout."); + return false; +} + +bool WaylandThread::is_suspended() const { + return main_window.suspended; +} + void WaylandThread::destroy() { if (!initialized) { return; diff --git a/platform/linuxbsd/wayland/wayland_thread.h b/platform/linuxbsd/wayland/wayland_thread.h index 86033c1a097..f3e3c3a2ac3 100644 --- a/platform/linuxbsd/wayland/wayland_thread.h +++ b/platform/linuxbsd/wayland/wayland_thread.h @@ -177,6 +177,7 @@ public: Rect2i rect; DisplayServer::WindowMode mode = DisplayServer::WINDOW_MODE_WINDOWED; + bool suspended = false; // These are true by default as it isn't guaranteed that we'll find an // xdg-shell implementation with wm_capabilities available. If and once we @@ -939,6 +940,9 @@ public: void set_frame(); bool get_reset_frame(); + bool wait_frame_suspend_ms(int p_timeout); + + bool is_suspended() const; Error init(); void destroy();