diff --git a/Source/Engine/Engine/Engine.cpp b/Source/Engine/Engine/Engine.cpp index 24d691c56..0bc79573e 100644 --- a/Source/Engine/Engine/Engine.cpp +++ b/Source/Engine/Engine/Engine.cpp @@ -146,26 +146,22 @@ int32 Engine::Main(const Char* cmdLine) EngineImpl::IsReady = true; // Main engine loop - bool canDraw = true; // Prevent drawing 2 or more frames in a row without update or fixed update (nothing will change) + const bool useSleep = true; // TODO: this should probably be a platform setting while (!ShouldExit()) { -#if 0 - // TODO: test it more and maybe use in future to reduce CPU usage - // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend - { - float tickFps; - auto tick = Time->GetHighestFrequency(tickFps); - double tickTargetStepTime = 1.0 / tickFps; - double nextTick = tick->LastEnd + tickTargetStepTime; - double timeToTick = nextTick - Platform::GetTimeSeconds(); - int32 sleepTimeMs = Math::Min(4, Math::FloorToInt(timeToTick * (1000.0 * 0.8))); // Convert seconds to milliseconds and apply adjustment with limit - if (!Device->WasVSyncUsed() && sleepTimeMs > 0) - { - PROFILE_CPU_NAMED("Idle"); - Platform::Sleep(sleepTimeMs); - } - } -#endif + // Reduce CPU usage by introducing idle time if the engine is running very fast and has enough time to spend + if ((useSleep && Time::UpdateFPS > 0) || !Platform::GetHasFocus()) + { + double nextTick = Time::GetNextTick(); + double timeToTick = nextTick - Platform::GetTimeSeconds(); + + // Sleep less than needed, some platforms may sleep slightly more than requested + if (timeToTick > 0.002) + { + PROFILE_CPU_NAMED("Idle"); + Platform::Sleep(1); + } + } // App paused logic if (Platform::GetIsPaused()) @@ -187,7 +183,6 @@ int32 Engine::Main(const Char* cmdLine) OnUpdate(); OnLateUpdate(); Time::OnEndUpdate(); - canDraw = true; } // Start physics simulation @@ -195,16 +190,14 @@ int32 Engine::Main(const Char* cmdLine) { OnFixedUpdate(); Time::OnEndPhysics(); - canDraw = true; } // Draw frame - if (canDraw && Time::OnBeginDraw()) + if (Time::OnBeginDraw()) { OnDraw(); Time::OnEndDraw(); FrameMark; - canDraw = false; } // Collect physics simulation results (does nothing if Simulate hasn't been called in the previous loop step) diff --git a/Source/Engine/Engine/Time.cpp b/Source/Engine/Engine/Time.cpp index 2dd348207..36db6a236 100644 --- a/Source/Engine/Engine/Time.cpp +++ b/Source/Engine/Engine/Time.cpp @@ -70,6 +70,7 @@ void Time::TickData::OnBeforeRun(float targetFps, double currentTime) LastLength = static_cast(DeltaTime.Ticks) / Constants::TicksPerSecond; LastBegin = currentTime - LastLength; LastEnd = currentTime; + NextBegin = targetFps > ZeroTolerance ? LastBegin + (1.0f / targetFps) : 0.0; } void Time::TickData::OnReset(float targetFps, double currentTime) @@ -91,10 +92,18 @@ bool Time::TickData::OnTickBegin(float targetFps, float maxDeltaTime) } else { - deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime); - const double minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0; - if (deltaTime < minDeltaTime) + if (time < NextBegin) return false; + + deltaTime = Math::Max((time - LastBegin), 0.0); + if (deltaTime > maxDeltaTime) + { + deltaTime = (double)maxDeltaTime; + NextBegin = time; + } + + if (targetFps > ZeroTolerance) + NextBegin += (1.0 / targetFps); } // Update data @@ -135,10 +144,19 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) } else { - deltaTime = Math::Clamp(time - LastBegin, 0.0, (double)maxDeltaTime); - minDeltaTime = targetFps > ZeroTolerance ? 1.0 / (double)targetFps : 0.0; - if (deltaTime < minDeltaTime) + if (time < NextBegin) return false; + + minDeltaTime = targetFps > ZeroTolerance ? 1.0 / targetFps : 0.0; + deltaTime = Math::Max((time - LastBegin), 0.0); + if (deltaTime > maxDeltaTime) + { + deltaTime = (double)maxDeltaTime; + NextBegin = time; + } + + if (targetFps > ZeroTolerance) + NextBegin += (1.0 / targetFps); } Samples.Add(deltaTime); @@ -158,26 +176,23 @@ bool Time::FixedStepTickData::OnTickBegin(float targetFps, float maxDeltaTime) return true; } -Time::TickData* Time::GetHighestFrequency(float& fps) +double Time::GetNextTick() { - const auto updateFps = UpdateFPS; - const auto drawFps = DrawFPS; - const auto physicsFps = PhysicsFPS; + const double nextUpdate = Time::Update.NextBegin; + const double nextPhysics = Time::Physics.NextBegin; + const double nextDraw = Time::Draw.NextBegin; - if (physicsFps >= drawFps && physicsFps >= updateFps) - { - fps = physicsFps; - return &Physics; - } + double nextTick = MAX_double; + if (UpdateFPS > 0 && nextUpdate < nextTick) + nextTick = nextUpdate; + if (PhysicsFPS > 0 && nextPhysics < nextTick) + nextTick = nextPhysics; + if (DrawFPS > 0 && nextDraw < nextTick) + nextTick = nextDraw; - if (drawFps >= physicsFps && drawFps >= updateFps) - { - fps = drawFps; - return &Draw; - } - - fps = updateFps; - return &Update; + if (nextTick == MAX_double) + return 0.0; + return nextTick; } void Time::SetGamePaused(bool value) diff --git a/Source/Engine/Engine/Time.h b/Source/Engine/Engine/Time.h index af0545184..d4cf673e9 100644 --- a/Source/Engine/Engine/Time.h +++ b/Source/Engine/Engine/Time.h @@ -48,6 +48,11 @@ public: /// double LastLength; + /// + /// The next tick start time. + /// + double NextBegin; + /// /// The delta time. /// @@ -167,11 +172,10 @@ public: } /// - /// Gets the tick data that uses the highest frequency for the ticking. + /// Gets the time of next upcoming tick data of ticking group with defined update frequency. /// - /// The FPS rate of the highest frequency ticking group. - /// The tick data. - static TickData* GetHighestFrequency(float& fps); + /// The time of next tick. + static double GetNextTick(); /// /// Gets the value indicating whenever game logic is paused (physics, script updates, etc.). @@ -185,7 +189,7 @@ public: /// /// Sets the value indicating whenever game logic is paused (physics, script updates, etc.). /// - /// >True if pause game logic, otherwise false. + /// True if pause game logic, otherwise false. API_PROPERTY() static void SetGamePaused(bool value); /// diff --git a/Source/Engine/Platform/Win32/Win32Platform.cpp b/Source/Engine/Platform/Win32/Win32Platform.cpp index 31c0f4a36..9d2041547 100644 --- a/Source/Engine/Platform/Win32/Win32Platform.cpp +++ b/Source/Engine/Platform/Win32/Win32Platform.cpp @@ -456,7 +456,21 @@ void Win32Platform::SetThreadAffinityMask(uint64 affinityMask) void Win32Platform::Sleep(int32 milliseconds) { - ::Sleep(static_cast(milliseconds)); + static thread_local HANDLE timer = NULL; + if (timer == NULL) + { + // Attempt to create high-resolution timer for each thread (Windows 10 build 17134 or later) + timer = CreateWaitableTimerEx(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + if (timer == NULL) // fallback for older versions of Windows + timer = CreateWaitableTimer(NULL, TRUE, NULL); + } + + // Negative value is relative to current time, minimum waitable time is 10 microseconds + LARGE_INTEGER dueTime; + dueTime.QuadPart = -int64_t(milliseconds) * 10000; + + SetWaitableTimerEx(timer, &dueTime, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(timer, INFINITE); } double Win32Platform::GetTimeSeconds() diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 2086cd8dc..89e050267 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -18,6 +18,7 @@ #include "../Win32/IncludeWindowsHeaders.h" #include #include +#include #include #include #if CRASH_LOG_ENABLE @@ -30,11 +31,12 @@ void* WindowsPlatform::Instance = nullptr; namespace { - String UserLocale, ComputerName, UserName; + String UserLocale, ComputerName, UserName, WindowsName; HANDLE EngineMutex = nullptr; Rectangle VirtualScreenBounds = Rectangle(0.0f, 0.0f, 0.0f, 0.0f); int32 VersionMajor = 0; int32 VersionMinor = 0; + int32 VersionBuild = 0; int32 SystemDpi = 96; #if CRASH_LOG_ENABLE CriticalSection SymLocker; @@ -126,6 +128,102 @@ LONG GetDWORDRegKey(HKEY hKey, const Char* strValueName, DWORD& nValue, DWORD nD return nError; } +void GetWindowsVersion(String& windowsName, int32& versionMajor, int32& versionMinor, int32& versionBuild) +{ + // Get OS version + + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey); + if (lRes == ERROR_SUCCESS) + { + GetStringRegKey(hKey, TEXT("ProductName"), windowsName, TEXT("Windows")); + + DWORD currentMajorVersionNumber; + DWORD currentMinorVersionNumber; + String currentBuildNumber; + GetDWORDRegKey(hKey, TEXT("CurrentMajorVersionNumber"), currentMajorVersionNumber, 0); + GetDWORDRegKey(hKey, TEXT("CurrentMinorVersionNumber"), currentMinorVersionNumber, 0); + GetStringRegKey(hKey, TEXT("CurrentBuildNumber"), currentBuildNumber, TEXT("0")); + VersionMajor = currentMajorVersionNumber; + VersionMinor = currentMinorVersionNumber; + StringUtils::Parse(currentBuildNumber.Get(), &VersionBuild); + + if (StringUtils::Compare(windowsName.Get(), TEXT("Windows 7"), 9) == 0) + { + VersionMajor = 6; + VersionMinor = 2; + } + + if (VersionMajor == 0 && VersionMinor == 0) + { + String windowsVersion; + GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); + + if (windowsVersion.HasChars()) + { + const int32 dot = windowsVersion.Find('.'); + if (dot != -1) + { + StringUtils::Parse(windowsVersion.Substring(0, dot).Get(), &VersionMajor); + StringUtils::Parse(windowsVersion.Substring(dot + 1).Get(), &VersionMinor); + } + } + } + } + else + { + if (IsWindowsServer()) + { + windowsName = TEXT("Windows Server"); + versionMajor = 6; + versionMinor = 3; + } + else if (IsWindows8Point1OrGreater()) + { + windowsName = TEXT("Windows 8.1"); + versionMajor = 6; + versionMinor = 3; + } + else if (IsWindows8OrGreater()) + { + windowsName = TEXT("Windows 8"); + versionMajor = 6; + versionMinor = 2; + } + else if (IsWindows7SP1OrGreater()) + { + windowsName = TEXT("Windows 7 SP1"); + versionMajor = 6; + versionMinor = 2; + } + else if (IsWindows7OrGreater()) + { + windowsName = TEXT("Windows 7"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaSP2OrGreater()) + { + windowsName = TEXT("Windows Vista SP2"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaSP1OrGreater()) + { + windowsName = TEXT("Windows Vista SP1"); + versionMajor = 6; + versionMinor = 1; + } + else if (IsWindowsVistaOrGreater()) + { + windowsName = TEXT("Windows Vista"); + versionMajor = 6; + versionMinor = 0; + } + } + RegCloseKey(hKey); +} + LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Find window to process that message @@ -414,6 +512,15 @@ void WindowsPlatform::PreInit(void* hInstance) OnSymbolsPathModified(); SymLocker.Unlock(); #endif + + GetWindowsVersion(WindowsName, VersionMajor, VersionMinor, VersionBuild); + + // Validate platform + if (VersionMajor < 6) + { + Error(TEXT("Not supported operating system version.")); + exit(-1); + } } bool WindowsPlatform::IsWindows10() @@ -473,6 +580,12 @@ bool WindowsPlatform::Init() return true; } + // Set lowest possible timer resolution for previous Windows versions + if (VersionMajor < 10 || (VersionMajor == 10 && VersionBuild < 17134)) + { + timeBeginPeriod(1); + } + DWORD tmp; Char buffer[256]; @@ -503,105 +616,7 @@ void WindowsPlatform::LogInfo() { Win32Platform::LogInfo(); - // Get OS version - { - String windowsName; - HKEY hKey; - LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"), 0, KEY_READ, &hKey); - if (lRes == ERROR_SUCCESS) - { - GetStringRegKey(hKey, TEXT("ProductName"), windowsName, TEXT("Windows")); - - DWORD currentMajorVersionNumber; - DWORD currentMinorVersionNumber; - GetDWORDRegKey(hKey, TEXT("CurrentMajorVersionNumber"), currentMajorVersionNumber, 0); - GetDWORDRegKey(hKey, TEXT("CurrentMinorVersionNumber"), currentMinorVersionNumber, 0); - VersionMajor = currentMajorVersionNumber; - VersionMinor = currentMinorVersionNumber; - - if (StringUtils::Compare(windowsName.Get(), TEXT("Windows 7"), 9) == 0) - { - VersionMajor = 6; - VersionMinor = 2; - } - - if (VersionMajor == 0 && VersionMinor == 0) - { - String windowsVersion; - GetStringRegKey(hKey, TEXT("CurrentVersion"), windowsVersion, TEXT("")); - - if (windowsVersion.HasChars()) - { - const int32 dot = windowsVersion.Find('.'); - if (dot != -1) - { - StringUtils::Parse(windowsVersion.Substring(0, dot).Get(), &VersionMajor); - StringUtils::Parse(windowsVersion.Substring(dot + 1).Get(), &VersionMinor); - } - } - } - } - else - { - if (IsWindowsServer()) - { - windowsName = TEXT("Windows Server"); - VersionMajor = 6; - VersionMinor = 3; - } - else if (IsWindows8Point1OrGreater()) - { - windowsName = TEXT("Windows 8.1"); - VersionMajor = 6; - VersionMinor = 3; - } - else if (IsWindows8OrGreater()) - { - windowsName = TEXT("Windows 8"); - VersionMajor = 6; - VersionMinor = 2; - } - else if (IsWindows7SP1OrGreater()) - { - windowsName = TEXT("Windows 7 SP1"); - VersionMajor = 6; - VersionMinor = 2; - } - else if (IsWindows7OrGreater()) - { - windowsName = TEXT("Windows 7"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaSP2OrGreater()) - { - windowsName = TEXT("Windows Vista SP2"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaSP1OrGreater()) - { - windowsName = TEXT("Windows Vista SP1"); - VersionMajor = 6; - VersionMinor = 1; - } - else if (IsWindowsVistaOrGreater()) - { - windowsName = TEXT("Windows Vista"); - VersionMajor = 6; - VersionMinor = 0; - } - } - RegCloseKey(hKey); - LOG(Info, "Microsoft {0} {1}-bit ({2}.{3})", windowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor); - } - - // Validate platform - if (VersionMajor < 6) - { - LOG(Error, "Not supported operating system version."); - exit(0); - } + LOG(Info, "Microsoft {0} {1}-bit ({2}.{3}.{4})", WindowsName, Platform::Is64BitPlatform() ? TEXT("64") : TEXT("32"), VersionMajor, VersionMinor, VersionBuild); // Check minimum amount of RAM auto memStats = Platform::GetMemoryStats();