diff options
-rw-r--r-- | src/modules/roto/roto.c | 315 |
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>", |