From c01a14007710961add7498b8fab9c15a4430e20c Mon Sep 17 00:00:00 2001 From: GoaLitiuM Date: Sat, 29 May 2021 19:34:03 +0300 Subject: [PATCH 1/3] Refactor Windows version logging, log Windows build number --- .../Platform/Windows/WindowsPlatform.cpp | 208 +++++++++--------- 1 file changed, 108 insertions(+), 100 deletions(-) diff --git a/Source/Engine/Platform/Windows/WindowsPlatform.cpp b/Source/Engine/Platform/Windows/WindowsPlatform.cpp index 2086cd8dc..1c87c19cb 100644 --- a/Source/Engine/Platform/Windows/WindowsPlatform.cpp +++ b/Source/Engine/Platform/Windows/WindowsPlatform.cpp @@ -30,11 +30,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 +127,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 +511,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() @@ -503,105 +609,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(); From cc60814334667ff724cbbe167c8ef44cbdc34f0b Mon Sep 17 00:00:00 2001 From: GoaLitiuM Date: Sat, 29 May 2021 19:46:37 +0300 Subject: [PATCH 2/3] Increase accuracy of Windows Sleep function Windows 10 version 1803 (build 17134) and later versions support high-resolution waitable timer, which offers much better accuracy over Sleep function without the need of increasing the global timer resolution. For older versions of Windows, we reduce the timer resolution to 1ms and use normal waitable timer for sleeping. --- Source/Engine/Platform/Win32/Win32Platform.cpp | 16 +++++++++++++++- .../Engine/Platform/Windows/WindowsPlatform.cpp | 7 +++++++ 2 files changed, 22 insertions(+), 1 deletion(-) 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 1c87c19cb..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 @@ -579,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]; From e06200926f474597e433b3f24d8f18dcdf507439 Mon Sep 17 00:00:00 2001 From: GoaLitiuM Date: Sat, 29 May 2021 20:15:29 +0300 Subject: [PATCH 3/3] Sleep between frames to save CPU cycles --- Source/Engine/Engine/Engine.cpp | 37 ++++++++------------ Source/Engine/Engine/Time.cpp | 61 ++++++++++++++++++++------------- Source/Engine/Engine/Time.h | 14 +++++--- 3 files changed, 62 insertions(+), 50 deletions(-) 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); ///