diff --git a/command.h b/command.h index 1be36000b6..fcced8bb95 100644 --- a/command.h +++ b/command.h @@ -261,6 +261,12 @@ enum event_command CMD_EVENT_BSV_RECORDING_TOGGLE, /* Toggle Run-Ahead. */ CMD_EVENT_RUNAHEAD_TOGGLE, + /* Toggle Preemtive Frames. */ + CMD_EVENT_PREEMPT_TOGGLE, + /* Deinitialize or Reinitialize Preemptive Frames. */ + CMD_EVENT_PREEMPT_UPDATE, + /* Force Preemptive Frames to refill its state buffer. */ + CMD_EVENT_PREEMPT_RESET_BUFFER, /* Toggle VRR runloop. */ CMD_EVENT_VRR_RUNLOOP_TOGGLE, /* AI service. */ @@ -505,6 +511,7 @@ static const struct cmd_map map[] = { { "VRR_RUNLOOP_TOGGLE", RARCH_VRR_RUNLOOP_TOGGLE }, { "RUNAHEAD_TOGGLE", RARCH_RUNAHEAD_TOGGLE }, + { "PREEMPT_TOGGLE", RARCH_PREEMPT_TOGGLE }, { "FPS_TOGGLE", RARCH_FPS_TOGGLE }, { "STATISTICS_TOGGLE", RARCH_STATISTICS_TOGGLE }, { "AI_SERVICE", RARCH_AI_SERVICE }, diff --git a/config.def.h b/config.def.h index 2dcbbb2316..60edc9c7b3 100644 --- a/config.def.h +++ b/config.def.h @@ -1299,6 +1299,8 @@ /* Hide warning messages when using the Run Ahead feature. */ #define DEFAULT_RUN_AHEAD_HIDE_WARNINGS false +/* Hide warning messages when using Preemptive Frames. */ +#define DEFAULT_PREEMPT_HIDE_WARNINGS false /* Enable stdin/network command interface. */ #define DEFAULT_NETWORK_CMD_ENABLE false diff --git a/config.def.keybinds.h b/config.def.keybinds.h index 1f37b95990..eaa274a549 100644 --- a/config.def.keybinds.h +++ b/config.def.keybinds.h @@ -543,6 +543,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_RUNAHEAD_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE, RETROK_UNKNOWN, + RARCH_PREEMPT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1143,6 +1150,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_RUNAHEAD_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE, RETROK_UNKNOWN, + RARCH_PREEMPT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, @@ -1753,6 +1767,13 @@ static const struct retro_keybind retro_keybinds_1[] = { RARCH_RUNAHEAD_TOGGLE, NO_BTN, NO_BTN, 0, true }, + { + NULL, NULL, + AXIS_NONE, AXIS_NONE, AXIS_NONE, + MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE, RETROK_UNKNOWN, + RARCH_PREEMPT_TOGGLE, NO_BTN, NO_BTN, 0, + true + }, { NULL, NULL, AXIS_NONE, AXIS_NONE, AXIS_NONE, diff --git a/configuration.c b/configuration.c index c88271b1d1..bbad0dce67 100644 --- a/configuration.c +++ b/configuration.c @@ -357,6 +357,7 @@ const struct input_bind_map input_config_bind_map[RARCH_BIND_LIST_END_NULL] = { DECLARE_META_BIND(2, toggle_vrr_runloop, RARCH_VRR_RUNLOOP_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_VRR_RUNLOOP_TOGGLE), DECLARE_META_BIND(2, runahead_toggle, RARCH_RUNAHEAD_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_RUNAHEAD_TOGGLE), + DECLARE_META_BIND(2, preempt_toggle, RARCH_PREEMPT_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE), DECLARE_META_BIND(2, fps_toggle, RARCH_FPS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE), DECLARE_META_BIND(2, toggle_statistics, RARCH_STATISTICS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_STATISTICS_TOGGLE), DECLARE_META_BIND(2, ai_service, RARCH_AI_SERVICE, MENU_ENUM_LABEL_VALUE_INPUT_META_AI_SERVICE), @@ -1686,6 +1687,8 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("run_ahead_enabled", &settings->bools.run_ahead_enabled, true, false, false); SETTING_BOOL("run_ahead_secondary_instance", &settings->bools.run_ahead_secondary_instance, true, DEFAULT_RUN_AHEAD_SECONDARY_INSTANCE, false); SETTING_BOOL("run_ahead_hide_warnings", &settings->bools.run_ahead_hide_warnings, true, DEFAULT_RUN_AHEAD_HIDE_WARNINGS, false); + SETTING_BOOL("preemptive_frames_enable", &settings->bools.preemptive_frames_enable, true, false, false); + SETTING_BOOL("preemptive_frames_hide_warnings", &settings->bools.preemptive_frames_hide_warnings, true, DEFAULT_PREEMPT_HIDE_WARNINGS, false); SETTING_BOOL("audio_sync", &settings->bools.audio_sync, true, DEFAULT_AUDIO_SYNC, false); SETTING_BOOL("video_shader_enable", &settings->bools.video_shader_enable, true, DEFAULT_SHADER_ENABLE, false); SETTING_BOOL("video_shader_watch_files", &settings->bools.video_shader_watch_files, true, DEFAULT_VIDEO_SHADER_WATCH_FILES, false); diff --git a/configuration.h b/configuration.h index 25d8f8525b..e8cb2d6aef 100644 --- a/configuration.h +++ b/configuration.h @@ -885,6 +885,8 @@ typedef struct settings bool run_ahead_enabled; bool run_ahead_secondary_instance; bool run_ahead_hide_warnings; + bool preemptive_frames_enable; + bool preemptive_frames_hide_warnings; bool pause_nonactive; bool pause_on_disconnect; bool block_sram_overwrite; diff --git a/input/input_defines.h b/input/input_defines.h index 0280cd114d..fa888a40db 100644 --- a/input/input_defines.h +++ b/input/input_defines.h @@ -120,6 +120,7 @@ enum RARCH_VRR_RUNLOOP_TOGGLE, RARCH_RUNAHEAD_TOGGLE, + RARCH_PREEMPT_TOGGLE, RARCH_FPS_TOGGLE, RARCH_STATISTICS_TOGGLE, RARCH_AI_SERVICE, diff --git a/intl/msg_hash_id.c b/intl/msg_hash_id.c index 8139863050..9e7dd94592 100644 --- a/intl/msg_hash_id.c +++ b/intl/msg_hash_id.c @@ -235,6 +235,10 @@ int msg_hash_get_help_id_enum(enum msg_hash_enums msg, char *s, size_t len) snprintf(s, len, "Toggles Run-Ahead mode on/off."); break; + case RARCH_PREEMPT_TOGGLE: + snprintf(s, len, + "Toggles Preemptive Frames on/off."); + break; default: if (string_is_empty(s)) strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_NO_INFORMATION_AVAILABLE), len); diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index b9f6433342..57eb55f63b 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3266,6 +3266,22 @@ MSG_HASH( MENU_ENUM_LABEL_RUN_AHEAD_FRAMES, "run_ahead_frames" ) +MSG_HASH( + MENU_ENUM_LABEL_PREEMPT_ENABLE, + "preemptive_frames_enable" + ) +MSG_HASH( + MENU_ENUM_LABEL_PREEMPT_UNSUPPORTED, + "preemptive_frames_unsupported" + ) +MSG_HASH( + MENU_ENUM_LABEL_PREEMPT_HIDE_WARNINGS, + "preemptive_frames_hide_warnings" + ) +MSG_HASH( + MENU_ENUM_LABEL_PREEMPT_FRAMES, + "preemptive_frames" + ) MSG_HASH( MENU_ENUM_LABEL_SORT_SAVEFILES_ENABLE, "sort_savefiles_enable" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 64ed225470..ea9dd9837a 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -3391,6 +3391,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_INPUT_META_RUNAHEAD_TOGGLE, "Switches Run-Ahead on/off." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE, + "Preemptive Frames (Toggle)" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_INPUT_META_PREEMPT_TOGGLE, + "Switches Preemptive Frames on/off." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, @@ -3720,6 +3728,38 @@ MSG_HASH( MENU_ENUM_SUBLABEL_RUN_AHEAD_HIDE_WARNINGS, "Hide the warning message that appears when using Run-Ahead and the core does not support save states." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PREEMPT_UNSUPPORTED, + "[Preemptive Frames Unavailable]" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PREEMPT_UNSUPPORTED, + "Current core is incompatible with preemptive frames due to lack of deterministic save state support." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PREEMPT_ENABLE, + "Run Preemptive Frames" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PREEMPT_ENABLE, + "Rerun core logic with the latest input when the controller state changes. Faster than Run-Ahead, but does not prevent audio issues cores may have with loading states." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PREEMPT_FRAMES, + "Number of Preemptive Frames" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PREEMPT_FRAMES, + "The number of frames to rerun. Causes gameplay issues such as jitter if the number of lag frames internal to the game is exceeded." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_PREEMPT_HIDE_WARNINGS, + "Hide Preemptive Frames Warnings" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_PREEMPT_HIDE_WARNINGS, + "Hide the warning message that appears when a core is incompatible with preemptive frames." + ) /* Settings > Core */ @@ -13902,6 +13942,34 @@ MSG_HASH( MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE, "Failed to create second instance. Run-Ahead will now use only one instance." ) +MSG_HASH( + MSG_PREEMPT_ENABLED, + "Preemptive Frames enabled. Latency frames removed: %u." + ) +MSG_HASH( + MSG_PREEMPT_DISABLED, + "Preemptive Frames disabled." + ) +MSG_HASH( + MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_SAVESTATES, + "Preemptive Frames has been disabled because this core does not support save states." + ) +MSG_HASH( + MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_PREEMPT, + "Preemptive Frames unavailable because this core lacks deterministic save state support." + ) +MSG_HASH( + MSG_PREEMPT_FAILED_TO_ALLOCATE, + "Failed to allocate memory for Preemptive Frames." + ) +MSG_HASH( + MSG_PREEMPT_FAILED_TO_SAVE_STATE, + "Failed to save state. Preemptive Frames has been disabled." + ) +MSG_HASH( + MSG_PREEMPT_FAILED_TO_LOAD_STATE, + "Failed to load state. Preemptive Frames has been disabled." + ) MSG_HASH( MSG_SCANNING_OF_FILE_FINISHED, "Scanning of file finished" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 7aed2f6a56..750b821ed4 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -433,6 +433,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_ui_companion_toggle, ME DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_vrr_runloop_toggle, MENU_ENUM_SUBLABEL_INPUT_META_VRR_RUNLOOP_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_runahead_toggle, MENU_ENUM_SUBLABEL_INPUT_META_RUNAHEAD_TOGGLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_preempt_toggle, MENU_ENUM_SUBLABEL_INPUT_META_PREEMPT_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_fps_toggle, MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_statistics_toggle, MENU_ENUM_SUBLABEL_INPUT_META_STATISTICS_TOGGLE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_meta_ai_service, MENU_ENUM_SUBLABEL_INPUT_META_AI_SERVICE) @@ -667,6 +668,10 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_run_ahead_enabled, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_run_ahead_secondary_instance, MENU_ENUM_SUBLABEL_RUN_AHEAD_SECONDARY_INSTANCE) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_run_ahead_hide_warnings, MENU_ENUM_SUBLABEL_RUN_AHEAD_HIDE_WARNINGS) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_run_ahead_frames, MENU_ENUM_SUBLABEL_RUN_AHEAD_FRAMES) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_preempt_unsupported, MENU_ENUM_SUBLABEL_PREEMPT_UNSUPPORTED) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_preempt_enable, MENU_ENUM_SUBLABEL_PREEMPT_ENABLE) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_preempt_hide_warnings, MENU_ENUM_SUBLABEL_PREEMPT_HIDE_WARNINGS) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_preempt_frames, MENU_ENUM_SUBLABEL_PREEMPT_FRAMES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_input_block_timeout, MENU_ENUM_SUBLABEL_INPUT_BLOCK_TIMEOUT) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_rewind, MENU_ENUM_SUBLABEL_REWIND_ENABLE) #ifdef HAVE_CHEATS @@ -2254,6 +2259,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case RARCH_RUNAHEAD_TOGGLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_runahead_toggle); return 0; + case RARCH_PREEMPT_TOGGLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_preempt_toggle); + return 0; case RARCH_FPS_TOGGLE: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_meta_fps_toggle); return 0; @@ -3831,6 +3839,18 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_RUN_AHEAD_FRAMES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_run_ahead_frames); break; + case MENU_ENUM_LABEL_PREEMPT_UNSUPPORTED: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_preempt_unsupported); + break; + case MENU_ENUM_LABEL_PREEMPT_ENABLE: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_preempt_enable); + break; + case MENU_ENUM_LABEL_PREEMPT_FRAMES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_preempt_frames); + break; + case MENU_ENUM_LABEL_PREEMPT_HIDE_WARNINGS: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_preempt_hide_warnings); + break; case MENU_ENUM_LABEL_INPUT_BLOCK_TIMEOUT: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_input_block_timeout); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 85f4c45a57..e5d9b1acb7 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -9265,6 +9265,7 @@ unsigned menu_displaylist_build_list( #ifdef HAVE_RUNAHEAD bool runahead_supported = true; bool runahead_enabled = settings->bools.run_ahead_enabled; + bool preempt_enabled = settings->bools.preemptive_frames_enable; #endif menu_displaylist_build_info_selective_t build_list[] = { {MENU_ENUM_LABEL_VIDEO_FRAME_DELAY, PARSE_ONLY_UINT, true }, @@ -9277,6 +9278,9 @@ unsigned menu_displaylist_build_list( {MENU_ENUM_LABEL_RUN_AHEAD_FRAMES, PARSE_ONLY_UINT, false }, {MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE, PARSE_ONLY_BOOL, false }, {MENU_ENUM_LABEL_RUN_AHEAD_HIDE_WARNINGS, PARSE_ONLY_BOOL, false }, + {MENU_ENUM_LABEL_PREEMPT_ENABLE, PARSE_ONLY_BOOL, false }, + {MENU_ENUM_LABEL_PREEMPT_FRAMES, PARSE_ONLY_UINT, false }, + {MENU_ENUM_LABEL_PREEMPT_HIDE_WARNINGS, PARSE_ONLY_BOOL, false }, #endif }; @@ -9330,6 +9334,7 @@ unsigned menu_displaylist_build_list( switch (build_list[i].enum_idx) { case MENU_ENUM_LABEL_RUN_AHEAD_ENABLED: + case MENU_ENUM_LABEL_PREEMPT_ENABLE: build_list[i].checked = true; break; case MENU_ENUM_LABEL_RUN_AHEAD_FRAMES: @@ -9338,6 +9343,11 @@ unsigned menu_displaylist_build_list( if (runahead_enabled) build_list[i].checked = true; break; + case MENU_ENUM_LABEL_PREEMPT_FRAMES: + case MENU_ENUM_LABEL_PREEMPT_HIDE_WARNINGS: + if (preempt_enabled) + build_list[i].checked = true; + break; default: break; } @@ -9356,13 +9366,21 @@ unsigned menu_displaylist_build_list( } #ifdef HAVE_RUNAHEAD - if (!runahead_supported && - menu_entries_append(list, + if (!runahead_supported) + { + if (menu_entries_append(list, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_RUN_AHEAD_UNSUPPORTED), msg_hash_to_str(MENU_ENUM_LABEL_RUN_AHEAD_UNSUPPORTED), MENU_ENUM_LABEL_RUN_AHEAD_UNSUPPORTED, FILE_TYPE_NONE, 0, 0, NULL)) - count++; + count++; + if (menu_entries_append(list, + msg_hash_to_str(MENU_ENUM_LABEL_VALUE_PREEMPT_UNSUPPORTED), + msg_hash_to_str(MENU_ENUM_LABEL_PREEMPT_UNSUPPORTED), + MENU_ENUM_LABEL_PREEMPT_UNSUPPORTED, + FILE_TYPE_NONE, 0, 0, NULL)) + count++; + } #endif if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_GAMEMODE_ENABLE, PARSE_ONLY_BOOL, false) == 0) diff --git a/menu/menu_driver.c b/menu/menu_driver.c index bb3bd8c622..5e29fb499c 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -6982,6 +6982,13 @@ void retroarch_menu_running_finished(bool quit) command_event(CMD_EVENT_GAME_FOCUS_TOGGLE, &game_focus_cmd); } } + +#if HAVE_RUNAHEAD + /* Preemptive Frames isn't run behind the menu, + * so its savestate buffer is out of date. */ + if (!settings->bools.menu_pause_libretro) + command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL); +#endif } /* Ensure that menu screensaver is disabled when diff --git a/menu/menu_setting.c b/menu/menu_setting.c index e3d8f01d8c..3f97bd3408 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -8654,31 +8654,6 @@ static void general_write_handler(rarch_setting_t *setting) default_aspect; } break; -#if defined(HAVE_RUNAHEAD) && (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)) - case MENU_ENUM_LABEL_RUN_AHEAD_ENABLED: - case MENU_ENUM_LABEL_RUN_AHEAD_FRAMES: - case MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE: - { - settings_t *settings = config_get_ptr(); - bool run_ahead_enabled = settings->bools.run_ahead_enabled; - unsigned run_ahead_frames = settings->uints.run_ahead_frames; - bool run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance; - - /* If any changes here will cause second - * instance runahead to be enabled, must - * re-apply cheats to ensure that they - * propagate to the newly-created secondary - * core */ - if (run_ahead_enabled && - (run_ahead_frames > 0) && - run_ahead_secondary_instance && - !retroarch_ctl(RARCH_CTL_IS_SECOND_CORE_LOADED, NULL) && - retroarch_ctl(RARCH_CTL_IS_SECOND_CORE_AVAILABLE, NULL) && - command_event(CMD_EVENT_LOAD_SECOND_CORE, NULL)) - command_event(CMD_EVENT_CHEATS_APPLY, NULL); - } - break; -#endif default: /* Special cases */ @@ -8713,6 +8688,104 @@ static void frontend_log_level_change_handler(rarch_setting_t *setting) verbosity_set_log_level(*setting->value.target.unsigned_integer); } +#ifdef HAVE_RUNAHEAD +static void runahead_change_handler(rarch_setting_t *setting) +{ + settings_t *settings = config_get_ptr(); + bool run_ahead_enabled = settings->bools.run_ahead_enabled; + bool preempt_enabled = settings->bools.preemptive_frames_enable; + bool refresh = false; +#if (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)) + unsigned run_ahead_frames = settings->uints.run_ahead_frames; + bool run_ahead_secondary_instance = settings->bools.run_ahead_secondary_instance; +#endif + + if (!setting) + return; + + switch (setting->enum_idx) + { + case MENU_ENUM_LABEL_RUN_AHEAD_ENABLED: + if (run_ahead_enabled && preempt_enabled) + { + /* Disable preemptive frames and inform user */ + settings->bools.preemptive_frames_enable = false; + runloop_preempt_deinit(); + runloop_msg_queue_push( + msg_hash_to_str(MSG_PREEMPT_DISABLED), 1, 100, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + /* fall-through */ + case MENU_ENUM_LABEL_RUN_AHEAD_FRAMES: +#if (defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)) + case MENU_ENUM_LABEL_RUN_AHEAD_SECONDARY_INSTANCE: + /* If any changes here will cause second + * instance runahead to be enabled, must + * re-apply cheats to ensure that they + * propagate to the newly-created secondary + * core */ + if ( run_ahead_enabled + && (run_ahead_frames > 0) + && run_ahead_secondary_instance + && !retroarch_ctl(RARCH_CTL_IS_SECOND_CORE_LOADED, NULL) + && retroarch_ctl(RARCH_CTL_IS_SECOND_CORE_AVAILABLE, NULL) + && command_event(CMD_EVENT_LOAD_SECOND_CORE, NULL)) + command_event(CMD_EVENT_CHEATS_APPLY, NULL); +#endif + break; + default: + break; + } +} + +static void preempt_change_handler(rarch_setting_t *setting) +{ + settings_t *settings = config_get_ptr(); + bool preempt_enabled = settings->bools.preemptive_frames_enable; + bool run_ahead_enabled = settings->bools.run_ahead_enabled; + preempt_t *preempt = runloop_state_get_ptr()->preempt_data; + bool refresh = false; + bool netplay_enabled; + +#ifdef HAVE_NETWORKING + netplay_enabled = netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL); +#else + netplay_enabled = false; +#endif + + if (!setting) + return; + + switch (setting->enum_idx) + { + case MENU_ENUM_LABEL_PREEMPT_ENABLE: + if (preempt_enabled && run_ahead_enabled) + { + /* Disable runahead and inform user */ + settings->bools.run_ahead_enabled = false; + runloop_msg_queue_push( + msg_hash_to_str(MSG_RUNAHEAD_DISABLED), 1, 100, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + + if ((preempt_enabled != !!preempt) && !netplay_enabled) + command_event(CMD_EVENT_PREEMPT_UPDATE, NULL); + + menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); + break; + case MENU_ENUM_LABEL_PREEMPT_FRAMES: + if ( preempt + && preempt->frames != settings->uints.run_ahead_frames + && !netplay_enabled) + command_event(CMD_EVENT_PREEMPT_UPDATE, NULL); + break; + default: + break; + } +} +#endif + #ifdef HAVE_OVERLAY static void overlay_enable_toggle_change_handler(rarch_setting_t *setting) { @@ -14793,6 +14866,7 @@ static bool setting_append_list( (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; menu_settings_list_current_add_range(list, list_info, 1, 10, 0.1, true, true); +#ifdef HAVE_RUNAHEAD CONFIG_BOOL( list, list_info, &settings->bools.run_ahead_enabled, @@ -14808,9 +14882,7 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE ); - (*list)[list_info->index - 1].action_ok = setting_bool_action_left_with_refresh; - (*list)[list_info->index - 1].action_left = setting_bool_action_left_with_refresh; - (*list)[list_info->index - 1].action_right = setting_bool_action_right_with_refresh; + (*list)[list_info->index - 1].change_handler = runahead_change_handler; CONFIG_UINT( list, list_info, @@ -14826,7 +14898,8 @@ static bool setting_append_list( (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; (*list)[list_info->index - 1].offset_by = 1; - menu_settings_list_current_add_range(list, list_info, 1, 12, 1, true, true); + (*list)[list_info->index - 1].change_handler = runahead_change_handler; + menu_settings_list_current_add_range(list, list_info, 1, MAX_RUNAHEAD_FRAMES, 1, true, true); #if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB) CONFIG_BOOL( @@ -14844,6 +14917,7 @@ static bool setting_append_list( general_read_handler, SD_FLAG_NONE ); + (*list)[list_info->index - 1].change_handler = runahead_change_handler; #endif CONFIG_BOOL( @@ -14862,6 +14936,56 @@ static bool setting_append_list( SD_FLAG_ADVANCED ); + CONFIG_BOOL( + list, list_info, + &settings->bools.preemptive_frames_enable, + MENU_ENUM_LABEL_PREEMPT_ENABLE, + MENU_ENUM_LABEL_VALUE_PREEMPT_ENABLE, + false, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + (*list)[list_info->index - 1].change_handler = preempt_change_handler; + + CONFIG_UINT( + list, list_info, + &settings->uints.run_ahead_frames, + MENU_ENUM_LABEL_PREEMPT_FRAMES, + MENU_ENUM_LABEL_VALUE_PREEMPT_FRAMES, + 1, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler); + (*list)[list_info->index - 1].ui_type = ST_UI_TYPE_UINT_COMBOBOX; + (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; + (*list)[list_info->index - 1].offset_by = 1; + (*list)[list_info->index - 1].change_handler = preempt_change_handler; + menu_settings_list_current_add_range(list, list_info, 1, MAX_RUNAHEAD_FRAMES, 1, true, true); + + CONFIG_BOOL( + list, list_info, + &settings->bools.preemptive_frames_hide_warnings, + MENU_ENUM_LABEL_PREEMPT_HIDE_WARNINGS, + MENU_ENUM_LABEL_VALUE_PREEMPT_HIDE_WARNINGS, + DEFAULT_PREEMPT_HIDE_WARNINGS, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_ADVANCED + ); +#endif + #ifdef ANDROID CONFIG_UINT( list, list_info, diff --git a/msg_hash.h b/msg_hash.h index 1e7b136b30..780005e296 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -520,6 +520,13 @@ enum msg_hash_enums MSG_RUNAHEAD_FAILED_TO_SAVE_STATE, MSG_RUNAHEAD_FAILED_TO_LOAD_STATE, MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE, + MSG_PREEMPT_ENABLED, + MSG_PREEMPT_DISABLED, + MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_SAVESTATES, + MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_PREEMPT, + MSG_PREEMPT_FAILED_TO_ALLOCATE, + MSG_PREEMPT_FAILED_TO_SAVE_STATE, + MSG_PREEMPT_FAILED_TO_LOAD_STATE, MSG_MISSING_ASSETS, MSG_RGUI_MISSING_FONTS, MSG_RGUI_INVALID_LANGUAGE, @@ -1033,6 +1040,7 @@ enum msg_hash_enums MENU_ENUM_LABEL_VALUE_INPUT_META_VRR_RUNLOOP_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_RUNAHEAD_TOGGLE, + MENU_ENUM_LABEL_VALUE_INPUT_META_PREEMPT_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_FPS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_STATISTICS_TOGGLE, MENU_ENUM_LABEL_VALUE_INPUT_META_AI_SERVICE, @@ -1109,6 +1117,7 @@ enum msg_hash_enums MENU_ENUM_SUBLABEL_INPUT_META_VRR_RUNLOOP_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_RUNAHEAD_TOGGLE, + MENU_ENUM_SUBLABEL_INPUT_META_PREEMPT_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_FPS_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_STATISTICS_TOGGLE, MENU_ENUM_SUBLABEL_INPUT_META_AI_SERVICE, @@ -2444,6 +2453,10 @@ enum msg_hash_enums MENU_LABEL(RUN_AHEAD_SECONDARY_INSTANCE), MENU_LABEL(RUN_AHEAD_HIDE_WARNINGS), MENU_LABEL(RUN_AHEAD_FRAMES), + MENU_LABEL(PREEMPT_UNSUPPORTED), + MENU_LABEL(PREEMPT_ENABLE), + MENU_LABEL(PREEMPT_FRAMES), + MENU_LABEL(PREEMPT_HIDE_WARNINGS), MENU_LABEL(INPUT_BLOCK_TIMEOUT), MENU_LABEL(TURBO), diff --git a/network/netplay/netplay_frontend.c b/network/netplay/netplay_frontend.c index 88011acb5d..1229c1b2c2 100644 --- a/network/netplay/netplay_frontend.c +++ b/network/netplay/netplay_frontend.c @@ -8435,6 +8435,11 @@ void deinit_netplay(void) net_st->data = NULL; net_st->flags &= ~(NET_DRIVER_ST_FLAG_NETPLAY_ENABLED | NET_DRIVER_ST_FLAG_NETPLAY_IS_CLIENT); + +#if HAVE_RUNAHEAD + /* Reinitialize preemptive frames if enabled */ + runloop_preempt_init(); +#endif } free(net_st->client_info); diff --git a/retroarch.c b/retroarch.c index 806a7445da..62c8a5da78 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2228,10 +2228,8 @@ bool command_event(enum event_command cmd, void *data) } break; case CMD_EVENT_RUNAHEAD_TOGGLE: +#if HAVE_RUNAHEAD { - char msg[256]; - msg[0] = '\0'; - if (!core_info_current_supports_runahead()) { runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_RUNAHEAD), @@ -2249,25 +2247,86 @@ bool command_event(enum event_command cmd, void *data) 1, 100, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); } - else if (!settings->bools.run_ahead_secondary_instance) - { - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED), - settings->uints.run_ahead_frames); - - runloop_msg_queue_push( - msg, 1, 100, false, - NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); - } else { - snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED_WITH_SECOND_INSTANCE), - settings->uints.run_ahead_frames); + char msg[256]; + if (!settings->bools.run_ahead_secondary_instance) + { + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED), + settings->uints.run_ahead_frames); + + runloop_msg_queue_push( + msg, 1, 100, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + else + { + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_RUNAHEAD_ENABLED_WITH_SECOND_INSTANCE), + settings->uints.run_ahead_frames); + + runloop_msg_queue_push( + msg, 1, 100, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + + /* Disable preemptive frames */ + settings->bools.preemptive_frames_enable = false; + runloop_preempt_deinit(); + } + } +#endif + break; + case CMD_EVENT_PREEMPT_TOGGLE: +#if HAVE_RUNAHEAD + { + bool old_warn = settings->bools.preemptive_frames_hide_warnings; + bool old_inited = runloop_st->preempt_data; + + /* Toggle with warnings shown */ + settings->bools.preemptive_frames_hide_warnings = false; + + settings->bools.preemptive_frames_enable = + !(settings->bools.preemptive_frames_enable); + command_event(CMD_EVENT_PREEMPT_UPDATE, NULL); + + settings->bools.preemptive_frames_hide_warnings = old_warn; + + if (old_inited && !runloop_st->preempt_data) + { + runloop_msg_queue_push(msg_hash_to_str(MSG_PREEMPT_DISABLED), + 1, 100, false, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + } + else if (runloop_st->preempt_data) + { + char msg[256]; + + snprintf(msg, sizeof(msg), msg_hash_to_str(MSG_PREEMPT_ENABLED), + settings->uints.run_ahead_frames); runloop_msg_queue_push( msg, 1, 100, false, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + + /* Disable runahead */ + settings->bools.run_ahead_enabled = false; } + else /* Failed to init */ + settings->bools.preemptive_frames_enable = false; } +#endif + break; + case CMD_EVENT_PREEMPT_UPDATE: +#if HAVE_RUNAHEAD + runloop_preempt_deinit(); + runloop_preempt_init(); +#endif + break; + case CMD_EVENT_PREEMPT_RESET_BUFFER: +#if HAVE_RUNAHEAD + if (runloop_st->preempt_data) + runloop_st->preempt_data->frame_count = 0; +#endif break; case CMD_EVENT_RECORDING_TOGGLE: if (recording_st->enable) @@ -2381,6 +2440,10 @@ bool command_event(enum event_command cmd, void *data) #endif if (!command_event_main_state(cmd)) return false; + +#if HAVE_RUNAHEAD + command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL); +#endif } break; case CMD_EVENT_UNDO_LOAD_STATE: @@ -2427,6 +2490,9 @@ bool command_event(enum event_command cmd, void *data) if (settings->bools.video_frame_delay_auto) video_st->frame_delay_target = 0; +#if HAVE_RUNAHEAD + command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL); +#endif return false; case CMD_EVENT_SAVE_STATE: case CMD_EVENT_SAVE_STATE_TO_RAM: @@ -2977,6 +3043,9 @@ bool command_event(enum event_command cmd, void *data) * RetroArch */ if (!(runloop_st->flags & RUNLOOP_FLAG_RUNAHEAD_AVAILABLE)) runloop_runahead_clear_variables(runloop_st); + + /* Deallocate preemptive frames */ + runloop_preempt_deinit(); #endif if (hwr) @@ -3512,6 +3581,10 @@ bool command_event(enum event_command cmd, void *data) return false; } +#if HAVE_RUNAHEAD + /* Deinit preemptive frames; not compatible with netplay */ + runloop_preempt_deinit(); +#endif } break; case CMD_EVENT_NETPLAY_DISCONNECT: @@ -6171,6 +6244,12 @@ bool retroarch_main_init(int argc, char *argv[]) audio_driver_load_system_sounds(); #endif +#if defined(HAVE_RUNAHEAD) && defined(HAVE_NETWORKING) + if (!netplay_driver_ctl(RARCH_NETPLAY_CTL_IS_ENABLED, NULL)) +#elif defined(HAVE_RUNAHEAD) + runloop_preempt_init(); +#endif + return true; error: diff --git a/runloop.c b/runloop.c index de75bf677c..fb1ebf3ba2 100644 --- a/runloop.c +++ b/runloop.c @@ -4974,6 +4974,380 @@ force_input_dirty: core_run(); runloop_st->flags |= RUNLOOP_FLAG_RUNAHEAD_FORCE_INPUT_DIRTY; } + +/* Preemptive Frames */ + +static int16_t preempt_input_state(unsigned port, + unsigned device, unsigned index, unsigned id) +{ + settings_t *settings = config_get_ptr(); + runloop_state_t *runloop_st = &runloop_state; + preempt_t *preempt = runloop_st->preempt_data; + unsigned device_class = device & RETRO_DEVICE_MASK; + unsigned *port_map = settings->uints.input_remap_port_map[port]; + uint8_t p; + + switch (device_class) + { + case RETRO_DEVICE_ANALOG: + /* Add requested inputs to mask */ + while ((p = *(port_map++)) < MAX_USERS) + preempt->analog_mask[p] |= (1 << (id + index * 2)); + break; + case RETRO_DEVICE_LIGHTGUN: + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_POINTER: + /* Set pointing device for this port */ + while ((p = *(port_map++)) < MAX_USERS) + preempt->ptr_dev[p] = device_class; + break; + default: + break; + } + + return input_driver_state_wrapper(port, device, index, id); +} + +static const char* preempt_allocate(const uint8_t frames) +{ + runloop_state_t *runloop_st = &runloop_state; + preempt_t *preempt = (preempt_t*)calloc(1, sizeof(preempt_t)); + retro_ctx_size_info_t info; + uint8_t i; + + if (!(runloop_st->preempt_data = preempt)) + return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE); + + core_serialize_size_special(&info); + if (!info.size) + return msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_SAVESTATES); + + preempt->state_size = info.size; + preempt->frames = frames; + + for (i = 0; i < frames; i++) + { + preempt->buffer[i] = malloc(preempt->state_size); + if (!preempt->buffer[i]) + return msg_hash_to_str(MSG_PREEMPT_FAILED_TO_ALLOCATE); + } + + return NULL; +} + +/** + * runloop_preempt_init: + * + * @return true on success, false on failure + * + * Allocates savestate buffer and sets overrides for preemptive frames. + **/ +bool runloop_preempt_init(void) +{ + runloop_state_t *runloop_st = &runloop_state; + settings_t *settings = config_get_ptr(); + const char *failed_str = NULL; + + if ( runloop_st->preempt_data + || !settings->bools.preemptive_frames_enable + || !settings->uints.run_ahead_frames + || !(runloop_st->current_core.flags & RETRO_CORE_FLAG_GAME_LOADED)) + return false; + + /* Check if supported - same requirements as runahead */ + if (!core_info_current_supports_runahead()) + { + failed_str = msg_hash_to_str(MSG_PREEMPT_CORE_DOES_NOT_SUPPORT_PREEMPT); + goto error; + } + + /* Set flags to block runahead and request 'same instance' states */ + runloop_st->flags &= ~(RUNLOOP_FLAG_RUNAHEAD_AVAILABLE + | RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE); + + /* Allocate - same 'frames' setting as runahead */ + if ((failed_str = preempt_allocate(settings->uints.run_ahead_frames))) + goto error; + + /* Only poll in preempt_run() */ + runloop_st->current_core.retro_set_input_poll(retro_input_poll_null); + /* Track requested analog states and pointing device types */ + runloop_st->current_core.retro_set_input_state(preempt_input_state); + + return true; + +error: + runloop_preempt_deinit(); + + if (!config_get_ptr()->bools.preemptive_frames_hide_warnings) + runloop_msg_queue_push( + failed_str, 0, 2 * 60, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_WARN("[Preemptive Frames]: %s\n", failed_str); + + return false; +} + +/** + * runloop_preempt_deinit: + * + * Frees preempt object and unsets overrides. + **/ +void runloop_preempt_deinit(void) +{ + runloop_state_t *runloop_st = &runloop_state; + preempt_t *preempt = runloop_st->preempt_data; + struct retro_core_t *current_core = &runloop_st->current_core; + size_t i; + + if (preempt == NULL) + return; + + /* Free memory */ + for (i = 0; i < preempt->frames; i++) + free(preempt->buffer[i]); + + free(preempt); + runloop_st->preempt_data = NULL; + + /* Undo overrides */ + runloop_st->flags |= (RUNLOOP_FLAG_RUNAHEAD_AVAILABLE + | RUNLOOP_FLAG_RUNAHEAD_SECONDARY_CORE_AVAILABLE); + + if (current_core->retro_set_input_poll) + current_core->retro_set_input_poll(runloop_st->input_poll_callback_original); + if (current_core->retro_set_input_state) + current_core->retro_set_input_state(runloop_st->retro_ctx.state_cb); +} + +static INLINE bool preempt_analog_input_dirty(preempt_t *preempt, + retro_input_state_t state_cb, unsigned port, unsigned mapped_port) +{ + int16_t state[20] = {0}; + uint8_t base, i; + + /* axes */ + for (i = 0; i < 2; i++) + { + base = i * 2; + if (preempt->analog_mask[port] & (1 << (base ))) + state[base ] = state_cb(mapped_port, RETRO_DEVICE_ANALOG, i, 0); + if (preempt->analog_mask[port] & (1 << (base + 1))) + state[base + 1] = state_cb(mapped_port, RETRO_DEVICE_ANALOG, i, 1); + } + + /* buttons */ + for (i = 0; i < RARCH_FIRST_CUSTOM_BIND; i++) + { + if (preempt->analog_mask[port] & (1 << (i + 4))) + state[i + 4] = state_cb(mapped_port, RETRO_DEVICE_ANALOG, + RETRO_DEVICE_INDEX_ANALOG_BUTTON, i); + } + + if (memcmp(preempt->analog_state[port], state, sizeof(state)) == 0) + return false; + + memcpy(preempt->analog_state[port], state, sizeof(state)); + return true; +} + +static INLINE bool preempt_ptr_input_dirty(preempt_t *preempt, + retro_input_state_t state_cb, unsigned device, + unsigned port, unsigned mapped_port) +{ + int16_t state[4] = {0}; + unsigned count_id = 0; + unsigned x_id = 0; + unsigned id, max_id; + + switch (device) + { + case RETRO_DEVICE_MOUSE: + max_id = RETRO_DEVICE_ID_MOUSE_BUTTON_5; + break; + case RETRO_DEVICE_LIGHTGUN: + x_id = RETRO_DEVICE_ID_LIGHTGUN_SCREEN_X; + max_id = RETRO_DEVICE_ID_LIGHTGUN_DPAD_RIGHT; + break; + case RETRO_DEVICE_POINTER: + max_id = RETRO_DEVICE_ID_POINTER_PRESSED; + count_id = RETRO_DEVICE_ID_POINTER_COUNT; + break; + default: + return false; + } + + /* x, y */ + state[0] = state_cb(mapped_port, device, 0, x_id ); + state[1] = state_cb(mapped_port, device, 0, x_id + 1); + + /* buttons */ + for (id = 2; id <= max_id; id++) + state[2] |= state_cb(mapped_port, device, 0, id) ? 1 << id : 0; + + /* ptr count */ + if (count_id) + state[3] = state_cb(mapped_port, device, 0, count_id); + + if (memcmp(preempt->ptrdev_state[port], state, sizeof(state)) == 0) + return false; + + memcpy(preempt->ptrdev_state[port], state, sizeof(state)); + return true; +} + +static INLINE void preempt_input_poll(preempt_t *preempt) +{ + settings_t *settings = config_get_ptr(); + runloop_state_t *runloop_st = &runloop_state; + retro_input_state_t state_cb = input_driver_state_wrapper; + unsigned max_users = settings->uints.input_max_users; + int16_t joypad_state; + unsigned p; + + input_driver_poll(); + + /* Check for input state changes */ + for (p = 0; p < max_users; p++) + { + unsigned mapped_port = settings->uints.input_remap_ports[p]; + unsigned device = settings->uints.input_libretro_device[mapped_port] + & RETRO_DEVICE_MASK; + + switch (device) + { + case RETRO_DEVICE_JOYPAD: + case RETRO_DEVICE_ANALOG: + /* Check full digital joypad */ + joypad_state = state_cb(mapped_port, RETRO_DEVICE_JOYPAD, + 0, RETRO_DEVICE_ID_JOYPAD_MASK); + if (joypad_state != preempt->joypad_state[p]) + { + preempt->joypad_state[p] = joypad_state; + runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY; + } + + /* Check requested analogs */ + if ( preempt->analog_mask[p] + && preempt_analog_input_dirty(preempt, state_cb, p, mapped_port)) + runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY; + break; + case RETRO_DEVICE_MOUSE: + case RETRO_DEVICE_LIGHTGUN: + case RETRO_DEVICE_POINTER: + /* Check full device state */ + if (preempt_ptr_input_dirty( + preempt, state_cb, preempt->ptr_dev[p], p, mapped_port)) + runloop_st->flags |= RUNLOOP_FLAG_INPUT_IS_DIRTY; + break; + default: + break; + } + } + + /* Clear requested inputs */ + memset(preempt->analog_mask, 0, max_users * sizeof(uint32_t)); + memset(preempt->ptr_dev, 0, max_users * sizeof(uint8_t)); +} + +static INLINE void preempt_suspend_av(bool suspend) +{ + audio_driver_state_t *audio_st = audio_state_get_ptr(); + video_driver_state_t *video_st = video_state_get_ptr(); + + if (suspend) + { + audio_st->flags |= AUDIO_FLAG_SUSPENDED; + video_st->flags &= ~VIDEO_FLAG_ACTIVE; + } + else + { + audio_st->flags &= ~AUDIO_FLAG_SUSPENDED; + video_st->flags |= VIDEO_FLAG_ACTIVE; + } +} + +/* macro for preempt_run */ +#define PREEMPT_NEXT_PTR(x) ((x + 1) % preempt->frames) + +/** + * preempt_run: + * @preempt : pointer to preemptive frames object + * + * Call in place of core_run() for preemptive frames. + **/ +static void preempt_run(preempt_t *preempt) +{ + runloop_state_t *runloop_st = &runloop_state; + struct retro_core_t *current_core = &runloop_st->current_core; + const char *failed_str = NULL; + + /* Poll and check for dirty input */ + preempt_input_poll(preempt); + + runloop_st->flags |= RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE; + + if ((runloop_st->flags & RUNLOOP_FLAG_INPUT_IS_DIRTY) + && preempt->frame_count >= preempt->frames) + { + /* Suspend A/V and run preemptive frames */ + preempt_suspend_av(true); + + if (!current_core->retro_unserialize( + preempt->buffer[preempt->start_ptr], preempt->state_size)) + { + failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_LOAD_STATE); + goto error; + } + + current_core->retro_run(); + preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr); + + while (preempt->replay_ptr != preempt->start_ptr) + { + if (!current_core->retro_serialize( + preempt->buffer[preempt->replay_ptr], preempt->state_size)) + { + failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE); + goto error; + } + + current_core->retro_run(); + preempt->replay_ptr = PREEMPT_NEXT_PTR(preempt->replay_ptr); + } + + preempt_suspend_av(false); + } + + /* Save current state and set start_ptr to oldest state */ + if (!current_core->retro_serialize( + preempt->buffer[preempt->start_ptr], preempt->state_size)) + { + failed_str = msg_hash_to_str(MSG_PREEMPT_FAILED_TO_SAVE_STATE); + goto error; + } + preempt->start_ptr = PREEMPT_NEXT_PTR(preempt->start_ptr); + runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE + | RUNLOOP_FLAG_INPUT_IS_DIRTY); + + /* Run normal frame */ + current_core->retro_run(); + preempt->frame_count++; + return; + +error: + runloop_st->flags &= ~(RUNLOOP_FLAG_REQUEST_SPECIAL_SAVESTATE + | RUNLOOP_FLAG_INPUT_IS_DIRTY); + preempt_suspend_av(false); + runloop_preempt_deinit(); + + if (!config_get_ptr()->bools.preemptive_frames_hide_warnings) + runloop_msg_queue_push( + failed_str, 0, 2 * 60, true, + NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO); + RARCH_ERR("[Preemptive Frames]: %s\n", failed_str); +} + #endif static retro_time_t runloop_core_runtime_tick( @@ -5583,6 +5957,8 @@ static void core_init_libretro_cbs(runloop_state_t *runloop_st, runloop_st->current_core.retro_set_input_state(state_cb); runloop_st->current_core.retro_set_input_poll(core_input_state_poll_maybe); + runloop_st->input_poll_callback_original = core_input_state_poll_maybe; + core_set_default_callbacks(cbs); #ifdef HAVE_NETWORKING @@ -7169,6 +7545,9 @@ static enum runloop_state_enum runloop_check_state( /* Check if we have pressed the Run-Ahead toggle button */ HOTKEY_CHECK(RARCH_RUNAHEAD_TOGGLE, CMD_EVENT_RUNAHEAD_TOGGLE, true, NULL); + /* Check if we have pressed the Preemptive Frames toggle button */ + HOTKEY_CHECK(RARCH_PREEMPT_TOGGLE, CMD_EVENT_PREEMPT_TOGGLE, true, NULL); + /* Check if we have pressed the AI Service toggle button */ HOTKEY_CHECK(RARCH_AI_SERVICE, CMD_EVENT_AI_SERVICE_TOGGLE, true, NULL); @@ -8004,6 +8383,8 @@ int runloop_iterate(void) run_ahead_num_frames, run_ahead_hide_warnings, run_ahead_secondary_instance); + else if (runloop_st->preempt_data) + preempt_run(runloop_st->preempt_data); else #endif core_run(); @@ -8522,6 +8903,9 @@ bool core_unserialize(retro_ctx_serialize_info_t *info) #ifdef HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, info); #endif +#if HAVE_RUNAHEAD + command_event(CMD_EVENT_PREEMPT_RESET_BUFFER, NULL); +#endif return true; } diff --git a/runloop.h b/runloop.h index 94b4347322..3e99ca7a38 100644 --- a/runloop.h +++ b/runloop.h @@ -159,7 +159,9 @@ typedef struct core_options_callbacks } core_options_callbacks_t; #ifdef HAVE_RUNAHEAD -typedef bool (*runahead_load_state_function)(const void*, size_t); +#define MAX_RUNAHEAD_FRAMES 12 + +typedef bool(*runahead_load_state_function)(const void*, size_t); typedef void *(*constructor_t)(void); typedef void (*destructor_t )(void*); @@ -172,6 +174,33 @@ typedef struct my_list_t int capacity; int size; } my_list; + +typedef struct preemptive_frames_data +{ + /* Savestate buffer */ + void* buffer[MAX_RUNAHEAD_FRAMES]; + size_t state_size; + + /* Number of latency frames to remove */ + uint8_t frames; + + /* Buffer indexes for replays */ + uint8_t start_ptr; + uint8_t replay_ptr; + + /* Frame count since buffer init/reset */ + uint64_t frame_count; + + /* Input states. Replays triggered on changes */ + int16_t joypad_state[MAX_USERS]; + int16_t analog_state[MAX_USERS][20]; + int16_t ptrdev_state[MAX_USERS][4]; + + /* Pointing device requested */ + uint8_t ptr_dev[MAX_USERS]; + /* Mask of analog states requested */ + uint32_t analog_mask[MAX_USERS]; +} preempt_t; #endif struct runloop @@ -197,6 +226,7 @@ struct runloop #endif my_list *runahead_save_state_list; my_list *input_state_list; + preempt_t *preempt_data; #endif #ifdef HAVE_REWIND @@ -208,6 +238,7 @@ struct runloop struct retro_subsystem_info subsystem_data[SUBSYSTEM_MAX_SUBSYSTEMS]; struct retro_callbacks retro_ctx; /* ptr alignment */ msg_queue_t msg_queue; /* ptr alignment */ + retro_input_poll_t input_poll_callback_original; /* ptr alignment */ retro_input_state_t input_state_callback_original; /* ptr alignment */ #ifdef HAVE_RUNAHEAD function_t retro_reset_callback_original; /* ptr alignment */ @@ -384,6 +415,9 @@ void runloop_event_deinit_core(void); #ifdef HAVE_RUNAHEAD void runloop_runahead_clear_variables(runloop_state_t *runloop_st); + +bool runloop_preempt_init(void); +void runloop_preempt_deinit(void); #endif bool runloop_event_init_core(