From 8e4429853d1ac024ac2b8069636cc210cf5bab1b Mon Sep 17 00:00:00 2001 From: Franklin Wei Date: Fri, 13 Jan 2017 19:11:33 -0500 Subject: puzzles: antialiased line drawing and optional "shortcuts" for undo/redo - line drawing should eventually be moved to xlcd, but for now it's very nonportable code - fixes a minor issue with the configuration screens Change-Id: I897f01b7210cbbec32665c2bc67920c965ea0bec --- apps/plugins/puzzles/puzzles.make | 4 +- apps/plugins/puzzles/rockbox.c | 411 ++++++++++++++++++++++++++++---------- apps/plugins/puzzles/untangle.c | 15 +- 3 files changed, 319 insertions(+), 111 deletions(-) diff --git a/apps/plugins/puzzles/puzzles.make b/apps/plugins/puzzles/puzzles.make index 4052423fb9..f49b663197 100644 --- a/apps/plugins/puzzles/puzzles.make +++ b/apps/plugins/puzzles/puzzles.make @@ -43,8 +43,8 @@ ROCKS += $(PUZZLES_ROCKS) endif # Hack to suppress all warnings: -PUZZLESFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -Os \ - -Wno-unused-parameter -Wno-sign-compare -Wno-strict-aliasing -w \ +PUZZLESFLAGS = $(filter-out -O%,$(PLUGINFLAGS)) -O3 \ + -Wno-unused-parameter -Wno-sign-compare -Wno-strict-aliasing -w \ -DFOR_REAL -I$(PUZZLES_SRCDIR) ifdef PUZZLES_COMBINED PUZZLESFLAGS += -DCOMBINED diff --git a/apps/plugins/puzzles/rockbox.c b/apps/plugins/puzzles/rockbox.c index 3e3bd15f33..2884e4e775 100644 --- a/apps/plugins/puzzles/rockbox.c +++ b/apps/plugins/puzzles/rockbox.c @@ -30,6 +30,7 @@ #include "lib/playback_control.h" #endif #include "lib/xlcd.h" +#include "fixedpoint.h" /* how many ticks between timer callbacks */ #define TIMER_INTERVAL (HZ / 50) @@ -75,7 +76,7 @@ extern bool audiobuf_available; static struct settings_t { int slowmo_factor; - bool bulk, timerflash, clipoff; + bool bulk, timerflash, clipoff, shortcuts, no_aa; } settings; /* clipping is implemented through viewports and offsetting @@ -179,14 +180,129 @@ static void rb_draw_rect(void *handle, int x, int y, int w, int h, int color) rb->lcd_fillrect(x, y, w, h); } +#define SWAP(a, b, t) do { t = a; a = b; b = t; } while(0); + +#define fp_fpart(f, bits) ((f) & ((1 << (bits)) - 1)) +#define fp_rfpart(f, bits) ((1 << (bits)) - fp_fpart(f, bits)) + +#define FRACBITS 16 + +/* most of our time drawing lines is spent in this function! */ +static inline void plot(unsigned x, unsigned y, unsigned long a, + unsigned long r1, unsigned long g1, unsigned long b1, + unsigned cl, unsigned cr, unsigned cu, unsigned cd) +{ + /* This is really quite possibly the least efficient way of doing + this. A better way would be in draw_antialiased_line(), but the + problem with that is that the algorithms I investigated at + least were incorrect at least part of the time and didn't make + drawing much faster overall. */ + if(!(cl <= x && x < cr && cu <= y && y < cd)) + return; + + fb_data *ptr = rb->lcd_framebuffer + y * LCD_WIDTH + x; + fb_data orig = *ptr; + unsigned long r2, g2, b2; + r2 = RGB_UNPACK_RED(orig); + g2 = RGB_UNPACK_GREEN(orig); + b2 = RGB_UNPACK_BLUE(orig); + + unsigned long r, g, b; + r = ((r1 * a) + (r2 * (256 - a))) >> 8; + g = ((g1 * a) + (g2 * (256 - a))) >> 8; + b = ((b1 * a) + (b2 * (256 - a))) >> 8; + + *ptr = LCD_RGBPACK(r, g, b); +} + +#undef ABS +#define ABS(a) ((a)<0?-(a):(a)) + +/* speed benchmark: 34392 lines/sec vs 112687 non-antialiased + * lines/sec at full optimization on ipod6g */ + +/* expects UN-OFFSET coordinates, directly access framebuffer */ +static void draw_antialiased_line(int x0, int y0, int x1, int y1) +{ + /* fixed-point Wu's algorithm, modified for integer-only endpoints */ + + /* passed to plot() to avoid re-calculation */ + unsigned short l = 0, r = LCD_WIDTH, u = 0, d = LCD_HEIGHT; + if(clipped) + { + l = clip_rect.x; + r = clip_rect.x + clip_rect.width; + u = clip_rect.y; + d = clip_rect.y + clip_rect.height; + } + + bool steep = ABS(y1 - y0) > ABS(x1 - x0); + int tmp; + if(steep) + { + SWAP(x0, y0, tmp); + SWAP(x1, y1, tmp); + } + if(x0 > x1) + { + SWAP(x0, x1, tmp); + SWAP(y0, y1, tmp); + } + + int dx, dy; + dx = x1 - x0; + dy = y1 - y0; + + if(!(dx << FRACBITS)) + return; /* bail out */ + + long gradient = fp_div(dy << FRACBITS, dx << FRACBITS, FRACBITS); + long intery = (y0 << FRACBITS); + + unsigned color = rb->lcd_get_foreground(); + unsigned long r1, g1, b1; + r1 = RGB_UNPACK_RED(color); + g1 = RGB_UNPACK_GREEN(color); + b1 = RGB_UNPACK_BLUE(color); + + /* main loop */ + if(steep) + { + for(int x = x0; x <= x1; ++x, intery += gradient) + { + unsigned y = intery >> FRACBITS; + unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8); + + plot(y, x, (1 << 8) - alpha, r1, g1, b1, l, r, u, d); + plot(y + 1, x, alpha, r1, g1, b1, l, r, u, d); + } + } + else + { + for(int x = x0; x <= x1; ++x, intery += gradient) + { + unsigned y = intery >> FRACBITS; + unsigned alpha = fp_fpart(intery, FRACBITS) >> (FRACBITS - 8); + + plot(x, y, (1 << 8) - alpha, r1, g1, b1, l, r, u, d); + plot(x, y + 1, alpha, r1, g1, b1, l, r, u, d); + } + } +} + static void rb_draw_line(void *handle, int x1, int y1, int x2, int y2, int color) { LOGF("rb_draw_line(%d, %d, %d, %d, %d)", x1, y1, x2, y2, color); - offset_coords(&x1, &y1); - offset_coords(&x2, &y2); rb_color(color); - rb->lcd_drawline(x1, y1, x2, y2); + if(settings.no_aa) + { + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + rb->lcd_drawline(x1, y1, x2, y2); + } + else + draw_antialiased_line(x1, y1, x2, y2); } /* @@ -351,12 +467,15 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, y1 = coords[2 * (i - 1) + 1]; x2 = coords[2 * i]; y2 = coords[2 * i + 1]; - offset_coords(&x1, &y1); - offset_coords(&x2, &y2); - rb->lcd_drawline(x1, y1, - x2, y2); - //rb->lcd_update(); - //rb->sleep(HZ/2); + if(settings.no_aa) + { + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); + rb->lcd_drawline(x1, y1, + x2, y2); + } + else + draw_antialiased_line(x1, y1, x2, y2); } int x1, y1, x2, y2; @@ -364,11 +483,16 @@ static void rb_draw_poly(void *handle, int *coords, int npoints, y1 = coords[1]; x2 = coords[2 * (npoints - 1)]; y2 = coords[2 * (npoints - 1) + 1]; - offset_coords(&x1, &y1); - offset_coords(&x2, &y2); + if(settings.no_aa) + { + offset_coords(&x1, &y1); + offset_coords(&x2, &y2); - rb->lcd_drawline(x1, y1, - x2, y2); + rb->lcd_drawline(x1, y1, + x2, y2); + } + else + draw_antialiased_line(x1, y1, x2, y2); } static void rb_draw_circle(void *handle, int cx, int cy, int radius, @@ -394,25 +518,6 @@ struct blitter { struct bitmap bmp; }; -static blitter *rb_blitter_new(void *handle, int w, int h) -{ - LOGF("rb_blitter_new"); - blitter *b = snew(blitter); - b->bmp.width = w; - b->bmp.height = h; - b->bmp.data = smalloc(w * h * sizeof(fb_data)); - b->have_data = false; - return b; -} - -static void rb_blitter_free(void *handle, blitter *bl) -{ - LOGF("rb_blitter_free"); - sfree(bl->bmp.data); - sfree(bl); - return; -} - /* originally from emcc.c */ static void trim_rect(int *x, int *y, int *w, int *h) { @@ -430,10 +535,10 @@ static void trim_rect(int *x, int *y, int *w, int *h) y1 = *y + *h; /* Clip each coordinate at both extremes of the canvas */ - x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH ? LCD_WIDTH : x0); - x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH ? LCD_WIDTH : x1); - y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT ? LCD_HEIGHT : y0); - y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT ? LCD_HEIGHT : y1); + x0 = (x0 < 0 ? 0 : x0 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x0); + x1 = (x1 < 0 ? 0 : x1 > LCD_WIDTH - 1 ? LCD_WIDTH - 1: x1); + y0 = (y0 < 0 ? 0 : y0 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y0); + y1 = (y1 < 0 ? 0 : y1 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1: y1); /* Transform back into x,y,w,h to return */ *x = x0; @@ -442,6 +547,25 @@ static void trim_rect(int *x, int *y, int *w, int *h) *h = y1 - y0; } +static blitter *rb_blitter_new(void *handle, int w, int h) +{ + LOGF("rb_blitter_new"); + blitter *b = snew(blitter); + b->bmp.width = w; + b->bmp.height = h; + b->bmp.data = smalloc(w * h * sizeof(fb_data)); + b->have_data = false; + return b; +} + +static void rb_blitter_free(void *handle, blitter *bl) +{ + LOGF("rb_blitter_free"); + sfree(bl->bmp.data); + sfree(bl); + return; +} + /* copy a section of the framebuffer */ static void rb_blitter_save(void *handle, blitter *bl, int x, int y) { @@ -623,8 +747,6 @@ void get_random_seed(void **randseed, int *randseedsize) *randseed = snew(long); long seed = *rb->current_tick; rb->memcpy(*randseed, &seed, sizeof(seed)); - //*(long*)*randseed = 42; // debug - //rb->splash(HZ, "DEBUG SEED ON"); *randseedsize = sizeof(long); } @@ -683,8 +805,7 @@ static int list_choose(const char *list_str, const char *title) } } -/* return value is only meaningful when type == C_STRING */ -static bool do_configure_item(config_item *cfg) +static void do_configure_item(config_item *cfg) { switch(cfg->type) { @@ -698,11 +819,11 @@ static bool do_configure_item(config_item *cfg) if(rb->kbd_input(newstr, MAX_STRLEN) < 0) { sfree(newstr); - return false; + break; } sfree(cfg->sval); cfg->sval = newstr; - return true; + break; } case C_BOOLEAN: { @@ -726,7 +847,6 @@ static bool do_configure_item(config_item *cfg) fatal("bad type"); break; } - return false; } const char *config_formatter(int sel, void *data, char *buf, size_t len) @@ -737,11 +857,13 @@ const char *config_formatter(int sel, void *data, char *buf, size_t len) return buf; } -static void config_menu(void) +static bool config_menu(void) { char *title; config_item *config = midend_get_config(me, CFG_SETTINGS, &title); + bool success = false; + if(!config) { rb->splash(HZ, "Nothing to configure."); @@ -782,22 +904,16 @@ static void config_menu(void) config_item old; int pos = rb->gui_synclist_get_sel_pos(&list); memcpy(&old, config + pos, sizeof(old)); - char *old_str; - if(old.type == C_STRING) - old_str = dupstr(old.sval); - bool freed_str = do_configure_item(config + pos); + do_configure_item(config + pos); char *err = midend_set_config(me, CFG_SETTINGS, config); if(err) { rb->splash(HZ, err); memcpy(config + pos, &old, sizeof(old)); - if(freed_str) - config[pos].sval = old_str; } - else if(old.type == C_STRING) + else { - /* success, and we duplicated the old string, so free it */ - sfree(old_str); + success = true; } break; } @@ -813,6 +929,7 @@ static void config_menu(void) done: sfree(title); free_cfg(config); + return success; } const char *preset_formatter(int sel, void *data, char *buf, size_t len) @@ -824,12 +941,12 @@ const char *preset_formatter(int sel, void *data, char *buf, size_t len) return buf; } -static void presets_menu(void) +static bool presets_menu(void) { if(!midend_num_presets(me)) { rb->splash(HZ, "No presets!"); - return; + return false; } /* display a list */ @@ -843,9 +960,8 @@ static void presets_menu(void) int current = midend_which_preset(me); rb->gui_synclist_select_item(&list, current >= 0 ? current : 0); - bool done = false; rb->gui_synclist_set_title(&list, "Game Type", NOICON); - while (!done) + while(1) { rb->gui_synclist_draw(&list); int button = rb->get_action(CONTEXT_LIST, TIMEOUT_BLOCK); @@ -860,13 +976,11 @@ static void presets_menu(void) game_params *params; midend_fetch_preset(me, sel, &junk, ¶ms); midend_set_params(me, params); - done = true; - break; + return true; } case ACTION_STD_PREV: case ACTION_STD_CANCEL: - done = true; - break; + return false; default: break; } @@ -948,6 +1062,37 @@ static void init_default_settings(void) settings.slowmo_factor = 1; settings.bulk = false; settings.timerflash = false; + settings.clipoff = false; + settings.shortcuts = false; + settings.no_aa = false; +} + +static void bench_aa(void) +{ + rb->sleep(0); +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(true); +#endif + int next = *rb->current_tick + HZ; + int i = 0; + while(*rb->current_tick < next) + { + draw_antialiased_line(0, 0, 20, 31); + ++i; + } + rb->splashf(HZ, "%d AA lines/sec", i); + next = *rb->current_tick + HZ; + int j = 0; + while(*rb->current_tick < next) + { + rb->lcd_drawline(0, 0, 20, 31); + ++j; + } + rb->splashf(HZ, "%d normal lines/sec", j); + rb->splashf(HZ, "Efficiency: %d%%", 100 * i / j); +#ifdef HAVE_ADJUSTABLE_CPU_FREQ + rb->cpu_boost(false); +#endif } static void debug_menu(void) @@ -958,6 +1103,9 @@ static void debug_menu(void) "Toggle bulk update", "Toggle flash pixel on timer", "Toggle clip", + "Toggle shortcuts", + "Toggle antialias", + "Benchmark antialias", "Back"); bool quit = false; int sel = 0; @@ -988,6 +1136,15 @@ static void debug_menu(void) settings.clipoff = !settings.clipoff; break; case 5: + settings.shortcuts = !settings.shortcuts; + break; + case 6: + settings.no_aa = !settings.no_aa; + break; + case 7: + bench_aa(); + break; + case 8: default: quit = true; break; @@ -1055,6 +1212,23 @@ static int pausemenu_cb(int action, const struct menu_item_ex *this_item) return action; } +static void clear_and_draw(void) +{ + rb->lcd_clear_display(); + rb->lcd_update(); + + midend_force_redraw(me); + draw_title(); +} + +static void reset_drawing(void) +{ + rb->lcd_set_viewport(NULL); + rb->lcd_set_backdrop(NULL); + rb->lcd_set_foreground(LCD_BLACK); + rb->lcd_set_background(BG_COLOR); +} + static int pause_menu(void) { #define static auto @@ -1139,19 +1313,27 @@ static int pause_menu(void) playback_control(NULL); break; case 9: - presets_menu(); - midend_new_game(me); - fix_size(); - quit = true; + if(presets_menu()) + { + midend_new_game(me); + fix_size(); + reset_drawing(); + clear_and_draw(); + quit = true; + } break; case 10: debug_menu(); break; case 11: - config_menu(); - midend_new_game(me); - fix_size(); - quit = true; + if(config_menu()) + { + midend_new_game(me); + fix_size(); + reset_drawing(); + clear_and_draw(); + quit = true; + } break; #ifdef COMBINED case 12: @@ -1257,8 +1439,8 @@ static int process_input(int tmo) } LOGF("accepting event 0x%08x", button); } - /* not inertia: events fire on presses */ - else + /* default is to ignore repeats except for untangle */ + else if(strcmp("Untangle", midend_which_game(me)->name)) { /* start accepting input again after a release */ if(!button) @@ -1267,6 +1449,7 @@ static int process_input(int tmo) return 0; } /* ignore repeats */ + /* Untangle gets special treatment */ if(!accept_input) return 0; accept_input = false; @@ -1316,7 +1499,34 @@ static int process_input(int tmo) case BTN_FIRE: state = CURSOR_SELECT; break; + + default: + break; } + + if(settings.shortcuts) + { + static bool shortcuts_ok = true; + switch(button) + { + case BTN_LEFT | BTN_FIRE: + if(shortcuts_ok) + midend_process_key(me, 0, 0, 'u'); + shortcuts_ok = false; + break; + case BTN_RIGHT | BTN_FIRE: + if(shortcuts_ok) + midend_process_key(me, 0, 0, 'r'); + shortcuts_ok = false; + break; + case 0: + shortcuts_ok = true; + break; + default: + break; + } + } + LOGF("process_input done"); LOGF("------------------"); return state; @@ -1403,13 +1613,23 @@ static void write_wrapper(void *ptr, void *buf, int len) rb->write(fd, buf, len); } -static void clear_and_draw(void) +static void init_colors(void) { - rb->lcd_clear_display(); - rb->lcd_update(); + float *floatcolors = midend_colors(me, &ncolors); - midend_force_redraw(me); - draw_title(); + /* convert them to packed RGB */ + colors = smalloc(ncolors * sizeof(unsigned)); + unsigned *ptr = colors; + float *floatptr = floatcolors; + for(int i = 0; i < ncolors; ++i) + { + int r = 255 * *(floatptr++); + int g = 255 * *(floatptr++); + int b = 255 * *(floatptr++); + LOGF("color %d is %d %d %d", i, r, g, b); + *ptr++ = LCD_RGBPACK(r, g, b); + } + sfree(floatcolors); } static char *init_for_game(const game *gm, int load_fd, bool draw) @@ -1431,26 +1651,9 @@ static char *init_for_game(const game *gm, int load_fd, bool draw) fix_size(); - float *floatcolors = midend_colors(me, &ncolors); + init_colors(); - /* convert them to packed RGB */ - colors = smalloc(ncolors * sizeof(unsigned)); - unsigned *ptr = colors; - float *floatptr = floatcolors; - for(int i = 0; i < ncolors; ++i) - { - int r = 255 * *(floatptr++); - int g = 255 * *(floatptr++); - int b = 255 * *(floatptr++); - LOGF("color %d is %d %d %d", i, r, g, b); - *ptr++ = LCD_RGBPACK(r, g, b); - } - sfree(floatcolors); - - rb->lcd_set_viewport(NULL); - rb->lcd_set_backdrop(NULL); - rb->lcd_set_foreground(LCD_BLACK); - rb->lcd_set_background(BG_COLOR); + reset_drawing(); if(draw) { @@ -1690,22 +1893,26 @@ enum plugin_status plugin_start(const void *param) playback_control(NULL); break; case 5: - presets_menu(); - if(!load_success) + if(presets_menu()) { + midend_new_game(me); + fix_size(); + init_colors(); + reset_drawing(); clear_and_draw(); goto game_loop; } - quit = true; break; case 6: - config_menu(); - if(!load_success) + if(config_menu()) { + midend_new_game(me); + fix_size(); + init_colors(); + reset_drawing(); clear_and_draw(); goto game_loop; } - quit = true; break; case 8: if(load_success) diff --git a/apps/plugins/puzzles/untangle.c b/apps/plugins/puzzles/untangle.c index 839013809b..d46afcbe66 100644 --- a/apps/plugins/puzzles/untangle.c +++ b/apps/plugins/puzzles/untangle.c @@ -1162,18 +1162,17 @@ static char *interpret_move(const game_state *state, game_ui *ui, { if(ui->dragpoint < 0) { - if(ui->cursorpoint < 0) - { - ui->cursorpoint = 0; - return ""; - } - /* We're selecting a point here. */ /* Search all the points and find the closest one (2-D) in * the given direction. */ int i, best; long bestd; + if(ui->cursorpoint < 0) + { + ui->cursorpoint = 0; + } + /* * Begin drag. We drag the vertex _nearest_ to the pointer, * just in case one is nearly on top of another and we want @@ -1196,7 +1195,7 @@ static char *interpret_move(const game_state *state, game_ui *ui, /* Figure out if this point falls into a 90 degree * range extending from the current point */ - float angle = atan2(-dy, dx); /* adjust for raster coordinates */ + float angle = atan2(-dy, dx); /* negate y to adjust for raster coordinates */ /* offset to [0..2*PI] */ if(angle < 0) @@ -1494,6 +1493,8 @@ static void game_redraw(drawing *dr, game_drawstate *ds, ds->bg = bg; game_compute_size(&state->params, ds->tilesize, &w, &h); + + clip(dr, 0, 0, w, h); draw_rect(dr, 0, 0, w, h, bg); /* -- cgit v1.2.3