From a44530c98fff03e5d391c7f52a54af842ef51629 Mon Sep 17 00:00:00 2001 From: Alex Duchesne Date: Thu, 22 Feb 2024 00:04:03 -0500 Subject: [PATCH] rg_display+rg_system: Removed update mode setting and improved frame pacing Now that partial update is no longer glitchy, there is no reason to ever disable it. However, the `Update` setting was also indirectly doing one more thing: It imposed a frameskip of 1 when `Full` was selected. This did prevent stuttering in some games. But that's an indirect effect, and even I keep forgetting that it's the reason why it can counter-intuitively improve performance. So I think it's better to handle the frameskip directly instead. --- README.md | 4 -- components/retro-go/rg_display.c | 25 ++-------- components/retro-go/rg_display.h | 16 +----- components/retro-go/rg_gui.c | 44 ++++------------- components/retro-go/rg_system.c | 85 +++++++++++++++++--------------- components/retro-go/rg_system.h | 5 +- gwenesis/main/main.c | 49 +++++++++--------- prboom-go/main/main.c | 2 +- retro-core/main/main_gbc.c | 27 +++++----- retro-core/main/main_gw.c | 12 ++--- retro-core/main/main_lynx.cpp | 30 ++++++----- retro-core/main/main_nes.c | 34 +++++++------ retro-core/main/main_pce.c | 22 ++++++--- retro-core/main/main_sms.c | 47 +++++++++--------- snes9x-go/main/main.c | 53 +++++++++++--------- 15 files changed, 219 insertions(+), 236 deletions(-) diff --git a/README.md b/README.md index 8d7b0f6a..17a06f44 100644 --- a/README.md +++ b/README.md @@ -139,10 +139,6 @@ which allows high quality audio through headphones. You can switch to it in the Retro-Go typically detects and resolves application crashes and freezes automatically. However, if you do get stuck in a boot loop, you can hold `DOWN` while powering up the device to return to the launcher. -### Artifacts or tearing -Retro-Go uses partial screen updating to achieve a higher framerate and reduce tearing. This method isn't -perfect however, if you notice display issues or stuttering you can try changing the `Update` option. - ### Sound quality The volume isn't correctly attenuated on the GO, resulting in upper volume levels that are too loud and lower levels that are distorted due to DAC resolution. A quick way to improve the audio is to cut one diff --git a/components/retro-go/rg_display.c b/components/retro-go/rg_display.c index 557ca370..36588237 100644 --- a/components/retro-go/rg_display.c +++ b/components/retro-go/rg_display.c @@ -31,7 +31,6 @@ static const char *SETTING_BACKLIGHT = "DispBacklight"; static const char *SETTING_SCALING = "DispScaling"; static const char *SETTING_FILTER = "DispFilter"; static const char *SETTING_ROTATION = "DispRotation"; -static const char *SETTING_UPDATE = "DispUpdate"; static const char *SETTING_BORDER = "DispBorder"; static const char *SETTING_CUSTOM_WIDTH = "DispCustomWidth"; static const char *SETTING_CUSTOM_HEIGHT = "DispCustomHeight"; @@ -315,7 +314,7 @@ static inline unsigned blend_pixels(unsigned a, unsigned b) static inline void write_update(const rg_video_update_t *update) { - int64_t time_start = rg_system_timer(); + const int64_t time_start = rg_system_timer(); const int screen_width = display.screen.width; const int screen_height = display.screen.height; @@ -340,7 +339,7 @@ static inline void write_update(const rg_video_update_t *update) union {const uint8_t *u8; const uint16_t *u16; } buffer = {update->buffer + display.source.offset}; const uint16_t *palette = update->palette; - bool partial = config.update_mode == RG_DISPLAY_UPDATE_PARTIAL; + bool partial = true; // config.update_mode == RG_DISPLAY_UPDATE_PARTIAL; int lines_per_buffer = LCD_BUFFER_LENGTH / draw_width; int lines_remaining = draw_height; @@ -482,8 +481,7 @@ static inline void write_update(const rg_video_update_t *update) lines_remaining -= lines_to_copy; } - counters.lastFullFrame = lines_updated > display.screen.height * 0.75; - if (counters.lastFullFrame) + if (lines_updated > display.screen.height * 0.80) counters.fullFrames++; counters.totalFrames++; counters.busyTime += rg_system_timer() - time_start; @@ -644,21 +642,9 @@ const rg_display_t *rg_display_get_info(void) return &display; } -const rg_display_counters_t *rg_display_get_counters(void) +rg_display_counters_t rg_display_get_counters(void) { - return &counters; -} - -void rg_display_set_update_mode(display_update_t update_mode) -{ - config.update_mode = RG_MIN(RG_MAX(0, update_mode), RG_DISPLAY_UPDATE_COUNT - 1); - rg_settings_set_number(NS_APP, SETTING_UPDATE, config.update_mode); - display.changed = true; -} - -display_update_t rg_display_get_update_mode(void) -{ - return config.update_mode; + return counters; } void rg_display_set_scaling(display_scaling_t scaling) @@ -940,7 +926,6 @@ void rg_display_init(void) .scaling = rg_settings_get_number(NS_APP, SETTING_SCALING, RG_DISPLAY_SCALING_FULL), .filter = rg_settings_get_number(NS_APP, SETTING_FILTER, RG_DISPLAY_FILTER_BOTH), .rotation = rg_settings_get_number(NS_APP, SETTING_ROTATION, RG_DISPLAY_ROTATION_AUTO), - .update_mode = rg_settings_get_number(NS_APP, SETTING_UPDATE, RG_DISPLAY_UPDATE_PARTIAL), .border_file = rg_settings_get_string(NS_APP, SETTING_BORDER, NULL), .custom_width = rg_settings_get_number(NS_APP, SETTING_CUSTOM_WIDTH, 240), .custom_height = rg_settings_get_number(NS_APP, SETTING_CUSTOM_HEIGHT, 240), diff --git a/components/retro-go/rg_display.h b/components/retro-go/rg_display.h index 81987a3e..9196ac8e 100644 --- a/components/retro-go/rg_display.h +++ b/components/retro-go/rg_display.h @@ -3,15 +3,6 @@ #include #include -typedef enum -{ - RG_DISPLAY_UPDATE_PARTIAL = 0, - RG_DISPLAY_UPDATE_FULL, - // RG_DISPLAY_UPDATE_INTERLACE, - // RG_DISPLAY_UPDATE_SMART, - RG_DISPLAY_UPDATE_COUNT, -} display_update_t; - typedef enum { RG_DISPLAY_SCALING_OFF = 0, // No scaling, center image on screen @@ -73,7 +64,6 @@ typedef struct display_rotation_t rotation; display_scaling_t scaling; display_filter_t filter; - display_update_t update_mode; display_backlight_t backlight; char *border_file; int custom_width, custom_height; @@ -82,10 +72,8 @@ typedef struct typedef struct { int32_t totalFrames; - int32_t partFrames; int32_t fullFrames; int64_t busyTime; - bool lastFullFrame; } rg_display_counters_t; typedef struct @@ -144,7 +132,7 @@ bool rg_display_save_frame(const char *filename, const rg_video_update_t *frame, void rg_display_set_source_format(int width, int height, int crop_h, int crop_v, int stride, rg_pixel_flags_t format); void rg_display_submit(const rg_video_update_t *update, uint32_t flags); -const rg_display_counters_t *rg_display_get_counters(void); +rg_display_counters_t rg_display_get_counters(void); const rg_display_t *rg_display_get_info(void); void rg_display_set_scaling(display_scaling_t scaling); @@ -155,8 +143,6 @@ void rg_display_set_rotation(display_rotation_t rotation); display_rotation_t rg_display_get_rotation(void); void rg_display_set_backlight(display_backlight_t percent); display_backlight_t rg_display_get_backlight(void); -void rg_display_set_update_mode(display_update_t update); -display_update_t rg_display_get_update_mode(void); void rg_display_set_border(const char *filename); char *rg_display_get_border(void); void rg_display_set_custom_width(int width); diff --git a/components/retro-go/rg_gui.c b/components/retro-go/rg_gui.c index 6f17739b..038e3e5c 100644 --- a/components/retro-go/rg_gui.c +++ b/components/retro-go/rg_gui.c @@ -588,10 +588,10 @@ void rg_gui_draw_status_bars(void) if (!app->initialized || app->isLauncher) return; - snprintf(header, max_len, "SPEED: %d%% (%d/%d) / BUSY: %d%%", - (int)round(stats.totalFPS / app->refreshRate * 100.f), - (int)round(stats.totalFPS - stats.skippedFPS), - (int)round(stats.totalFPS), + snprintf(header, max_len, "SPEED: %d%% (%d:%d) / BUSY: %d%%", + (int)round(stats.totalFPS / app->tickRate * 100.f), + (int)app->frameskip, + (int)round(stats.skippedFPS), (int)round(stats.busyPercent)); if (app->romPath && strlen(app->romPath) > max_len - 1) @@ -1215,29 +1215,6 @@ static rg_gui_event_t custom_height_cb(rg_gui_option_t *option, rg_gui_event_t e return RG_DIALOG_VOID; } -static rg_gui_event_t update_mode_update_cb(rg_gui_option_t *option, rg_gui_event_t event) -{ - int max = RG_DISPLAY_UPDATE_COUNT - 1; - int mode = rg_display_get_update_mode(); - int prev_mode = mode; - - if (event == RG_DIALOG_PREV && --mode < 0) - mode = max; // 0; - if (event == RG_DIALOG_NEXT && ++mode > max) - mode = 0; // max; - - if (mode != prev_mode) - rg_display_set_update_mode(mode); - - if (mode == RG_DISPLAY_UPDATE_PARTIAL) - strcpy(option->value, "Partial"); - if (mode == RG_DISPLAY_UPDATE_FULL) - strcpy(option->value, "Full "); - // if (mode == RG_DISPLAY_UPDATE_INTERLACE) strcpy(option->value, "Interlace"); - - return RG_DIALOG_VOID; -} - static rg_gui_event_t speedup_update_cb(rg_gui_option_t *option, rg_gui_event_t event) { rg_app_t *app = rg_system_get_app(); @@ -1347,13 +1324,12 @@ void rg_gui_options_menu(void) // App settings that are shown only inside a game else { - *opt++ = (rg_gui_option_t){0, "Scaling", "-", RG_DIALOG_FLAG_NORMAL, &scaling_update_cb}; - *opt++ = (rg_gui_option_t){0, " Width", "-", RG_DIALOG_FLAG_HIDDEN, &custom_width_cb}; - *opt++ = (rg_gui_option_t){0, " Height", "-", RG_DIALOG_FLAG_HIDDEN, &custom_height_cb}; - *opt++ = (rg_gui_option_t){0, "Filter ", "-", RG_DIALOG_FLAG_NORMAL, &filter_update_cb}; - *opt++ = (rg_gui_option_t){0, "Update ", "-", RG_DIALOG_FLAG_NORMAL, &update_mode_update_cb}; - *opt++ = (rg_gui_option_t){0, "Border ", "-", RG_DIALOG_FLAG_NORMAL, &border_update_cb}; - *opt++ = (rg_gui_option_t){0, "Speed ", "-", RG_DIALOG_FLAG_NORMAL, &speedup_update_cb}; + *opt++ = (rg_gui_option_t){0, "Scaling", "-", RG_DIALOG_FLAG_NORMAL, &scaling_update_cb}; + *opt++ = (rg_gui_option_t){0, " Width", "-", RG_DIALOG_FLAG_HIDDEN, &custom_width_cb}; + *opt++ = (rg_gui_option_t){0, " Height", "-", RG_DIALOG_FLAG_HIDDEN, &custom_height_cb}; + *opt++ = (rg_gui_option_t){0, "Filter", "-", RG_DIALOG_FLAG_NORMAL, &filter_update_cb}; + *opt++ = (rg_gui_option_t){0, "Border", "-", RG_DIALOG_FLAG_NORMAL, &border_update_cb}; + *opt++ = (rg_gui_option_t){0, "Speed", "-", RG_DIALOG_FLAG_NORMAL, &speedup_update_cb}; } size_t extra_options = get_dialog_items_count(app->options); diff --git a/components/retro-go/rg_system.c b/components/retro-go/rg_system.c index 869f8bc0..8972e6f3 100644 --- a/components/retro-go/rg_system.c +++ b/components/retro-go/rg_system.c @@ -81,7 +81,7 @@ static rg_app_t app; static logbuf_t logbuf; static rg_task_t tasks[8]; static int ledValue = -1; -static int wdtCounter = 0; +static int64_t watchdogTimer = INT64_MAX; static bool exitCalled = false; static const char *SETTING_BOOT_NAME = "BootName"; @@ -90,7 +90,7 @@ static const char *SETTING_BOOT_FLAGS = "BootFlags"; static const char *SETTING_TIMEZONE = "Timezone"; #define WDT_TIMEOUT 8000000 -#define WDT_RELOAD(val) wdtCounter = (val) +#define WDT_RELOAD(val) watchdogTimer = rg_system_timer() + (val) #define logbuf_putc(buf, c) (buf)->buffer[(buf)->cursor++] = c, (buf)->cursor %= RG_LOGBUF_SIZE; #define logbuf_puts(buf, str) for (const char *ptr = str; *ptr; ptr++) logbuf_putc(buf, *ptr); @@ -138,8 +138,8 @@ static void update_statistics(void) static counters_t counters = {0}; const counters_t previous = counters; - rg_display_counters_t display = *rg_display_get_counters(); - // rg_audio_counters_t audio = *rg_audio_get_counters(); + rg_display_counters_t display = rg_display_get_counters(); + // rg_audio_counters_t audio = rg_audio_get_counters(); counters.totalFrames = display.totalFrames; counters.fullFrames = display.fullFrames; @@ -147,33 +147,34 @@ static void update_statistics(void) counters.ticks = statistics.ticks; counters.updateTime = rg_system_timer(); - float elapsedTime = (counters.updateTime - previous.updateTime) / 1000000.f; - statistics.busyPercent = RG_MIN((counters.busyTime - previous.busyTime) / (elapsedTime * 1000000.f) * 100.f, 100.f); - statistics.totalFPS = (counters.ticks - previous.ticks) / elapsedTime; - statistics.skippedFPS = statistics.totalFPS - ((counters.totalFrames - previous.totalFrames) / elapsedTime); - statistics.fullFPS = (counters.fullFrames - previous.fullFrames) / elapsedTime; + if (counters.ticks && previous.ticks) + { + float totalTime = counters.updateTime - previous.updateTime; + float totalTimeSecs = totalTime / 1000000.f; + float busyTime = counters.busyTime - previous.busyTime; + float ticks = counters.ticks - previous.ticks; + // Since we sample semi-randomly, display and ticks could be out of sync. + // RG_MIN won't prevent bogus skippedFPS=1, but at least no -1... + float fullFrames = RG_MIN(counters.fullFrames - previous.fullFrames, ticks); + float frames = RG_MIN(counters.totalFrames - previous.totalFrames, ticks); + + statistics.busyPercent = busyTime / totalTime * 100.f; + statistics.totalFPS = ticks / totalTimeSecs; + statistics.skippedFPS = (ticks - frames) / totalTimeSecs; + statistics.fullFPS = fullFrames / totalTimeSecs; + } update_memory_statistics(); } static void system_monitor_task(void *arg) { - int64_t lastLoop = rg_system_timer(); int32_t numLoop = 0; float batteryPercent = 0.f; bool ledState = false; - // Give the app a few seconds to start before monitoring - rg_task_delay(2500); - WDT_RELOAD(WDT_TIMEOUT); - while (!exitCalled) { - int loopTime_us = rg_system_timer() - lastLoop; - lastLoop = rg_system_timer(); - rtcValue = time(NULL); - - // Maybe we should *try* to wait for vsync before updating? update_statistics(); if (rg_input_read_battery(&batteryPercent, NULL)) @@ -198,30 +199,35 @@ static void system_monitor_task(void *arg) (int)(statistics.fullFPS + 0.5f), (int)(batteryPercent + 0.5f)); - if ((wdtCounter -= loopTime_us) <= 0) + if (watchdogTimer <= rg_system_timer()) { - if ((lastLoop - statistics.lastTick) > WDT_TIMEOUT) - { - if (app.watchdog) - RG_PANIC("Application unresponsive!"); - else - RG_LOGW("Application unresponsive!"); - } + if (app.watchdog) + RG_PANIC("Application unresponsive!"); + else + RG_LOGW("Application unresponsive!"); WDT_RELOAD(WDT_TIMEOUT); } - #ifdef RG_ENABLE_PROFILING - if ((numLoop % 10) == 0) - rg_system_dump_profile(); - #endif - - // if ((numLoop % 300) == 299) - // { - // RG_LOGI("Saving system time"); - // rg_system_save_time(); - // } + // Auto frameskip + if (statistics.ticks > app.tickRate) + { + float speed = ((float)statistics.totalFPS / app.tickRate) * 100.f / app.speed; + // We don't fully go back to 0 frameskip because if we dip below 95% once, we're clearly + // borderline in power and going back to 0 is just asking for stuttering... + if (speed > 99.f && statistics.busyPercent < 90.f && app.frameskip > 1) + { + app.frameskip--; + RG_LOGI("Reduced frameskip to %d", app.frameskip); + } + else if (speed < 95.f && statistics.busyPercent > 90.f && app.frameskip < 4) + { + app.frameskip++; + RG_LOGI("Raised frameskip to %d", app.frameskip); + } + } rg_task_delay(1000); + rtcValue = time(NULL); numLoop++; } } @@ -302,8 +308,9 @@ rg_app_t *rg_system_init(int sampleRate, const rg_handlers_t *handlers, const rg .bootFlags = 0, .bootType = RG_RST_POWERON, .speed = 1.f, - .refreshRate = 60, .sampleRate = sampleRate, + .tickRate = 60, + .frameskip = 0, .overclock = 0, .watchdog = 1, .logLevel = RG_LOG_INFO, @@ -562,7 +569,7 @@ void rg_system_tick(int busyTime) statistics.lastTick = rg_system_timer(); statistics.busyTime += busyTime; statistics.ticks++; - // WDT_RELOAD(WDT_TIMEOUT); + WDT_RELOAD(WDT_TIMEOUT); } IRAM_ATTR int64_t rg_system_timer(void) diff --git a/components/retro-go/rg_system.h b/components/retro-go/rg_system.h index 744fc9b7..0924ef75 100644 --- a/components/retro-go/rg_system.h +++ b/components/retro-go/rg_system.h @@ -4,6 +4,7 @@ extern "C" { #endif +#include #include #include #include @@ -155,8 +156,9 @@ typedef struct uint32_t bootFlags; uint32_t bootType; float speed; - int refreshRate; int sampleRate; + int tickRate; + int frameskip; int overclock; int watchdog; int logLevel; @@ -243,6 +245,7 @@ rg_emu_state_t *rg_emu_get_states(const char *romPath, size_t slots); #define RG_LOGE(x, ...) rg_system_log(RG_LOG_ERROR, RG_LOG_TAG, x, ## __VA_ARGS__) #define RG_LOGW(x, ...) rg_system_log(RG_LOG_WARN, RG_LOG_TAG, x, ## __VA_ARGS__) #define RG_LOGI(x, ...) rg_system_log(RG_LOG_INFO, RG_LOG_TAG, x, ## __VA_ARGS__) +#define RG_LOGU(x, ...) rg_system_log(RG_LOG_USER, RG_LOG_TAG, x, ## __VA_ARGS__) #define RG_LOGD(x, ...) rg_system_log(RG_LOG_DEBUG, RG_LOG_TAG, x, ## __VA_ARGS__) void __cyg_profile_func_enter(void *this_fn, void *call_site); diff --git a/gwenesis/main/main.c b/gwenesis/main/main.c index 52a24880..91914d68 100644 --- a/gwenesis/main/main.c +++ b/gwenesis/main/main.c @@ -35,7 +35,6 @@ static rg_app_t *app; static bool yfm_enabled = true; static bool z80_enabled = true; static bool sn76489_enabled = true; -static int frameskip = 3; static FILE *savestate_fp = NULL; static int savestate_errors = 0; @@ -43,7 +42,6 @@ static int savestate_errors = 0; static const char *SETTING_YFM_EMULATION = "yfm_enable"; static const char *SETTING_Z80_EMULATION = "z80_enable"; static const char *SETTING_SN76489_EMULATION = "sn_enable"; -static const char *SETTING_FRAMESKIP = "frameskip"; // --- MAIN @@ -156,20 +154,6 @@ static rg_gui_event_t z80_update_cb(rg_gui_option_t *option, rg_gui_event_t even return RG_DIALOG_VOID; } -static rg_gui_event_t frameskip_cb(rg_gui_option_t *option, rg_gui_event_t event) -{ - if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) - { - frameskip += (event == RG_DIALOG_PREV) ? -1 : 1; - frameskip = RG_MAX(frameskip, 1); - rg_settings_set_number(NS_APP, SETTING_FRAMESKIP, frameskip); - } - - sprintf(option->value, "%d", frameskip); - - return RG_DIALOG_VOID; -} - static bool screenshot_handler(const char *filename, int width, int height) { return rg_display_save_frame(filename, currentUpdate, width, height); @@ -228,7 +212,6 @@ void app_main(void) {1, "YFM emulation", "On", 1, &yfm_update_cb}, {1, "SN76489 emulation", "On", 1, &sn76489_update_cb}, {3, "Z80 emulation", "On", 1, &z80_update_cb}, - {2, "Frameskip", "", 1, &frameskip_cb}, RG_DIALOG_END }; @@ -237,7 +220,6 @@ void app_main(void) yfm_enabled = rg_settings_get_number(NS_APP, SETTING_YFM_EMULATION, 1); sn76489_enabled = rg_settings_get_number(NS_APP, SETTING_SN76489_EMULATION, 0); z80_enabled = rg_settings_get_number(NS_APP, SETTING_Z80_EMULATION, 1); - frameskip = rg_settings_get_number(NS_APP, SETTING_FRAMESKIP, frameskip); updates[0].buffer = rg_alloc(320 * 240 + 64, MEM_FAST) + 32; // updates[1].buffer = rg_alloc(320 * 240 + 64, MEM_FAST) + 32; @@ -271,6 +253,9 @@ void app_main(void) rg_emu_load_state(app->saveSlot); } + app->tickRate = 60; + app->frameskip = 3; + extern unsigned char gwenesis_vdp_regs[0x20]; extern unsigned int gwenesis_vdp_status; extern unsigned short CRAM565[256]; @@ -281,10 +266,11 @@ void app_main(void) uint32_t keymap[8] = {RG_KEY_UP, RG_KEY_DOWN, RG_KEY_LEFT, RG_KEY_RIGHT, RG_KEY_A, RG_KEY_B, RG_KEY_SELECT, RG_KEY_START}; uint32_t joystick = 0, joystick_old; - uint32_t frames = 0; RG_LOGI("rg_display_set_source_format()\n"); + int skipFrames = 0; + RG_LOGI("emulation loop\n"); while (true) { @@ -310,7 +296,8 @@ void app_main(void) } int64_t startTime = rg_system_timer(); - bool drawFrame = (frames++ % frameskip) == 0; + bool drawFrame = skipFrames == 0; + bool slowFrame = false; int lines_per_frame = REG1_PAL ? LINES_PER_FRAME_PAL : LINES_PER_FRAME_NTSC; int hint_counter = gwenesis_vdp_regs[10]; @@ -411,15 +398,33 @@ void app_main(void) { for (int i = 0; i < 256; ++i) currentUpdate->palette[i] = (CRAM565[i] << 8) | (CRAM565[i] >> 8); + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); } - int elapsed = rg_system_timer() - startTime; - rg_system_tick(elapsed); + rg_system_tick(rg_system_timer() - startTime); if (yfm_enabled || z80_enabled) { // TODO: Mix in gwenesis_sn76489_buffer rg_audio_submit((void *)gwenesis_ym2612_buffer, AUDIO_BUFFER_LENGTH >> 1); } + + if (skipFrames == 0) + { + int frameTime = 1000000 / (app->tickRate * app->speed); + int elapsed = rg_system_timer() - startTime; + if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (elapsed > frameTime + 1500) // Allow some jitter + skipFrames = (elapsed + frameTime / 2) / frameTime; + else if (drawFrame && slowFrame) + skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; + } + else if (skipFrames > 0) + { + skipFrames--; + } } } diff --git a/prboom-go/main/main.c b/prboom-go/main/main.c index 99f99094..e4451661 100644 --- a/prboom-go/main/main.c +++ b/prboom-go/main/main.c @@ -533,7 +533,7 @@ void app_main() }; app = rg_system_init(AUDIO_SAMPLE_RATE, &handlers, options); - app->refreshRate = TICRATE; + app->tickRate = TICRATE; update.buffer = rg_alloc(SCREENHEIGHT*SCREENWIDTH, MEM_FAST); diff --git a/retro-core/main/main_gbc.c b/retro-core/main/main_gbc.c index 8bfbead2..780a65ca 100644 --- a/retro-core/main/main_gbc.c +++ b/retro-core/main/main_gbc.c @@ -4,6 +4,7 @@ #include static int skipFrames = 20; // The 20 is to hide startup flicker in some games +static bool slowFrame = false; static int video_time; static int audio_time; @@ -195,6 +196,7 @@ static rg_gui_event_t rtc_update_cb(rg_gui_option_t *option, rg_gui_event_t even static void video_callback(void *buffer) { int64_t startTime = rg_system_timer(); + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); video_time += rg_system_timer() - startTime; } @@ -273,8 +275,8 @@ void gbc_main(void) // Ready! - int joystick_old = -1; - int joystick = 0; + uint32_t joystick_old = -1; + uint32_t joystick = 0; while (true) { @@ -334,26 +336,25 @@ void gbc_main(void) } } - int elapsed = rg_system_timer() - startTime; - elapsed -= audio_time; + // Tick before submitting audio/syncing + rg_system_tick(rg_system_timer() - startTime - audio_time); if (skipFrames == 0) { - // This is all nonsense, we should sample lag on a per second basis or something... - int frameTime = 1000000 / (60 * app->speed); - if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer + int frameTime = 1000000 / (app->tickRate * app->speed); + int elapsed = rg_system_timer() - startTime; + if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (elapsed > frameTime + 1500) // Allow some jitter skipFrames = (elapsed + frameTime / 2) / frameTime; - else if (drawFrame && rg_display_get_counters()->lastFullFrame) // This could be avoided when scaling != full + else if (drawFrame && slowFrame) skipFrames = 1; - if (app->speed > 1.f) // This is a hack until we account for audio speed... - skipFrames += (int)app->speed; + if (app->speed > 1.f) + skipFrames += 2; } else if (skipFrames > 0) { skipFrames--; } - - // Tick before submitting audio/syncing - rg_system_tick(elapsed); } } diff --git a/retro-core/main/main_gw.c b/retro-core/main/main_gw.c index abf2dded..b6f8440c 100644 --- a/retro-core/main/main_gw.c +++ b/retro-core/main/main_gw.c @@ -136,7 +136,7 @@ void gw_main(void) }; app = rg_system_reinit(AUDIO_SAMPLE_RATE, &handlers, options); - app->refreshRate = GW_REFRESH_RATE; + app->tickRate = GW_REFRESH_RATE; updates[0].buffer = malloc(GW_SCREEN_WIDTH * GW_SCREEN_HEIGHT * 2); @@ -260,17 +260,17 @@ void gw_main(void) // to execute on the emulated device gw_system_run(GW_SYSTEM_CYCLES); - /* update the screen only if there is no pending frame to render */ - if (rg_display_sync(0) && drawFrame) + // Our refresh rate is 128Hz, which is way too fast for our display + // so make sure the previous frame is done sending before queuing a new one + if (rg_display_sync(false) && drawFrame) { gw_system_blit(currentUpdate->buffer); rg_display_submit(currentUpdate, 0); } /****************************************************************************/ - int elapsed = rg_system_timer() - startTime; - - rg_system_tick(elapsed); + // Tick before submitting audio/syncing + rg_system_tick(rg_system_timer() - startTime); /* copy audio samples for DMA */ rg_audio_sample_t mixbuffer[GW_AUDIO_BUFFER_LENGTH]; diff --git a/retro-core/main/main_lynx.cpp b/retro-core/main/main_lynx.cpp index 0b6c2934..0380ac53 100644 --- a/retro-core/main/main_lynx.cpp +++ b/retro-core/main/main_lynx.cpp @@ -178,7 +178,7 @@ extern "C" void lynx_main(void) updates[1].buffer = (void*)rg_alloc(HANDY_SCREEN_WIDTH * HANDY_SCREEN_WIDTH * 2, MEM_FAST); // The Lynx has a variable framerate but 60 is typical - app->refreshRate = 60; + app->tickRate = 60; // Init emulator lynx = new CSystem(app->romPath, MIKIE_PIXEL_FORMAT_16BPP_565_BE, app->sampleRate); @@ -201,6 +201,7 @@ extern "C" void lynx_main(void) float sampleTime = 1000000.f / app->sampleRate; long skipFrames = 0; + bool slowFrame = false; // Start emulation while (1) @@ -231,37 +232,40 @@ extern "C" void lynx_main(void) if (joystick & RG_KEY_SELECT) buttons |= BUTTON_OPT1; lynx->SetButtonData(buttons); - lynx->UpdateFrame(drawFrame); if (drawFrame) { + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); currentUpdate = &updates[currentUpdate == &updates[0]]; gPrimaryFrameBuffer = (UBYTE*)currentUpdate->buffer; } - int elapsed = rg_system_timer() - startTime; + app->tickRate = AUDIO_SAMPLE_RATE / (gAudioBufferPointer / 2); + rg_system_tick(rg_system_timer() - startTime); + + rg_audio_submit(audioBuffer, gAudioBufferPointer >> 1); // See if we need to skip a frame to keep up if (skipFrames == 0) { - if (app->speed > 1.f) - skipFrames += (int)app->speed * 2; + int frameTime = ((gAudioBufferPointer / 2) * sampleTime); + int elapsed = rg_system_timer() - startTime; + if (app->frameskip > 0) + skipFrames = app->frameskip; // The Lynx uses a variable framerate so we use the count of generated audio samples as reference instead - else if (elapsed > ((gAudioBufferPointer / 2) * sampleTime)) - skipFrames += 1; - else if (drawFrame && rg_display_get_counters()->lastFullFrame) // This could be avoided when scaling != full - skipFrames += 1; + else if (elapsed > frameTime + 1500) + skipFrames = 1; + else if (drawFrame && slowFrame) + skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; } else if (skipFrames > 0) { skipFrames--; } - - rg_system_tick(elapsed); - - rg_audio_submit(audioBuffer, gAudioBufferPointer >> 1); gAudioBufferPointer = 0; } } diff --git a/retro-core/main/main_nes.c b/retro-core/main/main_nes.c index 25c9a5ef..03e2c0d7 100644 --- a/retro-core/main/main_nes.c +++ b/retro-core/main/main_nes.c @@ -7,6 +7,7 @@ static int overscan = true; static int autocrop = 0; static int palette = 0; static int crop_h, crop_v; +static bool slowFrame = false; static nes_t *nes; static const char *SETTING_AUTOCROP = "autocrop"; @@ -161,6 +162,7 @@ static void blit_screen(uint8 *bmp) // 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; currentUpdate->buffer = NES_SCREEN_GETPTR(bmp, crop_h, crop_v); + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); } @@ -220,7 +222,7 @@ void nes_main(void) else if (ret < 0) RG_PANIC("Unsupported ROM."); - app->refreshRate = nes->refresh_rate; + app->tickRate = nes->refresh_rate; nes->blit_func = blit_screen; ppu_setopt(PPU_LIMIT_SPRITES, rg_settings_get_number(NS_APP, SETTING_SPRITELIMIT, 1)); @@ -277,31 +279,31 @@ void nes_main(void) input_update(0, buttons); nes_emulate(drawFrame); - int elapsed = rg_system_timer() - startTime; + // 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); - if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer - skipFrames = (elapsed + frameTime / 2) / frameTime; - else if (drawFrame && rg_display_get_counters()->lastFullFrame) - skipFrames = 1; - else if (nsfPlayer) + int elapsed = rg_system_timer() - startTime; + if (nsfPlayer) skipFrames = 10, nsf_draw_overlay(); - - if (app->speed > 1.f) // This is a hack until we account for audio speed... - skipFrames += (int)app->speed; + else if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (elapsed > frameTime + 1500) // Allow some jitter + skipFrames = (elapsed + frameTime / 2) / frameTime; + else if (drawFrame && slowFrame) + skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; } else if (skipFrames > 0) { skipFrames--; } - - // Tick before submitting audio/syncing - rg_system_tick(elapsed); - - // Audio is used to pace emulation :) - rg_audio_submit((void*)nes->apu->buffer, nes->apu->samples_per_frame); } RG_PANIC("Nofrendo died!"); diff --git a/retro-core/main/main_pce.c b/retro-core/main/main_pce.c index 1c4df47d..fa3cd39c 100644 --- a/retro-core/main/main_pce.c +++ b/retro-core/main/main_pce.c @@ -17,6 +17,8 @@ static int current_height = 0; static int current_width = 0; static int overscan = false; static int skipFrames = 0; +static bool drawFrame = true; +static bool slowFrame = false; static uint8_t *framebuffers[2]; static const char *SETTING_OVERSCAN = "overscan"; @@ -54,15 +56,16 @@ uint8_t *osd_gfx_framebuffer(int width, int height) current_width = width; current_height = height; } - return skipFrames ? NULL : currentUpdate->buffer; + return drawFrame ? currentUpdate->buffer : NULL; } void osd_vsync(void) { static int64_t lasttime, prevtime; - if (skipFrames == 0) + if (drawFrame) { + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); currentUpdate = &updates[currentUpdate == &updates[0]]; } @@ -70,10 +73,12 @@ void osd_vsync(void) // See if we need to skip a frame to keep up if (skipFrames == 0) { - if (app->speed > 1.f) - skipFrames = app->speed * 2.5f; - else + if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (drawFrame && slowFrame) skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; } else if (skipFrames > 0) { @@ -81,7 +86,7 @@ void osd_vsync(void) } int64_t curtime = rg_system_timer(); - int frameTime = 1000000 / 60 / app->speed; + int frameTime = 1000000 / (app->tickRate * app->speed); int sleep = frameTime - (curtime - lasttime); if (sleep > frameTime) @@ -104,6 +109,8 @@ void osd_vsync(void) if ((lasttime + frameTime) < prevtime) lasttime = prevtime; + + drawFrame = (skipFrames == 0); } void osd_input_read(uint8_t joypads[8]) @@ -227,6 +234,9 @@ void pce_main(void) rg_emu_load_state(app->saveSlot); } + app->tickRate = 60; + app->frameskip = 1; + emulationPaused = false; RunPCE(); diff --git a/retro-core/main/main_sms.c b/retro-core/main/main_sms.c index 917d4f5d..704f9f3c 100644 --- a/retro-core/main/main_sms.c +++ b/retro-core/main/main_sms.c @@ -167,7 +167,7 @@ void sms_main(void) system_poweron(); - app->refreshRate = (sms.display == DISPLAY_NTSC) ? FPS_NTSC : FPS_PAL; + app->tickRate = (sms.display == DISPLAY_NTSC) ? FPS_NTSC : FPS_PAL; updates[0].buffer += bitmap.viewport.x; updates[1].buffer += bitmap.viewport.x; @@ -196,6 +196,7 @@ void sms_main(void) int64_t startTime = rg_system_timer(); bool drawFrame = !skipFrames; + bool slowFrame = false; input.pad[0] = 0x00; input.pad[1] = 0x00; @@ -310,32 +311,12 @@ void sms_main(void) { if (render_copy_palette(currentUpdate->palette)) memcpy(&updates[currentUpdate == &updates[0]].palette, currentUpdate->palette, 512); + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); currentUpdate = &updates[currentUpdate == &updates[0]]; // Swap bitmap.data = currentUpdate->buffer - bitmap.viewport.x; } - int elapsed = rg_system_timer() - startTime; - - // See if we need to skip a frame to keep up - if (skipFrames == 0) - { - int frameTime = 1000000 / (app->refreshRate * app->speed); - if (elapsed > frameTime - 2000) // It takes about 2ms to copy the audio buffer - skipFrames = (elapsed + frameTime / 2) / frameTime; - else if (drawFrame && rg_display_get_counters()->lastFullFrame) - skipFrames = 1; - if (app->speed > 1.f) // This is a hack until we account for audio speed... - skipFrames += (int)app->speed; - } - else if (skipFrames > 0) - { - skipFrames--; - } - - // Tick before submitting audio/syncing - rg_system_tick(elapsed); - // The emulator's sound buffer isn't in a very convenient format, we must remix it. size_t sample_count = snd.sample_count; rg_audio_sample_t mixbuffer[sample_count]; @@ -345,7 +326,29 @@ void sms_main(void) mixbuffer[i].right = snd.stream[1][i] * 2.75f; } + // Tick before submitting audio/syncing + rg_system_tick(rg_system_timer() - startTime); + // Audio is used to pace emulation :) rg_audio_submit(mixbuffer, sample_count); + + // See if we need to skip a frame to keep up + if (skipFrames == 0) + { + int frameTime = 1000000 / (app->tickRate * app->speed); + int elapsed = rg_system_timer() - startTime; + if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (elapsed > frameTime + 1500) // Allow some jitter + skipFrames = (elapsed + frameTime / 2) / frameTime; + else if (drawFrame && slowFrame) + skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; + } + else if (skipFrames > 0) + { + skipFrames--; + } } } diff --git a/snes9x-go/main/main.c b/snes9x-go/main/main.c index 10bbf23f..e63504d4 100644 --- a/snes9x-go/main/main.c +++ b/snes9x-go/main/main.c @@ -27,7 +27,6 @@ static rg_app_t *app; static bool apu_enabled = true; static bool lowpass_filter = false; -static int frameskip = 4; bool overclock_cycles = false; int one_c = 4, slow_one_c = 5, two_c = 6; @@ -35,7 +34,6 @@ int one_c = 4, slow_one_c = 5, two_c = 6; static int keymap_id = 0; static keymap_t keymap; -static const char *SETTING_FRAMESKIP = "frameskip"; static const char *SETTING_KEYMAP = "keymap"; static const char *SETTING_APU_EMULATION = "apu"; // --- MAIN @@ -88,20 +86,6 @@ static rg_gui_event_t apu_toggle_cb(rg_gui_option_t *option, rg_gui_event_t even return RG_DIALOG_VOID; } -static rg_gui_event_t frameskip_cb(rg_gui_option_t *option, rg_gui_event_t event) -{ - if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) - { - frameskip += (event == RG_DIALOG_PREV) ? -1 : 1; - frameskip = RG_MAX(frameskip, 1); - rg_settings_set_number(NS_APP, SETTING_FRAMESKIP, frameskip); - } - - sprintf(option->value, "%d", frameskip); - - return RG_DIALOG_VOID; -} - static rg_gui_event_t lowpass_filter_cb(rg_gui_option_t *option, rg_gui_event_t event) { if (event == RG_DIALOG_PREV || event == RG_DIALOG_NEXT) @@ -279,13 +263,11 @@ void app_main(void) const rg_gui_option_t options[] = { {2, "Audio enable", (char *)"", 1, &apu_toggle_cb}, {2, "Audio filter", (char*)"", 1, &lowpass_filter_cb}, - {2, "Frameskip", (char *)"", 1, &frameskip_cb}, {2, "Controls", (char *)"", 1, &menu_keymap_cb}, RG_DIALOG_END, }; app = rg_system_init(AUDIO_SAMPLE_RATE, &handlers, options); - frameskip = rg_settings_get_number(NS_APP, SETTING_FRAMESKIP, frameskip); apu_enabled = rg_settings_get_number(NS_APP, SETTING_APU_EMULATION, 1); updates[0].buffer = malloc(SNES_WIDTH * SNES_HEIGHT_EXTENDED * 2); @@ -337,11 +319,12 @@ void app_main(void) rg_emu_load_state(app->saveSlot); } - app->refreshRate = Memory.ROMFramesPerSecond; + app->tickRate = Memory.ROMFramesPerSecond; + app->frameskip = 3; bool menuCancelled = false; bool menuPressed = false; - int frames = 0; + int skipFrames = 0; while (1) { @@ -371,13 +354,19 @@ void app_main(void) } int64_t startTime = rg_system_timer(); + bool drawFrame = (skipFrames == 0); + bool slowFrame = false; - IPPU.RenderThisFrame = (frames++ % frameskip) == 0; + IPPU.RenderThisFrame = drawFrame; GFX.Screen = currentUpdate->buffer; + S9xMainLoop(); - if (IPPU.RenderThisFrame) + if (drawFrame) + { + slowFrame = !rg_display_sync(false); rg_display_submit(currentUpdate, 0); + } #ifndef USE_BLARGG_APU if (apu_enabled && lowpass_filter) @@ -386,13 +375,29 @@ void app_main(void) S9xMixSamples((void *)mixbuffer, AUDIO_BUFFER_LENGTH << 1); #endif - int elapsed = rg_system_timer() - startTime; + rg_system_tick(rg_system_timer() - startTime); #ifndef USE_BLARGG_APU if (apu_enabled) rg_audio_submit(mixbuffer, AUDIO_BUFFER_LENGTH); #endif - rg_system_tick(elapsed); + if (skipFrames == 0) + { + int frameTime = 1000000 / (app->tickRate * app->speed); + int elapsed = rg_system_timer() - startTime; + if (app->frameskip > 0) + skipFrames = app->frameskip; + else if (elapsed > frameTime + 1500) // Allow some jitter + skipFrames = (elapsed + frameTime / 2) / frameTime; + else if (drawFrame && slowFrame) + skipFrames = 1; + if (app->speed > 1.f) + skipFrames += 2; + } + else if (skipFrames > 0) + { + skipFrames--; + } } }