From 0f88b77bfe5632753a5bf8ae7e67d139d3b106cd Mon Sep 17 00:00:00 2001 From: Silent Date: Sat, 24 Oct 2020 13:09:37 +0200 Subject: [PATCH 1/2] Fix APU timer ticking at wrong frequency --- .../hle/DSOUND/DirectSound/DirectSound.cpp | 42 +++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp index 1aa2d40aa..2205ec2cf 100644 --- a/src/core/hle/DSOUND/DirectSound/DirectSound.cpp +++ b/src/core/hle/DSOUND/DirectSound/DirectSound.cpp @@ -51,27 +51,43 @@ // Temporary APU Timer Functions // TODO: Move these to LLE APUDevice once we have one! -#define APU_TIMER_FREQUENCY 48000 -LARGE_INTEGER APUInitialPerformanceCounter; -double NativeToXboxAPU_FactorForPerformanceFrequency = 0; +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 void ResetApuTimer() { - // Measure current host performance counter and frequency - QueryPerformanceCounter(&APUInitialPerformanceCounter); - NativeToXboxAPU_FactorForPerformanceFrequency = (double)APU_TIMER_FREQUENCY / APUInitialPerformanceCounter.QuadPart; + // 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; } uint32_t GetAPUTime() { - ::LARGE_INTEGER PerformanceCounter; - QueryPerformanceCounter(&PerformanceCounter); + LARGE_INTEGER tsc; + QueryPerformanceCounter(&tsc); - // Re-Base on the time DirectSoundCreate was called - PerformanceCounter.QuadPart -= APUInitialPerformanceCounter.QuadPart; - // Apply a delta to make it appear to tick at 48khz - PerformanceCounter.QuadPart = (ULONGLONG)(NativeToXboxAPU_FactorForPerformanceFrequency * PerformanceCounter.QuadPart); - return (DWORD)PerformanceCounter.QuadPart; + 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; } From d5adbb2ab31d809ea7899c113d28b3c850e9c1f1 Mon Sep 17 00:00:00 2001 From: Silent Date: Sat, 24 Oct 2020 23:53:15 +0200 Subject: [PATCH 2/2] Refactor APU, TSC and ACPI timers to use shared code --- src/common/Timer.cpp | 34 +++++++++++++ src/common/Timer.h | 20 ++++++++ .../hle/DSOUND/DirectSound/DirectSound.cpp | 33 ++---------- src/core/kernel/exports/EmuKrnlKe.cpp | 51 +++---------------- 4 files changed, 65 insertions(+), 73 deletions(-) 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)