From 121ca3a4826395367f71799de36e57d0b29dafa8 Mon Sep 17 00:00:00 2001 From: Tony <45124675+sonninnos@users.noreply.github.com> Date: Thu, 2 Dec 2021 03:32:39 +0200 Subject: [PATCH] 'Automatic Frame Delay' improvements: (#13297) - swap interval handling - d3dx handling --- gfx/video_driver.c | 160 +++++++++++++++++++++++++++++++++++++++++++++ gfx/video_driver.h | 10 +++ retroarch.c | 4 ++ runloop.c | 60 ++++------------- 4 files changed, 187 insertions(+), 47 deletions(-) diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 8595a041ec..1bcadada35 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -3975,3 +3975,163 @@ void video_driver_reinit(int flags) video_driver_reinit_context(settings, flags); video_st->cache_context = false; } + +#define FRAME_DELAY_AUTO_DEBUG 0 +void video_frame_delay_auto(video_driver_state_t *video_st, video_frame_delay_auto_t *vfda) +{ + unsigned i = 0; + unsigned frame_time = 0; + unsigned frame_time_frames = vfda->frame_time_interval; + unsigned frame_time_target = 1000000.0f / vfda->refresh_rate; + unsigned frame_time_limit_min = frame_time_target * 1.30f; + unsigned frame_time_limit_med = frame_time_target * 1.50f; + unsigned frame_time_limit_max = frame_time_target * 1.90f; + unsigned frame_time_limit_cap = frame_time_target * 2.50f; + unsigned frame_time_limit_ign = frame_time_target * 3.75f; + unsigned frame_time_min = frame_time_target; + unsigned frame_time_max = frame_time_target; + unsigned frame_time_count_pos = 0; + unsigned frame_time_count_min = 0; + unsigned frame_time_count_med = 0; + unsigned frame_time_count_max = 0; + unsigned frame_time_count_ign = 0; + unsigned frame_time_index = + (video_st->frame_time_count & + (MEASURE_FRAME_TIME_SAMPLES_COUNT - 1)); + + /* Calculate average frame time */ + for (i = 1; i < frame_time_frames + 1; i++) + { + unsigned frame_time_i = 0; + + if (i > frame_time_index) + continue; + + frame_time_i = video_st->frame_time_samples[frame_time_index - i]; + + if (frame_time_max < frame_time_i) + frame_time_max = frame_time_i; + if (frame_time_min > frame_time_i) + frame_time_min = frame_time_i; + + /* Count frames over the target */ + if (frame_time_i > frame_time_target) + { + frame_time_count_pos++; + if (frame_time_i > frame_time_limit_min) + frame_time_count_min++; + if (frame_time_i > frame_time_limit_med) + frame_time_count_med++; + if (frame_time_i > frame_time_limit_max) + frame_time_count_max++; + if (frame_time_i > frame_time_limit_ign) + frame_time_count_ign++; + + /* Limit maximum to prevent false positives */ + if (frame_time_i > frame_time_limit_cap) + frame_time_i = frame_time_limit_cap; + } + + frame_time += frame_time_i; + } + + frame_time /= frame_time_frames; + + /* Ignore values when core is doing internal frame skipping */ + if (frame_time_count_ign > 0) + frame_time = 0; + + /* Special handlings for different video driver frame timings */ + if (frame_time < frame_time_limit_med && frame_time > frame_time_target) + { + unsigned frame_time_frames_half = frame_time_frames / 2; + unsigned frame_time_delta = frame_time_max - frame_time_min; + + /* Ensure outcome on certain conditions */ + int mode = 0; + + /* All frames are above the target */ + if (frame_time_count_pos == frame_time_frames) + mode = 1; + /* At least half of interval frames are above minimum level */ + else if (frame_time_count_min >= frame_time_frames_half) + mode = 2; + /* D3Dx stripe equalizer */ + else if ( + frame_time_count_pos == frame_time_frames_half + && frame_time_count_min >= 1 + && frame_time_delta > (frame_time_target / 3) + && frame_time_delta < (frame_time_target / 2) + && frame_time > frame_time_target + ) + mode = 3; + /* Boost med/max spikes */ + else if ( + frame_time_count_pos >= frame_time_frames_half + && ( frame_time_count_max > 0 + || frame_time_count_med > 1) + && frame_time_count_max == frame_time_count_med + && frame_time_delta < frame_time_target + ) + mode = 4; + /* Ignore */ + else if (frame_time_delta > frame_time_target + && frame_time_count_med == 0 + ) + mode = -1; + + if (mode > 0) + { +#if FRAME_DELAY_AUTO_DEBUG + RARCH_LOG("[Video]: Frame delay nudge %d by mode %d.\n", frame_time, mode); +#endif + frame_time = frame_time_limit_med; + } + else if (mode < 0) + { +#if FRAME_DELAY_AUTO_DEBUG + RARCH_LOG("[Video]: Frame delay ignore %d.\n", frame_time); +#endif + frame_time = 0; + } + } + + /* Final output decision */ + if (frame_time > frame_time_limit_min) + { + unsigned delay_decrease = 1; + + /* Increase decrease the more frame time is off target */ + if (frame_time > frame_time_limit_med && video_st->frame_delay_effective > delay_decrease) + { + delay_decrease++; + if (frame_time > frame_time_limit_max && video_st->frame_delay_effective > delay_decrease) + delay_decrease++; + } + + vfda->decrease = delay_decrease; + } + + vfda->time = frame_time; + vfda->target = frame_time_target; + +#if FRAME_DELAY_AUTO_DEBUG + if (frame_time_index > frame_time_frames) + RARCH_LOG("[Video]: %5d / pos:%d min:%d med:%d max:%d / delta:%5d = %5d %5d %5d %5d %5d %5d %5d %5d\n", + frame_time, + frame_time_count_pos, + frame_time_count_min, + frame_time_count_med, + frame_time_count_max, + frame_time_max - frame_time_min, + video_st->frame_time_samples[frame_time_index - 1], + video_st->frame_time_samples[frame_time_index - 2], + video_st->frame_time_samples[frame_time_index - 3], + video_st->frame_time_samples[frame_time_index - 4], + video_st->frame_time_samples[frame_time_index - 5], + video_st->frame_time_samples[frame_time_index - 6], + video_st->frame_time_samples[frame_time_index - 7], + video_st->frame_time_samples[frame_time_index - 8] + ); +#endif +} diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 911c473b55..55d38d67d9 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -958,6 +958,14 @@ typedef struct #endif } video_driver_state_t; +typedef struct video_frame_delay_auto { + float refresh_rate; + unsigned frame_time_interval; + unsigned decrease; + unsigned target; + unsigned time; +} video_frame_delay_auto_t; + extern struct aspect_ratio_elem aspectratio_lut[ASPECT_RATIO_END]; bool video_driver_has_windowed(void); @@ -1227,6 +1235,8 @@ bool *video_driver_get_threaded(void); void video_driver_set_threaded(bool val); +void video_frame_delay_auto(video_driver_state_t *video_st, video_frame_delay_auto_t *vfda); + /** * video_context_driver_init: * @core_set_shared_context : Boolean value that tells us whether shared context diff --git a/retroarch.c b/retroarch.c index a94c6a7f5e..618bc6a4a4 100644 --- a/retroarch.c +++ b/retroarch.c @@ -1813,6 +1813,10 @@ bool command_event(enum event_command cmd, void *data) #if HAVE_NETWORKING netplay_driver_ctl(RARCH_NETPLAY_CTL_RESET, NULL); #endif + /* Recalibrate frame delay target */ + if (settings->bools.video_frame_delay_auto) + video_st->frame_delay_target = 0; + return false; case CMD_EVENT_SAVE_STATE: case CMD_EVENT_SAVE_STATE_TO_RAM: diff --git a/runloop.c b/runloop.c index ead689e0b5..1d30022bd3 100644 --- a/runloop.c +++ b/runloop.c @@ -7554,12 +7554,17 @@ int runloop_iterate(void) if (settings->bools.video_frame_delay_auto) { float refresh_rate = settings->floats.video_refresh_rate; + unsigned video_swap_interval = settings->uints.video_swap_interval; + unsigned video_bfi = settings->uints.video_black_frame_insertion; unsigned frame_time_interval = 8; bool frame_time_update = /* Skip some starting frames for stabilization */ - video_st->frame_count > 10 && + video_st->frame_count > frame_time_interval && video_st->frame_count % frame_time_interval == 0; + /* Black frame insertion + swap interval multiplier */ + refresh_rate = (refresh_rate / (video_bfi + 1.0f) / video_swap_interval); + /* Set target moderately as half frame time with 0 delay */ if (video_frame_delay == 0) video_frame_delay = 1 / refresh_rate * 1000 / 2; @@ -7572,55 +7577,16 @@ int runloop_iterate(void) if (video_frame_delay_effective > 0 && frame_time_update) { - unsigned i = 0; - unsigned frame_time = 0; - unsigned frame_time_frames = frame_time_interval - 1; - unsigned frame_time_target = 1000000.0f / refresh_rate; - unsigned frame_time_limit_min = frame_time_target * 1.25; - unsigned frame_time_limit_med = frame_time_target * 1.50; - unsigned frame_time_limit_max = frame_time_target * 1.90; - unsigned frame_time_limit_cap = frame_time_target * 2.25; - unsigned frame_time_limit_ign = frame_time_target * 2.50; - unsigned frame_time_index = - (video_st->frame_time_count & - (MEASURE_FRAME_TIME_SAMPLES_COUNT - 1)); + video_frame_delay_auto_t vfda = {0}; + vfda.frame_time_interval = frame_time_interval; + vfda.refresh_rate = refresh_rate; - /* Calculate average frame time to balance spikes */ - for (i = 1; i < frame_time_frames + 1; i++) + video_frame_delay_auto(video_st, &vfda); + if (vfda.decrease > 0) { - retro_time_t frame_time_i = 0; - - if (i > (unsigned)frame_time_index) - continue; - - frame_time_i = video_st->frame_time_samples[frame_time_index - i]; - - /* Ignore values when core is doing internal frame skipping */ - if (frame_time_i > frame_time_limit_ign) - frame_time_i = 0; - /* Limit maximum to prevent false positives */ - else if (frame_time_i > frame_time_limit_cap) - frame_time_i = frame_time_limit_cap; - - frame_time += frame_time_i; - } - frame_time /= frame_time_frames; - - if (frame_time > frame_time_limit_min) - { - unsigned delay_decrease = 1; - - /* Increase decrease the more frame time is off target */ - if (frame_time > frame_time_limit_med && video_frame_delay_effective > delay_decrease) - { - delay_decrease++; - if (frame_time > frame_time_limit_max && video_frame_delay_effective > delay_decrease) - delay_decrease++; - } - - video_frame_delay_effective -= delay_decrease; + video_frame_delay_effective -= vfda.decrease; RARCH_LOG("[Video]: Frame delay decrease by %d to %d due to frame time: %d > %d.\n", - delay_decrease, video_frame_delay_effective, frame_time, frame_time_target); + vfda.decrease, video_frame_delay_effective, vfda.time, vfda.target); } } }