aboutsummaryrefslogtreecommitdiff
path: root/x.c
diff options
context:
space:
mode:
authorAvi Halachmi (:avih) <avihpit@yahoo.com>2019-02-26 22:37:49 +0200
committerHiltjo Posthuma <hiltjo@codemadness.org>2020-05-09 13:53:50 +0200
commit1d590910652519268152eae6b97cf30ace4e90c0 (patch)
treeaec0e38e3864e42409023f75be2aa148b0fe1a80 /x.c
parentd6ea0a1a61853dd892029a7126e7fdb70c371878 (diff)
downloadst-patched-1d590910652519268152eae6b97cf30ace4e90c0.tar.bz2
st-patched-1d590910652519268152eae6b97cf30ace4e90c0.tar.xz
st-patched-1d590910652519268152eae6b97cf30ace4e90c0.zip
auto-sync: draw on idle to avoid flicker/tearing
st could easily tear/flicker with animation or other unattended output. This commit eliminates most of the tear/flicker. Before this commit, the display timing had two "modes": - Interactively, st was waiting fixed `1000/xfps` ms after forwarding the kb/mouse event to the application and before drawing. - Unattended, and specifically with animations, the draw frequency was throttled to `actionfps`. Animation at a higher rate would throttle and likely tear, and at lower rates it was tearing big frames (specifically, when one `read` didn't get a full "frame"). The interactive behavior was decent, but it was impossible to get good unattended-draw behavior even with carefully chosen configuration. This commit changes the behavior such that it draws on idle instead of using fixed latency/frequency. This means that it tries to draw only when it's very likely that the application has completed its output (or after some duration without idle), so it mostly succeeds to avoid tear, flicker, and partial drawing. The config values minlatency/maxlatency replace xfps/actionfps and define the range which the algorithm is allowed to wait from the initial draw-trigger until the actual draw. The range enables the flexibility to choose when to draw - when least likely to flicker. It also unifies the interactive and unattended behavior and config values, which makes the code simpler as well - without sacrificing latency during interactive use, because typically interactively idle arrives very quickly, so the wait is typically minlatency. While it only slighly improves interactive behavior, for animations and other unattended-drawing it improves greatly, as it effectively adapts to any [animation] output rate without tearing, throttling, redundant drawing, or unnecessary delays (sounds impossible, but it works).
Diffstat (limited to 'x.c')
-rw-r--r--x.c120
1 files changed, 57 insertions, 63 deletions
diff --git a/x.c b/x.c
index e5f1737..cbbd11f 100644
--- a/x.c
+++ b/x.c
@@ -1867,10 +1867,9 @@ run(void)
1867 XEvent ev; 1867 XEvent ev;
1868 int w = win.w, h = win.h; 1868 int w = win.w, h = win.h;
1869 fd_set rfd; 1869 fd_set rfd;
1870 int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0; 1870 int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
1871 int ttyfd; 1871 struct timespec seltv, *tv, now, lastblink, trigger;
1872 struct timespec drawtimeout, *tv = NULL, now, last, lastblink; 1872 double timeout;
1873 long deltatime;
1874 1873
1875 /* Waiting for window mapping */ 1874 /* Waiting for window mapping */
1876 do { 1875 do {
@@ -1891,82 +1890,77 @@ run(void)
1891 ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd); 1890 ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
1892 cresize(w, h); 1891 cresize(w, h);
1893 1892
1894 clock_gettime(CLOCK_MONOTONIC, &last); 1893 for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
1895 lastblink = last;
1896
1897 for (xev = actionfps;;) {
1898 FD_ZERO(&rfd); 1894 FD_ZERO(&rfd);
1899 FD_SET(ttyfd, &rfd); 1895 FD_SET(ttyfd, &rfd);
1900 FD_SET(xfd, &rfd); 1896 FD_SET(xfd, &rfd);
1901 1897
1898 if (XPending(xw.dpy))
1899 timeout = 0; /* existing events might not set xfd */
1900
1901 seltv.tv_sec = timeout / 1E3;
1902 seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
1903 tv = timeout >= 0 ? &seltv : NULL;
1904
1902 if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) { 1905 if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
1903 if (errno == EINTR) 1906 if (errno == EINTR)
1904 continue; 1907 continue;
1905 die("select failed: %s\n", strerror(errno)); 1908 die("select failed: %s\n", strerror(errno));
1906 } 1909 }
1907 if (FD_ISSET(ttyfd, &rfd)) { 1910 clock_gettime(CLOCK_MONOTONIC, &now);
1908 ttyread();
1909 if (blinktimeout) {
1910 blinkset = tattrset(ATTR_BLINK);
1911 if (!blinkset)
1912 MODBIT(win.mode, 0, MODE_BLINK);
1913 }
1914 }
1915 1911
1916 if (FD_ISSET(xfd, &rfd)) 1912 if (FD_ISSET(ttyfd, &rfd))
1917 xev = actionfps; 1913 ttyread();
1918 1914
1919 clock_gettime(CLOCK_MONOTONIC, &now); 1915 xev = 0;
1920 drawtimeout.tv_sec = 0; 1916 while (XPending(xw.dpy)) {
1921 drawtimeout.tv_nsec = (1000 * 1E6)/ xfps; 1917 xev = 1;
1922 tv = &drawtimeout; 1918 XNextEvent(xw.dpy, &ev);
1923 1919 if (XFilterEvent(&ev, None))
1924 dodraw = 0; 1920 continue;
1925 if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) { 1921 if (handler[ev.type])
1926 tsetdirtattr(ATTR_BLINK); 1922 (handler[ev.type])(&ev);
1927 win.mode ^= MODE_BLINK;
1928 lastblink = now;
1929 dodraw = 1;
1930 }
1931 deltatime = TIMEDIFF(now, last);
1932 if (deltatime > 1000 / (xev ? xfps : actionfps)) {
1933 dodraw = 1;
1934 last = now;
1935 } 1923 }
1936 1924
1937 if (dodraw) { 1925 /*
1938 while (XPending(xw.dpy)) { 1926 * To reduce flicker and tearing, when new content or event
1939 XNextEvent(xw.dpy, &ev); 1927 * triggers drawing, we first wait a bit to ensure we got
1940 if (XFilterEvent(&ev, None)) 1928 * everything, and if nothing new arrives - we draw.
1941 continue; 1929 * We start with trying to wait minlatency ms. If more content
1942 if (handler[ev.type]) 1930 * arrives sooner, we retry with shorter and shorter preiods,
1943 (handler[ev.type])(&ev); 1931 * and eventually draw even without idle after maxlatency ms.
1932 * Typically this results in low latency while interacting,
1933 * maximum latency intervals during `cat huge.txt`, and perfect
1934 * sync with periodic updates from animations/key-repeats/etc.
1935 */
1936 if (FD_ISSET(ttyfd, &rfd) || xev) {
1937 if (!drawing) {
1938 trigger = now;
1939 drawing = 1;
1944 } 1940 }
1941 timeout = (maxlatency - TIMEDIFF(now, trigger)) \
1942 / maxlatency * minlatency;
1943 if (timeout > 0)
1944 continue; /* we have time, try to find idle */
1945 }
1945 1946
1946 draw(); 1947 /* idle detected or maxlatency exhausted -> draw */
1947 XFlush(xw.dpy); 1948 timeout = -1;
1948 1949 if (blinktimeout && tattrset(ATTR_BLINK)) {
1949 if (xev && !FD_ISSET(xfd, &rfd)) 1950 timeout = blinktimeout - TIMEDIFF(now, lastblink);
1950 xev--; 1951 if (timeout <= 0) {
1951 if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) { 1952 if (-timeout > blinktimeout) /* start visible */
1952 if (blinkset) { 1953 win.mode |= MODE_BLINK;
1953 if (TIMEDIFF(now, lastblink) \ 1954 win.mode ^= MODE_BLINK;
1954 > blinktimeout) { 1955 tsetdirtattr(ATTR_BLINK);
1955 drawtimeout.tv_nsec = 1000; 1956 lastblink = now;
1956 } else { 1957 timeout = blinktimeout;
1957 drawtimeout.tv_nsec = (1E6 * \
1958 (blinktimeout - \
1959 TIMEDIFF(now,
1960 lastblink)));
1961 }
1962 drawtimeout.tv_sec = \
1963 drawtimeout.tv_nsec / 1E9;
1964 drawtimeout.tv_nsec %= (long)1E9;
1965 } else {
1966 tv = NULL;
1967 }
1968 } 1958 }
1969 } 1959 }
1960
1961 draw();
1962 XFlush(xw.dpy);
1963 drawing = 0;
1970 } 1964 }
1971} 1965}
1972 1966