nuclear@0: /************************************************************************************ nuclear@0: nuclear@0: Filename : OVR_Timer.cpp nuclear@0: Content : Provides static functions for precise timing nuclear@0: Created : September 19, 2012 nuclear@0: Notes : nuclear@0: nuclear@0: Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. nuclear@0: nuclear@0: Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); nuclear@0: you may not use the Oculus VR Rift SDK except in compliance with the License, nuclear@0: which is provided at the time of installation or download, or which nuclear@0: otherwise accompanies this software in either electronic or hard copy form. nuclear@0: nuclear@0: You may obtain a copy of the License at nuclear@0: nuclear@0: http://www.oculusvr.com/licenses/LICENSE-3.2 nuclear@0: nuclear@0: Unless required by applicable law or agreed to in writing, the Oculus VR SDK nuclear@0: distributed under the License is distributed on an "AS IS" BASIS, nuclear@0: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. nuclear@0: See the License for the specific language governing permissions and nuclear@0: limitations under the License. nuclear@0: nuclear@0: ************************************************************************************/ nuclear@0: nuclear@0: #include "OVR_Timer.h" nuclear@0: #include "OVR_Log.h" nuclear@0: nuclear@0: #if defined(OVR_OS_MS) && !defined(OVR_OS_MS_MOBILE) nuclear@0: #define WIN32_LEAN_AND_MEAN nuclear@0: #include nuclear@0: #include nuclear@0: #elif defined(OVR_OS_ANDROID) nuclear@0: #include nuclear@0: #include nuclear@0: #elif defined(OVR_OS_MAC) nuclear@0: #include nuclear@0: #else nuclear@0: #include nuclear@0: #include nuclear@0: #include nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: #if defined(OVR_BUILD_DEBUG) && defined(OVR_OS_WIN32) nuclear@0: #ifndef NTSTATUS nuclear@0: #define NTSTATUS DWORD nuclear@0: #endif nuclear@0: nuclear@0: typedef NTSTATUS (NTAPI* NtQueryTimerResolutionType)(PULONG MaximumTime, PULONG MinimumTime, PULONG CurrentTime); nuclear@0: NtQueryTimerResolutionType pNtQueryTimerResolution; nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: nuclear@0: #if defined(OVR_OS_MS) && !defined(OVR_OS_WIN32) // Non-desktop Microsoft platforms... nuclear@0: nuclear@0: // Add this alias here because we're not going to include OVR_CAPI.cpp nuclear@0: extern "C" { nuclear@0: double ovr_GetTimeInSeconds() nuclear@0: { nuclear@0: return Timer::GetSeconds(); nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: namespace OVR { nuclear@0: nuclear@0: // For recorded data playback nuclear@0: bool Timer::useFakeSeconds = false; nuclear@0: double Timer::FakeSeconds = 0; nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------ nuclear@0: // *** Android Specific Timer nuclear@0: nuclear@0: #if defined(OVR_OS_ANDROID) // To consider: This implementation can also work on most Linux distributions nuclear@0: nuclear@0: //------------------------------------------------------------------------ nuclear@0: // *** Timer - Platform Independent functions nuclear@0: nuclear@0: // Returns global high-resolution application timer in seconds. nuclear@0: double Timer::GetSeconds() nuclear@0: { nuclear@0: if(useFakeSeconds) nuclear@0: return FakeSeconds; nuclear@0: nuclear@0: // Choreographer vsync timestamp is based on. nuclear@0: struct timespec tp; nuclear@0: const int status = clock_gettime(CLOCK_MONOTONIC, &tp); nuclear@0: nuclear@0: #ifdef OVR_BUILD_DEBUG nuclear@0: if (status != 0) nuclear@0: { nuclear@0: OVR_DEBUG_LOG(("clock_gettime status=%i", status )); nuclear@0: } nuclear@0: #else nuclear@0: OVR_UNUSED(status); nuclear@0: #endif nuclear@0: nuclear@0: return (double)tp.tv_sec; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: uint64_t Timer::GetTicksNanos() nuclear@0: { nuclear@0: if (useFakeSeconds) nuclear@0: return (uint64_t) (FakeSeconds * NanosPerSecond); nuclear@0: nuclear@0: // Choreographer vsync timestamp is based on. nuclear@0: struct timespec tp; nuclear@0: const int status = clock_gettime(CLOCK_MONOTONIC, &tp); nuclear@0: nuclear@0: #ifdef OVR_BUILD_DEBUG nuclear@0: if (status != 0) nuclear@0: { nuclear@0: OVR_DEBUG_LOG(("clock_gettime status=%i", status )); nuclear@0: } nuclear@0: #else nuclear@0: OVR_UNUSED(status); nuclear@0: #endif nuclear@0: nuclear@0: const uint64_t result = (uint64_t)tp.tv_sec * (uint64_t)(1000 * 1000 * 1000) + uint64_t(tp.tv_nsec); nuclear@0: return result; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void Timer::initializeTimerSystem() nuclear@0: { nuclear@0: // Empty for this platform. nuclear@0: } nuclear@0: nuclear@0: void Timer::shutdownTimerSystem() nuclear@0: { nuclear@0: // Empty for this platform. nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------ nuclear@0: // *** Win32 Specific Timer nuclear@0: nuclear@0: #elif defined (OVR_OS_MS) nuclear@0: nuclear@0: nuclear@0: // This helper class implements high-resolution wrapper that combines timeGetTime() output nuclear@0: // with QueryPerformanceCounter. timeGetTime() is lower precision but drives the high bits, nuclear@0: // as it's tied to the system clock. nuclear@0: struct PerformanceTimer nuclear@0: { nuclear@0: PerformanceTimer() nuclear@0: : UsingVistaOrLater(false), nuclear@0: TimeCS(), nuclear@0: OldMMTimeMs(0), nuclear@0: MMTimeWrapCounter(0), nuclear@0: PerfFrequency(0), nuclear@0: PerfFrequencyInverse(0), nuclear@0: PerfFrequencyInverseNanos(0), nuclear@0: PerfMinusTicksDeltaNanos(0), nuclear@0: LastResultNanos(0) nuclear@0: { } nuclear@0: nuclear@0: enum { nuclear@0: MMTimerResolutionNanos = 1000000 nuclear@0: }; nuclear@0: nuclear@0: void Initialize(); nuclear@0: void Shutdown(); nuclear@0: nuclear@0: uint64_t GetTimeSeconds(); nuclear@0: double GetTimeSecondsDouble(); nuclear@0: uint64_t GetTimeNanos(); nuclear@0: nuclear@0: UINT64 getFrequency() nuclear@0: { nuclear@0: if (PerfFrequency == 0) nuclear@0: { nuclear@0: LARGE_INTEGER freq; nuclear@0: QueryPerformanceFrequency(&freq); nuclear@0: PerfFrequency = freq.QuadPart; nuclear@0: PerfFrequencyInverse = 1.0 / (double)PerfFrequency; nuclear@0: PerfFrequencyInverseNanos = 1000000000.0 / (double)PerfFrequency; nuclear@0: } nuclear@0: return PerfFrequency; nuclear@0: } nuclear@0: nuclear@0: double GetFrequencyInverse() nuclear@0: { nuclear@0: OVR_ASSERT(PerfFrequencyInverse != 0.0); // Assert that the frequency has been initialized. nuclear@0: return PerfFrequencyInverse; nuclear@0: } nuclear@0: nuclear@0: bool UsingVistaOrLater; nuclear@0: nuclear@0: CRITICAL_SECTION TimeCS; nuclear@0: // timeGetTime() support with wrap. nuclear@0: uint32_t OldMMTimeMs; nuclear@0: uint32_t MMTimeWrapCounter; nuclear@0: // Cached performance frequency result. nuclear@0: uint64_t PerfFrequency; // cycles per second, typically a large value like 3000000, but usually not the same as the CPU clock rate. nuclear@0: double PerfFrequencyInverse; // seconds per cycle (will be a small fractional value). nuclear@0: double PerfFrequencyInverseNanos; // nanoseconds per cycle. nuclear@0: nuclear@0: // Computed as (perfCounterNanos - ticksCounterNanos) initially, nuclear@0: // and used to adjust timing. nuclear@0: uint64_t PerfMinusTicksDeltaNanos; nuclear@0: // Last returned value in nanoseconds, to ensure we don't back-step in time. nuclear@0: uint64_t LastResultNanos; nuclear@0: }; nuclear@0: nuclear@0: static PerformanceTimer Win32_PerfTimer; nuclear@0: nuclear@0: nuclear@0: void PerformanceTimer::Initialize() nuclear@0: { nuclear@0: #if defined(OVR_OS_WIN32) // Desktop Windows only nuclear@0: // The following has the effect of setting the NT timer resolution (NtSetTimerResolution) to 1 millisecond. nuclear@0: MMRESULT mmr = timeBeginPeriod(1); nuclear@0: OVR_ASSERT(TIMERR_NOERROR == mmr); nuclear@0: OVR_UNUSED(mmr); nuclear@0: #endif nuclear@0: nuclear@0: InitializeCriticalSection(&TimeCS); nuclear@0: MMTimeWrapCounter = 0; nuclear@0: getFrequency(); nuclear@0: nuclear@0: #if defined(OVR_OS_WIN32) // Desktop Windows only nuclear@0: // Set Vista flag. On Vista, we can just use QPC() without all the extra work nuclear@0: OSVERSIONINFOEX ver; nuclear@0: ZeroMemory(&ver, sizeof(OSVERSIONINFOEX)); nuclear@0: ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); nuclear@0: ver.dwMajorVersion = 6; // Vista+ nuclear@0: nuclear@0: DWORDLONG condMask = 0; nuclear@0: VER_SET_CONDITION(condMask, VER_MAJORVERSION, VER_GREATER_EQUAL); nuclear@0: nuclear@0: // VerifyVersionInfo returns true if the OS meets the conditions set above nuclear@0: UsingVistaOrLater = VerifyVersionInfo(&ver, VER_MAJORVERSION, condMask) != 0; nuclear@0: #else nuclear@0: UsingVistaOrLater = true; nuclear@0: #endif nuclear@0: nuclear@0: OVR_DEBUG_LOG(("PerformanceTimer UsingVistaOrLater = %d", (int)UsingVistaOrLater)); nuclear@0: nuclear@0: #if defined(OVR_BUILD_DEBUG) && defined(OVR_OS_WIN32) nuclear@0: HMODULE hNtDll = LoadLibrary(L"NtDll.dll"); nuclear@0: if (hNtDll) nuclear@0: { nuclear@0: pNtQueryTimerResolution = (NtQueryTimerResolutionType)GetProcAddress(hNtDll, "NtQueryTimerResolution"); nuclear@0: //pNtSetTimerResolution = (NtSetTimerResolutionType)GetProcAddress(hNtDll, "NtSetTimerResolution"); nuclear@0: nuclear@0: if(pNtQueryTimerResolution) nuclear@0: { nuclear@0: ULONG MinimumResolution; // in 100-ns units nuclear@0: ULONG MaximumResolution; nuclear@0: ULONG ActualResolution; nuclear@0: pNtQueryTimerResolution(&MinimumResolution, &MaximumResolution, &ActualResolution); nuclear@0: OVR_DEBUG_LOG(("NtQueryTimerResolution = Min %ld us, Max %ld us, Current %ld us", MinimumResolution / 10, MaximumResolution / 10, ActualResolution / 10)); nuclear@0: } nuclear@0: nuclear@0: FreeLibrary(hNtDll); nuclear@0: } nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: void PerformanceTimer::Shutdown() nuclear@0: { nuclear@0: DeleteCriticalSection(&TimeCS); nuclear@0: nuclear@0: #if defined(OVR_OS_WIN32) // Desktop Windows only nuclear@0: MMRESULT mmr = timeEndPeriod(1); nuclear@0: OVR_ASSERT(TIMERR_NOERROR == mmr); nuclear@0: OVR_UNUSED(mmr); nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: nuclear@0: uint64_t PerformanceTimer::GetTimeSeconds() nuclear@0: { nuclear@0: if (UsingVistaOrLater) nuclear@0: { nuclear@0: LARGE_INTEGER li; nuclear@0: QueryPerformanceCounter(&li); nuclear@0: OVR_ASSERT(PerfFrequencyInverse != 0); // Initialize should have been called earlier. nuclear@0: return (uint64_t)(li.QuadPart * PerfFrequencyInverse); nuclear@0: } nuclear@0: nuclear@0: return (uint64_t)(GetTimeNanos() * .0000000001); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: double PerformanceTimer::GetTimeSecondsDouble() nuclear@0: { nuclear@0: if (UsingVistaOrLater) nuclear@0: { nuclear@0: LARGE_INTEGER li; nuclear@0: QueryPerformanceCounter(&li); nuclear@0: OVR_ASSERT(PerfFrequencyInverse != 0); nuclear@0: return (li.QuadPart * PerfFrequencyInverse); nuclear@0: } nuclear@0: nuclear@0: return (GetTimeNanos() * .0000000001); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: uint64_t PerformanceTimer::GetTimeNanos() nuclear@0: { nuclear@0: uint64_t resultNanos; nuclear@0: LARGE_INTEGER li; nuclear@0: nuclear@0: OVR_ASSERT(PerfFrequencyInverseNanos != 0); // Initialize should have been called earlier. nuclear@0: nuclear@0: if (UsingVistaOrLater) // Includes non-desktop platforms nuclear@0: { nuclear@0: // Then we can use QPC() directly without all that extra work nuclear@0: QueryPerformanceCounter(&li); nuclear@0: resultNanos = (uint64_t)(li.QuadPart * PerfFrequencyInverseNanos); nuclear@0: } nuclear@0: else nuclear@0: { nuclear@0: // On Win32 QueryPerformanceFrequency is unreliable due to SMP and nuclear@0: // performance levels, so use this logic to detect wrapping and track nuclear@0: // high bits. nuclear@0: ::EnterCriticalSection(&TimeCS); nuclear@0: nuclear@0: // Get raw value and perf counter "At the same time". nuclear@0: QueryPerformanceCounter(&li); nuclear@0: nuclear@0: DWORD mmTimeMs = timeGetTime(); nuclear@0: if (OldMMTimeMs > mmTimeMs) nuclear@0: MMTimeWrapCounter++; nuclear@0: OldMMTimeMs = mmTimeMs; nuclear@0: nuclear@0: // Normalize to nanoseconds. nuclear@0: uint64_t perfCounterNanos = (uint64_t)(li.QuadPart * PerfFrequencyInverseNanos); nuclear@0: uint64_t mmCounterNanos = ((uint64_t(MMTimeWrapCounter) << 32) | mmTimeMs) * 1000000; nuclear@0: if (PerfMinusTicksDeltaNanos == 0) nuclear@0: PerfMinusTicksDeltaNanos = perfCounterNanos - mmCounterNanos; nuclear@0: nuclear@0: // Compute result before snapping. nuclear@0: // nuclear@0: // On first call, this evaluates to: nuclear@0: // resultNanos = mmCounterNanos. nuclear@0: // Next call, assuming no wrap: nuclear@0: // resultNanos = prev_mmCounterNanos + (perfCounterNanos - prev_perfCounterNanos). nuclear@0: // After wrap, this would be: nuclear@0: // resultNanos = snapped(prev_mmCounterNanos +/- 1ms) + (perfCounterNanos - prev_perfCounterNanos). nuclear@0: // nuclear@0: resultNanos = perfCounterNanos - PerfMinusTicksDeltaNanos; nuclear@0: nuclear@0: // Snap the range so that resultNanos never moves further apart then its target resolution. nuclear@0: // It's better to allow more slack on the high side as timeGetTime() may be updated at sporadically nuclear@0: // larger then 1 ms intervals even when 1 ms resolution is requested. nuclear@0: if (resultNanos > (mmCounterNanos + MMTimerResolutionNanos*2)) nuclear@0: { nuclear@0: resultNanos = mmCounterNanos + MMTimerResolutionNanos*2; nuclear@0: if (resultNanos < LastResultNanos) nuclear@0: resultNanos = LastResultNanos; nuclear@0: PerfMinusTicksDeltaNanos = perfCounterNanos - resultNanos; nuclear@0: } nuclear@0: else if (resultNanos < (mmCounterNanos - MMTimerResolutionNanos)) nuclear@0: { nuclear@0: resultNanos = mmCounterNanos - MMTimerResolutionNanos; nuclear@0: if (resultNanos < LastResultNanos) nuclear@0: resultNanos = LastResultNanos; nuclear@0: PerfMinusTicksDeltaNanos = perfCounterNanos - resultNanos; nuclear@0: } nuclear@0: nuclear@0: LastResultNanos = resultNanos; nuclear@0: ::LeaveCriticalSection(&TimeCS); nuclear@0: } nuclear@0: nuclear@0: //Tom's addition, to keep precision nuclear@0: //static uint64_t initial_time = 0; nuclear@0: //if (!initial_time) initial_time = resultNanos; nuclear@0: //resultNanos -= initial_time; nuclear@0: // FIXME: This cannot be used for cross-process timestamps nuclear@0: nuclear@0: return resultNanos; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------ nuclear@0: // *** Timer - Platform Independent functions nuclear@0: nuclear@0: // Returns global high-resolution application timer in seconds. nuclear@0: double Timer::GetSeconds() nuclear@0: { nuclear@0: if(useFakeSeconds) nuclear@0: return FakeSeconds; nuclear@0: nuclear@0: return Win32_PerfTimer.GetTimeSecondsDouble(); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: // Delegate to PerformanceTimer. nuclear@0: uint64_t Timer::GetTicksNanos() nuclear@0: { nuclear@0: if (useFakeSeconds) nuclear@0: return (uint64_t) (FakeSeconds * NanosPerSecond); nuclear@0: nuclear@0: return Win32_PerfTimer.GetTimeNanos(); nuclear@0: } nuclear@0: void Timer::initializeTimerSystem() nuclear@0: { nuclear@0: Win32_PerfTimer.Initialize(); nuclear@0: } nuclear@0: void Timer::shutdownTimerSystem() nuclear@0: { nuclear@0: Win32_PerfTimer.Shutdown(); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: #elif defined(OVR_OS_MAC) nuclear@0: nuclear@0: nuclear@0: double Timer::TimeConvertFactorNanos = 0.0; nuclear@0: double Timer::TimeConvertFactorSeconds = 0.0; nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------ nuclear@0: // *** Standard OS Timer nuclear@0: nuclear@0: // Returns global high-resolution application timer in seconds. nuclear@0: double Timer::GetSeconds() nuclear@0: { nuclear@0: if(useFakeSeconds) nuclear@0: return FakeSeconds; nuclear@0: nuclear@0: OVR_ASSERT(TimeConvertFactorNanos != 0.0); nuclear@0: return (double)mach_absolute_time() * TimeConvertFactorNanos; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: uint64_t Timer::GetTicksNanos() nuclear@0: { nuclear@0: if (useFakeSeconds) nuclear@0: return (uint64_t) (FakeSeconds * NanosPerSecond); nuclear@0: nuclear@0: OVR_ASSERT(TimeConvertFactorSeconds != 0.0); nuclear@0: return (uint64_t)(mach_absolute_time() * TimeConvertFactorSeconds); nuclear@0: } nuclear@0: nuclear@0: void Timer::initializeTimerSystem() nuclear@0: { nuclear@0: mach_timebase_info_data_t timeBase; nuclear@0: mach_timebase_info(&timeBase); nuclear@0: TimeConvertFactorSeconds = ((double)timeBase.numer / (double)timeBase.denom); nuclear@0: TimeConvertFactorNanos = TimeConvertFactorSeconds / 1000000000.0; nuclear@0: } nuclear@0: nuclear@0: void Timer::shutdownTimerSystem() nuclear@0: { nuclear@0: // Empty for this platform. nuclear@0: } nuclear@0: nuclear@0: nuclear@0: #else // Posix platforms (e.g. Linux, BSD Unix) nuclear@0: nuclear@0: nuclear@0: bool Timer::MonotonicClockAvailable = false; nuclear@0: nuclear@0: nuclear@0: // Returns global high-resolution application timer in seconds. nuclear@0: double Timer::GetSeconds() nuclear@0: { nuclear@0: if(useFakeSeconds) nuclear@0: return FakeSeconds; nuclear@0: nuclear@0: // http://linux/die/netman3/clock_gettime nuclear@0: #if defined(CLOCK_MONOTONIC) // If we can use clock_gettime, which has nanosecond precision... nuclear@0: if(MonotonicClockAvailable) nuclear@0: { nuclear@0: timespec ts; nuclear@0: clock_gettime(CLOCK_MONOTONIC, &ts); // Better to use CLOCK_MONOTONIC than CLOCK_REALTIME. nuclear@0: return static_cast(ts.tv_sec) + static_cast(ts.tv_nsec) / 1E9; nuclear@0: } nuclear@0: #endif nuclear@0: nuclear@0: // We cannot use rdtsc because its frequency changes at runtime. nuclear@0: struct timeval tv; nuclear@0: gettimeofday(&tv, 0); nuclear@0: nuclear@0: return static_cast(tv.tv_sec) + static_cast(tv.tv_usec) / 1E6; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: uint64_t Timer::GetTicksNanos() nuclear@0: { nuclear@0: if (useFakeSeconds) nuclear@0: return (uint64_t) (FakeSeconds * NanosPerSecond); nuclear@0: nuclear@0: #if defined(CLOCK_MONOTONIC) // If we can use clock_gettime, which has nanosecond precision... nuclear@0: if(MonotonicClockAvailable) nuclear@0: { nuclear@0: timespec ts; nuclear@0: clock_gettime(CLOCK_MONOTONIC, &ts); nuclear@0: return ((uint64_t)ts.tv_sec * 1000000000ULL) + (uint64_t)ts.tv_nsec; nuclear@0: } nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: // We cannot use rdtsc because its frequency changes at runtime. nuclear@0: uint64_t result; nuclear@0: nuclear@0: // Return microseconds. nuclear@0: struct timeval tv; nuclear@0: nuclear@0: gettimeofday(&tv, 0); nuclear@0: nuclear@0: result = (uint64_t)tv.tv_sec * 1000000; nuclear@0: result += tv.tv_usec; nuclear@0: nuclear@0: return result * 1000; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void Timer::initializeTimerSystem() nuclear@0: { nuclear@0: #if defined(CLOCK_MONOTONIC) nuclear@0: timespec ts; // We could also check for the availability of CLOCK_MONOTONIC with sysconf(_SC_MONOTONIC_CLOCK) nuclear@0: int result = clock_gettime(CLOCK_MONOTONIC, &ts); nuclear@0: MonotonicClockAvailable = (result == 0); nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: void Timer::shutdownTimerSystem() nuclear@0: { nuclear@0: // Empty for this platform. nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: #endif // OS-specific nuclear@0: nuclear@0: nuclear@0: nuclear@0: } // OVR nuclear@0: