From eef34e946130f23d4a0a7b0f257dfdf873a6feff Mon Sep 17 00:00:00 2001 From: MajorPainTheCactus <89490246+MajorPainTheCactus@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:59:36 +0000 Subject: [PATCH] Added rolling scan line simulation based on the shader subframe feature. This is implemented with a scrolling scissor rect rather than in the shader itself as this is more efficient although may not work for every shader pass - we may need an option to exclude certain passes. The implementation simply divides the screen up by the number of sub frames and then moves the scissor rect down over the screen over the number of sub frames. (#16282) --- config.def.h | 5 ++ configuration.c | 2 + configuration.h | 1 + gfx/drivers/d3d10.c | 79 +++++++++++++++++++ gfx/drivers/d3d11.c | 86 ++++++++++++++++++++- gfx/drivers/d3d12.c | 62 ++++++++++++++- gfx/drivers/gl3.c | 19 +++++ gfx/drivers/vulkan.c | 22 ++++++ gfx/drivers_shader/shader_gl3.cpp | 80 +++++++++++++++++++ gfx/drivers_shader/shader_gl3.h | 8 ++ gfx/drivers_shader/shader_vulkan.cpp | 110 ++++++++++++++++++++++----- gfx/drivers_shader/shader_vulkan.h | 7 ++ gfx/video_driver.c | 3 + gfx/video_driver.h | 2 + intl/msg_hash_lbl.h | 4 + intl/msg_hash_us.c | 3 + intl/msg_hash_us.h | 12 +++ menu/cbs/menu_cbs_sublabel.c | 4 + menu/menu_displaylist.c | 9 +++ menu/menu_setting.c | 20 +++++ msg_hash.h | 1 + ui/drivers/qt/qt_options.cpp | 1 + 22 files changed, 517 insertions(+), 23 deletions(-) diff --git a/config.def.h b/config.def.h index b33a181721..39d8d120e6 100644 --- a/config.def.h +++ b/config.def.h @@ -401,6 +401,11 @@ */ #define DEFAULT_SHADER_SUBFRAMES 1 +/* Divides implements basic rolling scanning of sub frames - does this simply by scrolling a + * a scissor rect down the screen according to how many sub frames there are + */ +#define DEFAULT_SCAN_SUBFRAMES false + /* 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 CRT-like motion clarity. diff --git a/configuration.c b/configuration.c index 618772b8ad..9e4705a710 100644 --- a/configuration.c +++ b/configuration.c @@ -1809,6 +1809,8 @@ static struct config_bool_setting *populate_settings_bool( /* Let implementation decide if automatic, or 1:1 PAR. */ SETTING_BOOL("video_aspect_ratio_auto", &settings->bools.video_aspect_ratio_auto, true, DEFAULT_ASPECT_RATIO_AUTO, false); + SETTING_BOOL("video_scan_subframes", &settings->bools.video_scan_subframes, true, DEFAULT_SCAN_SUBFRAMES, false); + SETTING_BOOL("video_allow_rotate", &settings->bools.video_allow_rotate, true, DEFAULT_ALLOW_ROTATE, false); SETTING_BOOL("video_windowed_fullscreen", &settings->bools.video_windowed_fullscreen, true, DEFAULT_WINDOWED_FULLSCREEN, false); SETTING_BOOL("video_crop_overscan", &settings->bools.video_crop_overscan, true, DEFAULT_CROP_OVERSCAN, false); diff --git a/configuration.h b/configuration.h index 6228682990..01285d01c3 100644 --- a/configuration.h +++ b/configuration.h @@ -611,6 +611,7 @@ typedef struct settings bool video_shader_watch_files; bool video_shader_remember_last_dir; bool video_shader_preset_save_reference_enable; + bool video_scan_subframes; bool video_threaded; bool video_font_enable; bool video_disable_composition; diff --git a/gfx/drivers/d3d10.c b/gfx/drivers/d3d10.c index b5e8b5ba01..f083cf92e7 100644 --- a/gfx/drivers/d3d10.c +++ b/gfx/drivers/d3d10.c @@ -60,6 +60,8 @@ #error "UWP does not support D3D10" #endif +#define D3D10_ROLLING_SCANLINE_SIMULATION + typedef struct { d3d10_texture_t texture; @@ -2417,6 +2419,41 @@ static bool d3d10_gfx_frame( context->lpVtbl->RSSetViewports(context, 1, &d3d10->pass[i].viewport); +#ifdef D3D10_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d10->flags & D3D10_ST_FLAG_MENU_ENABLE))) + { + D3D10_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)d3d10->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = d3d10->pass[i].viewport.Width ; + scissor_rect.bottom = (unsigned int)(((float)d3d10->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + d3d10->device->lpVtbl->RSSetScissorRects(d3d10->device, 1, + &scissor_rect); + } + else + { + D3D10_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = 0; + scissor_rect.right = d3d10->pass[i].viewport.Width; + scissor_rect.bottom = d3d10->pass[i].viewport.Height; + + d3d10->device->lpVtbl->RSSetScissorRects(d3d10->device, 1, + &scissor_rect); + } +#endif // D3D10_ROLLING_SCANLINE_SIMULATION + context->lpVtbl->Draw(context, 4, 0); texture = &d3d10->pass[i].rt; } @@ -2448,6 +2485,29 @@ static bool d3d10_gfx_frame( d3d10->clearcolor); context->lpVtbl->RSSetViewports(context, 1, &d3d10->frame.viewport); +#ifdef D3D10_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d10->flags & D3D10_ST_FLAG_MENU_ENABLE))) + { + D3D10_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = video_width ; + scissor_rect.bottom = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + d3d10->device->lpVtbl->RSSetScissorRects(d3d10->device, 1, + &scissor_rect); + } + else +#endif // D3D10_ROLLING_SCANLINE_SIMULATION { D3D10_RECT scissor_rect; @@ -2464,6 +2524,20 @@ static bool d3d10_gfx_frame( context->lpVtbl->OMSetBlendState(context, d3d10->blend_enable, NULL, D3D10_DEFAULT_SAMPLE_MASK); +#ifdef D3D10_ROLLING_SCANLINE_SIMULATION + { + D3D10_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = 0; + scissor_rect.right = video_width; + scissor_rect.bottom = video_height; + + d3d10->device->lpVtbl->RSSetScissorRects(d3d10->device, 1, + &scissor_rect); + } +#endif // D3D10_ROLLING_SCANLINE_SIMULATION + if ( (d3d10->flags & D3D10_ST_FLAG_MENU_ENABLE) && d3d10->menu.texture.handle) { @@ -2622,8 +2696,13 @@ static bool d3d10_gfx_frame( && (!(d3d10->flags & D3D10_ST_FLAG_FRAME_DUPE_LOCK))) { d3d10->flags |= D3D10_ST_FLAG_FRAME_DUPE_LOCK; + for (k = 1; k < video_info->shader_subframes; k++) { +#ifdef D3D10_ROLLING_SCANLINE_SIMULATION + video_info->current_subframe = k; +#endif // D3D10_ROLLING_SCANLINE_SIMULATION + if (d3d10->shader_preset) for (m = 0; m < d3d10->shader_preset->passes; m++) { diff --git a/gfx/drivers/d3d11.c b/gfx/drivers/d3d11.c index a35218a334..f7e942178a 100644 --- a/gfx/drivers/d3d11.c +++ b/gfx/drivers/d3d11.c @@ -76,6 +76,8 @@ const GUID DECLSPEC_SELECTANY libretro_IID_IDXGIFactory5 = { 0x7632e1f5,0xee65,0 #endif #endif +#define D3D11_ROLLING_SCANLINE_SIMULATION + /* Temporary workaround for d3d11 not being able to poll flags during init */ static gfx_ctx_driver_t d3d11_fake_context; @@ -3025,7 +3027,23 @@ static bool d3d11_gfx_frame( context, width, height, pitch, d3d11->format, frame, &d3d11->frame.texture[0]); } - context->lpVtbl->RSSetState(context, d3d11->scissor_disabled); +#ifdef D3D11_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d11->flags & D3D11_ST_FLAG_MENU_ENABLE))) + { + context->lpVtbl->RSSetState(context, d3d11->scissor_enabled); + } + else +#endif // D3D11_ROLLING_SCANLINE_SIMULATION + { + context->lpVtbl->RSSetState(context, d3d11->scissor_disabled); + } + d3d11->context->lpVtbl->OMSetBlendState( d3d11->context, d3d11->blend_disable, NULL, D3D11_DEFAULT_SAMPLE_MASK); @@ -3158,6 +3176,41 @@ static bool d3d11_gfx_frame( &d3d11->pass[i].rt.rt_view, NULL); context->lpVtbl->RSSetViewports(context, 1, &d3d11->pass[i].viewport); +#ifdef D3D11_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d11->flags & D3D11_ST_FLAG_MENU_ENABLE))) + { + D3D11_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)d3d11->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = d3d11->pass[i].viewport.Width ; + scissor_rect.bottom = (unsigned int)(((float)d3d11->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + d3d11->context->lpVtbl->RSSetScissorRects(d3d11->context, 1, + &scissor_rect); + } + else + { + D3D11_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = 0; + scissor_rect.right = d3d11->pass[i].viewport.Width; + scissor_rect.bottom = d3d11->pass[i].viewport.Height; + + d3d11->context->lpVtbl->RSSetScissorRects(d3d11->context, 1, + &scissor_rect); + } +#endif // D3D11_ROLLING_SCANLINE_SIMULATION + if (i == d3d11->shader_preset->passes - 1) context->lpVtbl->Draw(context, 4, 0); else @@ -3204,6 +3257,33 @@ static bool d3d11_gfx_frame( context->lpVtbl->VSSetConstantBuffers(context, 0, 1, &d3d11->frame.ubo); } +#ifdef D3D11_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d11->flags & D3D11_ST_FLAG_MENU_ENABLE))) + { + D3D11_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = video_width ; + scissor_rect.bottom = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + d3d11->context->lpVtbl->RSSetScissorRects(d3d11->context, 1, + &scissor_rect); + } + else + { + d3d11->context->lpVtbl->RSSetScissorRects(d3d11->context, 1, &d3d11->scissor); + } +#endif // D3D11_ROLLING_SCANLINE_SIMULATION + context->lpVtbl->Draw(context, 4, 0); context->lpVtbl->RSSetState(context, d3d11->scissor_enabled); d3d11->context->lpVtbl->RSSetScissorRects(d3d11->context, 1, &d3d11->scissor); @@ -3458,6 +3538,10 @@ static bool d3d11_gfx_frame( d3d11->flags |= D3D11_ST_FLAG_FRAME_DUPE_LOCK; for (k = 1; k < video_info->shader_subframes; k++) { +#ifdef D3D11_ROLLING_SCANLINE_SIMULATION + video_info->current_subframe = k; +#endif // D3D11_ROLLING_SCANLINE_SIMULATION + if (d3d11->shader_preset) for (m = 0; m < d3d11->shader_preset->passes; m++) { diff --git a/gfx/drivers/d3d12.c b/gfx/drivers/d3d12.c index e37956500c..c91b232077 100644 --- a/gfx/drivers/d3d12.c +++ b/gfx/drivers/d3d12.c @@ -79,6 +79,8 @@ } \ } +#define D3D12_ROLLING_SCANLINE_SIMULATION + typedef struct { d3d12_texture_t texture; @@ -3748,8 +3750,33 @@ static bool d3d12_gfx_frame( #endif cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->pass[i].viewport); - cmd->lpVtbl->RSSetScissorRects(cmd, 1, - &d3d12->pass[i].scissorRect); + +#ifdef D3D12_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d12->flags & D3D12_ST_FLAG_MENU_ENABLE))) + { + D3D12_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)d3d12->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = d3d12->pass[i].viewport.Width; + scissor_rect.bottom = (unsigned int)(((float)d3d12->pass[i].viewport.Height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + cmd->lpVtbl->RSSetScissorRects(cmd, 1, &scissor_rect); + } + else +#endif // D3D12_ROLLING_SCANLINE_SIMULATION + { + cmd->lpVtbl->RSSetScissorRects(cmd, 1, + &d3d12->pass[i].scissorRect); + } if (i == d3d12->shader_preset->passes - 1) start_vertex_location = 0; @@ -3836,7 +3863,32 @@ static bool d3d12_gfx_frame( } cmd->lpVtbl->RSSetViewports(cmd, 1, &d3d12->frame.viewport); - cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->frame.scissorRect); + +#ifdef D3D12_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !black_frame_insertion + && !nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(d3d12->flags & D3D12_ST_FLAG_MENU_ENABLE))) + { + D3D12_RECT scissor_rect; + + scissor_rect.left = 0; + scissor_rect.top = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)video_info->current_subframe); + scissor_rect.right = video_width ; + scissor_rect.bottom = (unsigned int)(((float)video_height / (float)video_info->shader_subframes) + * (float)(video_info->current_subframe + 1)); + + cmd->lpVtbl->RSSetScissorRects(cmd, 1, &scissor_rect); + } + else +#endif // D3D12_ROLLING_SCANLINE_SIMULATION + { + cmd->lpVtbl->RSSetScissorRects(cmd, 1, &d3d12->frame.scissorRect); + } cmd->lpVtbl->DrawInstanced(cmd, 4, 1, 0, 0); @@ -4046,6 +4098,10 @@ static bool d3d12_gfx_frame( d3d12->flags |= D3D12_ST_FLAG_FRAME_DUPE_LOCK; for (k = 1; k < video_info->shader_subframes; k++) { +#ifdef D3D12_ROLLING_SCANLINE_SIMULATION + video_info->current_subframe = k; +#endif // D3D12_ROLLING_SCANLINE_SIMULATION + if (d3d12->shader_preset) for (m = 0; m < d3d12->shader_preset->passes; m++) { diff --git a/gfx/drivers/gl3.c b/gfx/drivers/gl3.c index cb8b8060da..50de2d8895 100644 --- a/gfx/drivers/gl3.c +++ b/gfx/drivers/gl3.c @@ -2604,6 +2604,25 @@ static bool gl3_frame(void *data, const void *frame, gl->filter_chain, 1); } +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && !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))) + { + gl3_filter_chain_set_simulate_scanline( + gl->filter_chain, true); + } + else + { + gl3_filter_chain_set_simulate_scanline( + gl->filter_chain, false); + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION + gl3_filter_chain_set_input_texture(gl->filter_chain, &texture); gl3_filter_chain_build_offscreen_passes(gl->filter_chain, &gl->filter_chain_vp); diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 281f1742c5..5d4473133c 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -4232,6 +4232,28 @@ static bool vulkan_frame(void *data, const void *frame, vulkan_filter_chain_set_current_shader_subframe( (vulkan_filter_chain_t*)vk->filter_chain, 1); } + +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + if ( (video_info->shader_subframes > 1) + && (video_info->scan_subframes) + && (backbuffer->image != VK_NULL_HANDLE) + && !black_frame_insertion + && !input_driver_nonblock_state + && !runloop_is_slowmotion + && !runloop_is_paused + && (!(vk->flags & VK_FLAG_MENU_ENABLE)) + && !(vk->context->swap_interval > 1)) + { + vulkan_filter_chain_set_simulate_scanline( + (vulkan_filter_chain_t*)vk->filter_chain, true); + } + else + { + vulkan_filter_chain_set_simulate_scanline( + (vulkan_filter_chain_t*)vk->filter_chain, false); + } +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + #ifdef HAVE_REWIND vulkan_filter_chain_set_frame_direction( (vulkan_filter_chain_t*)vk->filter_chain, diff --git a/gfx/drivers_shader/shader_gl3.cpp b/gfx/drivers_shader/shader_gl3.cpp index 13a7035896..fbd46fdf88 100644 --- a/gfx/drivers_shader/shader_gl3.cpp +++ b/gfx/drivers_shader/shader_gl3.cpp @@ -935,6 +935,13 @@ public: current_subframe = cur_subframe; } +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + void set_simulate_scanline(bool simulate) + { + simulate_scanline = simulate; + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION + void set_name(const char *name) { pass_name = name; @@ -1036,6 +1043,9 @@ private: unsigned pass_number = 0; uint32_t total_subframes = 1; uint32_t current_subframe = 1; +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + bool simulate_scanline = false; +#endif // GL3_ROLLING_SCANLINE_SIMULATION size_t ubo_offset = 0; std::string pass_name; @@ -1790,12 +1800,55 @@ void Pass::build_commands( glClear(GL_COLOR_BUFFER_BIT); } +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + glEnable(GL_SCISSOR_TEST); + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION + if (final_pass) + { glViewport(current_viewport.x, current_viewport.y, current_viewport.width, current_viewport.height); +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + glScissor( current_viewport.x, + int32_t((float(current_viewport.height) / float(total_subframes)) + * float(current_subframe - 1)), + current_viewport.width, + uint32_t(float(current_viewport.height) / float(total_subframes)) + ); + } + else + { + glScissor( current_viewport.x, current_viewport.y, + current_viewport.width, current_viewport.height); + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION + } else + { glViewport(0, 0, size.width, size.height); +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + glScissor( 0, + int32_t((float(size.height) / float(total_subframes)) + * float(current_subframe - 1)), + size.width, + uint32_t(float(size.height) / float(total_subframes)) + ); + } + else + { + glScissor(0, 0, size.width, size.height); + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION + } + #if !defined(HAVE_OPENGLES) if (framebuffer && framebuffer->get_format() == GL_SRGB8_ALPHA8) glEnable(GL_FRAMEBUFFER_SRGB); @@ -1818,6 +1871,12 @@ void Pass::build_commands( glBindBuffer(GL_ARRAY_BUFFER, 0); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + glDisable(GL_SCISSOR_TEST); + } +#endif // GL3_ROLLING_SCANLINE_SIMULATION #if !defined(HAVE_OPENGLES) glDisable(GL_FRAMEBUFFER_SRGB); @@ -1870,6 +1929,9 @@ public: void set_rotation(uint32_t rot); void set_shader_subframes(uint32_t tot_subframes); void set_current_shader_subframe(uint32_t cur_subframe); +#ifdef GL3_ROLLING_SCANLINE_SIMULATION + void set_simulate_scanline(bool simulate); +#endif // GL3_ROLLING_SCANLINE_SIMULATION void set_pass_name(unsigned pass, const char *name); void add_static_texture(std::unique_ptr texture); @@ -2373,6 +2435,15 @@ void gl3_filter_chain::set_current_shader_subframe(uint32_t cur_subframe) passes[i]->set_current_shader_subframe(cur_subframe); } +#ifdef GL3_ROLLING_SCANLINE_SIMULATION +void gl3_filter_chain::set_simulate_scanline(bool simulate_scanline) +{ + unsigned i; + for (i = 0; i < passes.size(); i++) + passes[i]->set_simulate_scanline(simulate_scanline); +} +#endif // GL3_ROLLING_SCANLINE_SIMULATION + void gl3_filter_chain::set_pass_name(unsigned pass, const char *name) { passes[pass]->set_name(name); @@ -2807,6 +2878,15 @@ void gl3_filter_chain_set_current_shader_subframe( chain->set_current_shader_subframe(cur_subframe); } +#ifdef GL3_ROLLING_SCANLINE_SIMULATION +void gl3_filter_chain_set_simulate_scanline( + gl3_filter_chain_t *chain, + bool simulate_scanline) +{ + chain->set_simulate_scanline(simulate_scanline); +} +#endif // GL3_ROLLING_SCANLINE_SIMULATION + void gl3_filter_chain_set_frame_count_period( gl3_filter_chain_t *chain, unsigned pass, diff --git a/gfx/drivers_shader/shader_gl3.h b/gfx/drivers_shader/shader_gl3.h index 3749b41f77..0e364bda05 100644 --- a/gfx/drivers_shader/shader_gl3.h +++ b/gfx/drivers_shader/shader_gl3.h @@ -25,6 +25,8 @@ #include "glslang_util.h" +#define GL3_ROLLING_SCANLINE_SIMULATION + RETRO_BEGIN_DECLS typedef struct gl3_filter_chain gl3_filter_chain_t; @@ -125,6 +127,12 @@ void gl3_filter_chain_set_current_shader_subframe( gl3_filter_chain_t *chain, uint32_t cur_subframe); +#ifdef GL3_ROLLING_SCANLINE_SIMULATION +void gl3_filter_chain_set_simulate_scanline( + gl3_filter_chain_t *chain, + bool simulate_scanline); +#endif // GL3_ROLLING_SCANLINE_SIMULATION + void gl3_filter_chain_set_pass_name( gl3_filter_chain_t *chain, unsigned pass, diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index 5dfe4b1032..78a48f49ca 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -512,6 +512,9 @@ class Pass cache(cache), num_sync_indices(num_sync_indices), final_pass(final_pass) +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + ,simulate_scanline(false) +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION {} ~Pass(); @@ -548,6 +551,9 @@ class Pass void set_frame_count_period(unsigned p) { frame_count_period = p; } void set_shader_subframes(uint32_t ts) { total_subframes = ts; } void set_current_shader_subframe(uint32_t cs) { current_subframe = cs; } +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + void set_simulate_scanline(bool simulate) { simulate_scanline = simulate; } +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION void set_frame_direction(int32_t dir) { frame_direction = dir; } void set_rotation(uint32_t rot) { rotation = rot; } void set_name(const char *name) { pass_name = name; } @@ -581,6 +587,9 @@ class Pass unsigned num_sync_indices; unsigned sync_index; bool final_pass; +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + bool simulate_scanline; +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION Size2D get_output_size(const Size2D &original_size, const Size2D &max_source) const; @@ -699,6 +708,9 @@ struct vulkan_filter_chain void set_frame_count_period(unsigned pass, unsigned period); void set_shader_subframes(uint32_t total_subframes); void set_current_shader_subframe(uint32_t current_subframe); + #ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + void set_simulate_scanline(bool simulate_scanline); + #endif // VULKAN_ROLLING_SCANLINE_SIMULATION void set_frame_direction(int32_t direction); void set_rotation(uint32_t rot); void set_pass_name(unsigned pass, const char *name); @@ -1707,6 +1719,15 @@ void vulkan_filter_chain::set_current_shader_subframe(uint32_t current_subframe) passes[i]->set_current_shader_subframe(current_subframe); } +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION +void vulkan_filter_chain::set_simulate_scanline(bool simulate_scanline) +{ + unsigned i; + for (i = 0; i < passes.size(); i++) + passes[i]->set_simulate_scanline(simulate_scanline); +} +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + void vulkan_filter_chain::set_frame_direction(int32_t direction) { unsigned i; @@ -2758,18 +2779,39 @@ void Pass::build_commands( if (final_pass) { - const VkRect2D sci = { - { - int32_t(current_viewport.x), - int32_t(current_viewport.y) - }, - { - uint32_t(current_viewport.width), - uint32_t(current_viewport.height) - }, - }; vkCmdSetViewport(cmd, 0, 1, ¤t_viewport); - vkCmdSetScissor(cmd, 0, 1, &sci); + +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + const VkRect2D sci = { + { + int32_t(current_viewport.x), + int32_t((current_viewport.height / float(total_subframes)) + * float(current_subframe - 1)) + }, + { + uint32_t(current_viewport.width), + uint32_t(current_viewport.height / float(total_subframes)) + }, + }; + vkCmdSetScissor(cmd, 0, 1, &sci); + } + else +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + { + const VkRect2D sci = { + { + int32_t(current_viewport.x), + int32_t(current_viewport.y) + }, + { + uint32_t(current_viewport.width), + uint32_t(current_viewport.height) + }, + }; + vkCmdSetScissor(cmd, 0, 1, &sci); + } } else { @@ -2779,16 +2821,37 @@ void Pass::build_commands( float(current_framebuffer_size.height), 0.0f, 1.0f }; - const VkRect2D sci = { - { 0, 0 }, - { - current_framebuffer_size.width, - current_framebuffer_size.height - }, - }; vkCmdSetViewport(cmd, 0, 1, &_vp); - vkCmdSetScissor(cmd, 0, 1, &sci); + +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION + if (simulate_scanline) + { + const VkRect2D sci = { + { + 0, + int32_t((float(current_framebuffer_size.height) / float(total_subframes)) + * float(current_subframe - 1)) + }, + { + uint32_t(current_framebuffer_size.width), + uint32_t(float(current_framebuffer_size.height) / float(total_subframes)) + }, + }; + vkCmdSetScissor(cmd, 0, 1, &sci); + } + else +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + { + const VkRect2D sci = { + { 0, 0 }, + { + current_framebuffer_size.width, + current_framebuffer_size.height + }, + }; + vkCmdSetScissor(cmd, 0, 1, &sci); + } } vkCmdDraw(cmd, 4, 1, 0, 0); @@ -3403,6 +3466,15 @@ void vulkan_filter_chain_set_current_shader_subframe( chain->set_current_shader_subframe(cur_subframe); } +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION +void vulkan_filter_chain_set_simulate_scanline( + vulkan_filter_chain_t *chain, + bool simulate_scanline) +{ + chain->set_simulate_scanline(simulate_scanline); +} +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + void vulkan_filter_chain_set_frame_direction( vulkan_filter_chain_t *chain, int32_t direction) diff --git a/gfx/drivers_shader/shader_vulkan.h b/gfx/drivers_shader/shader_vulkan.h index a5aa16e0db..04725e3df5 100644 --- a/gfx/drivers_shader/shader_vulkan.h +++ b/gfx/drivers_shader/shader_vulkan.h @@ -27,6 +27,8 @@ #include "../include/vulkan/vulkan.h" +#define VULKAN_ROLLING_SCANLINE_SIMULATION + RETRO_BEGIN_DECLS typedef struct vulkan_filter_chain vulkan_filter_chain_t; @@ -126,6 +128,11 @@ void vulkan_filter_chain_set_shader_subframes(vulkan_filter_chain_t *chain, void vulkan_filter_chain_set_current_shader_subframe(vulkan_filter_chain_t *chain, uint32_t cur_subframe); +#ifdef VULKAN_ROLLING_SCANLINE_SIMULATION +void vulkan_filter_chain_set_simulate_scanline(vulkan_filter_chain_t *chain, + bool simulate_scanline); +#endif // VULKAN_ROLLING_SCANLINE_SIMULATION + void vulkan_filter_chain_set_frame_direction(vulkan_filter_chain_t *chain, int32_t direction); diff --git a/gfx/video_driver.c b/gfx/video_driver.c index 73dc2e9b03..3ad0d5aabd 100644 --- a/gfx/video_driver.c +++ b/gfx/video_driver.c @@ -2563,6 +2563,8 @@ void video_driver_build_info(video_frame_info_t *video_info) video_info->black_frame_insertion = settings->uints.video_black_frame_insertion; video_info->bfi_dark_frames = settings->uints.video_bfi_dark_frames; video_info->shader_subframes = settings->uints.video_shader_subframes; + video_info->current_subframe = 0; + video_info->scan_subframes = settings->bools.video_scan_subframes; 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; @@ -3920,6 +3922,7 @@ void video_driver_frame(const void *data, unsigned width, && video_st->current_video && video_st->current_video->frame) { + video_info.current_subframe = 0; if (video_st->current_video->frame( video_st->data, data, width, height, video_st->frame_count, (unsigned)pitch, diff --git a/gfx/video_driver.h b/gfx/video_driver.h index 572990dd8b..87a44cc8f2 100644 --- a/gfx/video_driver.h +++ b/gfx/video_driver.h @@ -417,6 +417,7 @@ typedef struct video_frame_info unsigned black_frame_insertion; unsigned bfi_dark_frames; unsigned shader_subframes; + unsigned current_subframe; unsigned fps_update_interval; unsigned memory_update_interval; unsigned msg_queue_delay; @@ -489,6 +490,7 @@ typedef struct video_frame_info bool crt_switch_hires_menu; bool hdr_enable; bool overlay_behind_menu; + bool scan_subframes; } video_frame_info_t; typedef void (*update_window_title_cb)(void*); diff --git a/intl/msg_hash_lbl.h b/intl/msg_hash_lbl.h index a5aa881bc6..0e2b8cf03b 100644 --- a/intl/msg_hash_lbl.h +++ b/intl/msg_hash_lbl.h @@ -4131,6 +4131,10 @@ MSG_HASH( MENU_ENUM_LABEL_VIDEO_SHADER_SUBFRAMES, "video_shader_subframes" ) +MSG_HASH( + MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES, + "video_scan_subframes" + ) 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 6831eab1d1..c38c8881b0 100644 --- a/intl/msg_hash_us.c +++ b/intl/msg_hash_us.c @@ -428,6 +428,9 @@ int msg_hash_get_help_us_enum(enum msg_hash_enums msg, char *s, size_t len) case MENU_ENUM_LABEL_VIDEO_SHADER_SUBFRAMES: strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_VIDEO_SHADER_SUBFRAMES), len); break; + case MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES: + strlcpy(s, msg_hash_to_str(MENU_ENUM_LABEL_HELP_VIDEO_SCAN_SUBFRAMES), 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 5d31d0092e..c3df585bc9 100644 --- a/intl/msg_hash_us.h +++ b/intl/msg_hash_us.h @@ -2098,6 +2098,18 @@ MSG_HASH( MENU_ENUM_LABEL_VALUE_VIDEO_GPU_SCREENSHOT, "GPU Screenshot" ) +MSG_HASH( + MENU_ENUM_LABEL_VALUE_VIDEO_SCAN_SUBFRAMES, + "Rolling scanline simulation" + ) +MSG_HASH( + MENU_ENUM_SUBLABEL_VIDEO_SCAN_SUBFRAMES, + "Simulates a basic rolling scanline over multiple sub-frames by dividing the screen up vertically and rendering each part of the screen according to how many sub frames there are" + ) +MSG_HASH( + MENU_ENUM_LABEL_HELP_VIDEO_SCAN_SUBFRAMES, + "Simulates a basic rolling scanline over multiple sub-frames by dividing the screen up vertically and rendering each part of the screen according to how many sub frames there are from the top of the screen down" + ) MSG_HASH( MENU_ENUM_SUBLABEL_VIDEO_GPU_SCREENSHOT, "Screenshots capture GPU shaded material if available." diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c index 494cb59a44..a2653a472f 100644 --- a/menu/cbs/menu_cbs_sublabel.c +++ b/menu/cbs/menu_cbs_sublabel.c @@ -501,6 +501,7 @@ DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_shader_delay, MENU_ 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_video_shader_subframes, MENU_ENUM_SUBLABEL_VIDEO_SHADER_SUBFRAMES) +DEFAULT_SUBLABEL_MACRO(action_bind_sublabel_video_scan_subframes, MENU_ENUM_SUBLABEL_VIDEO_SCAN_SUBFRAMES) 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) @@ -4828,6 +4829,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs, case MENU_ENUM_LABEL_VIDEO_SHADER_SUBFRAMES: BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_shader_subframes); break; + case MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES: + BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_video_scan_subframes); + 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 8ce436b0b6..78a84f5123 100644 --- a/menu/menu_displaylist.c +++ b/menu/menu_displaylist.c @@ -9400,6 +9400,7 @@ unsigned menu_displaylist_build_list( 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; + unsigned shader_subframes = settings->uints.video_shader_subframes; if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_VIDEO_VSYNC, @@ -9418,6 +9419,14 @@ unsigned menu_displaylist_build_list( MENU_ENUM_LABEL_VIDEO_SHADER_SUBFRAMES, PARSE_ONLY_UINT, false) == 0) count++; + + if (shader_subframes > 1) + { + if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, + MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES, + PARSE_ONLY_BOOL, false) == 0) + count++; + } } if (MENU_DISPLAYLIST_PARSE_SETTINGS_ENUM(list, MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION, diff --git a/menu/menu_setting.c b/menu/menu_setting.c index dd57f20578..ac3c9f6fc6 100644 --- a/menu/menu_setting.c +++ b/menu/menu_setting.c @@ -13551,6 +13551,26 @@ static bool setting_append_list( SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_CMD_APPLY_AUTO); SETTINGS_DATA_LIST_CURRENT_ADD_FLAGS(list, list_info, SD_FLAG_LAKKA_ADVANCED); + CONFIG_BOOL( + list, list_info, + &settings->bools.video_scan_subframes, + MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES, + MENU_ENUM_LABEL_VALUE_VIDEO_SCAN_SUBFRAMES, + DEFAULT_SCAN_SUBFRAMES, + MENU_ENUM_LABEL_VALUE_OFF, + MENU_ENUM_LABEL_VALUE_ON, + &group_info, + &subgroup_info, + parent_group, + general_write_handler, + general_read_handler, + SD_FLAG_NONE); + (*list)[list_info->index - 1].action_ok = setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_left = setting_bool_action_left_with_refresh; + (*list)[list_info->index - 1].action_right = setting_bool_action_right_with_refresh; + 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_swapchain_images, diff --git a/msg_hash.h b/msg_hash.h index bbb71f1f2a..57567e7450 100644 --- a/msg_hash.h +++ b/msg_hash.h @@ -1378,6 +1378,7 @@ enum msg_hash_enums MENU_LBL_H(VIDEO_BLACK_FRAME_INSERTION), MENU_LBL_H(VIDEO_BFI_DARK_FRAMES), MENU_LBL_H(VIDEO_SHADER_SUBFRAMES), + MENU_LBL_H(VIDEO_SCAN_SUBFRAMES), MENU_LBL_H(VIDEO_FRAME_DELAY), MENU_LBL_H(VIDEO_FRAME_DELAY_AUTO), MENU_ENUM_LABEL_VALUE_VIDEO_FRAME_DELAY_AUTOMATIC, diff --git a/ui/drivers/qt/qt_options.cpp b/ui/drivers/qt/qt_options.cpp index 66409953f0..71850a4c97 100644 --- a/ui/drivers/qt/qt_options.cpp +++ b/ui/drivers/qt/qt_options.cpp @@ -1375,6 +1375,7 @@ QWidget *VideoPage::widget() vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_SWAP_INTERVAL); vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_SHADER_SUBFRAMES); + vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_SCAN_SUBFRAMES); vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_BLACK_FRAME_INSERTION); vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_ADAPTIVE_VSYNC); vSyncGroup->add(MENU_ENUM_LABEL_VIDEO_FRAME_DELAY);