Bug fix #7: Correct a possible issue in VSync

- I realized I might have fundamentally misunderstood VSync. This commit
  tries to fix the possible problem, or at least move the tearing line
  close to the top of the screen.

- Software VSync is replaced by --sw-opti (software optimization), as
  I guess it isn't possible at all to do VSync without driver support.

- Add "vsync" and "sw-opti" as configuration file options.
This commit is contained in:
Richard Grenville 2012-10-26 11:12:28 +08:00
parent 049621bed7
commit 66e615775b
3 changed files with 135 additions and 113 deletions

View File

@ -34,7 +34,9 @@ mark-ovredir-focused = true;
detect-rounded-corners = true; detect-rounded-corners = true;
detect-client-opacity = false; detect-client-opacity = false;
refresh-rate = 0; refresh-rate = 0;
vsync = "none";
paint-on-overlay = true; paint-on-overlay = true;
sw-opti = false;
# Window type settings # Window type settings
wintypes: wintypes:

View File

@ -74,12 +74,11 @@ Bool reg_ignore_expire = False;
/// Window ID of the window we register as a symbol. /// Window ID of the window we register as a symbol.
Window reg_win = 0; Window reg_win = 0;
/// Currently used refresh rate. Used for Software VSync. /// Currently used refresh rate. Used for sw_opti.
short refresh_rate = 0; short refresh_rate = 0;
/// Interval between refresh in nanoseconds. Used for Software VSync. /// Interval between refresh in nanoseconds. Used for sw_opti.
unsigned long refresh_intv = 0; unsigned long refresh_intv = 0;
/// Nanosecond-level offset of the first painting. /// Nanosecond-level offset of the first painting. Used for sw_opti.
/// Used for Software VSync.
long paint_tm_offset = 0; long paint_tm_offset = 0;
#ifdef CONFIG_VSYNC_DRM #ifdef CONFIG_VSYNC_DRM
@ -179,6 +178,7 @@ static options_t opts = {
.paint_on_overlay = False, .paint_on_overlay = False,
.refresh_rate = 0, .refresh_rate = 0,
.sw_opti = False,
.vsync = VSYNC_NONE, .vsync = VSYNC_NONE,
.dbe = False, .dbe = False,
@ -1568,6 +1568,10 @@ win_paint_win(Display *dpy, win *w, Picture tgt_buffer) {
static void static void
paint_all(Display *dpy, XserverRegion region, win *t) { paint_all(Display *dpy, XserverRegion region, win *t) {
#ifdef DEBUG_REPAINT
static struct timespec last_paint = { 0 };
#endif
win *w; win *w;
XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None; XserverRegion reg_paint = None, reg_tmp = None, reg_tmp2 = None;
@ -1616,7 +1620,6 @@ paint_all(Display *dpy, XserverRegion region, win *t) {
#endif #endif
#ifdef DEBUG_REPAINT #ifdef DEBUG_REPAINT
print_timestamp();
printf("paint:"); printf("paint:");
#endif #endif
@ -1700,7 +1703,6 @@ paint_all(Display *dpy, XserverRegion region, win *t) {
#ifdef DEBUG_REPAINT #ifdef DEBUG_REPAINT
printf("\n"); printf("\n");
fflush(stdout);
#endif #endif
// Free up all temporary regions // Free up all temporary regions
@ -1708,6 +1710,9 @@ paint_all(Display *dpy, XserverRegion region, win *t) {
XFixesDestroyRegion(dpy, reg_tmp); XFixesDestroyRegion(dpy, reg_tmp);
XFixesDestroyRegion(dpy, reg_tmp2); XFixesDestroyRegion(dpy, reg_tmp2);
// Wait for VBlank
vsync_wait();
// DBE painting mode, only need to swap the buffer // DBE painting mode, only need to swap the buffer
if (opts.dbe) { if (opts.dbe) {
XdbeSwapInfo swap_info = { XdbeSwapInfo swap_info = {
@ -1725,6 +1730,20 @@ paint_all(Display *dpy, XserverRegion region, win *t) {
tgt_picture, 0, 0, 0, 0, tgt_picture, 0, 0, 0, 0,
0, 0, root_width, root_height); 0, 0, root_width, root_height);
} }
XFlush(dpy);
#ifdef DEBUG_REPAINT
// It prints the timestamp in the wrong line, but...
print_timestamp();
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;
fflush(stdout);
#endif
} }
static void static void
@ -3304,10 +3323,8 @@ usage(void) {
" Specify refresh rate of the screen. If not specified or 0, compton\n" " Specify refresh rate of the screen. If not specified or 0, compton\n"
" will try detecting this with X RandR extension.\n" " will try detecting this with X RandR extension.\n"
"--vsync vsync-method\n" "--vsync vsync-method\n"
" Set VSync method. There are 4 VSync methods currently available:\n" " Set VSync method. There are 2 VSync methods currently available:\n"
" none = No VSync\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" " drm = VSync with DRM_IOCTL_WAIT_VBLANK. May only work on some\n"
" drivers. Experimental.\n" " drivers. Experimental.\n"
" opengl = Try to VSync with SGI_swap_control OpenGL extension. Only\n" " opengl = Try to VSync with SGI_swap_control OpenGL extension. Only\n"
@ -3321,6 +3338,9 @@ usage(void) {
" (hopefully) eliminate tearing.\n" " (hopefully) eliminate tearing.\n"
"--paint-on-overlay\n" "--paint-on-overlay\n"
" Painting on X Composite overlay window.\n" " Painting on X Composite overlay window.\n"
"--sw-opti\n"
" Limit compton to repaint at most once every 1 / refresh_rate\n"
" second to boost performance. Experimental.\n"
"\n" "\n"
"Format of a condition:\n" "Format of a condition:\n"
"\n" "\n"
@ -3533,6 +3553,29 @@ open_config_file(char *cpath, char **ppath) {
return NULL; return NULL;
} }
/**
* Parse a VSync option argument.
*/
static inline void
parse_vsync(const char *optarg) {
const static char * const vsync_str[] = {
"none", // VSYNC_NONE
"drm", // VSYNC_DRM
"opengl", // VSYNC_OPENGL
};
vsync_t i;
for (i = 0; i < (sizeof(vsync_str) / sizeof(vsync_str[0])); ++i)
if (!strcasecmp(optarg, vsync_str[i])) {
opts.vsync = i;
break;
}
if ((sizeof(vsync_str) / sizeof(vsync_str[0])) == i) {
fputs("Invalid --vsync argument. Ignored.\n", stderr);
}
}
/** /**
* Parse a configuration file from default location. * Parse a configuration file from default location.
*/ */
@ -3543,6 +3586,7 @@ parse_config(char *cpath, struct options_tmp *pcfgtmp) {
config_t cfg; config_t cfg;
int ival = 0; int ival = 0;
double dval = 0.0; double dval = 0.0;
const char *sval = NULL;
f = open_config_file(cpath, &path); f = open_config_file(cpath, &path);
if (!f) { if (!f) {
@ -3637,10 +3681,15 @@ parse_config(char *cpath, struct options_tmp *pcfgtmp) {
&opts.detect_client_opacity); &opts.detect_client_opacity);
// --refresh-rate // --refresh-rate
lcfg_lookup_int(&cfg, "refresh-rate", &opts.refresh_rate); lcfg_lookup_int(&cfg, "refresh-rate", &opts.refresh_rate);
// --vsync
if (config_lookup_string(&cfg, "vsync", &sval))
parse_vsync(sval);
// --alpha-step // --alpha-step
config_lookup_float(&cfg, "alpha-step", &opts.alpha_step); config_lookup_float(&cfg, "alpha-step", &opts.alpha_step);
// --paint-on-overlay // --paint-on-overlay
lcfg_lookup_bool(&cfg, "paint-on-overlay", &opts.paint_on_overlay); lcfg_lookup_bool(&cfg, "paint-on-overlay", &opts.paint_on_overlay);
// --sw-opti
lcfg_lookup_bool(&cfg, "sw-opti", &opts.sw_opti);
// --shadow-exclude // --shadow-exclude
{ {
config_setting_t *setting = config_setting_t *setting =
@ -3709,15 +3758,10 @@ get_cfg(int argc, char *const *argv) {
{ "alpha-step", required_argument, NULL, 271 }, { "alpha-step", required_argument, NULL, 271 },
{ "dbe", no_argument, NULL, 272 }, { "dbe", no_argument, NULL, 272 },
{ "paint-on-overlay", no_argument, NULL, 273 }, { "paint-on-overlay", no_argument, NULL, 273 },
{ "sw-opti", no_argument, NULL, 274 },
// Must terminate with a NULL entry // Must terminate with a NULL entry
{ NULL, 0, NULL, 0 }, { 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 = { struct options_tmp cfgtmp = {
.no_dock_shadow = False, .no_dock_shadow = False,
@ -3879,17 +3923,7 @@ get_cfg(int argc, char *const *argv) {
break; break;
case 270: case 270:
// --vsync // --vsync
{ parse_vsync(optarg);
vsync_t i;
for (i = 0; i < (sizeof(vsync_str) / sizeof(vsync_str[0])); ++i)
if (!strcasecmp(optarg, vsync_str[i])) {
opts.vsync = i;
break;
}
if ((sizeof(vsync_str) / sizeof(vsync_str[0])) == i) {
fputs("Invalid --vsync argument. Ignored.\n", stderr);
}
}
break; break;
case 271: case 271:
// --alpha-step // --alpha-step
@ -3903,6 +3937,10 @@ get_cfg(int argc, char *const *argv) {
// --paint-on-overlay // --paint-on-overlay
opts.paint_on_overlay = True; opts.paint_on_overlay = True;
break; break;
case 274:
// --sw-opti
opts.sw_opti = True;
break;
default: default:
usage(); usage();
break; break;
@ -4018,12 +4056,12 @@ update_refresh_rate(Display *dpy) {
} }
/** /**
* Initialize software VSync. * Initialize refresh-rated based software optimization.
* *
* @return True for success, False otherwise * @return True for success, False otherwise
*/ */
static Bool static Bool
vsync_sw_init(void) { sw_opti_init(void) {
// Prepare refresh rate // Prepare refresh rate
// Check if user provides one // Check if user provides one
refresh_rate = opts.refresh_rate; refresh_rate = opts.refresh_rate;
@ -4047,21 +4085,6 @@ vsync_sw_init(void) {
return True; 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 * Get the smaller number that is bigger than <code>dividend</code> and is
* N times of <code>divisor</code>. * N times of <code>divisor</code>.
@ -4079,19 +4102,35 @@ lceil_ntimes(long dividend, long divisor) {
} }
/** /**
* Calculate time for which the program should wait for events if vsync_sw is * Wait for events until next paint.
* enabled.
* *
* @param timeout old timeout value, never negative! * Optionally use refresh-rate based optimization to reduce painting.
* @return time to wait, in struct timespec *
* @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 struct timespec static int
vsync_sw_ntimeout(int timeout) { evpoll(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;
}
// Just do a poll() if we are not using optimization
if (!opts.sw_opti)
return poll(fd, 1, timeout);
// Convert the old timeout to struct timespec // Convert the old timeout to struct timespec
struct timespec next_paint_tmout = { struct timespec next_paint_tmout = {
.tv_sec = timeout / MS_PER_SEC, .tv_sec = timeout / MS_PER_SEC,
.tv_nsec = timeout % MS_PER_SEC * (NS_PER_SEC / 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 // Get the nanosecond offset of the time when the we reach the timeout
// I don't think a 32-bit long could overflow here. // 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; long target_relative_offset = (next_paint_tmout.tv_nsec + get_time_timespec().tv_nsec - paint_tm_offset) % NS_PER_SEC;
@ -4100,19 +4139,19 @@ vsync_sw_ntimeout(int timeout) {
assert(target_relative_offset >= 0); assert(target_relative_offset >= 0);
// If the target time is sufficiently close to a VSync time, don't add // If the target time is sufficiently close to a refresh time, don't add
// an offset, to avoid certain blocking conditions. // an offset, to avoid certain blocking conditions.
if ((target_relative_offset % NS_PER_SEC) < VSYNC_SW_TOLERANCE) if ((target_relative_offset % NS_PER_SEC) < SW_OPTI_TOLERANCE)
return next_paint_tmout; return poll(fd, 1, timeout);
// Add an offset so we wait until the next VSync after timeout // Add an offset so we wait until the next refresh after timeout
next_paint_tmout.tv_nsec += lceil_ntimes(target_relative_offset, refresh_intv) - target_relative_offset; next_paint_tmout.tv_nsec += lceil_ntimes(target_relative_offset, refresh_intv) - target_relative_offset;
if (next_paint_tmout.tv_nsec > NS_PER_SEC) { if (next_paint_tmout.tv_nsec > NS_PER_SEC) {
next_paint_tmout.tv_nsec -= NS_PER_SEC; next_paint_tmout.tv_nsec -= NS_PER_SEC;
++next_paint_tmout.tv_sec; ++next_paint_tmout.tv_sec;
} }
return next_paint_tmout; return ppoll(fd, 1, &next_paint_tmout, NULL);
} }
/** /**
@ -4211,57 +4250,31 @@ vsync_opengl_wait(void) {
#endif #endif
/** /**
* Wait for next vsync and timeout unless new events appear. * Wait for next VSync.
*
* @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 static void
vsync_wait(Display *dpy, struct pollfd *fd, int timeout) { vsync_wait(void) {
// 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) if (VSYNC_NONE == opts.vsync)
return poll(fd, 1, timeout); return;
// 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 #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) { if (VSYNC_DRM == opts.vsync) {
vsync_drm_wait(); vsync_drm_wait();
return 0; return;
} }
#endif #endif
#ifdef CONFIG_VSYNC_OPENGL #ifdef CONFIG_VSYNC_OPENGL
// vsync_opengl: Same one-frame-latency issue, well, not sure how to deal it
// here.
if (VSYNC_OPENGL == opts.vsync) { if (VSYNC_OPENGL == opts.vsync) {
vsync_opengl_wait(); vsync_opengl_wait();
return 0; return;
} }
#endif #endif
// This place should not reached! // This place should not reached!
assert(0); assert(0);
return 0; return;
} }
/** /**
@ -4391,7 +4404,7 @@ main(int argc, char **argv) {
} }
// Query X RandR // Query X RandR
if (VSYNC_SW == opts.vsync && !opts.refresh_rate) { if (opts.sw_opti && !opts.refresh_rate) {
if (XRRQueryExtension(dpy, &randr_event, &randr_error)) if (XRRQueryExtension(dpy, &randr_event, &randr_error))
randr_exists = True; randr_exists = True;
else else
@ -4429,9 +4442,12 @@ main(int argc, char **argv) {
register_cm((VSYNC_OPENGL == opts.vsync)); register_cm((VSYNC_OPENGL == opts.vsync));
// Initialize software/DRM/OpenGL VSync // Initialize software optimization
if ((VSYNC_SW == opts.vsync && !vsync_sw_init()) if (opts.sw_opti)
|| (VSYNC_DRM == opts.vsync && !vsync_drm_init()) opts.sw_opti = sw_opti_init();
// Initialize DRM/OpenGL VSync
if ((VSYNC_DRM == opts.vsync && !vsync_drm_init())
|| (VSYNC_OPENGL == opts.vsync && !vsync_opengl_init())) || (VSYNC_OPENGL == opts.vsync && !vsync_opengl_init()))
opts.vsync = VSYNC_NONE; opts.vsync = VSYNC_NONE;
@ -4513,11 +4529,7 @@ main(int argc, char **argv) {
ufd.fd = ConnectionNumber(dpy); ufd.fd = ConnectionNumber(dpy);
ufd.events = POLLIN; ufd.events = POLLIN;
#ifdef DEBUG_REPAINT if (opts.sw_opti)
struct timespec last_paint = get_time_timespec();
#endif
if (VSYNC_SW == opts.vsync)
paint_tm_offset = get_time_timespec().tv_nsec; paint_tm_offset = get_time_timespec().tv_nsec;
reg_ignore_expire = True; reg_ignore_expire = True;
@ -4534,7 +4546,7 @@ main(int argc, char **argv) {
Bool ev_received = False; Bool ev_received = False;
while (QLength(dpy) while (QLength(dpy)
|| (vsync_wait(dpy, &ufd, || (evpoll(&ufd,
(ev_received ? 0: (idling ? -1: fade_timeout()))) > 0)) { (ev_received ? 0: (idling ? -1: fade_timeout()))) > 0)) {
XNextEvent(dpy, &ev); XNextEvent(dpy, &ev);
ev_handle((XEvent *) &ev); ev_handle((XEvent *) &ev);
@ -4547,14 +4559,6 @@ main(int argc, char **argv) {
t = paint_preprocess(dpy, list); t = paint_preprocess(dpy, list);
if (all_damage && !is_region_empty(dpy, all_damage)) { if (all_damage && !is_region_empty(dpy, 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; static int paint;
paint_all(dpy, all_damage, t); paint_all(dpy, all_damage, t);
reg_ignore_expire = False; reg_ignore_expire = False;

View File

@ -111,7 +111,7 @@ extern struct timeval time_start;
#define WINDOW_ARGB 2 #define WINDOW_ARGB 2
#define FADE_DELTA_TOLERANCE 0.2 #define FADE_DELTA_TOLERANCE 0.2
#define VSYNC_SW_TOLERANCE 1000 #define SW_OPTI_TOLERANCE 1000
#define NS_PER_SEC 1000000000L #define NS_PER_SEC 1000000000L
#define US_PER_SEC 1000000L #define US_PER_SEC 1000000L
@ -286,7 +286,6 @@ typedef struct _win {
typedef enum _vsync_t { typedef enum _vsync_t {
VSYNC_NONE, VSYNC_NONE,
VSYNC_SW,
VSYNC_DRM, VSYNC_DRM,
VSYNC_OPENGL, VSYNC_OPENGL,
} vsync_t; } vsync_t;
@ -313,9 +312,11 @@ typedef struct _options {
/// Whether to work under synchronized mode for debugging. /// Whether to work under synchronized mode for debugging.
Bool synchronize; Bool synchronize;
// VSync // VSync and software optimization
/// User-specified refresh rate. /// User-specified refresh rate.
int refresh_rate; int refresh_rate;
/// Whether to enable refresh-rate-based software optimization.
Bool sw_opti;
/// VSync method to use; /// VSync method to use;
vsync_t vsync; vsync_t vsync;
/// Whether to enable double buffer. /// Whether to enable double buffer.
@ -619,6 +620,21 @@ timespec_subtract(struct timespec *result,
return x->tv_sec < y->tv_sec; return x->tv_sec < y->tv_sec;
} }
/**
* 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;
}
/** /**
* Print time passed since program starts execution. * Print time passed since program starts execution.
* *
@ -1136,10 +1152,10 @@ static void
update_refresh_rate(Display *dpy); update_refresh_rate(Display *dpy);
static Bool static Bool
vsync_sw_init(void); sw_opti_init(void);
static struct timespec static int
vsync_sw_ntimeout(int timeout); evpoll(struct pollfd *fd, int timeout);
static Bool static Bool
vsync_drm_init(void); vsync_drm_init(void);
@ -1157,8 +1173,8 @@ static void
vsync_opengl_wait(void); vsync_opengl_wait(void);
#endif #endif
static Bool static void
vsync_wait(Display *dpy, struct pollfd *fd, int timeout); vsync_wait(void);
static void static void
init_alpha_picts(Display *dpy); init_alpha_picts(Display *dpy);