Skip to content
1 change: 1 addition & 0 deletions Core/GameEngine/Include/GameClient/Display.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions Generals/Code/GameEngine/Include/GameClient/InGameUI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
49 changes: 40 additions & 9 deletions Generals/Code/GameEngine/Source/GameClient/InGameUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) },
Expand Down Expand Up @@ -1133,17 +1134,20 @@ InGameUI::InGameUI()
m_lastNetworkLatencyFrames = ~0u;

m_renderFpsString = nullptr;
m_renderFpsLowString = nullptr;
m_renderFpsLimitString = nullptr;
m_renderFpsFont = "Tahoma";
m_renderFpsPointSize = TheGlobalData->m_renderFpsFontSize;
m_renderFpsBold = TRUE;
m_renderFpsPosition.x = kHudAnchorX;
m_renderFpsPosition.y = kHudAnchorY;
m_renderFpsColor = GameMakeColor( 255, 255, 0, 255 );
m_renderFpsLowColor = GameMakeColor( 190, 180, 130, 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;

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand All @@ -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)
Expand Down Expand Up @@ -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"\x25BC%u", renderFpsLow);
m_renderFpsLowString->setText(lowStr);
m_lastRenderFpsLow = renderFpsLow;
}
}

void InGameUI::drawNetworkLatency(Int &x, Int &y)
Expand Down Expand Up @@ -6033,19 +6055,22 @@ void InGameUI::drawRenderFps(Int &x, Int &y)
updateRenderFpsString();
}

UnsignedInt renderFpsLimit = 0u;
UnsignedInt renderFpsLimit = RenderFpsPreset::UncappedFpsValue;
if (TheGlobalData->m_useFpsLimit)
{
renderFpsLimit = (UnsignedInt)TheFramePacer->getFramesPerSecondLimit();
if (renderFpsLimit == RenderFpsPreset::UncappedFpsValue)
{
renderFpsLimit = 0u;
}
}
if (renderFpsLimit != m_lastRenderFpsLimit)
{
UnicodeString fpsLimitStr;
fpsLimitStr.format(L"[%u]", renderFpsLimit);
if (renderFpsLimit == RenderFpsPreset::UncappedFpsValue)
{
fpsLimitStr.format(L"\x25B2X");
}
else
{
fpsLimitStr.format(L"\x25B2%u", renderFpsLimit);
}
m_renderFpsLimitString->setText(fpsLimitStr);
m_lastRenderFpsLimit = renderFpsLimit;
}
Expand All @@ -6056,14 +6081,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);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -161,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 fps over the last 30 frames.
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;
Expand All @@ -172,9 +176,17 @@ class W3DDisplay : public Display
Render2DClass *m_2DRender; ///< interface for common 2D functions
IRegion2D m_clipRegion; ///< the clipping region for images
Bool m_isClippedEnabled; ///<used by 2D drawing operations to define clip re
Real m_averageFPS; ///<average fps over the last 30 frames.
Real m_averageFPS; ///< average fps over the last 1.0s.
Real m_low1PercentFPS; ///<1% low fps.
Real m_currentFPS; ///<current fps value.

enum { FPS_HISTORY_SIZE = 4096 }; // degrades gracefully beyond this size
UnsignedShort m_durationHistory[FPS_HISTORY_SIZE];
Int m_historyOffset;
Int m_historyCount;
Int64 m_lastUpdateTime64;
UnsignedInt m_lastLow1PercentUpdateMs;

TextureClass *m_batchTexture;
DrawImageMode m_batchMode;
Bool m_batchGrayscale;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ static void drawFramerateBar();

// SYSTEM INCLUDES ////////////////////////////////////////////////////////////
#include <numeric>
#include <algorithm>
#include <functional>
#include <stdlib.h>
#include <windows.h>
#include <io.h>
Expand Down Expand Up @@ -340,6 +342,7 @@ W3DDisplay::W3DDisplay()
m_2DScene = nullptr;
m_3DInterfaceScene = nullptr;
m_averageFPS = TheGlobalData->m_framesPerSecondLimit;
m_low1PercentFPS = TheGlobalData->m_framesPerSecondLimit;
#if defined(RTS_DEBUG)
m_timerAtCumuFPSStart = 0;
#endif
Expand All @@ -360,6 +363,13 @@ W3DDisplay::W3DDisplay()
m_batchGrayscale = FALSE;
m_batchNeedsInit = FALSE;

m_historyOffset = 0;
m_historyCount = 1;
m_lastUpdateTime64 = 0;
m_lastLow1PercentUpdateMs = 0;
m_currentFPS = 30.0f;
std::fill(m_durationHistory, m_durationHistory + FPS_HISTORY_SIZE, (UnsignedShort)2083);

#ifdef PROFILER_ENABLED
m_profilerFrameCapture = NEW W3DProfilerFrameCapture();
#endif
Expand Down Expand Up @@ -915,6 +925,7 @@ void W3DDisplay::init()

