diff --git a/config.def.h b/config.def.h index d6cdfe0e8c..d819d3a8f4 100644 --- a/config.def.h +++ b/config.def.h @@ -335,8 +335,13 @@ /* Video VSYNC (recommended) */ #define DEFAULT_VSYNC true +/* Vulkan specific */ #define DEFAULT_MAX_SWAPCHAIN_IMAGES 3 +/* D3D1x specific */ +#define DEFAULT_MAX_FRAME_LATENCY 1 + +/* GL specific */ #define DEFAULT_ADAPTIVE_VSYNC false /* Attempts to hard-synchronize CPU and GPU. diff --git a/configuration.c b/configuration.c index a37e25c6de..a979f4a3fa 100644 --- a/configuration.c +++ b/configuration.c @@ -2250,6 +2250,7 @@ static struct config_uint_setting *populate_settings_uint( SETTING_UINT("video_hard_sync_frames", &settings->uints.video_hard_sync_frames, true, DEFAULT_HARD_SYNC_FRAMES, false); SETTING_UINT("video_frame_delay", &settings->uints.video_frame_delay, true, DEFAULT_FRAME_DELAY, false); SETTING_UINT("video_max_swapchain_images", &settings->uints.video_max_swapchain_images, true, DEFAULT_MAX_SWAPCHAIN_IMAGES, false); + SETTING_UINT("video_max_frame_latency", &settings->uints.video_max_frame_latency, true, DEFAULT_MAX_FRAME_LATENCY, false); SETTING_UINT("video_swap_interval", &settings->uints.video_swap_interval, true, DEFAULT_SWAP_INTERVAL, false); SETTING_UINT("video_rotation", &settings->uints.video_rotation, true, ORIENTATION_NORMAL, false); SETTING_UINT("screen_orientation", &settings->uints.screen_orientation, true, ORIENTATION_NORMAL, false); diff --git a/configuration.h b/configuration.h index b9cb660303..b36b91867c 100644 --- a/configuration.h +++ b/configuration.h @@ -203,6 +203,7 @@ typedef struct settings unsigned video_fullscreen_x; unsigned video_fullscreen_y; unsigned video_max_swapchain_images; + unsigned video_max_frame_latency; unsigned video_swap_interval; unsigned video_hard_sync_frames; unsigned video_frame_delay; diff --git a/gfx/common/d3d11_common.h b/gfx/common/d3d11_common.h index 16db6ca17d..b07abf33ee 100644 --- a/gfx/common/d3d11_common.h +++ b/gfx/common/d3d11_common.h @@ -172,6 +172,7 @@ typedef struct d3d11_shader_t typedef struct { unsigned cur_mon_id; + HANDLE frameLatencyWaitableObject; DXGISwapChain swapChain; D3D11Device device; D3D_FEATURE_LEVEL supportedFeatureLevel; diff --git a/gfx/common/d3d12_common.h b/gfx/common/d3d12_common.h index bc2a8b18ef..3fb7da1d88 100644 --- a/gfx/common/d3d12_common.h +++ b/gfx/common/d3d12_common.h @@ -171,6 +171,7 @@ typedef struct struct { + HANDLE frameLatencyWaitableObject; DXGISwapChain handle; D3D12Resource renderTargets[2]; #ifdef HAVE_DXGI_HDR diff --git a/gfx/drivers/d3d11.c b/gfx/drivers/d3d11.c index 3bb72345b1..2e503feed1 100644 --- a/gfx/drivers/d3d11.c +++ b/gfx/drivers/d3d11.c @@ -82,6 +82,15 @@ static D3D11Device cached_device_d3d11; static D3D_FEATURE_LEVEL cached_supportedFeatureLevel; static D3D11DeviceContext cached_context_d3d11; +/* Waitable swap chain */ +static void WaitOnSwapChain(HANDLE frameLatencyWaitableObject) +{ + DWORD result = WaitForSingleObjectEx( + frameLatencyWaitableObject, + 1000, + true); +} + static INLINE void d3d11_release_shader(d3d11_shader_t* shader) { Release(shader->layout); @@ -94,6 +103,7 @@ static uint32_t d3d11_get_flags(void *data) { uint32_t flags = 0; + BIT32_SET(flags, GFX_CTX_FLAGS_CUSTOMIZABLE_FRAME_LATENCY); BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING); BIT32_SET(flags, GFX_CTX_FLAGS_OVERLAY_BEHIND_MENU_SUPPORTED); #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) @@ -1034,34 +1044,34 @@ static bool d3d11_init_swapchain(d3d11_video_t* d3d11, switch (d3d11->supportedFeatureLevel) { case D3D_FEATURE_LEVEL_9_1: - RARCH_LOG("[D3D11] Device created (Feature Level: 9.1)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 9.1)\n"); break; case D3D_FEATURE_LEVEL_9_2: - RARCH_LOG("[D3D11] Device created (Feature Level: 9.2)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 9.2)\n"); break; case D3D_FEATURE_LEVEL_9_3: - RARCH_LOG("[D3D11] Device created (Feature Level: 9.3)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 9.3)\n"); break; case D3D_FEATURE_LEVEL_10_0: - RARCH_LOG("[D3D11] Device created (Feature Level: 10.0)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 10.0)\n"); break; case D3D_FEATURE_LEVEL_10_1: - RARCH_LOG("[D3D11] Device created (Feature Level: 10.1)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 10.1)\n"); break; case D3D_FEATURE_LEVEL_11_0: - RARCH_LOG("[D3D11] Device created (Feature Level: 11.0)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 11.0)\n"); break; case D3D_FEATURE_LEVEL_11_1: - RARCH_LOG("[D3D11] Device created (Feature Level: 11.1)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 11.1)\n"); break; case D3D_FEATURE_LEVEL_12_0: - RARCH_LOG("[D3D11] Device created (Feature Level: 12.0)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 12.0)\n"); break; case D3D_FEATURE_LEVEL_12_1: - RARCH_LOG("[D3D11] Device created (Feature Level: 12.1)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: 12.1)\n"); break; default: - RARCH_LOG("[D3D11] Device created (Feature Level: N/A)\n"); + RARCH_LOG("[D3D11]: Device created (Feature Level: N/A)\n"); break; } } @@ -1080,6 +1090,7 @@ static bool d3d11_init_swapchain(d3d11_video_t* d3d11, d3d11->has_flip_model = true; d3d11->has_allow_tearing = true; desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; #endif @@ -1090,6 +1101,7 @@ static bool d3d11_init_swapchain(d3d11_video_t* d3d11, &desc, NULL, (IDXGISwapChain1**)&d3d11->swapChain))) return false; #else + desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; adapter->lpVtbl->GetParent( @@ -1154,6 +1166,17 @@ static bool d3d11_init_swapchain(d3d11_video_t* d3d11, #endif /* __WINRT__ */ + if ((d3d11->frameLatencyWaitableObject = DXGIGetFrameLatencyWaitableObject(d3d11->swapChain))) + { + settings_t* settings = config_get_ptr(); + UINT max_latency = settings->uints.video_max_frame_latency; + UINT cur_latency = 0; + + DXGISetMaximumFrameLatency(d3d11->swapChain, max_latency); + DXGIGetMaximumFrameLatency(d3d11->swapChain, &cur_latency); + RARCH_LOG("[D3D11]: Requesting %u maximum frame latency, using %u.\n", max_latency, cur_latency); + } + #ifdef HAVE_DXGI_HDR /* Check display HDR support and initialize ST.2084 support to match @@ -1917,6 +1940,8 @@ static bool d3d11_gfx_frame( { UINT swapchain_flags = d3d11->has_allow_tearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0; + swapchain_flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + #ifdef HAVE_DXGI_HDR d3d11->hdr.enable = video_hdr_enable; @@ -1992,6 +2017,8 @@ static bool d3d11_gfx_frame( d3d11->hdr.max_fall); #endif } + else + WaitOnSwapChain(d3d11->frameLatencyWaitableObject); { D3D11Texture2D back_buffer; diff --git a/gfx/drivers/d3d12.c b/gfx/drivers/d3d12.c index ecba95d43d..8e060d8a61 100644 --- a/gfx/drivers/d3d12.c +++ b/gfx/drivers/d3d12.c @@ -108,6 +108,15 @@ static void d3d12_gfx_sync(d3d12_video_t* d3d12) } } +/* Waitable swap chain */ +static void WaitOnSwapChain(HANDLE frameLatencyWaitableObject) +{ + DWORD result = WaitForSingleObjectEx( + frameLatencyWaitableObject, + 1000, + true); +} + #ifdef HAVE_OVERLAY static void d3d12_free_overlays(d3d12_video_t* d3d12) { @@ -1262,7 +1271,8 @@ static bool d3d12_init_swapchain(d3d12_video_t* d3d12, #else desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; #endif - desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + desc.Flags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; #ifdef __WINRT__ hr = DXGICreateSwapChainForCoreWindow(d3d12->factory, d3d12->queue.handle, corewindow, &desc, NULL, &d3d12->chain.handle); @@ -1275,6 +1285,17 @@ static bool d3d12_init_swapchain(d3d12_video_t* d3d12, return false; } + if ((d3d12->chain.frameLatencyWaitableObject = DXGIGetFrameLatencyWaitableObject(d3d12->chain.handle))) + { + settings_t* settings = config_get_ptr(); + UINT max_latency = settings->uints.video_max_frame_latency; + UINT cur_latency = 0; + + DXGISetMaximumFrameLatency(d3d12->chain.handle, max_latency); + DXGIGetMaximumFrameLatency(d3d12->chain.handle, &cur_latency); + RARCH_LOG("[D3D12]: Requesting %u maximum frame latency, using %u.\n", max_latency, cur_latency); + } + #ifdef HAVE_WINDOW DXGIMakeWindowAssociation(d3d12->factory, hwnd, DXGI_MWA_NO_ALT_ENTER); #endif @@ -2039,6 +2060,11 @@ static bool d3d12_gfx_frame( bool video_hdr_enable = video_info->hdr_enable; DXGI_FORMAT back_buffer_format = d3d12->shader_preset && d3d12->shader_preset->passes ? glslang_format_to_dxgi(d3d12->pass[d3d12->shader_preset->passes - 1].semantics.format) : DXGI_FORMAT_R8G8B8A8_UNORM; bool use_back_buffer = back_buffer_format != d3d12->chain.formats[d3d12->chain.bit_depth]; +#endif + + d3d12_gfx_sync(d3d12); + +#ifdef HAVE_DXGI_HDR if (d3d12->resize_chain || (d3d12->hdr.enable != video_hdr_enable)) #else if (d3d12->resize_chain) @@ -2147,6 +2173,8 @@ static bool d3d12_gfx_frame( d3d12->hdr.max_fall); #endif } + else + WaitOnSwapChain(d3d12->chain.frameLatencyWaitableObject); D3D12ResetCommandAllocator(d3d12->queue.allocator); @@ -2646,9 +2674,6 @@ static bool d3d12_gfx_frame( #endif DXGIPresent(d3d12->chain.handle, sync_interval, present_flags); - /* Sync after Present for minimal delay */ - d3d12_gfx_sync(d3d12); - return true; } @@ -2857,6 +2882,7 @@ static uint32_t d3d12_get_flags(void *data) { uint32_t flags = 0; + BIT32_SET(flags, GFX_CTX_FLAGS_CUSTOMIZABLE_FRAME_LATENCY); BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING); BIT32_SET(flags, GFX_CTX_FLAGS_OVERLAY_BEHIND_MENU_SUPPORTED); #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) diff --git a/gfx/video_defines.h b/gfx/video_defines.h index f1ce8e187c..ccea92e98a 100644 --- a/gfx/video_defines.h +++ b/gfx/video_defines.h @@ -208,6 +208,7 @@ enum display_flags GFX_CTX_FLAGS_GL_CORE_CONTEXT, GFX_CTX_FLAGS_MULTISAMPLING, GFX_CTX_FLAGS_CUSTOMIZABLE_SWAPCHAIN_IMAGES, + GFX_CTX_FLAGS_CUSTOMIZABLE_FRAME_LATENCY, GFX_CTX_FLAGS_HARD_SYNC, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION, GFX_CTX_FLAGS_MENU_FRAME_FILTERING, diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index f725efa511..4b9ef52369 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -3586,6 +3586,10 @@ MSG_HASH( MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES, "video_max_swapchain_images" ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, + "video_max_frame_latency" + ) MSG_HASH( MENU_ENUM_LABEL_VIDEO_MESSAGE_POS_X, "video_message_pos_x" diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index 428e0a0d23..21df6219e1 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -10893,12 +10893,20 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_MAX_SWAPCHAIN_IMAGES, - "Max swapchain images" + "Max Swapchain Images" ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_MAX_SWAPCHAIN_IMAGES, "Tells the video driver to explicitly use a specified buffering mode." ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_MAX_FRAME_LATENCY, + "Max Frame Latency" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_MAX_FRAME_LATENCY, + "Tells the video driver to explicitly use a specified buffering mode." + ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_SHADER_PRESET_PARAMETERS, "Modifies the shader preset itself currently used in the menu." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index c7c4640a28..e8da9a383f 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -353,10 +353,11 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_localap_enable, MENU_ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_timezone, MENU_ENUM_SUBLABEL_TIMEZONE) #endif DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_user_language, MENU_ENUM_SUBLABEL_USER_LANGUAGE) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_max_swapchain_images, MENU_ENUM_SUBLABEL_VIDEO_MAX_SWAPCHAIN_IMAGES ) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_max_swapchain_images, MENU_ENUM_SUBLABEL_VIDEO_MAX_SWAPCHAIN_IMAGES) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_max_frame_latency, MENU_ENUM_SUBLABEL_VIDEO_MAX_FRAME_LATENCY) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_online_updater, MENU_ENUM_SUBLABEL_ONLINE_UPDATER) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_fps_show, MENU_ENUM_SUBLABEL_FPS_SHOW) -DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_fps_update_interval, MENU_ENUM_SUBLABEL_FPS_UPDATE_INTERVAL) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_fps_update_interval, MENU_ENUM_SUBLABEL_FPS_UPDATE_INTERVAL) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_framecount_show, MENU_ENUM_SUBLABEL_FRAMECOUNT_SHOW) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_memory_show, MENU_ENUM_SUBLABEL_MEMORY_SHOW) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_memory_update_interval, MENU_ENUM_SUBLABEL_MEMORY_UPDATE_INTERVAL) @@ -4231,6 +4232,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_max_swapchain_images); break; + case MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_max_frame_latency); + break; case MENU_ENUM_LABEL_NETPLAY_PING_SHOW: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_netplay_ping_show); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 73c54339a1..1dae7f3231 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -8617,6 +8617,14 @@ unsigned menu_displaylist_build_list( count++; } + if (video_driver_test_all_flags(GFX_CTX_FLAGS_CUSTOMIZABLE_FRAME_LATENCY)) + { + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, + PARSE_ONLY_UINT, false) == 0) + count++; + } + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE, PARSE_ONLY_BOOL, false) == 0) @@ -9055,6 +9063,14 @@ unsigned menu_displaylist_build_list( count++; } + if (video_driver_test_all_flags(GFX_CTX_FLAGS_CUSTOMIZABLE_FRAME_LATENCY)) + { + MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, + PARSE_ONLY_UINT, false); + count++; + } + if (video_driver_test_all_flags(GFX_CTX_FLAGS_HARD_SYNC)) { MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 91c15d211d..202016bf1e 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -12507,6 +12507,23 @@ static bool setting_append_list( SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_CMD_APPLY_AUTO); MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT); + CONFIG_UINT( + list, list_info, + &settings->uints.video_max_frame_latency, + MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY, + MENU_ENUM_LABEL_VALUE_VIDEO_MAX_FRAME_LATENCY, + DEFAULT_MAX_FRAME_LATENCY, + &group_info, + &subgroup_info, + parent_group, + 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_range(list, list_info, (*list)[list_info->index - 1].offset_by, 3, 1, true, true); + SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_CMD_APPLY_AUTO); + MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT); + CONFIG_BOOL( list, list_info, &settings->bools.video_hard_sync, diff --git a/msg_hash.h b/msg_hash.h index bdb01c1d43..462fffdab1 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1097,6 +1097,7 @@ enum msg_hash_enums MENU_LABEL(VIDEO_FILTER_FLICKER), MENU_LABEL(VIDEO_SOFT_FILTER), MENU_LABEL(VIDEO_MAX_SWAPCHAIN_IMAGES), + MENU_LABEL(VIDEO_MAX_FRAME_LATENCY), MENU_LABEL(VIDEO_GPU_SCREENSHOT), MENU_LABEL(VIDEO_BLACK_FRAME_INSERTION), MENU_LABEL(VIDEO_FRAME_DELAY), diff --git a/ui/drivers/qt/qt_options.cpp b/ui/drivers/qt/qt_options.cpp index 763db7acdb..c19c1240cb 100644 --- a/ui/drivers/qt/qt_options.cpp +++ b/ui/drivers/qt/qt_options.cpp @@ -354,6 +354,7 @@ QWidget *LatencyPage::widget() } layout->add(MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES); + layout->add(MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY); layout->add(MENU_ENUM_LABEL_VIDEO_FRAME_DELAY); layout->add(MENU_ENUM_LABEL_AUDIO_LATENCY); @@ -1340,6 +1341,7 @@ QWidget *VideoPage::widget() } syncGroup->add(MENU_ENUM_LABEL_VIDEO_MAX_SWAPCHAIN_IMAGES); + syncGroup->add(MENU_ENUM_LABEL_VIDEO_MAX_FRAME_LATENCY); syncGroup->add(MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE); miscGroup->add(MENU_ENUM_LABEL_SUSPEND_SCREENSAVER_ENABLE);