From f70857eeaf32b7e811b6b73395a1441a69a432b8 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 29 Apr 2026 17:41:02 +0200 Subject: [PATCH 1/7] feature/fps-1percent-low-hud --- Core/GameEngine/Include/GameClient/Display.h | 1 + .../GameEngine/Include/GameClient/InGameUI.h | 3 + .../GameEngine/Source/GameClient/InGameUI.cpp | 34 +++++++- .../Include/W3DDevice/GameClient/W3DDisplay.h | 6 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 81 ++++++++++++++++--- .../Tools/GUIEdit/Include/GUIEditDisplay.h | 1 + .../GameEngine/Include/GameClient/InGameUI.h | 3 + .../GameEngine/Source/GameClient/InGameUI.cpp | 34 +++++++- .../Include/W3DDevice/GameClient/W3DDisplay.h | 6 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 81 ++++++++++++++++--- .../Tools/GUIEdit/Include/GUIEditDisplay.h | 1 + 11 files changed, 219 insertions(+), 32 deletions(-) diff --git a/Core/GameEngine/Include/GameClient/Display.h b/Core/GameEngine/Include/GameClient/Display.h index 8c022244206..752ee3c5adf 100644 --- a/Core/GameEngine/Include/GameClient/Display.h +++ b/Core/GameEngine/Include/GameClient/Display.h @@ -184,6 +184,7 @@ class Display : public SubsystemInterface virtual void setCinematicTextFrames( Int frames ) { m_cinematicTextFrames = frames; } virtual Real getAverageFPS() = 0; ///< returns the average FPS. + virtual Real getLow1PercentFPS() = 0; ///< returns the 1% low FPS. virtual Real getCurrentFPS() = 0; ///< returns the current FPS. virtual Int getLastFrameDrawCalls() = 0; ///< returns the number of draw calls issued in the previous frame diff --git a/Generals/Code/GameEngine/Include/GameClient/InGameUI.h b/Generals/Code/GameEngine/Include/GameClient/InGameUI.h index a6ecf5c4a3d..cdd923bdfa7 100644 --- a/Generals/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/Generals/Code/GameEngine/Include/GameClient/InGameUI.h @@ -747,16 +747,19 @@ friend class Drawable; // for selection/deselection transactions // Render FPS Counter DisplayString * m_renderFpsString; + DisplayString * m_renderFpsLowString; DisplayString * m_renderFpsLimitString; AsciiString m_renderFpsFont; Int m_renderFpsPointSize; Bool m_renderFpsBold; Coord2D m_renderFpsPosition; Color m_renderFpsColor; + Color m_renderFpsLowColor; Color m_renderFpsLimitColor; Color m_renderFpsDropColor; UnsignedInt m_renderFpsRefreshMs; UnsignedInt m_lastRenderFps; + UnsignedInt m_lastRenderFpsLow; UnsignedInt m_lastRenderFpsLimit; UnsignedInt m_lastRenderFpsUpdateMs; diff --git a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp index b0cd79ce60e..ca34e6c6c20 100644 --- a/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -888,6 +888,7 @@ const FieldParse InGameUI::s_fieldParseTable[] = { "RenderFpsBold", INI::parseBool, nullptr, offsetof( InGameUI, m_renderFpsBold ) }, { "RenderFpsPosition", INI::parseCoord2D, nullptr, offsetof( InGameUI, m_renderFpsPosition ) }, { "RenderFpsColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsColor ) }, + { "RenderFpsLowColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsLowColor ) }, { "RenderFpsLimitColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsLimitColor ) }, { "RenderFpsDropColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsDropColor ) }, { "RenderFpsRefreshMs", INI::parseUnsignedInt, nullptr, offsetof( InGameUI, m_renderFpsRefreshMs ) }, @@ -1133,6 +1134,7 @@ InGameUI::InGameUI() m_lastNetworkLatencyFrames = ~0u; m_renderFpsString = nullptr; + m_renderFpsLowString = nullptr; m_renderFpsLimitString = nullptr; m_renderFpsFont = "Tahoma"; m_renderFpsPointSize = TheGlobalData->m_renderFpsFontSize; @@ -1140,10 +1142,12 @@ InGameUI::InGameUI() m_renderFpsPosition.x = kHudAnchorX; m_renderFpsPosition.y = kHudAnchorY; m_renderFpsColor = GameMakeColor( 255, 255, 0, 255 ); + m_renderFpsLowColor = GameMakeColor( 180, 170, 120, 255 ); m_renderFpsLimitColor = GameMakeColor(119, 119, 119, 255); m_renderFpsDropColor = GameMakeColor( 0, 0, 0, 255 ); m_renderFpsRefreshMs = 1000; m_lastRenderFps = ~0u; + m_lastRenderFpsLow = ~0u; m_lastRenderFpsLimit = ~0u; m_lastRenderFpsUpdateMs = 0u; @@ -2215,6 +2219,8 @@ void InGameUI::freeCustomUiResources() m_networkLatencyString = nullptr; TheDisplayStringManager->freeDisplayString(m_renderFpsString); m_renderFpsString = nullptr; + TheDisplayStringManager->freeDisplayString(m_renderFpsLowString); + m_renderFpsLowString = nullptr; TheDisplayStringManager->freeDisplayString(m_renderFpsLimitString); m_renderFpsLimitString = nullptr; TheDisplayStringManager->freeDisplayString(m_systemTimeString); @@ -5868,6 +5874,12 @@ void InGameUI::refreshRenderFpsResources() m_lastRenderFpsUpdateMs = 0u; } + if (!m_renderFpsLowString) + { + m_renderFpsLowString = TheDisplayStringManager->newDisplayString(); + m_lastRenderFpsLow = ~0u; + } + if (!m_renderFpsLimitString) { m_renderFpsLimitString = TheDisplayStringManager->newDisplayString(); @@ -5878,6 +5890,7 @@ void InGameUI::refreshRenderFpsResources() Int adjustedRenderFpsFontSize = TheGlobalLanguageData->adjustFontSize(m_renderFpsPointSize); GameFont *fpsFont = TheWindowManager->winFindFont(m_renderFpsFont, adjustedRenderFpsFontSize, m_renderFpsBold); m_renderFpsString->setFont(fpsFont); + m_renderFpsLowString->setFont(fpsFont); m_renderFpsLimitString->setFont(fpsFont); if (m_renderFpsPointSize > 0) @@ -5990,6 +6003,15 @@ void InGameUI::updateRenderFpsString() m_renderFpsString->setText(fpsStr); m_lastRenderFps = renderFps; } + + const UnsignedInt renderFpsLow = (UnsignedInt)(TheDisplay->getLow1PercentFPS() + 0.5f); + if (renderFpsLow != m_lastRenderFpsLow) + { + UnicodeString lowStr; + lowStr.format(L"(%u)", renderFpsLow); + m_renderFpsLowString->setText(lowStr); + m_lastRenderFpsLow = renderFpsLow; + } } void InGameUI::drawNetworkLatency(Int &x, Int &y) @@ -6056,14 +6078,20 @@ void InGameUI::drawRenderFps(Int &x, Int &y) const Int drawY = kHudAnchorY + y; m_renderFpsString->draw(kHudAnchorX + x, drawY, m_renderFpsColor, m_renderFpsDropColor); - x += m_renderFpsString->getWidth(); + x += m_renderFpsString->getWidth() + kHudGapPx / 2; + m_renderFpsLowString->draw(kHudAnchorX + x, drawY, m_renderFpsLowColor, m_renderFpsDropColor); + x += m_renderFpsLowString->getWidth() + kHudGapPx / 2; m_renderFpsLimitString->draw(kHudAnchorX + x, drawY, m_renderFpsLimitColor, m_renderFpsDropColor); x += m_renderFpsLimitString->getWidth() + kHudGapPx; } else { - m_renderFpsString->draw(m_renderFpsPosition.x, m_renderFpsPosition.y, m_renderFpsColor, m_renderFpsDropColor); - m_renderFpsLimitString->draw(m_renderFpsPosition.x + m_renderFpsString->getWidth(), m_renderFpsPosition.y, m_renderFpsLimitColor, m_renderFpsDropColor); + Int currentX = m_renderFpsPosition.x; + m_renderFpsString->draw(currentX, m_renderFpsPosition.y, m_renderFpsColor, m_renderFpsDropColor); + currentX += m_renderFpsString->getWidth() + kHudGapPx / 2; + m_renderFpsLowString->draw(currentX, m_renderFpsPosition.y, m_renderFpsLowColor, m_renderFpsDropColor); + currentX += m_renderFpsLowString->getWidth() + kHudGapPx / 2; + m_renderFpsLimitString->draw(currentX, m_renderFpsPosition.y, m_renderFpsLimitColor, m_renderFpsDropColor); } } diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index a1ae9bcc298..72d26b0d1c8 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -148,6 +148,7 @@ class W3DDisplay : public Display void drawFPSStats(); ///< draw the fps on the screen virtual Real getAverageFPS() override; ///< return the average FPS. + virtual Real getLow1PercentFPS() override; ///< return the 1% low FPS. virtual Real getCurrentFPS() override; ///< return the current FPS. virtual Int getLastFrameDrawCalls() override; ///< returns the number of draw calls issued in the previous frame @@ -161,7 +162,7 @@ class W3DDisplay : public Display void drawCurrentDebugDisplay(); ///< draws current debug display void calculateTerrainLOD(); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border - void updateAverageFPS(); ///< calculate the average fps over the last 30 frames. + void updateAverageFPS(); ///< calculate the average and 1% low fps over time windows (0.5s and 3.0s). void setup2DRenderState(TextureClass *tex, DrawImageMode mode, Bool grayscale); virtual void onBeginBatch() override; virtual void onEndBatch() override; @@ -172,7 +173,8 @@ class W3DDisplay : public Display Render2DClass *m_2DRender; ///< interface for common 2D functions IRegion2D m_clipRegion; ///< the clipping region for images Bool m_isClippedEnabled; ///m_framesPerSecondLimit; + m_low1PercentFPS = TheGlobalData->m_framesPerSecondLimit; #if defined(RTS_DEBUG) m_timerAtCumuFPSStart = 0; #endif @@ -960,11 +961,12 @@ const UnsignedInt START_CUMU_FRAME = LOGICFRAMES_PER_SECOND / 2; // skip first h void W3DDisplay::updateAverageFPS() { - constexpr const Int FPS_HISTORY_SIZE = 30; - - static Int64 lastUpdateTime64 = 0; - static Int historyOffset = 0; + constexpr const Int FPS_HISTORY_SIZE = 1000; static Real fpsHistory[FPS_HISTORY_SIZE] = {0}; + static Real durationHistory[FPS_HISTORY_SIZE] = {0}; + static Int historyOffset = 0; + static Int historyCount = 0; + static Int64 lastUpdateTime64 = 0; const Int64 freq64 = getPerformanceCounterFrequency(); const Int64 time64 = getPerformanceCounter(); @@ -976,21 +978,73 @@ void W3DDisplay::updateAverageFPS() } #endif + if (lastUpdateTime64 == 0) + { + lastUpdateTime64 = time64; + return; + } + const Int64 timeDiff = time64 - lastUpdateTime64; // convert elapsed time to seconds Real elapsedSeconds = (Real)timeDiff/(Real)freq64; // append new sample to fps history. - if (historyOffset >= FPS_HISTORY_SIZE) - historyOffset = 0; + if (elapsedSeconds > 0) + { + m_currentFPS = 1.0f/elapsedSeconds; + fpsHistory[historyOffset] = m_currentFPS; + durationHistory[historyOffset] = elapsedSeconds; + historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; + if (historyCount < FPS_HISTORY_SIZE) historyCount++; + } + + // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. + if (historyCount > 0) + { + Real timeSum = 0; + Real fpsSum = 0; + Int avgSamples = 0; + Int lowSamples = 0; + Bool avgDone = FALSE; - m_currentFPS = 1.0f/elapsedSeconds; - fpsHistory[historyOffset++] = m_currentFPS; + for (Int i = 0; i < historyCount; ++i) + { + Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += durationHistory[idx]; + + lowSamples++; + + if (!avgDone) + { + fpsSum += fpsHistory[idx]; + avgSamples++; + if (timeSum >= 0.5f) avgDone = TRUE; + } + + if (timeSum >= 3.0f) break; + } - // determine average frame rate over our past history. - const Real sum = std::accumulate(fpsHistory, fpsHistory + FPS_HISTORY_SIZE, 0.0f); - m_averageFPS = sum / FPS_HISTORY_SIZE; + m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; + + // TheSuperHackers @feature One percent low FPS calculation (last 3.0 seconds) + // Throttle sorting to 1000ms intervals to match HUD refresh and save CPU + static UnsignedInt lastLowUpdate = 0; + UnsignedInt now = timeGetTime(); + if (now - lastLowUpdate >= 1000) + { + lastLowUpdate = now; + static Real sortBuffer[FPS_HISTORY_SIZE]; + for (Int i = 0; i < lowSamples; ++i) + { + Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + sortBuffer[i] = fpsHistory[idx]; + } + const Int lowCount = std::max(lowSamples / 100, 1); + std::nth_element(sortBuffer, sortBuffer + lowCount, sortBuffer + lowSamples); + m_low1PercentFPS = std::accumulate(sortBuffer, sortBuffer + lowCount, 0.0f) / (Real)lowCount; + } + } lastUpdateTime64 = time64; } @@ -1693,6 +1747,11 @@ Real W3DDisplay::getAverageFPS() return m_averageFPS; } +Real W3DDisplay::getLow1PercentFPS() +{ + return m_low1PercentFPS; +} + Real W3DDisplay::getCurrentFPS() { return m_currentFPS; diff --git a/Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h b/Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h index 8679d5fabd9..eeef74278a5 100644 --- a/Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h +++ b/Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h @@ -124,6 +124,7 @@ class GUIEditDisplay : public Display #endif virtual Real getAverageFPS() override { return 0; } + virtual Real getLow1PercentFPS() override { return 0; } virtual Real getCurrentFPS() override { return 0; } virtual Int getLastFrameDrawCalls() override { return 0; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h index 675fd35f8de..d7609495254 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/InGameUI.h @@ -769,16 +769,19 @@ friend class Drawable; // for selection/deselection transactions // Render FPS Counter DisplayString * m_renderFpsString; + DisplayString * m_renderFpsLowString; DisplayString * m_renderFpsLimitString; AsciiString m_renderFpsFont; Int m_renderFpsPointSize; Bool m_renderFpsBold; Coord2D m_renderFpsPosition; Color m_renderFpsColor; + Color m_renderFpsLowColor; Color m_renderFpsLimitColor; Color m_renderFpsDropColor; UnsignedInt m_renderFpsRefreshMs; UnsignedInt m_lastRenderFps; + UnsignedInt m_lastRenderFpsLow; UnsignedInt m_lastRenderFpsLimit; UnsignedInt m_lastRenderFpsUpdateMs; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp index 449924ec54f..2c17c0601d8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/InGameUI.cpp @@ -917,6 +917,7 @@ const FieldParse InGameUI::s_fieldParseTable[] = { "RenderFpsBold", INI::parseBool, nullptr, offsetof( InGameUI, m_renderFpsBold ) }, { "RenderFpsPosition", INI::parseCoord2D, nullptr, offsetof( InGameUI, m_renderFpsPosition ) }, { "RenderFpsColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsColor ) }, + { "RenderFpsLowColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsLowColor ) }, { "RenderFpsLimitColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsLimitColor ) }, { "RenderFpsDropColor", INI::parseColorInt, nullptr, offsetof( InGameUI, m_renderFpsDropColor ) }, { "RenderFpsRefreshMs", INI::parseUnsignedInt, nullptr, offsetof( InGameUI, m_renderFpsRefreshMs ) }, @@ -1163,6 +1164,7 @@ InGameUI::InGameUI() m_lastNetworkLatencyFrames = ~0u; m_renderFpsString = nullptr; + m_renderFpsLowString = nullptr; m_renderFpsLimitString = nullptr; m_renderFpsFont = "Tahoma"; m_renderFpsPointSize = TheGlobalData->m_renderFpsFontSize; @@ -1170,10 +1172,12 @@ InGameUI::InGameUI() m_renderFpsPosition.x = kHudAnchorX; m_renderFpsPosition.y = kHudAnchorY; m_renderFpsColor = GameMakeColor( 255, 255, 0, 255 ); + m_renderFpsLowColor = GameMakeColor( 180, 170, 120, 255 ); m_renderFpsLimitColor = GameMakeColor(119, 119, 119, 255); m_renderFpsDropColor = GameMakeColor( 0, 0, 0, 255 ); m_renderFpsRefreshMs = 1000; m_lastRenderFps = ~0u; + m_lastRenderFpsLow = ~0u; m_lastRenderFpsLimit = ~0u; m_lastRenderFpsUpdateMs = 0u; @@ -2272,6 +2276,8 @@ void InGameUI::freeCustomUiResources() m_networkLatencyString = nullptr; TheDisplayStringManager->freeDisplayString(m_renderFpsString); m_renderFpsString = nullptr; + TheDisplayStringManager->freeDisplayString(m_renderFpsLowString); + m_renderFpsLowString = nullptr; TheDisplayStringManager->freeDisplayString(m_renderFpsLimitString); m_renderFpsLimitString = nullptr; TheDisplayStringManager->freeDisplayString(m_systemTimeString); @@ -6041,6 +6047,12 @@ void InGameUI::refreshRenderFpsResources() m_lastRenderFpsUpdateMs = 0u; } + if (!m_renderFpsLowString) + { + m_renderFpsLowString = TheDisplayStringManager->newDisplayString(); + m_lastRenderFpsLow = ~0u; + } + if (!m_renderFpsLimitString) { m_renderFpsLimitString = TheDisplayStringManager->newDisplayString(); @@ -6051,6 +6063,7 @@ void InGameUI::refreshRenderFpsResources() Int adjustedRenderFpsFontSize = TheGlobalLanguageData->adjustFontSize(m_renderFpsPointSize); GameFont *fpsFont = TheWindowManager->winFindFont(m_renderFpsFont, adjustedRenderFpsFontSize, m_renderFpsBold); m_renderFpsString->setFont(fpsFont); + m_renderFpsLowString->setFont(fpsFont); m_renderFpsLimitString->setFont(fpsFont); if (m_renderFpsPointSize > 0) @@ -6163,6 +6176,15 @@ void InGameUI::updateRenderFpsString() m_renderFpsString->setText(fpsStr); m_lastRenderFps = renderFps; } + + const UnsignedInt renderFpsLow = (UnsignedInt)(TheDisplay->getLow1PercentFPS() + 0.5f); + if (renderFpsLow != m_lastRenderFpsLow) + { + UnicodeString fpsLowStr; + fpsLowStr.format(L"(%u)", renderFpsLow); + m_renderFpsLowString->setText(fpsLowStr); + m_lastRenderFpsLow = renderFpsLow; + } } void InGameUI::drawNetworkLatency(Int &x, Int &y) @@ -6229,14 +6251,20 @@ void InGameUI::drawRenderFps(Int &x, Int &y) const Int drawY = kHudAnchorY + y; m_renderFpsString->draw(kHudAnchorX + x, drawY, m_renderFpsColor, m_renderFpsDropColor); - x += m_renderFpsString->getWidth(); + x += m_renderFpsString->getWidth() + kHudGapPx / 2; + m_renderFpsLowString->draw(kHudAnchorX + x, drawY, m_renderFpsLowColor, m_renderFpsDropColor); + x += m_renderFpsLowString->getWidth() + kHudGapPx / 2; m_renderFpsLimitString->draw(kHudAnchorX + x, drawY, m_renderFpsLimitColor, m_renderFpsDropColor); x += m_renderFpsLimitString->getWidth() + kHudGapPx; } else { - m_renderFpsString->draw(m_renderFpsPosition.x, m_renderFpsPosition.y, m_renderFpsColor, m_renderFpsDropColor); - m_renderFpsLimitString->draw(m_renderFpsPosition.x + m_renderFpsString->getWidth(), m_renderFpsPosition.y, m_renderFpsLimitColor, m_renderFpsDropColor); + Int currentX = m_renderFpsPosition.x; + m_renderFpsString->draw(currentX, m_renderFpsPosition.y, m_renderFpsColor, m_renderFpsDropColor); + currentX += m_renderFpsString->getWidth() + kHudGapPx / 2; + m_renderFpsLowString->draw(currentX, m_renderFpsPosition.y, m_renderFpsLowColor, m_renderFpsDropColor); + currentX += m_renderFpsLowString->getWidth() + kHudGapPx / 2; + m_renderFpsLimitString->draw(currentX, m_renderFpsPosition.y, m_renderFpsLimitColor, m_renderFpsDropColor); } } diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index cf2e537f9e1..837d9a0c7eb 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -148,6 +148,7 @@ class W3DDisplay : public Display void drawFPSStats(); ///< draw the fps on the screen virtual Real getAverageFPS() override; ///< return the average FPS. + virtual Real getLow1PercentFPS() override; ///< return the 1% low FPS. virtual Real getCurrentFPS() override; ///< return the current FPS. virtual Int getLastFrameDrawCalls() override; ///< returns the number of draw calls issued in the previous frame @@ -161,7 +162,7 @@ class W3DDisplay : public Display void drawCurrentDebugDisplay(); ///< draws current debug display void calculateTerrainLOD(); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border - void updateAverageFPS(); ///< calculate the average fps over the last 30 frames. + void updateAverageFPS(); ///< calculate the average and 1% low fps over time windows (0.5s and 3.0s). void setup2DRenderState(TextureClass *tex, DrawImageMode mode, Bool grayscale); virtual void onBeginBatch() override; virtual void onEndBatch() override; @@ -172,7 +173,8 @@ class W3DDisplay : public Display Render2DClass *m_2DRender; ///< interface for common 2D functions IRegion2D m_clipRegion; ///< the clipping region for images Bool m_isClippedEnabled; ///m_framesPerSecondLimit; + m_low1PercentFPS = TheGlobalData->m_framesPerSecondLimit; #if defined(RTS_DEBUG) m_timerAtCumuFPSStart = 0; #endif @@ -1011,11 +1012,12 @@ const UnsignedInt START_CUMU_FRAME = LOGICFRAMES_PER_SECOND / 2; // skip first h void W3DDisplay::updateAverageFPS() { - constexpr const Int FPS_HISTORY_SIZE = 30; - - static Int64 lastUpdateTime64 = 0; - static Int historyOffset = 0; + constexpr const Int FPS_HISTORY_SIZE = 1000; static Real fpsHistory[FPS_HISTORY_SIZE] = {0}; + static Real durationHistory[FPS_HISTORY_SIZE] = {0}; + static Int historyOffset = 0; + static Int historyCount = 0; + static Int64 lastUpdateTime64 = 0; const Int64 freq64 = getPerformanceCounterFrequency(); const Int64 time64 = getPerformanceCounter(); @@ -1027,21 +1029,73 @@ void W3DDisplay::updateAverageFPS() } #endif + if (lastUpdateTime64 == 0) + { + lastUpdateTime64 = time64; + return; + } + const Int64 timeDiff = time64 - lastUpdateTime64; // convert elapsed time to seconds Real elapsedSeconds = (Real)timeDiff/(Real)freq64; // append new sample to fps history. - if (historyOffset >= FPS_HISTORY_SIZE) - historyOffset = 0; + if (elapsedSeconds > 0) + { + m_currentFPS = 1.0f/elapsedSeconds; + fpsHistory[historyOffset] = m_currentFPS; + durationHistory[historyOffset] = elapsedSeconds; + historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; + if (historyCount < FPS_HISTORY_SIZE) historyCount++; + } + + // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. + if (historyCount > 0) + { + Real timeSum = 0; + Real fpsSum = 0; + Int avgSamples = 0; + Int lowSamples = 0; + Bool avgDone = FALSE; - m_currentFPS = 1.0f/elapsedSeconds; - fpsHistory[historyOffset++] = m_currentFPS; + for (Int i = 0; i < historyCount; ++i) + { + Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += durationHistory[idx]; + + lowSamples++; + + if (!avgDone) + { + fpsSum += fpsHistory[idx]; + avgSamples++; + if (timeSum >= 0.5f) avgDone = TRUE; + } + + if (timeSum >= 3.0f) break; + } - // determine average frame rate over our past history. - const Real sum = std::accumulate(fpsHistory, fpsHistory + FPS_HISTORY_SIZE, 0.0f); - m_averageFPS = sum / FPS_HISTORY_SIZE; + m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; + + // TheSuperHackers @feature One percent low FPS calculation (last 3.0 seconds) + // Throttle sorting to 1000ms intervals to match HUD refresh and save CPU + static UnsignedInt lastLowUpdate = 0; + UnsignedInt now = timeGetTime(); + if (now - lastLowUpdate >= 1000) + { + lastLowUpdate = now; + static Real sortBuffer[FPS_HISTORY_SIZE]; + for (Int i = 0; i < lowSamples; ++i) + { + Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + sortBuffer[i] = fpsHistory[idx]; + } + const Int lowCount = std::max(lowSamples / 100, 1); + std::nth_element(sortBuffer, sortBuffer + lowCount, sortBuffer + lowSamples); + m_low1PercentFPS = std::accumulate(sortBuffer, sortBuffer + lowCount, 0.0f) / (Real)lowCount; + } + } lastUpdateTime64 = time64; } @@ -1763,6 +1817,11 @@ Real W3DDisplay::getAverageFPS() return m_averageFPS; } +Real W3DDisplay::getLow1PercentFPS() +{ + return m_low1PercentFPS; +} + Real W3DDisplay::getCurrentFPS() { return m_currentFPS; diff --git a/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h b/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h index f554dd614c8..cfb7036f9f1 100644 --- a/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h +++ b/GeneralsMD/Code/Tools/GUIEdit/Include/GUIEditDisplay.h @@ -124,6 +124,7 @@ class GUIEditDisplay : public Display #endif virtual Real getAverageFPS() override { return 0; } + virtual Real getLow1PercentFPS() override { return 0; } virtual Real getCurrentFPS() override { return 0; } virtual Int getLastFrameDrawCalls() override { return 0; } From c062022bbac681c0ccf5eb5c9a518279f41a2f88 Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Wed, 29 Apr 2026 18:03:30 +0200 Subject: [PATCH 2/7] fix if style violations --- .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 15 ++++++++++++--- .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 15 ++++++++++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 75108e685ba..9d7e74b34f2 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -996,7 +996,10 @@ void W3DDisplay::updateAverageFPS() fpsHistory[historyOffset] = m_currentFPS; durationHistory[historyOffset] = elapsedSeconds; historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; - if (historyCount < FPS_HISTORY_SIZE) historyCount++; + if (historyCount < FPS_HISTORY_SIZE) + { + historyCount++; + } } // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. @@ -1019,10 +1022,16 @@ void W3DDisplay::updateAverageFPS() { fpsSum += fpsHistory[idx]; avgSamples++; - if (timeSum >= 0.5f) avgDone = TRUE; + if (timeSum >= 0.5f) + { + avgDone = TRUE; + } } - if (timeSum >= 3.0f) break; + if (timeSum >= 3.0f) + { + break; + } } m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 630351d63ad..03b08b5f190 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1047,7 +1047,10 @@ void W3DDisplay::updateAverageFPS() fpsHistory[historyOffset] = m_currentFPS; durationHistory[historyOffset] = elapsedSeconds; historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; - if (historyCount < FPS_HISTORY_SIZE) historyCount++; + if (historyCount < FPS_HISTORY_SIZE) + { + historyCount++; + } } // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. @@ -1070,10 +1073,16 @@ void W3DDisplay::updateAverageFPS() { fpsSum += fpsHistory[idx]; avgSamples++; - if (timeSum >= 0.5f) avgDone = TRUE; + if (timeSum >= 0.5f) + { + avgDone = TRUE; + } } - if (timeSum >= 3.0f) break; + if (timeSum >= 3.0f) + { + break; + } } m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; From a303d261b5213b83e410bae551074f8ca11c618c Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 1 May 2026 01:13:05 +0200 Subject: [PATCH 3/7] Refactor performance monitoring into smaller SRP methods. Move FPS history state into W3DDisplay members. Implement accurate time-based windowing for frame metrics. Use ceiling logic for improved 1% low accuracy. Optimize percentile calculation using efficient selection algorithm. Rename and centralize performance update call sites. Increase history buffer for stable high-FPS monitoring. --- .../Include/W3DDevice/GameClient/W3DDisplay.h | 12 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 164 +++++++++--------- .../Include/W3DDevice/GameClient/W3DDisplay.h | 12 +- .../W3DDevice/GameClient/W3DDisplay.cpp | 164 +++++++++--------- 4 files changed, 190 insertions(+), 162 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 72d26b0d1c8..b0218b752f1 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -162,7 +162,10 @@ class W3DDisplay : public Display void drawCurrentDebugDisplay(); ///< draws current debug display void calculateTerrainLOD(); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border - void updateAverageFPS(); ///< calculate the average and 1% low fps over time windows (0.5s and 3.0s). + void updatePerformanceMetrics(); ///< update the average and 1% low fps metrics. + void addFpsSample(Real elapsedSeconds); ///< add a new sample to the history buffer. + Real calculateAverageFPS(Real windowSeconds); ///< calculate average FPS over a time window. + Real calculateLow1PercentFPS(Real windowSeconds); ///< calculate 1% low FPS over a time window. void setup2DRenderState(TextureClass *tex, DrawImageMode mode, Bool grayscale); virtual void onBeginBatch() override; virtual void onEndBatch() override; @@ -177,6 +180,13 @@ class W3DDisplay : public Display Real m_low1PercentFPS; ///<1% low fps. Real m_currentFPS; ///getFrame() == START_CUMU_FRAME) - { - m_timerAtCumuFPSStart = time64; - } -#endif + m_historyOffset = (m_historyOffset + 1) % FPS_HISTORY_SIZE; + if (m_historyCount < FPS_HISTORY_SIZE) m_historyCount++; +} + +Real W3DDisplay::calculateAverageFPS(Real windowSeconds) +{ + if (m_historyCount == 0) return m_currentFPS; + + Real timeSum = 0; + Real fpsSum = 0; + Int samples = 0; - if (lastUpdateTime64 == 0) + for (Int i = 0; i < m_historyCount; ++i) { - lastUpdateTime64 = time64; - return; + Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += m_durationHistory[idx]; + fpsSum += m_fpsHistory[idx]; + samples++; + + if (timeSum >= windowSeconds) break; } - const Int64 timeDiff = time64 - lastUpdateTime64; + return (samples > 0) ? (fpsSum / (Real)samples) : m_currentFPS; +} - // convert elapsed time to seconds - Real elapsedSeconds = (Real)timeDiff/(Real)freq64; +Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) +{ + if (m_historyCount == 0) return m_currentFPS; + + static Real sortBuffer[FPS_HISTORY_SIZE]; + Real timeSum = 0; + Int sampleCount = 0; - // append new sample to fps history. - if (elapsedSeconds > 0) + for (Int i = 0; i < m_historyCount; ++i) { - m_currentFPS = 1.0f/elapsedSeconds; - fpsHistory[historyOffset] = m_currentFPS; - durationHistory[historyOffset] = elapsedSeconds; - historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; - if (historyCount < FPS_HISTORY_SIZE) - { - historyCount++; - } + Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += m_durationHistory[idx]; + sortBuffer[sampleCount++] = m_fpsHistory[idx]; + + if (timeSum >= windowSeconds) break; } - // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. - if (historyCount > 0) + if (sampleCount == 0) return m_currentFPS; + + const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); + + std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); + + Real lowSum = 0; + for (Int i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + + return lowSum / (Real)bottomSampleCount; +} + +void W3DDisplay::updatePerformanceMetrics() +{ + const Int64 freq64 = getPerformanceCounterFrequency(); + const Int64 time64 = getPerformanceCounter(); + +#if defined(RTS_DEBUG) + if (TheGameLogic->getFrame() == START_CUMU_FRAME) m_timerAtCumuFPSStart = time64; +#endif + + if (m_lastUpdateTime64 == 0) { - Real timeSum = 0; - Real fpsSum = 0; - Int avgSamples = 0; - Int lowSamples = 0; - Bool avgDone = FALSE; + m_lastUpdateTime64 = time64; + return; + } - for (Int i = 0; i < historyCount; ++i) - { - Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; - timeSum += durationHistory[idx]; - - lowSamples++; - - if (!avgDone) - { - fpsSum += fpsHistory[idx]; - avgSamples++; - if (timeSum >= 0.5f) - { - avgDone = TRUE; - } - } - - if (timeSum >= 3.0f) - { - break; - } - } + const Int64 timeDiff = time64 - m_lastUpdateTime64; + Real elapsedSeconds = (Real)timeDiff / (Real)freq64; - m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; + addFpsSample(elapsedSeconds); + m_averageFPS = calculateAverageFPS(0.5f); - // TheSuperHackers @feature One percent low FPS calculation (last 3.0 seconds) - // Throttle sorting to 1000ms intervals to match HUD refresh and save CPU - static UnsignedInt lastLowUpdate = 0; - UnsignedInt now = timeGetTime(); - if (now - lastLowUpdate >= 1000) - { - lastLowUpdate = now; - static Real sortBuffer[FPS_HISTORY_SIZE]; - for (Int i = 0; i < lowSamples; ++i) - { - Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; - sortBuffer[i] = fpsHistory[idx]; - } - const Int lowCount = std::max(lowSamples / 100, 1); - std::nth_element(sortBuffer, sortBuffer + lowCount, sortBuffer + lowSamples); - m_low1PercentFPS = std::accumulate(sortBuffer, sortBuffer + lowCount, 0.0f) / (Real)lowCount; - } + static UnsignedInt lastLowUpdate = 0; + UnsignedInt now = timeGetTime(); + if (now - lastLowUpdate >= 1000) + { + lastLowUpdate = now; + m_low1PercentFPS = calculateLow1PercentFPS(3.0f); } - lastUpdateTime64 = time64; + m_lastUpdateTime64 = time64; } #if defined(RTS_DEBUG) //debug hack to view object under mouse stats @@ -1795,7 +1799,7 @@ void W3DDisplay::draw() if (TheGlobalData->m_headless) return; - updateAverageFPS(); + updatePerformanceMetrics(); if (TheGlobalData->m_enableDynamicLOD && TheGameLogic->getShowDynamicLOD()) { DynamicGameLODLevel lod=TheGameLODManager->findDynamicLODLevel(m_averageFPS); diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 837d9a0c7eb..089e2c53ff9 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -162,7 +162,10 @@ class W3DDisplay : public Display void drawCurrentDebugDisplay(); ///< draws current debug display void calculateTerrainLOD(); ///< Calculate terrain LOD. void renderLetterBox(UnsignedInt time); ///< draw letter box border - void updateAverageFPS(); ///< calculate the average and 1% low fps over time windows (0.5s and 3.0s). + void updatePerformanceMetrics(); ///< update the average and 1% low fps metrics. + void addFpsSample(Real elapsedSeconds); ///< add a new sample to the history buffer. + Real calculateAverageFPS(Real windowSeconds); ///< calculate average FPS over a time window. + Real calculateLow1PercentFPS(Real windowSeconds); ///< calculate 1% low FPS over a time window. void setup2DRenderState(TextureClass *tex, DrawImageMode mode, Bool grayscale); virtual void onBeginBatch() override; virtual void onEndBatch() override; @@ -177,6 +180,13 @@ class W3DDisplay : public Display Real m_low1PercentFPS; ///<1% low fps. Real m_currentFPS; ///getFrame() == START_CUMU_FRAME) - { - m_timerAtCumuFPSStart = time64; - } -#endif + m_historyOffset = (m_historyOffset + 1) % FPS_HISTORY_SIZE; + if (m_historyCount < FPS_HISTORY_SIZE) m_historyCount++; +} + +Real W3DDisplay::calculateAverageFPS(Real windowSeconds) +{ + if (m_historyCount == 0) return m_currentFPS; + + Real timeSum = 0; + Real fpsSum = 0; + Int samples = 0; - if (lastUpdateTime64 == 0) + for (Int i = 0; i < m_historyCount; ++i) { - lastUpdateTime64 = time64; - return; + Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += m_durationHistory[idx]; + fpsSum += m_fpsHistory[idx]; + samples++; + + if (timeSum >= windowSeconds) break; } - const Int64 timeDiff = time64 - lastUpdateTime64; + return (samples > 0) ? (fpsSum / (Real)samples) : m_currentFPS; +} - // convert elapsed time to seconds - Real elapsedSeconds = (Real)timeDiff/(Real)freq64; +Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) +{ + if (m_historyCount == 0) return m_currentFPS; + + static Real sortBuffer[FPS_HISTORY_SIZE]; + Real timeSum = 0; + Int sampleCount = 0; - // append new sample to fps history. - if (elapsedSeconds > 0) + for (Int i = 0; i < m_historyCount; ++i) { - m_currentFPS = 1.0f/elapsedSeconds; - fpsHistory[historyOffset] = m_currentFPS; - durationHistory[historyOffset] = elapsedSeconds; - historyOffset = (historyOffset + 1) % FPS_HISTORY_SIZE; - if (historyCount < FPS_HISTORY_SIZE) - { - historyCount++; - } + Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; + timeSum += m_durationHistory[idx]; + sortBuffer[sampleCount++] = m_fpsHistory[idx]; + + if (timeSum >= windowSeconds) break; } - // determine average frame rate over the last 0.5 seconds and 1% low over 3.0 seconds. - if (historyCount > 0) + if (sampleCount == 0) return m_currentFPS; + + const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); + + std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); + + Real lowSum = 0; + for (Int i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + + return lowSum / (Real)bottomSampleCount; +} + +void W3DDisplay::updatePerformanceMetrics() +{ + const Int64 freq64 = getPerformanceCounterFrequency(); + const Int64 time64 = getPerformanceCounter(); + +#if defined(RTS_DEBUG) + if (TheGameLogic->getFrame() == START_CUMU_FRAME) m_timerAtCumuFPSStart = time64; +#endif + + if (m_lastUpdateTime64 == 0) { - Real timeSum = 0; - Real fpsSum = 0; - Int avgSamples = 0; - Int lowSamples = 0; - Bool avgDone = FALSE; + m_lastUpdateTime64 = time64; + return; + } - for (Int i = 0; i < historyCount; ++i) - { - Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; - timeSum += durationHistory[idx]; - - lowSamples++; - - if (!avgDone) - { - fpsSum += fpsHistory[idx]; - avgSamples++; - if (timeSum >= 0.5f) - { - avgDone = TRUE; - } - } - - if (timeSum >= 3.0f) - { - break; - } - } + const Int64 timeDiff = time64 - m_lastUpdateTime64; + Real elapsedSeconds = (Real)timeDiff / (Real)freq64; - m_averageFPS = avgSamples > 0 ? fpsSum / (Real)avgSamples : m_currentFPS; + addFpsSample(elapsedSeconds); + m_averageFPS = calculateAverageFPS(0.5f); - // TheSuperHackers @feature One percent low FPS calculation (last 3.0 seconds) - // Throttle sorting to 1000ms intervals to match HUD refresh and save CPU - static UnsignedInt lastLowUpdate = 0; - UnsignedInt now = timeGetTime(); - if (now - lastLowUpdate >= 1000) - { - lastLowUpdate = now; - static Real sortBuffer[FPS_HISTORY_SIZE]; - for (Int i = 0; i < lowSamples; ++i) - { - Int idx = (historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; - sortBuffer[i] = fpsHistory[idx]; - } - const Int lowCount = std::max(lowSamples / 100, 1); - std::nth_element(sortBuffer, sortBuffer + lowCount, sortBuffer + lowSamples); - m_low1PercentFPS = std::accumulate(sortBuffer, sortBuffer + lowCount, 0.0f) / (Real)lowCount; - } + static UnsignedInt lastLowUpdate = 0; + UnsignedInt now = timeGetTime(); + if (now - lastLowUpdate >= 1000) + { + lastLowUpdate = now; + m_low1PercentFPS = calculateLow1PercentFPS(3.0f); } - lastUpdateTime64 = time64; + m_lastUpdateTime64 = time64; } #if defined(RTS_DEBUG) //debug hack to view object under mouse stats @@ -1865,7 +1869,7 @@ void W3DDisplay::draw() if (TheGlobalData->m_headless) return; - updateAverageFPS(); + updatePerformanceMetrics(); if (TheGlobalData->m_enableDynamicLOD && TheGameLogic->getShowDynamicLOD()) { DynamicGameLODLevel lod=TheGameLODManager->findDynamicLODLevel(m_averageFPS); From 514a3117f0f2be1a1939fa2c907b9f233a5aa00e Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 1 May 2026 01:18:22 +0200 Subject: [PATCH 4/7] vc6 fix in W3DDisplay static constexpr --- .../GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h | 2 +- .../GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index b0218b752f1..0032fb14ab7 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -180,7 +180,7 @@ class W3DDisplay : public Display Real m_low1PercentFPS; ///<1% low fps. Real m_currentFPS; /// Date: Fri, 1 May 2026 01:24:35 +0200 Subject: [PATCH 5/7] vc6 fix for W3DDisplay Int i; --- .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 5 +++-- .../Source/W3DDevice/GameClient/W3DDisplay.cpp | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index c391f2ea5ff..d0e20341a69 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1008,8 +1008,9 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) static Real sortBuffer[FPS_HISTORY_SIZE]; Real timeSum = 0; Int sampleCount = 0; + Int i; - for (Int i = 0; i < m_historyCount; ++i) + for (i = 0; i < m_historyCount; ++i) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; @@ -1025,7 +1026,7 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); Real lowSum = 0; - for (Int i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + for (i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; return lowSum / (Real)bottomSampleCount; } diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 0371732b623..bb41508fef5 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1059,8 +1059,9 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) static Real sortBuffer[FPS_HISTORY_SIZE]; Real timeSum = 0; Int sampleCount = 0; + Int i; - for (Int i = 0; i < m_historyCount; ++i) + for (i = 0; i < m_historyCount; ++i) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; @@ -1076,7 +1077,7 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); Real lowSum = 0; - for (Int i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + for (i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; return lowSum / (Real)bottomSampleCount; } From ecd3596df2fb351472af2c5e2c15dc970f19d3ef Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 1 May 2026 01:27:18 +0200 Subject: [PATCH 6/7] Format if-bodies to separate lines for style. Update average FPS math to use time-weighted mean. Move sortBuffer to class members for consistency. --- .../Include/W3DDevice/GameClient/W3DDisplay.h | 1 + .../W3DDevice/GameClient/W3DDisplay.cpp | 54 +++++++++++++------ .../Include/W3DDevice/GameClient/W3DDisplay.h | 1 + .../W3DDevice/GameClient/W3DDisplay.cpp | 54 +++++++++++++------ 4 files changed, 80 insertions(+), 30 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 0032fb14ab7..453a08d01ae 100644 --- a/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/Generals/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -183,6 +183,7 @@ class W3DDisplay : public Display enum { FPS_HISTORY_SIZE = 5000 }; // covers 5s at 1000 FPS, degrades gracefully beyond Real m_fpsHistory[FPS_HISTORY_SIZE]; Real m_durationHistory[FPS_HISTORY_SIZE]; + Real m_sortBuffer[FPS_HISTORY_SIZE]; Int m_historyOffset; Int m_historyCount; Int64 m_lastUpdateTime64; diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index d0e20341a69..67662f107e6 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -970,42 +970,54 @@ const UnsignedInt START_CUMU_FRAME = LOGICFRAMES_PER_SECOND / 2; // skip first h void W3DDisplay::addFpsSample(Real elapsedSeconds) { - if (elapsedSeconds <= 0.0f) return; + if (elapsedSeconds <= 0.0f) + { + return; + } m_currentFPS = 1.0f / elapsedSeconds; m_fpsHistory[m_historyOffset] = m_currentFPS; m_durationHistory[m_historyOffset] = elapsedSeconds; m_historyOffset = (m_historyOffset + 1) % FPS_HISTORY_SIZE; - if (m_historyCount < FPS_HISTORY_SIZE) m_historyCount++; + if (m_historyCount < FPS_HISTORY_SIZE) + { + m_historyCount++; + } } Real W3DDisplay::calculateAverageFPS(Real windowSeconds) { - if (m_historyCount == 0) return m_currentFPS; + if (m_historyCount == 0) + { + return m_currentFPS; + } Real timeSum = 0; - Real fpsSum = 0; Int samples = 0; for (Int i = 0; i < m_historyCount; ++i) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; - fpsSum += m_fpsHistory[idx]; samples++; - if (timeSum >= windowSeconds) break; + if (timeSum >= windowSeconds) + { + break; + } } - return (samples > 0) ? (fpsSum / (Real)samples) : m_currentFPS; + return (timeSum > 0) ? ((Real)samples / timeSum) : m_currentFPS; } Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) { - if (m_historyCount == 0) return m_currentFPS; + if (m_historyCount == 0) + { + return m_currentFPS; + } - static Real sortBuffer[FPS_HISTORY_SIZE]; Real timeSum = 0; Int sampleCount = 0; Int i; @@ -1014,19 +1026,28 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; - sortBuffer[sampleCount++] = m_fpsHistory[idx]; + m_sortBuffer[sampleCount++] = m_fpsHistory[idx]; - if (timeSum >= windowSeconds) break; + if (timeSum >= windowSeconds) + { + break; + } } - if (sampleCount == 0) return m_currentFPS; + if (sampleCount == 0) + { + return m_currentFPS; + } const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); - std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); + std::nth_element(m_sortBuffer, m_sortBuffer + bottomSampleCount, m_sortBuffer + sampleCount); Real lowSum = 0; - for (i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + for (i = 0; i < bottomSampleCount; ++i) + { + lowSum += m_sortBuffer[i]; + } return lowSum / (Real)bottomSampleCount; } @@ -1037,7 +1058,10 @@ void W3DDisplay::updatePerformanceMetrics() const Int64 time64 = getPerformanceCounter(); #if defined(RTS_DEBUG) - if (TheGameLogic->getFrame() == START_CUMU_FRAME) m_timerAtCumuFPSStart = time64; + if (TheGameLogic->getFrame() == START_CUMU_FRAME) + { + m_timerAtCumuFPSStart = time64; + } #endif if (m_lastUpdateTime64 == 0) diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index 7b606499c1b..344a90c9f34 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -183,6 +183,7 @@ class W3DDisplay : public Display enum { FPS_HISTORY_SIZE = 5000 }; // covers 5s at 1000 FPS, degrades gracefully beyond Real m_fpsHistory[FPS_HISTORY_SIZE]; Real m_durationHistory[FPS_HISTORY_SIZE]; + Real m_sortBuffer[FPS_HISTORY_SIZE]; Int m_historyOffset; Int m_historyCount; Int64 m_lastUpdateTime64; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index bb41508fef5..6142b8d775b 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1021,42 +1021,54 @@ const UnsignedInt START_CUMU_FRAME = LOGICFRAMES_PER_SECOND / 2; // skip first h void W3DDisplay::addFpsSample(Real elapsedSeconds) { - if (elapsedSeconds <= 0.0f) return; + if (elapsedSeconds <= 0.0f) + { + return; + } m_currentFPS = 1.0f / elapsedSeconds; m_fpsHistory[m_historyOffset] = m_currentFPS; m_durationHistory[m_historyOffset] = elapsedSeconds; m_historyOffset = (m_historyOffset + 1) % FPS_HISTORY_SIZE; - if (m_historyCount < FPS_HISTORY_SIZE) m_historyCount++; + if (m_historyCount < FPS_HISTORY_SIZE) + { + m_historyCount++; + } } Real W3DDisplay::calculateAverageFPS(Real windowSeconds) { - if (m_historyCount == 0) return m_currentFPS; + if (m_historyCount == 0) + { + return m_currentFPS; + } Real timeSum = 0; - Real fpsSum = 0; Int samples = 0; for (Int i = 0; i < m_historyCount; ++i) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; - fpsSum += m_fpsHistory[idx]; samples++; - if (timeSum >= windowSeconds) break; + if (timeSum >= windowSeconds) + { + break; + } } - return (samples > 0) ? (fpsSum / (Real)samples) : m_currentFPS; + return (timeSum > 0) ? ((Real)samples / timeSum) : m_currentFPS; } Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) { - if (m_historyCount == 0) return m_currentFPS; + if (m_historyCount == 0) + { + return m_currentFPS; + } - static Real sortBuffer[FPS_HISTORY_SIZE]; Real timeSum = 0; Int sampleCount = 0; Int i; @@ -1065,19 +1077,28 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) { Int idx = (m_historyOffset - 1 - i + FPS_HISTORY_SIZE) % FPS_HISTORY_SIZE; timeSum += m_durationHistory[idx]; - sortBuffer[sampleCount++] = m_fpsHistory[idx]; + m_sortBuffer[sampleCount++] = m_fpsHistory[idx]; - if (timeSum >= windowSeconds) break; + if (timeSum >= windowSeconds) + { + break; + } } - if (sampleCount == 0) return m_currentFPS; + if (sampleCount == 0) + { + return m_currentFPS; + } const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); - std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount); + std::nth_element(m_sortBuffer, m_sortBuffer + bottomSampleCount, m_sortBuffer + sampleCount); Real lowSum = 0; - for (i = 0; i < bottomSampleCount; ++i) lowSum += sortBuffer[i]; + for (i = 0; i < bottomSampleCount; ++i) + { + lowSum += m_sortBuffer[i]; + } return lowSum / (Real)bottomSampleCount; } @@ -1088,7 +1109,10 @@ void W3DDisplay::updatePerformanceMetrics() const Int64 time64 = getPerformanceCounter(); #if defined(RTS_DEBUG) - if (TheGameLogic->getFrame() == START_CUMU_FRAME) m_timerAtCumuFPSStart = time64; + if (TheGameLogic->getFrame() == START_CUMU_FRAME) + { + m_timerAtCumuFPSStart = time64; + } #endif if (m_lastUpdateTime64 == 0) From 512ece6d58c8de16334a5547fd321b4f435fe58a Mon Sep 17 00:00:00 2001 From: githubawn <115191165+githubawn@users.noreply.github.com> Date: Fri, 1 May 2026 17:10:33 +0200 Subject: [PATCH 7/7] change sampleCount from 99 to 50 --- .../GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp | 2 +- .../GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 67662f107e6..46a936b1cdf 100644 --- a/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/Generals/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1039,7 +1039,7 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) return m_currentFPS; } - const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); + const Int bottomSampleCount = std::max((sampleCount + 50) / 100, 1); std::nth_element(m_sortBuffer, m_sortBuffer + bottomSampleCount, m_sortBuffer + sampleCount); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp index 6142b8d775b..e13bcd6359d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp @@ -1090,7 +1090,7 @@ Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds) return m_currentFPS; } - const Int bottomSampleCount = std::max((sampleCount + 99) / 100, 1); + const Int bottomSampleCount = std::max((sampleCount + 50) / 100, 1); std::nth_element(m_sortBuffer, m_sortBuffer + bottomSampleCount, m_sortBuffer + sampleCount);