diff --git a/config.def.h b/config.def.h index 73d09d3b6b..d002b96da1 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 14f624300e..a227ee11b5 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 2a60eff8ba..f6bb9d122b 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 5c89021e88..fe6cd6f461 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 94ef6776e7..45c5bd48a1 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 e383f5b976..4892f81339 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 ae27a7e4e7..51eb1ccf09 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 3f5034544e..009db77675 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 60589d2e6e..543248341e 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 0cb589ac83..7ee0e609fa 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 aeefa5494b..47f8d00f5d 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 a1b26d4a1b..e2e6106113 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 2b72b871a1..0fa7ac58cd 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 ffa6e5a9dd..d52885ae4f 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 ( + 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 (gl->ctx_driver->swap_buffers) - gl->ctx_driver->swap_buffers(gl->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 && !(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 9e78dd2e2b..3cb76f4105 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 ( + 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 (gl->ctx_driver->swap_buffers) - gl->ctx_driver->swap_buffers(gl->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 && !(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 ed5d72549f..25846b5062 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 e312c5d513..f30c5f6c0d 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 186c6bba23..a374bb0419 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 52346b3bec..12e1939df9 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 09b03c3cc3..8f93b852c1 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 96b07440dc..a748f5f57e 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 d45f6dd228..d70107cde9 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 3481d29890..7d7b2222a1 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 7a7bd6ae72..8906a1c82c 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 810151125b..b8229b3110 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 e566d02604..531690432f 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),