diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 6a6785cc0b..7bca67bfe5 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -658,7 +658,6 @@ struct Pcsx2Config PCRTCOffsets : 1, PCRTCOverscan : 1, IntegerScaling : 1, - SyncToHostRefreshRate : 1, UseDebugDevice : 1, UseBlitSwapChain : 1, DisableShaderCache : 1, @@ -726,11 +725,9 @@ struct Pcsx2Config // forces the MTGS to execute tags/tasks in fully blocking/synchronous // style. Useful for debugging potential bugs in the MTGS pipeline. bool SynchronousMTGS = false; - bool FrameLimitEnable = true; VsyncMode VsyncEnable = VsyncMode::Off; - float LimitScalar = 1.0f; float FramerateNTSC = DEFAULT_FRAME_RATE_NTSC; float FrameratePAL = DEFAULT_FRAME_RATE_PAL; @@ -1137,24 +1134,24 @@ struct Pcsx2Config }; // ------------------------------------------------------------------------ - struct FramerateOptions + struct EmulationSpeedOptions { + BITFIELD32() + bool FrameLimitEnable : 1; + bool SyncToHostRefreshRate : 1; + BITFIELD_END + float NominalScalar{1.0f}; float TurboScalar{2.0f}; float SlomoScalar{0.5f}; + EmulationSpeedOptions(); + void LoadSave(SettingsWrapper& wrap); void SanityCheck(); - bool operator==(const FramerateOptions& right) const - { - return OpEqu(NominalScalar) && OpEqu(TurboScalar) && OpEqu(SlomoScalar); - } - - bool operator!=(const FramerateOptions& right) const - { - return !this->operator==(right); - } + bool operator==(const EmulationSpeedOptions& right) const; + bool operator!=(const EmulationSpeedOptions& right) const; }; // ------------------------------------------------------------------------ @@ -1320,7 +1317,7 @@ struct Pcsx2Config GamefixOptions Gamefixes; ProfilerOptions Profiler; DebugOptions Debugger; - FramerateOptions Framerate; + EmulationSpeedOptions EmulationSpeed; SPU2Options SPU2; DEV9Options DEV9; USBOptions USB; @@ -1346,7 +1343,6 @@ struct Pcsx2Config std::string CurrentIRX; std::string CurrentGameArgs; AspectRatioType CurrentAspectRatio = AspectRatioType::RAuto4_3_3_2; - LimiterModeType LimiterMode = LimiterModeType::Nominal; Pcsx2Config(); void LoadSave(SettingsWrapper& wrap); @@ -1359,11 +1355,8 @@ struct Pcsx2Config std::string FullpathToBios() const; std::string FullpathToMcd(uint slot) const; - bool operator==(const Pcsx2Config& right) const; - bool operator!=(const Pcsx2Config& right) const - { - return !this->operator==(right); - } + bool operator==(const Pcsx2Config& right) const = delete; + bool operator!=(const Pcsx2Config& right) const = delete; /// Copies runtime configuration settings (e.g. frame limiter state). void CopyRuntimeConfig(Pcsx2Config& cfg); diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 22623c8703..1e6c72370f 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -36,12 +36,9 @@ #include "VMManager.h" #include "VUmicro.h" -using namespace Threading; - extern u8 psxhblankgate; static const uint EECNT_FUTURE_TARGET = 0x10000000; static int gates = 0; -static bool s_use_vsync_for_timing = false; uint g_FrameCount = 0; @@ -187,14 +184,6 @@ void rcntInit() cpuRcntSet(); } - -#ifndef _WIN32 -#include -#endif - -static s64 m_iTicks=0; -static u64 m_iStart=0; - struct vSyncTimingInfo { double Framerate; // frames per second (8 bit fixed) @@ -210,10 +199,8 @@ struct vSyncTimingInfo u32 hScanlinesPerFrame; // number of scanlines per frame (525/625 for NTSC/PAL) }; - static vSyncTimingInfo vSyncInfo; - static void vSyncInfoCalc(vSyncTimingInfo* info, double framesPerSecond, u32 scansPerFrame) { constexpr double clock = static_cast(PS2CLK); @@ -350,39 +337,6 @@ double GetVerticalFrequency() } } -static double AdjustToHostRefreshRate(double vertical_frequency, double frame_limit) -{ - if (!EmuConfig.GS.SyncToHostRefreshRate || EmuConfig.GS.LimitScalar != 1.0f) - { - SPU2::SetDeviceSampleRateMultiplier(1.0); - s_use_vsync_for_timing = false; - return frame_limit; - } - - float host_refresh_rate; - if (!GSGetHostRefreshRate(&host_refresh_rate)) - { - Console.Warning("Cannot sync to host refresh since the query failed."); - SPU2::SetDeviceSampleRateMultiplier(1.0); - s_use_vsync_for_timing = false; - return frame_limit; - } - - const double ratio = host_refresh_rate / vertical_frequency; - const bool syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f); - s_use_vsync_for_timing = (syncing_to_host && !EmuConfig.GS.SkipDuplicateFrames && EmuConfig.GS.VsyncEnable != VsyncMode::Off); - Console.WriteLn("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s %s", host_refresh_rate, - vertical_frequency, ratio, syncing_to_host ? "can sync" : "can't sync", - s_use_vsync_for_timing ? "and using vsync for pacing" : "and using sleep for pacing"); - - if (!syncing_to_host) - return frame_limit; - - frame_limit *= ratio; - SPU2::SetDeviceSampleRateMultiplier(ratio); - return frame_limit; -} - void UpdateVSyncRate(bool force) { // Notice: (and I probably repeat this elsewhere, but it's worth repeating) @@ -397,11 +351,6 @@ void UpdateVSyncRate(bool force) if (vSyncInfo.Framerate != frames_per_second || vSyncInfo.VideoMode != gsVideoMode || force) { - const double frame_limit = AdjustToHostRefreshRate(vertical_frequency, frames_per_second * EmuConfig.GS.LimitScalar); - - const double tick_rate = GetTickFrequency() / 2.0; - const s64 ticks = static_cast(tick_rate / std::max(frame_limit, 1.0)); - u32 total_scanlines = 0; bool custom = false; @@ -471,20 +420,10 @@ void UpdateVSyncRate(bool force) ((vsyncCounter.Mode == MODE_VSYNC) ? vSyncInfo.Blank : vSyncInfo.Render); cpuRcntSet(); - PerformanceMetrics::SetVerticalFrequency(vertical_frequency); - - if (m_iTicks != ticks) - m_iTicks = ticks; - - m_iStart = GetCPUTicks(); + VMManager::Internal::FrameRateChanged(); } } -void frameLimitReset() -{ - m_iStart = GetCPUTicks(); -} - // FMV switch stuff extern uint eecount_on_last_vdec; extern bool FMVstarted; @@ -546,55 +485,16 @@ static __fi void DoFMVSwitch() RendererSwitched = false; } -// Framelimiter - Measures the delta time between calls and stalls until a -// certain amount of time passes if such time hasn't passed yet. -static __fi void frameLimit() -{ - // Framelimiter off in settings? Framelimiter go brrr. - if (EmuConfig.GS.LimitScalar == 0.0f || s_use_vsync_for_timing) - return; - - const u64 uExpectedEnd = m_iStart + m_iTicks; // Compute when we would expect this frame to end, assuming everything goes perfectly perfect. - const u64 iEnd = GetCPUTicks(); // The current tick we actually stopped on. - const s64 sDeltaTime = iEnd - uExpectedEnd; // The diff between when we stopped and when we expected to. - - // If frame ran too long... - if (sDeltaTime >= m_iTicks) - { - // ... Fudge the next frame start over a bit. Prevents fast forward zoomies. - m_iStart += (sDeltaTime / m_iTicks) * m_iTicks; - return; - } - - // Conversion of delta from CPU ticks (microseconds) to milliseconds - s32 msec = (int) ((sDeltaTime * -1000) / (s64) GetTickFrequency()); - - // If any integer value of milliseconds exists, sleep it off. - // Prior comments suggested that 1-2 ms sleeps were inaccurate on some OSes; - // further testing suggests instead that this was utter bullshit. - if (msec > 1) - { - Threading::Sleep(msec - 1); - } - - // Conversion to milliseconds loses some precision; after sleeping off whole milliseconds, - // spin the thread without sleeping until we finally reach our expected end time. - while (GetCPUTicks() < uExpectedEnd) - { - // SKREEEEEEEE - } - - // Finally, set our next frame start to when this one ends - m_iStart = uExpectedEnd; -} - static __fi void VSyncStart(u32 sCycle) { // End-of-frame tasks. DoFMVSwitch(); VMManager::Internal::VSyncOnCPUThread(); - frameLimit(); // limit FPS + // Don't bother throttling if we're going to pause. + if (!VMManager::Internal::IsExecutionInterrupted()) + VMManager::Internal::Throttle(); + gsPostVsyncStart(); // MUST be after framelimit; doing so before causes funk with frame times! if(EmuConfig.Trace.Enabled && EmuConfig.Trace.EE.m_EnableAll) @@ -628,7 +528,8 @@ static __fi void VSyncStart(u32 sCycle) // Without the patch and fixing this, the games have other issues, so I'm not going to rush to fix it. // Refraction - // Bail out before the next frame starts if we're paused, or the CPU has changed + // Bail out before the next frame starts if we're paused, or the CPU has changed. + // Need to re-check this, because we might've paused during the sleep time. if (VMManager::Internal::IsExecutionInterrupted()) Cpu->ExitExecution(); } diff --git a/pcsx2/Counters.h b/pcsx2/Counters.h index 16de6cc974..782fd7ec99 100644 --- a/pcsx2/Counters.h +++ b/pcsx2/Counters.h @@ -146,5 +146,4 @@ template< uint page > extern bool rcntWrite32( u32 mem, mem32_t& value ); template< uint page > extern u16 rcntRead32( u32 mem ); // returns u16 by design! (see implementation for details) extern void UpdateVSyncRate(bool force); -extern void frameLimitReset(); diff --git a/pcsx2/GS.cpp b/pcsx2/GS.cpp index c52a9e5eed..2878ad480b 100644 --- a/pcsx2/GS.cpp +++ b/pcsx2/GS.cpp @@ -42,38 +42,6 @@ void gsReset() UpdateVSyncRate(true); } -void gsUpdateFrequency(Pcsx2Config& config) -{ - if (config.GS.FrameLimitEnable && - (!config.EnableFastBootFastForward || !VMManager::Internal::IsFastBootInProgress())) - { - switch (config.LimiterMode) - { - case LimiterModeType::Nominal: - config.GS.LimitScalar = config.Framerate.NominalScalar; - break; - case LimiterModeType::Slomo: - config.GS.LimitScalar = config.Framerate.SlomoScalar; - break; - case LimiterModeType::Turbo: - config.GS.LimitScalar = config.Framerate.TurboScalar; - break; - case LimiterModeType::Unlimited: - config.GS.LimitScalar = 0.0f; - break; - default: - pxAssert("Unknown framelimiter mode!"); - } - } - else - { - config.GS.LimitScalar = 0.0f; - } - - MTGS::UpdateVSyncMode(); - UpdateVSyncRate(true); -} - static __fi void gsCSRwrite( const tGS_CSR& csr ) { if (csr.RESET) { diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 49a6db2f11..dbdf02e14a 100644 --- a/pcsx2/GS.h +++ b/pcsx2/GS.h @@ -283,7 +283,6 @@ extern bool gsIsInterlaced; extern void gsReset(); extern void gsSetVideoMode(GS_VideoMode mode); extern void gsPostVsyncStart(); -extern void gsUpdateFrequency(Pcsx2Config& config); extern void gsWrite8(u32 mem, u8 value); extern void gsWrite16(u32 mem, u16 value); diff --git a/pcsx2/GSDumpReplayer.cpp b/pcsx2/GSDumpReplayer.cpp index 781d6d54dc..8c322ad6fb 100644 --- a/pcsx2/GSDumpReplayer.cpp +++ b/pcsx2/GSDumpReplayer.cpp @@ -237,7 +237,7 @@ static void GSDumpReplayerSendPacketToMTGS(GIF_PATH path, const u8* data, u32 le static void GSDumpReplayerUpdateFrameLimit() { constexpr u32 default_frame_limit = 60; - const u32 frame_limit = static_cast(default_frame_limit * EmuConfig.GS.LimitScalar); + const u32 frame_limit = static_cast(default_frame_limit * VMManager::GetTargetSpeed()); if (frame_limit > 0) s_frame_ticks = (GetTickFrequency() + (frame_limit / 2)) / frame_limit; diff --git a/pcsx2/Hotkeys.cpp b/pcsx2/Hotkeys.cpp index 7f2f996bde..dc217baf45 100644 --- a/pcsx2/Hotkeys.cpp +++ b/pcsx2/Hotkeys.cpp @@ -41,12 +41,15 @@ void VMManager::Internal::ResetVMHotkeyState() static void HotkeyAdjustTargetSpeed(double delta) { const double min_speed = Achievements::ChallengeModeActive() ? 1.0 : 0.1; - EmuConfig.Framerate.NominalScalar = std::max(min_speed, EmuConfig.Framerate.NominalScalar + delta); - EmuConfig.LimiterMode = LimiterModeType::Unlimited; // force update - VMManager::SetLimiterMode(LimiterModeType::Nominal); + EmuConfig.EmulationSpeed.NominalScalar = std::max(min_speed, EmuConfig.EmulationSpeed.NominalScalar + delta); + if (VMManager::GetLimiterMode() != LimiterModeType::Nominal) + VMManager::SetLimiterMode(LimiterModeType::Nominal); + else + VMManager::UpdateTargetSpeed(); + Host::AddIconOSDMessage("SpeedChanged", ICON_FA_CLOCK, fmt::format(TRANSLATE_FS("Hotkeys", "Target speed set to {:.0f}%."), - std::round(EmuConfig.Framerate.NominalScalar * 100.0)), + std::round(EmuConfig.EmulationSpeed.NominalScalar * 100.0)), Host::OSD_QUICK_DURATION); } @@ -166,7 +169,7 @@ DEFINE_HOTKEY("ToggleFrameLimit", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE [](s32 pressed) { if (!pressed && VMManager::HasValidVM()) { - VMManager::SetLimiterMode((EmuConfig.LimiterMode != LimiterModeType::Unlimited) ? + VMManager::SetLimiterMode((VMManager::GetLimiterMode() != LimiterModeType::Unlimited) ? LimiterModeType::Unlimited : LimiterModeType::Nominal); } @@ -176,7 +179,7 @@ DEFINE_HOTKEY("ToggleTurbo", TRANSLATE_NOOP("Hotkeys", "System"), if (!pressed && VMManager::HasValidVM()) { VMManager::SetLimiterMode( - (EmuConfig.LimiterMode != LimiterModeType::Turbo) ? LimiterModeType::Turbo : LimiterModeType::Nominal); + (VMManager::GetLimiterMode() != LimiterModeType::Turbo) ? LimiterModeType::Turbo : LimiterModeType::Nominal); } }) DEFINE_HOTKEY("ToggleSlowMotion", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE_NOOP("Hotkeys", "Toggle Slow Motion"), @@ -184,7 +187,7 @@ DEFINE_HOTKEY("ToggleSlowMotion", TRANSLATE_NOOP("Hotkeys", "System"), TRANSLATE if (!pressed && VMManager::HasValidVM()) { VMManager::SetLimiterMode( - (EmuConfig.LimiterMode != LimiterModeType::Slomo) ? LimiterModeType::Slomo : LimiterModeType::Nominal); + (VMManager::GetLimiterMode() != LimiterModeType::Slomo) ? LimiterModeType::Slomo : LimiterModeType::Nominal); } }) DEFINE_HOTKEY("HoldTurbo", TRANSLATE_NOOP("Hotkeys", "System"), diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp index 073c740772..d2eed9826a 100644 --- a/pcsx2/ImGui/FullscreenUI.cpp +++ b/pcsx2/ImGui/FullscreenUI.cpp @@ -1076,7 +1076,7 @@ void FullscreenUI::DoToggleFrameLimit() return; VMManager::SetLimiterMode( - (EmuConfig.LimiterMode != LimiterModeType::Unlimited) ? LimiterModeType::Unlimited : LimiterModeType::Nominal); + (VMManager::GetLimiterMode() != LimiterModeType::Unlimited) ? LimiterModeType::Unlimited : LimiterModeType::Nominal); }); } diff --git a/pcsx2/ImGui/ImGuiOverlays.cpp b/pcsx2/ImGui/ImGuiOverlays.cpp index cadba6f32b..892fada255 100644 --- a/pcsx2/ImGui/ImGuiOverlays.cpp +++ b/pcsx2/ImGui/ImGuiOverlays.cpp @@ -156,11 +156,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y) { fmt::format_to(std::back_inserter(text), "{}{}%", first ? "" : " | ", static_cast(std::round(speed))); - // We read the main config here, since MTGS doesn't get updated with speed toggles. - if (EmuConfig.GS.LimitScalar == 0.0f) + const float target_speed = VMManager::GetTargetSpeed(); + if (target_speed == 0.0f) text += " (Max)"; else - fmt::format_to(std::back_inserter(text), " ({:.0f}%)", EmuConfig.GS.LimitScalar * 100.0f); + fmt::format_to(std::back_inserter(text), " ({:.0f}%)", target_speed * 100.0f); } if (!text.empty()) { @@ -249,10 +249,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y) if (GSConfig.OsdShowIndicators) { - const bool is_normal_speed = (EmuConfig.GS.LimitScalar == EmuConfig.Framerate.NominalScalar); + const float target_speed = VMManager::GetTargetSpeed(); + const bool is_normal_speed = (target_speed == EmuConfig.EmulationSpeed.NominalScalar); if (!is_normal_speed) { - const bool is_slowmo = (EmuConfig.GS.LimitScalar < EmuConfig.Framerate.NominalScalar); + const bool is_slowmo = (target_speed < EmuConfig.EmulationSpeed.NominalScalar); DRAW_LINE(standard_font, is_slowmo ? ICON_FA_FORWARD : ICON_FA_FAST_FORWARD, IM_COL32(255, 255, 255, 255)); } } diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index dec434c86b..c0b331fdda 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -500,7 +500,6 @@ Pcsx2Config::GSOptions::GSOptions() PCRTCOverscan = false; IntegerScaling = false; LinearPresent = GSPostBilinearMode::BilinearSmooth; - SyncToHostRefreshRate = false; UseDebugDevice = false; UseBlitSwapChain = false; DisableShaderCache = false; @@ -563,9 +562,6 @@ bool Pcsx2Config::GSOptions::operator==(const GSOptions& right) const OpEqu(SynchronousMTGS) && OpEqu(VsyncQueueSize) && - OpEqu(FrameLimitEnable) && - - OpEqu(LimitScalar) && OpEqu(FramerateNTSC) && OpEqu(FrameratePAL) && @@ -686,14 +682,11 @@ void Pcsx2Config::GSOptions::LoadSave(SettingsWrapper& wrap) #endif SettingsWrapEntry(VsyncQueueSize); - SettingsWrapEntry(FrameLimitEnable); wrap.EnumEntry(CURRENT_SETTINGS_SECTION, "VsyncEnable", VsyncEnable, NULL, VsyncEnable); - // LimitScalar is set at runtime. SettingsWrapEntry(FramerateNTSC); SettingsWrapEntry(FrameratePAL); - SettingsWrapBitBool(SyncToHostRefreshRate); SettingsWrapEnumEx(AspectRatio, "AspectRatio", AspectRatioNames); SettingsWrapEnumEx(FMVAspectRatioSwitch, "FMVAspectRatioSwitch", FMVAspectRatioSwitchNames); SettingsWrapIntEnumEx(ScreenshotSize, "ScreenshotSize"); @@ -1270,7 +1263,15 @@ void Pcsx2Config::FilenameOptions::LoadSave(SettingsWrapper& wrap) wrap.Entry(CURRENT_SETTINGS_SECTION, "BIOS", Bios, Bios); } -void Pcsx2Config::FramerateOptions::SanityCheck() +Pcsx2Config::EmulationSpeedOptions::EmulationSpeedOptions() +{ + bitset = 0; + + FrameLimitEnable = true; + SyncToHostRefreshRate = false; +} + +void Pcsx2Config::EmulationSpeedOptions::SanityCheck() { // Ensure Conformation of various options... @@ -1279,13 +1280,29 @@ void Pcsx2Config::FramerateOptions::SanityCheck() SlomoScalar = std::clamp(SlomoScalar, 0.05f, 10.0f); } -void Pcsx2Config::FramerateOptions::LoadSave(SettingsWrapper& wrap) +void Pcsx2Config::EmulationSpeedOptions::LoadSave(SettingsWrapper& wrap) { SettingsWrapSection("Framerate"); SettingsWrapEntry(NominalScalar); SettingsWrapEntry(TurboScalar); SettingsWrapEntry(SlomoScalar); + + // This was in the wrong place... but we can't change it without breaking existing configs. + //SettingsWrapBitBool(FrameLimitEnable); + //SettingsWrapBitBool(SyncToHostRefreshRate); + FrameLimitEnable = wrap.EntryBitBool("EmuCore/GS", "FrameLimitEnable", FrameLimitEnable, FrameLimitEnable); + SyncToHostRefreshRate = wrap.EntryBitBool("EmuCore/GS", "SyncToHostRefreshRate", SyncToHostRefreshRate, SyncToHostRefreshRate); +} + +bool Pcsx2Config::EmulationSpeedOptions::operator==(const EmulationSpeedOptions& right) const +{ + return OpEqu(bitset) && OpEqu(NominalScalar) && OpEqu(TurboScalar) && OpEqu(SlomoScalar); +} + +bool Pcsx2Config::EmulationSpeedOptions::operator!=(const EmulationSpeedOptions& right) const +{ + return !this->operator==(right); } Pcsx2Config::USBOptions::USBOptions() @@ -1543,7 +1560,7 @@ void Pcsx2Config::LoadSaveCore(SettingsWrapper& wrap) SettingsWrapEntryEx(CurrentBlockdump, "BlockDumpSaveDirectory"); BaseFilenames.LoadSave(wrap); - Framerate.LoadSave(wrap); + EmulationSpeed.LoadSave(wrap); LoadSaveMemcards(wrap); #ifdef _WIN32 @@ -1600,39 +1617,12 @@ std::string Pcsx2Config::FullpathToMcd(uint slot) const return Path::Combine(EmuFolders::MemoryCards, Mcd[slot].Filename); } -bool Pcsx2Config::operator==(const Pcsx2Config& right) const -{ - bool equal = - OpEqu(bitset) && - OpEqu(Cpu) && - OpEqu(GS) && - OpEqu(DEV9) && - OpEqu(Speedhacks) && - OpEqu(Gamefixes) && - OpEqu(Profiler) && - OpEqu(Debugger) && - OpEqu(Framerate) && - OpEqu(Trace) && - OpEqu(BaseFilenames) && - OpEqu(GzipIsoIndexTemplate) && - OpEqu(PINESlot); - for (u32 i = 0; i < sizeof(Mcd) / sizeof(Mcd[0]); i++) - { - equal &= OpEqu(Mcd[i].Enabled); - equal &= OpEqu(Mcd[i].Filename); - } - - return equal; -} - void Pcsx2Config::CopyRuntimeConfig(Pcsx2Config& cfg) { - GS.LimitScalar = cfg.GS.LimitScalar; CurrentBlockdump = std::move(cfg.CurrentBlockdump); CurrentIRX = std::move(cfg.CurrentIRX); CurrentGameArgs = std::move(cfg.CurrentGameArgs); CurrentAspectRatio = cfg.CurrentAspectRatio; - LimiterMode = cfg.LimiterMode; for (u32 i = 0; i < sizeof(Mcd) / sizeof(Mcd[0]); i++) { diff --git a/pcsx2/PerformanceMetrics.cpp b/pcsx2/PerformanceMetrics.cpp index 86e2db8713..475b6e0d11 100644 --- a/pcsx2/PerformanceMetrics.cpp +++ b/pcsx2/PerformanceMetrics.cpp @@ -32,7 +32,6 @@ static const float UPDATE_INTERVAL = 0.5f; -static float s_vertical_frequency = 0.0f; static float s_fps = 0.0f; static float s_internal_fps = 0.0f; static float s_minimum_frame_time = 0.0f; @@ -268,11 +267,6 @@ void PerformanceMetrics::SetGSSWThread(u32 index, Threading::ThreadHandle thread s_gs_sw_threads[index].handle = std::move(thread); } -void PerformanceMetrics::SetVerticalFrequency(float rate) -{ - s_vertical_frequency = rate; -} - u64 PerformanceMetrics::GetFrameNumber() { return s_frame_number; @@ -300,7 +294,7 @@ float PerformanceMetrics::GetInternalFPS() float PerformanceMetrics::GetSpeed() { - return (s_fps / s_vertical_frequency) * 100.0; + return (s_fps / VMManager::GetFrameRate()) * 100.0; } float PerformanceMetrics::GetAverageFrameTime() diff --git a/pcsx2/PerformanceMetrics.h b/pcsx2/PerformanceMetrics.h index ca0b613049..1d39b17d23 100644 --- a/pcsx2/PerformanceMetrics.h +++ b/pcsx2/PerformanceMetrics.h @@ -42,9 +42,6 @@ namespace PerformanceMetrics void SetGSSWThreadCount(u32 count); void SetGSSWThread(u32 index, Threading::ThreadHandle thread); - /// Sets the vertical frequency, used in speed calculations. - void SetVerticalFrequency(float rate); - u64 GetFrameNumber(); InternalFPSMethod GetInternalFPSMethod(); diff --git a/pcsx2/SPU2/SndOut.cpp b/pcsx2/SPU2/SndOut.cpp index 101d45bee8..36158177c3 100644 --- a/pcsx2/SPU2/SndOut.cpp +++ b/pcsx2/SPU2/SndOut.cpp @@ -445,6 +445,11 @@ bool SndBuffer::Init(const char* modname) return true; } +bool SndBuffer::IsOpen() +{ + return (s_output_module != nullptr); +} + void SndBuffer::Cleanup() { if (s_output_module) diff --git a/pcsx2/SPU2/SndOut.h b/pcsx2/SPU2/SndOut.h index d6da6a7b4b..3b936fddde 100644 --- a/pcsx2/SPU2/SndOut.h +++ b/pcsx2/SPU2/SndOut.h @@ -304,6 +304,7 @@ namespace SndBuffer { void UpdateTempoChangeAsyncMixing(); bool Init(const char* modname); + bool IsOpen(); void Cleanup(); void Write(StereoOut16 Sample); void ClearContents(); diff --git a/pcsx2/SPU2/SndOut_Cubeb.cpp b/pcsx2/SPU2/SndOut_Cubeb.cpp index 6a13ba1794..71c6c6404a 100644 --- a/pcsx2/SPU2/SndOut_Cubeb.cpp +++ b/pcsx2/SPU2/SndOut_Cubeb.cpp @@ -20,6 +20,7 @@ #include "Host.h" #include "IconsFontAwesome5.h" +#include "common/Assertions.h" #include "common/Console.h" #include "common/StringUtil.h" #include "common/RedtapeWindows.h" @@ -136,6 +137,9 @@ public: bool Init() override { + if (stream) + pxFailRel("Cubeb stream already open in Init()"); + #ifdef _WIN32 const HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); m_COMInitializedByUs = SUCCEEDED(hr); diff --git a/pcsx2/SPU2/spu2.cpp b/pcsx2/SPU2/spu2.cpp index 661904aa3f..033c4f1073 100644 --- a/pcsx2/SPU2/spu2.cpp +++ b/pcsx2/SPU2/spu2.cpp @@ -174,7 +174,8 @@ void SPU2::SetDeviceSampleRateMultiplier(double multiplier) return; s_device_sample_rate_multiplier = multiplier; - UpdateSampleRate(); + if (SndBuffer::IsOpen()) + UpdateSampleRate(); } bool SPU2::Open() diff --git a/pcsx2/VMManager.cpp b/pcsx2/VMManager.cpp index f0745b4361..8c90091acd 100644 --- a/pcsx2/VMManager.cpp +++ b/pcsx2/VMManager.cpp @@ -96,7 +96,7 @@ namespace VMManager static void CheckForConfigChanges(const Pcsx2Config& old_config); static void CheckForCPUConfigChanges(const Pcsx2Config& old_config); static void CheckForGSConfigChanges(const Pcsx2Config& old_config); - static void CheckForFramerateConfigChanges(const Pcsx2Config& old_config); + static void CheckForEmulationSpeedConfigChanges(const Pcsx2Config& old_config); static void CheckForPatchConfigChanges(const Pcsx2Config& old_config); static void CheckForDEV9ConfigChanges(const Pcsx2Config& old_config); static void CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config); @@ -104,7 +104,6 @@ namespace VMManager static void EnforceAchievementsChallengeModeSettings(); static void LogUnsafeSettingsToConsole(const std::string& messages); static void WarnAboutUnsafeSettings(); - static void ResetFrameLimiterState(); static bool AutoDetectSource(const std::string& filename); static void UpdateDiscDetails(bool booting); @@ -133,6 +132,11 @@ namespace VMManager static void SaveSessionTime(const std::string& prev_serial); static void ReloadPINE(); + static LimiterModeType GetInitialLimiterMode(); + static float GetTargetSpeedForLimiterMode(LimiterModeType mode); + static void ResetFrameLimiter(); + static double AdjustToHostRefreshRate(float frame_rate, float target_speed); + static void SetTimerResolutionIncreased(bool enabled); static void SetHardwareDependentDefaultSettings(SettingsInterface& si); static void EnsureCPUInfoInitialized(); @@ -175,6 +179,12 @@ static u32 s_mxcsr_saved; static bool s_fast_boot_requested = false; static bool s_gs_open_on_initialize = false; +static LimiterModeType s_limiter_mode = LimiterModeType::Nominal; +static s64 s_limiter_ticks_per_frame = 0; +static u64 s_limiter_frame_start = 0; +static float s_target_speed = 0.0f; +static bool s_use_vsync_for_timing = false; + // Used to track play time. We use a monotonic timer here, in case of clock changes. static u64 s_session_start_time = 0; @@ -247,7 +257,7 @@ void VMManager::SetState(VMState state) else { PerformanceMetrics::Reset(); - frameLimitReset(); + ResetFrameLimiter(); } SPU2::SetOutputPaused(paused); @@ -997,11 +1007,6 @@ bool VMManager::HasBootedELF() return s_current_crc != 0 && s_elf_executed; } -static LimiterModeType GetInitialLimiterMode() -{ - return EmuConfig.GS.FrameLimitEnable ? LimiterModeType::Nominal : LimiterModeType::Unlimited; -} - bool VMManager::AutoDetectSource(const std::string& filename) { if (!filename.empty()) @@ -1191,7 +1196,9 @@ bool VMManager::Initialize(VMBootParameters boot_params) } #endif - EmuConfig.LimiterMode = GetInitialLimiterMode(); + s_limiter_mode = GetInitialLimiterMode(); + s_target_speed = GetTargetSpeedForLimiterMode(s_limiter_mode); + s_use_vsync_for_timing = false; Console.WriteLn("Opening GS..."); s_gs_open_on_initialize = MTGS::IsOpen(); @@ -1297,8 +1304,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) SysClearExecutionCache(); memBindConditionalHandlers(); - gsUpdateFrequency(EmuConfig); - frameLimitReset(); cpuReset(); Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds()); @@ -1309,8 +1314,6 @@ bool VMManager::Initialize(VMBootParameters boot_params) SetEmuThreadAffinities(); - PerformanceMetrics::Clear(); - // do we want to load state? if (!GSDumpReplayer::IsReplayingDump() && !state_to_load.empty()) { @@ -1321,6 +1324,7 @@ bool VMManager::Initialize(VMBootParameters boot_params) } } + PerformanceMetrics::Clear(); return true; } @@ -1443,8 +1447,6 @@ void VMManager::Reset() SysClearExecutionCache(); memBindConditionalHandlers(); - UpdateVSyncRate(true); - frameLimitReset(); cpuReset(); if (g_InputRecording.isActive()) @@ -1453,6 +1455,8 @@ void VMManager::Reset() MTGS::PresentCurrentFrame(); } + ResetFrameLimiter(); + // If we were paused, state won't be resetting, so don't flip back to running. if (s_state.load(std::memory_order_acquire) == VMState::Resetting) s_state.store(VMState::Running, std::memory_order_release); @@ -1747,17 +1751,160 @@ bool VMManager::SaveStateToSlot(s32 slot, bool zip_on_thread) LimiterModeType VMManager::GetLimiterMode() { - return EmuConfig.LimiterMode; + return s_limiter_mode; } void VMManager::SetLimiterMode(LimiterModeType type) { - if (EmuConfig.LimiterMode == type) + if (s_limiter_mode == type) return; - EmuConfig.LimiterMode = type; - gsUpdateFrequency(EmuConfig); - SPU2::OnTargetSpeedChanged(); + s_limiter_mode = type; + UpdateTargetSpeed(); +} + +float VMManager::GetTargetSpeed() +{ + return s_target_speed; +} + +LimiterModeType VMManager::GetInitialLimiterMode() +{ + return EmuConfig.EmulationSpeed.FrameLimitEnable ? LimiterModeType::Nominal : LimiterModeType::Unlimited; +} + +double VMManager::AdjustToHostRefreshRate(float frame_rate, float target_speed) +{ + if (!EmuConfig.EmulationSpeed.SyncToHostRefreshRate || target_speed != 1.0f) + { + SPU2::SetDeviceSampleRateMultiplier(1.0); + s_use_vsync_for_timing = false; + return target_speed; + } + + float host_refresh_rate; + if (!GSGetHostRefreshRate(&host_refresh_rate)) + { + Console.Warning("Cannot sync to host refresh since the query failed."); + SPU2::SetDeviceSampleRateMultiplier(1.0); + s_use_vsync_for_timing = false; + return target_speed; + } + + const double ratio = host_refresh_rate / frame_rate; + const bool syncing_to_host = (ratio >= 0.95f && ratio <= 1.05f); + s_use_vsync_for_timing = (syncing_to_host && !EmuConfig.GS.SkipDuplicateFrames && EmuConfig.GS.VsyncEnable != VsyncMode::Off); + Console.WriteLn("Refresh rate: Host=%fhz Guest=%fhz Ratio=%f - %s %s", host_refresh_rate, frame_rate, ratio, + syncing_to_host ? "can sync" : "can't sync", s_use_vsync_for_timing ? "and using vsync for pacing" : "and using sleep for pacing"); + + if (!syncing_to_host) + return target_speed; + + target_speed *= ratio; + SPU2::SetDeviceSampleRateMultiplier(ratio); + return target_speed; +} + +float VMManager::GetTargetSpeedForLimiterMode(LimiterModeType mode) +{ + if (EmuConfig.EmulationSpeed.FrameLimitEnable && (!EmuConfig.EnableFastBootFastForward || !VMManager::Internal::IsFastBootInProgress())) + { + switch (s_limiter_mode) + { + case LimiterModeType::Nominal: + return EmuConfig.EmulationSpeed.NominalScalar; + + case LimiterModeType::Slomo: + return EmuConfig.EmulationSpeed.SlomoScalar; + + case LimiterModeType::Turbo: + return EmuConfig.EmulationSpeed.TurboScalar; + + case LimiterModeType::Unlimited: + return 0.0f; + + jNO_DEFAULT + } + } + + return 0.0f; +} + +void VMManager::UpdateTargetSpeed() +{ + const float frame_rate = GetFrameRate(); + const float target_speed = AdjustToHostRefreshRate(frame_rate, GetTargetSpeedForLimiterMode(s_limiter_mode)); + const float target_frame_rate = frame_rate * target_speed; + + s_limiter_ticks_per_frame = + static_cast(static_cast(GetTickFrequency()) / static_cast(std::max(frame_rate * target_speed, 1.0f))); + + DevCon.WriteLn(fmt::format("Frame rate: {}, target speed: {}, target frame rate: {}, ticks per frame: {}", frame_rate, target_speed, + target_frame_rate, s_limiter_ticks_per_frame)); + + if (s_target_speed != target_speed) + { + s_target_speed = target_speed; + + MTGS::UpdateVSyncMode(); + SPU2::OnTargetSpeedChanged(); + ResetFrameLimiter(); + } +} + +float VMManager::GetFrameRate() +{ + return GetVerticalFrequency(); +} + +void VMManager::ResetFrameLimiter() +{ + s_limiter_frame_start = GetCPUTicks(); +} + +void VMManager::Internal::Throttle() +{ + if (s_target_speed == 0.0f || s_use_vsync_for_timing) + return; + + const u64 uExpectedEnd = + s_limiter_frame_start + + s_limiter_ticks_per_frame; // Compute when we would expect this frame to end, assuming everything goes perfectly perfect. + const u64 iEnd = GetCPUTicks(); // The current tick we actually stopped on. + const s64 sDeltaTime = iEnd - uExpectedEnd; // The diff between when we stopped and when we expected to. + + // If frame ran too long... + if (sDeltaTime >= s_limiter_ticks_per_frame) + { + // ... Fudge the next frame start over a bit. Prevents fast forward zoomies. + s_limiter_frame_start += (sDeltaTime / s_limiter_ticks_per_frame) * s_limiter_ticks_per_frame; + return; + } + + // Conversion of delta from CPU ticks (microseconds) to milliseconds + const s32 msec = static_cast((sDeltaTime * -1000) / static_cast(GetTickFrequency())); + + // If any integer value of milliseconds exists, sleep it off. + // Prior comments suggested that 1-2 ms sleeps were inaccurate on some OSes; + // further testing suggests instead that this was utter bullshit. + if (msec > 1) + { + Threading::Sleep(msec - 1); + } + + // Conversion to milliseconds loses some precision; after sleeping off whole milliseconds, + // spin the thread without sleeping until we finally reach our expected end time. + while (GetCPUTicks() < uExpectedEnd) + { + } + + // Finally, set our next frame start to when this one ends + s_limiter_frame_start = uExpectedEnd; +} + +void VMManager::Internal::FrameRateChanged() +{ + UpdateTargetSpeed(); } void VMManager::FrameAdvance(u32 num_frames /*= 1*/) @@ -1904,7 +2051,7 @@ VsyncMode Host::GetEffectiveVSyncMode() const bool has_vm = VMManager::GetState() != VMState::Shutdown; // Force vsync off when not running at 100% speed. - if (has_vm && EmuConfig.GS.LimitScalar != 1.0f) + if (has_vm && (s_target_speed != 1.0f && !s_use_vsync_for_timing)) return VsyncMode::Off; // Otherwise use the config setting. @@ -1925,7 +2072,7 @@ void VMManager::Internal::DisableFastBoot() // Stop fast forwarding boot if enabled. if (EmuConfig.EnableFastBootFastForward && !s_elf_executed) - ResetFrameLimiterState(); + UpdateTargetSpeed(); } bool VMManager::Internal::HasBootedELF() @@ -1978,7 +2125,7 @@ void VMManager::Internal::EntryPointCompilingOnCPUThread() if (reset_speed_limiter) { - ResetFrameLimiterState(); + UpdateTargetSpeed(); PerformanceMetrics::Reset(); } @@ -2077,22 +2224,30 @@ void VMManager::CheckForGSConfigChanges(const Pcsx2Config& old_config) Console.WriteLn("Updating GS configuration..."); - if (EmuConfig.GS.FrameLimitEnable != old_config.GS.FrameLimitEnable) - EmuConfig.LimiterMode = GetInitialLimiterMode(); + // We could just check whichever NTSC or PAL is appropriate for our current mode, + // but people _really_ shouldn't be screwing with framerate, so whatever. + if (EmuConfig.GS.FramerateNTSC != old_config.GS.FramerateNTSC || + EmuConfig.GS.FrameratePAL != old_config.GS.FrameratePAL) + { + UpdateVSyncRate(false); + UpdateTargetSpeed(); + } + else if (EmuConfig.GS.VsyncEnable != old_config.GS.VsyncEnable) + { + // Still need to update target speed, because of sync-to-host-refresh. + UpdateTargetSpeed(); + } - ResetFrameLimiterState(); MTGS::ApplySettings(); } -void VMManager::CheckForFramerateConfigChanges(const Pcsx2Config& old_config) +void VMManager::CheckForEmulationSpeedConfigChanges(const Pcsx2Config& old_config) { - if (EmuConfig.Framerate == old_config.Framerate) + if (EmuConfig.EmulationSpeed == old_config.EmulationSpeed) return; - Console.WriteLn("Updating frame rate configuration"); - gsUpdateFrequency(EmuConfig); - UpdateVSyncRate(true); - frameLimitReset(); + Console.WriteLn("Updating emulation speed configuration"); + UpdateTargetSpeed(); } void VMManager::CheckForPatchConfigChanges(const Pcsx2Config& old_config) @@ -2176,7 +2331,7 @@ void VMManager::CheckForMiscConfigChanges(const Pcsx2Config& old_config) if (EmuConfig.EnableFastBootFastForward && !old_config.EnableFastBootFastForward && VMManager::Internal::IsFastBootInProgress()) { - ResetFrameLimiterState(); + UpdateTargetSpeed(); } if (EmuConfig.InhibitScreensaver != old_config.InhibitScreensaver) @@ -2196,7 +2351,7 @@ void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config) if (HasValidVM()) { CheckForCPUConfigChanges(old_config); - CheckForFramerateConfigChanges(old_config); + CheckForEmulationSpeedConfigChanges(old_config); CheckForPatchConfigChanges(old_config); SPU2::CheckForConfigChanges(old_config); CheckForDEV9ConfigChanges(old_config); @@ -2219,13 +2374,6 @@ void VMManager::CheckForConfigChanges(const Pcsx2Config& old_config) Host::CheckForSettingsChanges(old_config); } -void VMManager::ResetFrameLimiterState() -{ - gsUpdateFrequency(EmuConfig); - UpdateVSyncRate(true); - frameLimitReset(); -} - void VMManager::ReloadPatches(bool reload_files, bool reload_enabled_list, bool verbose, bool verbose_if_changed) { if (!HasValidVM()) @@ -2252,9 +2400,9 @@ void VMManager::EnforceAchievementsChallengeModeSettings() }; // Can't use slow motion. - ClampSpeed(EmuConfig.Framerate.NominalScalar); - ClampSpeed(EmuConfig.Framerate.TurboScalar); - ClampSpeed(EmuConfig.Framerate.SlomoScalar); + ClampSpeed(EmuConfig.EmulationSpeed.NominalScalar); + ClampSpeed(EmuConfig.EmulationSpeed.TurboScalar); + ClampSpeed(EmuConfig.EmulationSpeed.SlomoScalar); // Can't use cheats. if (EmuConfig.EnableCheats) diff --git a/pcsx2/VMManager.h b/pcsx2/VMManager.h index 191b98f242..5701c5aef2 100644 --- a/pcsx2/VMManager.h +++ b/pcsx2/VMManager.h @@ -18,10 +18,8 @@ #include #include #include -#include -#include #include -#include +#include #include "common/Pcsx2Defs.h" @@ -149,6 +147,16 @@ namespace VMManager /// Updates the host vsync state, as well as timer frequencies. Call when the speed limiter is adjusted. void SetLimiterMode(LimiterModeType type); + /// Returns the target speed, based on the limiter mode. + float GetTargetSpeed(); + + /// Ensures the target speed reflects the current configuration. Call if you change anything in + /// EmuConfig.EmulationSpeed without going through the usual config apply. + void UpdateTargetSpeed(); + + /// Returns the current frame rate of the virtual machine. + float GetFrameRate(); + /// Runs the virtual machine for the specified number of video frames, and then automatically pauses. void FrameAdvance(u32 num_frames = 1); @@ -236,6 +244,12 @@ namespace VMManager /// Returns the PC of the currently-executing ELF's entry point. u32 GetCurrentELFEntryPoint(); + /// Called when the internal frame rate changes. + void FrameRateChanged(); + + /// Throttles execution, or limits the frame rate. + void Throttle(); + const std::string& GetELFOverride(); bool IsExecutionInterrupted(); void ELFLoadingOnCPUThread(std::string elf_path);