Skip to content

Commit

Permalink
Merge pull request #87750 from Riteo/wayland-timeout-loop
Browse files Browse the repository at this point in the history
Wayland: Suspend window after frame timeout or suspend state
  • Loading branch information
akien-mga committed Feb 16, 2024
2 parents ebf00b8 + 2e07dcf commit 5c48275
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 19 deletions.
8 changes: 0 additions & 8 deletions platform/linuxbsd/os_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
2 changes: 0 additions & 2 deletions platform/linuxbsd/os_linuxbsd.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,6 @@ class OS_LinuxBSD : public OS_Unix {

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();
Expand Down
31 changes: 28 additions & 3 deletions platform/linuxbsd/wayland/display_server_wayland.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();

Expand Down
2 changes: 1 addition & 1 deletion platform/linuxbsd/wayland/display_server_wayland.h
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ class DisplayServerWayland : public DisplayServer {

Context context;

bool frame = false;
bool suspended = false;
bool emulate_vsync = false;

String rendering_driver;
Expand Down
116 changes: 111 additions & 5 deletions platform/linuxbsd/wayland/wayland_thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions platform/linuxbsd/wayland/wayland_thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ class WaylandThread {

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
Expand Down Expand Up @@ -939,6 +940,9 @@ class WaylandThread {

void set_frame();
bool get_reset_frame();
bool wait_frame_suspend_ms(int p_timeout);

bool is_suspended() const;

Error init();
void destroy();
Expand Down

0 comments on commit 5c48275

Please sign in to comment.