1
0
Fork 0
mirror of https://github.com/yshui/picom.git synced 2024-11-18 13:55:36 -05:00

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:
Richard Grenville 2012-10-08 10:20:01 +08:00
parent 1306b1591f
commit b59e592588
6 changed files with 654 additions and 50 deletions

View file

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

View file

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

View file

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

View file

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

545
src/compton.c Normal file → Executable file
View file

@ -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 = <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++;

View file

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