retro-go/retro-core/main/main_nes.c

323 lines
9.8 KiB
C
Raw Normal View History

#include "shared.h"
#include <nofrendo.h>
2020-02-13 17:21:07 -05:00
static int overscan = true;
static int autocrop = 0;
static int palette = 0;
static bool slowFrame = false;
2024-02-29 23:37:08 -05:00
static bool nsfPlayer = false;
static nes_t *nes;
static rg_app_t *app;
2024-02-28 16:35:41 -05:00
static rg_surface_t *updates[2];
static rg_surface_t *currentUpdate;
2021-03-04 13:50:18 -05:00
static const char *SETTING_AUTOCROP = "autocrop";
static const char *SETTING_OVERSCAN = "overscan";
static const char *SETTING_PALETTE = "palette";
static const char *SETTING_SPRITELIMIT = "spritelimit";
// --- MAIN
2020-02-13 17:21:07 -05:00
2022-09-13 19:16:50 -04:00
static void event_handler(int event, void *arg)
{
if (event == RG_EVENT_REDRAW)
{
2024-02-29 23:37:08 -05:00
if (nsfPlayer)
rg_display_clear(C_BLACK);
else if (nes)
(nes->blit_func)(NULL);
}
}
static bool screenshot_handler(const char *filename, int width, int height)
{
return rg_surface_save_image_file(currentUpdate, filename, width, height);
}
static bool save_state_handler(const char *filename)
{
return state_save(filename) == 0;
}
static bool load_state_handler(const char *filename)
{
if (state_load(filename) != 0)
{
nes_reset(true);
return false;
}
return true;
}
static bool reset_handler(bool hard)
{
nes_reset(hard);
return true;
}
static void build_palette(int n)
{
uint16_t *pal = nofrendo_buildpalette(n, 16);
for (int i = 0; i < 256; i++)
{
uint16_t color = (pal[i] >> 8) | ((pal[i]) << 8);
2024-02-28 16:35:41 -05:00
updates[0]->palette[i] = color;
updates[1]->palette[i] = color;
}
free(pal);
}
static rg_gui_event_t sprite_limit_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
bool spritelimit = ppu_getopt(PPU_LIMIT_SPRITES);
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
spritelimit = !spritelimit;
rg_settings_set_number(NS_APP, SETTING_SPRITELIMIT, spritelimit);
ppu_setopt(PPU_LIMIT_SPRITES, spritelimit);
}
Adding Localization support (#159) * Innitial commit Localization for retro-go using a simple 0(n) lookup function called rg_gettext() * adding language settings in options menu * adding more gettext() * new lookup function * adding "For these changes to take effect you must restart your device." gui alert + fixing gettext() function * modifying the gui dialog * updating struct syntax * update struct syntax (again) * creating the python tool for localization * updating tool + adding missing translations * moving stuff to libs + starting writing readme * adding missing "libs/localization" folder import in cmakelist + added the "fixme for rg_system" * synthax adjust + moving back stuff from libs to retro-go * removing trailing spaces * adding the enum for language ids * updating documentation according to the latest changes * small tweaks * Moved LOCALIZATION.md to the root folder Whilst it is mostly relevant to libretro-go, it really is project-wide documentation. * rg_localization: Got rid of the switch, made GUI dynamic This makes adding a language more straightforward. I kept the *msg *fr *en for now to avoid updating translations.h, but it could be replaced by the GCC extension as such: [RG_LANG_EN] = "...", [RG_LANG_FR] = "...", So that adding a language is really just updating the enum... * rg_localization: translations is const, we can use RG_COUNT * rg_gui: Fixed language selection * rg_localization: No need to validate rg_language in rg_gettext It should always be valid, there's no need to validate it. * rg_gui: Show language name in the log * rg_localization: Got rid of the Translation struct I am not 100% positive this is a good move... Benefits: - One less thing to change when adding a language - Less code is always better Cons: - It doesn't make it clear what the "key" is (the english text) - If in the future we need to add things like flags it will have to be returned to a struct * updated python tool + updating translations * added missing translations * audio filter wrong translation * fix : "a propose de retro-go" --------- Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
2024-11-16 19:04:50 +01:00
strcpy(option->value, spritelimit ? _("On") : _("Off"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t overscan_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT)
{
overscan = !overscan;
rg_settings_set_number(NS_APP, SETTING_OVERSCAN, overscan);
return RG_DIALOG_REDRAW;
}
Adding Localization support (#159) * Innitial commit Localization for retro-go using a simple 0(n) lookup function called rg_gettext() * adding language settings in options menu * adding more gettext() * new lookup function * adding "For these changes to take effect you must restart your device." gui alert + fixing gettext() function * modifying the gui dialog * updating struct syntax * update struct syntax (again) * creating the python tool for localization * updating tool + adding missing translations * moving stuff to libs + starting writing readme * adding missing "libs/localization" folder import in cmakelist + added the "fixme for rg_system" * synthax adjust + moving back stuff from libs to retro-go * removing trailing spaces * adding the enum for language ids * updating documentation according to the latest changes * small tweaks * Moved LOCALIZATION.md to the root folder Whilst it is mostly relevant to libretro-go, it really is project-wide documentation. * rg_localization: Got rid of the switch, made GUI dynamic This makes adding a language more straightforward. I kept the *msg *fr *en for now to avoid updating translations.h, but it could be replaced by the GCC extension as such: [RG_LANG_EN] = "...", [RG_LANG_FR] = "...", So that adding a language is really just updating the enum... * rg_localization: translations is const, we can use RG_COUNT * rg_gui: Fixed language selection * rg_localization: No need to validate rg_language in rg_gettext It should always be valid, there's no need to validate it. * rg_gui: Show language name in the log * rg_localization: Got rid of the Translation struct I am not 100% positive this is a good move... Benefits: - One less thing to change when adding a language - Less code is always better Cons: - It doesn't make it clear what the "key" is (the english text) - If in the future we need to add things like flags it will have to be returned to a struct * updated python tool + updating translations * added missing translations * audio filter wrong translation * fix : "a propose de retro-go" --------- Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
2024-11-16 19:04:50 +01:00
strcpy(option->value, overscan ? _("Auto") : _("Off"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t autocrop_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
int val = autocrop;
int max = 2;
2020-06-01 15:02:49 -04:00
if (event == RG_DIALOG_PREV) val = val > 0 ? val - 1 : max;
if (event == RG_DIALOG_NEXT) val = val < max ? val + 1 : 0;
2020-06-01 15:02:49 -04:00
if (val != autocrop)
{
autocrop = val;
rg_settings_set_number(NS_APP, SETTING_AUTOCROP, val);
return RG_DIALOG_REDRAW;
}
Adding Localization support (#159) * Innitial commit Localization for retro-go using a simple 0(n) lookup function called rg_gettext() * adding language settings in options menu * adding more gettext() * new lookup function * adding "For these changes to take effect you must restart your device." gui alert + fixing gettext() function * modifying the gui dialog * updating struct syntax * update struct syntax (again) * creating the python tool for localization * updating tool + adding missing translations * moving stuff to libs + starting writing readme * adding missing "libs/localization" folder import in cmakelist + added the "fixme for rg_system" * synthax adjust + moving back stuff from libs to retro-go * removing trailing spaces * adding the enum for language ids * updating documentation according to the latest changes * small tweaks * Moved LOCALIZATION.md to the root folder Whilst it is mostly relevant to libretro-go, it really is project-wide documentation. * rg_localization: Got rid of the switch, made GUI dynamic This makes adding a language more straightforward. I kept the *msg *fr *en for now to avoid updating translations.h, but it could be replaced by the GCC extension as such: [RG_LANG_EN] = "...", [RG_LANG_FR] = "...", So that adding a language is really just updating the enum... * rg_localization: translations is const, we can use RG_COUNT * rg_gui: Fixed language selection * rg_localization: No need to validate rg_language in rg_gettext It should always be valid, there's no need to validate it. * rg_gui: Show language name in the log * rg_localization: Got rid of the Translation struct I am not 100% positive this is a good move... Benefits: - One less thing to change when adding a language - Less code is always better Cons: - It doesn't make it clear what the "key" is (the english text) - If in the future we need to add things like flags it will have to be returned to a struct * updated python tool + updating translations * added missing translations * audio filter wrong translation * fix : "a propose de retro-go" --------- Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
2024-11-16 19:04:50 +01:00
if (val == 0) strcpy(option->value, _("Never"));
if (val == 1) strcpy(option->value, _("Auto"));
if (val == 2) strcpy(option->value, _("Always"));
return RG_DIALOG_VOID;
}
static rg_gui_event_t palette_update_cb(rg_gui_option_t *option, rg_gui_event_t event)
{
int pal = palette;
int max = NES_PALETTE_COUNT - 1;
if (event == RG_DIALOG_PREV) pal = pal > 0 ? pal - 1 : max;
if (event == RG_DIALOG_NEXT) pal = pal < max ? pal + 1 : 0;
if (pal != palette)
{
palette = pal;
rg_settings_set_number(NS_APP, SETTING_PALETTE, pal);
build_palette(pal);
return RG_DIALOG_REDRAW;
}
if (pal == NES_PALETTE_NOFRENDO) strcpy(option->value, _("Nofrendo"));
Adding Localization support (#159) * Innitial commit Localization for retro-go using a simple 0(n) lookup function called rg_gettext() * adding language settings in options menu * adding more gettext() * new lookup function * adding "For these changes to take effect you must restart your device." gui alert + fixing gettext() function * modifying the gui dialog * updating struct syntax * update struct syntax (again) * creating the python tool for localization * updating tool + adding missing translations * moving stuff to libs + starting writing readme * adding missing "libs/localization" folder import in cmakelist + added the "fixme for rg_system" * synthax adjust + moving back stuff from libs to retro-go * removing trailing spaces * adding the enum for language ids * updating documentation according to the latest changes * small tweaks * Moved LOCALIZATION.md to the root folder Whilst it is mostly relevant to libretro-go, it really is project-wide documentation. * rg_localization: Got rid of the switch, made GUI dynamic This makes adding a language more straightforward. I kept the *msg *fr *en for now to avoid updating translations.h, but it could be replaced by the GCC extension as such: [RG_LANG_EN] = "...", [RG_LANG_FR] = "...", So that adding a language is really just updating the enum... * rg_localization: translations is const, we can use RG_COUNT * rg_gui: Fixed language selection * rg_localization: No need to validate rg_language in rg_gettext It should always be valid, there's no need to validate it. * rg_gui: Show language name in the log * rg_localization: Got rid of the Translation struct I am not 100% positive this is a good move... Benefits: - One less thing to change when adding a language - Less code is always better Cons: - It doesn't make it clear what the "key" is (the english text) - If in the future we need to add things like flags it will have to be returned to a struct * updated python tool + updating translations * added missing translations * audio filter wrong translation * fix : "a propose de retro-go" --------- Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
2024-11-16 19:04:50 +01:00
if (pal == NES_PALETTE_COMPOSITE) strcpy(option->value, _("Composite"));
if (pal == NES_PALETTE_NESCLASSIC) strcpy(option->value, _("NES Classic"));
if (pal == NES_PALETTE_NTSC) strcpy(option->value, _("NTSC"));
if (pal == NES_PALETTE_PVM) strcpy(option->value, _("PVM"));
if (pal == NES_PALETTE_SMOOTH) strcpy(option->value, _("Smooth"));
return RG_DIALOG_VOID;
}
static void blit_screen(uint8 *bmp)
2020-02-13 17:21:07 -05:00
{
slowFrame = bmp && !rg_display_sync(false);
// A rolling average should be used for autocrop == 1, it causes jitter in some games...
// int crop_h = (autocrop == 2) || (autocrop == 1 && nes->ppu->left_bg_counter > 210) ? 8 : 0;
int crop_v = (overscan) ? nes->overscan : 0;
int crop_h = (autocrop) ? 8 : 0;
// crop_h = (autocrop == 2) || (autocrop == 1 && nes->ppu->left_bg_counter > 210) ? 8 : 0;
currentUpdate->width = NES_SCREEN_WIDTH - crop_h * 2;
currentUpdate->height = NES_SCREEN_HEIGHT - crop_v * 2;
currentUpdate->offset = crop_v * currentUpdate->stride + crop_h + 8;
rg_display: New partial update system The previous partial update system worked as follows: 1. Compare the previous and current framebuffers line by line 2. For each line find the first different pixel and the last one 3. Do a second pass and merge line differences into rectangles 4. Adjust the rectangles until they're no longer on boundaries that would break scaling or filtering 5. Prepare/render the region within a rectangle (do pixel conversion, scaling, filtering, etc) 5. Send the block to the SPI display This worked reasonably well but: - Step 4 was always a problem and we often ended up with artifacts despite many attempts at fixing it - Mid-frame palette changes were completely broken - The need for two framebuffers is an issue in some emulators - It was fairly slow. Better than a full redraw, but still slow The new system works as follows: 1. Prepare/render a few lines to be sent to the display, as if we were doing a full update 2. Do a checksum of the block and compare it to the checksum of the same block from the previous frame 3. If it matches, drop the block and advance the window. Otherwise store the new checksum and send the SPI buffer This has many advantages: - We don't have to care about boundaries since they're always the same - In many cases it's faster than doing a diff, even though we have to render the full lines - It can work with a single framebuffer - It uses less memory - We can invalidate lines that rg_display_write writes to, avoiding accidental leftovers The main drawback is that it's currently less granular. If a single pixel changes in a block, we have to send it fully to the display. Whereas previously we narrowed down the diff to a smaller region. But I intend to experiment with block sizes and shapes, there's no reason we couldn't split the frame into smaller blocks. It was just more convenient to start with the current block size (4 full lines).
2024-02-15 00:47:43 -05:00
rg_display_submit(currentUpdate, 0);
2020-02-13 17:21:07 -05:00
}
static void nsf_draw_overlay(void)
{
extern int nsf_current_song;
char song[32];
const nsfheader_t *header = (nsfheader_t *)nes->cart->data_ptr;
const rg_gui_option_t options[] = {
Adding Localization support (#159) * Innitial commit Localization for retro-go using a simple 0(n) lookup function called rg_gettext() * adding language settings in options menu * adding more gettext() * new lookup function * adding "For these changes to take effect you must restart your device." gui alert + fixing gettext() function * modifying the gui dialog * updating struct syntax * update struct syntax (again) * creating the python tool for localization * updating tool + adding missing translations * moving stuff to libs + starting writing readme * adding missing "libs/localization" folder import in cmakelist + added the "fixme for rg_system" * synthax adjust + moving back stuff from libs to retro-go * removing trailing spaces * adding the enum for language ids * updating documentation according to the latest changes * small tweaks * Moved LOCALIZATION.md to the root folder Whilst it is mostly relevant to libretro-go, it really is project-wide documentation. * rg_localization: Got rid of the switch, made GUI dynamic This makes adding a language more straightforward. I kept the *msg *fr *en for now to avoid updating translations.h, but it could be replaced by the GCC extension as such: [RG_LANG_EN] = "...", [RG_LANG_FR] = "...", So that adding a language is really just updating the enum... * rg_localization: translations is const, we can use RG_COUNT * rg_gui: Fixed language selection * rg_localization: No need to validate rg_language in rg_gettext It should always be valid, there's no need to validate it. * rg_gui: Show language name in the log * rg_localization: Got rid of the Translation struct I am not 100% positive this is a good move... Benefits: - One less thing to change when adding a language - Less code is always better Cons: - It doesn't make it clear what the "key" is (the english text) - If in the future we need to add things like flags it will have to be returned to a struct * updated python tool + updating translations * added missing translations * audio filter wrong translation * fix : "a propose de retro-go" --------- Co-authored-by: Alex Duchesne <ducalex007@gmail.com>
2024-11-16 19:04:50 +01:00
{0, _("Name"), (char*)header->name, RG_DIALOG_FLAG_NORMAL, NULL},
{0, _("Artist"), (char*)header->artist, RG_DIALOG_FLAG_NORMAL, NULL},
{0, _("Copyright"), (char*)header->copyright, RG_DIALOG_FLAG_NORMAL, NULL},
{0, _("Playing"), (char*)song, RG_DIALOG_FLAG_NORMAL, NULL},
RG_DIALOG_END,
};
snprintf(song, sizeof(song), "%d / %d", nsf_current_song, header->total_songs);
rg_gui_draw_dialog("NSF Player", options, 4, -1);
}
static void options_handler(rg_gui_option_t *dest)
{
*dest++ = (rg_gui_option_t){0, _("Palette"), "-", RG_DIALOG_FLAG_NORMAL, &palette_update_cb};
*dest++ = (rg_gui_option_t){0, _("Overscan"), "-", RG_DIALOG_FLAG_NORMAL, &overscan_update_cb};
*dest++ = (rg_gui_option_t){0, _("Crop sides"), "-", RG_DIALOG_FLAG_NORMAL, &autocrop_update_cb};
*dest++ = (rg_gui_option_t){0, _("Sprite limit"), "-", RG_DIALOG_FLAG_NORMAL, &sprite_limit_cb};
*dest++ = (rg_gui_option_t)RG_DIALOG_END;
}
void nes_main(void)
2020-02-13 17:21:07 -05:00
{
const rg_handlers_t handlers = {
.loadState = &load_state_handler,
.saveState = &save_state_handler,
.reset = &reset_handler,
2022-09-13 19:16:50 -04:00
.event = &event_handler,
.screenshot = &screenshot_handler,
.options = &options_handler,
};
app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, NULL);
overscan = rg_settings_get_number(NS_APP, SETTING_OVERSCAN, 1);
autocrop = rg_settings_get_number(NS_APP, SETTING_AUTOCROP, 0);
palette = rg_settings_get_number(NS_APP, SETTING_PALETTE, NES_PALETTE_PVM);
2024-02-28 16:35:41 -05:00
updates[0] = rg_surface_create(NES_SCREEN_PITCH, NES_SCREEN_HEIGHT, RG_PIXEL_PAL565_BE, MEM_FAST);
updates[1] = rg_surface_create(NES_SCREEN_PITCH, NES_SCREEN_HEIGHT, RG_PIXEL_PAL565_BE, MEM_FAST);
currentUpdate = updates[0];
nes = nes_init(SYS_DETECT, app->sampleRate, true, RG_BASE_PATH_BIOS "/fds_bios.bin");
if (!nes)
RG_PANIC("Init failed.");
int ret = -1;
if (rg_extension_match(app->romPath, "zip"))
{
void *data;
size_t size;
if (!rg_storage_unzip_file(app->romPath, NULL, &data, &size, RG_FILE_ALIGN_8KB))
RG_PANIC("ROM file unzipping failed!");
ret = nes_insertcart(rom_loadmem(data, size));
}
else
{
ret = nes_loadfile(app->romPath);
}
if (ret == -1)
RG_PANIC("ROM load failed.");
else if (ret == -2)
RG_PANIC("Unsupported mapper.");
else if (ret == -3)
RG_PANIC("BIOS file required.");
else if (ret < 0)
RG_PANIC("Unsupported ROM.");
nes->blit_func = blit_screen;
nsfPlayer = nes->cart->type == ROM_TYPE_NSF;
2024-02-29 23:37:08 -05:00
ppu_setopt(PPU_LIMIT_SPRITES, rg_settings_get_number(NS_APP, SETTING_SPRITELIMIT, 1));
build_palette(palette);
// This is necessary for successful state restoration
// I have not yet investigated why that is...
nes_emulate(false);
nes_emulate(false);
if (app->bootFlags & RG_BOOT_RESUME)
{
rg_emu_load_state(app->saveSlot);
}
rg_system_set_tick_rate(nes->refresh_rate);
int skipFrames = 0;
while (true)
{
uint32_t joystick = rg_input_read_gamepad();
if (joystick & (RG_KEY_MENU|RG_KEY_OPTION))
{
if (joystick & RG_KEY_MENU)
rg_gui_game_menu();
else
rg_gui_options_menu();
}
int64_t startTime = rg_system_timer();
bool drawFrame = !skipFrames && !nsfPlayer;
int buttons = 0;
if (joystick & RG_KEY_START) buttons |= NES_PAD_START;
if (joystick & RG_KEY_SELECT) buttons |= NES_PAD_SELECT;
if (joystick & RG_KEY_UP) buttons |= NES_PAD_UP;
if (joystick & RG_KEY_RIGHT) buttons |= NES_PAD_RIGHT;
if (joystick & RG_KEY_DOWN) buttons |= NES_PAD_DOWN;
if (joystick & RG_KEY_LEFT) buttons |= NES_PAD_LEFT;
if (joystick & RG_KEY_A) buttons |= NES_PAD_A;
if (joystick & RG_KEY_B) buttons |= NES_PAD_B;
if (drawFrame)
{
2024-02-28 16:35:41 -05:00
currentUpdate = updates[currentUpdate == updates[0]];
nes_setvidbuf(currentUpdate->data);
}
input_update(0, buttons);
nes_emulate(drawFrame);
// Tick before submitting audio/syncing
rg_system_tick(rg_system_timer() - startTime);
// Audio is used to pace emulation :)
rg_audio_submit((void*)nes->apu->buffer, nes->apu->samples_per_frame);
if (skipFrames == 0)
{
int frameTime = 1000000 / (nes->refresh_rate * app->speed);
int elapsed = rg_system_timer() - startTime;
if (nsfPlayer)
skipFrames = 10, nsf_draw_overlay();
else if (app->frameskip > 0)
skipFrames = app->frameskip;
else if (elapsed > frameTime + 1500) // Allow some jitter
skipFrames = 1; // (elapsed / frameTime)
else if (drawFrame && slowFrame)
skipFrames = 1;
}
else if (skipFrames > 0)
{
skipFrames--;
}
}
RG_PANIC("Nofrendo died!");
2020-01-31 16:13:27 -05:00
}