From 3137f8470b54505aea3a081fa994f044770a2b48 Mon Sep 17 00:00:00 2001 From: Tony <45124675+sonninnos@users.noreply.github.com> Date: Sat, 6 Nov 2021 00:42:23 +0200 Subject: [PATCH] Add 'Automatic Frame Delay' option (#13190) --- config.def.h | 1 + configuration.c | 1 + configuration.h | 1 + gfx/video_driver.c | 4 +- gfx/video_driver.h | 2 + intl/msg_hash_lbl.h | 4 ++ intl/msg_hash_us.c | 13 +++++- intl/msg_hash_us.h | 8 ++++ menu/cbs/menu_cbs_sublabel.c | 4 ++ menu/menu_displaylist.c | 5 ++ menu/menu_setting.c | 17 +++++++ msg_hash.h | 1 + retroarch.c | 89 +++++++++++++++++++++++++++++++++++- 13 files changed, 146 insertions(+), 4 deletions(-) diff --git a/config.def.h b/config.def.h index a94fb5bb88..0f57cec9b1 100644 --- a/config.def.h +++ b/config.def.h @@ -344,6 +344,7 @@ */ #define DEFAULT_FRAME_DELAY 0 #define MAXIMUM_FRAME_DELAY 19 +#define DEFAULT_FRAME_DELAY_AUTO false /* Inserts black frame(s) inbetween frames. * Useful for Higher Hz monitors (set to multiples of 60 Hz) who want to play 60 Hz diff --git a/configuration.c b/configuration.c index 59eee6e35e..d0ff149599 100644 --- a/configuration.c +++ b/configuration.c @@ -1667,6 +1667,7 @@ static struct config_bool_setting *populate_settings_bool( SETTING_BOOL("video_smooth", &settings->bools.video_smooth, true, DEFAULT_VIDEO_SMOOTH, false); SETTING_BOOL("video_ctx_scaling", &settings->bools.video_ctx_scaling, true, DEFAULT_VIDEO_CTX_SCALING, false); SETTING_BOOL("video_force_aspect", &settings->bools.video_force_aspect, true, DEFAULT_FORCE_ASPECT, false); + SETTING_BOOL("video_frame_delay_auto", &settings->bools.video_frame_delay_auto, true, DEFAULT_FRAME_DELAY_AUTO, false); #if defined(DINGUX) SETTING_BOOL("video_dingux_ipu_keep_aspect", &settings->bools.video_dingux_ipu_keep_aspect, true, DEFAULT_DINGUX_IPU_KEEP_ASPECT, false); #endif diff --git a/configuration.h b/configuration.h index e59711d5d2..898f2af8f2 100644 --- a/configuration.h +++ b/configuration.h @@ -534,6 +534,7 @@ typedef struct settings bool video_smooth; bool video_ctx_scaling; bool video_force_aspect; + bool video_frame_delay_auto; bool video_crop_overscan; bool video_aspect_ratio_auto; bool video_dingux_ipu_keep_aspect; diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 8bebf5ed1c..0af1b7f6f5 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -3838,12 +3838,14 @@ void video_driver_frame(const void *data, unsigned width, snprintf(video_info.stat_text, sizeof(video_info.stat_text), "Video Statistics:\n -Frame rate: %6.2f fps\n -Frame time: %6.2f ms\n -Frame time deviation: %.3f %%\n" - " -Frame count: %" PRIu64"\n -Viewport: %d x %d x %3.2f\n" + " -Frame delay (target/effective): %u/%u ms\n -Frame count: %" PRIu64"\n -Viewport: %d x %d x %3.2f\n" "Audio Statistics:\n -Average buffer saturation: %.2f %%\n -Standard deviation: %.2f %%\n -Time spent close to underrun: %.2f %%\n -Time spent close to blocking: %.2f %%\n -Sample count: %d\n" "Core Geometry:\n -Size: %u x %u\n -Max Size: %u x %u\n -Aspect: %3.2f\nCore Timing:\n -FPS: %3.2f\n -Sample Rate: %6.2f\n", last_fps, frame_time / 1000.0f, 100.0f * stddev, + video_st->frame_delay_target, + video_st->frame_delay_effective, video_st->frame_count, video_info.width, video_info.height, diff --git a/gfx/video_driver.h b/gfx/video_driver.h index e0a7cb0526..4466229e01 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -869,6 +869,8 @@ typedef struct unsigned state_scale; unsigned state_out_bpp; #endif + unsigned frame_delay_target; + unsigned frame_delay_effective; unsigned frame_cache_width; unsigned frame_cache_height; unsigned width; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index ff6d1db8a9..cbc10e1298 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3280,6 +3280,10 @@ MSG_HASH( MENU_ENUM_LABEL_VIDEO_FRAME_DELAY, "video_frame_delay" ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO, + "video_frame_delay_auto" + ) MSG_HASH( MENU_ENUM_LABEL_VIDEO_SHADER_DELAY, "video_shader_delay" diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 79a864a363..75d5483731 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -26,6 +26,7 @@ #ifdef RARCH_INTERNAL #include "../configuration.h" +#include "../config.def.h" int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) { @@ -1543,7 +1544,17 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) "Can reduce latency at the cost of\n" "higher risk of stuttering.\n" " \n" - "Maximum is 15."); + "Maximum is %d.", MAXIMUM_FRAME_DELAY); + break; + case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO: + snprintf(s, len, + "Temporarily decreases effective 'Frame Delay'\n" + "until target refresh rate is stable.\n" + " \n" + "Measuring starts from half frame time when\n" + "'Frame Delay' is 0.\n" + " \n" + "E.g. 8 for NTSC and 10 for PAL."); break; case MENU_ENUM_LABEL_VIDEO_SHADER_DELAY: snprintf(s, len, diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 8d421ec032..068de0158f 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1849,6 +1849,14 @@ MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_FRAME_DELAY, "Reduces latency at the cost of a higher risk of video stuttering. Adds a delay after VSync (in ms)." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_FRAME_DELAY_AUTO, + "Automatic Frame Delay" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_FRAME_DELAY_AUTO, + "Decrease effective 'Frame Delay' temporarily to prevent future frame drops. Starting point is half frame time when 'Frame Delay' is 0." + ) MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_HARD_SYNC, "Hard GPU Sync" diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 502c657164..b469d96fe7 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -312,6 +312,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_materialui_thumbnail_background_enab #endif DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_add_content_list, MENU_ENUM_SUBLABEL_ADD_CONTENT_LIST) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_frame_delay, MENU_ENUM_SUBLABEL_VIDEO_FRAME_DELAY) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_frame_delay_auto, MENU_ENUM_SUBLABEL_VIDEO_FRAME_DELAY_AUTO) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_shader_delay, MENU_ENUM_SUBLABEL_VIDEO_SHADER_DELAY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_black_frame_insertion, MENU_ENUM_SUBLABEL_VIDEO_BLACK_FRAME_INSERTION) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_systeminfo_cpu_cores, MENU_ENUM_SUBLABEL_CPU_CORES) @@ -3822,6 +3823,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_frame_delay); break; + case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_frame_delay_auto); + break; case MENU_ENUM_LABEL_VIDEO_SHADER_DELAY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_shader_delay); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 028330d5b7..d6205ab696 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8006,6 +8006,10 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_VIDEO_FRAME_DELAY, PARSE_ONLY_UINT, false) == 0) count++; + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO, + PARSE_ONLY_BOOL, false) == 0) + count++; } if (video_driver_test_all_flags(GFX_CTX_FLAGS_HARD_SYNC)) @@ -8425,6 +8429,7 @@ unsigned menu_displaylist_build_list( bool video_hard_sync = settings->bools.video_hard_sync; menu_displaylist_build_info_selective_t build_list[] = { {MENU_ENUM_LABEL_VIDEO_FRAME_DELAY, PARSE_ONLY_UINT, true }, + {MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO, PARSE_ONLY_BOOL, true }, {MENU_ENUM_LABEL_AUDIO_LATENCY, PARSE_ONLY_UINT, true }, {MENU_ENUM_LABEL_INPUT_POLL_TYPE_BEHAVIOR, PARSE_ONLY_UINT, true }, {MENU_ENUM_LABEL_INPUT_BLOCK_TIMEOUT, PARSE_ONLY_UINT, true }, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 6432af54ac..880ff10690 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -12178,6 +12178,23 @@ static bool setting_append_list( menu_settings_list_current_add_range(list, list_info, 0, MAXIMUM_FRAME_DELAY, 1, true, true); SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_LAKKA_ADVANCED); + CONFIG_BOOL( + list, list_info, + &settings->bools.video_frame_delay_auto, + MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO, + MENU_ENUM_LABEL_VALUE_VIDEO_FRAME_DELAY_AUTO, + DEFAULT_FRAME_DELAY_AUTO, + 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 + ); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_LAKKA_ADVANCED); + /* Unlike all other shader-related menu entries * (which appear in the shaders quick menu, and * are thus hidden automatically on platforms diff --git a/msg_hash.h b/msg_hash.h index 848a1e0d9e..ea33750180 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1049,6 +1049,7 @@ enum msg_hash_enums MENU_LABEL(VIDEO_GPU_SCREENSHOT), MENU_LABEL(VIDEO_BLACK_FRAME_INSERTION), MENU_LABEL(VIDEO_FRAME_DELAY), + MENU_LABEL(VIDEO_FRAME_DELAY_AUTO), MENU_LABEL(VIDEO_SHADER_DELAY), MENU_LABEL(VIDEO_VSYNC), MENU_LABEL(VIDEO_ADAPTIVE_VSYNC), diff --git a/retroarch.c b/retroarch.c index f14806f1f7..bc2bfe64da 100644 --- a/retroarch.c +++ b/retroarch.c @@ -4035,6 +4035,10 @@ static void command_event_deinit_core( if (video_st->video_refresh_rate_original) video_display_server_restore_refresh_rate(); + /* Recalibrate frame delay target */ + if (settings->bools.video_frame_delay_auto) + video_st->frame_delay_target = 0; + if (reinit) driver_uninit(p_rarch, DRIVERS_CMD_ALL); @@ -9132,6 +9136,10 @@ bool runloop_environment_cb(unsigned cmd, void *data) if (video_fullscreen) video_driver_hide_mouse(); + /* Recalibrate frame delay target */ + if (settings->bools.video_frame_delay_auto) + video_st->frame_delay_target = 0; + return true; } return false; @@ -16708,6 +16716,7 @@ int runloop_iterate(void) settings_t *settings = config_get_ptr(); runloop_state_t *runloop_st = &runloop_state; unsigned video_frame_delay = settings->uints.video_frame_delay; + unsigned video_frame_delay_effective = video_st->frame_delay_effective; bool vrr_runloop_enable = settings->bools.vrr_runloop_enable; unsigned max_users = settings->uints.input_max_users; retro_time_t current_time = cpu_features_get_time_usec(); @@ -16931,8 +16940,84 @@ int runloop_iterate(void) } } - if ((video_frame_delay > 0) && input_st && !input_st->nonblocking_flag) - retro_sleep(video_frame_delay); + if (input_st && !input_st->nonblocking_flag) + { + if (settings->bools.video_frame_delay_auto) + { + float refresh_rate = settings->floats.video_refresh_rate; + 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 == 0; + + /* Set target moderately as half frame time with 0 delay */ + if (video_frame_delay == 0) + video_frame_delay = 1 / refresh_rate * 1000 / 2; + + if (video_st->frame_delay_target != video_frame_delay) + { + video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay; + RARCH_LOG("[Video]: Frame delay reset to %d.\n", video_frame_delay); + } + + 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)); + + /* Calculate average frame time to balance spikes */ + for (i = 1; i < frame_time_frames + 1; i++) + { + unsigned 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; + 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); + } + } + } + else + video_st->frame_delay_target = video_frame_delay_effective = video_frame_delay; + + video_st->frame_delay_effective = video_frame_delay_effective; + + if (video_frame_delay_effective > 0) + retro_sleep(video_frame_delay_effective); + } { #ifdef HAVE_RUNAHEAD