From 37431e5da9fb37ecfed0163abe5f7d8072a5ed0b Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Mon, 30 May 2022 16:46:47 -0700 Subject: modules/checkers: experimenting with fill modes this introduces a color= setting syntax: color=#rrggbb color=0xrrggbb color=rrggbb where rrggbb is case-insensitive html-style hexadecimal also introduces a fill= setting: fill=color fill=sampled fill=textured fill=random fill=mixed sampled draws the color from the incoming fragment when layered, textured draws the pixels from the texture when available, random randomizes the choice from color,sampled,textured. mixed isn't implemented fully and is just aliased to random currently. The thinking for mixed is to allow specifying proportions for color,sampled,textured which would then be applied as weights when randomizing the selection from the three at every filled checker. the current implementation is just calling rand() when randomized, but should really be like the other dynamics in checkers with rate control and hash-based. and introduces a fill_module= setting: this is a first stab at employing other modules for filling the filled cells. Note since checkers is already a threaded module, the fill module context gets created per-cpu but with an n_cpus=1. This is kind of the first time module contexts are being rendered manifold for the same frame, and that's illuminating some shortcomings which needed to be dealt with. Some modules automatically advance a phase/T value on every render which gets persisted in their context struct. With how checkers is using contexts, it's desirable for multiple renders of the same context using the same ticks to produce the same output. So modules need to be more careful about time and determine "dt" (delta-time) values, and animate proportional to ticks elapsed. When ticks doesn't change between renders, dt is zero, and nothing should change. For now this is using a hard-coded list of modules to choose from, you specify the module by name or "none" for no fill_module (solid checker fill). ex: "fill_module=shapes" There's a need for something like fragment color and flag overrides to allow til_module_render() to be treated as more of a brush where the caller gets to specify what colors to use, or if texturing should be allowed. For now, when fill_module=$module is employed, the color determination stuff within checkers doesn't get applied. That will need to be fixed in the future. --- src/modules/checkers/checkers.c | 260 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 256 insertions(+), 4 deletions(-) diff --git a/src/modules/checkers/checkers.c b/src/modules/checkers/checkers.c index c936269..8b30445 100644 --- a/src/modules/checkers/checkers.c +++ b/src/modules/checkers/checkers.c @@ -27,6 +27,9 @@ #define CHECKERS_DEFAULT_PATTERN CHECKERS_PATTERN_CHECKERED #define CHECKERS_DEFAULT_DYNAMICS CHECKERS_DYNAMICS_ODD #define CHECKERS_DEFAULT_DYNAMICS_RATE 1.0 +#define CHECKERS_DEFAULT_FILL CHECKERS_FILL_COLOR +#define CHECKERS_DEFAULT_COLOR 0xffffff +#define CHECKERS_DEFAULT_FILL_MODULE "none" typedef enum checkers_pattern_t { @@ -41,17 +44,29 @@ typedef enum checkers_dynamics_t { CHECKERS_DYNAMICS_RANDOM, } checkers_dynamics_t; +typedef enum checkers_fill_t { + CHECKERS_FILL_COLOR, + CHECKERS_FILL_SAMPLED, + CHECKERS_FILL_TEXTURED, + CHECKERS_FILL_RANDOM, + CHECKERS_FILL_MIXED, +} checkers_fill_t; + typedef struct checkers_setup_t { til_setup_t til_setup; unsigned size; checkers_pattern_t pattern; checkers_dynamics_t dynamics; float rate; + checkers_fill_t fill; + uint32_t color; + const til_module_t *fill_module; } checkers_setup_t; typedef struct checkers_context_t { til_module_context_t til_module_context; checkers_setup_t setup; + til_module_context_t *fill_module_contexts[]; } checkers_context_t; @@ -60,26 +75,61 @@ static checkers_setup_t checkers_default_setup = { .pattern = CHECKERS_DEFAULT_PATTERN, .dynamics = CHECKERS_DEFAULT_DYNAMICS, .rate = CHECKERS_DEFAULT_DYNAMICS_RATE, + .color = CHECKERS_DEFAULT_COLOR, }; static til_module_context_t * checkers_create_context(unsigned seed, unsigned ticks, unsigned n_cpus, til_setup_t *setup) { + size_t size = sizeof(checkers_context_t); checkers_context_t *ctxt; if (!setup) setup = &checkers_default_setup.til_setup; - ctxt = til_module_context_new(sizeof(checkers_context_t), ticks, seed, n_cpus); + if (((checkers_setup_t *)setup)->fill_module) + size += sizeof(til_module_context_t *) * n_cpus; + + ctxt = til_module_context_new(size, ticks, seed, n_cpus); if (!ctxt) return NULL; ctxt->setup = *(checkers_setup_t *)setup; + if (ctxt->setup.fill_module) { + const til_module_t *module = ctxt->setup.fill_module; + til_setup_t *module_setup = NULL; + + (void) til_module_randomize_setup(module, &module_setup, NULL); + + /* since checkers is already threaded, create an n_cpus=1 context per-cpu */ + for (unsigned i = 0; i < n_cpus; i++) /* TODO: errors */ + (void) til_module_create_context(module, seed, ticks, 1, module_setup, &ctxt->fill_module_contexts[i]); + + /* XXX: it would be interesting to support various patterns/layouts by varying the seed, but this will require + * more complex context allocation strategies while also maintaining the per-cpu allocation. + */ + + til_setup_free(module_setup); + } + return &ctxt->til_module_context; } +static void checkers_destroy_context(til_module_context_t *context) +{ + checkers_context_t *ctxt = (checkers_context_t *)context; + + if (ctxt->setup.fill_module) { + for (unsigned i = 0; i < context->n_cpus; i++) + til_module_context_free(ctxt->fill_module_contexts[i]); + } + + free(ctxt); +} + + static int checkers_fragmenter(til_module_context_t *context, const til_fb_fragment_t *fragment, unsigned number, til_fb_fragment_t *res_fragment) { checkers_context_t *ctxt = (checkers_context_t *)context; @@ -92,7 +142,15 @@ static void checkers_prepare_frame(til_module_context_t *context, unsigned ticks { checkers_context_t *ctxt = (checkers_context_t *)context; - *res_frame_plan = (til_frame_plan_t){ .fragmenter = checkers_fragmenter }; + /* XXX: note cpu_affinity is required when fill_module is used, to ensure module_contexts + * have a stable relationship to fragnum. Otherwise the output would be unstable because the + * module contexts would be randomly distributed across the filled checkers frame-to-frame. + * This is unfortunate since cpu_affinity is likely to be slower than just letting the render + * threads render fragments in whatever order (the preferred default). fill_module here + * is actually *the* reason til_frame_plan_t.cpu_affinity got implemented, before this there + * wasn't even a til_frame_plan_t container; a bare til_fragmenter_t was returned. + */ + *res_frame_plan = (til_frame_plan_t){ .fragmenter = checkers_fragmenter, .cpu_affinity = !!ctxt->setup.fill_module }; } @@ -110,6 +168,8 @@ static inline unsigned hash(unsigned x) static void checkers_render_fragment(til_module_context_t *context, unsigned ticks, unsigned cpu, til_fb_fragment_t *fragment) { checkers_context_t *ctxt = (checkers_context_t *)context; + uint32_t color = ctxt->setup.color, flags = 0; + checkers_fill_t fill = ctxt->setup.fill; int state; switch (ctxt->setup.pattern) { @@ -142,10 +202,104 @@ static void checkers_render_fragment(til_module_context_t *context, unsigned tic break; } + if (fill == CHECKERS_FILL_RANDOM || fill == CHECKERS_FILL_MIXED) + fill = rand() % CHECKERS_FILL_RANDOM; /* TODO: mixed should have a setting for controlling the ratios */ + + switch (ctxt->setup.fill) { + case CHECKERS_FILL_SAMPLED: + if (fragment->cleared) + color = til_fb_fragment_get_pixel_unchecked(fragment, fragment->x + (fragment->width >> 1), fragment->y + (fragment->height >> 1)); + break; + case CHECKERS_FILL_TEXTURED: + flags = TIL_FB_DRAW_FLAG_TEXTURABLE; + break; + case CHECKERS_FILL_COLOR: + default: + break; + } + if (!state) til_fb_fragment_clear(fragment); - else - til_fb_fragment_fill(fragment, TIL_FB_DRAW_FLAG_TEXTURABLE, 0xffffffff); + else { + if (!ctxt->setup.fill_module) + til_fb_fragment_fill(fragment, flags, color); + else { + fragment->frame_width = ctxt->setup.size; + fragment->frame_height = ctxt->setup.size; + fragment->x = fragment->y = 0; + + /* TODO: we need a way to send down color and flags, and use the module render as a brush of sorts */ + til_module_render(ctxt->fill_module_contexts[cpu], ticks, fragment); + } + } +} + + +/* TODO: migrate to libtil */ +static char * checkers_random_color(void) +{ + /* til should probably have a common randomize color helper for this with a large collection of + * reasonable colors, and maybe even have themed palettes one can choose from... */ + static const char * colors[] = { + "#ffffff", + "#ff0000", + "#00ff00", + "#0000ff", + "#ffff00", + "#00ffff", + "#ff00ff", + }; + + return strdup(colors[rand() % nelems(colors)]); +} + + +/* TODO: migrate to libtil */ +static int checkers_rgb_to_uint32(const char *in, uint32_t *out) +{ + uint32_t color = 0; + + /* this isn't html, but accept #rrggbb syntax */ + if (*in == '#') + in++; + else if (in[0] == '0' && in[1] == 'x') /* and 0xrrggbb */ + in += 2; + + if (strlen(in) != 6) + return -EINVAL; + + for (int i = 0; i < 6;) { + uint8_t c = 0; + + color <<= 8; + + for (int j = 0; j < 2; in++, j++, i++) { + c <<= 4; + + switch (*in) { + case '0'...'9': + c |= (*in) - '0'; + break; + + case 'a'...'f': + c |= (*in) - 'a' + 10; + break; + + case 'A'...'F': + c |= (*in) - 'A' + 10; + break; + + default: + return -EINVAL; + } + } + + color |= c; + } + + *out = color; + + return 0; } @@ -153,8 +307,11 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se { const char *size; const char *pattern; + const char *fill_module; const char *dynamics; const char *dynamics_rate; + const char *fill; + const char *color; const char *size_values[] = { "4", "8", @@ -169,6 +326,19 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se "random", NULL }; + const char *fill_module_values[] = { + "none", + "blinds", + "moire", + "pixbounce", + "plato", + "roto", + "shapes", + "snow", + "spiro", + "stars", + NULL + }; const char *dynamics_values[] = { "odd", "even", @@ -187,6 +357,14 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se ".0001", NULL }; + const char *fill_values[] = { + "color", + "sampled", + "textured", + "random", + "mixed", + NULL + }; int r; r = til_settings_get_and_describe_value(settings, @@ -218,6 +396,20 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se if (r) return r; + r = til_settings_get_and_describe_value(settings, + &(til_setting_desc_t){ + .name = "Filled cell module (\"none\" for plain checkers)", + .key = "fill_module", + .preferred = fill_module_values[0], + .values = fill_module_values, + .annotations = NULL + }, + &fill_module, + res_setting, + res_desc); + if (r) + return r; + r = til_settings_get_and_describe_value(settings, &(til_setting_desc_t){ .name = "Checkers dynamics", @@ -248,6 +440,38 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se return r; } + r = til_settings_get_and_describe_value(settings, + &(til_setting_desc_t){ + .name = "Fill mode", + .key = "fill", + .preferred = fill_values[CHECKERS_DEFAULT_FILL], + .values = fill_values, + .annotations = NULL + }, + &fill, + res_setting, + res_desc); + if (r) + return r; + + /* Even though sampled and textured fills don't neceesarily use the color, + * if there's no texture or no underlay to sample, we should have a color to fallback on. + */ + r = til_settings_get_and_describe_value(settings, + &(til_setting_desc_t){ + .name = "Fill color", + .key = "color", + .preferred = TIL_SETTINGS_STR(CHECKERS_DEFAULT_COLOR), + .random = checkers_random_color, + .values = NULL, + .annotations = NULL + }, + &color, + res_setting, + res_desc); + if (r) + return r; + if (res_setup) { checkers_setup_t *setup; @@ -266,6 +490,14 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se return -EINVAL; } + if (strcasecmp(fill_module, "none")) { + setup->fill_module = til_lookup_module(fill_module); + if (!setup->fill_module) { + free(setup); + return -ENOMEM; + } + } + if (!strcasecmp(dynamics, "odd")) setup->dynamics = CHECKERS_DYNAMICS_ODD; else if (!strcasecmp(dynamics, "even")) @@ -282,6 +514,25 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se if (setup->dynamics != CHECKERS_DYNAMICS_ODD && setup->dynamics != CHECKERS_DYNAMICS_EVEN) sscanf(dynamics_rate, "%f", &setup->rate); + if (!strcasecmp(fill, "color")) + setup->fill = CHECKERS_FILL_COLOR; + else if (!strcasecmp(fill, "sampled")) + setup->fill = CHECKERS_FILL_SAMPLED; + else if (!strcasecmp(fill, "textured")) + setup->fill = CHECKERS_FILL_TEXTURED; + else if (!strcasecmp(fill, "random")) + setup->fill = CHECKERS_FILL_RANDOM; + else if (!strcasecmp(fill, "mixed")) + setup->fill = CHECKERS_FILL_MIXED; + else { + free(setup); + return -EINVAL; + } + + r = checkers_rgb_to_uint32(color, &setup->color); + if (r < 0) + return r; + *res_setup = &setup->til_setup; } @@ -291,6 +542,7 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se til_module_t checkers_module = { .create_context = checkers_create_context, + .destroy_context = checkers_destroy_context, .prepare_frame = checkers_prepare_frame, .render_fragment = checkers_render_fragment, .setup = checkers_setup, -- cgit v1.2.3