From 22d6df280117fba8eb7584bca631d73a7ba359e2 Mon Sep 17 00:00:00 2001 From: Bob Loosen <bob.loosen@gmail.com> Date: Wed, 8 May 2013 21:08:46 +0200 Subject: [PATCH] Add Vsync support for the compositor (bug #8898). --- configure.ac.in | 9 + defaults/defaults | 1 + settings-dialogs/tweaks-settings.c | 9 + settings-dialogs/xfwm4-tweaks-dialog.glade | 40 +++- src/Makefile.am | 3 +- src/compositor.c | 255 ++++++++++++++++++++- src/screen.h | 13 ++ src/settings.c | 7 + src/settings.h | 1 + 9 files changed, 324 insertions(+), 14 deletions(-) diff --git a/configure.ac.in b/configure.ac.in index 9aa2617d5..9d90daccf 100644 --- a/configure.ac.in +++ b/configure.ac.in @@ -19,6 +19,7 @@ m4_define([xcomposite_minimum_version], [0.2]) m4_define([wnck_minimum_version], [2.22]) m4_define([startup_notification_minimum_version], [0.5]) m4_define([intltool_minimum_version], [0.31]) +m4_define([libdrm_minimum_version], [2.4]) dnl init autoconf AC_COPYRIGHT([Copyright (c) 2002-2011 @@ -89,6 +90,14 @@ XDT_CHECK_PACKAGE([LIBWNCK], [libwnck-1.0], [wnck_minimum_version]) XDT_CHECK_PACKAGE([DBUS], [dbus-1], [1.0.0]) XDT_CHECK_PACKAGE([DBUS_GLIB], [dbus-glib-1], [0.72]) +dnl +dnl Sync to vblank support +dnl +XDT_CHECK_OPTIONAL_PACKAGE([LIBDRM], + [libdrm], [libdrm_minimum_version], + [libdrm], + [userspace interface to the kernel DRM services], [yes]) + dnl dnl Startup notification support dnl diff --git a/defaults/defaults b/defaults/defaults index 6db210a28..17dc3d719 100644 --- a/defaults/defaults +++ b/defaults/defaults @@ -49,6 +49,7 @@ snap_resist=false snap_to_border=true snap_to_windows=false snap_width=10 +sync_to_vblank=false theme=Default tile_on_move=true title_alignment=center diff --git a/settings-dialogs/tweaks-settings.c b/settings-dialogs/tweaks-settings.c index f60b60669..0b6a7eb9a 100644 --- a/settings-dialogs/tweaks-settings.c +++ b/settings-dialogs/tweaks-settings.c @@ -205,6 +205,7 @@ wm_tweaks_dialog_configure_widgets (GtkBuilder *builder) GtkWidget *show_frame_shadow_check = GTK_WIDGET (gtk_builder_get_object (builder, "show_frame_shadow_check")); GtkWidget *show_popup_shadow_check = GTK_WIDGET (gtk_builder_get_object (builder, "show_popup_shadow_check")); GtkWidget *show_dock_shadow_check = GTK_WIDGET (gtk_builder_get_object (builder, "show_dock_shadow_check")); + GtkWidget *sync_to_vblank_check = GTK_WIDGET (gtk_builder_get_object (builder, "sync_to_vblank_check")); GtkWidget *frame_opacity_scale = GTK_WIDGET (gtk_builder_get_object (builder, "frame_opacity_scale")); GtkWidget *inactive_opacity_scale = GTK_WIDGET (gtk_builder_get_object (builder, "inactive_opacity_scale")); @@ -405,6 +406,14 @@ wm_tweaks_dialog_configure_widgets (GtkBuilder *builder) "/general/show_dock_shadow", G_TYPE_BOOLEAN, (GObject *)show_dock_shadow_check, "active"); +#ifdef HAVE_LIBDRM + xfconf_g_property_bind (xfwm4_channel, + "/general/sync_to_vblank", + G_TYPE_BOOLEAN, + (GObject *)sync_to_vblank_check, "active"); +#else + gtk_widget_hide (sync_to_vblank_check); +#endif xfconf_g_property_bind (xfwm4_channel, "/general/frame_opacity", diff --git a/settings-dialogs/xfwm4-tweaks-dialog.glade b/settings-dialogs/xfwm4-tweaks-dialog.glade index e9996afd0..d026dfd3f 100644 --- a/settings-dialogs/xfwm4-tweaks-dialog.glade +++ b/settings-dialogs/xfwm4-tweaks-dialog.glade @@ -840,8 +840,8 @@ when switching via keyboard shortcuts</property> </packing> </child> <child> - <object class="GtkCheckButton" id="show_frame_shadow_check"> - <property name="label" translatable="yes">Show shadows under _regular windows</property> + <object class="GtkCheckButton" id="sync_to_vblank_check"> + <property name="label" translatable="yes">Synchronize drawing to the _vertical blank</property> <property name="use_action_appearance">False</property> <property name="visible">True</property> <property name="can_focus">True</property> @@ -887,6 +887,22 @@ when switching via keyboard shortcuts</property> <property name="position">3</property> </packing> </child> + <child> + <object class="GtkCheckButton" id="show_frame_shadow_check"> + <property name="label" translatable="yes">Show shadows under _regular windows</property> + <property name="use_action_appearance">False</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="use_underline">True</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> <child> <object class="GtkLabel" id="label12"> <property name="visible">True</property> @@ -899,7 +915,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">True</property> - <property name="position">4</property> + <property name="position">5</property> </packing> </child> <child> @@ -959,7 +975,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">5</property> + <property name="position">6</property> </packing> </child> <child> @@ -974,7 +990,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">6</property> + <property name="position">7</property> </packing> </child> <child> @@ -1034,7 +1050,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">7</property> + <property name="position">8</property> </packing> </child> <child> @@ -1049,7 +1065,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">8</property> + <property name="position">9</property> </packing> </child> <child> @@ -1109,7 +1125,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">9</property> + <property name="position">10</property> </packing> </child> <child> @@ -1124,7 +1140,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">10</property> + <property name="position">11</property> </packing> </child> <child> @@ -1184,7 +1200,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">11</property> + <property name="position">12</property> </packing> </child> <child> @@ -1199,7 +1215,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">12</property> + <property name="position">13</property> </packing> </child> <child> @@ -1259,7 +1275,7 @@ when switching via keyboard shortcuts</property> <packing> <property name="expand">False</property> <property name="fill">False</property> - <property name="position">13</property> + <property name="position">14</property> </packing> </child> </object> diff --git a/src/Makefile.am b/src/Makefile.am index f92e3f9c6..ee2fff6fd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -80,6 +80,7 @@ xfwm4_CFLAGS = \ $(LIBXFCE4UI_CFLAGS) \ $(LIBXFCE4KBD_PRIVATE_CFLAGS) \ $(RENDER_CFLAGS) \ + $(LIBDRM_CFLAGS) \ $(LIBSTARTUP_NOTIFICATION_CFLAGS) \ $(COMPOSITOR_CFLAGS) \ -DPACKAGE_LOCALE_DIR=\"$(localedir)\" \ @@ -101,7 +102,7 @@ xfwm4_LDADD = \ $(RENDER_LIBS) \ $(COMPOSITOR_LIBS) \ $(RANDR_LIBS) \ - $(MATH_LIBS) + $(MATH_LIBS) EXTRA_DIST = \ default_icon.png \ diff --git a/src/compositor.c b/src/compositor.c index 428989ccb..03607d063 100644 --- a/src/compositor.c +++ b/src/compositor.c @@ -36,6 +36,14 @@ #include <string.h> #include <libxfce4util/libxfce4util.h> +#ifdef HAVE_LIBDRM +#include <stdint.h> +#include <fcntl.h> +#include <errno.h> +#include <drm.h> +#include <stropts.h> +#endif /* HAVE_LIBDRM */ + #include "display.h" #include "screen.h" #include "client.h" @@ -86,7 +94,12 @@ #define WIN_IS_REDIRECTED(cw) (cw->redirected) /* Set TIMEOUT_REPAINT to 0 to disable timeout repaint */ -#define TIMEOUT_REPAINT 10 /* msec.) */ +#define TIMEOUT_REPAINT 10 /* msec */ +#define TIMEOUT_REPAINT_MIN 1 +#define TIMEOUT_REPAINT_MAX 20 +#define TIMEOUT_DRI 10 /* seconds */ + +#define DRM_CARD0 "/dev/dri/card0" typedef struct _CWindow CWindow; struct _CWindow @@ -1247,6 +1260,113 @@ paint_win (CWindow *cw, XserverRegion region, gboolean solid_part) } } +#if HAVE_LIBDRM +#if TIMEOUT_REPAINT + +static void +open_dri (ScreenInfo *screen_info) +{ + screen_info->dri_fd = open (DRM_CARD0, O_RDWR); + if (screen_info->dri_fd == -1) + { + g_warning ("Error opening %s: %s", DRM_CARD0, g_strerror (errno)); + } +} + +static void +close_dri (ScreenInfo *screen_info) +{ + if (screen_info->dri_fd != -1) + { + close (screen_info->dri_fd); + screen_info->dri_fd = -1; + } +} + +static gboolean +dri_enabled (ScreenInfo *screen_info) +{ + return (screen_info->dri_fd != -1 && screen_info->params->sync_to_vblank); +} + +static void +wait_vblank (ScreenInfo *screen_info) +{ + int retval; + drm_wait_vblank_t vblank; + + if (screen_info->dri_time > g_get_monotonic_time()) + { + return; + } + + vblank.request.sequence = 1; + vblank.request.type = _DRM_VBLANK_RELATIVE; + if (screen_info->dri_secondary) + { + vblank.request.type |= _DRM_VBLANK_SECONDARY; + } + + do + { + retval = ioctl (screen_info->dri_fd, DRM_IOCTL_WAIT_VBLANK, &vblank); + vblank.request.type &= ~_DRM_VBLANK_RELATIVE; + } + while (retval == -1 && errno == EINTR); + + screen_info->vblank_time = g_get_monotonic_time (); + + if (retval == -1) + { + if (screen_info->dri_success) + { + screen_info->dri_success = FALSE; + g_warning ("Error waiting on vblank with DRI: %s", g_strerror (errno)); + } + + /* if getting the vblank fails, try to get it from the other output */ + screen_info->dri_secondary = !screen_info->dri_secondary; + + /* the output that we tried to get the vblank from might be disabled, + if that's the case, the device needs to be reopened, or it will continue to fail */ + close_dri (screen_info); + open_dri (screen_info); + + /* retry in 10 seconds */ + screen_info->dri_time = g_get_monotonic_time() + TIMEOUT_DRI * 1000000; + } + else if (!screen_info->dri_success) + { + g_message ("Using vertical blank of %s DRI output", + screen_info->dri_secondary ? "secondary" : "primary"); + + screen_info->dri_success = TRUE; + } +} + +#ifdef HAVE_RANDR +static void +get_refresh_rate (ScreenInfo* screen_info) +{ + gint refresh_rate; + XRRScreenConfiguration* randr_info; + + randr_info = XRRGetScreenInfo (screen_info->display_info->dpy, screen_info->xroot); + refresh_rate = XRRConfigCurrentRate (randr_info); + XRRFreeScreenConfigInfo (randr_info); + + if (refresh_rate != screen_info->refresh_rate) + { + g_message ("Detected refreshrate:%i hertz", refresh_rate); + screen_info->refresh_rate = refresh_rate; + } +} +#endif /* HAVE_RANDR */ + +#endif /* TIMEOUT_REPAINT */ + +#endif /* HAVE_LIBDRM */ + static void paint_all (ScreenInfo *screen_info, XserverRegion region) { @@ -1258,6 +1378,12 @@ paint_all (ScreenInfo *screen_info, XserverRegion region) gint screen_height; CWindow *cw; +#ifdef HAVE_LIBDRM +#if TIMEOUT_REPAINT + gboolean use_dri; +#endif /* TIMEOUT_REPAINT */ +#endif /* HAVE_LIBDRM */ + TRACE ("entering paint_all"); g_return_if_fail (screen_info); @@ -1406,8 +1532,34 @@ paint_all (ScreenInfo *screen_info, XserverRegion region) TRACE ("Copying data back to screen"); /* Set clipping back to the given region */ XFixesSetPictureClipRegion (dpy, screen_info->rootBuffer, 0, 0, region); + +#ifdef HAVE_LIBDRM +#if TIMEOUT_REPAINT + use_dri = dri_enabled (screen_info); + + if (use_dri) + { + /* sync all previous rendering commands, tell xlib to render the pixmap + * onto the root window, wait for the vblank, then flush, this minimizes + * tearing*/ + XFlush (dpy); + } +#endif /* TIMEOUT_REPAINT */ +#endif /* HAVE_LIBDRM */ + XRenderComposite (dpy, PictOpSrc, screen_info->rootBuffer, None, screen_info->rootPicture, 0, 0, 0, 0, 0, 0, screen_width, screen_height); + +#ifdef HAVE_LIBDRM +#if TIMEOUT_REPAINT + if (use_dri) + { + wait_vblank (screen_info); + XFlush (dpy); + } +#endif /* TIMEOUT_REPAINT */ +#endif /* HAVE_LIBDRM */ + XFixesDestroyRegion (dpy, paint_region); } @@ -1467,13 +1619,57 @@ static void add_repair (ScreenInfo *screen_info) { #if TIMEOUT_REPAINT +#ifdef HAVE_LIBDRM + gint64 interval; +#endif /* HAVE_LIBDRM */ + if (screen_info->compositor_timeout_id != 0) { return; } + +#ifdef HAVE_LIBDRM + if (dri_enabled (screen_info)) + { + /* schedule the next render to be half a refresh period after the last vertical blank, + but at least 1 ms in the future so that all queued events can be processed, + and to reduce latency if we didn't render for a while */ +#ifdef HAVE_RANDR + if (screen_info->refresh_rate > 0) + { + interval = (screen_info->vblank_time + 500000 / screen_info->refresh_rate - + g_get_monotonic_time ()) / 1000; + } + else +#endif /* HAVE_RANDR */ + { + interval = TIMEOUT_REPAINT - ((g_get_monotonic_time () - screen_info->vblank_time) / 1000); + } + + if (interval > TIMEOUT_REPAINT_MAX) + { + interval = TIMEOUT_REPAINT_MAX; + } + else if (interval < TIMEOUT_REPAINT_MIN) + { + interval = TIMEOUT_REPAINT_MIN; + } + } + else + { + interval = TIMEOUT_REPAINT; + } +#endif /* HAVE_LIBDRM */ + screen_info->compositor_timeout_id = +#ifdef HAVE_LIBDRM + g_timeout_add (interval, + compositor_timeout_cb, screen_info); +#else g_timeout_add (TIMEOUT_REPAINT, compositor_timeout_cb, screen_info); +#endif /*HAVE_LIBDRM */ + #endif /* TIMEOUT_REPAINT */ } @@ -2543,6 +2739,26 @@ compositorHandleShapeNotify (DisplayInfo *display_info, XShapeEvent *ev) } } +#ifdef HAVE_LIBDRM +#ifdef HAVE_RANDR +static void +compositorHandleRandrNotify (DisplayInfo *display_info, XRRScreenChangeNotifyEvent *ev) +{ + ScreenInfo *screen_info; + + g_return_if_fail (display_info != NULL); + g_return_if_fail (ev != NULL); + TRACE ("entering compositorHandleRandrNotify for 0x%lx", ev->window); + + screen_info = myDisplayGetScreenFromRoot (display_info, ev->window); + if (screen_info) + get_refresh_rate (screen_info); + + XRRUpdateConfiguration ((XEvent *) ev); +} +#endif /* HAVE_RANDR */ +#endif /* HAVE_LIBDRM */ + static void compositorSetCMSelection (ScreenInfo *screen_info, Window w) { @@ -2753,6 +2969,15 @@ compositorHandleEvent (DisplayInfo *display_info, XEvent *ev) { compositorHandleShapeNotify (display_info, (XShapeEvent *) ev); } +#ifdef HAVE_LIBDRM +#ifdef HAVE_RANDR + else if (ev->type == (display_info->xrandr_event_base + RRScreenChangeNotify)) + { + compositorHandleRandrNotify (display_info, (XRRScreenChangeNotifyEvent *) ev); + } +#endif /* HAVE_RANDR */ +#endif /* HAVE_LIBDRM */ + #if TIMEOUT_REPAINT == 0 repair_display (display_info); #endif /* TIMEOUT_REPAINT */ @@ -2971,6 +3196,22 @@ compositorManageScreen (ScreenInfo *screen_info) compositorSetCMSelection (screen_info, screen_info->xfwm4_win); TRACE ("Manual compositing enabled"); +#ifdef HAVE_LIBDRM + open_dri (screen_info); + screen_info->dri_success = TRUE; + screen_info->dri_secondary = FALSE; + screen_info->dri_time = 0; + screen_info->vblank_time = 0; + +#ifdef HAVE_RANDR + if (display_info->have_xrandr) + { + get_refresh_rate(screen_info); + XRRSelectInput(display_info->dpy, screen_info->xroot, RRScreenChangeNotifyMask); + } +#endif /* HAVE_RANDR */ +#endif /* HAVE_LIBDRM */ + return TRUE; #else return FALSE; @@ -3063,6 +3304,18 @@ compositorUnmanageScreen (ScreenInfo *screen_info) display_info->composite_mode); compositorSetCMSelection (screen_info, None); + +#ifdef HAVE_LIBDRM + close_dri (screen_info); + +#ifdef HAVE_RANDR + if (display_info->have_xrandr) + { + XRRSelectInput (display_info->dpy, screen_info->xroot, 0); + } +#endif /* HAVE_RANDR */ +#endif /* HAVE_LIBDRM */ + #endif /* HAVE_COMPOSITOR */ } diff --git a/src/screen.h b/src/screen.h index 88e484509..a2b25c02d 100644 --- a/src/screen.h +++ b/src/screen.h @@ -181,6 +181,19 @@ struct _ScreenInfo gboolean damages_pending; guint compositor_timeout_id; + +#ifdef HAVE_LIBDRM + gint dri_fd; + gboolean dri_secondary; + gboolean dri_success; + gint64 dri_time; + gint64 vblank_time; + +#ifdef HAVE_RANDR + gint refresh_rate; +#endif /* HAVE_RANDR */ +#endif /* HAVE_LIBDRM */ + #endif /* HAVE_COMPOSITOR */ }; diff --git a/src/settings.c b/src/settings.c index 607bb7b10..a8a9e066f 100644 --- a/src/settings.c +++ b/src/settings.c @@ -720,6 +720,7 @@ loadSettings (ScreenInfo *screen_info) {"snap_to_border", NULL, G_TYPE_BOOLEAN, TRUE}, {"snap_to_windows", NULL, G_TYPE_BOOLEAN, TRUE}, {"snap_width", NULL, G_TYPE_INT, TRUE}, + {"sync_to_vblank", NULL, G_TYPE_BOOLEAN, TRUE}, {"theme", NULL, G_TYPE_STRING, TRUE}, {"tile_on_move", NULL, G_TYPE_BOOLEAN, TRUE}, {"title_alignment", NULL, G_TYPE_STRING, TRUE}, @@ -823,6 +824,8 @@ loadSettings (ScreenInfo *screen_info) getBoolValue ("snap_resist", rc); screen_info->params->snap_width = getIntValue ("snap_width", rc); + screen_info->params->sync_to_vblank = + getBoolValue ("sync_to_vblank", rc); screen_info->params->tile_on_move = getBoolValue ("tile_on_move", rc); screen_info->params->toggle_workspaces = @@ -1329,6 +1332,10 @@ cb_xfwm4_channel_property_changed(XfconfChannel *channel, const gchar *property_ { screen_info->params->tile_on_move = g_value_get_boolean (value); } + else if (!strcmp (name, "sync_to_vblank")) + { + screen_info->params->sync_to_vblank = g_value_get_boolean (value); + } else if (!strcmp (name, "toggle_workspaces")) { screen_info->params->toggle_workspaces = g_value_get_boolean (value); diff --git a/src/settings.h b/src/settings.h index be01b6b1f..d595ddc1d 100644 --- a/src/settings.h +++ b/src/settings.h @@ -223,6 +223,7 @@ struct _XfwmParams gboolean snap_resist; gboolean snap_to_border; gboolean snap_to_windows; + gboolean sync_to_vblank; gboolean tile_on_move; gboolean title_vertical_offset_active; gboolean title_vertical_offset_inactive; -- GitLab