From fcb9dc8cfd41d3f5ae3463ab4625ffd03daa25e4 Mon Sep 17 00:00:00 2001 From: Yuxuan Shui Date: Tue, 7 Mar 2023 17:03:26 +0000 Subject: [PATCH] core: make sure unredirection happens when screen is off So when the screen is off, we calls queue_redraw, hoping draw_callback will be called and unredirects the screen. However, queue_redraw doesn't queue another redraw when one is already queued. Redraws can be queued in two ways: one is timer based, which is fine, because it will be triggered no matter what; the other is frame based, which is triggered by Present events. When the screen is off, X server, depends on the driver, could send abnormal Present events, which we ignore. But that also means queued frame based redraw will stop being triggered. Those two factors combined means sometimes unredirection does not happen when the screen is off. Which means we aren't going to free the GL context, which are still receiving Present events, but can't handle them, because we are not rendering anything with GL. In the end all these causes memory usage to balloon, until the screen is turned on and we start rendering again. And all these is not caught by leak checkers because this technically is not a leak, as everything is eventually freed. Signed-off-by: Yuxuan Shui --- src/picom.c | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/picom.c b/src/picom.c index 3e0fd786..e97f0e6f 100644 --- a/src/picom.c +++ b/src/picom.c @@ -133,6 +133,7 @@ void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { } auto now_screen_is_off = dpms_screen_is_off(r); if (ps->screen_is_off != now_screen_is_off) { + log_debug("Screen is now %s", now_screen_is_off ? "off" : "on"); ps->screen_is_off = now_screen_is_off; queue_redraw(ps); } @@ -145,14 +146,17 @@ void check_dpms_status(EV_P attr_unused, ev_timer *w, int revents attr_unused) { * XXX move to win.c */ static inline struct managed_win *find_win_all(session_t *ps, const xcb_window_t wid) { - if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) + if (!wid || PointerRoot == wid || wid == ps->root || wid == ps->overlay) { return NULL; + } auto w = find_managed_win(ps, wid); - if (!w) + if (!w) { w = find_toplevel(ps, wid); - if (!w) + } + if (!w) { w = find_managed_window_or_parent(ps, wid); + } return w; } @@ -244,6 +248,11 @@ void schedule_render(session_t *ps) { ps->target_msc = ps->last_msc + frame_delay; auto delay = deadline - (uint64_t)render_time_estimate - now_us; delay_s = (double)delay / 1000000.0; + if (delay_s > 1) { + log_warn("Delay too long: %f s, render_time: %d us, frame_time: %" PRIu64 + " us, now_us: %" PRIu64 " us, next_msc: %" PRIu64 " us", + delay_s, render_time_estimate, frame_time, now_us, deadline); + } log_trace("Delay: %lu us, last_msc: %" PRIu64 ", render_time: %d, frame_time: " "%" PRIu64 ", now_us: %" PRIu64 ", next_msc: %" PRIu64, @@ -257,6 +266,18 @@ schedule: } void queue_redraw(session_t *ps) { + if (ps->screen_is_off) { + // The screen is off, if there is a draw queued for the next frame (i.e. + // ps->redraw_needed == true), it won't be triggered until the screen is + // on again, because the abnormal Present events we will receive from the + // X server when the screen is off. Yet we need the draw_callback to be + // called as soon as possible so the screen can be unredirected. + // So here we unconditionally start the draw timer. + ev_timer_stop(ps->loop, &ps->draw_timer); + ev_timer_set(&ps->draw_timer, 0, 0); + ev_timer_start(ps->loop, &ps->draw_timer); + return; + } // Whether we have already rendered for the current frame. // If frame pacing is not enabled, pretend this is false. // If --benchmark is used, redraw is always queued