#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2013 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License // If the library is compiled as a DLL, this ensures symbols are exported #define NUCLEX_GAME_SOURCE 1 #include "Nuclex/Game/Config.h" #if defined(NUCLEX_GAME_WIN32) || defined(NUCLEX_GAME_WINRT) #include "Nuclex/Game/Timing/WindowsClock.h" #include #include #include #include // Don't use WIN32_LEAN_AND_MEAN in this case. #define NO_MINMAX #include // Yes, Microsoft, a global define named "max" was a fantastic idea. #undef min #undef max namespace { #if defined(NUCLEX_GAME_WINRT) // ------------------------------------------------------------------------------------------- // /// Determines the highest possible resolution the timer supports /// The smallest interval the timer can step by in milliseconds std::uint32_t getHighestPossibleTimerResolution() { return 16; // According to MSDN, GetTickCount64() has a resolution of 16 ms minimum } // ------------------------------------------------------------------------------------------- // #elif defined(NUCLEX_GAME_WIN32) // ------------------------------------------------------------------------------------------- // /// Determines the highest possible resolution the timer supports /// The smallest interval the timer can step by in milliseconds std::uint32_t getHighestPossibleTimerResolution() { TIMECAPS timeCaps; MMRESULT result = ::timeGetDevCaps(&timeCaps, sizeof(timeCaps)); if(result != MMSYSERR_NOERROR) { throw std::runtime_error("Could not query timer capabilities"); } return timeCaps.wPeriodMin; } // ------------------------------------------------------------------------------------------- // /// Configures the timer to at least the specified resolution /// Resolution the timer will be configured to void enterTimerResolution(std::uint32_t resolution) { MMRESULT result = ::timeBeginPeriod(resolution); if(result != MMSYSERR_NOERROR) { throw std::runtime_error("Could not increase timer resolution"); } } // ------------------------------------------------------------------------------------------- // /// Restores the previous resolution of the timer /// Resolution from which the timer will be restored void leaveTimerResolution(std::uint32_t resolution) { MMRESULT result = ::timeEndPeriod(resolution); if(result != MMSYSERR_NOERROR) { throw std::runtime_error("Could not restore timer resolution"); } } // ------------------------------------------------------------------------------------------- // #endif } // anonymous namespace namespace Nuclex { namespace Game { namespace Timing { // ------------------------------------------------------------------------------------------- // WindowsClock::WindowsClock() : timerResolution(getHighestPossibleTimerResolution()) { bool performanceCounterAvailable = TryGetCountFrequency(this->performanceCounterFrequency); if(performanceCounterAvailable) { std::uint64_t currentCounts = GetCurrentCounts();; this->performanceCounterOffset = getTimeAsCounts() - currentCounts; this->previousCounts = currentCounts + this->performanceCounterOffset; } else { this->performanceCounterFrequency = 0; } #if defined(NUCLEX_GAME_WIN32) // Windows desktop applications can increase the timer resolution at will enterTimerResolution(this->timerResolution); #endif } // ------------------------------------------------------------------------------------------- // WindowsClock::~WindowsClock() { #if defined(NUCLEX_GAME_WIN32) leaveTimerResolution(this->timerResolution); #endif } // ------------------------------------------------------------------------------------------- // bool WindowsClock::TryGetCountFrequency(std::uint64_t &countFrequency) { ::LARGE_INTEGER frequency; BOOL isSupported = ::QueryPerformanceFrequency(&frequency); if(isSupported == FALSE) { countFrequency = 0; return false; } else { countFrequency = frequency.QuadPart; return true; } } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::GetCountFrequency() { std::uint64_t countFrequency; if(!TryGetCountFrequency(countFrequency)) { throw std::runtime_error("The system does not provide a performance counter"); } return countFrequency; } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::GetCurrentCounts() { ::LARGE_INTEGER counts; BOOL wasQueried = ::QueryPerformanceCounter(&counts); if(wasQueried == FALSE) { throw std::runtime_error("The performance counter could not be queried"); } return counts.QuadPart; } // ------------------------------------------------------------------------------------------- // std::chrono::milliseconds WindowsClock::GetUptime() { #if defined(NUCLEX_GAME_WINRT) // WinRT only offers GetTickCount64() // We intentionally force a 32 bit wrap-around so we can multiply-divide carelessly // in the rest of the code. The actual wrap-around is handled by the caller. return std::chrono::milliseconds( static_cast(::GetTickCount64()) ); #else return std::chrono::milliseconds( static_cast(::timeGetTime()) ); #endif } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::GetWraparoundTime() const { if(this->performanceCounterFrequency == 0) { // if 0, no performance counter is available return std::numeric_limits::max(); } else { return std::numeric_limits::max(); } } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::GetTime() const { if(this->performanceCounterFrequency == 0) { // if 0, no performance counter is available return GetUptime().count(); } // Normally we could just return the performance counter. But out in the wild are systems // with PCI-to-ISA bridges that make performance counters jump randomly by several seconds // and systems with broken BIOSes that cause counters to have different states across // CPU cores. We have to compensate for this. { std::lock_guard mutexGuard(this->mutex); std::uint64_t currentCounts = GetCurrentCounts() + this->performanceCounterOffset; std::uint64_t timeAsCounts = getTimeAsCounts(); // Calculate the difference between the time and the performance counter std::int64_t difference = (currentCounts - timeAsCounts); { std::uint32_t maxUint32 = std::numeric_limits::max(); std::int64_t wraparoundOffset = maxUint32 * this->performanceCounterFrequency / 1000; // Did the time wrap around? We need to recalculate the offset. if(difference > (wraparoundOffset / 2)) { difference -= wraparoundOffset; this->performanceCounterOffset += timeAsCounts - currentCounts; } } // If the performance counter goes backwards or is off by more than 3 times // the timer resolution Windows boasts of supporting, we assume that // the performance counter has leaped. bool leapOccurred; { std::int64_t allowedDeviationCounts = this->timerResolution * 3 * this->performanceCounterFrequency / 1000; leapOccurred = (currentCounts < this->previousCounts) || // Jumped backwards (abs(difference) > allowedDeviationCounts); // Jumped too far } // If the performance counter leaped, fall back to the time API (but make sure time never // goes backwards) and resynchronize the performance counter offset to the leap location. if(leapOccurred) { this->previousCounts = std::max(this->previousCounts, timeAsCounts); this->performanceCounterOffset += timeAsCounts - currentCounts; } else { // Everything seems normal, we trust the performance counter this->previousCounts = currentCounts; } return this->previousCounts; } } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::GetFrequency() const { if(this->performanceCounterFrequency == 0) { // if 0, no performance counter is available return 1000; // The fallback timer reports time in millseconds } else { return this->performanceCounterFrequency; } } // ------------------------------------------------------------------------------------------- // std::uint64_t WindowsClock::getTimeAsCounts() const { using namespace std; assert( (this->performanceCounterFrequency != 0) && "Performance counter must be available if this method is called" ); return GetUptime().count() * this->performanceCounterFrequency / 1000; } // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Game::Timing #endif // defined(NUCLEX_GAME_WIN32) || defined(NUCLEX_GAME_WINRT)