diff options
author | neonloop | 2021-03-21 16:41:44 +0000 |
---|---|---|
committer | neonloop | 2021-03-21 16:41:44 +0000 |
commit | 4c26780f101a12d455ffe900e6a0d5238d1c9552 (patch) | |
tree | 8913a7229c15e1ed597b5a128a08aa554150f674 /frontend | |
parent | 5ef784ab8af08b9629b573b3cfd25f21384771af (diff) | |
download | picogpsp-4c26780f101a12d455ffe900e6a0d5238d1c9552.tar.gz picogpsp-4c26780f101a12d455ffe900e6a0d5238d1c9552.tar.bz2 picogpsp-4c26780f101a12d455ffe900e6a0d5238d1c9552.zip |
Adds a libpicofe frontend and menus
Implements:
- fast-forward (toggle)
- audio-buffer-based frameskip and frame limiting
- color-correction (using the libretro code)
- lcd ghosting (using the libretro code)
- FPS display
- input rebinding
- emulator key rebinding
- scaling (with the old gpsp filters and drowsnug's scaled-nofilter)
- saving and savestates
- game-specific configuration
Not yet implemented:
- cheats
- fast-forward (hold)
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/config.c | 91 | ||||
-rw-r--r-- | frontend/config.h | 9 | ||||
m--------- | frontend/libpicofe | 0 | ||||
-rw-r--r-- | frontend/main.c | 416 | ||||
-rw-r--r-- | frontend/main.h | 70 | ||||
-rw-r--r-- | frontend/menu.c | 335 | ||||
-rw-r--r-- | frontend/menu.h | 10 | ||||
-rw-r--r-- | frontend/plat.h | 19 | ||||
-rw-r--r-- | frontend/plat_linux.c | 258 | ||||
-rw-r--r-- | frontend/plat_trimui.c | 277 | ||||
-rw-r--r-- | frontend/scale.c | 555 | ||||
-rw-r--r-- | frontend/scale.h | 9 |
12 files changed, 2049 insertions, 0 deletions
diff --git a/frontend/config.c b/frontend/config.c new file mode 100644 index 0000000..027fb8e --- /dev/null +++ b/frontend/config.c @@ -0,0 +1,91 @@ +#include "common.h" +#include "frontend/main.h" +#include "frontend/config.h" + +typedef enum { + CE_TYPE_STR = 0, + CE_TYPE_NUM = 4, +} config_entry_type; + +#define CE_STR(val) \ + { #val, CE_TYPE_STRING, val } + +#define CE_NUM(val) \ + { #val, CE_TYPE_NUM, &val } + +static const struct { + const char *name; + config_entry_type type; + void *val; +} config_data[] = { + CE_NUM(dynarec_enable), + CE_NUM(frameskip_style), + CE_NUM(max_frameskip), + CE_NUM(scaling_mode), + CE_NUM(color_correct), + CE_NUM(lcd_blend), + CE_NUM(show_fps), +}; + +void config_write(FILE *f) +{ + for (int i = 0; i < array_size(config_data); i++) { + switch (config_data[i].type) + { + case CE_TYPE_STR: + fprintf(f, "%s = %s\n", config_data[i].name, (char *)config_data[i].val); + break; + case CE_TYPE_NUM: + fprintf(f, "%s = %u\n", config_data[i].name, *(uint32_t *)config_data[i].val); + break; + default: + printf("unhandled type %d for %s\n", config_data[i].type, (char *)config_data[i].val); + break; + } + } +} + +static void parse_str_val(char *cval, const char *src) +{ + char *tmp; + strncpy(cval, src, 256); + cval[256 - 1] = 0; + tmp = strchr(cval, '\n'); + if (tmp == NULL) + tmp = strchr(cval, '\r'); + if (tmp != NULL) + *tmp = 0; +} + +static void parse_num_val(uint32_t *cval, const char *src) +{ + char *tmp = NULL; + uint32_t val; + val = strtoul(src, &tmp, 10); + if (tmp == NULL || src == tmp) + return; // parse failed + + *cval = val; +} + +void config_read(const char* cfg) +{ + for (int i = 0; i < array_size(config_data); i++) { + char *tmp; + + tmp = strstr(cfg, config_data[i].name); + if (tmp == NULL) + continue; + tmp += strlen(config_data[i].name); + if (strncmp(tmp, " = ", 3) != 0) + continue; + tmp += 3; + + if (config_data[i].type == CE_TYPE_STR) { + parse_str_val(config_data[i].val, tmp); + continue; + } + + parse_num_val(config_data[i].val, tmp); + } +} diff --git a/frontend/config.h b/frontend/config.h new file mode 100644 index 0000000..d45e435 --- /dev/null +++ b/frontend/config.h @@ -0,0 +1,9 @@ +#ifndef FRONTEND_CONFIG_H +#define FRONTEND_CONFIG_H + +#include <stdio.h> + +void config_write(FILE *f); +void config_read(const char *cfg); + +#endif diff --git a/frontend/libpicofe b/frontend/libpicofe new file mode 160000 +Subproject 76e45c3489a0c32d91744413c198f685b553f6c diff --git a/frontend/main.c b/frontend/main.c new file mode 100644 index 0000000..49f1cb2 --- /dev/null +++ b/frontend/main.c @@ -0,0 +1,416 @@ +#include <stdio.h> +#include <string.h> +#include "common.h" +#include "main.h" +#include "memmap.h" +#include "frontend/menu.h" +#include "frontend/plat.h" +#include "frontend/libpicofe/plat.h" + +int should_quit = 0; + +u32 idle_loop_target_pc = 0xFFFFFFFF; +u32 iwram_stack_optimize = 1; +u32 translation_gate_target_pc[MAX_TRANSLATION_GATES]; +u32 translation_gate_targets = 0; + +uint16_t *gba_screen_pixels_prev = NULL; +uint16_t *gba_processed_pixels = NULL; + +int use_libretro_save_method = 0; + +u32 skip_next_frame = 0; + +int dynarec_enable; +int state_slot; +frameskip_style_t frameskip_style; +scaling_mode_t scaling_mode; +int max_frameskip; +int color_correct; +int lcd_blend; +int show_fps; +int limit_frames; + +static float vsyncsps = 0.0; +static float rendersps = 0.0; + +void quit(); + +void gamepak_related_name(char *buf, size_t len, char *new_extension) +{ + char root_dir[512]; + char filename[512]; + char *p; + + plat_get_root_dir(root_dir, len); + p = strrchr(gamepak_filename, PATH_SEPARATOR_CHAR); + + if (p) + p++; + else + p = gamepak_filename; + strncpy(filename, p, sizeof(filename)); + filename[sizeof(filename) - 1] = 0; + p = strrchr(filename, '.'); + if (p) + *p = 0; + + snprintf(buf, len, "%s%s%s", root_dir, filename, new_extension); +} + +void toggle_fast_forward(int force_off) +{ + static frameskip_style_t frameskip_style_was; + static int max_frameskip_was; + static int limit_frames_was; + static int global_process_audio_was; + static int fast_forward; + + if (force_off && !fast_forward) + return; + + fast_forward = !fast_forward; + + if (fast_forward) { + frameskip_style_was = frameskip_style; + max_frameskip_was = max_frameskip; + limit_frames_was = limit_frames; + global_process_audio_was = global_process_audio; + + frameskip_style = FRAMESKIP_MANUAL; + max_frameskip = 5; + limit_frames = 0; + global_process_audio = 0; + } else { + frameskip_style = frameskip_style_was; + max_frameskip = max_frameskip_was; + limit_frames = limit_frames_was; + global_process_audio = global_process_audio_was; + } +} + +void state_file_name(char *buf, size_t len, unsigned state_slot) +{ + char ext[20]; + snprintf(ext, sizeof(ext), ".st%d", state_slot); + + gamepak_related_name(buf, len, ext); +} + +void config_file_name(char *buf, size_t len, int is_game) +{ + char root_dir[MAXPATHLEN]; + + if (is_game) { + gamepak_related_name(buf, len, ".cfg"); + } else { + plat_get_root_dir(root_dir, MAXPATHLEN); + snprintf(buf, len, "%s%s", root_dir, "picogpsp.cfg"); + } +} + +void handle_emu_action(emu_action action) +{ + static frameskip_style_t prev_frameskip_style; + static emu_action prev_action = EACTION_NONE; + if (prev_action != EACTION_NONE && prev_action == action) return; + + switch (action) + { + case EACTION_NONE: + break; + case EACTION_QUIT: + should_quit = 1; + break; + case EACTION_TOGGLE_FPS: + show_fps = !show_fps; + /* Force the hud to clear */ + plat_video_set_msg(" "); + break; + case EACTION_SAVE_STATE: + save_state_file(0); + break; + case EACTION_LOAD_STATE: + load_state_file(0); + break; + case EACTION_TOGGLE_FSKIP: + if (prev_frameskip_style == FRAMESKIP_NONE) + prev_frameskip_style = FRAMESKIP_AUTO; + + if (frameskip_style == FRAMESKIP_NONE) { + frameskip_style = prev_frameskip_style; + } else { + prev_frameskip_style = frameskip_style; + frameskip_style = FRAMESKIP_NONE; + } + break; + case EACTION_TOGGLE_FF: + toggle_fast_forward(0); + break; + case EACTION_MENU: + toggle_fast_forward(1); + update_backup(); + menu_loop(); + break; + default: + break; + } + + prev_action = action; +} + +void synchronize(void) +{ + static uint32_t vsyncs = 0; + static uint32_t renders = 0; + static uint32_t nextsec = 0; + static uint32_t skipped_frames = 0; + unsigned int ticks = 0; + + float capacity = plat_sound_capacity(); + + switch (frameskip_style) + { + case FRAMESKIP_AUTO: + skip_next_frame = 0; + + if (capacity > 0.5) { + skip_next_frame = 1; + skipped_frames++; + } + break; + case FRAMESKIP_MANUAL: + skip_next_frame = 1; + skipped_frames++; + break; + default: + skip_next_frame = 0; + break; + } + + if (skipped_frames > max_frameskip) { + skip_next_frame = 0; + skipped_frames = 0; + } + + while (limit_frames && capacity < 0.1) { + plat_sleep_ms(1); + capacity = plat_sound_capacity(); + } + + if (show_fps) { + ticks = plat_get_ticks_ms(); + if (ticks > nextsec) { + vsyncsps = vsyncs; + rendersps = renders; + vsyncs = 0; + renders = 0; + nextsec = ticks + 1000; + } + vsyncs++; + if (!skip_next_frame) renders++; + } +} + +void print_hud() +{ + char msg[HUD_LEN]; + if (show_fps) { + snprintf(msg, HUD_LEN, "FPS: %2.0f (%4.1f)", rendersps, vsyncsps); + plat_video_set_msg(msg); + } +} + +int save_state_file(unsigned state_slot) +{ + char state_filename[MAXPATHLEN]; + void *data; + FILE *f; + int ret = 0; + state_file_name(state_filename, MAXPATHLEN, state_slot); + + f = fopen(state_filename, "wb"); + + if (!f) + return -1; + + data = calloc(1, GBA_STATE_MEM_SIZE); + if (!data) { + ret = -1; + goto fail; + } + + gba_save_state(data); + + if (fwrite(data, 1, GBA_STATE_MEM_SIZE, f) != GBA_STATE_MEM_SIZE) { + ret = -1; + goto fail; + } + +fail: + if (data) + free(data); + if (f) + fclose(f); + + return ret; +} + +int load_state_file(unsigned state_slot) +{ + char state_filename[MAXPATHLEN]; + void *data; + FILE *f; + int ret = 0; + state_file_name(state_filename, MAXPATHLEN, state_slot); + + f = fopen(state_filename, "rb"); + + if (!f) + return -1; + + data = calloc(1, GBA_STATE_MEM_SIZE); + if (!data) { + ret = -1; + goto fail; + } + + + if (fread(data, 1, GBA_STATE_MEM_SIZE, f) != GBA_STATE_MEM_SIZE) { + ret = -1; + goto fail; + } + + gba_load_state(data); + +fail: + if (data) + free(data); + if (f) + fclose(f); + + return ret; +} + +int main(int argc, char *argv[]) +{ + char bios_filename[MAXPATHLEN]; + char filename[MAXPATHLEN]; + char path[MAXPATHLEN]; + + if (argc < 2) { + printf("Usage: picogpsp FILE"); + return 0; + }; + + strncpy(filename, argv[1], MAXPATHLEN); + if (filename[0] != '/') { + getcwd(path, MAXPATHLEN); + if (strlen(path) + strlen(filename) + 1 < MAXPATHLEN) { + strcat(path, "/"); + strcat(path, filename); + strcpy(filename, path); + } else + filename[0] = 0; + } + + getcwd(bios_filename, MAXPATHLEN); + strncat(bios_filename, "/gba_bios.bin", MAXPATHLEN - strlen(bios_filename)); + if (load_bios(bios_filename)) { + fprintf(stderr, "Could not load BIOS image file %s.\n", bios_filename); + return -1; + } + + plat_get_root_dir(save_path, 512); + + if (!gamepak_rom) + init_gamepak_buffer(); + + if(!gba_screen_pixels) + gba_screen_pixels = (uint16_t*)calloc(GBA_SCREEN_PITCH * GBA_SCREEN_HEIGHT, sizeof(uint16_t)); + + if (plat_init()) { + return -1; + }; + + if (load_gamepak(filename) != 0) + { + fprintf(stderr, "Could not load the game file.\n"); + return -1; + } + + init_main(); + init_sound(1); + menu_init(); + + #if defined(HAVE_DYNAREC) + if (dynarec_enable) + { +#ifdef HAVE_MMAP + rom_translation_cache = mmap(NULL, ROM_TRANSLATION_CACHE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); + ram_translation_cache = mmap(NULL, RAM_TRANSLATION_CACHE_SIZE, + PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0); +#endif + } + else + dynarec_enable = 0; +#else + dynarec_enable = 0; +#endif + + reset_gba(); + + do { + update_input(); + + synchronize(); + +#ifdef HAVE_DYNAREC + if (dynarec_enable) + execute_arm_translate(execute_cycles); + else +#endif + execute_arm(execute_cycles); + + render_audio(); + + print_hud(); + + if (!skip_next_frame) + plat_video_flip(); + } while (!should_quit); + + quit(); + return 0; +} + +void quit() +{ + update_backup(); + + memory_term(); + + if (gba_screen_pixels_prev) { + free(gba_screen_pixels_prev); + gba_screen_pixels_prev = NULL; + } + + if (gba_processed_pixels) { + free(gba_processed_pixels); + gba_processed_pixels = NULL; + } + + free(gba_screen_pixels); + gba_screen_pixels = NULL; + +#if defined(HAVE_MMAP) && defined(HAVE_DYNAREC) + munmap(rom_translation_cache, ROM_TRANSLATION_CACHE_SIZE); + munmap(ram_translation_cache, RAM_TRANSLATION_CACHE_SIZE); +#endif + + menu_finish(); + plat_finish(); + + exit(0); +} diff --git a/frontend/main.h b/frontend/main.h new file mode 100644 index 0000000..d039c3b --- /dev/null +++ b/frontend/main.h @@ -0,0 +1,70 @@ +#ifndef __FRONTEND_MAIN_H__ +#define __FRONTEND_MAIN_H__ + +#include <stddef.h> + +#define MAXPATHLEN 512 + +typedef enum { + EACTION_NONE = 0, + EACTION_MENU, + EACTION_TOGGLE_FPS, + EACTION_TOGGLE_FSKIP, + EACTION_TOGGLE_FF, + EACTION_SAVE_STATE, + EACTION_LOAD_STATE, + EACTION_QUIT, +} emu_action; + +typedef enum { + KBIT_A = 0, + KBIT_B, + KBIT_SELECT, + KBIT_START, + KBIT_RIGHT, + KBIT_LEFT, + KBIT_UP, + KBIT_DOWN, + KBIT_R, + KBIT_L +} keybit; + +typedef enum { + FRAMESKIP_NONE = 0, + FRAMESKIP_AUTO, + FRAMESKIP_MANUAL, +} frameskip_style_t; + +typedef enum { + SCALING_NONE = 0, + SCALING_ASPECT_SHARP, + SCALING_ASPECT_SMOOTH, + SCALING_FULL_SHARP, + SCALING_FULL_SMOOTH, +} scaling_mode_t; + + +extern int should_quit; + +extern int state_slot; +extern int dynarec_enable; +extern frameskip_style_t frameskip_style; +extern scaling_mode_t scaling_mode; +extern int max_frameskip; +extern int color_correct; +extern int lcd_blend; +extern int show_fps; +extern int limit_frames; + +extern uint16_t *gba_screen_pixels_prev; +extern uint16_t *gba_processed_pixels; + +#define array_size(x) (sizeof(x) / sizeof(x[0])) + +void state_file_name(char *buf, size_t len, unsigned state_slot); +void config_file_name(char *buf, size_t len, int is_game); +void handle_emu_action(emu_action action); +int save_state_file(unsigned state_slot); +int load_state_file(unsigned state_slot); + +#endif /* __FRONTEND_MAIN_H__ */ diff --git a/frontend/menu.c b/frontend/menu.c new file mode 100644 index 0000000..3ee0a93 --- /dev/null +++ b/frontend/menu.c @@ -0,0 +1,335 @@ +#include <sys/stat.h> +#include "common.h" + +#include "frontend/config.h" +#include "frontend/main.h" +#include "frontend/menu.h" +#include "frontend/scale.h" +#include "frontend/plat.h" + +#include "frontend/libpicofe/config_file.h" + +#define MENU_ALIGN_LEFT 0 +#define MENU_X2 0 + +typedef enum +{ + MA_NONE = 1, + MA_MAIN_RESUME_GAME, + MA_MAIN_SAVE_STATE, + MA_MAIN_LOAD_STATE, + MA_MAIN_RESET_GAME, + MA_MAIN_CREDITS, + MA_MAIN_EXIT, + MA_OPT_SAVECFG, + MA_OPT_SAVECFG_GAME, + MA_CTRL_PLAYER1, + MA_CTRL_EMU, +} menu_id; + +int menu_save_config(int is_game); +void menu_set_defaults(void); + +me_bind_action me_ctrl_actions[] = +{ + { "UP ", 1 << KBIT_UP}, + { "DOWN ", 1 << KBIT_DOWN }, + { "LEFT ", 1 << KBIT_LEFT }, + { "RIGHT ", 1 << KBIT_RIGHT }, + { "A BUTTON ", 1 << KBIT_A }, + { "B BUTTON ", 1 << KBIT_B }, + { "START ", 1 << KBIT_START }, + { "SELECT ", 1 << KBIT_SELECT }, + { "L BUTTON ", 1 << KBIT_L }, + { "R BUTTON ", 1 << KBIT_R }, + { NULL, 0 } +}; + +me_bind_action emuctrl_actions[] = +{ + { "Save State ", 1 << EACTION_SAVE_STATE }, + { "Load State ", 1 << EACTION_LOAD_STATE }, + { "Toggle Frameskip ", 1 << EACTION_TOGGLE_FSKIP }, + { "Show/Hide FPS ", 1 << EACTION_TOGGLE_FPS }, + { "Toggle FF ", 1 << EACTION_TOGGLE_FF }, + { "Enter Menu ", 1 << EACTION_MENU }, + { NULL, 0 } +}; + +int emu_check_save_file(int slot, int *time) +{ + char fname[MAXPATHLEN]; + struct stat status; + int ret; + + state_file_name(fname, sizeof(fname), slot); + + ret = stat(fname, &status); + if (ret != 0) + return 0; + + return 1; +} + +int emu_save_load_game(int load, int unused) +{ + int ret; + + if (load) + ret = load_state_file(state_slot); + else + ret = save_state_file(state_slot); + + return ret; +} + +#include "frontend/libpicofe/menu.c" + +static const char *mgn_saveloadcfg(int id, int *offs) +{ + return ""; +} + +static int mh_restore_defaults(int id, int keys) +{ + menu_set_defaults(); + menu_update_msg("defaults restored"); + return 1; +} + +static int mh_savecfg(int id, int keys) +{ + if (menu_save_config(id == MA_OPT_SAVECFG_GAME ? 1 : 0) == 0) + menu_update_msg("config saved"); + else + menu_update_msg("failed to write config"); + + return 1; +} + +static const char h_restore_def[] = "Switches back to default / recommended\n" + "configuration"; + +static const char h_color_correct[] = "Modifies colors to simulate original display"; +static const char h_lcd_blend[] = "Blends frames to simulate LCD lag"; +static const char h_show_fps[] = "Shows frames and vsyncs per second"; +static const char h_dynarec_enable[] = "Improves performance, but may reduce accuracy"; + + +static const char *men_frameskip[] = { "OFF", "Auto", "Manual", NULL }; + +static const char *men_scaling[] = { "Native", "3:2 Sharp", "3:2 Smooth", "4:3 Sharp", "4:3 Smooth", NULL}; + +static menu_entry e_menu_options[] = +{ + mee_enum ("Frameskip", 0, frameskip_style, men_frameskip), + mee_range ("Max Frameskip", 0, max_frameskip, 1, 5), + mee_enum ("Scaling", 0, scaling_mode, men_scaling), + mee_onoff_h ("Color Correction", 0, color_correct, 1, h_color_correct), + mee_onoff_h ("LCD Ghosting", 0, lcd_blend, 1, h_lcd_blend), + mee_onoff_h ("Dynamic Recompiler", 0, dynarec_enable, 1, h_dynarec_enable), + mee_onoff_h ("Show FPS", 0, show_fps, 1, h_show_fps), + mee_cust_nosave ("Save global config", MA_OPT_SAVECFG, mh_savecfg, mgn_saveloadcfg), + mee_cust_nosave ("Save game config", MA_OPT_SAVECFG_GAME, mh_savecfg, mgn_saveloadcfg), + mee_handler_h ("Restore defaults", mh_restore_defaults, h_restore_def), + mee_end, +}; + +static int menu_loop_options(int id, int keys) +{ + static int sel = 0; + int prev_dynarec_enable = dynarec_enable; + + me_loop(e_menu_options, &sel); + + if (prev_dynarec_enable != dynarec_enable) + wipe_caches(); + + return 0; +} + +static int key_config_loop_wrap(int id, int keys) +{ + switch (id) { + case MA_CTRL_PLAYER1: + key_config_loop(me_ctrl_actions, array_size(me_ctrl_actions) - 1, 0); + break; + case MA_CTRL_EMU: + key_config_loop(emuctrl_actions, array_size(emuctrl_actions) - 1, -1); + break; + default: + break; + } + return 0; +} + +static menu_entry e_menu_keyconfig[] = +{ + mee_handler_id ("Player controls", MA_CTRL_PLAYER1, key_config_loop_wrap), + mee_handler_id ("Emulator controls", MA_CTRL_EMU, key_config_loop_wrap), + mee_cust_nosave ("Save global config", MA_OPT_SAVECFG, mh_savecfg, mgn_saveloadcfg), + mee_cust_nosave ("Save game config", MA_OPT_SAVECFG_GAME, mh_savecfg, mgn_saveloadcfg), + mee_end, +}; + +static int menu_loop_keyconfig(int id, int keys) +{ + static int sel = 0; + me_loop(e_menu_keyconfig, &sel); + return 0; +} + + +static int main_menu_handler(int id, int keys) +{ + switch (id) + { + case MA_MAIN_RESUME_GAME: + return 1; + case MA_MAIN_SAVE_STATE: + return menu_loop_savestate(0); + case MA_MAIN_LOAD_STATE: + return menu_loop_savestate(1); + case MA_MAIN_RESET_GAME: + reset_gba(); + return 1; + case MA_MAIN_EXIT: + should_quit = 1; + return 1; + default: + lprintf("%s: something unknown selected\n", __FUNCTION__); + break; + } + + return 0; +} + +static menu_entry e_menu_main[] = +{ + mee_handler_id("Resume game", MA_MAIN_RESUME_GAME, main_menu_handler), + mee_handler_id("Save State", MA_MAIN_SAVE_STATE, main_menu_handler), + mee_handler_id("Load State", MA_MAIN_LOAD_STATE, main_menu_handler), + mee_handler_id("Reset game", MA_MAIN_RESET_GAME, main_menu_handler), + mee_handler ("Options", menu_loop_options), + mee_handler ("Controls", menu_loop_keyconfig), + /* mee_handler_id("Cheats", MA_MAIN_CHEATS, main_menu_handler), */ + mee_handler_id("Exit", MA_MAIN_EXIT, main_menu_handler), + mee_end, +}; + +void draw_savestate_bg(int slot) +{ +} + +void menu_set_defaults(void) +{ + dynarec_enable = 1; + frameskip_style = 1; + scaling_mode = 0; + max_frameskip = 3; + color_correct = 0; + lcd_blend = 0; + show_fps = 0; + limit_frames = 1; +} + +void menu_loop(void) +{ + static int sel = 0; + plat_video_menu_enter(1); + video_scale(g_menubg_ptr, g_menuscreen_h, g_menuscreen_pp); + menu_darken_bg(g_menubg_ptr, g_menubg_ptr, g_menuscreen_h * g_menuscreen_pp, 0); + me_loop_d(e_menu_main, &sel, NULL, NULL); + + /* wait until menu, ok, back is released */ + while (in_menu_wait_any(NULL, 50) & (PBTN_MENU|PBTN_MOK|PBTN_MBACK)) + ; + memset(g_menubg_ptr, 0, g_menuscreen_h * g_menuscreen_pp * sizeof(uint16_t)); + + plat_video_menu_leave(); +} + +int menu_save_config(int is_game) +{ + char config_filename[MAXPATHLEN]; + FILE *config_file; + + config_file_name(config_filename, MAXPATHLEN, is_game); + config_file = fopen(config_filename, "wb"); + if (!config_file) { + fprintf(stderr, "Could not write config to %s\n", config_filename); + return -1; + } + + config_write(config_file); + config_write_keys(config_file); + + fclose(config_file); + return 0; +} + + +void menu_load_config(void) +{ + char config_filename[MAXPATHLEN]; + FILE *config_file; + size_t length; + char *config; + + config_file_name(config_filename, MAXPATHLEN, 1); + config_file = fopen(config_filename, "rb"); + if (!config_file) { + config_file_name(config_filename, MAXPATHLEN, 0); + config_file = fopen(config_filename, "rb"); + } + + if (!config_file) + return; + + fseek(config_file, 0, SEEK_END); + length = ftell(config_file); + fseek(config_file, 0, SEEK_SET); + + config = calloc(1, length); + + if (fread(config, 1, length, config_file)) + fclose(config_file); + + config_read(config); + config_read_keys(config); + + if (config) + free(config); +} + +void menu_init(void) +{ + menu_init_base(); + + menu_set_defaults(); + menu_load_config(); + + g_menubg_src_ptr = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1); + g_menubg_ptr = calloc(g_menuscreen_w * g_menuscreen_h * 2, 1); + if (g_menubg_src_ptr == NULL || g_menubg_ptr == NULL) { + fprintf(stderr, "OOM\n"); + exit(1); + } +} + +void menu_finish(void) +{ +} + +static void debug_menu_loop(void) +{ +} + +void menu_update_msg(const char *msg) +{ + strncpy(menu_error_msg, msg, sizeof(menu_error_msg)); + menu_error_msg[sizeof(menu_error_msg) - 1] = 0; + + menu_error_time = plat_get_ticks_ms(); + lprintf("msg: %s\n", menu_error_msg); +} diff --git a/frontend/menu.h b/frontend/menu.h new file mode 100644 index 0000000..e151098 --- /dev/null +++ b/frontend/menu.h @@ -0,0 +1,10 @@ +#ifndef _FRONTEND_MENU_H__ +#define _FRONTEND_MENU_H__ + +#include "frontend/libpicofe/menu.h" + +void menu_init(void); +void menu_loop(void); +void menu_finish(void); + +#endif diff --git a/frontend/plat.h b/frontend/plat.h new file mode 100644 index 0000000..dc4be73 --- /dev/null +++ b/frontend/plat.h @@ -0,0 +1,19 @@ +#ifndef __FRONTEND_PLAT_H__ +#define __FRONTEND_PLAT_H__ + +#define HUD_LEN 39 + +int plat_init(void); +void plat_finish(void); +void plat_minimize(void); +void *plat_prepare_screenshot(int *w, int *h, int *bpp); + +void plat_video_open(void); +void plat_video_set_msg(char *new_msg); +void plat_video_flip(void); +void plat_video_close(void); + +float plat_sound_capacity(void); +void plat_sound_write(void *data, int bytes); + +#endif /* __FRONTEND_PLAT_H__ */ diff --git a/frontend/plat_linux.c b/frontend/plat_linux.c new file mode 100644 index 0000000..38e28cc --- /dev/null +++ b/frontend/plat_linux.c @@ -0,0 +1,258 @@ +#include <SDL/SDL.h> +#include "common.h" + +#include "frontend/main.h" +#include "frontend/plat.h" +#include "frontend/scale.h" +#include "frontend/libpicofe/menu.h" +#include "frontend/libpicofe/plat.h" +#include "frontend/libpicofe/input.h" +#include "frontend/libpicofe/in_sdl.h" + +static SDL_Surface* screen; + +#define BUF_LEN 16384 +static short buf[BUF_LEN]; +static int buf_w, buf_r; + +static char msg[HUD_LEN]; + +static const struct in_default_bind in_sdl_defbinds[] = { + { SDLK_UP, IN_BINDTYPE_PLAYER12, KBIT_UP }, + { SDLK_DOWN, IN_BINDTYPE_PLAYER12, KBIT_DOWN }, + { SDLK_LEFT, IN_BINDTYPE_PLAYER12, KBIT_LEFT }, + { SDLK_RIGHT, IN_BINDTYPE_PLAYER12, KBIT_RIGHT }, + { SDLK_LCTRL, IN_BINDTYPE_PLAYER12, KBIT_B }, + { SDLK_SPACE, IN_BINDTYPE_PLAYER12, KBIT_A }, + { SDLK_RETURN, IN_BINDTYPE_PLAYER12, KBIT_START }, + { SDLK_RCTRL, IN_BINDTYPE_PLAYER12, KBIT_SELECT }, + { SDLK_TAB, IN_BINDTYPE_PLAYER12, KBIT_L }, + { SDLK_BACKSPACE, IN_BINDTYPE_PLAYER12, KBIT_R }, + { SDLK_ESCAPE, IN_BINDTYPE_EMU, EACTION_MENU }, + { 0, 0, 0 } +}; + +const struct menu_keymap in_sdl_key_map[] = +{ + { SDLK_UP, PBTN_UP }, + { SDLK_DOWN, PBTN_DOWN }, + { SDLK_LEFT, PBTN_LEFT }, + { SDLK_RIGHT, PBTN_RIGHT }, + { SDLK_SPACE, PBTN_MOK }, + { SDLK_LCTRL, PBTN_MBACK }, + { SDLK_LALT, PBTN_MA2 }, + { SDLK_LSHIFT, PBTN_MA3 }, + { SDLK_TAB, PBTN_L }, + { SDLK_BACKSPACE, PBTN_R }, +}; + +const struct menu_keymap in_sdl_joy_map[] = +{ + { SDLK_UP, PBTN_UP }, + { SDLK_DOWN, PBTN_DOWN }, + { SDLK_LEFT, PBTN_LEFT }, + { SDLK_RIGHT, PBTN_RIGHT }, + { SDLK_WORLD_0, PBTN_MOK }, + { SDLK_WORLD_1, PBTN_MBACK }, + { SDLK_WORLD_2, PBTN_MA2 }, + { SDLK_WORLD_3, PBTN_MA3 }, +}; + +static const struct in_pdata in_sdl_platform_data = { + .defbinds = in_sdl_defbinds, + .key_map = in_sdl_key_map, + .kmap_size = array_size(in_sdl_key_map), + .joy_map = in_sdl_joy_map, + .jmap_size = array_size(in_sdl_joy_map), +}; + +static void *fb_flip(void) +{ + SDL_Flip(screen); + return screen->pixels; +} + +void plat_video_menu_enter(int is_rom_loaded) +{ +} + +void plat_video_menu_begin(void) +{ + g_menuscreen_ptr = fb_flip(); +} + +void plat_video_menu_end(void) +{ + g_menuscreen_ptr = fb_flip(); +} + +void plat_video_menu_leave(void) +{ + SDL_LockSurface(screen); + memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t)); + SDL_UnlockSurface(screen); + g_menuscreen_ptr = fb_flip(); + SDL_LockSurface(screen); + memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t)); + SDL_UnlockSurface(screen); +} + +void plat_video_open(void) +{ +} + +void plat_video_set_msg(char *new_msg) +{ + snprintf(msg, HUD_LEN, "%s", new_msg); +} + +void plat_video_flip(void) +{ + video_post_process(); + SDL_LockSurface(screen); + if (msg[0]) + video_clear_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t)); + + video_scale(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t)); + + if (msg[0]) + video_print_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t), msg); + SDL_UnlockSurface(screen); + + g_menuscreen_ptr = fb_flip(); + msg[0] = 0; +} + +void plat_video_close(void) +{ +} + +void plat_sound_callback(void *unused, u8 *stream, int len) +{ + short *p = (short *)stream; + len /= sizeof(short); + + while (buf_r != buf_w && len > 0) { + *p++ = buf[buf_r++]; + buf_r %= BUF_LEN; + --len; + } + + while(len > 0) { + *p++ = 0; + --len; + } +} + +void plat_sound_finish(void) +{ + SDL_PauseAudio(1); + SDL_CloseAudio(); +} + +int plat_sound_init(void) +{ + + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { + return -1; + } + + SDL_AudioSpec spec; + + spec.freq = sound_frequency; + spec.format = AUDIO_S16; + spec.channels = 2; + spec.samples = 512; + spec.callback = plat_sound_callback; + + if (SDL_OpenAudio(&spec, NULL) < 0) { + plat_sound_finish(); + return -1; + } + + SDL_PauseAudio(0); + return 0; +} + +float plat_sound_capacity(void) +{ + int buffered = 0; + if (buf_w != buf_r) { + buffered = buf_w > buf_r ? buf_w - buf_r : (buf_w + BUF_LEN) - buf_r; + } + + return 1.0 - (float)buffered / BUF_LEN; +} + +void plat_sound_write(void *data, int bytes) +{ + short *sound_data = (short *)data; + SDL_LockAudio(); + + while (bytes > 0) { + while (((buf_w + 1) % BUF_LEN) == buf_r) { + SDL_UnlockAudio(); + + if (!limit_frames) + return; + + plat_sleep_ms(1); + SDL_LockAudio(); + } + + buf[buf_w] = *sound_data++; + + ++buf_w; + buf_w %= BUF_LEN; + bytes -= sizeof(short); + } + SDL_UnlockAudio(); +} + +void plat_sdl_event_handler(void *event_) +{ +} + +int plat_init(void) +{ + SDL_Init(SDL_INIT_VIDEO); + screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE); + + if (screen == NULL) { + printf("%s, failed to set video mode\n", __func__); + return -1; + } + + SDL_ShowCursor(0); + + g_menuscreen_w = 320; + g_menuscreen_h = 240; + g_menuscreen_pp = 320; + g_menuscreen_ptr = fb_flip(); + + if (in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler)) { + fprintf(stderr, "SDL input failed to init: %s\n", SDL_GetError()); + return -1; + } + in_probe(); + + if (plat_sound_init()) { + fprintf(stderr, "SDL sound failed to init: %s\n", SDL_GetError()); + return -1; + } + return 0; +} + +void plat_pre_finish(void) +{ +} + +void plat_finish(void) +{ + plat_sound_finish(); + SDL_Quit(); +} + +void plat_trigger_vibrate(int pad, int low, int high) +{ +} diff --git a/frontend/plat_trimui.c b/frontend/plat_trimui.c new file mode 100644 index 0000000..988a433 --- /dev/null +++ b/frontend/plat_trimui.c @@ -0,0 +1,277 @@ +#include <SDL/SDL.h> +#include "common.h" + +#include "frontend/main.h" +#include "frontend/plat.h" +#include "frontend/scale.h" +#include "frontend/libpicofe/menu.h" +#include "frontend/libpicofe/plat.h" +#include "frontend/libpicofe/input.h" +#include "frontend/libpicofe/in_sdl.h" + +static SDL_Surface* screen; + +#define BUF_LEN 16384 + +static short buf[BUF_LEN]; +static int buf_w, buf_r; + +static char msg[HUD_LEN]; + +static const struct in_default_bind in_sdl_defbinds[] = { + { SDLK_UP, IN_BINDTYPE_PLAYER12, KBIT_UP }, + { SDLK_DOWN, IN_BINDTYPE_PLAYER12, KBIT_DOWN }, + { SDLK_LEFT, IN_BINDTYPE_PLAYER12, KBIT_LEFT }, + { SDLK_RIGHT, IN_BINDTYPE_PLAYER12, KBIT_RIGHT }, + { SDLK_LCTRL, IN_BINDTYPE_PLAYER12, KBIT_B }, + { SDLK_SPACE, IN_BINDTYPE_PLAYER12, KBIT_A }, + { SDLK_RETURN, IN_BINDTYPE_PLAYER12, KBIT_START }, + { SDLK_RCTRL, IN_BINDTYPE_PLAYER12, KBIT_SELECT }, + { SDLK_TAB, IN_BINDTYPE_PLAYER12, KBIT_L }, + { SDLK_BACKSPACE, IN_BINDTYPE_PLAYER12, KBIT_R }, + { SDLK_ESCAPE, IN_BINDTYPE_EMU, EACTION_MENU }, + { 0, 0, 0 } +}; + +const struct menu_keymap in_sdl_key_map[] = +{ + { SDLK_UP, PBTN_UP }, + { SDLK_DOWN, PBTN_DOWN }, + { SDLK_LEFT, PBTN_LEFT }, + { SDLK_RIGHT, PBTN_RIGHT }, + { SDLK_SPACE, PBTN_MOK }, + { SDLK_LCTRL, PBTN_MBACK }, + { SDLK_LALT, PBTN_MA2 }, + { SDLK_LSHIFT, PBTN_MA3 }, + { SDLK_TAB, PBTN_L }, + { SDLK_BACKSPACE, PBTN_R }, +}; + +const struct menu_keymap in_sdl_joy_map[] = +{ + { SDLK_UP, PBTN_UP }, + { SDLK_DOWN, PBTN_DOWN }, + { SDLK_LEFT, PBTN_LEFT }, + { SDLK_RIGHT, PBTN_RIGHT }, + { SDLK_WORLD_0, PBTN_MOK }, + { SDLK_WORLD_1, PBTN_MBACK }, + { SDLK_WORLD_2, PBTN_MA2 }, + { SDLK_WORLD_3, PBTN_MA3 }, +}; + +static const char * const in_sdl_key_names[SDLK_LAST] = { + [SDLK_UP] = "up", + [SDLK_DOWN] = "down", + [SDLK_LEFT] = "left", + [SDLK_RIGHT] = "right", + [SDLK_LSHIFT] = "x", + [SDLK_LCTRL] = "b", + [SDLK_SPACE] = "a", + [SDLK_LALT] = "y", + [SDLK_RETURN] = "start", + [SDLK_RCTRL] = "select", + [SDLK_TAB] = "l", + [SDLK_BACKSPACE] = "r", + [SDLK_ESCAPE] = "menu", +}; + +static const struct in_pdata in_sdl_platform_data = { + .defbinds = in_sdl_defbinds, + .key_map = in_sdl_key_map, + .kmap_size = array_size(in_sdl_key_map), + .joy_map = in_sdl_joy_map, + .jmap_size = array_size(in_sdl_joy_map), + .key_names = in_sdl_key_names, +}; + +static void *fb_flip(void) +{ + SDL_Flip(screen); + return screen->pixels; +} + +void plat_video_menu_enter(int is_rom_loaded) +{ +} + +void plat_video_menu_begin(void) +{ + g_menuscreen_ptr = fb_flip(); +} + +void plat_video_menu_end(void) +{ + g_menuscreen_ptr = fb_flip(); +} + +void plat_video_menu_leave(void) +{ + SDL_LockSurface(screen); + memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t)); + SDL_UnlockSurface(screen); + g_menuscreen_ptr = fb_flip(); + SDL_LockSurface(screen); + memset(g_menuscreen_ptr, 0, 320 * 240 * sizeof(uint16_t)); + SDL_UnlockSurface(screen); +} + +void plat_video_open(void) +{ +} + +void plat_video_set_msg(char *new_msg) +{ + snprintf(msg, HUD_LEN, "%s", new_msg); +} + +void plat_video_flip(void) +{ + video_post_process(); + SDL_LockSurface(screen); + if (msg[0]) + video_clear_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t)); + + video_scale(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t)); + + if (msg[0]) + video_print_msg(screen->pixels, screen->h, screen->pitch / sizeof(uint16_t), msg); + SDL_UnlockSurface(screen); + + g_menuscreen_ptr = fb_flip(); + msg[0] = 0; +} + +void plat_video_close(void) +{ +} + +void plat_sound_callback(void *unused, u8 *stream, int len) +{ + short *p = (short *)stream; + len /= sizeof(short); + + while (buf_r != buf_w && len > 0) { + *p++ = buf[buf_r++]; + buf_r %= BUF_LEN; + --len; + } + + while(len > 0) { + *p++ = 0; + --len; + } +} + +void plat_sound_finish(void) +{ + SDL_PauseAudio(1); + SDL_CloseAudio(); +} + +int plat_sound_init(void) +{ + + if (SDL_InitSubSystem(SDL_INIT_AUDIO)) { + return -1; + } + + SDL_AudioSpec spec; + + spec.freq = sound_frequency; + spec.format = AUDIO_S16SYS; + spec.channels = 2; + spec.samples = 512; + spec.callback = plat_sound_callback; + + if (SDL_OpenAudio(&spec, NULL) < 0) { + plat_sound_finish(); + return -1; + } + + SDL_PauseAudio(0); + return 0; +} + +float plat_sound_capacity(void) +{ + int buffered = 0; + if (buf_w != buf_r) { + buffered = buf_w > buf_r ? buf_w - buf_r : (buf_w + BUF_LEN) - buf_r; + } + + return 1.0 - (float)buffered / BUF_LEN; +} + +void plat_sound_write(void *data, int bytes) +{ + short *sound_data = (short *)data; + SDL_LockAudio(); + + while (bytes > 0) { + while (((buf_w + 1) % BUF_LEN) == buf_r) { + SDL_UnlockAudio(); + + if (!limit_frames) + return; + + plat_sleep_ms(1); + SDL_LockAudio(); + } + buf[buf_w] = *sound_data++; + + ++buf_w; + buf_w %= BUF_LEN; + bytes -= sizeof(short); + } + SDL_UnlockAudio(); +} + +void plat_sdl_event_handler(void *event_) +{ +} + +int plat_init(void) +{ + SDL_Init(SDL_INIT_VIDEO); + screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE); + + if (screen == NULL) { + printf("%s, failed to set video mode\n", __func__); + return -1; + } + + SDL_ShowCursor(0); + + g_menuscreen_w = 320; + g_menuscreen_h = 240; + g_menuscreen_pp = 320; + g_menuscreen_ptr = fb_flip(); + + if (in_sdl_init(&in_sdl_platform_data, plat_sdl_event_handler)) { + fprintf(stderr, "SDL input failed to init: %s\n", SDL_GetError()); + return -1; + } + in_probe(); + + sound_frequency = 48000; + + if (plat_sound_init()) { + fprintf(stderr, "SDL sound failed to init: %s\n", SDL_GetError()); + return -1; + } + return 0; +} + +void plat_pre_finish(void) +{ +} + +void plat_finish(void) +{ + plat_sound_finish(); + SDL_Quit(); +} + +void plat_trigger_vibrate(int pad, int low, int high) +{ +} diff --git a/frontend/scale.c b/frontend/scale.c new file mode 100644 index 0000000..24f8a5a --- /dev/null +++ b/frontend/scale.c @@ -0,0 +1,555 @@ +#include "common.h" +#include "gba_cc_lut.h" +#include "frontend/main.h" +#include "frontend/libpicofe/fonts.h" + +#define Average(A, B) ((((A) & 0xF7DE) >> 1) + (((B) & 0xF7DE) >> 1) + ((A) & (B) & 0x0821)) + +/* Calculates the average of two pairs of RGB565 pixels. The result is, in + * the lower bits, the average of both lower pixels, and in the upper bits, + * the average of both upper pixels. */ +#define Average32(A, B) ((((A) & 0xF7DEF7DE) >> 1) + (((B) & 0xF7DEF7DE) >> 1) + ((A) & (B) & 0x08210821)) + +/* Raises a pixel from the lower half to the upper half of a pair. */ +#define Raise(N) ((N) << 16) + +/* Extracts the upper pixel of a pair into the lower pixel of a pair. */ +#define Hi(N) ((N) >> 16) + +/* Extracts the lower pixel of a pair. */ +#define Lo(N) ((N) & 0xFFFF) + +/* Calculates the average of two RGB565 pixels. The source of the pixels is + * the lower 16 bits of both parameters. The result is in the lower 16 bits. + * The average is weighted so that the first pixel contributes 3/4 of its + * color and the second pixel contributes 1/4. */ +#define AverageQuarters3_1(A, B) ( (((A) & 0xF7DE) >> 1) + (((A) & 0xE79C) >> 2) + (((B) & 0xE79C) >> 2) + ((( (( ((A) & 0x1863) + ((A) & 0x0821) ) << 1) + ((B) & 0x1863) ) >> 2) & 0x1863) ) + +static inline void gba_upscale(uint16_t *to, uint16_t *from, + uint32_t src_x, uint32_t src_y, uint32_t src_pitch, uint32_t dst_pitch) +{ + /* Before: + * a b c d e f + * g h i j k l + * + * After (multiple letters = average): + * a ab bc c d de ef f + * ag abgh bchi ci dj dejk efkl fl + * g gh hi i j jk kl l + */ + + const uint32_t dst_x = src_x * 4 / 3; + const uint32_t src_skip = src_pitch - src_x * sizeof(uint16_t), + dst_skip = dst_pitch - dst_x * sizeof(uint16_t); + + uint32_t x, y; + + for (y = 0; y < src_y; y += 2) { + for (x = 0; x < src_x / 6; x++) { + // -- Row 1 -- + // Read RGB565 elements in the source grid. + // The notation is high_low (little-endian). + uint32_t b_a = (*(uint32_t*) (from )), + d_c = (*(uint32_t*) (from + 2)), + f_e = (*(uint32_t*) (from + 4)); + + // Generate ab_a from b_a. + *(uint32_t*) (to) = likely(Hi(b_a) == Lo(b_a)) + ? b_a + : Lo(b_a) /* 'a' verbatim to low pixel */ | + Raise(Average(Hi(b_a), Lo(b_a))) /* ba to high pixel */; + + // Generate c_bc from b_a and d_c. + *(uint32_t*) (to + 2) = likely(Hi(b_a) == Lo(d_c)) + ? Lo(d_c) | Raise(Lo(d_c)) + : Raise(Lo(d_c)) /* 'c' verbatim to high pixel */ | + Average(Lo(d_c), Hi(b_a)) /* bc to low pixel */; + + // Generate de_d from d_c and f_e. + *(uint32_t*) (to + 4) = likely(Hi(d_c) == Lo(f_e)) + ? Lo(f_e) | Raise(Lo(f_e)) + : Hi(d_c) /* 'd' verbatim to low pixel */ | + Raise(Average(Lo(f_e), Hi(d_c))) /* de to high pixel */; + + // Generate f_ef from f_e. + *(uint32_t*) (to + 6) = likely(Hi(f_e) == Lo(f_e)) + ? f_e + : Raise(Hi(f_e)) /* 'f' verbatim to high pixel */ | + Average(Hi(f_e), Lo(f_e)) /* ef to low pixel */; + + if (likely(y + 1 < src_y)) // Is there a source row 2? + { + // -- Row 2 -- + uint32_t h_g = (*(uint32_t*) ((uint8_t*) from + src_pitch )), + j_i = (*(uint32_t*) ((uint8_t*) from + src_pitch + 4)), + l_k = (*(uint32_t*) ((uint8_t*) from + src_pitch + 8)); + + // Generate abgh_ag from b_a and h_g. + uint32_t bh_ag = Average32(b_a, h_g); + *(uint32_t*) ((uint8_t*) to + dst_pitch) = likely(Hi(bh_ag) == Lo(bh_ag)) + ? bh_ag + : Lo(bh_ag) /* ag verbatim to low pixel */ | + Raise(Average(Hi(bh_ag), Lo(bh_ag))) /* abgh to high pixel */; + + // Generate ci_bchi from b_a, d_c, h_g and j_i. + uint32_t ci_bh = + Hi(bh_ag) /* bh verbatim to low pixel */ | + Raise(Average(Lo(d_c), Lo(j_i))) /* ci to high pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch + 4) = likely(Hi(ci_bh) == Lo(ci_bh)) + ? ci_bh + : Raise(Hi(ci_bh)) /* ci verbatim to high pixel */ | + Average(Hi(ci_bh), Lo(ci_bh)) /* bchi to low pixel */; + + // Generate fl_efkl from f_e and l_k. + uint32_t fl_ek = Average32(f_e, l_k); + *(uint32_t*) ((uint8_t*) to + dst_pitch + 12) = likely(Hi(fl_ek) == Lo(fl_ek)) + ? fl_ek + : Raise(Hi(fl_ek)) /* fl verbatim to high pixel */ | + Average(Hi(fl_ek), Lo(fl_ek)) /* efkl to low pixel */; + + // Generate dejk_dj from d_c, f_e, j_i and l_k. + uint32_t ek_dj = + Raise(Lo(fl_ek)) /* ek verbatim to high pixel */ | + Average(Hi(d_c), Hi(j_i)) /* dj to low pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch + 8) = likely(Hi(ek_dj) == Lo(ek_dj)) + ? ek_dj + : Lo(ek_dj) /* dj verbatim to low pixel */ | + Raise(Average(Hi(ek_dj), Lo(ek_dj))) /* dejk to high pixel */; + + // -- Row 3 -- + // Generate gh_g from h_g. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2) = likely(Hi(h_g) == Lo(h_g)) + ? h_g + : Lo(h_g) /* 'g' verbatim to low pixel */ | + Raise(Average(Hi(h_g), Lo(h_g))) /* gh to high pixel */; + + // Generate i_hi from g_h and j_i. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 4) = likely(Hi(h_g) == Lo(j_i)) + ? Lo(j_i) | Raise(Lo(j_i)) + : Raise(Lo(j_i)) /* 'i' verbatim to high pixel */ | + Average(Lo(j_i), Hi(h_g)) /* hi to low pixel */; + + // Generate jk_j from j_i and l_k. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 8) = likely(Hi(j_i) == Lo(l_k)) + ? Lo(l_k) | Raise(Lo(l_k)) + : Hi(j_i) /* 'j' verbatim to low pixel */ | + Raise(Average(Hi(j_i), Lo(l_k))) /* jk to high pixel */; + + // Generate l_kl from l_k. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 12) = likely(Hi(l_k) == Lo(l_k)) + ? l_k + : Raise(Hi(l_k)) /* 'l' verbatim to high pixel */ | + Average(Hi(l_k), Lo(l_k)) /* kl to low pixel */; + } + + from += 6; + to += 8; + } + + // Skip past the waste at the end of the first line, if any, + // then past 1 whole lines of source and 2 of destination. + from = (uint16_t*) ((uint8_t*) from + src_skip + src_pitch); + to = (uint16_t*) ((uint8_t*) to + dst_skip + 2 * dst_pitch); + } +} + +static inline void gba_upscale_aspect(uint16_t *to, uint16_t *from, + uint32_t src_x, uint32_t src_y, uint32_t src_pitch, uint32_t dst_pitch) +{ + /* Before: + * a b c d e f + * g h i j k l + * m n o p q r + * + * After (multiple letters = average): + * a ab bc c d de ef f + * ag abgh bchi ci dj dejk efkl fl + * gm ghmn hino io jp jkpq klqr lr + * m mn no o p pq qr r + */ + + const uint32_t dst_x = src_x * 4 / 3; + const uint32_t src_skip = src_pitch - src_x * sizeof(uint16_t), + dst_skip = dst_pitch - dst_x * sizeof(uint16_t); + + uint32_t x, y; + + for (y = 0; y < src_y; y += 3) { + for (x = 0; x < src_x / 6; x++) { + // -- Row 1 -- + // Read RGB565 elements in the source grid. + // The notation is high_low (little-endian). + uint32_t b_a = (*(uint32_t*) (from )), + d_c = (*(uint32_t*) (from + 2)), + f_e = (*(uint32_t*) (from + 4)); + + // Generate ab_a from b_a. + *(uint32_t*) (to) = likely(Hi(b_a) == Lo(b_a)) + ? b_a + : Lo(b_a) /* 'a' verbatim to low pixel */ | + Raise(Average(Hi(b_a), Lo(b_a))) /* ba to high pixel */; + + // Generate c_bc from b_a and d_c. + *(uint32_t*) (to + 2) = likely(Hi(b_a) == Lo(d_c)) + ? Lo(d_c) | Raise(Lo(d_c)) + : Raise(Lo(d_c)) /* 'c' verbatim to high pixel */ | + Average(Lo(d_c), Hi(b_a)) /* bc to low pixel */; + + // Generate de_d from d_c and f_e. + *(uint32_t*) (to + 4) = likely(Hi(d_c) == Lo(f_e)) + ? Lo(f_e) | Raise(Lo(f_e)) + : Hi(d_c) /* 'd' verbatim to low pixel */ | + Raise(Average(Lo(f_e), Hi(d_c))) /* de to high pixel */; + + // Generate f_ef from f_e. + *(uint32_t*) (to + 6) = likely(Hi(f_e) == Lo(f_e)) + ? f_e + : Raise(Hi(f_e)) /* 'f' verbatim to high pixel */ | + Average(Hi(f_e), Lo(f_e)) /* ef to low pixel */; + + if (likely(y + 1 < src_y)) // Is there a source row 2? + { + // -- Row 2 -- + uint32_t h_g = (*(uint32_t*) ((uint8_t*) from + src_pitch )), + j_i = (*(uint32_t*) ((uint8_t*) from + src_pitch + 4)), + l_k = (*(uint32_t*) ((uint8_t*) from + src_pitch + 8)); + + // Generate abgh_ag from b_a and h_g. + uint32_t bh_ag = Average32(b_a, h_g); + *(uint32_t*) ((uint8_t*) to + dst_pitch) = likely(Hi(bh_ag) == Lo(bh_ag)) + ? bh_ag + : Lo(bh_ag) /* ag verbatim to low pixel */ | + Raise(Average(Hi(bh_ag), Lo(bh_ag))) /* abgh to high pixel */; + + // Generate ci_bchi from b_a, d_c, h_g and j_i. + uint32_t ci_bh = + Hi(bh_ag) /* bh verbatim to low pixel */ | + Raise(Average(Lo(d_c), Lo(j_i))) /* ci to high pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch + 4) = likely(Hi(ci_bh) == Lo(ci_bh)) + ? ci_bh + : Raise(Hi(ci_bh)) /* ci verbatim to high pixel */ | + Average(Hi(ci_bh), Lo(ci_bh)) /* bchi to low pixel */; + + // Generate fl_efkl from f_e and l_k. + uint32_t fl_ek = Average32(f_e, l_k); + *(uint32_t*) ((uint8_t*) to + dst_pitch + 12) = likely(Hi(fl_ek) == Lo(fl_ek)) + ? fl_ek + : Raise(Hi(fl_ek)) /* fl verbatim to high pixel */ | + Average(Hi(fl_ek), Lo(fl_ek)) /* efkl to low pixel */; + + // Generate dejk_dj from d_c, f_e, j_i and l_k. + uint32_t ek_dj = + Raise(Lo(fl_ek)) /* ek verbatim to high pixel */ | + Average(Hi(d_c), Hi(j_i)) /* dj to low pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch + 8) = likely(Hi(ek_dj) == Lo(ek_dj)) + ? ek_dj + : Lo(ek_dj) /* dj verbatim to low pixel */ | + Raise(Average(Hi(ek_dj), Lo(ek_dj))) /* dejk to high pixel */; + + if (likely(y + 2 < src_y)) // Is there a source row 3? + { + // -- Row 3 -- + uint32_t n_m = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 )), + p_o = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 + 4)), + r_q = (*(uint32_t*) ((uint8_t*) from + src_pitch * 2 + 8)); + + // Generate ghmn_gm from h_g and n_m. + uint32_t hn_gm = Average32(h_g, n_m); + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2) = likely(Hi(hn_gm) == Lo(hn_gm)) + ? hn_gm + : Lo(hn_gm) /* gm verbatim to low pixel */ | + Raise(Average(Hi(hn_gm), Lo(hn_gm))) /* ghmn to high pixel */; + + // Generate io_hino from h_g, j_i, n_m and p_o. + uint32_t io_hn = + Hi(hn_gm) /* hn verbatim to low pixel */ | + Raise(Average(Lo(j_i), Lo(p_o))) /* io to high pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 4) = likely(Hi(io_hn) == Lo(io_hn)) + ? io_hn + : Raise(Hi(io_hn)) /* io verbatim to high pixel */ | + Average(Hi(io_hn), Lo(io_hn)) /* hino to low pixel */; + + // Generate lr_klqr from l_k and r_q. + uint32_t lr_kq = Average32(l_k, r_q); + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 12) = likely(Hi(lr_kq) == Lo(lr_kq)) + ? lr_kq + : Raise(Hi(lr_kq)) /* lr verbatim to high pixel */ | + Average(Hi(lr_kq), Lo(lr_kq)) /* klqr to low pixel */; + + // Generate jkpq_jp from j_i, l_k, p_o and r_q. + uint32_t kq_jp = + Raise(Lo(lr_kq)) /* kq verbatim to high pixel */ | + Average(Hi(j_i), Hi(p_o)) /* jp to low pixel */; + *(uint32_t*) ((uint8_t*) to + dst_pitch * 2 + 8) = likely(Hi(kq_jp) == Lo(kq_jp)) + ? kq_jp + : Lo(kq_jp) /* jp verbatim to low pixel */ | + Raise(Average(Hi(kq_jp), Lo(kq_jp))) /* jkpq to high pixel */; + + // -- Row 4 -- + // Generate mn_m from n_m. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 3) = likely(Hi(n_m) == Lo(n_m)) + ? n_m + : Lo(n_m) /* 'm' verbatim to low pixel */ | + Raise(Average(Hi(n_m), Lo(n_m))) /* mn to high pixel */; + + // Generate o_no from n_m and p_o. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 4) = likely(Hi(n_m) == Lo(p_o)) + ? Lo(p_o) | Raise(Lo(p_o)) + : Raise(Lo(p_o)) /* 'o' verbatim to high pixel */ | + Average(Lo(p_o), Hi(n_m)) /* no to low pixel */; + + // Generate pq_p from p_o and r_q. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 8) = likely(Hi(p_o) == Lo(r_q)) + ? Lo(r_q) | Raise(Lo(r_q)) + : Hi(p_o) /* 'p' verbatim to low pixel */ | + Raise(Average(Hi(p_o), Lo(r_q))) /* pq to high pixel */; + + // Generate r_qr from r_q. + *(uint32_t*) ((uint8_t*) to + dst_pitch * 3 + 12) = likely(Hi(r_q) == Lo(r_q)) + ? r_q + : Raise(Hi(r_q)) /* 'r' verbatim to high pixel */ | + Average(Hi(r_q), Lo(r_q)) /* qr to low pixel */; + } + } + + from += 6; + to += 8; + } + + // Skip past the waste at the end of the first line, if any, + // then past 2 whole lines of source and 3 of destination. + from = (uint16_t*) ((uint8_t*) from + src_skip + 2 * src_pitch); + to = (uint16_t*) ((uint8_t*) to + dst_skip + 3 * dst_pitch); + } +} + +static inline void gba_nofilter_noscale(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch, uint16_t *src) { + int dst_x = ((dst_pitch - GBA_SCREEN_PITCH) / 2); + int dst_y = ((dst_h - GBA_SCREEN_HEIGHT) / 2); + + for (int y = 0; y < GBA_SCREEN_HEIGHT; y++) { + memcpy(dst + (dst_y + y) * dst_pitch + dst_x, + src + y * GBA_SCREEN_PITCH, + GBA_SCREEN_PITCH * sizeof(src[0])); + } +} + +/* drowsnug's nofilter upscaler */ +#define AVERAGE16(c1, c2) (((c1) + (c2) + (((c1) ^ (c2)) & 0x0821))>>1) //More accurate +static inline void gba_nofilter_upscale(uint16_t *dst, uint16_t *src, int h) +{ + int Eh = 0; + int dh = 0; + int width = 240; + int vf = 0; + + dst += ((240-h)/2) * 320; // blank upper border. h=240(full) or h=214(aspect) + + int x, y; + for (y = 0; y < h; y++) + { + int source = dh * width; + for (x = 0; x < 320/4; x++) + { + register uint16_t a, b, c; + + a = src[source]; + b = src[source+1]; + c = src[source+2]; + + if(vf == 1){ + a = AVERAGE16(a, src[source+width]); + b = AVERAGE16(b, src[source+width+1]); + c = AVERAGE16(c, src[source+width+2]); + } + + *dst++ = a; + *dst++ = (AVERAGE16(a,b) & 0b0000000000011111) | (b & 0b1111111111100000); + *dst++ = (b & 0b0000011111111111) | (AVERAGE16(b,c) & 0b1111100000000000); + *dst++ = c; + source+=3; + + } + Eh += 160; + if(Eh >= h) { + Eh -= h; + dh++; + vf = 0; + } + else + vf = 1; + } +} + +void video_clear_msg(uint16_t *dst, uint32_t h, uint32_t pitch) +{ + memset(dst + (h - 10) * pitch, 0, 10 * pitch * sizeof(uint16_t)); +} + +void video_print_msg(uint16_t *dst, uint32_t h, uint32_t pitch, char *msg) +{ + basic_text_out16_nf(dst, pitch, 2, h - 10, msg); +} + +void video_scale(uint16_t *dst, uint32_t h, uint32_t pitch) { + uint16_t *gba_screen_pixels_buf = gba_screen_pixels; + + if (color_correct || lcd_blend) + gba_screen_pixels_buf = gba_processed_pixels; + + switch (scaling_mode) + { + case SCALING_ASPECT_SHARP: + gba_nofilter_upscale(dst, gba_screen_pixels_buf, 214); + break; + case SCALING_ASPECT_SMOOTH: + dst += ((h - (GBA_SCREEN_HEIGHT * 4 / 3)) / 2 * pitch); + gba_upscale_aspect(dst, gba_screen_pixels_buf, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, GBA_SCREEN_PITCH * 2, pitch * 2); + break; + case SCALING_FULL_SHARP: + gba_nofilter_upscale(dst, gba_screen_pixels_buf, 240); + break; + case SCALING_FULL_SMOOTH: + gba_upscale(dst, gba_screen_pixels_buf, GBA_SCREEN_WIDTH, GBA_SCREEN_HEIGHT, GBA_SCREEN_PITCH * 2, pitch * 2); + break; + default: + gba_nofilter_noscale(dst, h, pitch, gba_screen_pixels_buf); + break; + } +} + +/* Video post processing START */ + +/* Note: This code is intentionally W.E.T. + * (Write Everything Twice). These functions + * are performance critical, and we cannot + * afford to do unnecessary comparisons/switches + * inside the inner for loops */ + +static void video_post_process_cc(void) +{ + uint16_t *src = gba_screen_pixels; + uint16_t *dst = gba_processed_pixels; + size_t x, y; + + for (y = 0; y < GBA_SCREEN_HEIGHT; y++) + { + for (x = 0; x < GBA_SCREEN_PITCH; x++) + { + u16 src_color = *(src + x); + + /* Convert colour to RGB555 and perform lookup */ + *(dst + x) = *(gba_cc_lut + (((src_color & 0xFFC0) >> 1) | (src_color & 0x1F))); + } + + src += GBA_SCREEN_PITCH; + dst += GBA_SCREEN_PITCH; + } +} + +static void video_post_process_mix(void) +{ + uint16_t *src_curr = gba_screen_pixels; + uint16_t *src_prev = gba_screen_pixels_prev; + uint16_t *dst = gba_processed_pixels; + size_t x, y; + + for (y = 0; y < GBA_SCREEN_HEIGHT; y++) + { + for (x = 0; x < GBA_SCREEN_PITCH; x++) + { + /* Get colours from current + previous frames (RGB565) */ + uint16_t rgb_curr = *(src_curr + x); + uint16_t rgb_prev = *(src_prev + x); + + /* Store colours for next frame */ + *(src_prev + x) = rgb_curr; + + /* Mix colours + * > "Mixing Packed RGB Pixels Efficiently" + * http://blargg.8bitalley.com/info/rgb_mixing.html */ + *(dst + x) = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x821)) >> 1; + } + + src_curr += GBA_SCREEN_PITCH; + src_prev += GBA_SCREEN_PITCH; + dst += GBA_SCREEN_PITCH; + } +} + +static void video_post_process_cc_mix(void) +{ + uint16_t *src_curr = gba_screen_pixels; + uint16_t *src_prev = gba_screen_pixels_prev; + uint16_t *dst = gba_processed_pixels; + size_t x, y; + + for (y = 0; y < GBA_SCREEN_HEIGHT; y++) + { + for (x = 0; x < GBA_SCREEN_PITCH; x++) + { + /* Get colours from current + previous frames (RGB565) */ + uint16_t rgb_curr = *(src_curr + x); + uint16_t rgb_prev = *(src_prev + x); + + /* Store colours for next frame */ + *(src_prev + x) = rgb_curr; + + /* Mix colours + * > "Mixing Packed RGB Pixels Efficiently" + * http://blargg.8bitalley.com/info/rgb_mixing.html */ + uint16_t rgb_mix = (rgb_curr + rgb_prev + ((rgb_curr ^ rgb_prev) & 0x821)) >> 1; + + /* Convert colour to RGB555 and perform lookup */ + *(dst + x) = *(gba_cc_lut + (((rgb_mix & 0xFFC0) >> 1) | (rgb_mix & 0x1F))); + } + + src_curr += GBA_SCREEN_PITCH; + src_prev += GBA_SCREEN_PITCH; + dst += GBA_SCREEN_PITCH; + } +} + +void video_post_process(void) +{ + size_t buf_size = GBA_SCREEN_PITCH * GBA_SCREEN_HEIGHT * sizeof(u16); + + /* If post processing is disabled, return + * immediately */ + if (!color_correct && !lcd_blend) + return; + + /* Initialise output buffer, if required */ + if (!gba_processed_pixels && + (color_correct || lcd_blend)) + { + gba_processed_pixels = (u16*)malloc(buf_size); + + if (!gba_processed_pixels) + return; + + memset(gba_processed_pixels, 0xFFFF, buf_size); + } + + /* Initialise 'history' buffer, if required */ + if (!gba_screen_pixels_prev && + lcd_blend) + { + gba_screen_pixels_prev = (u16*)malloc(buf_size); + + if (!gba_screen_pixels_prev) + return; + + memset(gba_screen_pixels_prev, 0xFFFF, buf_size); + } + + /* Assign post processing function */ + if (color_correct && lcd_blend) + video_post_process_cc_mix(); + else if (color_correct) + video_post_process_cc(); + else if (lcd_blend) + video_post_process_mix(); +} + +/* Video post processing END */ diff --git a/frontend/scale.h b/frontend/scale.h new file mode 100644 index 0000000..fd538eb --- /dev/null +++ b/frontend/scale.h @@ -0,0 +1,9 @@ +#ifndef __FRONTEND_SCALE_H__ +#define __FRONTEND_SCALE_H__ + +void video_post_process(void); +void video_scale(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch); +void video_clear_msg(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch); +void video_print_msg(uint16_t *dst, uint32_t dst_h, uint32_t dst_pitch, char *msg); + +#endif /* __FRONTEND_SCALE_H__ */ |