diff --git a/core/hw/pvr/spg.cpp b/core/hw/pvr/spg.cpp index c51c4c6ef..c8729afbf 100755 --- a/core/hw/pvr/spg.cpp +++ b/core/hw/pvr/spg.cpp @@ -1,3 +1,4 @@ +#include #include "spg.h" #include "hw/holly/holly_intc.h" #include "hw/holly/sb.h" @@ -23,6 +24,11 @@ static u32 Frame_Cycles; int render_end_schid; int vblank_schid; +static std::array real_times; +static std::array cpu_cycles; +static u32 cpu_time_idx; +bool SH4FastEnough; + void CalculateSync() { u32 pixel_clock = PIXEL_CLOCK / (FB_R_CTRL.vclk_div ? 1 : 2); @@ -127,13 +133,28 @@ int spg_line_sched(int tag, int cycl, int jit) else SPG_STATUS.fieldnum=0; - vblk_cnt++; rend_vblank(); + double now = os_GetSeconds() * 1000000.0; + cpu_time_idx = (cpu_time_idx + 1) % cpu_cycles.size(); + if (cpu_cycles[cpu_time_idx] != 0) + { + u32 cycle_span = (u32)(sh4_sched_now64() - cpu_cycles[cpu_time_idx]); + double time_span = now - real_times[cpu_time_idx]; + double cpu_speed = ((double)cycle_span / time_span) / (SH4_MAIN_CLOCK / 100000000); + SH4FastEnough = cpu_speed >= 85.0; + } + else + SH4FastEnough = false; + cpu_cycles[cpu_time_idx] = sh4_sched_now64(); + real_times[cpu_time_idx] = now; + #ifdef TEST_AUTOMATION replay_input(); #endif - + +#if !defined(NDEBUG) || defined(DEBUGFAST) + vblk_cnt++; if ((os_GetSeconds()-last_fps)>2) { static int Last_FC; @@ -190,6 +211,7 @@ int spg_line_sched(int tag, int cycl, int jit) fskip=0; last_fps=os_GetSeconds(); } +#endif } if (lightgun_line != 0xffff && lightgun_line == prv_cur_scanline) { @@ -273,6 +295,11 @@ void spg_Term() void spg_Reset(bool hard) { CalculateSync(); + + SH4FastEnough = false; + cpu_time_idx = 0; + cpu_cycles.fill(0); + real_times.fill(0.0); } void SetREP(TA_context* cntx) diff --git a/core/hw/pvr/spg.h b/core/hw/pvr/spg.h index a369f5755..d110c8b3a 100644 --- a/core/hw/pvr/spg.h +++ b/core/hw/pvr/spg.h @@ -1,6 +1,8 @@ #pragma once #include "ta_ctx.h" +extern bool SH4FastEnough; + bool spg_Init(); void spg_Term(); void spg_Reset(bool Manual); diff --git a/core/hw/pvr/ta_ctx.cpp b/core/hw/pvr/ta_ctx.cpp index 35347d16a..07049dfc1 100644 --- a/core/hw/pvr/ta_ctx.cpp +++ b/core/hw/pvr/ta_ctx.cpp @@ -1,8 +1,10 @@ #include "ta_ctx.h" +#include "spg.h" #include "oslib/oslib.h" extern u32 fskip; extern u32 FrameCount; +static int RenderCount; TA_context* ta_ctx; tad_context ta_tad; @@ -73,13 +75,18 @@ bool QueueRender(TA_context* ctx) { verify(ctx != 0); - if (rqueue && settings.pvr.SynchronousRender) - //wait for a frame if - // we have another one queue'd - // and SynchronousRender is enabled + bool skipFrame = false; + RenderCount++; + if (RenderCount % (settings.pvr.ta_skip + 1) != 0) + skipFrame = true; + else if (rqueue && (settings.pvr.AutoSkipFrame == 0 + || (settings.pvr.AutoSkipFrame == 1 && SH4FastEnough))) + // The previous render hasn't completed yet so we wait. + // If autoskipframe is enabled (normal level), we only do so if the CPU is running + // fast enough over the last frames frame_finished.Wait(); - if (rqueue) + if (skipFrame || rqueue) { tactx_Recycle(ctx); fskip++; diff --git a/core/hw/pvr/ta_vtx.cpp b/core/hw/pvr/ta_vtx.cpp index 1d1c9037a..222449f6d 100644 --- a/core/hw/pvr/ta_vtx.cpp +++ b/core/hw/pvr/ta_vtx.cpp @@ -1432,8 +1432,6 @@ static void getRegionTileClipping(u32& xmin, u32& xmax, u32& ymin, u32& ymax); FifoSplitter<0> TAFifo0; -int ta_parse_cnt = 0; - // // Check if a vertex has huge x,y,z values or negative z // @@ -1581,69 +1579,66 @@ bool ta_parse_vdrc(TA_context* ctx) vd_ctx = ctx; vd_rc = vd_ctx->rend; - ta_parse_cnt++; - if (ctx->rend.isRTT || 0 == (ta_parse_cnt % ( settings.pvr.ta_skip + 1))) + TAFifo0.vdec_init(); + + bool empty_context = true; + int op_poly_count = 0; + int pt_poly_count = 0; + int tr_poly_count = 0; + + PolyParam *bgpp = vd_rc.global_param_op.head(); + if (bgpp->pcw.Texture) { - TAFifo0.vdec_init(); - - bool empty_context = true; - int op_poly_count = 0; - int pt_poly_count = 0; - int tr_poly_count = 0; - - PolyParam *bgpp = vd_rc.global_param_op.head(); - if (bgpp->pcw.Texture) - { - bgpp->texid = renderer->GetTexture(bgpp->tsp, bgpp->tcw); - empty_context = false; - } - - for (u32 pass = 0; pass <= ctx->tad.render_pass_count; pass++) - { - ctx->MarkRend(pass); - vd_rc.proc_start = ctx->rend.proc_start; - vd_rc.proc_end = ctx->rend.proc_end; - - Ta_Dma* ta_data=(Ta_Dma*)vd_rc.proc_start; - Ta_Dma* ta_data_end=((Ta_Dma*)vd_rc.proc_end)-1; - - do - { - ta_data =TaCmd(ta_data,ta_data_end); - } - while(ta_data<=ta_data_end); - - if (ctx->rend.Overrun) - break; - - bool empty_pass = vd_rc.global_param_op.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->op_count) - && vd_rc.global_param_pt.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->pt_count) - && vd_rc.global_param_tr.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->tr_count); - empty_context = empty_context && empty_pass; - - if (pass == 0 || !empty_pass) - { - RenderPass *render_pass = vd_rc.render_passes.Append(); - render_pass->op_count = vd_rc.global_param_op.used(); - make_index(&vd_rc.global_param_op, op_poly_count, - render_pass->op_count, true, &vd_rc); - op_poly_count = render_pass->op_count; - render_pass->mvo_count = vd_rc.global_param_mvo.used(); - render_pass->pt_count = vd_rc.global_param_pt.used(); - make_index(&vd_rc.global_param_pt, pt_poly_count, - render_pass->pt_count, true, &vd_rc); - pt_poly_count = render_pass->pt_count; - render_pass->tr_count = vd_rc.global_param_tr.used(); - make_index(&vd_rc.global_param_tr, tr_poly_count, - render_pass->tr_count, false, &vd_rc); - tr_poly_count = render_pass->tr_count; - render_pass->mvo_tr_count = vd_rc.global_param_mvo_tr.used(); - render_pass->autosort = UsingAutoSort(pass); - render_pass->z_clear = ClearZBeforePass(pass); - } - } - rv = !empty_context; + bgpp->texid = renderer->GetTexture(bgpp->tsp, bgpp->tcw); + empty_context = false; } + + for (u32 pass = 0; pass <= ctx->tad.render_pass_count; pass++) + { + ctx->MarkRend(pass); + vd_rc.proc_start = ctx->rend.proc_start; + vd_rc.proc_end = ctx->rend.proc_end; + + Ta_Dma* ta_data=(Ta_Dma*)vd_rc.proc_start; + Ta_Dma* ta_data_end=((Ta_Dma*)vd_rc.proc_end)-1; + + do + { + ta_data =TaCmd(ta_data,ta_data_end); + } + while(ta_data<=ta_data_end); + + if (ctx->rend.Overrun) + break; + + bool empty_pass = vd_rc.global_param_op.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->op_count) + && vd_rc.global_param_pt.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->pt_count) + && vd_rc.global_param_tr.used() == (pass == 0 ? 0 : (int)vd_rc.render_passes.LastPtr()->tr_count); + empty_context = empty_context && empty_pass; + + if (pass == 0 || !empty_pass) + { + RenderPass *render_pass = vd_rc.render_passes.Append(); + render_pass->op_count = vd_rc.global_param_op.used(); + make_index(&vd_rc.global_param_op, op_poly_count, + render_pass->op_count, true, &vd_rc); + op_poly_count = render_pass->op_count; + render_pass->mvo_count = vd_rc.global_param_mvo.used(); + render_pass->pt_count = vd_rc.global_param_pt.used(); + make_index(&vd_rc.global_param_pt, pt_poly_count, + render_pass->pt_count, true, &vd_rc); + pt_poly_count = render_pass->pt_count; + render_pass->tr_count = vd_rc.global_param_tr.used(); + make_index(&vd_rc.global_param_tr, tr_poly_count, + render_pass->tr_count, false, &vd_rc); + tr_poly_count = render_pass->tr_count; + render_pass->mvo_tr_count = vd_rc.global_param_mvo_tr.used(); + render_pass->autosort = UsingAutoSort(pass); + render_pass->z_clear = ClearZBeforePass(pass); + } + } + rv = !empty_context; + bool overrun = ctx->rend.Overrun; if (overrun) WARN_LOG(PVR, "ERROR: TA context overrun"); diff --git a/core/nullDC.cpp b/core/nullDC.cpp old mode 100755 new mode 100644 index 0efb133d8..9c0589b94 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -760,7 +760,7 @@ void InitSettings() settings.pvr.ta_skip = 0; settings.pvr.MaxThreads = 3; - settings.pvr.SynchronousRender = true; + settings.pvr.AutoSkipFrame = 0; settings.debug.SerialConsole = false; settings.debug.SerialPTY = false; @@ -860,7 +860,16 @@ void LoadSettings(bool game_specific) settings.pvr.ta_skip = cfgLoadInt(config_section, "ta.skip", settings.pvr.ta_skip); settings.pvr.MaxThreads = cfgLoadInt(config_section, "pvr.MaxThreads", settings.pvr.MaxThreads); - settings.pvr.SynchronousRender = cfgLoadBool(config_section, "pvr.SynchronousRendering", settings.pvr.SynchronousRender); + if (game_specific) + settings.pvr.AutoSkipFrame = cfgLoadInt(config_section, "pvr.AutoSkipFrame", settings.pvr.AutoSkipFrame); + else + { + // compatibility with previous SynchronousRendering option + int autoskip = cfgLoadInt(config_section, "pvr.AutoSkipFrame", 99); + if (autoskip == 99) + autoskip = cfgLoadBool(config_section, "pvr.SynchronousRendering", true) ? 1 : 2; + settings.pvr.AutoSkipFrame = autoskip; + } settings.debug.SerialConsole = cfgLoadBool(config_section, "Debug.SerialConsoleEnabled", settings.debug.SerialConsole); settings.debug.SerialPTY = cfgLoadBool(config_section, "Debug.SerialPTY", settings.debug.SerialPTY); @@ -1017,7 +1026,7 @@ void SaveSettings() cfgSaveBool("config", "rend.WidescreenGameHacks", settings.rend.WidescreenGameHacks); cfgSaveInt("config", "pvr.MaxThreads", settings.pvr.MaxThreads); - cfgSaveBool("config", "pvr.SynchronousRendering", settings.pvr.SynchronousRender); + cfgSaveInt("config", "pvr.AutoSkipFrame", settings.pvr.AutoSkipFrame); cfgSaveBool("config", "Debug.SerialConsoleEnabled", settings.debug.SerialConsole); cfgSaveBool("config", "Debug.SerialPTY", settings.debug.SerialPTY); diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 3d9d5624e..f9000dfab 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -1151,9 +1151,21 @@ static void gui_display_settings() } if (ImGui::CollapsingHeader("Rendering Options", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Checkbox("Synchronous Rendering", &settings.pvr.SynchronousRender); + ImGui::Text("Automatic Frame Skipping:"); + ImGui::Columns(3, "autoskip", false); + ImGui::RadioButton("Disabled", &settings.pvr.AutoSkipFrame, 0); ImGui::SameLine(); - ShowHelpMarker("Reduce frame skipping by pausing the CPU when possible. Recommended for most platforms"); + ShowHelpMarker("No frame skipping"); + ImGui::NextColumn(); + ImGui::RadioButton("Normal", &settings.pvr.AutoSkipFrame, 1); + ImGui::SameLine(); + ShowHelpMarker("Skip a frame when the GPU and CPU are both running slow"); + ImGui::NextColumn(); + ImGui::RadioButton("Maximum", &settings.pvr.AutoSkipFrame, 2); + ImGui::SameLine(); + ShowHelpMarker("Skip a frame when the GPU is running slow"); + ImGui::Columns(1, nullptr, false); + ImGui::Checkbox("Clipping", &settings.rend.Clipping); ImGui::SameLine(); ShowHelpMarker("Enable clipping. May produce graphical errors when disabled"); diff --git a/core/types.h b/core/types.h index 58a881f1d..7aa0babe3 100644 --- a/core/types.h +++ b/core/types.h @@ -475,7 +475,7 @@ struct settings_t RenderType rend; u32 MaxThreads; - bool SynchronousRender; + int AutoSkipFrame; // 0: none, 1: some, 2: more bool IsOpenGL() { return rend == RenderType::OpenGL || rend == RenderType::OpenGL_OIT; } } pvr;