aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config.def.h11
-rw-r--r--x.c120
2 files changed, 65 insertions, 66 deletions
diff --git a/config.def.h b/config.def.h
index 0895a1f..fdbacfd 100644
--- a/config.def.h
+++ b/config.def.h
@@ -43,9 +43,14 @@ static unsigned int tripleclicktimeout = 600;
/* alt screens */
int allowaltscreen = 1;
-/* frames per second st should at maximum draw to the screen */
-static unsigned int xfps = 120;
-static unsigned int actionfps = 30;
+/*
+ * draw latency range in ms - from new content/keypress/etc until drawing.
+ * within this range, st draws when content stops arriving (idle). mostly it's
+ * near minlatency, but it waits longer for slow updates to avoid partial draw.
+ * low minlatency will tear/flicker more, as it can "detect" idle too early.
+ */
+static double minlatency = 8;
+static double maxlatency = 33;
/*
* blinking timeout (set to 0 to disable blinking) for the terminal blinking
diff --git a/x.c b/x.c
index e5f1737..cbbd11f 100644
--- a/x.c
+++ b/x.c
@@ -1867,10 +1867,9 @@ run(void)
XEvent ev;
int w = win.w, h = win.h;
fd_set rfd;
- int xfd = XConnectionNumber(xw.dpy), xev, blinkset = 0, dodraw = 0;
- int ttyfd;
- struct timespec drawtimeout, *tv = NULL, now, last, lastblink;
- long deltatime;
+ int xfd = XConnectionNumber(xw.dpy), ttyfd, xev, drawing;
+ struct timespec seltv, *tv, now, lastblink, trigger;
+ double timeout;
/* Waiting for window mapping */
do {
@@ -1891,82 +1890,77 @@ run(void)
ttyfd = ttynew(opt_line, shell, opt_io, opt_cmd);
cresize(w, h);
- clock_gettime(CLOCK_MONOTONIC, &last);
- lastblink = last;
-
- for (xev = actionfps;;) {
+ for (timeout = -1, drawing = 0, lastblink = (struct timespec){0};;) {
FD_ZERO(&rfd);
FD_SET(ttyfd, &rfd);
FD_SET(xfd, &rfd);
+ if (XPending(xw.dpy))
+ timeout = 0; /* existing events might not set xfd */
+
+ seltv.tv_sec = timeout / 1E3;
+ seltv.tv_nsec = 1E6 * (timeout - 1E3 * seltv.tv_sec);
+ tv = timeout >= 0 ? &seltv : NULL;
+
if (pselect(MAX(xfd, ttyfd)+1, &rfd, NULL, NULL, tv, NULL) < 0) {
if (errno == EINTR)
continue;
die("select failed: %s\n", strerror(errno));
}
- if (FD_ISSET(ttyfd, &rfd)) {
- ttyread();
- if (blinktimeout) {
- blinkset = tattrset(ATTR_BLINK);
- if (!blinkset)
- MODBIT(win.mode, 0, MODE_BLINK);
- }
- }
+ clock_gettime(CLOCK_MONOTONIC, &now);
- if (FD_ISSET(xfd, &rfd))
- xev = actionfps;
+ if (FD_ISSET(ttyfd, &rfd))
+ ttyread();
- clock_gettime(CLOCK_MONOTONIC, &now);
- drawtimeout.tv_sec = 0;
- drawtimeout.tv_nsec = (1000 * 1E6)/ xfps;
- tv = &drawtimeout;
-
- dodraw = 0;
- if (blinktimeout && TIMEDIFF(now, lastblink) > blinktimeout) {
- tsetdirtattr(ATTR_BLINK);
- win.mode ^= MODE_BLINK;
- lastblink = now;
- dodraw = 1;
- }
- deltatime = TIMEDIFF(now, last);
- if (deltatime > 1000 / (xev ? xfps : actionfps)) {
- dodraw = 1;
- last = now;
+ xev = 0;
+ while (XPending(xw.dpy)) {
+ xev = 1;
+ XNextEvent(xw.dpy, &ev);
+ if (XFilterEvent(&ev, None))
+ continue;
+ if (handler[ev.type])
+ (handler[ev.type])(&ev);
}
- if (dodraw) {
- while (XPending(xw.dpy)) {
- XNextEvent(xw.dpy, &ev);
- if (XFilterEvent(&ev, None))
- continue;
- if (handler[ev.type])
- (handler[ev.type])(&ev);
+ /*
+ * To reduce flicker and tearing, when new content or event
+ * triggers drawing, we first wait a bit to ensure we got
+ * everything, and if nothing new arrives - we draw.
+ * We start with trying to wait minlatency ms. If more content
+ * arrives sooner, we retry with shorter and shorter preiods,
+ * and eventually draw even without idle after maxlatency ms.
+ * Typically this results in low latency while interacting,
+ * maximum latency intervals during `cat huge.txt`, and perfect
+ * sync with periodic updates from animations/key-repeats/etc.
+ */
+ if (FD_ISSET(ttyfd, &rfd) || xev) {
+ if (!drawing) {
+ trigger = now;
+ drawing = 1;
}
+ timeout = (maxlatency - TIMEDIFF(now, trigger)) \
+ / maxlatency * minlatency;
+ if (timeout > 0)
+ continue; /* we have time, try to find idle */
+ }
- draw();
- XFlush(xw.dpy);
-
- if (xev && !FD_ISSET(xfd, &rfd))
- xev--;
- if (!FD_ISSET(ttyfd, &rfd) && !FD_ISSET(xfd, &rfd)) {
- if (blinkset) {
- if (TIMEDIFF(now, lastblink) \
- > blinktimeout) {
- drawtimeout.tv_nsec = 1000;
- } else {
- drawtimeout.tv_nsec = (1E6 * \
- (blinktimeout - \
- TIMEDIFF(now,
- lastblink)));
- }
- drawtimeout.tv_sec = \
- drawtimeout.tv_nsec / 1E9;
- drawtimeout.tv_nsec %= (long)1E9;
- } else {
- tv = NULL;
- }
+ /* idle detected or maxlatency exhausted -> draw */
+ timeout = -1;
+ if (blinktimeout && tattrset(ATTR_BLINK)) {
+ timeout = blinktimeout - TIMEDIFF(now, lastblink);
+ if (timeout <= 0) {
+ if (-timeout > blinktimeout) /* start visible */
+ win.mode |= MODE_BLINK;
+ win.mode ^= MODE_BLINK;
+ tsetdirtattr(ATTR_BLINK);
+ lastblink = now;
+ timeout = blinktimeout;
}
}
+
+ draw();
+ XFlush(xw.dpy);
+ drawing = 0;
}
}
remember that computers suck.