From ee93a06b8b1922b31e12cfe60566779f185ddeba Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 29 Jul 2022 16:20:32 +0800 Subject: [PATCH] Implement monitor refresh rate synchronization on X * src/xfns.c (x_set_parent_frame, Fx_create_frame): Disable vsync on child and embedded frames. * src/xmenu.c (x_menu_show): Fix XMenu position calculation in child frames. * src/xterm.c (x_sync_is_frame_drawn_event) (x_sync_wait_for_frame_drawn_event): New functions. (x_sync_update_begin): Wait for frame to be drawn if not double buffered. (x_sync_update_finish): Set FRAME_X_WAITING_FOR_DRAW (f). (show_back_buffer): Wait for frame to be drawn before flipping buffers. (XTframe_up_to_date): Set FRAME_X_WAITING_FOR_DRAW if bumped. (handle_one_xevent): Handle frame drawn events. * src/xterm.h (struct x_output): New fields for frame dirtyness and vsync. --- src/xfns.c | 21 +++++++++++- src/xmenu.c | 23 +++++++------ src/xterm.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++++++--- src/xterm.h | 25 ++++++++++++++ 4 files changed, 147 insertions(+), 15 deletions(-) diff --git a/src/xfns.c b/src/xfns.c index 076cd97875a..579237068a2 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -976,6 +976,16 @@ x_set_parent_frame (struct frame *f, Lisp_Object new_value, Lisp_Object old_valu gdk_x11_window_set_frame_sync_enabled (window, FALSE); } #endif + +#if defined HAVE_XSYNC && !defined USE_GTK + /* Frame synchronization can't be used in child frames since + they are not directly managed by the compositing manager. + Re-enabling vsync in former child frames also leads to + inconsistent display. In addition, they can only be updated + outside of a toplevel frame. */ + FRAME_X_OUTPUT (f)->use_vsync_p = false; + FRAME_X_WAITING_FOR_DRAW (f) = false; +#endif unblock_input (); fset_parent_frame (f, new_value); @@ -5113,7 +5123,10 @@ This function is an internal primitive--use `make-frame' instead. */) } #ifdef HAVE_XSYNC - if (dpyinfo->xsync_supported_p) + if (dpyinfo->xsync_supported_p + /* Frame synchronization isn't supported in child frames. */ + && NILP (parent_frame) + && !f->output_data.x->explicit_parent) { #ifndef HAVE_GTK3 XSyncValue initial_value; @@ -5149,6 +5162,12 @@ This function is an internal primitive--use `make-frame' instead. */) ((STRINGP (value) && !strcmp (SSDATA (value), "extended")) ? 2 : 1)); #endif + +#ifndef USE_GTK + if (FRAME_X_EXTENDED_COUNTER (f)) + FRAME_X_OUTPUT (f)->use_vsync_p + = x_wm_supports (f, dpyinfo->Xatom_net_wm_frame_drawn); +#endif } #endif diff --git a/src/xmenu.c b/src/xmenu.c index e5e24b87d16..3be0fb18766 100644 --- a/src/xmenu.c +++ b/src/xmenu.c @@ -2536,6 +2536,9 @@ Lisp_Object x_menu_show (struct frame *f, int x, int y, int menuflags, Lisp_Object title, const char **error_name) { +#ifdef HAVE_X_WINDOWS + Window dummy_window; +#endif Window root; XMenu *menu; int pane, selidx, lpane, status; @@ -2584,20 +2587,22 @@ x_menu_show (struct frame *f, int x, int y, int menuflags, inhibit_garbage_collection (); #ifdef HAVE_X_WINDOWS - { - /* Adjust coordinates to relative to the outer (window manager) window. */ - int left_off, top_off; + XTranslateCoordinates (FRAME_X_DISPLAY (f), - x_real_pos_and_offsets (f, &left_off, NULL, &top_off, NULL, - NULL, NULL, NULL, NULL, NULL); + /* From-window, to-window. */ + FRAME_X_WINDOW (f), + FRAME_DISPLAY_INFO (f)->root_window, - x += left_off; - y += top_off; - } -#endif /* HAVE_X_WINDOWS */ + /* From-position, to-position. */ + x, y, &x, &y, + /* Child of win. */ + &dummy_window); +#else + /* MSDOS without X support. */ x += f->left_pos; y += f->top_pos; +#endif /* Create all the necessary panes and their items. */ maxwidth = maxlines = lines = i = 0; diff --git a/src/xterm.c b/src/xterm.c index e9db4b364fb..d3ffd432dd2 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -6597,6 +6597,43 @@ x_set_frame_alpha (struct frame *f) ***********************************************************************/ #if defined HAVE_XSYNC && !defined USE_GTK +static Bool +x_sync_is_frame_drawn_event (Display *dpy, XEvent *event, + XPointer user_data) +{ + struct frame *f; + struct x_display_info *dpyinfo; + + f = (struct frame *) user_data; + dpyinfo = FRAME_DISPLAY_INFO (f); + + if (event->type == ClientMessage + && (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_drawn) + && event->xclient.window == FRAME_OUTER_WINDOW (f)) + return True; + + return False; +} + +/* Wait for the compositing manager to finish drawing the last frame. + If the compositing manager has already drawn everything, do + nothing. */ + +static void +x_sync_wait_for_frame_drawn_event (struct frame *f) +{ + XEvent event; + + if (!FRAME_X_WAITING_FOR_DRAW (f)) + return; + + /* Wait for the frame drawn message to arrive. */ + XIfEvent (FRAME_X_DISPLAY (f), &event, + x_sync_is_frame_drawn_event, (XPointer) f); + FRAME_X_WAITING_FOR_DRAW (f) = false; +} + /* Tell the compositing manager to postpone updates of F until a frame has finished drawing. */ @@ -6616,6 +6653,15 @@ x_sync_update_begin (struct frame *f) if (XSyncValueLow32 (value) % 2) return; + /* Wait for a pending frame draw event if the last frame has not yet + been drawn if F isn't double buffered. (In double buffered + frames, this happens before buffer flipping). */ + +#ifdef HAVE_XDBE + if (!FRAME_X_DOUBLE_BUFFERED_P (f)) +#endif + x_sync_wait_for_frame_drawn_event (f); + /* Since Emacs needs a non-urgent redraw, ensure that value % 4 == 0. */ if (XSyncValueLow32 (value) % 4 == 2) @@ -6668,7 +6714,20 @@ x_sync_update_finish (struct frame *f) FRAME_X_EXTENDED_COUNTER (f), FRAME_X_COUNTER_VALUE (f)); - /* TODO: implement sync fences. */ + /* FIXME: this leads to freezes if the compositing manager crashes + in the meantime. */ + if (FRAME_OUTPUT_DATA (f)->use_vsync_p) + FRAME_X_WAITING_FOR_DRAW (f) = true; +} + +/* Handle a _NET_WM_FRAME_DRAWN message from the compositor. */ + +static void +x_sync_handle_frame_drawn (struct x_display_info *dpyinfo, + XEvent *message, struct frame *f) +{ + if (FRAME_OUTER_WINDOW (f) == message->xclient.window) + FRAME_X_WAITING_FOR_DRAW (f) = false; } #endif @@ -6775,16 +6834,26 @@ x_draw_window_divider (struct window *w, int x0, int x1, int y0, int y1) static void show_back_buffer (struct frame *f) { + XdbeSwapInfo swap_info; +#ifdef USE_CAIRO + cairo_t *cr; +#endif + block_input (); if (FRAME_X_DOUBLE_BUFFERED_P (f)) { +#if defined HAVE_XSYNC && !defined USE_GTK + /* Wait for drawing of the previous frame to complete before + displaying this new frame. */ + x_sync_wait_for_frame_drawn_event (f); +#endif + #ifdef USE_CAIRO - cairo_t *cr = FRAME_CR_CONTEXT (f); + cr = FRAME_CR_CONTEXT (f); if (cr) cairo_surface_flush (cairo_get_target (cr)); #endif - XdbeSwapInfo swap_info; memset (&swap_info, 0, sizeof (swap_info)); swap_info.swap_window = FRAME_X_WINDOW (f); swap_info.swap_action = XdbeCopied; @@ -6911,6 +6980,11 @@ XTframe_up_to_date (struct frame *f) FRAME_X_COUNTER_VALUE (f)); FRAME_X_OUTPUT (f)->ext_sync_end_pending_p = false; + +#ifndef USE_GTK + if (FRAME_OUTPUT_DATA (f)->use_vsync_p) + FRAME_X_WAITING_FOR_DRAW (f) = true; +#endif } #else if (FRAME_X_OUTPUT (f)->xg_sync_end_pending_p) @@ -17072,8 +17146,17 @@ handle_one_xevent (struct x_display_info *dpyinfo, #if defined HAVE_XSYNC && !defined USE_GTK /* These messages are sent by the compositing manager after a frame is drawn under extended synchronization. */ - if (event->xclient.message_type == dpyinfo->Xatom_net_wm_frame_drawn - || event->xclient.message_type == dpyinfo->Xatom_net_wm_frame_timings) + if (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_drawn) + { + if (any) + x_sync_handle_frame_drawn (dpyinfo, (XEvent *) event, any); + + goto done; + } + + if (event->xclient.message_type + == dpyinfo->Xatom_net_wm_frame_timings) goto done; #endif diff --git a/src/xterm.h b/src/xterm.h index 3e237158e7e..1163dd5cd1e 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1026,16 +1026,39 @@ struct x_output #endif #ifdef HAVE_XSYNC + /* The "basic frame counter" used for resize synchronization. */ XSyncCounter basic_frame_counter; + + /* The "extended frame counter" used for frame synchronization. */ XSyncCounter extended_frame_counter; + + /* The pending value of the basic counter. */ XSyncValue pending_basic_counter_value; + + /* The current value of the extended counter. */ XSyncValue current_extended_counter_value; + /* Whether or not basic resize synchronization is in progress. */ bool_bf sync_end_pending_p : 1; + + /* Whether or not extended resize synchronization is in + progress. */ bool_bf ext_sync_end_pending_p : 1; + #ifdef HAVE_GTK3 + /* Whether or not GDK resize synchronization is in progress. */ bool_bf xg_sync_end_pending_p : 1; #endif + + /* Whether or Emacs is waiting for the compositing manager to draw a + frame. */ + bool_bf waiting_for_frame_p : 1; + +#ifndef USE_GTK + /* Whether or not Emacs should wait for the compositing manager to + draw frames before starting a new frame. */ + bool_bf use_vsync_p : 1; +#endif #endif /* Relief GCs, colors etc. */ @@ -1215,6 +1238,8 @@ extern void x_mark_frame_dirty (struct frame *f); FRAME_X_OUTPUT (f)->basic_frame_counter #define FRAME_X_EXTENDED_COUNTER(f) \ FRAME_X_OUTPUT (f)->extended_frame_counter +#define FRAME_X_WAITING_FOR_DRAW(f) \ + FRAME_X_OUTPUT (f)->waiting_for_frame_p #define FRAME_X_COUNTER_VALUE(f) \ FRAME_X_OUTPUT (f)->current_extended_counter_value #endif -- 2.39.5