summaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorneonloop2021-03-21 16:41:44 +0000
committerneonloop2021-03-21 16:41:44 +0000
commit4c26780f101a12d455ffe900e6a0d5238d1c9552 (patch)
tree8913a7229c15e1ed597b5a128a08aa554150f674 /frontend
parent5ef784ab8af08b9629b573b3cfd25f21384771af (diff)
downloadpicogpsp-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.c91
-rw-r--r--frontend/config.h9
m---------frontend/libpicofe0
-rw-r--r--frontend/main.c416
-rw-r--r--frontend/main.h70
-rw-r--r--frontend/menu.c335
-rw-r--r--frontend/menu.h10
-rw-r--r--frontend/plat.h19
-rw-r--r--frontend/plat_linux.c258
-rw-r--r--frontend/plat_trimui.c277
-rw-r--r--frontend/scale.c555
-rw-r--r--frontend/scale.h9
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__ */