From 8c77e5ccdc5b015d38213cda3aa4c5b576af98b5 Mon Sep 17 00:00:00 2001 From: Arignir Date: Thu, 14 Mar 2024 21:37:27 +0100 Subject: [PATCH] Add support for fractional speed (25%, 50%, etc.) --- include/app/app.h | 11 ++++-- include/gba/event.h | 4 +- include/gba/gba.h | 6 ++- include/gba/scheduler.h | 13 +++++-- source/app/bindings.c | 26 +++++++------ source/app/config.c | 7 ++-- source/app/emulator.c | 13 +++++-- source/app/main.c | 2 +- source/app/windows/menubar.c | 36 ++++++++++++------ source/app/windows/settings.c | 69 ++++++++++++++++++++++++++--------- source/gba/gba.c | 4 +- source/gba/scheduler.c | 18 +++++++-- 12 files changed, 142 insertions(+), 67 deletions(-) diff --git a/include/app/app.h b/include/app/app.h index 5101bc0..31d7125 100644 --- a/include/app/app.h +++ b/include/app/app.h @@ -17,7 +17,6 @@ #include #include #include -#include "hades.h" #include "gba/gba.h" #define GLSL(src) "#version 330 core\n" #src @@ -87,6 +86,8 @@ enum bind_actions { BIND_EMULATOR_PAUSE, BIND_EMULATOR_STOP, BIND_EMULATOR_RESET, + BIND_EMULATOR_SPEED_X0_25, + BIND_EMULATOR_SPEED_X0_50, BIND_EMULATOR_SPEED_X1, BIND_EMULATOR_SPEED_X2, BIND_EMULATOR_SPEED_X3, @@ -175,10 +176,12 @@ struct app { // Current FPS uint32_t fps; - // Speed - uint32_t speed; + // Fast forward bool fast_forward; + // Speed + float speed; + // Skip BIOS bool skip_bios; @@ -457,7 +460,7 @@ void app_emulator_run(struct app *app); void app_emulator_pause(struct app *app); void app_emulator_exit(struct app *app); void app_emulator_key(struct app *app, enum keys key, bool pressed); -void app_emulator_speed(struct app *app, uint32_t); +void app_emulator_speed(struct app *app, bool, float); void app_emulator_update_backup(struct app *app); void app_emulator_screenshot(struct app *app); void app_emulator_screenshot_path(struct app *app, char const *); diff --git a/include/gba/event.h b/include/gba/event.h index bfc4cb5..7a99ae2 100644 --- a/include/gba/event.h +++ b/include/gba/event.h @@ -9,7 +9,6 @@ #pragma once -#include "hades.h" #include "gba/gba.h" /* @@ -52,7 +51,8 @@ struct message_reset { struct message_speed { struct event_header header; - uint32_t speed; + bool fast_forward; + float speed; }; struct message_key { diff --git a/include/gba/gba.h b/include/gba/gba.h index 606c272..054cb9c 100644 --- a/include/gba/gba.h +++ b/include/gba/gba.h @@ -123,8 +123,10 @@ struct launch_config { // True if the BIOS should be skipped bool skip_bios; - // Speed. 0 = unlimited, 1 = 60fps, 2 = 120fps, etc. - uint32_t speed; + bool fast_forward; // Fast forward + + float speed; // Speed. 0.5 = 30fps, 1 = 60fps, 2 = 120fps, etc. + // Can't be 0 unless `fast_forward` is true. // Set to the frontend's audio frequency. // Can be 0 if the frontend has no audio. diff --git a/include/gba/scheduler.h b/include/gba/scheduler.h index 2d50a5a..b6e08b3 100644 --- a/include/gba/scheduler.h +++ b/include/gba/scheduler.h @@ -9,6 +9,8 @@ #pragma once +#include "hades.h" + #define INVALID_EVENT_HANDLE ((size_t)(-1)) typedef size_t event_handler_t; @@ -69,10 +71,13 @@ struct scheduler { struct scheduler_event *events; size_t events_size; - uint32_t speed; // Speed. 0 = unlimited, 1 = 60fps, 2 = 120fps, etc. + bool fast_forward; // Fast forward + + float speed; // Speed. 0.5 = 30fps, 1 = 60fps, 2 = 120fps, etc. + // Can't be 0 unless `fast_forward` is true. - uint64_t time_per_frame; - uint64_t time_last_frame; + uint64_t time_per_frame; // In usec + uint64_t time_last_frame; // In usec uint64_t accumulated_time; }; @@ -133,4 +138,4 @@ void sched_process_events(struct gba *gba); void sched_run_for(struct gba *gba, uint64_t cycles); void sched_frame_limiter(struct gba *gba,struct event_args args); void sched_reset_frame_limiter(struct gba *gba); -void sched_update_speed(struct gba *gba, uint32_t speed); +void sched_update_speed(struct gba *gba, bool fast_forward, float speed); diff --git a/source/app/bindings.c b/source/app/bindings.c index 66667e6..1b372f2 100644 --- a/source/app/bindings.c +++ b/source/app/bindings.c @@ -10,7 +10,6 @@ #include #include #include -#include "hades.h" #include "app/app.h" void @@ -39,12 +38,6 @@ app_bindings_setup_default( app->binds.keyboard[BIND_EMULATOR_MUTE] = SDL_GetKeyFromName("M"); app->binds.keyboard[BIND_EMULATOR_SCREENSHOT] = SDL_GetKeyFromName("F2"); app->binds.keyboard[BIND_EMULATOR_PAUSE] = SDL_GetKeyFromName("F3"); - app->binds.keyboard[BIND_EMULATOR_SPEED_X1] = SDL_GetKeyFromName("1"); - app->binds.keyboard[BIND_EMULATOR_SPEED_X2] = SDL_GetKeyFromName("2"); - app->binds.keyboard[BIND_EMULATOR_SPEED_X3] = SDL_GetKeyFromName("3"); - app->binds.keyboard[BIND_EMULATOR_SPEED_X4] = SDL_GetKeyFromName("4"); - app->binds.keyboard[BIND_EMULATOR_SPEED_X5] = SDL_GetKeyFromName("5"); - app->binds.keyboard[BIND_EMULATOR_FAST_FORWARD_TOGGLE] = SDL_GetKeyFromName("0"); app->binds.keyboard[BIND_EMULATOR_FAST_FORWARD_HOLD] = SDL_GetKeyFromName("Space"); app->binds.keyboard[BIND_EMULATOR_QUICKSAVE_1] = SDL_GetKeyFromName("F5"); app->binds.keyboard[BIND_EMULATOR_QUICKLOAD_1] = SDL_GetKeyFromName("F8"); @@ -139,7 +132,7 @@ app_bindings_handle( case BIND_GBA_START: app_emulator_key(app, KEY_START, pressed); break; case BIND_EMULATOR_FAST_FORWARD_HOLD: { app->emulation.fast_forward = pressed; - app_emulator_speed(app, app->emulation.fast_forward ? 0 : app->emulation.speed); + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); break; }; default: break; @@ -156,19 +149,30 @@ app_bindings_handle( case BIND_EMULATOR_PAUSE: app->emulation.is_running ? app_emulator_pause(app) : app_emulator_run(app); break; case BIND_EMULATOR_STOP: app_emulator_stop(app); break; case BIND_EMULATOR_RESET: app_emulator_reset(app); break; + case BIND_EMULATOR_SPEED_X0_25: + case BIND_EMULATOR_SPEED_X0_50: case BIND_EMULATOR_SPEED_X1: case BIND_EMULATOR_SPEED_X2: case BIND_EMULATOR_SPEED_X3: case BIND_EMULATOR_SPEED_X4: case BIND_EMULATOR_SPEED_X5: { + float speeds[] = { + 0.25f, + 0.50f, + 1.00f, + 2.00f, + 3.00f, + 4.00f, + 5.00f, + }; app->emulation.fast_forward = false; - app->emulation.speed = 1 + (bind - BIND_EMULATOR_SPEED_X1); - app_emulator_speed(app, app->emulation.speed); + app->emulation.speed = speeds[bind - BIND_EMULATOR_SPEED_X0_25]; + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); break; }; case BIND_EMULATOR_FAST_FORWARD_TOGGLE: { app->emulation.fast_forward ^= true; - app_emulator_speed(app, app->emulation.fast_forward ? 0 : app->emulation.speed); + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); break; } case BIND_EMULATOR_QUICKSAVE_1: diff --git a/source/app/config.c b/source/app/config.c index 1a28d29..77f466e 100644 --- a/source/app/config.c +++ b/source/app/config.c @@ -67,8 +67,7 @@ app_config_load( double d; if (mjson_get_number(data, data_len, "$.emulation.speed", &d)) { - app->emulation.speed = (int)d; - app->emulation.speed = max(0, min(app->emulation.speed, 5)); + app->emulation.speed = max(0.0, min(d, 5.0)); } if (mjson_get_bool(data, data_len, "$.emulation.fast_forward", &b)) { @@ -250,7 +249,7 @@ app_config_save( // Emulation "emulation": { "skip_bios": %B, - "speed": %d, + "speed": %g, "fast_forward": %B, "backup_storage": { "autodetect": %B, @@ -287,7 +286,7 @@ app_config_save( app->file.recent_roms[3], app->file.recent_roms[4], (int)app->emulation.skip_bios, - (int)app->emulation.speed, + app->emulation.speed, (int)app->emulation.fast_forward, (int)app->emulation.backup_storage.autodetect, (int)app->emulation.backup_storage.type, diff --git a/source/app/emulator.c b/source/app/emulator.c index ac00085..7b19193 100644 --- a/source/app/emulator.c +++ b/source/app/emulator.c @@ -559,7 +559,8 @@ app_emulator_configure_and_run( app->emulation.game_path = strdup(rom_path); app->emulation.launch_config->skip_bios = app->emulation.skip_bios; - app->emulation.launch_config->speed = app->emulation.fast_forward ? 0 : app->emulation.speed; + app->emulation.launch_config->fast_forward = app->emulation.fast_forward; + app->emulation.launch_config->speed = app->emulation.speed; app->emulation.launch_config->audio_frequency = GBA_CYCLES_PER_SECOND / app->audio.resample_frequency; if (app->emulation.backup_storage.autodetect) { @@ -578,7 +579,11 @@ app_emulator_configure_and_run( logln(HS_INFO, " Skip BIOS: %s", app->emulation.launch_config->skip_bios ? "true" : "false"); logln(HS_INFO, " Backup storage: %s", backup_storage_names[app->emulation.launch_config->backup_storage.type]); logln(HS_INFO, " GPIO: %s", gpio_device_names[app->emulation.launch_config->gpio_device_type]); - logln(HS_INFO, " Speed: %i", app->emulation.speed); + if (app->emulation.launch_config->fast_forward) { + logln(HS_INFO, " Speed: Fast Forward"); + } else { + logln(HS_INFO, " Speed: %.0f%%", app->emulation.launch_config->speed * 100.f); + } logln(HS_INFO, " Audio Frequency: %iHz (%i cycles)", app->audio.resample_frequency, app->emulation.launch_config->audio_frequency); event.header.kind = MESSAGE_RESET; @@ -722,12 +727,14 @@ app_emulator_key( void app_emulator_speed( struct app *app, - uint32_t speed + bool fast_forward, + float speed ) { struct message_speed event; event.header.kind = MESSAGE_SPEED; event.header.size = sizeof(event); + event.fast_forward = fast_forward; event.speed = speed; channel_lock(&app->emulation.gba->channels.messages); diff --git a/source/app/main.c b/source/app/main.c index 4f6905c..a5ce74f 100644 --- a/source/app/main.c +++ b/source/app/main.c @@ -61,7 +61,7 @@ main( app.args.with_gui = true; app.emulation.is_started = false; app.emulation.is_running = false; - app.emulation.speed = 1; + app.emulation.speed = 1.0; app.emulation.fast_forward = false; app.emulation.backup_storage.autodetect = true; app.emulation.backup_storage.type = BACKUP_NONE; diff --git a/source/app/windows/menubar.c b/source/app/windows/menubar.c index 056ab15..6a424cb 100644 --- a/source/app/windows/menubar.c +++ b/source/app/windows/menubar.c @@ -103,28 +103,40 @@ app_win_menubar_emulation( bind = SDL_GetKeyName(app->binds.keyboard[BIND_EMULATOR_FAST_FORWARD_TOGGLE]); if (igMenuItem_Bool("Fast Forward", bind ?: "", app->emulation.fast_forward, true)) { app->emulation.fast_forward ^= true; - app_emulator_speed(app, app->emulation.fast_forward ? 0 : app->emulation.speed); + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); } igSeparator(); - char const *speed[] = { - "x1", - "x2", - "x3", - "x4", - "x5", + char const *speeds_str[] = { + "25% (15fps)", + "50% (30fps)", + "100% (60fps)", + "200% (120fps)", + "300% (180fps)", + "400% (240fps)", + "500% (300fps)", + }; + + float speeds[] = { + 0.25f, + 0.50f, + 1.00f, + 2.00f, + 3.00f, + 4.00f, + 5.00f, }; igBeginDisabled(app->emulation.fast_forward); - for (x = 0; x < 5; ++x) { + for (x = 0; x < array_length(speeds); ++x) { char const *bind; - bind = SDL_GetKeyName(app->binds.keyboard[BIND_EMULATOR_SPEED_X1 + x]); - if (igMenuItem_Bool(speed[x], bind ?: "", app->emulation.speed == x + 1, true)) { - app->emulation.speed = x + 1; + bind = SDL_GetKeyName(app->binds.keyboard[BIND_EMULATOR_SPEED_X0_25 + x]); + if (igMenuItem_Bool(speeds_str[x], bind ?: "", app->emulation.speed >= speeds[x] - 0.01 && app->emulation.speed <= speeds[x] + 0.01, true)) { + app->emulation.speed = speeds[x]; app->emulation.fast_forward = false; - app_emulator_speed(app, app->emulation.speed); + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); } } igEndDisabled(); diff --git a/source/app/windows/settings.c b/source/app/windows/settings.c index e7a3322..fee9497 100644 --- a/source/app/windows/settings.c +++ b/source/app/windows/settings.c @@ -42,12 +42,24 @@ static char const *aspect_ratio_names[ASPECT_RATIO_LEN] = { [ASPECT_RATIO_STRETCH] = "Stretch", }; -static char const * const speed_names[] = { - "x1 (60 fps)", - "x2 (120 fps)", - "x3 (180 fps)", - "x4 (240 fps)", - "x5 (300 fps)", +char const *speeds_str[] = { + "25% (15fps)", + "50% (30fps)", + "100% (60fps)", + "200% (120fps)", + "300% (180fps)", + "400% (240fps)", + "500% (300fps)", +}; + +float speeds[] = { + 0.25f, + 0.50f, + 1.00f, + 2.00f, + 3.00f, + 4.00f, + 5.00f, }; static char const * const display_size_names[] = { @@ -75,11 +87,13 @@ char const * const binds_pretty_name[] = { [BIND_EMULATOR_PAUSE] = "Pause", [BIND_EMULATOR_STOP] = "Stop", [BIND_EMULATOR_RESET] = "Reset", - [BIND_EMULATOR_SPEED_X1] = "Speed x1", - [BIND_EMULATOR_SPEED_X2] = "Speed x2", - [BIND_EMULATOR_SPEED_X3] = "Speed x3", - [BIND_EMULATOR_SPEED_X4] = "Speed x4", - [BIND_EMULATOR_SPEED_X5] = "Speed x5", + [BIND_EMULATOR_SPEED_X0_25] = "Speed 25% (15fps)", + [BIND_EMULATOR_SPEED_X0_50] = "Speed 50% (30fps)", + [BIND_EMULATOR_SPEED_X1] = "Speed 100% (60fps)", + [BIND_EMULATOR_SPEED_X2] = "Speed 200% (120fps)", + [BIND_EMULATOR_SPEED_X3] = "Speed 300% (180fps)", + [BIND_EMULATOR_SPEED_X4] = "Speed 400% (240fps)", + [BIND_EMULATOR_SPEED_X5] = "Speed 500% (300fps)", [BIND_EMULATOR_FAST_FORWARD_TOGGLE] = "Fast Forward (Toggle)", [BIND_EMULATOR_FAST_FORWARD_HOLD] = "Fast Forward (Hold)", [BIND_EMULATOR_QUICKSAVE_1] = "Quicksave 1", @@ -121,6 +135,8 @@ char const * const binds_slug[] = { [BIND_EMULATOR_PAUSE] = "pause", [BIND_EMULATOR_STOP] = "stop", [BIND_EMULATOR_RESET] = "reset", + [BIND_EMULATOR_SPEED_X0_25] = "speed_x0_25", + [BIND_EMULATOR_SPEED_X0_50] = "speed_x0_50", [BIND_EMULATOR_SPEED_X1] = "speed_x1", [BIND_EMULATOR_SPEED_X2] = "speed_x2", [BIND_EMULATOR_SPEED_X3] = "speed_x3", @@ -156,10 +172,11 @@ app_win_settings_emulation( struct app *app ) { ImGuiViewport *vp; - int32_t speed; + char buffer[16]; + float speed; + uint32_t i; vp = igGetMainViewport(); - speed = app->emulation.speed ? app->emulation.speed - 1 : 0; igTextWrapped("Emulation Settings"); igSpacing(); @@ -224,7 +241,7 @@ app_win_settings_emulation( igTableNextColumn(); if (igCheckbox("##FastForward", &app->emulation.fast_forward)) { - app_emulator_speed(app, app->emulation.fast_forward ? 0 : app->emulation.speed); + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); } // Speed @@ -234,10 +251,26 @@ app_win_settings_emulation( igTextWrapped("Speed"); igTableNextColumn(); - if (igCombo_Str_arr("##Speed", &speed, speed_names, array_length(speed_names), 0)) { - app->emulation.speed = speed + 1; - app->emulation.fast_forward = false; - app_emulator_speed(app, app->emulation.speed); + + speed = app->emulation.speed; + for (i = 0; i < array_length(speeds); ++i) { + if (app->emulation.speed >= speeds[i] - 0.01 && app->emulation.speed <= speeds[i] + 0.01) { + speed = speeds[i]; + break; + } + } + + snprintf(buffer, sizeof(buffer), "%.0f%%", speed * 100.f); + + if (igBeginCombo("##Speed", buffer, ImGuiComboFlags_None)) { + for (i = 0; i < array_length(speeds_str); ++i) { + if (igSelectable_Bool(speeds_str[i], speed >= speeds[i] - 0.01 && speed <= speeds[i] + 0.01, ImGuiSelectableFlags_None, (ImVec2){ 0.f, 0.f })) { + app->emulation.speed = speeds[i]; + app->emulation.fast_forward = false; + app_emulator_speed(app, app->emulation.fast_forward, app->emulation.speed); + } + } + igEndCombo(); } igEndDisabled(); diff --git a/source/gba/gba.c b/source/gba/gba.c index 303e62f..934fbb2 100644 --- a/source/gba/gba.c +++ b/source/gba/gba.c @@ -158,7 +158,7 @@ gba_state_reset( scheduler->events = calloc(scheduler->events_size, sizeof(struct scheduler_event)); hs_assert(scheduler->events); - sched_update_speed(gba, config->speed); + sched_update_speed(gba, config->fast_forward, config->speed); // Frame limiter sched_add_event( @@ -427,7 +427,7 @@ gba_process_message( struct message_speed const *msg_speed; msg_speed = (struct message_speed const *)message; - sched_update_speed(gba, msg_speed->speed); + sched_update_speed(gba, msg_speed->fast_forward, msg_speed->speed); break; }; case MESSAGE_QUICKSAVE: { diff --git a/source/gba/scheduler.c b/source/gba/scheduler.c index 63ed30d..7d326ff 100644 --- a/source/gba/scheduler.c +++ b/source/gba/scheduler.c @@ -187,7 +187,7 @@ sched_frame_limiter( struct gba *gba, struct event_args args __unused ) { - if (gba->scheduler.speed) { + if (!gba->scheduler.fast_forward) { uint64_t now; now = hs_time(); @@ -204,12 +204,22 @@ sched_frame_limiter( void sched_update_speed( struct gba *gba, - uint32_t speed + bool fast_forward, + float speed ) { struct scheduler *scheduler; scheduler = &gba->scheduler; - scheduler->speed = speed; - scheduler->time_per_frame = speed ? (1.f / 59.737f * 1000.f * 1000.f / (float)speed) : 0; + + scheduler->fast_forward = fast_forward; + + if (fast_forward) { + scheduler->speed = 0.0; + scheduler->time_per_frame = 0.0; + } else { + scheduler->speed = speed; + scheduler->time_per_frame = 1000.f * 1000.f / (speed * 59.737f); + } + sched_reset_frame_limiter(gba); }