diff options
author | Vito Caputo <vcaputo@pengaru.com> | 2022-05-30 16:46:47 -0700 |
---|---|---|
committer | Vito Caputo <vcaputo@pengaru.com> | 2022-06-10 21:23:09 -0700 |
commit | 37431e5da9fb37ecfed0163abe5f7d8072a5ed0b (patch) | |
tree | 16cd8b35dfb8c51795e120f3caf865a2b3bc7639 | |
parent | d5db150801023c307fcbe1cd98b4fd8e2d27b55e (diff) |
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.
-rw-r--r-- | src/modules/checkers/checkers.c | 260 |
1 files 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, @@ -220,6 +398,20 @@ static int checkers_setup(const til_settings_t *settings, til_setting_t **res_se 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", .key = "dynamics", .preferred = dynamics_values[0], @@ -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, |