From f4519939652a1ba7e37b6d60179e43ef4e6b602f Mon Sep 17 00:00:00 2001 From: LibretroAdmin Date: Sun, 4 Oct 2026 11:13:05 +0200 Subject: [PATCH] rewind: the buffer a load asks for is set up on the frame after The drivers stage marks the rewind buffer pending instead of setting it up, and runloop_check_state sets it up on the next frame, before the core runs, so the full serialize it takes is no longer part of the load. Rewind init and deinit clear the pending mark. The content_load harness checks no frame of a load sets up the buffer and the first frame after does. --- retroarch.c | 4 +- runloop.c | 14 +++++ .../tasks/content_load/content_load_test.c | 52 +++++++++++++++++++ state_manager.c | 9 ++-- state_manager.h | 5 +- 5 files changed, 79 insertions(+), 5 deletions(-) diff --git a/retroarch.c b/retroarch.c index 0e07c9aa03..5fd17d83f7 100644 --- a/retroarch.c +++ b/retroarch.c @@ -9074,7 +9074,9 @@ bool retroarch_main_init_drivers(bool staged, command_event(CMD_EVENT_CONTROLLER_INIT, NULL); #ifdef HAVE_REWIND - command_event(CMD_EVENT_REWIND_INIT, NULL); + /* Set up on the frame after the load, before the core runs: the + * buffer is one full serialize of the core */ + runloop_st->rewind_st.flags |= STATE_MGR_REWIND_ST_FLAG_INIT_PENDING; #endif if (*rec_st->path) diff --git a/runloop.c b/runloop.c index 8bad36a1a0..2a6e2df72b 100644 --- a/runloop.c +++ b/runloop.c @@ -7029,6 +7029,17 @@ static void runloop_idle_wait(void) retro_sleep(10); } +#ifdef HAVE_REWIND +/* The rewind buffer a load asked for: one full serialize of the core, + * taken on the frame after the load, before the core runs. */ +static VIDEO_NOINLINE void runloop_rewind_init_pending( + runloop_state_t *runloop_st) +{ + runloop_st->rewind_st.flags &= ~STATE_MGR_REWIND_ST_FLAG_INIT_PENDING; + command_event(CMD_EVENT_REWIND_INIT, NULL); +} +#endif + static enum runloop_state_enum runloop_check_state( input_driver_state_t *input_st, audio_driver_state_t *audio_st, @@ -8203,6 +8214,9 @@ static enum runloop_state_enum runloop_check_state( return RUNLOOP_STATE_PAUSE; } + if (runloop_st->rewind_st.flags & STATE_MGR_REWIND_ST_FLAG_INIT_PENDING) + runloop_rewind_init_pending(runloop_st); + rewinding = state_manager_check_rewind( &runloop_st->rewind_st, &runloop_st->current_core, diff --git a/samples/tasks/content_load/content_load_test.c b/samples/tasks/content_load/content_load_test.c index 8691382af5..f6ccc3d167 100644 --- a/samples/tasks/content_load/content_load_test.c +++ b/samples/tasks/content_load/content_load_test.c @@ -68,6 +68,7 @@ #include "../../../content.h" #include "../../../input/input_driver.h" #include "../../../runahead.h" +#include "../../../state_manager.h" #ifdef HAVE_NETWORKING #include "../../../network/netplay/netplay.h" #endif @@ -510,6 +511,54 @@ static void lane_contentless_names(void) } #endif +#ifdef HAVE_REWIND +/* The rewind buffer is one full serialize of the core: a load leaves + * it for the frame after, which takes it before the core runs. */ +static void lane_rewind_after_load(void) +{ + struct load_frame log[LOAD_FRAMES]; + settings_t *settings = config_get_ptr(); + runloop_state_t *runloop_st = runloop_state_get_ptr(); + bool rewind_enable = settings->bools.rewind_enable; + unsigned n, i; + unsigned had = failures; + + configuration_set_bool(settings, settings->bools.rewind_enable, true); + open_menu(); + CHECK(task_push_load_contentless_core_from_menu(core_path), + "the load was not started"); + for (n = 0; n < LOAD_FRAMES && runloop_is_content_switching(); n++) + { + runloop_iterate(); + task_queue_check(); + CHECK(!(runloop_st->rewind_st.flags + & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED), + "frame %u of the load set up the rewind buffer", n); + } + CHECK(!runloop_is_content_switching() && core_is_up(), + "the load did not finish"); + CHECK(runloop_st->rewind_st.flags & STATE_MGR_REWIND_ST_FLAG_INIT_PENDING, + "the load did not leave the rewind buffer for the frame after"); + for (i = 0; i < 2 && !(runloop_st->rewind_st.flags + & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED); i++) + { + runloop_iterate(); + task_queue_check(); + } + CHECK(i == 1, + "the rewind buffer was set up %u frames after the load, not on " + "the first", i); + CHECK(!(runloop_st->rewind_st.flags & STATE_MGR_REWIND_ST_FLAG_INIT_PENDING), + "the rewind buffer is still pending after it was set up"); + + command_event(CMD_EVENT_REWIND_DEINIT, NULL); + configuration_set_bool(settings, settings->bools.rewind_enable, + rewind_enable); + if (failures == had) + fprintf(stderr, "[pass] rewind-after-load lane\n"); +} +#endif + static void lane_one_at_a_time(void) { struct load_frame log[LOAD_FRAMES]; @@ -2062,6 +2111,9 @@ int main(int argc, char *argv[]) lane_probe_cached(); #endif lane_one_at_a_time(); +#ifdef HAVE_REWIND + lane_rewind_after_load(); +#endif lane_reinit_deferred(); lane_fallback(); lane_fallback_threaded(); diff --git a/state_manager.c b/state_manager.c index 48b57b617e..1f8843cca2 100644 --- a/state_manager.c +++ b/state_manager.c @@ -767,9 +767,11 @@ void state_manager_event_init( core_info_t *core_info = NULL; void *state = NULL; - if ( !rewind_st - || (rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED) - || rewind_st->state) + if (!rewind_st) + return; + rewind_st->flags &= ~STATE_MGR_REWIND_ST_FLAG_INIT_PENDING; + if ( (rewind_st->flags & STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED) + || rewind_st->state) return; rewind_st->size = 0; @@ -862,6 +864,7 @@ void state_manager_event_deinit( | STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED | STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED | STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED + | STATE_MGR_REWIND_ST_FLAG_INIT_PENDING ); /* Restore regular (non-rewind) core audio diff --git a/state_manager.h b/state_manager.h index 20a0613755..b647698a95 100644 --- a/state_manager.h +++ b/state_manager.h @@ -34,7 +34,10 @@ enum state_manager_rewind_st_flags STATE_MGR_REWIND_ST_FLAG_FRAME_IS_REVERSED = (1 << 0), STATE_MGR_REWIND_ST_FLAG_INIT_ATTEMPTED = (1 << 1), STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_CHECKED = (1 << 2), - STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED = (1 << 3) + STATE_MGR_REWIND_ST_FLAG_HOTKEY_WAS_PRESSED = (1 << 3), + /* A load wants the buffer: it is set up on the frame after, + * before the core runs, rather than inside the load */ + STATE_MGR_REWIND_ST_FLAG_INIT_PENDING = (1 << 4) }; struct state_manager