From 8398f4b0f98f4c26f84b52a16a8daecd697a61db Mon Sep 17 00:00:00 2001 From: David Herrmann Date: Tue, 21 Aug 2012 17:56:34 +0200 Subject: [PATCH] terminal: add --fps to limit framerate Instead of redrawing on change, we now use a framerate-timer which redraws the screen. This timer stays active for 1s after the last redraw so we do not enable/disable the timer while the console is under heavy work-load. This still needs to be benchmarked but it seems to work nicely. Signed-off-by: David Herrmann --- src/main.c | 2 ++ src/main.h | 3 ++ src/terminal.c | 92 +++++++++++++++++++++++++++++++++++++++++--------- 3 files changed, 81 insertions(+), 16 deletions(-) diff --git a/src/main.c b/src/main.c index 0f06a40..d3c85f3 100644 --- a/src/main.c +++ b/src/main.c @@ -432,6 +432,7 @@ static void print_help() "\t --fbdev [off] Use fbdev instead of DRM\n" "\t --dumb [off] Use dumb DRM instead of hardware-\n" "\t accelerated DRM devices\n" + "\t --fps [50] Limit frame-rate\n" "\n" "Input Device Options:\n" "\t --xkb-layout [us] Set XkbLayout for input devices\n" @@ -523,6 +524,7 @@ struct conf_option options[] = { CONF_OPTION_BOOL(0, "silent", NULL, &kmscon_conf.silent, false), CONF_OPTION_BOOL(0, "fbdev", NULL, &kmscon_conf.use_fbdev, false), CONF_OPTION_BOOL(0, "dumb", NULL, &kmscon_conf.dumb, false), + CONF_OPTION_UINT(0, "fps", NULL, &kmscon_conf.fps, 50), CONF_OPTION_BOOL('s', "switchvt", NULL, &kmscon_conf.switchvt, false), CONF_OPTION_BOOL('l', "login", aftercheck_login, &kmscon_conf.login, false), CONF_OPTION_STRING('t', "term", NULL, &kmscon_conf.term, "vt220"), diff --git a/src/main.h b/src/main.h index 05439c4..9b305bf 100644 --- a/src/main.h +++ b/src/main.h @@ -79,6 +79,9 @@ struct kmscon_conf_t { /* color palette */ char *palette; + + /* frame-rate limit */ + unsigned int fps; }; extern struct kmscon_conf_t kmscon_conf; diff --git a/src/terminal.c b/src/terminal.c index 19b2c1b..312831d 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -67,7 +67,9 @@ struct kmscon_terminal { unsigned int min_cols; unsigned int min_rows; - bool redraw; + unsigned int fps; + unsigned int redraw; + struct ev_timer *redraw_timer; struct kmscon_console *console; struct kmscon_vte *vte; struct kmscon_pty *pty; @@ -76,9 +78,8 @@ struct kmscon_terminal { void *data; }; -static void draw_all(struct ev_eloop *eloop, void *unused, void *data) +static void redraw(struct kmscon_terminal *term) { - struct kmscon_terminal *term = data; struct uterm_screen *screen; struct kmscon_dlist *iter; struct screen *ent; @@ -86,9 +87,6 @@ static void draw_all(struct ev_eloop *eloop, void *unused, void *data) if (!term->awake) return; - ev_eloop_unregister_idle_cb(term->eloop, draw_all, term); - term->redraw = false; - kmscon_dlist_for_each(iter, &term->screens) { ent = kmscon_dlist_entry(iter, struct screen, list); screen = ent->screen; @@ -98,18 +96,47 @@ static void draw_all(struct ev_eloop *eloop, void *unused, void *data) } } +static void redraw_timer_event(struct ev_timer *timer, uint64_t num, void *data) +{ + struct kmscon_terminal *term = data; + + /* When a redraw is scheduled, the redraw-counter is set to the current + * frame-rate. If this counter is full, we know that data changed and we + * need to redraw the terminal. We then decrement the counter until it + * drops to zero. This guarantees that we stay active for 1s without + * a call to ev_timer_enable/disable() which required syscalls which can + * be quite slow. + * If a redraw is schedule in the meantime, the counter is reset to the + * framerate and we have to redraw the screen. If it drops to zero, that + * is, 1s after the last redraw, we disable the timer to stop consuming + * CPU-power. + * TODO: On _really_ slow machines we might want to avoid fps-limits + * and redraw on change. We also should check whether waking up for the + * fps-timeouts for 1s is really faster than calling + * ev_timer_enable/disable() all the time. */ + + if (term->redraw-- != term->fps) { + if (!term->redraw) { + ev_timer_disable(term->redraw_timer); + log_debug("disable-timer"); + } + return; + } + + log_debug("draw"); + redraw(term); +} + static void schedule_redraw(struct kmscon_terminal *term) { - int ret; - - if (term->redraw || !term->awake) + if (!term->awake) return; - ret = ev_eloop_register_idle_cb(term->eloop, draw_all, term); - if (ret) - log_warn("cannot schedule redraw"); - else - term->redraw = true; + log_debug("schedule"); + if (!term->redraw) + ev_timer_enable(term->redraw_timer); + if (term->redraw < term->fps) + term->redraw = term->fps; } /* @@ -354,6 +381,8 @@ int kmscon_terminal_new(struct kmscon_terminal **out, { struct kmscon_terminal *term; int ret; + struct itimerspec spec; + unsigned long fps; if (!out || !loop || !input) return -EINVAL; @@ -368,6 +397,19 @@ int kmscon_terminal_new(struct kmscon_terminal **out, term->input = input; kmscon_dlist_init(&term->screens); + if (kmscon_conf.fps) { + fps = 1000000000ULL / kmscon_conf.fps; + if (fps == 0) + fps = 1000000000ULL / 100; + else if (fps > 200000000ULL) + fps = 200000000ULL; + } else { + fps = 1000000000ULL / 25; + } + + term->fps = 1000000000ULL / fps; + log_debug("FPS: %lu TIMER: %lu", term->fps, fps); + ret = kmscon_console_new(&term->console); if (ret) goto err_free; @@ -385,6 +427,20 @@ int kmscon_terminal_new(struct kmscon_terminal **out, if (ret) goto err_pty; + memset(&spec, 0, sizeof(spec)); + spec.it_value.tv_nsec = 1; + spec.it_interval.tv_nsec = fps; + + ret = ev_timer_new(&term->redraw_timer, &spec, redraw_timer_event, + term, log_llog); + if (ret) + goto err_input; + ev_timer_disable(term->redraw_timer); + + ret = ev_eloop_add_timer(term->eloop, term->redraw_timer); + if (ret) + goto err_timer; + ev_eloop_ref(term->eloop); uterm_input_ref(term->input); *out = term; @@ -392,6 +448,10 @@ int kmscon_terminal_new(struct kmscon_terminal **out, log_debug("new terminal object %p", term); return 0; +err_timer: + ev_timer_unref(term->redraw_timer); +err_input: + uterm_input_unregister_cb(term->input, input_event, term); err_pty: kmscon_pty_unref(term->pty); err_vte: @@ -422,12 +482,12 @@ void kmscon_terminal_unref(struct kmscon_terminal *term) log_debug("free terminal object %p", term); kmscon_terminal_close(term); rm_all_screens(term); + ev_eloop_rm_timer(term->redraw_timer); + ev_timer_unref(term->redraw_timer); uterm_input_unregister_cb(term->input, input_event, term); kmscon_pty_unref(term->pty); kmscon_vte_unref(term->vte); kmscon_console_unref(term->console); - if (term->redraw) - ev_eloop_unregister_idle_cb(term->eloop, draw_all, term); uterm_input_unref(term->input); ev_eloop_unref(term->eloop); free(term);