diff --git a/rpcs3/Emu/RSX/GSFrameBase.h b/rpcs3/Emu/RSX/GSFrameBase.h index 4630334c41..f345f6255e 100644 --- a/rpcs3/Emu/RSX/GSFrameBase.h +++ b/rpcs3/Emu/RSX/GSFrameBase.h @@ -25,6 +25,7 @@ public: virtual void flip(draw_context_t ctx, bool skip_frame = false) = 0; virtual int client_width() = 0; virtual int client_height() = 0; + virtual f64 client_display_rate() = 0; virtual bool has_alpha() = 0; virtual display_handle_t handle() const = 0; diff --git a/rpcs3/Emu/RSX/GSRender.cpp b/rpcs3/Emu/RSX/GSRender.cpp index ef5bea89dd..917a772a34 100644 --- a/rpcs3/Emu/RSX/GSRender.cpp +++ b/rpcs3/Emu/RSX/GSRender.cpp @@ -52,3 +52,14 @@ void GSRender::flip(const rsx::display_flip_info_t&) m_frame->flip(m_context); } } + +f64 GSRender::get_display_refresh_rate() const +{ + if (m_frame) + { + return m_frame->client_display_rate(); + } + + // Minimum + return 20.; +} diff --git a/rpcs3/Emu/RSX/GSRender.h b/rpcs3/Emu/RSX/GSRender.h index d939c48ccb..eea040bf29 100644 --- a/rpcs3/Emu/RSX/GSRender.h +++ b/rpcs3/Emu/RSX/GSRender.h @@ -31,6 +31,7 @@ public: void on_exit() override; void flip(const rsx::display_flip_info_t& info) override; + f64 get_display_refresh_rate() const override; GSFrameBase* get_frame() const { return m_frame; } }; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 7b9f99327e..37ed9bac6f 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -3775,6 +3775,39 @@ namespace rsx m_profiler.enabled = !!g_cfg.video.overlay; } + f64 thread::get_cached_display_refresh_rate() + { + constexpr u64 uses_per_query = 512; + + f64 result = m_cached_display_rate; + u64 count = m_display_rate_fetch_count++; + + while (true) + { + if (count % 512 == 0) + { + result = get_display_refresh_rate(); + m_cached_display_rate.store(result); + m_display_rate_fetch_count += uses_per_query; // Notify users of the new value + break; + } + + const u64 new_count = m_display_rate_fetch_count; + const f64 new_cached = m_cached_display_rate; + + if (result == new_cached && count / uses_per_query == new_count / uses_per_query) + { + break; + } + + // An update might have gone through + count = new_count; + result = new_cached; + } + + return result; + } + bool thread::request_emu_flip(u32 buffer) { if (is_current_thread()) // requested through command buffer @@ -3826,9 +3859,11 @@ namespace rsx switch (frame_limit) { case frame_limit_type::none: limit = g_cfg.core.max_cpu_preempt_count_per_frame ? static_cast(g_cfg.video.vblank_rate) : 0.; break; + case frame_limit_type::_30: limit = 30.; break; case frame_limit_type::_50: limit = 50.; break; case frame_limit_type::_60: limit = 60.; break; - case frame_limit_type::_30: limit = 30.; break; + case frame_limit_type::_120: limit = 120.; break; + case frame_limit_type::display_rate: limit = get_cached_display_refresh_rate(); break; case frame_limit_type::_auto: limit = static_cast(g_cfg.video.vblank_rate); break; case frame_limit_type::_ps3: limit = 0.; break; case frame_limit_type::infinite: limit = 0.; break; diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index b386236a29..3f7b3842f1 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -194,6 +194,11 @@ namespace rsx // Overlays rsx::overlays::display_manager* m_overlay_manager = nullptr; + atomic_t m_display_rate_fetch_count = 0; + atomic_t m_cached_display_rate = 60.; + f64 get_cached_display_refresh_rate(); + virtual f64 get_display_refresh_rate() const = 0; + // Invalidated memory range address_range m_invalidated_memory_range; diff --git a/rpcs3/Emu/system_config_types.cpp b/rpcs3/Emu/system_config_types.cpp index a71e2abde9..ef6ecbbf87 100644 --- a/rpcs3/Emu/system_config_types.cpp +++ b/rpcs3/Emu/system_config_types.cpp @@ -332,9 +332,11 @@ void fmt_class_string::format(std::string& out, u64 arg) switch (value) { case frame_limit_type::none: return "Off"; + case frame_limit_type::_30: return "30"; case frame_limit_type::_50: return "50"; case frame_limit_type::_60: return "60"; - case frame_limit_type::_30: return "30"; + case frame_limit_type::_120: return "60"; + case frame_limit_type::display_rate: return "Display"; case frame_limit_type::_auto: return "Auto"; case frame_limit_type::_ps3: return "PS3 Native"; case frame_limit_type::infinite: return "Infinite"; diff --git a/rpcs3/Emu/system_config_types.h b/rpcs3/Emu/system_config_types.h index f1aedbdde4..d648ff9779 100644 --- a/rpcs3/Emu/system_config_types.h +++ b/rpcs3/Emu/system_config_types.h @@ -206,9 +206,11 @@ enum class video_aspect enum class frame_limit_type { none, + _30, _50, _60, - _30, + _120, + display_rate, _auto, _ps3, infinite, diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 2965c4664c..b08e094b01 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -958,9 +958,11 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ switch (static_cast(index)) { case frame_limit_type::none: return tr("Off", "Frame limit"); + case frame_limit_type::_30: return tr("30", "Frame limit"); case frame_limit_type::_50: return tr("50", "Frame limit"); case frame_limit_type::_60: return tr("60", "Frame limit"); - case frame_limit_type::_30: return tr("30", "Frame limit"); + case frame_limit_type::_120: return tr("120", "Frame limit"); + case frame_limit_type::display_rate: return tr("Display", "Frame limit"); case frame_limit_type::_auto: return tr("Auto", "Frame limit"); case frame_limit_type::_ps3: return tr("PS3 Native", "Frame limit"); case frame_limit_type::infinite: return tr("Infinite", "Frame limit"); diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 05637522cb..b21703d2ec 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -781,6 +781,23 @@ int gs_frame::client_height() return height() * devicePixelRatio(); } +f64 gs_frame::client_display_rate() +{ + f64 rate = 20.; // Minimum is 20 + + Emu.BlockingCallFromMainThread([this, &rate]() + { + const QList screens = QGuiApplication::screens(); + + for (int i = 0; i < screens.count(); i++) + { + rate = std::fmax(rate, ::at32(screens, i)->refreshRate()); + } + }); + + return rate; +} + void gs_frame::flip(draw_context_t, bool /*skip_frame*/) { static Timer fps_t; diff --git a/rpcs3/rpcs3qt/gs_frame.h b/rpcs3/rpcs3qt/gs_frame.h index 48c15f9400..95d358105e 100644 --- a/rpcs3/rpcs3qt/gs_frame.h +++ b/rpcs3/rpcs3qt/gs_frame.h @@ -89,6 +89,7 @@ protected: void flip(draw_context_t context, bool skip_frame = false) override; int client_width() override; int client_height() override; + f64 client_display_rate() override; bool has_alpha() override; bool event(QEvent* ev) override; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 73563abcfd..e8e073e16e 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -545,6 +545,33 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->frameLimitBox, emu_settings_type::FrameLimit); SubscribeTooltip(ui->gb_frameLimit, tooltips.settings.frame_limit); + { + const QList screens = QGuiApplication::screens(); + + f64 rate = 20.; // Minimum rate + + for (int i = 0; i < screens.count(); i++) + { + rate = std::fmax(rate, ::at32(screens, i)->refreshRate()); + } + + for (int i = 0; i < ui->frameLimitBox->count(); i++) + { + const QVariantList var_list = ui->frameLimitBox->itemData(i).toList(); + + if (var_list.size() != 2 || !var_list[1].canConvert()) + { + fmt::throw_exception("Invalid data found in combobox entry %d (text='%s', listsize=%d, itemcount=%d)", i, ui->frameLimitBox->itemText(i), var_list.size(), ui->frameLimitBox->count()); + } + + if (static_cast(frame_limit_type::display_rate) == var_list[1].toInt()) + { + ui->frameLimitBox->setItemText(i, tr("Display (%1)", "Frame Limit").arg(std::round(rate))); + break; + } + } + } + m_emu_settings->EnhanceComboBox(ui->antiAliasing, emu_settings_type::MSAA); SubscribeTooltip(ui->gb_antiAliasing, tooltips.settings.anti_aliasing); diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index ecf1d340ac..185bf56dd3 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -172,7 +172,7 @@ public: const QString resolution = tr("This setting will be ignored if the Resolution Scale is set to anything other than 100%!\nLeave this on 1280x720. Every PS3 game is compatible with this resolution.\nOnly use 1920x1080 if the game supports it.\nRarely due to emulation bugs some games will only render at low resolutions like 480p."); const QString graphics_adapter = tr("On multi GPU systems select which GPU to use in RPCS3 when using Vulkan.\nThis is not needed when using OpenGL."); const QString aspect_ratio = tr("Leave this on 16:9 unless you have a 4:3 monitor."); - const QString frame_limit = tr("Off is the fastest option.\nUsing the frame limiter will add extra overhead and slow down the game. However, some games will crash if the framerate is too high.\nPS3 native should only be used if Auto is not working correctly as it can introduce frame-pacing issues.\nInfinite adds a positive feedback loop which adds another vblank signal per frame allowing more games to be fps limitless."); + const QString frame_limit = tr("Off is the fastest option.\nUsing the frame limiter will add extra overhead and slow down the game. However, some games will crash if the framerate is too high.\nPS3 native should only be used if Auto is not working correctly as it can introduce frame-pacing issues.\nInfinite adds a positive feedback loop which adds another vblank signal per frame allowing more games to be fps limitless.\nExperienced users with need of other frame limits should use the setting \"Second Frame Limit\" in the configuration file."); const QString anti_aliasing = tr("Emulate PS3 multisampling layout.\nCan fix some otherwise difficult to solve graphics glitches.\nLow to moderate performance hit depending on your GPU hardware."); const QString anisotropic_filter = tr("Higher values increase sharpness of textures on sloped surfaces at the cost of GPU resources.\nModern GPUs can handle this setting just fine, even at 16x.\nKeep this on Automatic if you want to use the original setting used by a real PS3."); const QString resolution_scale = tr("Scales the game's resolution by the given percentage.\nThe base resolution is always 1280x720.\nSet this value to 100% if you want to use the normal Resolution options.\nValues below 100% will usually not improve performance.");