From 6fabe39ba8841ffe392c9b659505e12edaff43d6 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Mon, 26 Feb 2018 03:29:12 -0800 Subject: rototiller,fb: swap dispatch with page flipping For the sake of sdl_fb, move page flipping into the main thread and run module render dispatch from another thread instead. This eliminates the fb flipper thread, moving its functionality into fb_flip() which synchronously consumes and performs a single flip from the same queue as before - the function is verbatim the loop body of the flipper thread. Now main() calls fb_flip() in a loop where it previously dispatched pages for rendering. Rendering dispatch is now performed in a created thread. See the comment in fb.c for more explanation of this shuffle. --- src/fb.c | 82 +++++++++++++++++++++++++++++--------------------------- src/fb.h | 1 + src/rototiller.c | 72 +++++++++++++++++++++++++++++++++---------------- 3 files changed, 93 insertions(+), 62 deletions(-) (limited to 'src') diff --git a/src/fb.c b/src/fb.c index f398d43..7485800 100644 --- a/src/fb.c +++ b/src/fb.c @@ -37,6 +37,17 @@ * longer be a flipper thread. * * Let me know if you're aware of a better way with existing mainline drm! + * + * + * XXX: fb_new() used to create a thread which did the equivalent of fb_flip() + * continuously in a loop. This posed a problem for the sdl_fb backend, due to + * the need for event pumping in the page flip hook. SDL internally uses TLS + * and requires that the same thread which initialized SDL call the event + * functions. To satisfy this requirement, the body of the flipper thread loop + * has been moved to the fb_flip() function. Rototiller's main thread is + * expected to call this repeatedly, turning it effectively into the flipper + * thread. This required rototiller to move what was previously the main + * thread's duties - page rendering dispatch, to a separate thread. */ @@ -55,8 +66,6 @@ typedef struct fb_t { const fb_ops_t *ops; void *ops_context; - pthread_t thread; - _fb_page_t *active_page; /* page currently displayed */ pthread_mutex_t ready_mutex; @@ -80,38 +89,39 @@ typedef struct fb_t { /* Consumes ready pages queued via fb_page_put(), submits them to drm to flip * on vsync. Produces inactive pages from those replaced, making them * available to fb_page_get(). */ -static void * fb_flipper_thread(void *_fb) +int fb_flip(fb_t *fb) { - fb_t *fb = _fb; - - for (;;) { - _fb_page_t *next_active_page; - /* wait for a flip req, submit the req page for flip on vsync, wait for it to flip before making the - * active page inactive/available, repeat. - */ - pthread_mutex_lock(&fb->ready_mutex); - while (!fb->ready_pages_head) - pthread_cond_wait(&fb->ready_cond, &fb->ready_mutex); - - next_active_page = fb->ready_pages_head; - fb->ready_pages_head = next_active_page->next; - if (!fb->ready_pages_head) - fb->ready_pages_tail = NULL; - pthread_mutex_unlock(&fb->ready_mutex); - - /* submit the next active page for page flip on vsync, and wait for it. */ - pexit_if(fb->ops->page_flip(fb->ops_context, next_active_page->ops_page) < 0, - "unable to flip page"); - - /* now that we're displaying a new page, make the previously active one inactive so rendering can reuse it */ - pthread_mutex_lock(&fb->inactive_mutex); - fb->active_page->next = fb->inactive_pages; - fb->inactive_pages = fb->active_page; - pthread_cond_signal(&fb->inactive_cond); - pthread_mutex_unlock(&fb->inactive_mutex); - - fb->active_page = next_active_page; - } + _fb_page_t *next_active_page; + int r; + + /* wait for a flip req, submit the req page for flip on vsync, wait for it to flip before making the + * active page inactive/available, repeat. + */ + pthread_mutex_lock(&fb->ready_mutex); + while (!fb->ready_pages_head) + pthread_cond_wait(&fb->ready_cond, &fb->ready_mutex); + + next_active_page = fb->ready_pages_head; + fb->ready_pages_head = next_active_page->next; + if (!fb->ready_pages_head) + fb->ready_pages_tail = NULL; + pthread_mutex_unlock(&fb->ready_mutex); + + /* submit the next active page for page flip on vsync, and wait for it. */ + r = fb->ops->page_flip(fb->ops_context, next_active_page->ops_page); + if (r < 0) /* TODO: vet this: what happens to this page? */ + return r; + + /* now that we're displaying a new page, make the previously active one inactive so rendering can reuse it */ + pthread_mutex_lock(&fb->inactive_mutex); + fb->active_page->next = fb->inactive_pages; + fb->inactive_pages = fb->active_page; + pthread_cond_signal(&fb->inactive_cond); + pthread_mutex_unlock(&fb->inactive_mutex); + + fb->active_page = next_active_page; + + return 0; } @@ -126,9 +136,6 @@ static int fb_acquire(fb_t *fb, _fb_page_t *page) fb->active_page = page; - /* start up the page flipper thread */ - pthread_create(&fb->thread, NULL, fb_flipper_thread, fb); - return 0; } @@ -136,9 +143,6 @@ static int fb_acquire(fb_t *fb, _fb_page_t *page) /* release the fb, making the visible page inactive */ static void fb_release(fb_t *fb) { - pthread_cancel(fb->thread); - pthread_join(fb->thread, NULL); - fb->ops->release(fb->ops_context); fb->active_page->next = fb->inactive_pages; fb->inactive_pages = fb->active_page; diff --git a/src/fb.h b/src/fb.h index 7743b82..f9d0b77 100644 --- a/src/fb.h +++ b/src/fb.h @@ -47,6 +47,7 @@ void fb_page_put(fb_t *fb, fb_page_t *page); void fb_free(fb_t *fb); void fb_get_put_pages_count(fb_t *fb, unsigned *count); fb_t * fb_new(const fb_ops_t *ops, settings_t *settings, int n_pages); +int fb_flip(fb_t *fb); void fb_fragment_divide(fb_fragment_t *fragment, unsigned n_fragments, fb_fragment_t fragments[]); int fb_fragment_slice_single(const fb_fragment_t *fragment, unsigned n_fragments, unsigned num, fb_fragment_t *res_fragment); int fb_fragment_tile_single(const fb_fragment_t *fragment, unsigned tile_size, unsigned num, fb_fragment_t *res_fragment); diff --git a/src/rototiller.c b/src/rototiller.c index 79d0778..0fbc794 100644 --- a/src/rototiller.c +++ b/src/rototiller.c @@ -129,6 +129,7 @@ int parse_argv(int argc, const char *argv[], argv_t *res_args) return 0; } + typedef struct setup_t { settings_t *module; settings_t *video; @@ -182,6 +183,7 @@ static int setup_video(settings_t *settings, setting_desc_t **next_setting) return -EINVAL; } + /* select module if not yet selected, then setup the module. */ static int setup_module(settings_t *settings, setting_desc_t **next_setting) { @@ -223,6 +225,7 @@ static int setup_module(settings_t *settings, setting_desc_t **next_setting) return 0; } + /* turn args into settings, automatically applying defaults if appropriate, or interactively if appropriate. */ /* returns negative value on error, 0 when settings unchanged from args, 1 when changed */ static int setup_from_args(argv_t *args, int defaults, setup_t *res_setup) @@ -321,6 +324,30 @@ static int print_help(void) } +typedef struct rototiller_t { + rototiller_module_t *module; + void *module_context; + threads_t *threads; + pthread_t thread; + fb_t *fb; +} rototiller_t; + +void * rototiller_thread(void *_rt) +{ + rototiller_t *rt = _rt; + + for (;;) { + fb_page_t *page; + + page = fb_page_get(rt->fb); + module_render_page(rt->module, rt->module_context, rt->threads, page); + fb_page_put(rt->fb, page); + } + + return NULL; +} + + /* When run with partial/no arguments, if stdin is a tty, enter an interactive setup. * If stdin is not a tty, or if --defaults is supplied in argv, default settings are used. * If any changes to the settings occur in the course of execution, either interactively or @@ -329,13 +356,10 @@ static int print_help(void) */ int main(int argc, const char *argv[]) { - argv_t args = {}; - setup_t setup = {}; - void *context = NULL; - rototiller_module_t *module; - threads_t *threads; - fb_t *fb; - int r; + rototiller_t rototiller = {}; + setup_t setup = {}; + argv_t args = {}; + int r; exit_if(parse_argv(argc, argv, &args) < 0, "unable to process arguments"); @@ -349,38 +373,40 @@ int main(int argc, const char *argv[]) exit_if(r && print_setup_as_args(&setup) < 0, "unable to print setup"); - exit_if(!(module = module_lookup(settings_get_key(setup.module, 0))), + exit_if(!(rototiller.module = module_lookup(settings_get_key(setup.module, 0))), "unable to lookup module from settings \"%s\"", settings_get_key(setup.module, 0)); - exit_if(!(fb = fb_new(fb_ops, setup.video, NUM_FB_PAGES)), + exit_if(!(rototiller.fb = fb_new(fb_ops, setup.video, NUM_FB_PAGES)), "unable to create fb"); exit_if(!fps_setup(), "unable to setup fps counter"); - exit_if(module->create_context && - !(context = module->create_context()), + exit_if(rototiller.module->create_context && + !(rototiller.module_context = rototiller.module->create_context()), "unable to create module context"); - pexit_if(!(threads = threads_create()), - "unable to create threads"); + pexit_if(!(rototiller.threads = threads_create()), + "unable to create rendering threads"); - for (;;) { - fb_page_t *page; + pexit_if(pthread_create(&rototiller.thread, NULL, rototiller_thread, &rototiller) != 0, + "unable to create dispatch thread"); - fps_print(fb); + for (;;) { + if (fb_flip(rototiller.fb) < 0) + break; - page = fb_page_get(fb); - module_render_page(module, context, threads, page); - fb_page_put(fb, page); + fps_print(rototiller.fb); } - threads_destroy(threads); + pthread_cancel(rototiller.thread); + pthread_join(rototiller.thread, NULL); + threads_destroy(rototiller.threads); - if (context) - module->destroy_context(context); + if (rototiller.module_context) + rototiller.module->destroy_context(rototiller.module_context); - fb_free(fb); + fb_free(rototiller.fb); return EXIT_SUCCESS; } -- cgit v1.2.1