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);