mirror of
https://github.com/libretro/RetroArch.git
synced 2024-11-24 08:30:16 +00:00
529 lines
13 KiB
C
529 lines
13 KiB
C
#include <stddef.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <boolean.h>
|
|
|
|
#include "dirty_input.h"
|
|
#include "mylist.h"
|
|
#include "secondary_core.h"
|
|
#include "run_ahead.h"
|
|
|
|
#include "../core.h"
|
|
#include "../dynamic.h"
|
|
#include "../audio/audio_driver.h"
|
|
#include "../gfx/video_driver.h"
|
|
#include "../configuration.h"
|
|
#include "../retroarch.h"
|
|
|
|
static bool runahead_create(void);
|
|
static bool runahead_save_state(void);
|
|
static bool runahead_load_state(void);
|
|
static bool runahead_load_state_secondary(void);
|
|
static bool runahead_run_secondary(void);
|
|
static void runahead_suspend_audio(void);
|
|
static void runahead_resume_audio(void);
|
|
static void runahead_suspend_video(void);
|
|
static void runahead_resume_video(void);
|
|
static void set_fast_savestate(void);
|
|
static void unset_fast_savestate(void);
|
|
static void set_hard_disable_audio(void);
|
|
static void unset_hard_disable_audio(void);
|
|
|
|
static bool core_run_use_last_input(void);
|
|
|
|
static size_t runahead_save_state_size = 0;
|
|
static bool runahead_save_state_size_known = false;
|
|
|
|
/* Save State List for Run Ahead */
|
|
static MyList *runahead_save_state_list;
|
|
|
|
static void *runahead_save_state_alloc(void)
|
|
{
|
|
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)
|
|
malloc(sizeof(retro_ctx_serialize_info_t));
|
|
|
|
if (!savestate)
|
|
return NULL;
|
|
|
|
savestate->data = NULL;
|
|
savestate->data_const = NULL;
|
|
savestate->size = 0;
|
|
|
|
if (runahead_save_state_size > 0 && runahead_save_state_size_known)
|
|
{
|
|
savestate->data = malloc(runahead_save_state_size);
|
|
savestate->data_const = savestate->data;
|
|
savestate->size = runahead_save_state_size;
|
|
}
|
|
|
|
return savestate;
|
|
}
|
|
|
|
static void runahead_save_state_free(void *state)
|
|
{
|
|
retro_ctx_serialize_info_t *savestate = (retro_ctx_serialize_info_t*)state;
|
|
if (!savestate)
|
|
return;
|
|
free(savestate->data);
|
|
free(savestate);
|
|
}
|
|
|
|
static void runahead_save_state_list_init(size_t saveStateSize)
|
|
{
|
|
runahead_save_state_size = saveStateSize;
|
|
runahead_save_state_size_known = true;
|
|
mylist_create(&runahead_save_state_list, 16,
|
|
runahead_save_state_alloc, runahead_save_state_free);
|
|
}
|
|
|
|
static void runahead_save_state_list_destroy(void)
|
|
{
|
|
mylist_destroy(&runahead_save_state_list);
|
|
}
|
|
|
|
#if 0
|
|
static void runahead_save_state_list_rotate(void)
|
|
{
|
|
int i;
|
|
void *element;
|
|
void *firstElement;
|
|
firstElement = runahead_save_state_list->data[0];
|
|
for (i = 1; i < runahead_save_state_list->size; i++)
|
|
runahead_save_state_list->data[i - 1] =
|
|
runahead_save_state_list->data[i];
|
|
runahead_save_state_list->data[runahead_save_state_list->size - 1] =
|
|
firstElement;
|
|
}
|
|
#endif
|
|
|
|
/* Hooks - Hooks to cleanup, and add dirty input hooks */
|
|
|
|
static function_t originalRetroDeinit = NULL;
|
|
static function_t originalRetroUnload = NULL;
|
|
|
|
extern struct retro_core_t current_core;
|
|
extern struct retro_callbacks retro_ctx;
|
|
|
|
static void remove_hooks(void)
|
|
{
|
|
if (originalRetroDeinit)
|
|
{
|
|
current_core.retro_deinit = originalRetroDeinit;
|
|
originalRetroDeinit = NULL;
|
|
}
|
|
|
|
if (originalRetroUnload)
|
|
{
|
|
current_core.retro_unload_game = originalRetroUnload;
|
|
originalRetroUnload = NULL;
|
|
}
|
|
remove_input_state_hook();
|
|
}
|
|
|
|
static void unload_hook(void)
|
|
{
|
|
remove_hooks();
|
|
runahead_destroy();
|
|
secondary_core_destroy();
|
|
if (current_core.retro_unload_game)
|
|
current_core.retro_unload_game();
|
|
}
|
|
|
|
|
|
static void deinit_hook(void)
|
|
{
|
|
remove_hooks();
|
|
runahead_destroy();
|
|
secondary_core_destroy();
|
|
if (current_core.retro_deinit)
|
|
current_core.retro_deinit();
|
|
}
|
|
|
|
static void add_hooks(void)
|
|
{
|
|
if (!originalRetroDeinit)
|
|
{
|
|
originalRetroDeinit = current_core.retro_deinit;
|
|
current_core.retro_deinit = deinit_hook;
|
|
}
|
|
|
|
if (!originalRetroUnload)
|
|
{
|
|
originalRetroUnload = current_core.retro_unload_game;
|
|
current_core.retro_unload_game = unload_hook;
|
|
}
|
|
add_input_state_hook();
|
|
}
|
|
|
|
|
|
/* Runahead Code */
|
|
|
|
static bool runahead_video_driver_is_active = true;
|
|
static bool runahead_available = true;
|
|
static bool runahead_secondary_core_available = true;
|
|
static bool runahead_force_input_dirty = true;
|
|
static uint64_t runahead_last_frame_count = 0;
|
|
|
|
static void runahead_clear_variables(void)
|
|
{
|
|
runahead_save_state_size = 0;
|
|
runahead_save_state_size_known = false;
|
|
runahead_video_driver_is_active = true;
|
|
runahead_available = true;
|
|
runahead_secondary_core_available = true;
|
|
runahead_force_input_dirty = true;
|
|
runahead_last_frame_count = 0;
|
|
}
|
|
|
|
static uint64_t runahead_get_frame_count()
|
|
{
|
|
bool is_alive, is_focused = false;
|
|
uint64_t frame_count = 0;
|
|
video_driver_get_status(&frame_count, &is_alive, &is_focused);
|
|
return frame_count;
|
|
}
|
|
|
|
|
|
static void runahead_check_for_gui(void)
|
|
{
|
|
/* Hack: If we were in the GUI, force a resync. */
|
|
uint64_t frame_count = runahead_get_frame_count();
|
|
|
|
if (frame_count != runahead_last_frame_count + 1)
|
|
runahead_force_input_dirty = true;
|
|
|
|
runahead_last_frame_count = frame_count;
|
|
}
|
|
|
|
void run_ahead(int runahead_count, bool useSecondary)
|
|
{
|
|
int frame_number = 0;
|
|
bool last_frame = false;
|
|
bool suspended_frame = false;
|
|
#if defined(HAVE_DYNAMIC) || defined(HAVE_DYLIB)
|
|
const bool have_dynamic = true;
|
|
#else
|
|
const bool have_dynamic = false;
|
|
#endif
|
|
|
|
if (runahead_count <= 0 || !runahead_available)
|
|
{
|
|
core_run();
|
|
runahead_force_input_dirty = true;
|
|
return;
|
|
}
|
|
|
|
if (!runahead_save_state_size_known)
|
|
{
|
|
if (!runahead_create())
|
|
{
|
|
settings_t *settings = config_get_ptr();
|
|
if (!settings->bools.run_ahead_hide_warnings)
|
|
{
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_CORE_DOES_NOT_SUPPORT_SAVESTATES), 0, 2 * 60, true);
|
|
}
|
|
core_run();
|
|
runahead_force_input_dirty = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
runahead_check_for_gui();
|
|
|
|
if (!useSecondary || !have_dynamic || !runahead_secondary_core_available)
|
|
{
|
|
/* TODO: multiple savestates for higher performance
|
|
* when not using secondary core */
|
|
for (frame_number = 0; frame_number <= runahead_count; frame_number++)
|
|
{
|
|
last_frame = frame_number == runahead_count;
|
|
suspended_frame = !last_frame;
|
|
|
|
if (suspended_frame)
|
|
{
|
|
runahead_suspend_audio();
|
|
runahead_suspend_video();
|
|
}
|
|
|
|
if (frame_number == 0)
|
|
core_run();
|
|
else
|
|
core_run_use_last_input();
|
|
|
|
if (suspended_frame)
|
|
{
|
|
runahead_resume_video();
|
|
runahead_resume_audio();
|
|
}
|
|
|
|
if (frame_number == 0)
|
|
{
|
|
if (!runahead_save_state())
|
|
{
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE), 0, 3 * 60, true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (last_frame)
|
|
{
|
|
if (!runahead_load_state())
|
|
{
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE), 0, 3 * 60, true);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if HAVE_DYNAMIC
|
|
if (!secondary_core_ensure_exists())
|
|
{
|
|
runahead_secondary_core_available = false;
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_CREATE_SECONDARY_INSTANCE), 0, 3 * 60, true);
|
|
core_run();
|
|
runahead_force_input_dirty = true;
|
|
return;
|
|
}
|
|
|
|
/* run main core with video suspended */
|
|
runahead_suspend_video();
|
|
core_run();
|
|
runahead_resume_video();
|
|
|
|
if (input_is_dirty || runahead_force_input_dirty)
|
|
{
|
|
input_is_dirty = false;
|
|
|
|
if (!runahead_save_state())
|
|
{
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_SAVE_STATE), 0, 3 * 60, true);
|
|
return;
|
|
}
|
|
|
|
if (!runahead_load_state_secondary())
|
|
{
|
|
runloop_msg_queue_push(msg_hash_to_str(MSG_RUNAHEAD_FAILED_TO_LOAD_STATE), 0, 3 * 60, true);
|
|
return;
|
|
}
|
|
|
|
for (frame_number = 0; frame_number < runahead_count - 1; frame_number++)
|
|
{
|
|
runahead_suspend_video();
|
|
runahead_suspend_audio();
|
|
set_hard_disable_audio();
|
|
runahead_run_secondary();
|
|
unset_hard_disable_audio();
|
|
runahead_resume_audio();
|
|
runahead_resume_video();
|
|
}
|
|
}
|
|
runahead_suspend_audio();
|
|
set_hard_disable_audio();
|
|
runahead_run_secondary();
|
|
unset_hard_disable_audio();
|
|
runahead_resume_audio();
|
|
#endif
|
|
}
|
|
runahead_force_input_dirty = false;
|
|
}
|
|
|
|
static void runahead_error(void)
|
|
{
|
|
runahead_available = false;
|
|
runahead_save_state_list_destroy();
|
|
remove_hooks();
|
|
runahead_save_state_size = 0;
|
|
runahead_save_state_size_known = true;
|
|
}
|
|
|
|
static bool runahead_create(void)
|
|
{
|
|
/* get savestate size and allocate buffer */
|
|
retro_ctx_size_info_t info;
|
|
set_fast_savestate();
|
|
core_serialize_size(&info);
|
|
unset_fast_savestate();
|
|
|
|
runahead_save_state_list_init(info.size);
|
|
runahead_video_driver_is_active = video_driver_is_active();
|
|
|
|
if (runahead_save_state_size == 0 || !runahead_save_state_size_known)
|
|
{
|
|
runahead_error();
|
|
return false;
|
|
}
|
|
|
|
add_hooks();
|
|
runahead_force_input_dirty = true;
|
|
mylist_resize(runahead_save_state_list, 1, true);
|
|
return true;
|
|
}
|
|
|
|
static bool runahead_save_state(void)
|
|
{
|
|
bool okay = false;
|
|
retro_ctx_serialize_info_t *serialize_info;
|
|
if (!runahead_save_state_list)
|
|
return false;
|
|
serialize_info =
|
|
(retro_ctx_serialize_info_t*)runahead_save_state_list->data[0];
|
|
set_fast_savestate();
|
|
okay = core_serialize(serialize_info);
|
|
unset_fast_savestate();
|
|
if (!okay)
|
|
{
|
|
runahead_error();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool runahead_load_state(void)
|
|
{
|
|
bool okay = false;
|
|
retro_ctx_serialize_info_t *serialize_info = (retro_ctx_serialize_info_t*)
|
|
runahead_save_state_list->data[0];
|
|
bool last_dirty = input_is_dirty;
|
|
|
|
set_fast_savestate();
|
|
/* calling core_unserialize has side effects with
|
|
* netplay (it triggers transmitting your save state)
|
|
call retro_unserialize directly from the core instead */
|
|
okay = current_core.retro_unserialize(
|
|
serialize_info->data_const, serialize_info->size);
|
|
unset_fast_savestate();
|
|
input_is_dirty = last_dirty;
|
|
|
|
if (!okay)
|
|
runahead_error();
|
|
|
|
return okay;
|
|
}
|
|
|
|
static bool runahead_load_state_secondary(void)
|
|
{
|
|
bool okay = false;
|
|
retro_ctx_serialize_info_t *serialize_info =
|
|
(retro_ctx_serialize_info_t*)runahead_save_state_list->data[0];
|
|
|
|
set_fast_savestate();
|
|
okay = secondary_core_deserialize(
|
|
serialize_info->data_const, (int)serialize_info->size);
|
|
unset_fast_savestate();
|
|
|
|
if (!okay)
|
|
{
|
|
runahead_secondary_core_available = false;
|
|
runahead_error();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool runahead_run_secondary(void)
|
|
{
|
|
if (!secondary_core_run_use_last_input())
|
|
{
|
|
runahead_secondary_core_available = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void runahead_suspend_audio(void)
|
|
{
|
|
audio_driver_suspend();
|
|
}
|
|
|
|
static void runahead_resume_audio(void)
|
|
{
|
|
audio_driver_resume();
|
|
}
|
|
|
|
static void runahead_suspend_video(void)
|
|
{
|
|
video_driver_unset_active();
|
|
}
|
|
|
|
static void runahead_resume_video(void)
|
|
{
|
|
if (runahead_video_driver_is_active)
|
|
video_driver_set_active();
|
|
else
|
|
video_driver_unset_active();
|
|
}
|
|
|
|
void runahead_destroy(void)
|
|
{
|
|
runahead_save_state_list_destroy();
|
|
remove_hooks();
|
|
runahead_clear_variables();
|
|
}
|
|
|
|
static bool request_fast_savestate;
|
|
static bool hard_disable_audio;
|
|
|
|
|
|
bool want_fast_savestate(void)
|
|
{
|
|
return request_fast_savestate;
|
|
}
|
|
|
|
static void set_fast_savestate(void)
|
|
{
|
|
request_fast_savestate = true;
|
|
}
|
|
|
|
static void unset_fast_savestate(void)
|
|
{
|
|
request_fast_savestate = false;
|
|
}
|
|
|
|
bool get_hard_disable_audio(void)
|
|
{
|
|
return hard_disable_audio;
|
|
}
|
|
|
|
static void set_hard_disable_audio(void)
|
|
{
|
|
hard_disable_audio = true;
|
|
}
|
|
|
|
static void unset_hard_disable_audio(void)
|
|
{
|
|
hard_disable_audio = false;
|
|
}
|
|
|
|
static void runahead_input_poll_null(void)
|
|
{
|
|
}
|
|
|
|
static bool core_run_use_last_input(void)
|
|
{
|
|
extern struct retro_callbacks retro_ctx;
|
|
extern struct retro_core_t current_core;
|
|
|
|
retro_input_poll_t old_poll_function = retro_ctx.poll_cb;
|
|
retro_input_state_t old_input_function = retro_ctx.state_cb;
|
|
|
|
retro_ctx.poll_cb = runahead_input_poll_null;
|
|
retro_ctx.state_cb = input_state_get_last;
|
|
|
|
current_core.retro_set_input_poll(retro_ctx.poll_cb);
|
|
current_core.retro_set_input_state(retro_ctx.state_cb);
|
|
|
|
current_core.retro_run();
|
|
|
|
retro_ctx.poll_cb = old_poll_function;
|
|
retro_ctx.state_cb = old_input_function;
|
|
|
|
current_core.retro_set_input_poll(retro_ctx.poll_cb);
|
|
current_core.retro_set_input_state(retro_ctx.state_cb);
|
|
|
|
return true;
|
|
}
|