From 72c901a90eee3014c68479e67afa9df758d32abe Mon Sep 17 00:00:00 2001 From: Ophidon Date: Sat, 20 Jan 2024 02:11:31 -0500 Subject: [PATCH] Squashed commit of the following: (#16142) commit 793d41c303206b43932ddcefd44a45836def55eb Author: Ophidon Date: Fri Jan 19 23:12:31 2024 -0500 Build Fix 2 Move declarations of iterators. commit c0e959b3d3cd773a66a17cfe034f08eaa53d525a Author: Ophidon Date: Fri Jan 19 22:57:01 2024 -0500 Build Fix Help string was 14 characters too long for c89. commit fc5506c7906bf82d6f88b7b0d7e4764d58d90622 Author: Ophidon Date: Fri Jan 19 22:40:45 2024 -0500 BFI Updates Significant BFI updates. - Adds BFI to dx10/11/12 in general. - Updates existing BFI menu option descriptions to be somewhat more clear in how to use correctly. - Adds Variable Strobe length via new 'Dark Frames' bfi sub-choice. Only valid at 180hz and above, as it must work with whole frames. - Algorithm to auto select 'decent' Dark Frames choice, for any given selected BFI refresh rate. Will also avoid defaults that can cause Image Retention at any Hz higher than 120. (Impossible to avoid at 120 if you have an affected screen... get an OLED :D ) . - Some sanity checking on selecting BFI or the other synchronizations options like Swap Interval > 1, that don't play well with BFI. --- config.def.h | 10 ++- configuration.c | 1 + configuration.h | 1 + gfx/common/d3d10_defines.h | 3 +- gfx/common/d3d11_defines.h | 3 +- gfx/common/d3d12_defines.h | 5 +- gfx/common/gl1_defines.h | 3 +- gfx/common/gl2_common.h | 3 +- gfx/common/gl3_defines.h | 3 +- gfx/drivers/d3d10.c | 49 ++++++++++++ gfx/drivers/d3d11.c | 48 ++++++++++++ gfx/drivers/d3d12.c | 88 +++++++++++++++++++++ gfx/drivers/gl1.c | 50 +++++++++--- gfx/drivers/gl2.c | 63 +++++++++++---- gfx/drivers/gl3.c | 64 ++++++++++----- gfx/drivers/metal.m | 1 - gfx/drivers/vulkan.c | 39 ++++++++-- gfx/video_driver.c | 1 + gfx/video_driver.h | 1 + intl/msg_hash_lbl.h | 4 + intl/msg_hash_us.c | 3 + intl/msg_hash_us.h | 80 ++++++++++++++++++- menu/cbs/menu_cbs_sublabel.c | 4 + menu/menu_displaylist.c | 9 +++ menu/menu_setting.c | 147 ++++++++++++++++++++++++++++++++++- msg_hash.h | 18 +++++ 26 files changed, 635 insertions(+), 66 deletions(-) diff --git a/config.def.h b/config.def.h index 73d09d3b6b3..d002b96da14 100644 --- a/config.def.h +++ b/config.def.h @@ -396,11 +396,17 @@ /* Inserts black frame(s) inbetween frames. * Useful for Higher Hz monitors (set to multiples of 60 Hz) who want to play 60 Hz - * material with eliminated ghosting. video_refresh_rate should still be configured - * as if it is a 60 Hz monitor (divide refresh rate by multiple of 60 Hz). + * material with CRT-like motion clarity. */ #define DEFAULT_BLACK_FRAME_INSERTION 0 +/* Black Frame Insertion Dark Frames. + * Increase for more clarity at the cost of lower brightness. Adjusting can also eliminate + * any temporary image retention if noticed. Only useful at 180hz or higher 60hz multiples, + * as 120hz only has one total extra frame for BFI to work with. + */ +#define DEFAULT_BFI_DARK_FRAMES 1 + /* Uses a custom swap interval for VSync. * Set this to effectively halve monitor refresh rate. */ diff --git a/configuration.c b/configuration.c index 14f624300ee..a227ee11b5c 100644 --- a/configuration.c +++ b/configuration.c @@ -2393,6 +2393,7 @@ static struct config_uint_setting *populate_settings_uint( 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_black_frame_insertion", &settings->uints.video_black_frame_insertion, true, DEFAULT_BLACK_FRAME_INSERTION, false); + SETTING_UINT("video_bfi_dark_frames", &settings->uints.video_bfi_dark_frames, true, DEFAULT_BFI_DARK_FRAMES, 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 2a60eff8bae..f6bb9d122b4 100644 --- a/configuration.h +++ b/configuration.h @@ -345,6 +345,7 @@ typedef struct settings unsigned core_updater_auto_backup_history_size; unsigned video_black_frame_insertion; + unsigned video_bfi_dark_frames; unsigned video_autoswitch_refresh_rate; unsigned quit_on_close_content; diff --git a/gfx/common/d3d10_defines.h b/gfx/common/d3d10_defines.h index 5c89021e883..fe6cd6f4614 100644 --- a/gfx/common/d3d10_defines.h +++ b/gfx/common/d3d10_defines.h @@ -42,7 +42,8 @@ enum d3d10_video_flags D3D10_ST_FLAG_OVERLAYS_ENABLE = (1 << 7), D3D10_ST_FLAG_OVERLAYS_FULLSCREEN = (1 << 8), D3D10_ST_FLAG_MENU_ENABLE = (1 << 9), - D3D10_ST_FLAG_MENU_FULLSCREEN = (1 << 10) + D3D10_ST_FLAG_MENU_FULLSCREEN = (1 << 10), + D3D10_ST_FLAG_FRAME_DUPE_LOCK = (1 << 11) }; diff --git a/gfx/common/d3d11_defines.h b/gfx/common/d3d11_defines.h index 94ef6776e75..45c5bd48a1d 100644 --- a/gfx/common/d3d11_defines.h +++ b/gfx/common/d3d11_defines.h @@ -54,7 +54,8 @@ enum d3d11_state_flags D3D11_ST_FLAG_OVERLAYS_ENABLE = (1 << 14), D3D11_ST_FLAG_OVERLAYS_FULLSCREEN = (1 << 15), D3D11_ST_FLAG_MENU_ENABLE = (1 << 16), - D3D11_ST_FLAG_MENU_FULLSCREEN = (1 << 17) + D3D11_ST_FLAG_MENU_FULLSCREEN = (1 << 17), + D3D11_ST_FLAG_FRAME_DUPE_LOCK = (1 << 18) }; enum d3d11_feature_level_hint diff --git a/gfx/common/d3d12_defines.h b/gfx/common/d3d12_defines.h index e383f5b976c..4892f81339a 100644 --- a/gfx/common/d3d12_defines.h +++ b/gfx/common/d3d12_defines.h @@ -62,7 +62,8 @@ enum d3d12_video_flags D3D12_ST_FLAG_VSYNC = (1 << 12), D3D12_ST_FLAG_WAITABLE_SWAPCHAINS = (1 << 13), D3D12_ST_FLAG_WAIT_FOR_VBLANK = (1 << 14), - D3D12_ST_FLAG_HW_IFACE_ENABLE = (1 << 15) + D3D12_ST_FLAG_HW_IFACE_ENABLE = (1 << 15), + D3D12_ST_FLAG_FRAME_DUPE_LOCK = (1 << 16) }; typedef enum @@ -350,7 +351,7 @@ typedef struct #ifdef DEBUG D3D12Debug debugController; #endif - uint16_t flags; + uint32_t flags; } d3d12_video_t; /* end of auto-generated */ diff --git a/gfx/common/gl1_defines.h b/gfx/common/gl1_defines.h index ae27a7e4e7e..51eb1ccf091 100644 --- a/gfx/common/gl1_defines.h +++ b/gfx/common/gl1_defines.h @@ -65,7 +65,8 @@ enum gl1_flags GL1_FLAG_SMOOTH = (1 << 8), GL1_FLAG_MENU_SMOOTH = (1 << 9), GL1_FLAG_OVERLAY_ENABLE = (1 << 10), - GL1_FLAG_OVERLAY_FULLSCREEN = (1 << 11) + GL1_FLAG_OVERLAY_FULLSCREEN = (1 << 11), + GL1_FLAG_FRAME_DUPE_LOCK = (1 << 12) }; typedef struct gl1 diff --git a/gfx/common/gl2_common.h b/gfx/common/gl2_common.h index 3f5034544e6..009db776753 100644 --- a/gfx/common/gl2_common.h +++ b/gfx/common/gl2_common.h @@ -185,7 +185,8 @@ enum gl2_flags GL2_FLAG_OVERLAY_FULLSCREEN = (1 << 18), GL2_FLAG_MENU_TEXTURE_ENABLE = (1 << 19), GL2_FLAG_MENU_TEXTURE_FULLSCREEN= (1 << 20), - GL2_FLAG_NONE = (1 << 21) + GL2_FLAG_NONE = (1 << 21), + GL2_FLAG_FRAME_DUPE_LOCK = (1 << 22) }; struct gl2 diff --git a/gfx/common/gl3_defines.h b/gfx/common/gl3_defines.h index 60589d2e6e3..543248341e5 100644 --- a/gfx/common/gl3_defines.h +++ b/gfx/common/gl3_defines.h @@ -56,7 +56,8 @@ enum gl3_flags GL3_FLAG_FULLSCREEN = (1 << 9), GL3_FLAG_QUITTING = (1 << 10), GL3_FLAG_SHOULD_RESIZE = (1 << 11), - GL3_FLAG_KEEP_ASPECT = (1 << 12) + GL3_FLAG_KEEP_ASPECT = (1 << 12), + GL3_FLAG_FRAME_DUPE_LOCK = (1 << 13) }; struct gl3_streamed_texture diff --git a/gfx/drivers/d3d10.c b/gfx/drivers/d3d10.c index 0cb589ac833..7ee0e609faa 100644 --- a/gfx/drivers/d3d10.c +++ b/gfx/drivers/d3d10.c @@ -2167,6 +2167,13 @@ static bool d3d10_gfx_frame( const char *stat_text = video_info->stat_text; bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false; bool overlay_behind_menu = video_info->overlay_behind_menu; + unsigned black_frame_insertion = video_info->black_frame_insertion; + int bfi_light_frames; + unsigned n; + bool nonblock_state = video_info->input_driver_nonblock_state; + bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; + bool runloop_is_paused = video_info->runloop_is_paused; + #ifdef HAVE_GFX_WIDGETS bool widgets_active = video_info->widgets_active; #endif @@ -2542,6 +2549,47 @@ static bool d3d10_gfx_frame( #endif DXGIPresent(d3d10->swapChain, d3d10->swap_interval, 0); + if ( + black_frame_insertion + && !(d3d10->flags & D3D10_ST_FLAG_MENU_ENABLE) + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused) + { + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(d3d10->flags & D3D10_ST_FLAG_FRAME_DUPE_LOCK)) + { + d3d10->flags |= D3D10_ST_FLAG_FRAME_DUPE_LOCK; + while (bfi_light_frames > 0) + { + if (!(d3d10_gfx_frame(d3d10, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + d3d10->flags &= ~D3D10_ST_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + d3d10->flags &= ~D3D10_ST_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(d3d10->flags & D3D10_ST_FLAG_FRAME_DUPE_LOCK)) + { + context->lpVtbl->OMSetRenderTargets(context, 1, &d3d10->renderTargetView, NULL); + context->lpVtbl->ClearRenderTargetView(context, d3d10->renderTargetView, d3d10->clearcolor); + DXGIPresent(d3d10->swapChain, d3d10->swap_interval, 0); + } + } + } + return true; } @@ -2764,6 +2812,7 @@ static uint32_t d3d10_get_flags(void *data) BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING); BIT32_SET(flags, GFX_CTX_FLAGS_OVERLAY_BEHIND_MENU_SUPPORTED); + BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION); #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG); #endif diff --git a/gfx/drivers/d3d11.c b/gfx/drivers/d3d11.c index aeefa5494bf..47f8d00f5db 100644 --- a/gfx/drivers/d3d11.c +++ b/gfx/drivers/d3d11.c @@ -997,6 +997,7 @@ static uint32_t d3d11_get_flags(void *data) 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); + BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION); #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG); #endif @@ -2793,6 +2794,12 @@ static bool d3d11_gfx_frame( struct font_params* osd_params = (struct font_params*)&video_info->osd_stat_params; bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false; bool overlay_behind_menu = video_info->overlay_behind_menu; + unsigned black_frame_insertion = video_info->black_frame_insertion; + int bfi_light_frames; + unsigned n; + bool nonblock_state = video_info->input_driver_nonblock_state; + bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; + bool runloop_is_paused = video_info->runloop_is_paused; #ifdef HAVE_GFX_WIDGETS bool widgets_active = video_info->widgets_active; #endif @@ -3375,6 +3382,47 @@ static bool d3d11_gfx_frame( Release(pOutput); } + if ( + black_frame_insertion + && !(d3d11->flags & D3D11_ST_FLAG_MENU_ENABLE) + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused) + { + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(d3d11->flags & D3D11_ST_FLAG_FRAME_DUPE_LOCK)) + { + d3d11->flags |= D3D11_ST_FLAG_FRAME_DUPE_LOCK; + while (bfi_light_frames > 0) + { + if (!(d3d11_gfx_frame(d3d11, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + d3d11->flags &= ~D3D11_ST_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + d3d11->flags &= ~D3D11_ST_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(d3d11->flags & D3D11_ST_FLAG_FRAME_DUPE_LOCK)) + { + context->lpVtbl->OMSetRenderTargets(context, 1, &rtv, NULL); + context->lpVtbl->ClearRenderTargetView(context, rtv, d3d11->clearcolor); + DXGIPresent(d3d11->swapChain, d3d11->swap_interval, present_flags); + } + } + } + Release(rtv); return true; diff --git a/gfx/drivers/d3d12.c b/gfx/drivers/d3d12.c index a1b26d4a1ba..e2e61061135 100644 --- a/gfx/drivers/d3d12.c +++ b/gfx/drivers/d3d12.c @@ -1145,6 +1145,7 @@ static uint32_t d3d12_get_flags(void *data) 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); + BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION); #if defined(HAVE_SLANG) && defined(HAVE_SPIRV_CROSS) BIT32_SET(flags, GFX_CTX_FLAGS_SHADERS_SLANG); #endif @@ -3189,6 +3190,48 @@ static void d3d12_init_render_targets(d3d12_video_t* d3d12, unsigned width, unsi d3d12->flags &= ~D3D12_ST_FLAG_RESIZE_RTS; } +static void dx12_inject_black_frame(d3d12_video_t* d3d12) +{ + D3D12GraphicsCommandList cmd = d3d12->queue.cmd; + + + D3D12_GFX_SYNC(); + + d3d12->queue.allocator->lpVtbl->Reset(d3d12->queue.allocator); + cmd->lpVtbl->Reset(cmd, d3d12->queue.allocator, + d3d12->pipes[VIDEO_SHADER_STOCK_BLEND]); + + d3d12->chain.frame_index = DXGIGetCurrentBackBufferIndex( + d3d12->chain.handle); + + D3D12_RESOURCE_TRANSITION( + cmd, + d3d12->chain.renderTargets[d3d12->chain.frame_index], + D3D12_RESOURCE_STATE_PRESENT, + D3D12_RESOURCE_STATE_RENDER_TARGET); + + cmd->lpVtbl->OMSetRenderTargets( + cmd, 1, &d3d12->chain.desc_handles[d3d12->chain.frame_index], + FALSE, NULL); + cmd->lpVtbl->ClearRenderTargetView( + cmd, + d3d12->chain.desc_handles[d3d12->chain.frame_index], + d3d12->chain.clearcolor, + 0, NULL); + + D3D12_RESOURCE_TRANSITION( + cmd, + d3d12->chain.renderTargets[d3d12->chain.frame_index], + D3D12_RESOURCE_STATE_RENDER_TARGET, + D3D12_RESOURCE_STATE_PRESENT); + + cmd->lpVtbl->Close(cmd); + d3d12->queue.handle->lpVtbl->ExecuteCommandLists(d3d12->queue.handle, 1, + (ID3D12CommandList* const*)&d3d12->queue.cmd); + DXGIPresent(d3d12->chain.handle, d3d12->chain.swap_interval, 0); + +} + static bool d3d12_gfx_frame( void* data, const void* frame, @@ -3213,6 +3256,12 @@ static bool d3d12_gfx_frame( &video_info->osd_stat_params; bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false; bool overlay_behind_menu = video_info->overlay_behind_menu; + unsigned black_frame_insertion = video_info->black_frame_insertion; + int bfi_light_frames; + unsigned n; + bool nonblock_state = video_info->input_driver_nonblock_state; + bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; + bool runloop_is_paused = video_info->runloop_is_paused; #ifdef HAVE_GFX_WIDGETS bool widgets_active = video_info->widgets_active; #endif @@ -3923,6 +3972,45 @@ static bool d3d12_gfx_frame( Release(pOutput); } + if ( + black_frame_insertion + && !(d3d12->flags & D3D12_ST_FLAG_MENU_ENABLE) + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused) + { + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(d3d12->flags & D3D12_ST_FLAG_FRAME_DUPE_LOCK)) + { + d3d12->flags |= D3D12_ST_FLAG_FRAME_DUPE_LOCK; + while (bfi_light_frames > 0) + { + if (!(d3d12_gfx_frame(d3d12, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + d3d12->flags &= ~D3D12_ST_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + d3d12->flags &= ~D3D12_ST_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(d3d12->flags & D3D12_ST_FLAG_FRAME_DUPE_LOCK)) + { + dx12_inject_black_frame(d3d12); + } + } + } + return true; } diff --git a/gfx/drivers/gl1.c b/gfx/drivers/gl1.c index 2b72b871a18..0fa7ac58cde 100644 --- a/gfx/drivers/gl1.c +++ b/gfx/drivers/gl1.c @@ -1481,6 +1481,8 @@ static bool gl1_frame(void *data, const void *frame, unsigned pot_height = 0; unsigned video_width = video_info->width; unsigned video_height = video_info->height; + int bfi_light_frames; + unsigned n; #ifdef HAVE_MENU bool menu_is_alive = (video_info->menu_st_flags & MENU_ST_FLAG_ALIVE) ? true : false; #endif @@ -1715,22 +1717,51 @@ static bool gl1_frame(void *data, const void *frame, && !video_info->runloop_is_paused && !(gl1->flags & GL1_FLAG_MENU_TEXTURE_ENABLE)) { - int n; - for (n = 0; n < (int)video_info->black_frame_insertion; ++n) - { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - if (gl1->ctx_driver->swap_buffers) - gl1->ctx_driver->swap_buffers(gl1->ctx_data); - } + + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(gl1->flags & GL1_FLAG_FRAME_DUPE_LOCK)) + { + gl1->flags |= GL1_FLAG_FRAME_DUPE_LOCK; + + while (bfi_light_frames > 0) + { + if (!(gl1_frame(gl1, frame, 0, 0, frame_count, 0, msg, video_info))) + { + gl1->flags &= ~GL1_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + gl1->flags &= ~GL1_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(gl1->flags & GL1_FLAG_FRAME_DUPE_LOCK)) + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if (gl1->ctx_driver->swap_buffers) + gl1->ctx_driver->swap_buffers(gl1->ctx_data); + } + } } #endif + /* check if we are fast forwarding or in menu, if we are ignore hard sync */ if ( hard_sync && !video_info->input_driver_nonblock_state + ) { glClear(GL_COLOR_BUFFER_BIT); @@ -1742,7 +1773,6 @@ static bool gl1_frame(void *data, const void *frame, glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); } - return true; } diff --git a/gfx/drivers/gl2.c b/gfx/drivers/gl2.c index ffa6e5a9ddd..d52885ae4f3 100644 --- a/gfx/drivers/gl2.c +++ b/gfx/drivers/gl2.c @@ -3429,6 +3429,8 @@ static bool gl2_frame(void *data, const void *frame, #ifndef EMSCRIPTEN unsigned black_frame_insertion = video_info->black_frame_insertion; #endif + int bfi_light_frames; + unsigned n; bool input_driver_nonblock_state = video_info->input_driver_nonblock_state; bool hard_sync = video_info->hard_sync; unsigned hard_sync_frames = video_info->hard_sync_frames; @@ -3711,23 +3713,50 @@ static bool gl2_frame(void *data, const void *frame, #ifndef EMSCRIPTEN /* Disable BFI during fast forward, slow-motion, * and pause to prevent flicker. */ - if ( - black_frame_insertion - && !input_driver_nonblock_state - && !runloop_is_slowmotion - && !runloop_is_paused - && (!(gl->flags & GL2_FLAG_MENU_TEXTURE_ENABLE))) - { - size_t n; - for (n = 0; n < black_frame_insertion; ++n) - { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - if (gl->ctx_driver->swap_buffers) - gl->ctx_driver->swap_buffers(gl->ctx_data); - } - } + if ( + video_info->black_frame_insertion + && !video_info->input_driver_nonblock_state + && !video_info->runloop_is_slowmotion + && !video_info->runloop_is_paused + && !(gl->flags & GL2_FLAG_MENU_TEXTURE_ENABLE)) + { + + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(gl->flags & GL2_FLAG_FRAME_DUPE_LOCK)) + { + gl->flags |= GL2_FLAG_FRAME_DUPE_LOCK; + + while (bfi_light_frames > 0) + { + if (!(gl2_frame(gl, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + gl->flags &= ~GL2_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + gl->flags &= ~GL2_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(gl->flags & GL2_FLAG_FRAME_DUPE_LOCK)) + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if (gl->ctx_driver->swap_buffers) + gl->ctx_driver->swap_buffers(gl->ctx_data); + } + } + } #endif /* check if we are fast forwarding or in menu, diff --git a/gfx/drivers/gl3.c b/gfx/drivers/gl3.c index 9e78dd2e2b8..3cb76f4105c 100644 --- a/gfx/drivers/gl3.c +++ b/gfx/drivers/gl3.c @@ -2504,7 +2504,8 @@ static bool gl3_frame(void *data, const void *frame, bool msg_bgcolor_enable = video_info->msg_bgcolor_enable; #endif unsigned black_frame_insertion = video_info->black_frame_insertion; - + int bfi_light_frames; + unsigned n; unsigned hard_sync_frames = video_info->hard_sync_frames; bool runloop_is_paused = video_info->runloop_is_paused; bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; @@ -2672,23 +2673,50 @@ static bool gl3_frame(void *data, const void *frame, #ifndef EMSCRIPTEN /* Disable BFI during fast forward, slow-motion, * and pause to prevent flicker. */ - if ( - black_frame_insertion - && !input_driver_nonblock_state - && !runloop_is_slowmotion - && !runloop_is_paused - && (!(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE))) - { - size_t n; - for (n = 0; n < black_frame_insertion; ++n) - { - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - if (gl->ctx_driver->swap_buffers) - gl->ctx_driver->swap_buffers(gl->ctx_data); - } - } + if ( + video_info->black_frame_insertion + && !video_info->input_driver_nonblock_state + && !video_info->runloop_is_slowmotion + && !video_info->runloop_is_paused + && !(gl->flags & GL3_FLAG_MENU_TEXTURE_ENABLE)) + { + + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK)) + { + gl->flags |= GL3_FLAG_FRAME_DUPE_LOCK; + + while (bfi_light_frames > 0) + { + if (!(gl3_frame(gl, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK; + return false; + } + --bfi_light_frames; + } + gl->flags &= ~GL3_FLAG_FRAME_DUPE_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(gl->flags & GL3_FLAG_FRAME_DUPE_LOCK)) + { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + if (gl->ctx_driver->swap_buffers) + gl->ctx_driver->swap_buffers(gl->ctx_data); + } + } + } #endif if ( hard_sync diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index ed5d72549fe..25846b5062c 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -2504,7 +2504,6 @@ static uint32_t metal_get_flags(void *data) uint32_t flags = 0; BIT32_SET(flags, GFX_CTX_FLAGS_CUSTOMIZABLE_SWAPCHAIN_IMAGES); - BIT32_SET(flags, GFX_CTX_FLAGS_BLACK_FRAME_INSERTION); BIT32_SET(flags, GFX_CTX_FLAGS_MENU_FRAME_FILTERING); BIT32_SET(flags, GFX_CTX_FLAGS_SCREENSHOTS_SUPPORTED); diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index e312c5d5139..f30c5f6c0db 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -4064,6 +4064,8 @@ static bool vulkan_frame(void *data, const void *frame, bool statistics_show = video_info->statistics_show; const char *stat_text = video_info->stat_text; unsigned black_frame_insertion = video_info->black_frame_insertion; + int bfi_light_frames; + unsigned n; bool input_driver_nonblock_state = video_info->input_driver_nonblock_state; bool runloop_is_slowmotion = video_info->runloop_is_slowmotion; bool runloop_is_paused = video_info->runloop_is_paused; @@ -4870,7 +4872,7 @@ static bool vulkan_frame(void *data, const void *frame, vulkan_check_swapchain(vk); /* Disable BFI during fast forward, slow-motion, - * and pause to prevent flicker. */ + * pause, and menu to prevent flicker. */ if ( (backbuffer->image != VK_NULL_HANDLE) && (vk->context->flags & VK_CTX_FLAG_HAS_ACQUIRED_SWAPCHAIN) @@ -4880,12 +4882,37 @@ static bool vulkan_frame(void *data, const void *frame, && !runloop_is_paused && (!(vk->flags & VK_FLAG_MENU_ENABLE))) { - int n; - for (n = 0; n < (int) black_frame_insertion; ++n) + if (video_info->bfi_dark_frames > video_info->black_frame_insertion) + video_info->bfi_dark_frames = video_info->black_frame_insertion; + + /* BFI now handles variable strobe strength, like on-off-off, vs on-on-off for 180hz. + This needs to be done with duping frames instead of increased swap intervals for + a couple reasons. Swap interval caps out at 4 in most all apis as of coding, + and seems to be flat ignored >1 at least in modern Windows for some older APIs. */ + bfi_light_frames = video_info->black_frame_insertion - video_info->bfi_dark_frames; + if (bfi_light_frames > 0 && !(vk->context->flags & VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK)) { - vulkan_inject_black_frame(vk, video_info); - if (vk->ctx_driver->swap_buffers) - vk->ctx_driver->swap_buffers(vk->ctx_data); + vk->context->flags |= VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; + while (bfi_light_frames > 0) + { + if (!(vulkan_frame(vk, NULL, 0, 0, frame_count, 0, msg, video_info))) + { + vk->context->flags &= ~VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; + return false; + } + --bfi_light_frames; + } + vk->context->flags &= ~VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK; + } + + for (n = 0; n < video_info->bfi_dark_frames; ++n) + { + if (!(vk->context->flags & VK_CTX_FLAG_SWAP_INTERVAL_EMULATION_LOCK)) + { + vulkan_inject_black_frame(vk, video_info); + if (vk->ctx_driver->swap_buffers) + vk->ctx_driver->swap_buffers(vk->ctx_data); + } } } diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 186c6bba237..a374bb04199 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -2551,6 +2551,7 @@ void video_driver_build_info(video_frame_info_t *video_info) video_info->crt_switch_porch_adjust = settings->ints.crt_switch_porch_adjust; video_info->crt_switch_hires_menu = settings->bools.crt_switch_hires_menu; video_info->black_frame_insertion = settings->uints.video_black_frame_insertion; + video_info->bfi_dark_frames = settings->uints.video_bfi_dark_frames; video_info->hard_sync = settings->bools.video_hard_sync; video_info->hard_sync_frames = settings->uints.video_hard_sync_frames; video_info->runahead = settings->bools.run_ahead_enabled; diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 52346b3bec6..12e1939df9d 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -415,6 +415,7 @@ typedef struct video_frame_info unsigned custom_vp_full_width; unsigned custom_vp_full_height; unsigned black_frame_insertion; + unsigned bfi_dark_frames; unsigned fps_update_interval; unsigned memory_update_interval; unsigned msg_queue_delay; diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index 09b03c3cc34..8f93b852c1e 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -4031,6 +4031,10 @@ MSG_HASH( MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION, "video_black_frame_insertion" ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES, + "video_bfi_dark_frames" + ) MSG_HASH( MENU_ENUM_LABEL_VIDEO_CROP_OVERSCAN, "video_crop_overscan" diff --git a/intl/msg_hash_us.c b/intl/msg_hash_us.c index 96b07440dce..a748f5f57e7 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -419,6 +419,9 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) case MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION: strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_VIDEO_BLACK_FRAME_INSERTION), len); break; + case MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_VIDEO_BFI_DARK_FRAMES), len); + break; case MENU_ENUM_LABEL_SAVEFILE_DIRECTORY: strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_SAVEFILE_DIRECTORY), len); break; diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h index d45f6dd2287..d70107cde9d 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -1920,11 +1920,87 @@ MSG_HASH( ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_BLACK_FRAME_INSERTION, - "Insert a black frame between frames. Useful on some high refresh rate screens to eliminate ghosting." + "Insert black frame(s) between frames. Can greatly reduce motion blur by emulating CRT scan out, but at cost of brightness. Do not combine with Swap Interval > 1 (Auto is ok), Frame Delay, or Sync to Exact Content Framerate." ) MSG_HASH( MENU_ENUM_LABEL_HELP_VIDEO_BLACK_FRAME_INSERTION, - "Inserts a black frame inbetween frames. Useful for 120 Hz monitors to play 60 Hz material with eliminated ghosting. Video refresh rate should still be configured as if it is a 60 Hz monitor (divide refresh rate by 2)." + "Inserts black frame(s) inbetween frames for enhanced motion clarity. Only use option designated for your current display refresh rate. Not for use at refresh rates that are non-multiples of 60Hz such as 144Hz, 165Hz, etc. Do not combine with Swap Interval > 1 (Auto is ok), Frame Delay, or Sync to Exact Content Framerate. Leaving system VRR on is ok, just not that setting. If you notice -any- temporary image retention, you should disable at 120hz, and for higher hz adjust the dark frames setting below." + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_OFF, + "Off" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_120, + "1 - For 120Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_180, + "2 - For 180Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_240, + "3 - For 240Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_300, + "4 - For 300Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_360, + "5 - For 360Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_420, + "6 - For 420Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_480, + "7 - For 480Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_540, + "8 - For 540Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_600, + "9 - For 600Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_660, + "10 - For 660Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_720, + "11 - For 720Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_780, + "12 - For 780Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_840, + "13 - For 840Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_900, + "14 - For 900Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_960, + "15 - For 960Hz Display Refresh Rate" + ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_BFI_DARK_FRAMES, + "Black Frame Insertion - Dark Frames" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_BFI_DARK_FRAMES, + "Adjust number of black frames in total BFI scan out sequence. More equals higher motion clarity, less equals higher brightness. Not applicable at 120hz as there is only 1 BFI frame to work with total. Settings higher than possible will limit you to the maximum possible for your chosen refresh rate." + ) +MSG_HASH( + MENU_ENUM_LABEL_HELP_VIDEO_BFI_DARK_FRAMES, + "Adjusts the number of frames displayed in the bfi sequence that are black. More black frames increases motion clarity but reduces brightness. Not applicable at 120hz as there is only one total extra 60hz frame, so it must be black otherwise BFI would not be active at all." ) MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_GPU_SCREENSHOT, diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 3481d29890c..7d7b2222a11 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -493,6 +493,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_frame_delay, MENU_ 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_video_bfi_dark_frames, MENU_ENUM_SUBLABEL_VIDEO_BFI_DARK_FRAMES) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_toggle_gamepad_combo, MENU_ENUM_SUBLABEL_INPUT_MENU_ENUM_TOGGLE_GAMEPAD_COMBO) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_quit_gamepad_combo, MENU_ENUM_SUBLABEL_INPUT_QUIT_GAMEPAD_COMBO) DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_show_hidden_files, MENU_ENUM_SUBLABEL_SHOW_HIDDEN_FILES) @@ -4739,6 +4740,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_black_frame_insertion); break; + case MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_bfi_dark_frames); + break; case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_frame_delay); break; diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c index 7a7bd6ae72e..8906a1c82c3 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -9316,6 +9316,7 @@ unsigned menu_displaylist_build_list( bool video_vsync = settings->bools.video_vsync; bool video_hard_sync = settings->bools.video_hard_sync; bool video_wait_swap = settings->bools.video_waitable_swapchains; + unsigned bfi = settings->uints.video_black_frame_insertion; if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_VIDEO_VSYNC, @@ -9332,6 +9333,14 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION, PARSE_ONLY_UINT, false) == 0) count++; + + if (bfi > 0) + { + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES, + PARSE_ONLY_UINT, false) == 0) + count++; + } if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_VIDEO_ADAPTIVE_VSYNC, PARSE_ONLY_BOOL, false) == 0) diff --git a/menu/menu_setting.c b/menu/menu_setting.c index 810151125bc..b8229b31108 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -6392,6 +6392,65 @@ static void setting_get_string_representation_video_swap_interval(rarch_setting_ snprintf(s, len, "%u", *setting->value.target.unsigned_integer); } +static void setting_get_string_representation_black_frame_insertion(rarch_setting_t *setting, + char *s, size_t len) +{ + if (!setting) + return; + + switch (*setting->value.target.unsigned_integer) + { + case 0: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_OFF), len); + break; + case 1: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_120), len); + break; + case 2: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_180), len); + break; + case 3: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_240), len); + break; + case 4: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_300), len); + break; + case 5: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_360), len); + break; + case 6: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_420), len); + break; + case 7: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_480), len); + break; + case 8: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_540), len); + break; + case 9: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_600), len); + break; + case 10: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_660), len); + break; + case 11: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_720), len); + break; + case 12: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_780), len); + break; + case 13: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_840), len); + break; + case 14: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_900), len); + break; + case 15: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_960), len); + break; + } +} + static void setting_get_string_representation_video_frame_delay(rarch_setting_t *setting, char *s, size_t len) { @@ -8105,14 +8164,79 @@ static void general_write_handler(rarch_setting_t *setting) settings->floats.audio_max_timing_skew, *setting->value.target.fraction); break; + case MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION: + /* If enabling BFI, auto disable other sync settings + that do not work together with BFI */ + if (*setting->value.target.unsigned_integer > 0) + { + configuration_set_uint(settings, + settings->uints.video_swap_interval, + 0); + configuration_set_uint(settings, + settings->uints.video_frame_delay, + 0); + configuration_set_bool(settings, + settings->bools.video_frame_delay_auto, + 0); + configuration_set_bool(settings, + settings->bools.vrr_runloop_enable, + 0); + + /* Set reasonable default for dark frames for current BFI value. + Even results OR odd 60hz multiples should be mostly immune to lcd voltage retention. + Nothing to be done for 120hz except phase retention usage if needed. */ + if (*setting->value.target.unsigned_integer == 1) + { + configuration_set_uint(settings, + settings->uints.video_bfi_dark_frames, + 1); + } + /* Odd 60hz multiples, safe to nudge result over 50% for more clarity by default */ + else if ((*setting->value.target.unsigned_integer + 1) % 2 != 0) + { + configuration_set_uint(settings, + settings->uints.video_bfi_dark_frames, + ((*setting->value.target.unsigned_integer + 1) / 2) + 1); + } + /* Odd result on even multiple, bump result up one */ + else if (((*setting->value.target.unsigned_integer + 1) / 2) % 2 != 0) + { + configuration_set_uint(settings, + settings->uints.video_bfi_dark_frames, + ((*setting->value.target.unsigned_integer + 1) / 2) + 1); + } + /* Even result on even multiple, leave alone */ + else + { + configuration_set_uint(settings, + settings->uints.video_bfi_dark_frames, + ((*setting->value.target.unsigned_integer + 1) / 2)); + } + } #ifdef HAVE_CHEEVOS + rcheevos_validate_config_settings(); +#endif + break; + case MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES: + /* Limit choice to max possible for current BFI Hz Setting */ + if (*setting->value.target.unsigned_integer > settings->uints.video_black_frame_insertion) + configuration_set_uint(settings, + settings->uints.video_bfi_dark_frames, + settings->uints.video_black_frame_insertion); + break; case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY: case MENU_ENUM_LABEL_VIDEO_FRAME_DELAY_AUTO: case MENU_ENUM_LABEL_VIDEO_SWAP_INTERVAL: - case MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION: + case MENU_ENUM_LABEL_VRR_RUNLOOP_ENABLE: + /* BFI doesn't play nice with any of these */ + configuration_set_bool(settings, + settings->uints.video_black_frame_insertion, + 0); +#ifdef HAVE_CHEEVOS rcheevos_validate_config_settings(); - break; #endif + break; + case MENU_ENUM_LABEL_VIDEO_REFRESH_RATE_AUTO: driver_ctl(RARCH_DRIVER_CTL_SET_REFRESH_RATE, setting->value.target.fraction); @@ -13449,9 +13573,26 @@ static bool setting_append_list( general_write_handler, general_read_handler); (*list)[list_info->index - 1].action_ok = &setting_action_ok_uint; - menu_settings_list_current_add_range(list, list_info, 0, 5, 1, true, true); + (*list)[list_info->index - 1].get_string_representation = + &setting_get_string_representation_black_frame_insertion; + menu_settings_list_current_add_range(list, list_info, 0, 15, 1, true, true); MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT); SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_CMD_APPLY_AUTO); + + CONFIG_UINT( + list, list_info, + &settings->uints.video_bfi_dark_frames, + MENU_ENUM_LABEL_VIDEO_BFI_DARK_FRAMES, + MENU_ENUM_LABEL_VALUE_VIDEO_BFI_DARK_FRAMES, + DEFAULT_BFI_DARK_FRAMES, + &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, 1, 15, 1, true, true); } } #endif diff --git a/msg_hash.h b/msg_hash.h index e566d026049..531690432fd 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1347,6 +1347,7 @@ enum msg_hash_enums MENU_LABEL(VIDEO_MAX_FRAME_LATENCY), MENU_LABEL(VIDEO_GPU_SCREENSHOT), MENU_LBL_H(VIDEO_BLACK_FRAME_INSERTION), + MENU_LBL_H(VIDEO_BFI_DARK_FRAMES), MENU_LBL_H(VIDEO_FRAME_DELAY), MENU_LBL_H(VIDEO_FRAME_DELAY_AUTO), MENU_ENUM_LABEL_VALUE_VIDEO_FRAME_DELAY_AUTOMATIC, @@ -1412,6 +1413,23 @@ enum msg_hash_enums MENU_LABEL(VIDEO_SWAP_INTERVAL), MENU_ENUM_LABEL_VALUE_VIDEO_SWAP_INTERVAL_AUTO, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_120, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_180, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_240, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_300, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_360, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_420, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_480, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_540, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_600, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_660, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_720, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_780, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_840, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_900, + MENU_ENUM_LABEL_VALUE_VIDEO_BLACK_FRAME_INSERTION_VALUE_960, + MENU_LABEL(VIDEO_FULLSCREEN), MENU_LBL_H(VIDEO_MONITOR_INDEX), MENU_LABEL(VIDEO_WIIU_PREFER_DRC),