// we're now online
m_initialized = true;
m_lastUpdateTime64 = getPerformanceCounter();
if( TheGlobalData->m_displayDebug )
{
m_debugDisplayCallback = StatDebugDisplay;
Expand Down Expand Up @@ -958,14 +969,82 @@ void W3DDisplay::reset()

const UnsignedInt START_CUMU_FRAME = LOGICFRAMES_PER_SECOND / 2; // skip first half-sec

void W3DDisplay::updateAverageFPS()
void W3DDisplay::addFpsSample(Real elapsedSeconds)
{
Int64 ticks = (Int64)(elapsedSeconds * 62500.0f + 0.5f);
UnsignedShort quantized = (ticks >= 65535) ? 65535 : (ticks <= 0 ? 1 : (UnsignedShort)ticks);

m_currentFPS = 62500.0f / (Real)quantized;
m_durationHistory[m_historyOffset] = quantized;

m_historyOffset = (m_historyOffset + 1) & (FPS_HISTORY_SIZE - 1);
if (m_historyCount < FPS_HISTORY_SIZE)
{
m_historyCount++;
}
}

Real W3DDisplay::calculateAverageFPS(Real windowSeconds)
{
constexpr const Int FPS_HISTORY_SIZE = 30;
UnsignedInt unitsSum = 0;
Int samples = 0;
const UnsignedInt windowUnits = (UnsignedInt)(windowSeconds * 62500.0f);

for (Int i = 0; i < m_historyCount; ++i)
{
Int idx = (m_historyOffset - 1 - i) & (FPS_HISTORY_SIZE - 1);
unitsSum += m_durationHistory[idx];
samples++;

if (unitsSum >= windowUnits)
{
break;
}
}

return (unitsSum > 0) ? ((Real)samples * 62500.0f / (Real)unitsSum) : m_currentFPS;
}

Real W3DDisplay::calculateLow1PercentFPS(Real windowSeconds)
{
UnsignedInt unitsSum = 0;
Int sampleCount = 0;
const UnsignedInt windowUnits = (UnsignedInt)(windowSeconds * 62500.0f);
UnsignedShort sortBuffer[FPS_HISTORY_SIZE];

Int i;
for (i = 0; i < m_historyCount; ++i)
{
Int idx = (m_historyOffset - 1 - i) & (FPS_HISTORY_SIZE - 1);
unitsSum += m_durationHistory[idx];
sortBuffer[sampleCount++] = m_durationHistory[idx];

static Int64 lastUpdateTime64 = 0;
static Int historyOffset = 0;
static Real fpsHistory[FPS_HISTORY_SIZE] = {0};
if (unitsSum >= windowUnits)
{
break;
}
}

if (sampleCount == 0)
{
return m_currentFPS;
}

const Int bottomSampleCount = std::max((sampleCount + 50) / 100, 1);

std::nth_element(sortBuffer, sortBuffer + bottomSampleCount, sortBuffer + sampleCount, std::greater<UnsignedShort>());

UnsignedInt durationUnitsSum = 0;
for (i = 0; i < bottomSampleCount; ++i)
{
durationUnitsSum += sortBuffer[i];
}

return (durationUnitsSum > 0) ? ((Real)bottomSampleCount * 62500.0f / (Real)durationUnitsSum) : m_currentFPS;
}

void W3DDisplay::updatePerformanceMetrics()
{
const Int64 freq64 = getPerformanceCounterFrequency();
const Int64 time64 = getPerformanceCounter();

Expand All @@ -976,23 +1055,13 @@ void W3DDisplay::updateAverageFPS()
}
#endif

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;

m_currentFPS = 1.0f/elapsedSeconds;
fpsHistory[historyOffset++] = m_currentFPS;
const Int64 timeDiff = time64 - m_lastUpdateTime64;
Real elapsedSeconds = (Real)timeDiff / (Real)freq64;

// 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;
addFpsSample(elapsedSeconds);
m_averageFPS = calculateAverageFPS(1.0f); // 1.0s window for smooth Dynamic LOD tracking and UI matching

lastUpdateTime64 = time64;
m_lastUpdateTime64 = time64;
}

#if defined(RTS_DEBUG) //debug hack to view object under mouse stats
Expand Down Expand Up @@ -1693,6 +1762,17 @@ Real W3DDisplay::getAverageFPS()
return m_averageFPS;
}

Real W3DDisplay::getLow1PercentFPS()
{
UnsignedInt now = timeGetTime();
if (now - m_lastLow1PercentUpdateMs >= 1000)
{
m_low1PercentFPS = calculateLow1PercentFPS(3.0f);
m_lastLow1PercentUpdateMs = now;
}
return m_low1PercentFPS;
}

Real W3DDisplay::getCurrentFPS()
{
return m_currentFPS;
Expand Down Expand Up @@ -1727,7 +1807,7 @@ void W3DDisplay::draw()
if (TheGlobalData->m_headless)
return;

updateAverageFPS();
updatePerformanceMetrics();
if (TheGlobalData->m_enableDynamicLOD && TheGameLogic->getShowDynamicLOD())
{
DynamicGameLODLevel lod=TheGameLODManager->findDynamicLODLevel(m_averageFPS);
Expand Down
1 change: 1 addition & 0 deletions Generals/Code/Tools/GUIEdit/Include/GUIEditDisplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
Loading
Loading