sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

st-autosync-0.8.3.diff (7328B)


      1 From 1892290c3b0ef064083c8af4e4bec443a36ca5c8 Mon Sep 17 00:00:00 2001
      2 From: "Avi Halachmi (:avih)" <avihpit@yahoo.com>
      3 Date: Tue, 26 Feb 2019 22:37:49 +0200
      4 Subject: [PATCH] auto-sync: draw on idle to avoid flicker/tearing
      5 
      6 st could easily tear/flicker with animation or other unattended
      7 output. This commit eliminates most of the tear/flicker.
      8 
      9 Before this commit, the display timing had two "modes":
     10 
     11 - Interactively, st was waiting fixed `1000/xfps` ms after forwarding
     12   the kb/mouse event to the application and before drawing.
     13 
     14 - Unattended, and specifically with animations, the draw frequency was
     15   throttled to `actionfps`. Animation at a higher rate would throttle
     16   and likely tear, and at lower rates it was tearing big frames
     17   (specifically, when one `read` didn't get a full "frame").
     18 
     19 The interactive behavior was decent, but it was impossible to get good
     20 unattended-draw behavior even with carefully chosen configuration.
     21 
     22 This commit changes the behavior such that it draws on idle instead of
     23 using fixed latency/frequency. This means that it tries to draw only
     24 when it's very likely that the application has completed its output
     25 (or after some duration without idle), so it mostly succeeds to avoid
     26 tear, flicker, and partial drawing.
     27 
     28 The config values minlatency/maxlatency replace xfps/actionfps and
     29 define the range which the algorithm is allowed to wait from the
     30 initial draw-trigger until the actual draw. The range enables the
     31 flexibility to choose when to draw - when least likely to flicker.
     32 
     33 It also unifies the interactive and unattended behavior and config
     34 values, which makes the code simpler as well - without sacrificing
     35 latency during interactive use, because typically interactively idle
     36 arrives very quickly, so the wait is typically minlatency.
     37 
     38 While it only slighly improves interactive behavior, for animations
     39 and other unattended-drawing it improves greatly, as it effectively
     40 adapts to any [animation] output rate without tearing, throttling,
     41 redundant drawing, or unnecessary delays (sounds impossible, but it
     42 works).
     43 ---
     44  config.def.h |  11 +++--
     45  x.c          | 120 ++++++++++++++++++++++++---------------------------
     46  2 files changed, 65 insertions(+), 66 deletions(-)
     47 
     48 diff --git a/config.def.h b/config.def.h
     49 index 0895a1f..fdbacfd 100644
     50 --- a/config.def.h
     51 +++ b/config.def.h
     52 @@ -43,9 +43,14 @@ static unsigned int tripleclicktimeout = 600;
     53  /* alt screens */
     54  int allowaltscreen = 1;
     55  
     56 -/* frames per second st should at maximum draw to the screen */
     57 -static unsigned int xfps = 120;
     58 -static unsigned int actionfps = 30;
     59 +/*
     60 + * draw latency range in ms - from new content/keypress/etc until drawing.
     61 + * within this range, st draws when content stops arriving (idle). mostly it's
     62 + * near minlatency, but it waits longer for slow updates to avoid partial draw.
     63 + * low minlatency will tear/flicker more, as it can "detect" idle too early.
     64 + */
     65 +static double minlatency = 8;
     66 +static double maxlatency = 33;
     67  
     68  /*
     69   * blinking timeout (set to 0 to disable blinking) for the terminal blinking
     70 diff --git a/x.c b/x.c
     71 index e5f1737..cbbd11f 100644
     72 --- a/x.c
     73 +++ b/x.c
     74 @@ -1867,10 +1867,9 @@ run(void)
     75  	XEvent ev;
     76  	int w = win.w, h = win.h;
     77  	fd_set rfd;
     78 -	int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0;
     79 -	int ttyfd;
     80 -	struct timespec drawtimeout, *tv = NULL, now, last, lastblink;
     81 -	long deltatime;
     82 +	int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
     83 +	struct timespec seltv, *tv, now, lastblink, trigger;
     84 +	double timeout;
     85  
     86  	/* Waiting for window mapping */
     87  	do {
     88 @@ -1891,82 +1890,77 @@ run(void)
     89  	ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
     90  	cresize(w, h);
     91  
     92 -	clock_gettime(CLOCK_MONOTONIC, &last);
     93 -	lastblink = last;
     94 -
     95 -	for (xev = actionfps;;) {
     96 +	for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
     97  		FD_ZERO(&rfd);
     98  		FD_SET(ttyfd, &rfd);
     99  		FD_SET(xfd, &rfd);
    100  
    101 +		if (XPending(xw.dpy))
    102 +			timeout = 0;  /* existing events might not set xfd */
    103 +
    104 +		seltv.tv_sec = timeout / 1E3;
    105 +		seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
    106 +		tv = timeout >= 0 ? &seltv : NULL;
    107 +
    108  		if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
    109  			if (errno == EINTR)
    110  				continue;
    111  			die("select failed: %s\n", strerror(errno));
    112  		}
    113 -		if (FD_ISSET(ttyfd, &rfd)) {
    114 -			ttyread();
    115 -			if (blinktimeout) {
    116 -				blinkset = tattrset(ATTR_BLINK);
    117 -				if (!blinkset)
    118 -					MODBIT(win.mode, 0, MODE_BLINK);
    119 -			}
    120 -		}
    121 +		clock_gettime(CLOCK_MONOTONIC, &now);
    122  
    123 -		if (FD_ISSET(xfd, &rfd))
    124 -			xev = actionfps;
    125 +		if (FD_ISSET(ttyfd, &rfd))
    126 +			ttyread();
    127  
    128 -		clock_gettime(CLOCK_MONOTONIC, &now);
    129 -		drawtimeout.tv_sec = 0;
    130 -		drawtimeout.tv_nsec =  (1000 * 1E6)/ xfps;
    131 -		tv = &drawtimeout;
    132 -
    133 -		dodraw = 0;
    134 -		if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) {
    135 -			tsetdirtattr(ATTR_BLINK);
    136 -			win.mode ^= MODE_BLINK;
    137 -			lastblink = now;
    138 -			dodraw = 1;
    139 -		}
    140 -		deltatime = TIMEDIFF(now, last);
    141 -		if (deltatime > 1000 / (xev ? xfps : actionfps)) {
    142 -			dodraw = 1;
    143 -			last = now;
    144 +		xev = 0;
    145 +		while (XPending(xw.dpy)) {
    146 +			xev = 1;
    147 +			XNextEvent(xw.dpy, &ev);
    148 +			if (XFilterEvent(&ev, None))
    149 +				continue;
    150 +			if (handler[ev.type])
    151 +				(handler[ev.type])(&ev);
    152  		}
    153  
    154 -		if (dodraw) {
    155 -			while (XPending(xw.dpy)) {
    156 -				XNextEvent(xw.dpy, &ev);
    157 -				if (XFilterEvent(&ev, None))
    158 -					continue;
    159 -				if (handler[ev.type])
    160 -					(handler[ev.type])(&ev);
    161 +		/*
    162 +		 * To reduce flicker and tearing, when new content or event
    163 +		 * triggers drawing, we first wait a bit to ensure we got
    164 +		 * everything, and if nothing new arrives - we draw.
    165 +		 * We start with trying to wait minlatency ms. If more content
    166 +		 * arrives sooner, we retry with shorter and shorter preiods,
    167 +		 * and eventually draw even without idle after maxlatency ms.
    168 +		 * Typically this results in low latency while interacting,
    169 +		 * maximum latency intervals during `cat huge.txt`, and perfect
    170 +		 * sync with periodic updates from animations/key-repeats/etc.
    171 +		 */
    172 +		if (FD_ISSET(ttyfd, &rfd) || xev) {
    173 +			if (!drawing) {
    174 +				trigger = now;
    175 +				drawing = 1;
    176  			}
    177 +			timeout = (maxlatency - TIMEDIFF(now, trigger)) \
    178 +			          / maxlatency * minlatency;
    179 +			if (timeout > 0)
    180 +				continue;  /* we have time, try to find idle */
    181 +		}
    182  
    183 -			draw();
    184 -			XFlush(xw.dpy);
    185 -
    186 -			if (xev && !FD_ISSET(xfd, &rfd))
    187 -				xev--;
    188 -			if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) {
    189 -				if (blinkset) {
    190 -					if (TIMEDIFF(now, lastblink) \
    191 -							> blinktimeout) {
    192 -						drawtimeout.tv_nsec = 1000;
    193 -					} else {
    194 -						drawtimeout.tv_nsec = (1E6 * \
    195 -							(blinktimeout - \
    196 -							TIMEDIFF(now,
    197 -								lastblink)));
    198 -					}
    199 -					drawtimeout.tv_sec = \
    200 -					    drawtimeout.tv_nsec / 1E9;
    201 -					drawtimeout.tv_nsec %= (long)1E9;
    202 -				} else {
    203 -					tv = NULL;
    204 -				}
    205 +		/* idle detected or maxlatency exhausted -> draw */
    206 +		timeout = -1;
    207 +		if (blinktimeout && tattrset(ATTR_BLINK)) {
    208 +			timeout = blinktimeout - TIMEDIFF(now, lastblink);
    209 +			if (timeout <= 0) {
    210 +				if (-timeout > blinktimeout) /* start visible */
    211 +					win.mode |= MODE_BLINK;
    212 +				win.mode ^= MODE_BLINK;
    213 +				tsetdirtattr(ATTR_BLINK);
    214 +				lastblink = now;
    215 +				timeout = blinktimeout;
    216  			}
    217  		}
    218 +
    219 +		draw();
    220 +		XFlush(xw.dpy);
    221 +		drawing = 0;
    222  	}
    223  }
    224  
    225 
    226 base-commit: 43a395ae91f7d67ce694e65edeaa7bbc720dd027
    227 -- 
    228 2.17.1
    229