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