VMManager: Rewrite frame limiting

No more messing with counter state on setting changes.

Closes #9929.
This commit is contained in:
Stenzek 2023-09-09 19:44:06 +10:00 committed by Connor McLaughlin
parent 5df6fc4c1b
commit abe64ae8fb
18 changed files with 287 additions and 269 deletions

View File

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

View File

@ -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 <sys/time.h>
#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<double>(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<s64>(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();
}

View File

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

View File

@ -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) {

View File

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

View File

@ -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<u32>(default_frame_limit * EmuConfig.GS.LimitScalar);
const u32 frame_limit = static_cast<u32>(default_frame_limit * VMManager::GetTargetSpeed());
if (frame_limit > 0)
s_frame_ticks = (GetTickFrequency() + (frame_limit / 2)) / frame_limit;

View File

@ -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"),

View File

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

View File

@ -156,11 +156,11 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y)
{
fmt::format_to(std::back_inserter(text), "{}{}%", first ? "" : " | ", static_cast<u32>(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));
}
}

View File

@ -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++)
{

View File

@ -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()

View File

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

View File

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

View File

@ -304,6 +304,7 @@ namespace SndBuffer
{
void UpdateTempoChangeAsyncMixing();
bool Init(const char* modname);
bool IsOpen();
void Cleanup();
void Write(StereoOut16 Sample);
void ClearContents();

View File

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

View File

@ -174,7 +174,8 @@ void SPU2::SetDeviceSampleRateMultiplier(double multiplier)
return;
s_device_sample_rate_multiplier = multiplier;
UpdateSampleRate();
if (SndBuffer::IsOpen())
UpdateSampleRate();
}
bool SPU2::Open()

View File

@ -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<s64>(static_cast<double>(GetTickFrequency()) / static_cast<double>(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<s32>((sDeltaTime * -1000) / static_cast<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)
{
}
// 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)

View File

@ -18,10 +18,8 @@
#include <functional>
#include <optional>
#include <string>
#include <vector>
#include <string>
#include <string_view>
#include <optional>
#include <vector>
#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);