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.
This commit is contained in:
parent
1306b1591f
commit
b59e592588
|
@ -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)
|
||||
|
|
13
Makefile
13
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))
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
// 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);
|
||||
#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);
|
||||
|
@ -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 = <target>:<type>[<flags>]:<pattern>\n"
|
||||
|
@ -3041,26 +3121,71 @@ usage(void) {
|
|||
" flag is \"i\" (ignore case).\n"
|
||||
"\n"
|
||||
" <pattern> 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 <code>dividend</code> and is
|
||||
* N times of <code>divisor</code>.
|
||||
*/
|
||||
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++;
|
||||
|
|
123
src/compton.h
123
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 <getopt.h>
|
||||
#include <stdbool.h>
|
||||
#include <locale.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <fnmatch.h>
|
||||
|
||||
|
@ -69,6 +75,21 @@
|
|||
#include <X11/extensions/Xdamage.h>
|
||||
#include <X11/extensions/Xrender.h>
|
||||
#include <X11/extensions/shape.h>
|
||||
#include <X11/extensions/Xrandr.h>
|
||||
|
||||
#ifdef CONFIG_VSYNC_DRM
|
||||
#include <fcntl.h>
|
||||
// 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 <libdrm/drm.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_VSYNC_OPENGL
|
||||
#include <GL/glx.h>
|
||||
#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);
|
||||
|
|
Loading…
Reference in New Issue