diff --git a/src/common/Timer.cpp b/src/common/Timer.cpp index 15b2a22e1..e8f14b4c8 100644 --- a/src/common/Timer.cpp +++ b/src/common/Timer.cpp @@ -170,3 +170,37 @@ void Timer_Init() #error "Unsupported OS" #endif } + +// ****************************************************************** + +void ScaledPerformanceCounter::Reset(uint32_t frequency) +{ + std::lock_guard lock(m_mutex); + + m_frequencyFactor = Muldiv64(HostClockFrequency, SCALE_S_IN_NS, frequency); + + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); + m_lastQPC = tsc.QuadPart; + + m_currentCount = 0; + m_currentRemainder = 0; +} + +uint64_t ScaledPerformanceCounter::Tick() +{ + std::lock_guard lock(m_mutex); + + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + + int64_t lastQpc = std::exchange(m_lastQPC, qpc.QuadPart); + qpc.QuadPart -= lastQpc; + qpc.QuadPart *= SCALE_S_IN_NS; + qpc.QuadPart += m_currentRemainder; + uint64_t quotient = qpc.QuadPart / m_frequencyFactor; + uint64_t remainder = qpc.QuadPart % m_frequencyFactor; + + m_currentRemainder = remainder; + return m_currentCount += quotient; +} diff --git a/src/common/Timer.h b/src/common/Timer.h index 3ca073241..7324c7aa7 100644 --- a/src/common/Timer.h +++ b/src/common/Timer.h @@ -29,6 +29,7 @@ #define TIMER_H #include +#include #define SCALE_S_IN_NS 1000000000 #define SCALE_MS_IN_NS 1000000 @@ -63,4 +64,23 @@ void Timer_ChangeExpireTime(TimerObject* Timer, uint64_t Expire_ms); uint64_t GetTime_NS(TimerObject* Timer); void Timer_Init(); +// A stateful replacement for QueryPerformanceCounter, ticking at an arbitrary frequency +// Thread-safe and designed to avoid overflows at all cost +class ScaledPerformanceCounter +{ +public: + ScaledPerformanceCounter() = default; + void Reset(uint32_t frequency); + uint64_t Tick(); + +private: + std::mutex m_mutex; + + uint64_t m_frequencyFactor; + int64_t m_lastQPC; + + uint64_t m_currentCount; + uint64_t m_currentRemainder; +}; + #endif diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index 2205ec2cf..25de7da9b 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -32,6 +32,7 @@ #include #include #include "DirectSoundGlobal.hpp" // Global variables +#include #include "Logging.h" #include "DirectSoundLogging.hpp" @@ -52,42 +53,16 @@ // TODO: Move these to LLE APUDevice once we have one! static constexpr uint32_t APU_TIMER_FREQUENCY = 48000; -static constexpr uint32_t SEC_TO_NSEC = 1000000000; // For seconds -> nanoseconds conversions -static uint64_t NativeToXbox_FactorForApu = 0; - -static LARGE_INTEGER LastApuQPC; -static DWORD CurrentApu = 0; -static ULONGLONG CurrentApuRemainder = 0; // Must be 64-bit because it stores a nanosecond precision remainder +static ScaledPerformanceCounter ApuCounter; void ResetApuTimer() { - // Measure current host performance counter and frequency - LARGE_INTEGER HostClockFrequency; - QueryPerformanceFrequency(&HostClockFrequency); - - NativeToXbox_FactorForApu = Muldiv64(HostClockFrequency.QuadPart, SEC_TO_NSEC, APU_TIMER_FREQUENCY); - - LARGE_INTEGER tsc; - QueryPerformanceCounter(&tsc); - LastApuQPC = tsc; - CurrentApu = 0; - CurrentApuRemainder = 0; + ApuCounter.Reset(APU_TIMER_FREQUENCY); } uint32_t GetAPUTime() { - LARGE_INTEGER tsc; - QueryPerformanceCounter(&tsc); - - LARGE_INTEGER lastTsc = std::exchange(LastApuQPC, tsc); - tsc.QuadPart -= lastTsc.QuadPart; - tsc.QuadPart *= SEC_TO_NSEC; - tsc.QuadPart += CurrentApuRemainder; - DWORD quotient = static_cast(tsc.QuadPart / NativeToXbox_FactorForApu); - ULONGLONG remainder = tsc.QuadPart % NativeToXbox_FactorForApu; - - CurrentApuRemainder = remainder; - return CurrentApu += quotient; + return static_cast(ApuCounter.Tick()); } diff --git a/src/core/kernel/exports/EmuKrnlKe.cpp b/src/core/kernel/exports/EmuKrnlKe.cpp index dcf3808ea..05bad2237 100644 --- a/src/core/kernel/exports/EmuKrnlKe.cpp +++ b/src/core/kernel/exports/EmuKrnlKe.cpp @@ -326,47 +326,14 @@ void InitDpcThread() static constexpr uint32_t XBOX_TSC_FREQUENCY = 733333333; // Xbox Time Stamp Counter Frequency = 733333333 (CPU Clock) static constexpr uint32_t XBOX_ACPI_FREQUENCY = 3375000; // Xbox ACPI frequency (3.375 mhz) -static constexpr uint32_t SEC_TO_NSEC = 1000000000; // For seconds -> nanoseconds conversions -static uint64_t NativeToXbox_FactorForRdtsc = 0, NativeToXbox_FactorForAcpi = 0; - -// State for CxbxGetPerformanceCounter - concurrent access should be next to non-existent, but secure against it anyway -static std::mutex RdtscLock, AcpiLock; -static LARGE_INTEGER LastRdtscQPC, LastAcpiQPC; -static ULONGLONG CurrentRdtsc = 0, CurrentAcpi = 0; -static ULONGLONG CurrentRdtscRemainder = 0, CurrentAcpiRemainder = 0; +static ScaledPerformanceCounter TscCounter, AcpiCounter; ULONGLONG CxbxGetPerformanceCounter(bool acpi) { - if (acpi == false && NativeToXbox_FactorForRdtsc != 0) { - std::lock_guard lock(RdtscLock); - - LARGE_INTEGER tsc; - QueryPerformanceCounter(&tsc); - - LARGE_INTEGER lastTsc = std::exchange(LastRdtscQPC, tsc); - tsc.QuadPart -= lastTsc.QuadPart; - tsc.QuadPart *= SEC_TO_NSEC; - tsc.QuadPart += CurrentRdtscRemainder; - ULONGLONG quotient = tsc.QuadPart / NativeToXbox_FactorForRdtsc; - ULONGLONG remainder = tsc.QuadPart % NativeToXbox_FactorForRdtsc; - - CurrentRdtscRemainder = remainder; - return CurrentRdtsc += quotient; - } else if (acpi == true && NativeToXbox_FactorForAcpi != 0) { - std::lock_guard lock(AcpiLock); - - LARGE_INTEGER tsc; - QueryPerformanceCounter(&tsc); - - LARGE_INTEGER lastTsc = std::exchange(LastAcpiQPC, tsc); - tsc.QuadPart -= lastTsc.QuadPart; - tsc.QuadPart *= SEC_TO_NSEC; - tsc.QuadPart += CurrentAcpiRemainder; - ULONGLONG quotient = tsc.QuadPart / NativeToXbox_FactorForAcpi; - ULONGLONG remainder = tsc.QuadPart % NativeToXbox_FactorForAcpi; - - CurrentAcpiRemainder = remainder; - return CurrentAcpi += quotient; + if (acpi == false) { + return TscCounter.Tick(); + } else if (acpi == true) { + return AcpiCounter.Tick(); } LARGE_INTEGER tsc; @@ -376,12 +343,8 @@ ULONGLONG CxbxGetPerformanceCounter(bool acpi) void CxbxInitPerformanceCounters() { - NativeToXbox_FactorForRdtsc = Muldiv64(HostClockFrequency, SEC_TO_NSEC, XBOX_TSC_FREQUENCY); - NativeToXbox_FactorForAcpi = Muldiv64(HostClockFrequency, SEC_TO_NSEC, XBOX_ACPI_FREQUENCY); - - LARGE_INTEGER tsc; - QueryPerformanceCounter(&tsc); - LastRdtscQPC = LastAcpiQPC = tsc; + TscCounter.Reset(XBOX_TSC_FREQUENCY); + AcpiCounter.Reset(XBOX_ACPI_FREQUENCY); // Let's initialize the Dpc handling thread too, // here for now (should be called by our caller)