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 <yshuiv7@gmail.com>
This commit is contained in:
Yuxuan Shui 2023-03-07 17:03:26 +00:00
parent edeb17ad46
commit fcb9dc8cfd
No known key found for this signature in database
GPG Key ID: D3A4405BE6CC17F4
1 changed files with 24 additions and 3 deletions

View File

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