summaryrefslogtreecommitdiff
path: root/src/modules/roto
diff options
context:
space:
mode:
authorVito Caputo <vcaputo@pengaru.com>2023-07-15 18:37:05 -0700
committerVito Caputo <vcaputo@pengaru.com>2023-07-15 18:37:05 -0700
commitcc5be69ae52c4a7a53183482ef97cd88fbce3ca7 (patch)
treeb0767cd2fadb30fcd2dbb8483b6b0922c2ef0ce4 /src/modules/roto
parent44512371023da3e842bc4e752113a149b2559185 (diff)
modules/roto: implement fill_module= setting
This makes it possible to tiled+rotate the output of another module in the same manner checkers::fill_module fills cells with module output. The default stays "none" for the classic roto with the psychedelic color cycling. When !"none" the color cycling doesn't get applied currently. It might be interesting to try support that in the future though.
Diffstat (limited to 'src/modules/roto')
-rw-r--r--src/modules/roto/roto.c315
1 files changed, 296 insertions, 19 deletions
diff --git a/src/modules/roto/roto.c b/src/modules/roto/roto.c
index 28e27e1..ac6fa4f 100644
--- a/src/modules/roto/roto.c
+++ b/src/modules/roto/roto.c
@@ -14,12 +14,14 @@
#define FIXED_BITS 11 /* fractional bits */
#define FIXED_EXP (1 << FIXED_BITS) /* 2^FIXED_BITS */
#define FIXED_MASK (FIXED_EXP - 1) /* fractional part mask */
-#define FIXED_COS(_rad) costab[(_rad) % FIXED_TRIG_LUT_SIZE]
-#define FIXED_SIN(_rad) sintab[(_rad) % FIXED_TRIG_LUT_SIZE]
+#define FIXED_COS(_rad) roto_costab[(_rad) % FIXED_TRIG_LUT_SIZE]
+#define FIXED_SIN(_rad) roto_sintab[(_rad) % FIXED_TRIG_LUT_SIZE]
#define FIXED_MULT(_a, _b) (((_a) * (_b)) >> FIXED_BITS)
#define FIXED_NEW(_i) ((_i) << FIXED_BITS)
#define FIXED_TO_INT(_f) ((_f) >> FIXED_BITS)
+#define ROTO_TEXTURE_SIZE 256
+
typedef struct color_t {
int r, g, b;
} color_t;
@@ -28,36 +30,45 @@ typedef struct roto_context_t {
til_module_context_t til_module_context;
unsigned r, rr;
color_t palette[2];
+ til_module_context_t *fill_module_context;
+ til_fb_fragment_t fill_fb;
} roto_context_t;
-static int32_t costab[FIXED_TRIG_LUT_SIZE], sintab[FIXED_TRIG_LUT_SIZE];
-static uint8_t texture[256][256];
+typedef struct roto_setup_t {
+ til_setup_t til_setup;
+
+ const til_module_t *fill_module;
+ til_setup_t *fill_module_setup;
+} roto_setup_t;
+static int32_t roto_costab[FIXED_TRIG_LUT_SIZE], roto_sintab[FIXED_TRIG_LUT_SIZE];
+static uint8_t roto_texture[ROTO_TEXTURE_SIZE][ROTO_TEXTURE_SIZE];
-static void init_roto(uint8_t texture[256][256], int32_t *costab, int32_t *sintab)
+
+static void init_roto(uint8_t texture[ROTO_TEXTURE_SIZE][ROTO_TEXTURE_SIZE], int32_t *roto_costab, int32_t *roto_sintab)
{
int x, y, i;
/* Generate simple checker pattern texture, nothing clever, feel free to play! */
/* If you modify texture on every frame instead of only @ initialization you can
* produce some neat output. These values are indexed into palette[] below. */
- for (y = 0; y < 128; y++) {
- for (x = 0; x < 128; x++)
+ for (y = 0; y < ROTO_TEXTURE_SIZE >> 1; y++) {
+ for (x = 0; x < ROTO_TEXTURE_SIZE >> 1; x++)
texture[y][x] = 1;
- for (; x < 256; x++)
+ for (; x < ROTO_TEXTURE_SIZE; x++)
texture[y][x] = 0;
}
- for (; y < 256; y++) {
- for (x = 0; x < 128; x++)
+ for (; y < ROTO_TEXTURE_SIZE; y++) {
+ for (x = 0; x < ROTO_TEXTURE_SIZE >> 1; x++)
texture[y][x] = 0;
- for (; x < 256; x++)
+ for (; x < ROTO_TEXTURE_SIZE; x++)
texture[y][x] = 1;
}
/* Generate fixed-point cos & sin LUTs. */
for (i = 0; i < FIXED_TRIG_LUT_SIZE; i++) {
- costab[i] = ((cos((double)2*M_PI*i/FIXED_TRIG_LUT_SIZE))*FIXED_EXP);
- sintab[i] = ((sin((double)2*M_PI*i/FIXED_TRIG_LUT_SIZE))*FIXED_EXP);
+ roto_costab[i] = ((cos((double)2*M_PI*i/FIXED_TRIG_LUT_SIZE))*FIXED_EXP);
+ roto_sintab[i] = ((sin((double)2*M_PI*i/FIXED_TRIG_LUT_SIZE))*FIXED_EXP);
}
}
@@ -70,13 +81,39 @@ static til_module_context_t * roto_create_context(const til_module_t *module, ti
if (!initialized) {
initialized = 1;
- init_roto(texture, costab, sintab);
+ init_roto(roto_texture, roto_costab, roto_sintab);
}
ctxt = til_module_context_new(module, sizeof(roto_context_t), stream, seed, ticks, n_cpus, setup);
if (!ctxt)
return NULL;
+ if (((roto_setup_t *)setup)->fill_module) {
+ const til_module_t *module = ((roto_setup_t *)setup)->fill_module;
+
+ if (til_module_create_contexts(module,
+ stream,
+ seed,
+ ticks,
+ n_cpus,
+ ((roto_setup_t *)setup)->fill_module_setup,
+ 1,
+ &ctxt->fill_module_context) < 0)
+ return til_module_context_free(&ctxt->til_module_context);
+
+ ctxt->fill_fb = (til_fb_fragment_t){
+ .buf = malloc(ROTO_TEXTURE_SIZE * ROTO_TEXTURE_SIZE * sizeof(uint32_t)),
+
+ .frame_width = ROTO_TEXTURE_SIZE,
+ .frame_height = ROTO_TEXTURE_SIZE,
+ .width = ROTO_TEXTURE_SIZE,
+ .height = ROTO_TEXTURE_SIZE,
+ .pitch = ROTO_TEXTURE_SIZE,
+ };
+ if (!ctxt->fill_fb.buf)
+ return til_module_context_free(&ctxt->til_module_context);
+ }
+
ctxt->r = rand_r(&seed);
ctxt->rr = rand_r(&seed);
@@ -84,6 +121,16 @@ static til_module_context_t * roto_create_context(const til_module_t *module, ti
}
+static void roto_destroy_context(til_module_context_t *context)
+{
+ roto_context_t *ctxt = (roto_context_t *)context;
+
+ free(ctxt->fill_fb.buf);
+ til_module_context_free(ctxt->fill_module_context);
+ free(ctxt);
+}
+
+
/* linearly interpolate between two colors, alpha is fixed-point value 0-FIXED_EXP. */
static inline color_t lerp_color(color_t *a, color_t *b, int alpha)
{
@@ -105,7 +152,7 @@ static inline color_t lerp_color(color_t *a, color_t *b, int alpha)
/* Return the bilinearly interpolated color palette[texture[ty][tx]] (Anti-Aliasing) */
/* tx, ty are fixed-point for fractions, palette colors are also in fixed-point format. */
-static uint32_t bilerp_color(uint8_t texture[256][256], color_t *palette, int tx, int ty)
+static uint32_t bilerp_color(uint8_t texture[ROTO_TEXTURE_SIZE][ROTO_TEXTURE_SIZE], color_t *palette, int tx, int ty)
{
uint8_t itx = FIXED_TO_INT(tx), ity = FIXED_TO_INT(ty);
color_t n_color, s_color, color;
@@ -181,6 +228,100 @@ static uint32_t bilerp_color(uint8_t texture[256][256], color_t *palette, int tx
}
+static inline color_t * pixel32_to_color(uint32_t pixel, color_t *res_color)
+{
+ *res_color = (color_t){
+ .r = FIXED_NEW((pixel >> 16) & 0xff),
+ .g = FIXED_NEW((pixel >> 8) & 0xff),
+ .b = FIXED_NEW((pixel) & 0xff),
+ };
+
+ return res_color;
+}
+
+
+/* Return the bilinearly interpolated color palette[texture[ty][tx]] (Anti-Aliasing) */
+/* tx, ty are fixed-point for fractions, palette colors are also in fixed-point format. */
+static uint32_t bilerp_color_pixel32(uint32_t texture[ROTO_TEXTURE_SIZE][ROTO_TEXTURE_SIZE], int tx, int ty)
+{
+ uint8_t itx = FIXED_TO_INT(tx), ity = FIXED_TO_INT(ty);
+ color_t n_color, s_color, color;
+ int x_alpha, y_alpha;
+ uint32_t nw, ne, sw, se;
+
+ /* We need the 4 texels constituting a 2x2 square pattern to interpolate.
+ * A point tx,ty can only intersect one texel; one corner of the 2x2 square.
+ * Where relative to the corner's center the intersection occurs determines which corner has been intersected,
+ * and the other corner texels may then be addressed relative to that corner.
+ * Alpha values must also be determined for both axis, these values describe the position between
+ * the 2x2 texel centers the intersection occurred, aka the weight or bias.
+ * Once the two alpha values are known, linear interpolation between the texel colors is trivial.
+ */
+
+ if ((ty & FIXED_MASK) > (FIXED_EXP >> 1)) {
+ y_alpha = ty & (FIXED_MASK >> 1);
+
+ if ((tx & (FIXED_MASK)) > (FIXED_EXP >> 1)) {
+ nw = texture[ity][itx];
+ ne = texture[ity][(uint8_t)(itx + 1)];
+ sw = texture[(uint8_t)(ity + 1)][itx];
+ se = texture[(uint8_t)(ity + 1)][(uint8_t)(itx + 1)];
+
+ x_alpha = tx & (FIXED_MASK >> 1);
+ } else {
+ ne = texture[ity][itx];
+ nw = texture[ity][(uint8_t)(itx - 1)];
+ se = texture[(uint8_t)(ity + 1)][itx];
+ sw = texture[(uint8_t)(ity + 1)][(uint8_t)(itx - 1)];
+
+ x_alpha = (FIXED_EXP >> 1) + (tx & (FIXED_MASK >> 1));
+ }
+ } else {
+ y_alpha = (FIXED_EXP >> 1) + (ty & (FIXED_MASK >> 1));
+
+ if ((tx & (FIXED_MASK)) > (FIXED_EXP >> 1)) {
+ sw = texture[ity][itx];
+ se = texture[ity][(uint8_t)(itx + 1)];
+ nw = texture[(uint8_t)(ity - 1)][itx];
+ ne = texture[(uint8_t)(ity - 1)][(uint8_t)(itx + 1)];
+
+ x_alpha = tx & (FIXED_MASK >> 1);
+ } else {
+ se = texture[ity][itx];
+ sw = texture[ity][(uint8_t)(itx - 1)];
+ ne = texture[(uint8_t)(ity - 1)][itx];
+ nw = texture[(uint8_t)(ity - 1)][(uint8_t)(itx - 1)];
+
+ x_alpha = (FIXED_EXP >> 1) + (tx & (FIXED_MASK >> 1));
+ }
+ }
+
+ /* Skip interpolation of same colors, a substantial optimization with plain textures like the checker pattern */
+ if (nw == ne) {
+ if (ne == sw && sw == se)
+ return sw;
+
+ pixel32_to_color(nw, &n_color);
+ } else {
+ n_color = lerp_color(pixel32_to_color(nw, &(color_t){}),
+ pixel32_to_color(ne, &(color_t){}),
+ x_alpha);
+ }
+
+ if (sw == se) {
+ pixel32_to_color(sw, &s_color);
+ } else {
+ s_color = lerp_color(pixel32_to_color(sw, &(color_t){}),
+ pixel32_to_color(se, &(color_t){}),
+ x_alpha);
+ }
+
+ color = lerp_color(&n_color, &s_color, y_alpha);
+
+ return (FIXED_TO_INT(color.r) << 16) | (FIXED_TO_INT(color.g) << 8) | FIXED_TO_INT(color.b);
+}
+
+
/* prepare a frame for concurrent rendering */
static void roto_prepare_frame(til_module_context_t *context, til_stream_t *stream, unsigned ticks, til_fb_fragment_t **fragment_ptr, til_frame_plan_t *res_frame_plan)
{
@@ -202,6 +343,13 @@ static void roto_prepare_frame(til_module_context_t *context, til_stream_t *stre
ctxt->palette[1].g = (FIXED_MULT(FIXED_COS(ctxt->rr / 2), FIXED_NEW(127)) + FIXED_NEW(128));
ctxt->palette[1].b = (FIXED_MULT(FIXED_SIN(ctxt->rr), FIXED_NEW(127)) + FIXED_NEW(128));
}
+
+ if (ctxt->fill_module_context) {
+ til_fb_fragment_t *fb_ptr = &ctxt->fill_fb;
+
+ ctxt->fill_fb.cleared = 0;
+ til_module_render(ctxt->fill_module_context, stream, ticks, &fb_ptr);
+ }
}
@@ -233,11 +381,24 @@ static void roto_render_fragment(til_module_context_t *context, til_stream_t *st
x_cos_r = x_cos_r_init;
x_sin_r = x_sin_r_init;
- for (int x = 0; x < fragment->width; x++, buf++) {
- *buf = bilerp_color(texture, ctxt->palette, x_sin_r - y_cos_r, y_sin_r + x_cos_r);
+ if (ctxt->fill_module_context) {
+ for (int x = 0; x < fragment->width; x++, buf++) {
+ /* TODO: it would be interesting to support an overlay mode where we alpha blend this into the existing surface,
+ * so fill_modules that were overlayable would overlay in the rotated+tiled form...
+ */
+ *buf = bilerp_color_pixel32((uint32_t (*)[ROTO_TEXTURE_SIZE])ctxt->fill_fb.buf, x_sin_r - y_cos_r, y_sin_r + x_cos_r);
+
+ x_cos_r += cos_r;
+ x_sin_r += sin_r;
+ }
- x_cos_r += cos_r;
- x_sin_r += sin_r;
+ } else {
+ for (int x = 0; x < fragment->width; x++, buf++) {
+ *buf = bilerp_color(roto_texture, ctxt->palette, x_sin_r - y_cos_r, y_sin_r + x_cos_r);
+
+ x_cos_r += cos_r;
+ x_sin_r += sin_r;
+ }
}
buf += fragment->stride;
@@ -247,10 +408,126 @@ static void roto_render_fragment(til_module_context_t *context, til_stream_t *st
}
+static void roto_setup_free(til_setup_t *setup)
+{
+ roto_setup_t *s = (roto_setup_t *)setup;
+
+ if (s) {
+ til_setup_free(s->fill_module_setup);
+ free(setup);
+ }
+}
+
+
+static int roto_setup(const til_settings_t *settings, til_setting_t **res_setting, const til_setting_desc_t **res_desc, til_setup_t **res_setup)
+{
+ const char *fill_module, *fill_module_name;
+ const til_settings_t *fill_module_settings;
+ til_setting_t *fill_module_setting;
+ const char *fill_module_values[] = {
+ "none",
+ "blinds",
+ "checkers",
+ "moire",
+ "pixbounce",
+ "plato",
+ "roto",
+ "shapes",
+ "spiro",
+ "stars",
+ NULL
+ };
+ int r;
+
+ r = til_settings_get_and_describe_value(settings,
+ &(til_setting_spec_t){
+ .name = "Filled module (\"none\" for classic roto)",
+ .key = "fill_module",
+ .preferred = fill_module_values[0],
+ .values = fill_module_values,
+ .annotations = NULL,
+ .as_nested_settings = 1,
+ },
+ &fill_module, /* XXX: this isn't really of direct use now that it's a potentially full-blown settings string, see fill_module_settings */
+ res_setting,
+ res_desc);
+ if (r)
+ return r;
+
+ assert(res_setting && *res_setting);
+ assert((*res_setting)->value_as_nested_settings);
+
+ fill_module_settings = (*res_setting)->value_as_nested_settings;
+ fill_module_name = til_settings_get_value_by_idx(fill_module_settings, 0, &fill_module_setting);
+
+ if (!fill_module_name || !fill_module_setting->desc) {
+ r = til_setting_desc_new(fill_module_settings,
+ &(til_setting_spec_t){
+ .name = "Fill module name",
+ .preferred = "none",
+ .as_label = 1,
+ },
+ res_desc);
+ if (r < 0)
+ return r;
+
+ *res_setting = fill_module_name ? fill_module_setting : NULL;
+
+ return 1;
+ }
+
+ if (strcasecmp(fill_module_name, "none")) {
+ const til_module_t *mod = til_lookup_module(fill_module_name);
+
+ if (!mod) {
+ *res_setting = fill_module_setting;
+
+ return -EINVAL;
+ }
+
+ if (mod->setup) {
+ r = mod->setup(fill_module_settings, res_setting, res_desc, NULL);
+ if (r)
+ return r;
+ }
+ }
+
+
+
+ if (res_setup) {
+ roto_setup_t *setup;
+
+ setup = til_setup_new(settings, sizeof(*setup), roto_setup_free);
+ if (!setup)
+ return -ENOMEM;
+
+ if (strcasecmp(fill_module_name, "none")) {
+ setup->fill_module = til_lookup_module(fill_module_name);
+ if (!setup->fill_module) {
+ til_setup_free(&setup->til_setup);
+ return -EINVAL;
+ }
+
+ r = til_module_setup_finalize(setup->fill_module, fill_module_settings, &setup->fill_module_setup);
+ if (r < 0) {
+ til_setup_free(&setup->til_setup);
+ return r;
+ }
+ }
+
+ *res_setup = &setup->til_setup;
+ }
+
+ return 0;
+}
+
+
til_module_t roto_module = {
.create_context = roto_create_context,
+ .destroy_context = roto_destroy_context,
.prepare_frame = roto_prepare_frame,
.render_fragment = roto_render_fragment,
+ .setup = roto_setup,
.name = "roto",
.description = "Anti-aliased tiled texture rotation (threaded)",
.author = "Vito Caputo <vcaputo@pengaru.com>",
© All Rights Reserved