From b59e592588e0d6f09085ec7b7f3381abb7945453 Mon Sep 17 00:00:00 2001 From: Richard Grenville Date: Mon, 8 Oct 2012 10:20:01 +0800 Subject: [PATCH] Feature: #7: VSync - Add VSync feature. 3 possible VSync methods available: "sw" (software, not too reliable, but at least you have something to fallback to), "drm" (using DRM_IOCTL_WAIT_VBLANK, should work only on DRI drivers), "opengl" (using SGI_swap_control extension OpenGL, might work on more drivers than the DRM method). "sw" and "opengl" are briefly tested, "drm" received utterly no test (because I use the nVidia binary blob). They are enabled with "--vsync sw" / "--vsync drm" / "--vsync opengl". - Add --refresh-rate to let user specify a refresh rate for software VSync, in case the automatic refresh rate detection does not work well. - Seemingly the automatic refresh rate detection using X RandR in software VSync detects refresh rate incorrectly. Need further investigation. - Fix a few bugs in fading timing. - Add a workaround for client window detection on Fluxbox, as Fluxbox (incorrectly?) sets the override-redirect flag upon all frame windows. - Software VSync adds dependency on librt (a part of glibc) for nanosecond-level timing functions, and libXrandr for automatic refresh rate detection; DRM VSync adds dependency on libdrm to use its drm.h, but does not link to libdrm; OpenGL VSync adds dependency on libGL. - Print timing information on DEBUG_REPAINT. --- CMakeLists.txt | 19 +- Makefile | 13 +- README.md | 3 + compton.sample.conf | 1 + src/compton.c | 545 ++++++++++++++++++++++++++++++++++++++++---- src/compton.h | 123 +++++++++- 6 files changed, 654 insertions(+), 50 deletions(-) mode change 100644 => 100755 src/compton.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7969e70..838fa55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ set(CMAKE_C_FLAGS_DEBUG "-ggdb") set(CMAKE_C_FLAGS_RELEASE "-O2 -march=native") set(CMAKE_C_FLAGS_RELWITHDEBINFO "-O2 -march=native -ggdb") -add_definitions("-Wall") +add_definitions("-Wall" "-std=c99") # == Options == @@ -31,9 +31,23 @@ if (CONFIG_LIBCONFIG) add_definitions("-DCONFIG_LIBCONFIG") endif () +option(CONFIG_VSYNC_DRM "Enable DRM VSync support" ON) +if (CONFIG_VSYNC_DRM) + add_definitions("-DCONFIG_VSYNC_DRM") +endif () + +option(CONFIG_VSYNC_OPENGL "Enable OpenGL VSync support" ON) +if (CONFIG_VSYNC_OPENGL) + add_definitions("-DCONFIG_VSYNC_OPENGL") +endif () + # == Find libraries == -target_link_libraries(compton "-lm") +target_link_libraries(compton "-lm" "-lrt") + +if (CONFIG_VSYNC_OPENGL) + target_link_libraries(compton "-lGL") +endif () include(FindPkgConfig) @@ -53,6 +67,7 @@ X11LIB_CHK(Xdamage) X11LIB_CHK(Xext) X11LIB_CHK(Xfixes) X11LIB_CHK(Xrender) +X11LIB_CHK(Xrandr) # --- Find libpcre --- if (CONFIG_REGEX_PCRE) diff --git a/Makefile b/Makefile index fda1e85..4b8937c 100644 --- a/Makefile +++ b/Makefile @@ -4,8 +4,8 @@ PREFIX ?= /usr BINDIR ?= $(PREFIX)/bin MANDIR ?= $(PREFIX)/share/man/man1 -PACKAGES = x11 xcomposite xfixes xdamage xrender xext -LIBS = -lm +PACKAGES = x11 xcomposite xfixes xdamage xrender xext xrandr +LIBS = -lm -lrt INCS = # Parse configuration flags @@ -29,6 +29,15 @@ ifeq "$(NO_REGEX_PCRE)" "" endif endif +ifeq "$(NO_VSYNC_DRM)" "" + CFG += -DCONFIG_VSYNC_DRM +endif + +ifeq "$(NO_VSYNC_OPENGL)" "" + CFG += -DCONFIG_VSYNC_OPENGL + LIBS += -lGL +endif + CFLAGS += $(CFG) LIBS += $(shell pkg-config --libs $(PACKAGES)) diff --git a/README.md b/README.md index d3dfc5c..f6dbd52 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ __R__ for runtime * libxfixes (B,R) * libXext (B,R) * libxrender (B,R) +* libXrandr (B,R) * pkg-config (B) * make (B) * xproto / x11proto (B) @@ -52,6 +53,8 @@ __R__ for runtime * xprop,xwininfo / x11-utils (R) * libpcre (B,R) (Will probably be made optional soon) * libconfig (B,R) (Will probably be made optional soon) +* libdrm (B) (Will probably be made optional soon) +* libGL (B,R) (Will probably be made optional soon) To build, make sure you have the above dependencies: diff --git a/compton.sample.conf b/compton.sample.conf index f814f87..880046a 100644 --- a/compton.sample.conf +++ b/compton.sample.conf @@ -32,6 +32,7 @@ mark-wmwin-focused = true; mark-ovredir-focused = true; detect-rounded-corners = true; detect-client-opacity = false; +refresh-rate = 0; # Window type settings wintypes: diff --git a/src/compton.c b/src/compton.c old mode 100644 new mode 100755 index a64b0b7..81199cd --- a/src/compton.c +++ b/src/compton.c @@ -51,22 +51,62 @@ XserverRegion all_damage; Bool has_name_pixmap; #endif int root_height, root_width; + /// Whether the program is idling. I.e. no fading, no potential window /// changes. Bool idling; +/// Window ID of the window we register as a symbol. +Window reg_win = 0; + +/// Currently used refresh rate. Used for Software VSync. +short refresh_rate = 0; +/// Interval between refresh in nanoseconds. Used for Software VSync. +unsigned long refresh_intv = 0; +/// Nanosecond-level offset of the first painting. +/// Used for Software VSync. +long paint_tm_offset = 0; + +#ifdef CONFIG_VSYNC_DRM +/// File descriptor of DRI device file. Used for DRM VSync. +int drm_fd = 0; +#endif + +#ifdef CONFIG_VSYNC_OPENGL +/// GLX context. +GLXContext glx_context; + +/// Pointer to glXGetVideoSyncSGI function. Used by OpenGL VSync. +f_GetVideoSync glx_get_video_sync = NULL; + +/// Pointer to glXWaitVideoSyncSGI function. Used by OpenGL VSync. +f_WaitVideoSync glx_wait_video_sync = NULL; +#endif /* errors */ ignore *ignore_head = NULL, **ignore_tail = &ignore_head; int xfixes_event, xfixes_error; int damage_event, damage_error; int composite_event, composite_error; -/// Whether X Shape extension exists. -Bool shape_exists = True; -/// Event base number and error base number for X Shape extension. -int shape_event, shape_error; int render_event, render_error; int composite_opcode; +/// Whether X Shape extension exists. +Bool shape_exists = False; +/// Event base number and error base number for X Shape extension. +int shape_event, shape_error; + +/// Whether X RandR extension exists. +Bool randr_exists = False; +/// Event base number and error base number for X RandR extension. +int randr_event, randr_error; + +#ifdef CONFIG_VSYNC_OPENGL +/// Whether X GLX extension exists. +Bool glx_exists = False; +/// Event base number and error base number for X GLX extension. +int glx_event, glx_error; +#endif + /* shadows */ conv *gaussian_map; @@ -119,6 +159,9 @@ static options_t opts = { .synchronize = False, .detect_rounded_corners = False, + .refresh_rate = 0, + .vsync = VSYNC_NONE, + .wintype_shadow = { False }, .shadow_red = 0.0, .shadow_green = 0.0, @@ -162,7 +205,7 @@ unsigned long fade_time = 0; * passed since the epoch. */ static unsigned long -get_time_in_milliseconds() { +get_time_ms() { struct timeval tv; gettimeofday(&tv, NULL); @@ -177,7 +220,7 @@ get_time_in_milliseconds() { */ static int fade_timeout(void) { - int diff = opts.fade_delta - get_time_in_milliseconds() + fade_time; + int diff = opts.fade_delta - get_time_ms() + fade_time; if (diff < 0) diff = 0; @@ -1243,13 +1286,11 @@ static win * paint_preprocess(Display *dpy, win *list) { win *w; win *t = NULL, *next = NULL; - // Sounds like the timeout in poll() frequently does not work - // accurately, asking it to wait to 20ms, and often it would wait for - // 19ms, so the step value has to be rounded. - unsigned steps = roundl((double) (get_time_in_milliseconds() - fade_time) / opts.fade_delta); - // Reset fade_time - fade_time = get_time_in_milliseconds(); + // Fading step calculation + unsigned steps = (sub_unslong(get_time_ms(), fade_time) + + FADE_DELTA_TOLERANCE * opts.fade_delta) / opts.fade_delta; + fade_time += steps * opts.fade_delta; for (w = list; w; w = next) { // In case calling the fade callback function destroys this window @@ -1411,6 +1452,7 @@ paint_all(Display *dpy, XserverRegion region, win *t) { paint_root(dpy); #ifdef DEBUG_REPAINT + print_timestamp(); printf("paint:"); #endif @@ -1605,13 +1647,15 @@ map_win(Display *dpy, Window id, // window should have been prepared at this point if (!w->client_win) { Window cw = 0; - if (!w->a.override_redirect) { - cw = find_client_win(dpy, w->id); + // Always recursively look for a window with WM_STATE, as Fluxbox + // sets override-redirect flags on all frame windows. + cw = find_client_win(dpy, w->id); #ifdef DEBUG_CLIENTWIN - printf("find_client_win(%#010lx): client %#010lx\n", w->id, cw); + printf("find_client_win(%#010lx): client %#010lx\n", w->id, cw); #endif - } - else { + // Set a window's client window to itself only if we didn't find a + // client window and the window has override-redirect flag + if (!cw && w->a.override_redirect) { cw = w->id; #ifdef DEBUG_CLIENTWIN printf("find_client_win(%#010lx): client self (override-redirected)\n", w->id); @@ -2792,7 +2836,7 @@ ev_property_notify(XPropertyEvent *ev) { if ((w = find_win(dpy, ev->window))) w->opacity_prop = wid_get_opacity_prop(dpy, w->id, OPAQUE); else if (opts.detect_client_opacity - && (w = find_toplevel(dpy, ev->window))) + && (w = find_toplevel(dpy, ev->window))) w->opacity_prop_client = wid_get_opacity_prop(dpy, w->client_win, OPAQUE); if (w) { @@ -2865,7 +2909,22 @@ ev_shape_notify(XShapeEvent *ev) { } } -inline static void +/** + * Handle ScreenChangeNotify events from X RandR extension. + */ +static void +ev_screen_change_notify(XRRScreenChangeNotifyEvent *ev) { + if (!opts.refresh_rate) { + update_refresh_rate(dpy); + if (!refresh_rate) { + fprintf(stderr, "ev_screen_change_notify(): Refresh rate detection " + "failed, software VSync disabled."); + opts.vsync = VSYNC_NONE; + } + } +} + +static void ev_handle(XEvent *ev) { if ((ev->type & 0x7f) != KeymapNotify) { discard_ignore(dpy, ev->xany.serial); @@ -2939,6 +2998,10 @@ ev_handle(XEvent *ev) { ev_shape_notify((XShapeEvent *) ev); break; } + if (randr_exists && ev->type == (randr_event + RRScreenChangeNotify)) { + ev_screen_change_notify((XRRScreenChangeNotifyEvent *) ev); + break; + } if (ev->type == damage_event + XDamageNotify) { ev_damage_notify((XDamageNotifyEvent *)ev); } @@ -2950,11 +3013,14 @@ ev_handle(XEvent *ev) { * Main */ +/** + * Print usage text and exit. + */ static void usage(void) { - fprintf(stderr, "compton (development version)\n"); - fprintf(stderr, "usage: compton [options]\n"); - fprintf(stderr, + fputs( + "compton (development version)\n" + "usage: compton [options]\n" "Options:\n" "\n" "-d display\n" @@ -3026,6 +3092,20 @@ usage(void) { " managers not passing _NET_WM_OPACITY of client windows to frame\n" " windows.\n" "\n" + "--refresh-rate val\n" + " Specify refresh rate of the screen. If not specified or 0, compton\n" + " will try detecting this with X RandR extension.\n" + "--vsync vsync-method\n" + " Set VSync method. There are 4 VSync methods currently available:\n" + " none = No VSync\n" + " sw = software VSync, basically limits compton to send a request\n" + " every 1 / refresh_rate second. Experimental.\n" + " drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n" + " drivers. Experimental.\n" + " opengl = Try to VSync with SGI_swap_control OpenGL extension. Only\n" + " work on some drivers. Experimental.\n" + " (Note some VSync methods may not be enabled at compile time.)\n" + "\n" "Format of a condition:\n" "\n" " condition = :[]:\n" @@ -3041,26 +3121,71 @@ usage(void) { " flag is \"i\" (ignore case).\n" "\n" " is the actual pattern string.\n" - ); + , stderr); exit(1); } +/** + * Register a window as symbol, and initialize GLX context if wanted. + */ static void -register_cm(int scr) { - Window w; +register_cm(Bool want_glxct) { Atom a; char *buf; int len, s; - if (scr < 0) return; +#ifdef CONFIG_VSYNC_OPENGL + // Create a window with the wanted GLX visual + if (want_glxct) { + Bool ret = False; + // Get visual for the window + int attribs[] = { GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, 1, GLX_BLUE_SIZE, 1, None }; + XVisualInfo *pvi = glXChooseVisual(dpy, scr, attribs); + if (!pvi) { + fprintf(stderr, "register_cm(): Failed to choose visual required " + "by fake OpenGL VSync window. OpenGL VSync turned off.\n"); + } + else { + // Create the window + XSetWindowAttributes swa = { + .colormap = XCreateColormap(dpy, root, pvi->visual, AllocNone), + .border_pixel = 0, + }; - w = XCreateSimpleWindow( - dpy, RootWindow(dpy, 0), - 0, 0, 1, 1, 0, None, None); + pvi->screen = scr; + reg_win = XCreateWindow(dpy, root, 0, 0, 1, 1, 0, pvi->depth, + InputOutput, pvi->visual, CWBorderPixel | CWColormap, &swa); + if (!reg_win) + fprintf(stderr, "register_cm(): Failed to create window required " + "by fake OpenGL VSync. OpenGL VSync turned off.\n"); + else { + // Get GLX context + glx_context = glXCreateContext(dpy, pvi, None, GL_TRUE); + if (!glx_context) { + fprintf(stderr, "register_cm(): Failed to get GLX context. " + "OpenGL VSync turned off.\n"); + opts.vsync = VSYNC_NONE; + } + else { + // Attach GLX context + if (!(ret = glXMakeCurrent(dpy, reg_win, glx_context))) + fprintf(stderr, "register_cm(): Failed to attach GLX context." + " OpenGL VSync turned off.\n"); + } + } + } + if (!ret) + opts.vsync = VSYNC_NONE; + } +#endif + + if (!reg_win) + reg_win = XCreateSimpleWindow(dpy, root, 0, 0, 1, 1, 0, + None, None); Xutf8SetWMProperties( - dpy, w, "xcompmgr", "xcompmgr", + dpy, reg_win, "xcompmgr", "xcompmgr", NULL, 0, NULL, NULL, NULL); len = strlen(REGISTER_PROP) + 2; @@ -3077,7 +3202,7 @@ register_cm(int scr) { a = XInternAtom(dpy, buf, False); free(buf); - XSetSelectionOwner(dpy, a, w, 0); + XSetSelectionOwner(dpy, a, reg_win, 0); } static void @@ -3288,6 +3413,8 @@ parse_config(char *cpath, struct options_tmp *pcfgtmp) { // --detect-client-opacity lcfg_lookup_bool(&cfg, "detect-client-opacity", &opts.detect_client_opacity); + // --refresh-rate + lcfg_lookup_int(&cfg, "refresh-rate", &opts.refresh_rate); // --shadow-exclude { config_setting_t *setting = @@ -3351,9 +3478,17 @@ get_cfg(int argc, char *const *argv) { { "shadow-ignore-shaped", no_argument, NULL, 266 }, { "detect-rounded-corners", no_argument, NULL, 267 }, { "detect-client-opacity", no_argument, NULL, 268 }, + { "refresh-rate", required_argument, NULL, 269 }, + { "vsync", required_argument, NULL, 270 }, // Must terminate with a NULL entry { NULL, 0, NULL, 0 }, }; + const static char * const vsync_str[] = { + "none", // VSYNC_NONE + "sw", // VSYNC_SW + "drm", // VSYNC_DRM + "opengl", // VSYNC_OPENGL + }; struct options_tmp cfgtmp = { .no_dock_shadow = False, @@ -3509,6 +3644,18 @@ get_cfg(int argc, char *const *argv) { // --detect-client-opacity opts.detect_client_opacity = True; break; + case 269: + // --refresh-rate + opts.refresh_rate = atoi(optarg); + break; + case 270: + // --vsync + for (vsync_t i = 0; i < sizeof(vsync_str) / sizeof(vsync_str[0]); ++i) + if (!strcasecmp(optarg, vsync_str[i])) { + opts.vsync = i; + break; + } + break; default: usage(); break; @@ -3529,6 +3676,7 @@ get_cfg(int argc, char *const *argv) { opts.frame_opacity = normalize_d(opts.frame_opacity); opts.shadow_opacity = normalize_d(opts.shadow_opacity); cfgtmp.menu_opacity = normalize_d(cfgtmp.menu_opacity); + opts.refresh_rate = normalize_i_range(opts.refresh_rate, 0, 300); if (OPAQUE == opts.inactive_opacity) { opts.inactive_opacity = 0; } @@ -3602,6 +3750,270 @@ get_atoms(void) { "_NET_WM_WINDOW_TYPE_DND", False); } +/** + * Update refresh rate info with X Randr extension. + */ +static void +update_refresh_rate(Display *dpy) { + XRRScreenConfiguration* randr_info; + + if (!(randr_info = XRRGetScreenInfo(dpy, root))) + return; + refresh_rate = XRRConfigCurrentRate(randr_info); + + XRRFreeScreenConfigInfo(randr_info); + + if (refresh_rate) + refresh_intv = NS_PER_SEC / refresh_rate; + else + refresh_intv = 0; +} + +/** + * Initialize software VSync. + * + * @return True for success, False otherwise + */ +static Bool +vsync_sw_init(void) { + // Prepare refresh rate + // Check if user provides one + refresh_rate = opts.refresh_rate; + if (refresh_rate) + refresh_intv = NS_PER_SEC / refresh_rate; + + // Auto-detect refresh rate otherwise + if (!refresh_rate && randr_exists) { + update_refresh_rate(dpy); + } + + // Turn off vsync_sw if we can't get the refresh rate + if (!refresh_rate) + return False; + + // Monitor screen changes only if vsync_sw is enabled and we are using + // an auto-detected refresh rate + if (randr_exists && !opts.refresh_rate) + XRRSelectInput(dpy, root, RRScreenChangeNotify); + + return True; +} + +/** + * Get current time in struct timespec. + * + * Note its starting time is unspecified. + */ +static inline struct timespec +get_time_timespec(void) { + struct timespec tm = { 0 }; + + clock_gettime(CLOCK_MONOTONIC, &tm); + + // Return a time of all 0 if the call fails + return tm; +} + +/** + * Get the smaller number that is bigger than dividend and is + * N times of divisor. + */ +static inline long +lceil_ntimes(long dividend, long divisor) { + // It's possible to use the more beautiful expression here: + // ret = ((dividend - 1) / divisor + 1) * divisor; + // But it does not work well for negative values. + long ret = dividend / divisor * divisor; + if (ret < dividend) + ret += divisor; + + return ret; +} + +/** + * Calculate time for which the program should wait for events if vsync_sw is + * enabled. + * + * @param timeout old timeout value, never negative! + * @return time to wait, in struct timespec + */ +static struct timespec +vsync_sw_ntimeout(int timeout) { + // Convert the old timeout to struct timespec + struct timespec next_paint_tmout = { + .tv_sec = timeout / MS_PER_SEC, + .tv_nsec = timeout % MS_PER_SEC * (NS_PER_SEC / MS_PER_SEC) + }; + // Get the nanosecond offset of the time when the we reach the timeout + // I don't think a 32-bit long could overflow here. + long target_relative_offset = (next_paint_tmout.tv_nsec + get_time_timespec().tv_nsec - paint_tm_offset) % NS_PER_SEC; + if (target_relative_offset < 0) + target_relative_offset += NS_PER_SEC; + + assert(target_relative_offset >= 0); + + // If the target time is sufficiently close to a VSync time, don't add + // an offset, to avoid certain blocking conditions. + if ((target_relative_offset % NS_PER_SEC) < VSYNC_SW_TOLERANCE) + return next_paint_tmout; + + // Add an offset so we wait until the next VSync after timeout + next_paint_tmout.tv_nsec += lceil_ntimes(target_relative_offset, refresh_intv) - target_relative_offset; + if (next_paint_tmout.tv_nsec > NS_PER_SEC) { + next_paint_tmout.tv_nsec -= NS_PER_SEC; + ++next_paint_tmout.tv_sec; + } + + return next_paint_tmout; +} + +/** + * Initialize DRM VSync. + * + * @return True for success, False otherwise + */ +static Bool +vsync_drm_init(void) { +#ifdef CONFIG_VSYNC_DRM + // Should we always open card0? + if ((drm_fd = open("/dev/dri/card0", O_RDWR)) < 0) { + fprintf(stderr, "vsync_drm_init(): Failed to open device.\n"); + return False; + } + + if (vsync_drm_wait()) + return False; + + return True; +#else + fprintf(stderr, "Program not compiled with DRM VSync support.\n"); + return False; +#endif +} + +#ifdef CONFIG_VSYNC_DRM +/** + * Wait for next VSync, DRM method. + * + * Stolen from: https://github.com/MythTV/mythtv/blob/master/mythtv/libs/libmythtv/vsync.cpp + */ +static int +vsync_drm_wait(void) { + int ret = -1; + drm_wait_vblank_t vbl; + + vbl.request.type = _DRM_VBLANK_RELATIVE, + vbl.request.sequence = 1; + + do { + ret = ioctl(drm_fd, DRM_IOCTL_WAIT_VBLANK, &vbl); + vbl.request.type &= ~_DRM_VBLANK_RELATIVE; + } while (ret && errno == EINTR); + + if (ret) + fprintf(stderr, "vsync_drm_wait(): VBlank ioctl did not work, " + "unimplemented in this drmver?\n"); + + return ret; + +} +#endif + +/** + * Initialize OpenGL VSync. + * + * Stolen from: http://git.tuxfamily.org/?p=ccm/cairocompmgr.git;a=commitdiff;h=efa4ceb97da501e8630ca7f12c99b1dce853c73e + * Possible original source: http://www.inb.uni-luebeck.de/~boehme/xvideo_sync.html + * + * @return True for success, False otherwise + */ +static Bool +vsync_opengl_init(void) { +#ifdef CONFIG_VSYNC_OPENGL + // Get video sync functions + glx_get_video_sync = (f_GetVideoSync) + glXGetProcAddress ((const GLubyte *) "glXGetVideoSyncSGI"); + glx_wait_video_sync = (f_WaitVideoSync) + glXGetProcAddress ((const GLubyte *) "glXWaitVideoSyncSGI"); + if (!glx_wait_video_sync || !glx_get_video_sync) { + fprintf(stderr, "vsync_opengl_init(): " + "Failed to get glXWait/GetVideoSyncSGI function.\n"); + return False; + } + + return True; +#else + fprintf(stderr, "Program not compiled with OpenGL VSync support.\n"); + return False; +#endif +} + +#ifdef CONFIG_VSYNC_OPENGL +/** + * Wait for next VSync, OpenGL method. + */ +static void +vsync_opengl_wait(void) { + unsigned vblank_count; + + glx_get_video_sync(&vblank_count); + glx_wait_video_sync(2, (vblank_count + 1) % 2, &vblank_count); + // I see some code calling glXSwapIntervalSGI(1) afterwards, is it required? +} +#endif + +/** + * Wait for next vsync and timeout unless new events appear. + * + * @param fd struct pollfd used for poll() + * @param timeout second timeout (fading timeout) + * @return > 0 if we get some events, 0 if timeout is reached, < 0 on + * problems + */ +static Bool +vsync_wait(Display *dpy, struct pollfd *fd, int timeout) { + // Always wait infinitely if asked so, to minimize CPU usage + if (timeout < 0) { + int ret = poll(fd, 1, timeout); + // Reset fade_time so the fading steps during idling are not counted + fade_time = get_time_ms(); + return ret; + } + + if (VSYNC_NONE == opts.vsync) + return poll(fd, 1, timeout); + + // vsync_sw: Wait until the next sync right after next fading timeout + if (VSYNC_SW == opts.vsync) { + struct timespec new_tmout = vsync_sw_ntimeout(timeout); + // printf("ppoll(): %3ld:%09ld\n", new_tmout.tv_sec, new_tmout.tv_nsec); + return ppoll(fd, 1, &new_tmout, NULL); + } + +#ifdef CONFIG_VSYNC_DRM + // vsync_drm: We are not accepting events when waiting for next sync, + // so I guess this would generate a latency of at most one frame. I'm + // not sure if it's possible to add some smart logic in vsync_drm_wait() + // to avoid this problem, unless I could find more documentation... + if (VSYNC_DRM == opts.vsync) { + vsync_drm_wait(); + return 0; + } +#endif + +#ifdef CONFIG_VSYNC_OPENGL + // vsync_opengl: Same one-frame-latency issue, well, not sure how to deal it + // here. + if (VSYNC_OPENGL == opts.vsync) { + vsync_opengl_wait(); + return 0; + } +#endif + + // This place should not reached! + return 0; +} + int main(int argc, char **argv) { XEvent ev; @@ -3622,7 +4034,7 @@ main(int argc, char **argv) { get_cfg(argc, argv); - fade_time = get_time_in_milliseconds(); + fade_time = get_time_ms(); dpy = XOpenDisplay(opts.display); if (!dpy) { @@ -3667,11 +4079,39 @@ main(int argc, char **argv) { exit(1); } - if (!XShapeQueryExtension(dpy, &shape_event, &shape_error)) { - shape_exists = False; + // Query X Shape + if (XShapeQueryExtension(dpy, &shape_event, &shape_error)) { + shape_exists = True; } - register_cm(scr); + // Query X RandR + if (VSYNC_SW == opts.vsync && !opts.refresh_rate) { + if (XRRQueryExtension(dpy, &randr_event, &randr_error)) + randr_exists = True; + else + fprintf(stderr, "No XRandR extension, automatic refresh rate " + "detection impossible.\n"); + } + +#ifdef CONFIG_VSYNC_OPENGL + // Query X GLX extension + if (VSYNC_OPENGL == opts.vsync) { + if (glXQueryExtension(dpy, &glx_event, &glx_error)) + glx_exists = True; + else { + fprintf(stderr, "No GLX extension, OpenGL VSync impossible.\n"); + opts.vsync = VSYNC_NONE; + } + } +#endif + + register_cm((VSYNC_OPENGL == opts.vsync)); + + // Initialize software/DRM/OpenGL VSync + if ((VSYNC_SW == opts.vsync && !vsync_sw_init()) + || (VSYNC_DRM == opts.vsync && !vsync_drm_init()) + || (VSYNC_OPENGL == opts.vsync && !vsync_opengl_init())) + opts.vsync = VSYNC_NONE; if (opts.fork_after_register) fork_after(); @@ -3735,29 +4175,46 @@ main(int argc, char **argv) { ufd.fd = ConnectionNumber(dpy); ufd.events = POLLIN; +#ifdef DEBUG_REPAINT + struct timespec last_paint = get_time_timespec(); +#endif + + if (VSYNC_SW == opts.vsync) + paint_tm_offset = get_time_timespec().tv_nsec; + t = paint_preprocess(dpy, list); + paint_all(dpy, None, t); // Initialize idling idling = False; - for (;;) { - do { - if (!QLength(dpy)) { - if (poll(&ufd, 1, (idling ? -1: fade_timeout())) == 0) { - break; - } - } + // Main loop + while (1) { + Bool ev_received = False; + while (QLength(dpy) + || (vsync_wait(dpy, &ufd, + (ev_received ? 0: (idling ? -1: fade_timeout()))) > 0)) { XNextEvent(dpy, &ev); - ev_handle((XEvent *)&ev); - } while (QLength(dpy)); + ev_handle((XEvent *) &ev); + ev_received = True; + } // idling will be turned off during paint_preprocess() if needed idling = True; t = paint_preprocess(dpy, list); + if (all_damage) { +#ifdef DEBUG_REPAINT + struct timespec now = get_time_timespec(); + struct timespec diff = { 0 }; + timespec_subtract(&diff, &now, &last_paint); + printf("[ %5ld:%09ld ] ", diff.tv_sec, diff.tv_nsec); + last_paint = now; +#endif + static int paint; paint_all(dpy, all_damage, t); paint++; diff --git a/src/compton.h b/src/compton.h index dca11b6..0f2f35e 100644 --- a/src/compton.h +++ b/src/compton.h @@ -26,7 +26,12 @@ // #define CONFIG_REGEX_PCRE_JIT 1 // Whether to enable parsing of configuration files using libconfig // #define CONFIG_LIBCONFIG 1 +// Whether to enable DRM VSync support +// #define CONFIG_VSYNC_DRM 1 +// Whether to enable OpenGL VSync support +// #define CONFIG_VSYNC_OPENGL 1 +#define NDEBUG 1 // === Includes === // For some special functions @@ -44,6 +49,7 @@ #include #include #include +#include #include @@ -69,6 +75,21 @@ #include #include #include +#include + +#ifdef CONFIG_VSYNC_DRM +#include +// We references some definitions in drm.h, which could also be found in +// /usr/src/linux/include/drm/drm.h, but that path is probably even less +// reliable than libdrm +#include +#include +#include +#endif + +#ifdef CONFIG_VSYNC_OPENGL +#include +#endif // === Constants === #if COMPOSITE_MAJOR > 0 || COMPOSITE_MINOR >= 2 @@ -89,6 +110,13 @@ extern struct timeval time_start; #define WINDOW_TRANS 1 #define WINDOW_ARGB 2 +#define FADE_DELTA_TOLERANCE 0.2 +#define VSYNC_SW_TOLERANCE 1000 + +#define NS_PER_SEC 1000000000L +#define US_PER_SEC 1000000L +#define MS_PER_SEC 1000 + // Window flags // Window size is changed @@ -259,6 +287,18 @@ typedef struct _win { struct _win *prev_trans; } win; +typedef enum _vsync_t { + VSYNC_NONE, + VSYNC_SW, + VSYNC_DRM, + VSYNC_OPENGL, +} vsync_t; + +#ifdef CONFIG_VSYNC_OPENGL +typedef int (*f_WaitVideoSync) (int, int, unsigned *); +typedef int (*f_GetVideoSync) (unsigned *); +#endif + typedef struct _options { // General char *display; @@ -273,6 +313,12 @@ typedef struct _options { /// Whether to work under synchronized mode for debugging. Bool synchronize; + // VSync + /// User-specified refresh rate. + int refresh_rate; + /// VSync method to use; + vsync_t vsync; + // Shadow Bool wintype_shadow[NUM_WINTYPES]; /// Red, green and blue tone of the shadow. @@ -363,6 +409,16 @@ set_ignore(Display *dpy, unsigned long sequence); static int should_ignore(Display *dpy, unsigned long sequence); +/** + * Subtract two unsigned long values. + * + * Truncate to 0 if the result is negative. + */ +static inline unsigned long +sub_unslong(unsigned long a, unsigned long b) { + return (a > b) ? a - b : 0; +} + /** * Set a Bool array of all wintypes to true. */ @@ -524,6 +580,41 @@ timeval_subtract(struct timeval *result, return x->tv_sec < y->tv_sec; } +/* + * Subtracting two struct timespec values. + * + * Taken from glibc manual. + * + * Subtract the `struct timespec' values X and Y, + * storing the result in RESULT. + * Return 1 if the difference is negative, otherwise 0. + */ +static inline int +timespec_subtract(struct timespec *result, + struct timespec *x, + struct timespec *y) { + /* Perform the carry for the later subtraction by updating y. */ + if (x->tv_nsec < y->tv_nsec) { + int nsec = (y->tv_nsec - x->tv_nsec) / NS_PER_SEC + 1; + y->tv_nsec -= NS_PER_SEC * nsec; + y->tv_sec += nsec; + } + + if (x->tv_nsec - y->tv_nsec > NS_PER_SEC) { + int nsec = (x->tv_nsec - y->tv_nsec) / NS_PER_SEC; + y->tv_nsec += NS_PER_SEC * nsec; + y->tv_sec -= nsec; + } + + /* Compute the time remaining to wait. + tv_nsec is certainly positive. */ + result->tv_sec = x->tv_sec - y->tv_sec; + result->tv_nsec = x->tv_nsec - y->tv_nsec; + + /* Return 1 if result is negative. */ + return x->tv_sec < y->tv_sec; +} + /** * Print time passed since program starts execution. * @@ -586,7 +677,7 @@ free_damage(Display *dpy, Damage *p) { } static unsigned long -get_time_in_milliseconds(void); +get_time_ms(void); static int fade_timeout(void); @@ -885,7 +976,7 @@ static void usage(void); static void -register_cm(int scr); +register_cm(Bool want_glxct); inline static void ev_focus_in(XFocusChangeEvent *ev); @@ -1007,3 +1098,31 @@ get_cfg(int argc, char *const *argv); static void get_atoms(void); + +static void +update_refresh_rate(Display *dpy); + +static Bool +vsync_sw_init(void); + +static struct timespec +vsync_sw_ntimeout(int timeout); + +static Bool +vsync_drm_init(void); + +#ifdef CONFIG_VSYNC_DRM +static int +vsync_drm_wait(void); +#endif + +static Bool +vsync_opengl_init(void); + +#ifdef CONFIG_VSYNC_OPENGL +static void +vsync_opengl_wait(void); +#endif + +static Bool +vsync_wait(Display *dpy, struct pollfd *fd, int timeout);