diff --git a/audio/audio_driver.c b/audio/audio_driver.c index 2f0fa691de..ff28e63678 100644 --- a/audio/audio_driver.c +++ b/audio/audio_driver.c @@ -954,10 +954,10 @@ float audio_driver_monitor_adjust_system_rates( unsigned video_swap_interval, float audio_max_timing_skew) { - float inp_sample_rate = input_sample_rate; - const float target_video_sync_rate = video_refresh_rate - / video_swap_interval; - float timing_skew = + float inp_sample_rate = input_sample_rate; + float target_video_sync_rate = video_refresh_rate + / (float)video_swap_interval; + float timing_skew = fabs(1.0f - input_fps / target_video_sync_rate); if (timing_skew <= audio_max_timing_skew) return (inp_sample_rate * target_video_sync_rate / input_fps); diff --git a/command.c b/command.c index 6a70196ed1..a3d5dd3663 100644 --- a/command.c +++ b/command.c @@ -1740,7 +1740,7 @@ void command_event_reinit(const int flags) struct menu_state *menu_st = menu_state_get_ptr(); bool video_fullscreen = settings->bools.video_fullscreen; bool adaptive_vsync = settings->bools.video_adaptive_vsync; - unsigned swap_interval = settings->uints.video_swap_interval; + unsigned swap_interval_config = settings->uints.video_swap_interval; #endif enum input_game_focus_cmd_type game_focus_cmd = GAME_FOCUS_CMD_REAPPLY; @@ -1775,6 +1775,6 @@ void command_event_reinit(const int flags) video_st->data, false, video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && adaptive_vsync, - swap_interval); + runloop_get_video_swap_interval(swap_interval_config)); #endif } diff --git a/configuration.c b/configuration.c index 88d3b987d5..8674953ea0 100644 --- a/configuration.c +++ b/configuration.c @@ -3520,7 +3520,7 @@ static bool config_load_file(global_t *global, if (settings->uints.video_frame_delay > MAXIMUM_FRAME_DELAY) settings->uints.video_frame_delay = MAXIMUM_FRAME_DELAY; - settings->uints.video_swap_interval = MAX(settings->uints.video_swap_interval, 1); + settings->uints.video_swap_interval = MAX(settings->uints.video_swap_interval, 0); settings->uints.video_swap_interval = MIN(settings->uints.video_swap_interval, 4); audio_set_float(AUDIO_ACTION_VOLUME_GAIN, settings->floats.audio_volume); diff --git a/driver.c b/driver.c index 08dfb21cdf..83eeaee500 100644 --- a/driver.c +++ b/driver.c @@ -308,6 +308,18 @@ static void driver_adjust_system_rates( double input_sample_rate = info->sample_rate; double input_fps = info->fps; + /* Update video swap interval if automatic + * switching is enabled */ + runloop_set_video_swap_interval( + vrr_runloop_enable, + video_st->crt_switching_active, + video_swap_interval, + audio_max_timing_skew, + video_refresh_rate, + input_fps); + video_swap_interval = runloop_get_video_swap_interval( + video_swap_interval); + if (input_sample_rate > 0.0) { audio_driver_state_t *audio_st = audio_state_get_ptr(); @@ -341,6 +353,7 @@ static void driver_adjust_system_rates( video_refresh_rate, vrr_runloop_enable, audio_max_timing_skew, + video_swap_interval, input_fps)) { /* We won't be able to do VSync reliably @@ -389,7 +402,8 @@ void driver_set_nonblock_state(void) bool audio_sync = settings->bools.audio_sync; bool video_vsync = settings->bools.video_vsync; bool adaptive_vsync = settings->bools.video_adaptive_vsync; - unsigned swap_interval = settings->uints.video_swap_interval; + unsigned swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); bool video_driver_active = video_st->active; bool audio_driver_active = audio_st->active; bool runloop_force_nonblock = runloop_st->force_nonblock; diff --git a/gfx/common/d3d9_common.c b/gfx/common/d3d9_common.c index 445d7ea1d3..9c7a782931 100644 --- a/gfx/common/d3d9_common.c +++ b/gfx/common/d3d9_common.c @@ -702,7 +702,8 @@ void d3d9_make_d3dpp(d3d9_video_t *d3d, if (info->vsync) { settings_t *settings = config_get_ptr(); - unsigned video_swap_interval = settings->uints.video_swap_interval; + unsigned video_swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); switch (video_swap_interval) { diff --git a/gfx/drivers/d3d8.c b/gfx/drivers/d3d8.c index e5106bcb63..567bf1d703 100644 --- a/gfx/drivers/d3d8.c +++ b/gfx/drivers/d3d8.c @@ -584,7 +584,8 @@ static void d3d8_make_d3dpp(void *data, if (info->vsync) { settings_t *settings = config_get_ptr(); - unsigned video_swap_interval = settings->uints.video_swap_interval; + unsigned video_swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); switch (video_swap_interval) { diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 18b75efd39..25f5cc5106 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -894,12 +894,16 @@ bool video_driver_monitor_adjust_system_rates( float video_refresh_rate, bool vrr_runloop_enable, float audio_max_timing_skew, + unsigned video_swap_interval, double input_fps) { + float target_video_sync_rate = timing_skew_hz + / (float)video_swap_interval; + if (!vrr_runloop_enable) { float timing_skew = fabs( - 1.0f - input_fps / timing_skew_hz); + 1.0f - input_fps / target_video_sync_rate); /* We don't want to adjust pitch too much. If we have extreme cases, * just don't readjust at all. */ if (timing_skew <= audio_max_timing_skew) @@ -909,7 +913,7 @@ bool video_driver_monitor_adjust_system_rates( video_refresh_rate, (float)input_fps); } - return input_fps <= timing_skew_hz; + return input_fps <= target_video_sync_rate; } void video_driver_reset_custom_viewport(settings_t *settings) @@ -1166,7 +1170,8 @@ void video_switch_refresh_rate_maybe( float refresh_rate = *refresh_rate_suggest; float video_refresh_rate = settings->floats.video_refresh_rate; unsigned crt_switch_resolution = settings->uints.crt_switch_resolution; - unsigned video_swap_interval = settings->uints.video_swap_interval; + unsigned video_swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); unsigned video_bfi = settings->uints.video_black_frame_insertion; bool video_fullscreen = settings->bools.video_fullscreen; bool video_windowed_full = settings->bools.video_windowed_fullscreen; @@ -3432,7 +3437,8 @@ bool video_driver_init_internal(bool *video_is_threaded, bool verbosity_enabled) !runloop_st->force_nonblock; video.force_aspect = settings->bools.video_force_aspect; video.font_enable = settings->bools.video_font_enable; - video.swap_interval = settings->uints.video_swap_interval; + video.swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); video.adaptive_vsync = settings->bools.video_adaptive_vsync; #ifdef GEKKO video.viwidth = settings->uints.video_viwidth; diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 18ca168362..21103026e7 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -1177,6 +1177,7 @@ bool video_driver_monitor_adjust_system_rates( float video_refresh_rate, bool vrr_runloop_enable, float audio_max_timing_skew, + unsigned video_swap_interval, double input_fps); void crt_switch_driver_refresh(void); diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 6d14163329..4c1e261baf 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1998,7 +1998,11 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SWAP_INTERVAL, - "Use a custom swap interval for VSync. Set this to effectively halve monitor refresh rate." + "Use a custom swap interval for VSync. Effectively reduces monitor refresh rate by the specified factor. 'Auto' sets factor based on core-reported frame rate, providing improved frame pacing when running e.g. 30 fps content on a 60 Hz display or 60 fps content on a 120 Hz display." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_SWAP_INTERVAL_AUTO, + "Auto" ) MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_ADAPTIVE_VSYNC, diff --git a/menu/menu_driver.c b/menu/menu_driver.c index 6e1fa15ddd..720c4a28e3 100644 --- a/menu/menu_driver.c +++ b/menu/menu_driver.c @@ -6726,7 +6726,6 @@ void menu_driver_toggle( bool input_overlay_enable = false; #endif bool video_adaptive_vsync = false; - bool video_swap_interval = false; if (settings) { @@ -6744,7 +6743,6 @@ void menu_driver_toggle( input_overlay_enable = settings->bools.input_overlay_enable; #endif video_adaptive_vsync = settings->bools.video_adaptive_vsync; - video_swap_interval = settings->uints.video_swap_interval; } if (on) @@ -6796,15 +6794,18 @@ void menu_driver_toggle( menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh); - /* Menu should always run with vsync on. */ + /* Menu should always run with vsync on and + * a video swap interval of 1 */ if (current_video->set_nonblock_state) + { current_video->set_nonblock_state( video_driver_data, false, video_driver_test_all_flags(GFX_CTX_FLAGS_ADAPTIVE_VSYNC) && video_adaptive_vsync, - video_swap_interval + 1 ); + } /* Stop all rumbling before entering the menu. */ command_event(CMD_EVENT_RUMBLE_STOP, NULL); diff --git a/menu/menu_setting.c b/menu/menu_setting.c index e3354cf856..6b77466a1a 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -6319,6 +6319,18 @@ static void setting_get_string_representation_uint_cheat_browse_address( } #endif +static void setting_get_string_representation_video_swap_interval(rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + if (*setting->value.target.unsigned_integer == 0) + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_SWAP_INTERVAL_AUTO), len); + else + snprintf(s, len, "%u", *setting->value.target.unsigned_integer); +} + static void setting_get_string_representation_uint_video_rotation(rarch_setting_t *setting, char *s, size_t len) { @@ -12430,9 +12442,10 @@ static bool setting_append_list( general_write_handler, general_read_handler); (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - (*list)[list_info->index - 1].offset_by = 1; - MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_VIDEO_SET_BLOCKING_STATE); - menu_settings_list_current_add_range(list, list_info, 1, 4, 1, true, true); + (*list)[list_info->index - 1].get_string_representation = + &setting_get_string_representation_video_swap_interval; + MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT); + menu_settings_list_current_add_range(list, list_info, 0, 4, 1, true, true); SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_CMD_APPLY_AUTO); SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_LAKKA_ADVANCED); diff --git a/msg_hash.h b/msg_hash.h index d3b15f9a88..3315df2378 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1138,6 +1138,8 @@ enum msg_hash_enums MENU_LABEL(VIDEO_THREADED), MENU_LABEL(VIDEO_SWAP_INTERVAL), + MENU_ENUM_LABEL_VALUE_VIDEO_SWAP_INTERVAL_AUTO, + MENU_LABEL(VIDEO_FULLSCREEN), MENU_LABEL(VIDEO_MONITOR_INDEX), MENU_LABEL(VIDEO_WIIU_PREFER_DRC), diff --git a/retroarch.c b/retroarch.c index 0550a6cb54..bcfa74f273 100644 --- a/retroarch.c +++ b/retroarch.c @@ -2405,7 +2405,8 @@ bool command_event(enum event_command cmd, void *data) case CMD_EVENT_VIDEO_SET_BLOCKING_STATE: { bool adaptive_vsync = settings->bools.video_adaptive_vsync; - unsigned swap_interval = settings->uints.video_swap_interval; + unsigned swap_interval = runloop_get_video_swap_interval( + settings->uints.video_swap_interval); video_driver_state_t *video_st = video_state_get_ptr(); @@ -5786,6 +5787,7 @@ bool retroarch_ctl(enum rarch_ctl_state state, void *data) input_game_focus_free(); runloop_fastmotion_override_free(); runloop_core_options_cb_free(); + runloop_st->video_swap_interval_auto = 1; memset(&input_st->analog_requested, 0, sizeof(input_st->analog_requested)); } diff --git a/runloop.c b/runloop.c index 91e6d48cf4..4092f01aa8 100644 --- a/runloop.c +++ b/runloop.c @@ -3589,6 +3589,7 @@ static void uninit_libretro_symbols( input_game_focus_free(); runloop_fastmotion_override_free(); runloop_core_options_cb_free(); + runloop_st->video_swap_interval_auto = 1; camera_st->active = false; location_st->active = false; @@ -5210,6 +5211,76 @@ float runloop_get_fastforward_ratio( return settings->floats.fastforward_ratio; } +void runloop_set_video_swap_interval( + bool vrr_runloop_enable, + bool crt_switching_active, + unsigned swap_interval_config, + float audio_max_timing_skew, + float video_refresh_rate, + double input_fps) +{ + runloop_state_t *runloop_st = &runloop_state; + float core_hz = input_fps; + float timing_hz = crt_switching_active ? + input_fps : video_refresh_rate; + float swap_ratio; + unsigned swap_integer; + float timing_skew; + + /* If automatic swap interval selection is + * disabled, just record user-set value */ + if (swap_interval_config != 0) + { + runloop_st->video_swap_interval_auto = + swap_interval_config; + return; + } + + /* > If VRR is enabled, swap interval is irrelevant, + * just set to 1 + * > If core fps is higher than display refresh rate, + * set swap interval to 1 + * > If core fps or display refresh rate are zero, + * set swap interval to 1 */ + if (vrr_runloop_enable || + (core_hz > timing_hz) || + (core_hz <= 0.0f) || + (timing_hz <= 0.0f)) + { + runloop_st->video_swap_interval_auto = 1; + return; + } + + /* Check whether display refresh rate is an integer + * multiple of core fps (within timing skew tolerance) */ + swap_ratio = timing_hz / core_hz; + swap_integer = (unsigned)(swap_ratio + 0.5f); + + /* > Sanity check: swap interval must be in the + * range [1,4] - if we are outside this, then + * bail... */ + if ((swap_integer < 1) || (swap_integer > 4)) + { + runloop_st->video_swap_interval_auto = 1; + return; + } + + timing_skew = fabs(1.0f - core_hz / (timing_hz / (float)swap_integer)); + + runloop_st->video_swap_interval_auto = + (timing_skew <= audio_max_timing_skew) ? + swap_integer : 1; +} + +unsigned runloop_get_video_swap_interval( + unsigned swap_interval_config) +{ + runloop_state_t *runloop_st = &runloop_state; + return (swap_interval_config == 0) ? + runloop_st->video_swap_interval_auto : + swap_interval_config; +} + unsigned int retroarch_get_rotation(void) { settings_t *settings = config_get_ptr(); @@ -7634,7 +7705,8 @@ 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_swap_interval = runloop_get_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 = diff --git a/runloop.h b/runloop.h index 2beb9b8048..76f57dd782 100644 --- a/runloop.h +++ b/runloop.h @@ -219,6 +219,7 @@ struct runloop unsigned perf_ptr_libretro; unsigned subsystem_current_count; unsigned entry_state_slot; + unsigned video_swap_interval_auto; fastmotion_overrides_t fastmotion_override; /* float alignment */ @@ -415,6 +416,16 @@ float runloop_get_fastforward_ratio( settings_t *settings, struct retro_fastforwarding_override *fastmotion_override); +void runloop_set_video_swap_interval( + bool vrr_runloop_enable, + bool crt_switching_active, + unsigned swap_interval_config, + float audio_max_timing_skew, + float video_refresh_rate, + double input_fps); +unsigned runloop_get_video_swap_interval( + unsigned swap_interval_config); + void runloop_task_msg_queue_push( retro_task_t *task, const char *msg, unsigned prio, unsigned duration,