diff --git a/code/nel/include/nel/misc/time_nl.h b/code/nel/include/nel/misc/time_nl.h index 2bd3edb72..e7deaae24 100644 --- a/code/nel/include/nel/misc/time_nl.h +++ b/code/nel/include/nel/misc/time_nl.h @@ -48,6 +48,19 @@ typedef sint64 TTicks; class CTime { public: + struct CTimerInfo + { + /// Returns if there is a high precision timer that can be used. + bool IsHighPrecisionAvailable; + /// If a CPU specific timer is used and the values are not consistent accross threads. + bool RequiresSingleCore; + /// The resolution of the high resolution timer. + TTicks HighPrecisionResolution; + }; + + /** Get advanced information on the used timers. + */ + static void probeTimerInfo(CTimerInfo &result); /** Return the number of second since midnight (00:00:00), January 1, 1970, * coordinated universal time, according to the system clock. @@ -71,9 +84,9 @@ public: * the value can jump backwards if the system time is changed by a user or a NTP time sync process. * The value is different on 2 different computers; use the CUniTime class to get a universal * time that is the same on all computers. - * \warning On Win32, the value is on 32 bits only. It wraps around to 0 every about 49.71 days. + * \warning On Win32, the value is on 32 bits only, and uses the low-res timer unless probeTimerInfo was called and a high resolution timer can be used. It wraps around to 0 every about 49.71 days. */ - static TTime getLocalTime (); + static TTime getLocalTime(); /** Return the time in processor ticks. Use it for profile purpose. * If the performance time is not supported on this hardware, it returns 0. diff --git a/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp b/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp index b50709cd2..2cd16bf1c 100644 --- a/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp +++ b/code/nel/src/3d/driver/direct3d/driver_direct3d_texture.cpp @@ -868,6 +868,13 @@ bool CDriverD3D::uploadTextureInternal (ITexture& tex, CRect& rect, uint8 destMi D3DFORMAT destFormat, D3DFORMAT srcFormat) { H_AUTO_D3D(CDriverD3D_uploadTextureInternal) + + if (rect.Width == 0 || rect.Height == 0) + { + nlwarning("Rectangle width or height cannot be 0"); + return false; + } + // The D3D texture CTextureDrvInfosD3D* d3dtext = getTextureD3D(tex); diff --git a/code/nel/src/misc/p_thread.cpp b/code/nel/src/misc/p_thread.cpp index 4c4fe1101..ab5530342 100644 --- a/code/nel/src/misc/p_thread.cpp +++ b/code/nel/src/misc/p_thread.cpp @@ -34,16 +34,16 @@ struct CPMainThread : public CPThread { CPMainThread() : CPThread(NULL, 0) { - if(pthread_key_create(&threadSpecificKey, NULL) != 0) + if(pthread_key_create(&threadSpecificKey, NULL) != 0) throw EThread("cannot create thread specific storage key."); if(pthread_setspecific(threadSpecificKey, this) != 0) throw EThread("cannot set main thread ptr in thread specific storage."); } - ~CPMainThread() + ~CPMainThread() { - if(pthread_key_delete(threadSpecificKey) != 0) + if(pthread_key_delete(threadSpecificKey) != 0) throw EThread("cannot delete thread specific storage key."); } }; @@ -139,7 +139,7 @@ void CPThread::start() /* setting the size of the stack also */ ret = pthread_attr_setstacksize(&tattr, _StackSize); } - + bool detach_old_thread = false; pthread_t old_thread_handle; if (_State != ThreadStateNone) @@ -221,6 +221,9 @@ void CPThread::wait () bool CPThread::setCPUMask(uint64 cpuMask) { #ifdef __USE_GNU + + nlwarning("This code does not work. May cause a segmentation fault..."); + sint res = pthread_setaffinity_np(_ThreadHandle, sizeof(uint64), (const cpu_set_t*)&cpuMask); if (res) @@ -228,9 +231,14 @@ bool CPThread::setCPUMask(uint64 cpuMask) nlwarning("pthread_setaffinity_np() returned %d", res); return false; } -#endif // __USE_GNU return true; + +#else // __USE_GNU + + return false; + +#endif // __USE_GNU } /* @@ -238,9 +246,12 @@ bool CPThread::setCPUMask(uint64 cpuMask) */ uint64 CPThread::getCPUMask() { - uint64 cpuMask = 1; - #ifdef __USE_GNU + + nlwarning("This code does not work. May cause a segmentation fault..."); + + uint64 cpuMask = 0; + sint res = pthread_getaffinity_np(_ThreadHandle, sizeof(uint64), (cpu_set_t*)&cpuMask); if (res) @@ -248,9 +259,14 @@ uint64 CPThread::getCPUMask() nlwarning("pthread_getaffinity_np() returned %d", res); return 0; } -#endif // __USE_GNU return cpuMask; + +#else // __USE_GNU + + return 0; + +#endif // __USE_GNU } void CPThread::setPriority(TThreadPriority priority) @@ -311,9 +327,9 @@ IProcess *IProcess::getCurrentProcess () */ uint64 CPProcess::getCPUMask() { - uint64 cpuMask = 1; - #ifdef __USE_GNU + + uint64 cpuMask = 0; sint res = sched_getaffinity(getpid(), sizeof(uint64), (cpu_set_t*)&cpuMask); if (res) @@ -321,15 +337,21 @@ uint64 CPProcess::getCPUMask() nlwarning("sched_getaffinity() returned %d, errno = %d: %s", res, errno, strerror(errno)); return 0; } -#endif // __USE_GNU return cpuMask; + +#else // __USE_GNU + + return 0; + +#endif // __USE_GNU } /// set the CPU mask bool CPProcess::setCPUMask(uint64 cpuMask) { #ifdef __USE_GNU + sint res = sched_setaffinity(getpid(), sizeof(uint64), (const cpu_set_t*)&cpuMask); if (res) @@ -337,9 +359,14 @@ bool CPProcess::setCPUMask(uint64 cpuMask) nlwarning("sched_setaffinity() returned %d, errno = %d: %s", res, errno, strerror(errno)); return false; } -#endif // __USE_GNU return true; + +#else // __USE_GNU + + return false; + +#endif // __USE_GNU } diff --git a/code/nel/src/misc/time_nl.cpp b/code/nel/src/misc/time_nl.cpp index f018208f9..dd43fd2f7 100644 --- a/code/nel/src/misc/time_nl.cpp +++ b/code/nel/src/misc/time_nl.cpp @@ -32,9 +32,262 @@ #include "nel/misc/time_nl.h" #include "nel/misc/sstring.h" +#include + namespace NLMISC { +namespace { +#ifdef NL_OS_WINDOWS +bool a_HaveQueryPerformance = false; +LARGE_INTEGER a_QueryPerformanceFrequency; +#endif +#ifdef NL_OS_UNIX +# if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) +# if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0) +# define NL_MONOTONIC_CLOCK +# endif +# endif +# ifdef NL_MONOTONIC_CLOCK +bool a_CheckedMonotonicClock = false; +bool a_HasMonotonicClock = false; +uint64 a_MonotonicClockFrequency = 0; +uint64 a_MonotonicClockResolutionNs = 0; +bool hasMonotonicClock() +{ + if (!a_CheckedMonotonicClock) + { + /* Initialize the local time engine. + * On Unix, this method will find out if the Monotonic Clock is supported + * (seems supported by kernel 2.6, not by kernel 2.4). See getLocalTime(). + */ + struct timespec tv; + if ((clock_gettime( CLOCK_MONOTONIC, &tv ) == 0) && + (clock_getres( CLOCK_MONOTONIC, &tv ) == 0)) + { +// nldebug( "Monotonic local time supported (resolution %.6f ms)", ((float)tv.tv_sec)*1000.0f + ((float)tv.tv_nsec)/1000000.0f ); + + if (tv.tv_sec > 0) + { + nlwarning("Monotonic clock not ok, resolution > 1s"); + a_HasMonotonicClock = false; + } + else + { + uint64 nsPerTick = tv.tv_nsec; + uint64 nsPerSec = 1000000000L; + uint64 tickPerSec = nsPerSec / nsPerTick; + a_MonotonicClockFrequency = tickPerSec; + a_MonotonicClockResolutionNs = nsPerTick; + a_HasMonotonicClock = true; + } + } + else + { + a_HasMonotonicClock = false; + } + a_CheckedMonotonicClock = true; + } + return a_HasMonotonicClock; +} +# endif +#endif +} + +void CTime::probeTimerInfo(CTime::CTimerInfo &result) +{ + breakable + { +#ifdef NL_OS_WINDOWS + LARGE_INTEGER winPerfFreq; + LARGE_INTEGER winPerfCount; + DWORD lowResTime; + if (!QueryPerformanceFrequency(&winPerfFreq)) + { + nldebug("Cannot query performance frequency"); + result.IsHighPrecisionAvailable = false; + } + else + { + result.HighPrecisionResolution = winPerfFreq.QuadPart; + } + if (winPerfFreq.QuadPart == 1000) + { + nldebug("Higher precision timer not available, OS defaulted to GetTickCount"); + result.IsHighPrecisionAvailable = false; + } + if (!QueryPerformanceCounter(&winPerfCount)) + { + nldebug("Cannot query performance counter"); + result.IsHighPrecisionAvailable = false; + result.HighPrecisionResolution = 1000; + } + a_HaveQueryPerformance = result.IsHighPrecisionAvailable; + a_QueryPerformanceFrequency.QuadPart = winPerfFreq.QuadPart; + if (!result.IsHighPrecisionAvailable) + { + lowResTime = timeGetTime(); + } +#else + + // Other platforms are awesome. Generic implementation for now. + TTime localTime = getLocalTime(); + result.IsHighPrecisionAvailable = true; + result.HighPrecisionResolution = 0; + +# ifdef NL_MONOTONIC_CLOCK + timespec monoClock; + if (hasMonotonicClock()) + { + clock_gettime(CLOCK_MONOTONIC, &monoClock); + result.HighPrecisionResolution = a_MonotonicClockFrequency; + } + else + { + nldebug("Monotonic clock not available"); + } +# endif + +#endif + + uint64 cpuMask = IProcess::getCurrentProcess()->getCPUMask(); +#ifdef NL_OS_WINDOWS + uint64 threadMask = IThread::getCurrentThread()->getCPUMask(); // broken on linux, don't expect it to work anywhere +#else + uint64 threadMask = cpuMask; +#endif + + uint identical = 0; // Identical stamps may indicate the os handling backwards glitches. + uint backwards = 0; // Happens when the timers are not always in sync and the implementation is faulty. + uint regular = 0; // How many times the number advanced normally. + uint skipping = 0; // Does not really mean anything necessarily. + uint frequencybug = 0; // Should never happen. + // uint badcore = 0; // Affinity does not work. + + // Cycle 32 times trough all cores, and verify if the timing remains consistent. + for (uint i = 32; i; --i) + { + uint64 currentBit = 1; + for (uint j = 64; j; --j) + { + if (cpuMask & currentBit) + { +#ifdef NL_OS_WINDOWS + if (!IThread::getCurrentThread()->setCPUMask(currentBit)) +#else + if (!IProcess::getCurrentProcess()->setCPUMask(currentBit)) +#endif + break; // Thread was set to last cpu. +#ifdef NL_OS_WINDOWS + // Make sure the thread is rescheduled. + SwitchToThread(); + Sleep(0); + // Verify the core + /* Can only verify on 2003, Vista and higher. + if (1 << GetCurrentProcessorNumber() != currentBit) + ++badcore; + */ + // Check if the timer is still sane. + if (result.IsHighPrecisionAvailable) + { + LARGE_INTEGER winPerfFreqN; + LARGE_INTEGER winPerfCountN; + QueryPerformanceFrequency(&winPerfFreqN); + if (winPerfFreqN.QuadPart != winPerfFreq.QuadPart) + ++frequencybug; + QueryPerformanceCounter(&winPerfCountN); + if (winPerfCountN.QuadPart == winPerfCount.QuadPart) + ++identical; + if (winPerfCountN.QuadPart < winPerfCount.QuadPart || winPerfCountN.QuadPart - winPerfCount.QuadPart < 0) + ++backwards; + if (winPerfCountN.QuadPart - winPerfCount.QuadPart > winPerfFreq.QuadPart / 20) // 50ms skipping check + ++skipping; + else if (winPerfCountN.QuadPart > winPerfCount.QuadPart) + ++regular; + winPerfCount.QuadPart = winPerfCountN.QuadPart; + } + else + { + DWORD lowResTimeN; + lowResTimeN = timeGetTime(); + if (lowResTimeN == lowResTime) + ++identical; + if (lowResTimeN < lowResTime || lowResTimeN - lowResTime < 0) + ++backwards; + if (lowResTimeN - lowResTime > 50) + ++skipping; + else if (lowResTimeN > lowResTime) + ++regular; + lowResTime = lowResTimeN; + } +#else +#ifdef NL_OS_UNIX + sched_yield(); +#else + nlSleep(0); +#endif +# ifdef NL_MONOTONIC_CLOCK + if (hasMonotonicClock()) + { + timespec monoClockN; + clock_gettime(CLOCK_MONOTONIC, &monoClockN); + if (monoClock.tv_sec == monoClockN.tv_sec && monoClock.tv_nsec == monoClockN.tv_nsec) + ++identical; + if (monoClockN.tv_sec < monoClock.tv_sec || (monoClock.tv_sec == monoClockN.tv_sec && monoClockN.tv_nsec < monoClock.tv_nsec)) + ++backwards; + if (monoClock.tv_sec == monoClockN.tv_sec && (monoClockN.tv_nsec - monoClock.tv_nsec > 50000000L)) + ++skipping; + else if ((monoClock.tv_sec == monoClockN.tv_sec && monoClock.tv_nsec < monoClockN.tv_nsec) || monoClock.tv_sec < monoClockN.tv_sec) + ++regular; + monoClock.tv_sec = monoClockN.tv_sec; + monoClock.tv_nsec = monoClockN.tv_nsec; + } + else +# endif + { + TTime localTimeN = getLocalTime(); + if (localTimeN == localTime) + ++identical; + if (localTimeN < localTime || localTimeN - localTime < 0) + ++backwards; + if (localTimeN - localTime > 50) + ++skipping; + else if (localTimeN > localTime) + ++regular; + localTime = localTimeN; + } +#endif + } + currentBit <<= 1; + } + } + +#ifdef NL_OS_WINDOWS + IThread::getCurrentThread()->setCPUMask(threadMask); +#else + IProcess::getCurrentProcess()->setCPUMask(threadMask); +#endif + + nldebug("Timer resolution: %i Hz", (int)(result.HighPrecisionResolution)); + nldebug("Time identical: %i, backwards: %i, regular: %i, skipping: %i, frequency bug: %i", identical, backwards, regular, skipping, frequencybug); + if (identical > regular) + nlwarning("The system timer is of relatively low resolution, you may experience issues"); + if (backwards > 0 || frequencybug > 0) + { + nlwarning("The current system timer is not reliable across multiple cpu cores"); + result.RequiresSingleCore = true; + } + else result.RequiresSingleCore = false; + + if (result.HighPrecisionResolution == 14318180) + nldebug("Detected known HPET era timer frequency"); + if (result.HighPrecisionResolution == 3579545) + nldebug("Detected known AHCI era timer frequency"); + if (result.HighPrecisionResolution == 1193182) + nldebug("Detected known i8253/i8254 era timer frequency"); + } +} + /* Return the number of second since midnight (00:00:00), January 1, 1970, * coordinated universal time, according to the system clock. * This values is the same on all computer if computers are synchronized (with NTP for example). @@ -97,54 +350,42 @@ TTime CTime::getLocalTime () //else //{ // This is not affected by system time changes. But it cycles every 49 days. - return timeGetTime(); + // return timeGetTime(); // Only this was left active before it was commented. //} -#elif defined (NL_OS_UNIX) + /* + * The above is no longer relevant. + */ - static bool initdone = false; - static bool isMonotonicClockSupported = false; - if ( ! initdone ) + if (a_HaveQueryPerformance) { - -#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) -#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0) - - /* Initialize the local time engine. - * On Unix, this method will find out if the Monotonic Clock is supported - * (seems supported by kernel 2.6, not by kernel 2.4). See getLocalTime(). - */ - struct timespec tv; - if ( (clock_gettime( CLOCK_MONOTONIC, &tv ) == 0) && - (clock_getres( CLOCK_MONOTONIC, &tv ) == 0) ) - { -// nldebug( "Monotonic local time supported (resolution %.6f ms)", ((float)tv.tv_sec)*1000.0f + ((float)tv.tv_nsec)/1000000.0f ); - isMonotonicClockSupported = true; - } - else - -#endif -#endif - { -// nlwarning( "Monotonic local time not supported, caution with time sync" ); - } - - initdone = true; + // On a (fast) 15MHz timer this rolls over after 7000 days. + // If my calculations are right. + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + counter.QuadPart *= (LONGLONG)1000L; + counter.QuadPart /= a_QueryPerformanceFrequency.QuadPart; + return counter.QuadPart; } + else + { + // Use default reliable low resolution timer. + return timeGetTime(); + } + +#elif defined (NL_OS_UNIX) -#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0) -#if defined(_POSIX_MONOTONIC_CLOCK) && (_POSIX_MONOTONIC_CLOCK >= 0) +#ifdef NL_MONOTONIC_CLOCK - if ( isMonotonicClockSupported ) + if (hasMonotonicClock()) { - struct timespec tv; + timespec tv; // This is not affected by system time changes. if ( clock_gettime( CLOCK_MONOTONIC, &tv ) != 0 ) nlerror ("Can't get clock time again"); return (TTime)tv.tv_sec * (TTime)1000 + (TTime)((tv.tv_nsec/*+500*/) / 1000000); } -#endif #endif // This is affected by system time changes. @@ -156,7 +397,6 @@ TTime CTime::getLocalTime () #endif } - /* Return the time in processor ticks. Use it for profile purpose. * If the performance time is not supported on this hardware, it returns 0. * \warning On a multiprocessor system, the value returned by each processor may @@ -183,7 +423,7 @@ TTicks CTime::getPerformanceTime () return (hi << 32) | (lo & 0xffffffff); #elif defined(HAVE_X86) and !defined(NL_OS_MAC) uint64 x; - // RDTSC - Read time-stamp counter into EDX:EAX. + // RDTSC - Read time-stamp counter into EDX:EAX. __asm__ volatile (".byte 0x0f, 0x31" : "=A" (x)); return x; #else // HAVE_X86 diff --git a/code/nel/tools/misc/CMakeLists.txt b/code/nel/tools/misc/CMakeLists.txt index fcb259a64..5386cbbc6 100644 --- a/code/nel/tools/misc/CMakeLists.txt +++ b/code/nel/tools/misc/CMakeLists.txt @@ -18,3 +18,5 @@ IF(WIN32) ADD_SUBDIRECTORY(words_dic) ENDIF(MFC_FOUND) ENDIF(WIN32) + +ADD_SUBDIRECTORY(probe_timers) diff --git a/code/nel/tools/misc/probe_timers/CMakeLists.txt b/code/nel/tools/misc/probe_timers/CMakeLists.txt new file mode 100644 index 000000000..df0f34926 --- /dev/null +++ b/code/nel/tools/misc/probe_timers/CMakeLists.txt @@ -0,0 +1,9 @@ +FILE(GLOB SRC *.cpp) + +ADD_EXECUTABLE(nl_probe_timers ${SRC}) + +TARGET_LINK_LIBRARIES(nl_probe_timers nelmisc) +NL_DEFAULT_PROPS(nl_probe_timers "NeL, Tools, Misc: Probe Timers") +NL_ADD_RUNTIME_FLAGS(nl_probe_timers) + +INSTALL(TARGETS nl_probe_timers RUNTIME DESTINATION bin COMPONENT toolsmisc) diff --git a/code/nel/tools/misc/probe_timers/main.cpp b/code/nel/tools/misc/probe_timers/main.cpp new file mode 100644 index 000000000..de4c1f785 --- /dev/null +++ b/code/nel/tools/misc/probe_timers/main.cpp @@ -0,0 +1,38 @@ +// NeL - MMORPG Framework +// Copyright (C) 2012 by authors +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program 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 +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +#include +#include +#include + +#include "nel/misc/types_nl.h" +#include "nel/misc/time_nl.h" + +using namespace NLMISC; + +int main (int argc, char **argv) +{ + for (uint i = 0; i < 8; ++i) + { + CTime::CTimerInfo timerInfo; + CTime::probeTimerInfo(timerInfo); + } + + printf ("\nPress to exit\n"); + getchar (); + + return EXIT_SUCCESS; +} diff --git a/code/ryzom/client/src/init.cpp b/code/ryzom/client/src/init.cpp index 11d01934f..bb2540824 100644 --- a/code/ryzom/client/src/init.cpp +++ b/code/ryzom/client/src/init.cpp @@ -686,8 +686,11 @@ void prelogInit() #ifdef NL_OS_WINDOWS _control87 (_EM_INVALID|_EM_DENORMAL/*|_EM_ZERODIVIDE|_EM_OVERFLOW*/|_EM_UNDERFLOW|_EM_INEXACT, _MCW_EM); #endif // NL_OS_WINDOWS - - setCPUMask(); + + CTime::CTimerInfo timerInfo; + NLMISC::CTime::probeTimerInfo(timerInfo); + if (timerInfo.RequiresSingleCore) // TODO: Also have a FV configuration value to force single core. + setCPUMask(); FPU_CHECKER_ONCE diff --git a/code/ryzom/client/src/time_client.h b/code/ryzom/client/src/time_client.h index 80b21195b..9243a908d 100644 --- a/code/ryzom/client/src/time_client.h +++ b/code/ryzom/client/src/time_client.h @@ -134,24 +134,9 @@ void updateClientTime(); // update smoothed time (useful for sky animation) void updateSmoothedTime(); -inline TTime ryzomGetLocalTime () +inline NLMISC::TTime ryzomGetLocalTime() { -#ifdef NL_OS_WINDOWS - if (ClientCfg.TimerMode == 0) - { - return (TTime)(NLMISC::CTime::ticksToSecond (NLMISC::CTime::getPerformanceTime()) * 1000.0); - } - else // if (ClientCfg.TimerMode == 1) - { - // Use 1 ms timer precision - timeBeginPeriod(1); - DWORD start = timeGetTime(); - timeEndPeriod(1); - return (TTime)start; - } -#else // NL_OS_WINDOWS - return (TTime)(NLMISC::CTime::ticksToSecond (NLMISC::CTime::getPerformanceTime()) * 1000.0); -#endif // NL_OS_WINDOWS + return NLMISC::CTime::getLocalTime(); } inline NLMISC::TTicks ryzomGetPerformanceTime()