From a953b27614672dd3d2c00ee6b88535e42eaf63be Mon Sep 17 00:00:00 2001 From: jdgleaver <38211560+jdgleaver@users.noreply.github.com> Date: Mon, 31 Jan 2022 15:32:17 +0000 Subject: [PATCH] Rework optional fast-forward frame skipping: Drop frames based on frame timing (#13578) --- gfx/video_driver.c | 59 +++++++++++++++++++++++++++++++++++++--------- gfx/video_driver.h | 2 ++ runloop.c | 25 ++++++-------------- runloop.h | 2 -- 4 files changed, 57 insertions(+), 31 deletions(-) diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 3ac2f0f699..f9d0a4feae 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -2803,6 +2803,7 @@ void video_driver_build_info(video_frame_info_t *video_info) video_info->runloop_is_paused = runloop_st->paused; video_info->runloop_is_slowmotion = runloop_st->slowmotion; + video_info->fastforward_frameskip = settings->bools.fastforward_frameskip; video_info->input_driver_nonblock_state = input_st ? input_st->nonblocking_flag : false; @@ -3568,13 +3569,16 @@ void video_driver_frame(const void *data, unsigned width, { char status_text[128]; static char video_driver_msg[256]; + static retro_time_t last_time; static retro_time_t curr_time; static retro_time_t fps_time; + static retro_time_t frame_time_accumulator; static float last_fps, frame_time; static uint64_t last_used_memory, last_total_memory; /* Initialise 'last_frame_duped' to 'true' * to ensure that the first frame is rendered */ static bool last_frame_duped = true; + bool render_frame = true; retro_time_t new_time; video_frame_info_t video_info; video_driver_state_t *video_st= &video_driver_st; @@ -3582,7 +3586,6 @@ void video_driver_frame(const void *data, unsigned width, const enum retro_pixel_format video_driver_pix_fmt = video_st->pix_fmt; bool runloop_idle = runloop_st->idle; - bool render_frame = !runloop_st->fastforward_frameskip_frames_current; bool video_driver_active = video_st->active; #if defined(HAVE_GFX_WIDGETS) bool widgets_active = dispwidget_get_ptr()->active; @@ -3621,17 +3624,55 @@ void video_driver_frame(const void *data, unsigned width, video_driver_build_info(&video_info); - /* Always render a frame if: - * - Menu is open + /* If fast forward is active and fast forward + * frame skipping is enabled, drop any frames + * that occur at a rate higher than the core-set + * refresh rate. However: We must always render + * the current frame when: + * - The menu is open * - The last frame was NULL and the * current frame is not (i.e. if core was * previously sending duped frames, ensure * that the next frame update is captured) */ - render_frame |= video_info.menu_is_alive || (last_frame_duped && !!data); - last_frame_duped = !data; + if (video_info.input_driver_nonblock_state && + video_info.fastforward_frameskip && + !(video_info.menu_is_alive || + (last_frame_duped && !!data))) + { + /* Accumulate the elapsed time since the + * last frame */ + frame_time_accumulator += new_time - last_time; - if (!render_frame) - runloop_st->fastforward_frameskip_frames_current--; + /* Render frame if the accumulated time is + * greater than or equal to the expected + * core frame time */ + render_frame = frame_time_accumulator >= + video_st->core_frame_time; + + /* If frame is to be rendered, subtract + * expected frame time from accumulator */ + if (render_frame) + { + frame_time_accumulator -= video_st->core_frame_time; + + /* If fast forward is working correctly, + * the actual frame time will always be + * less than the expected frame time. + * But if the host cannot run the core + * fast enough to achieve at least 1x + * speed then the frame time accumulator + * will never empty and may potentially + * overflow. If a 'runaway' accumulator + * is detected, we simply reset it */ + if (frame_time_accumulator > video_st->core_frame_time) + frame_time_accumulator = 0; + } + } + else + frame_time_accumulator = 0; + + last_time = new_time; + last_frame_duped = !data; /* Get the amount of frames per seconds. */ if (video_st->frame_count) @@ -3933,16 +3974,12 @@ void video_driver_frame(const void *data, unsigned width, } if (render_frame && video_st->current_video && video_st->current_video->frame) - { video_st->active = video_st->current_video->frame( video_st->data, data, width, height, video_st->frame_count, (unsigned)pitch, video_info.menu_screensaver_active || video_info.notifications_hidden ? "" : video_driver_msg, &video_info); - runloop_st->fastforward_frameskip_frames_current = runloop_st->fastforward_frameskip_frames; - } - video_st->frame_count++; /* Display the status text, with a higher priority. */ diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 45adfa8037..b3dd7d977a 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -497,6 +497,7 @@ typedef struct video_frame_info bool timedate_enable; bool runloop_is_slowmotion; bool runloop_is_paused; + bool fastforward_frameskip; bool menu_is_alive; bool menu_screensaver_active; bool msg_bgcolor_enable; @@ -819,6 +820,7 @@ typedef struct #endif struct retro_system_av_info av_info; /* double alignment */ retro_time_t frame_time_samples[MEASURE_FRAME_TIME_SAMPLES_COUNT]; + retro_time_t core_frame_time; uint64_t frame_time_count; uint64_t frame_count; uint8_t *record_gpu_buffer; diff --git a/runloop.c b/runloop.c index 95e8f5c29e..02f29d9384 100644 --- a/runloop.c +++ b/runloop.c @@ -2628,6 +2628,10 @@ bool runloop_environment_cb(unsigned cmd, void *data) (*info)->timing.sample_rate); memcpy(av_info, *info, sizeof(*av_info)); + video_st->core_frame_time = 1000000 / + ((video_st->av_info.timing.fps > 0.0) ? + video_st->av_info.timing.fps : 60.0); + command_event(CMD_EVENT_REINIT, &reinit_flags); if (no_video_reinit) video_driver_set_aspect_ratio(); @@ -4910,22 +4914,6 @@ static bool core_unload_game(void) return true; } -static void runloop_apply_fastmotion_frameskip(runloop_state_t *runloop_st, settings_t *settings) -{ - unsigned frames = 0; - - if (runloop_st->fastmotion && settings->bools.fastforward_frameskip) - { - frames = (unsigned)settings->floats.fastforward_ratio; - /* Pick refresh rate as unlimited throttle rate */ - frames = (!frames) ? (unsigned)roundf(settings->floats.video_refresh_rate) : frames; - /* Decrease one to represent skipped frames */ - frames--; - } - - runloop_st->fastforward_frameskip_frames_current = runloop_st->fastforward_frameskip_frames = frames; -} - static void runloop_apply_fastmotion_override(runloop_state_t *runloop_st, settings_t *settings) { video_driver_state_t *video_st = video_state_get_ptr(); @@ -4963,7 +4951,6 @@ static void runloop_apply_fastmotion_override(runloop_state_t *runloop_st, setti if (!runloop_st->fastmotion) runloop_st->fastforward_after_frames = 1; - runloop_apply_fastmotion_frameskip(runloop_st, settings); driver_set_nonblock_state(); /* Reset frame time counter when toggling @@ -5315,6 +5302,9 @@ static bool core_load(unsigned poll_type_behavior) return false; runloop_st->current_core.retro_get_system_av_info(&video_st->av_info); + video_st->core_frame_time = 1000000 / + ((video_st->av_info.timing.fps > 0.0) ? + video_st->av_info.timing.fps : 60.0); return true; } @@ -7080,7 +7070,6 @@ static enum runloop_state_enum runloop_check_state( runloop_st->fastmotion = true; } - runloop_apply_fastmotion_frameskip(runloop_st, settings); driver_set_nonblock_state(); /* Reset frame time counter when toggling diff --git a/runloop.h b/runloop.h index 1e29a8a70d..2beb9b8048 100644 --- a/runloop.h +++ b/runloop.h @@ -216,8 +216,6 @@ struct runloop unsigned max_frames; unsigned audio_latency; unsigned fastforward_after_frames; - unsigned fastforward_frameskip_frames; - unsigned fastforward_frameskip_frames_current; unsigned perf_ptr_libretro; unsigned subsystem_current_count; unsigned entry_state_slot;