瀏覽代碼

Merge pull request #87750 from Riteo/wayland-timeout-loop

Wayland: Suspend window after frame timeout or suspend state
Rémi Verschelde 1 年之前
父節點
當前提交
5c482754e4

+ 0 - 8
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();
 

+ 0 - 2
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();

+ 28 - 3
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();
 

+ 1 - 1
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;

+ 111 - 5
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;

+ 4 - 0
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();