From 7f8b26c6c317b1a4d020b2bb2569f8b1b552d8e8 Mon Sep 17 00:00:00 2001 From: Vito Caputo Date: Fri, 30 Dec 2016 20:39:40 -0800 Subject: roto: bilinear interpolation of texture colors This implements anti-aliasing, no more jaggies! Still 100% software rendering, fixed point arithmetic. Maybe add zooming with mipmaps next? --- modules/roto/roto.c | 131 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 31 deletions(-) diff --git a/modules/roto/roto.c b/modules/roto/roto.c index 1e06be0..bf92df4 100644 --- a/modules/roto/roto.c +++ b/modules/roto/roto.c @@ -18,6 +18,87 @@ #define FIXED_NEW(_i) ((_i) << FIXED_BITS) #define FIXED_TO_INT(_f) ((_f) >> FIXED_BITS) +typedef struct color_t { + int r, g, b; +} color_t; + + +/* 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) +{ + color_t c = { + .r = a->r + FIXED_MULT(alpha, b->r - a->r), + .g = a->g + FIXED_MULT(alpha, b->g - a->g), + .b = a->b + FIXED_MULT(alpha, b->b - a->b), + }; + + return c; +} + + +/* 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) +{ + uint8_t itx = FIXED_TO_INT(tx), ity = FIXED_TO_INT(ty); + color_t n_color, s_color, color; + int x_alpha, y_alpha; + uint8_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)); + } + } + + n_color = lerp_color(&palette[nw], &palette[ne], x_alpha); + s_color = lerp_color(&palette[sw], &palette[se], 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); +} + static void init_roto(uint8_t texture[256][256], int32_t *costab, int32_t *sintab) { @@ -53,12 +134,11 @@ static void roto32(fb_fragment_t *fragment) static int32_t costab[FIXED_TRIG_LUT_SIZE], sintab[FIXED_TRIG_LUT_SIZE]; static uint8_t texture[256][256]; static int initialized; - static uint32_t colors[2]; + static color_t palette[2]; static unsigned r, rr; int y_cos_r, y_sin_r, x_cos_r, x_sin_r, x_cos_r_init, x_sin_r_init, cos_r, sin_r; int x, y, stride = fragment->stride / 4, width = fragment->width, height = fragment->height; - uint8_t tx, ty; /* 256x256 texture; 8 bit texture indices to modulo via overflow. */ uint32_t *buf = fragment->buf; if (!initialized) { @@ -73,13 +153,13 @@ static void roto32(fb_fragment_t *fragment) sin_r = FIXED_SIN(r); /* Vary the colors, this is just a mashup of sinusoidal rgb values. */ - colors[0] = ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr), FIXED_NEW(127))) + 128) << 16) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127))) + 128) << 8) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr / 3), FIXED_NEW(127))) + 128)); + palette[0].r = (FIXED_MULT(FIXED_COS(rr), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[0].g = (FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[0].b = (FIXED_MULT(FIXED_COS(rr / 3), FIXED_NEW(127)) + FIXED_NEW(128)); - colors[1] = ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127))) + 128) << 16) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr / 2), FIXED_NEW(127))) + 128)) << 8 | - ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr), FIXED_NEW(127))) + 128) ); + palette[1].r = (FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[1].g = (FIXED_MULT(FIXED_COS(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[1].b = (FIXED_MULT(FIXED_SIN(rr), FIXED_NEW(127)) + FIXED_NEW(128)); /* The dimensions are cut in half and negated to center the rotation. */ /* The [xy]_{sin,cos}_r variables are accumulators to replace multiplication with addition. */ @@ -95,11 +175,7 @@ static void roto32(fb_fragment_t *fragment) x_sin_r = x_sin_r_init; for (x = 0; x < width; x++, buf++) { - - tx = FIXED_TO_INT(x_sin_r - y_cos_r); - ty = FIXED_TO_INT(y_sin_r + x_cos_r); - - *buf = colors[texture[ty][tx]]; + *buf = bilerp_color(texture, palette, x_sin_r - y_cos_r, y_sin_r + x_cos_r); x_cos_r += cos_r; x_sin_r += sin_r; @@ -122,12 +198,11 @@ static void roto64(fb_fragment_t *fragment) static int32_t costab[FIXED_TRIG_LUT_SIZE], sintab[FIXED_TRIG_LUT_SIZE]; static uint8_t texture[256][256]; static int initialized; - static uint32_t colors[2]; + static color_t palette[2]; static unsigned r, rr; int y_cos_r, y_sin_r, x_cos_r, x_sin_r, x_cos_r_init, x_sin_r_init, cos_r, sin_r; int x, y, stride = fragment->stride / 8, width = fragment->width, height = fragment->height; - uint8_t tx, ty; /* 256x256 texture; 8 bit texture indices to modulo via overflow. */ uint64_t *buf = (uint64_t *)fragment->buf; if (!initialized) { @@ -142,13 +217,13 @@ static void roto64(fb_fragment_t *fragment) sin_r = FIXED_SIN(r); /* Vary the colors, this is just a mashup of sinusoidal rgb values. */ - colors[0] = ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr), FIXED_NEW(127))) + 128) << 16) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127))) + 128) << 8) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr / 3), FIXED_NEW(127))) + 128)); + palette[0].r = (FIXED_MULT(FIXED_COS(rr), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[0].g = (FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[0].b = (FIXED_MULT(FIXED_COS(rr / 3), FIXED_NEW(127)) + FIXED_NEW(128)); - colors[1] = ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127))) + 128) << 16) | - ((FIXED_TO_INT(FIXED_MULT(FIXED_COS(rr / 2), FIXED_NEW(127))) + 128)) << 8 | - ((FIXED_TO_INT(FIXED_MULT(FIXED_SIN(rr), FIXED_NEW(127))) + 128) ); + palette[1].r = (FIXED_MULT(FIXED_SIN(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[1].g = (FIXED_MULT(FIXED_COS(rr / 2), FIXED_NEW(127)) + FIXED_NEW(128)); + palette[1].b = (FIXED_MULT(FIXED_SIN(rr), FIXED_NEW(127)) + FIXED_NEW(128)); /* The dimensions are cut in half and negated to center the rotation. */ /* The [xy]_{sin,cos}_r variables are accumulators to replace multiplication with addition. */ @@ -168,19 +243,13 @@ static void roto64(fb_fragment_t *fragment) for (x = 0; x < width; x++, buf++) { uint64_t p; - tx = FIXED_TO_INT(x_sin_r - y_cos_r); - ty = FIXED_TO_INT(y_sin_r + x_cos_r); - - p = colors[texture[ty][tx]]; + p = bilerp_color(texture, palette, x_sin_r - y_cos_r, y_sin_r + x_cos_r); x_cos_r += cos_r; x_sin_r += sin_r; - tx = FIXED_TO_INT(x_sin_r - y_cos_r); - ty = FIXED_TO_INT(y_sin_r + x_cos_r); + p |= (uint64_t)(bilerp_color(texture, palette, x_sin_r - y_cos_r, y_sin_r + x_cos_r)) << 32; - p |= (uint64_t)colors[texture[ty][tx]] << 32; - *buf = p; x_cos_r += cos_r; @@ -201,7 +270,7 @@ static void roto64(fb_fragment_t *fragment) rototiller_renderer_t roto32_renderer = { .render = roto32, .name = "roto32", - .description = "Tiled texture rotation (32-bit)", + .description = "Anti-aliased tiled texture rotation (32-bit)", .author = "Vito Caputo ", .license = "GPLv2", }; @@ -210,7 +279,7 @@ rototiller_renderer_t roto32_renderer = { rototiller_renderer_t roto64_renderer = { .render = roto64, .name = "roto64", - .description = "Tiled texture rotation (64-bit)", + .description = "Anti-aliased tiled texture rotation (64-bit)", .author = "Vito Caputo ", .license = "GPLv2", }; -- cgit v1.2.1