From 11b87c843e20f66bd68e02353ba4a1072e1230a6 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Sun, 14 Feb 2021 22:43:08 -0800 Subject: *: split rototiller.[ch] into lib and main This is a first approximation of separating the core modules and threaded rendering from the cli-centric rototiller program and its sdl+drm video backends. Unfortunately this seemed to require switching over to libtool archives (.la) to permit consolidating the per-lib and per-module .a files into the librototiller.a and linking just with librototiller.a to depend on the aggregate of libs+modules+librototiller-glue in a simple fashion. If an alternative to .la comes up I will switch over to it, using libtool really slows down the build process. Those are implementation/build system details though. What's important in these changes is establishing something resembling a librototiller API boundary, enabling creating alternative frontends which vendor this tree as a submodule and link just to librototiller.{la,a} for all the modules+threaded rendering of them, while providing their own fb_ops_t for outputting into, and their own settings applicators for driving the modules setup. --- src/main.c | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) create mode 100644 src/main.c (limited to 'src/main.c') diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..5180529 --- /dev/null +++ b/src/main.c @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "settings.h" +#include "setup.h" +#include "fb.h" +#include "fps.h" +#include "rototiller.h" +#include "util.h" + +/* Copyright (C) 2016 Vito Caputo */ + +#define NUM_FB_PAGES 3 +/* ^ By triple-buffering, we can have a page tied up being displayed, another + * tied up submitted and waiting for vsync, and still not block on getting + * another page so we can begin rendering another frame before vsync. With + * just two pages we end up twiddling thumbs until the vsync arrives. + */ +#define DEFAULT_VIDEO "sdl" + +extern fb_ops_t drm_fb_ops; +extern fb_ops_t sdl_fb_ops; +fb_ops_t *fb_ops; + +typedef struct rototiller_t { + const rototiller_module_t *module; + void *module_context; + pthread_t thread; + fb_t *fb; + struct timeval start_tv; + unsigned ticks_offset; +} rototiller_t; + +static rototiller_t rototiller; + + +typedef struct argv_t { + const char *module; + const char *video; + + unsigned use_defaults:1; + unsigned help:1; +} argv_t; + +/* + * ./rototiller --video=drm,dev=/dev/dri/card3,connector=VGA-1,mode=640x480@60 + * ./rototiller --video=sdl,size=640x480 + * ./rototiller --module=roto,foo=bar,module=settings + * ./rototiller --defaults + */ +static int parse_argv(int argc, const char *argv[], argv_t *res_args) +{ + int i; + + assert(argc > 0); + assert(argv); + assert(res_args); + + /* this is intentionally being kept very simple, no new dependencies like getopt. */ + + for (i = 1; i < argc; i++) { + if (!strncmp("--video=", argv[i], 8)) { + res_args->video = &argv[i][8]; + } else if (!strncmp("--module=", argv[i], 9)) { + res_args->module = &argv[i][9]; + } else if (!strcmp("--defaults", argv[i])) { + res_args->use_defaults = 1; + } else if (!strcmp("--help", argv[i])) { + res_args->help = 1; + } else { + return -EINVAL; + } + } + + return 0; +} + + +typedef struct setup_t { + settings_t *module; + settings_t *video; +} setup_t; + +/* FIXME: this is unnecessarily copy-pasta, i think modules should just be made + * more generic to encompass the setting up uniformly, then basically + * subclass the video backend vs. renderer stuff. + */ + +/* select video backend if not yet selected, then setup the selected backend. */ +static int setup_video(settings_t *settings, setting_desc_t **next_setting) +{ + const char *video; + + /* XXX: there's only one option currently, so this is simple */ + video = settings_get_key(settings, 0); + if (!video) { + setting_desc_t *desc; + const char *values[] = { +#ifdef HAVE_DRM + "drm", +#endif + "sdl", + NULL, + }; + int r; + + r = setting_desc_clone(&(setting_desc_t){ + .name = "Video Backend", + .key = NULL, + .regex = "[a-z]+", + .preferred = DEFAULT_VIDEO, + .values = values, + .annotations = NULL + }, next_setting); + if (r < 0) + return r; + + return 1; + } + + /* XXX: this is kind of hacky for now */ +#ifdef HAVE_DRM + if (!strcmp(video, "drm")) { + fb_ops = &drm_fb_ops; + + return drm_fb_ops.setup(settings, next_setting); + } else +#endif + if (!strcmp(video, "sdl")) { + fb_ops = &sdl_fb_ops; + + return sdl_fb_ops.setup(settings, next_setting); + } + + return -EINVAL; +} + + +/* 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, setup_t *res_setup) +{ + int r, changes = 0; + setup_t setup; + + setup.module = settings_new(args->module); + if (!setup.module) + return -ENOMEM; + + setup.video = settings_new(args->video); + if (!setup.video) { + settings_free(setup.module); + + return -ENOMEM; + } + + r = setup_interactively(setup.module, rototiller_module_setup, args->use_defaults); + if (r < 0) { + settings_free(setup.module); + settings_free(setup.video); + + return r; + } + + if (r) + changes = 1; + + r = setup_interactively(setup.video, setup_video, args->use_defaults); + if (r < 0) { + settings_free(setup.module); + settings_free(setup.video); + + return r; + } + + if (r) + changes = 1; + + *res_setup = setup; + + return changes; +} + + +static int print_setup_as_args(setup_t *setup) +{ + char *module_args, *video_args; + char buf[64]; + int r; + + module_args = settings_as_arg(setup->module); + if (!module_args) { + r = -ENOMEM; + + goto _out; + } + + video_args = settings_as_arg(setup->video); + if (!video_args) { + r = -ENOMEM; + + goto _out_module; + } + + r = printf("\nConfigured settings as flags:\n --module=%s --video=%s\n\nPress enter to continue...\n", + module_args, + video_args); + + if (r < 0) + goto _out_video; + + (void) fgets(buf, sizeof(buf), stdin); + +_out_video: + free(video_args); +_out_module: + free(module_args); +_out: + return r; +} + + +static int print_help(void) +{ + return printf( + "Run without any flags or partial settings for interactive mode.\n" + "\n" + "Supported flags:\n" + " --defaults use defaults for unspecified settings\n" + " --help this help\n" + " --module= module settings\n" + " --video= video settings\n" + ); +} + + +static unsigned get_ticks(const struct timeval *start, const struct timeval *now, unsigned offset) +{ + return (unsigned)((now->tv_sec - start->tv_sec) * 1000 + (now->tv_usec - start->tv_usec) / 1000) + offset; +} + + +static void * rototiller_thread(void *_rt) +{ + rototiller_t *rt = _rt; + struct timeval now; + + for (;;) { + fb_page_t *page; + unsigned ticks; + + page = fb_page_get(rt->fb); + + gettimeofday(&now, NULL); + ticks = get_ticks(&rt->start_tv, &now, rt->ticks_offset); + + rototiller_module_render(rt->module, rt->module_context, ticks, &page->fragment); + + 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 + * throught --defaults, then print out the explicit CLI invocation usable for reproducing + * the invocation. + */ +int main(int argc, const char *argv[]) +{ + setup_t setup = {}; + argv_t args = {}; + int r; + + exit_if(parse_argv(argc, argv, &args) < 0, + "unable to process arguments"); + + if (args.help) + return print_help() < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + exit_if((r = setup_from_args(&args, &setup)) < 0, + "unable to setup: %s", strerror(-r)); + + exit_if(r && print_setup_as_args(&setup) < 0, + "unable to print setup"); + + exit_if(!(rototiller.module = rototiller_lookup_module(settings_get_key(setup.module, 0))), + "unable to lookup module from settings \"%s\"", settings_get_key(setup.module, 0)); + + exit_if((r = fb_new(fb_ops, setup.video, NUM_FB_PAGES, &rototiller.fb)) < 0, + "unable to create fb: %s", strerror(-r)); + + exit_if(!fps_setup(), + "unable to setup fps counter"); + + exit_if((r = rototiller_init()) < 0, + "unable to initialize librototiller: %s", strerror(-r)); + + gettimeofday(&rototiller.start_tv, NULL); + exit_if((r = rototiller_module_create_context( + rototiller.module, + get_ticks(&rototiller.start_tv, + &rototiller.start_tv, + rototiller.ticks_offset), + &rototiller.module_context)) < 0, + "unable to create module context: %s", strerror(-r)); + + pexit_if(pthread_create(&rototiller.thread, NULL, rototiller_thread, &rototiller) != 0, + "unable to create dispatch thread"); + + for (;;) { + if (fb_flip(rototiller.fb) < 0) + break; + + fps_print(rototiller.fb); + } + + pthread_cancel(rototiller.thread); + pthread_join(rototiller.thread, NULL); + rototiller_shutdown(); + + if (rototiller.module_context) + rototiller.module->destroy_context(rototiller.module_context); + + fb_free(rototiller.fb); + + return EXIT_SUCCESS; +} -- cgit v1.2.1