From a8bd1a01801a4ce05b1a7eb56f832ba816040d1a Mon Sep 17 00:00:00 2001 From: MrS-ibra Date: Wed, 4 Mar 2026 15:31:10 +0300 Subject: [PATCH] Add adaptive render frame skipping for 60hz --- .../Code/GameEngine/Include/Common/GameLOD.h | 11 +- .../Code/GameEngine/Source/Common/GameLOD.cpp | 102 +++++++++--------- .../Include/W3DDevice/GameClient/W3DDisplay.h | 4 + .../W3DDevice/GameClient/W3DDisplay.cpp | 17 ++- .../Libraries/Source/WWVegas/WW3D2/ww3d.cpp | 11 ++ .../Libraries/Source/WWVegas/WW3D2/ww3d.h | 3 + 6 files changed, 92 insertions(+), 56 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h b/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h index cb8263a75ac..20163366c25 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameLOD.h @@ -190,6 +190,9 @@ class GameLODManager Bool isReallyLowMHz() const { return m_cpuFreq < m_reallyLowMHz; } #if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) void updateGraphicsQualityState(float averageFPS); + void restoreQualitySettings(); + bool getFrameSkipEnabled() const { return m_frameSkipEnabled; } + bool isQualityReduced() const { return m_isQualityReduced; } #endif StaticGameLODInfo m_staticGameLODInfo[STATIC_GAME_LOD_COUNT]; @@ -230,14 +233,14 @@ class GameLODManager Real m_compositeBenchIndex; Int m_reallyLowMHz; #if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) - bool m_userGraphSnapshotTaken; bool m_userShadowVolumesEnabled; bool m_userShadowDecalsEnabled; bool m_userHeatEffectsEnabled; bool m_isQualityReduced; - int m_stableFPSDuration; - int m_lowFPSSecondsCount; - DynamicGameLODLevel m_userDynamicLOD; + bool m_frameSkipEnabled; + int m_stableFPSSecondsCount; + int m_lowFPSSecondsCount; + int m_userMaxParticleCount; #endif }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp index c4841282b6b..9099b692df1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GameLOD.cpp @@ -36,7 +36,7 @@ #include "Common/GameLOD.h" #include "GameClient/TerrainVisual.h" #include "GameClient/GameClient.h" -#include "GameClient/Shell.h" +#include "GameLogic/GameLogic.h" #include "Common/UserPreferences.h" #define DEFINE_PARTICLE_SYSTEM_NAMES @@ -232,14 +232,11 @@ GameLODManager::GameLODManager(void) m_numBenchProfiles=0; m_reallyLowMHz = 400; #if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) - m_userGraphSnapshotTaken = false; - m_userShadowVolumesEnabled = true; - m_userShadowDecalsEnabled = true; - m_userHeatEffectsEnabled = true; m_isQualityReduced = false; - m_stableFPSDuration = 0; + m_frameSkipEnabled = false; + m_stableFPSSecondsCount = 0; m_lowFPSSecondsCount = 0; - m_userDynamicLOD = DYNAMIC_GAME_LOD_VERY_HIGH; + m_userMaxParticleCount = 0; #endif for (Int i=0; im_useShadowVolumes; - m_userShadowDecalsEnabled = TheGlobalData->m_useShadowDecals; - m_userHeatEffectsEnabled = TheGlobalData->m_useHeatEffects; - m_userDynamicLOD = m_currentDynamicLOD; - m_userGraphSnapshotTaken = true; - } + if (!TheGameLogic || (TheGameLogic->getFrame() % LOGICFRAMES_PER_SECOND) != 0) + return; - if (m_isQualityReduced && TheGameClient && TheGameClient->getFrame() <= 1) + if (TheGameLogic->isInShellGame() || TheGameLogic->isInReplayGame() || (TheGameLogic->getFrame() < LOGICFRAMES_PER_SECOND)) { - TheWritableGlobalData->m_useShadowVolumes = m_userShadowVolumesEnabled; - TheWritableGlobalData->m_useShadowDecals = m_userShadowDecalsEnabled; - TheWritableGlobalData->m_useHeatEffects = m_userHeatEffectsEnabled; - setDynamicLODLevel(m_userDynamicLOD); - if (TheGameClient) - TheGameClient->allocateShadows(); - m_isQualityReduced = false; - m_stableFPSDuration = 0; + if (m_isQualityReduced || m_frameSkipEnabled) + restoreQualitySettings(); + return; } if (!m_isQualityReduced) @@ -808,24 +794,26 @@ void GameLODManager::updateGraphicsQualityState(float averageFPS) m_userShadowVolumesEnabled = TheGlobalData->m_useShadowVolumes; m_userShadowDecalsEnabled = TheGlobalData->m_useShadowDecals; m_userHeatEffectsEnabled = TheGlobalData->m_useHeatEffects; - m_userDynamicLOD = m_currentDynamicLOD; + m_userMaxParticleCount = TheGlobalData->m_maxParticleCount; } - if (averageFPS < 56.0f) - m_lowFPSSecondsCount++, m_stableFPSDuration = 0; - else if (averageFPS > 57.0f) - m_stableFPSDuration++, m_lowFPSSecondsCount = 0; + // Track how many consecutive seconds FPS is below or above threshold. + const float MIN_ACCEPTABLE_FPS = 58.f; + averageFPS < MIN_ACCEPTABLE_FPS ? (m_lowFPSSecondsCount++, m_stableFPSSecondsCount = 0) + : (m_stableFPSSecondsCount++, m_lowFPSSecondsCount = 0); - bool shouldReduceQuality = (m_lowFPSSecondsCount >= 2 && TheGameClient && TheGameClient->getFrame() > LOGICFRAMES_PER_SECOND * 10 && !TheShell->isShellActive()); - if (shouldReduceQuality && !m_isQualityReduced) + bool isInGame = TheGameLogic->isInGame(); + if (isInGame) { - if (averageFPS < 56.0f) - m_dynamicGameLODInfo[DYNAMIC_GAME_LOD_LOW].m_minDynamicParticlePriority = WEAPON_TRAIL; - if (averageFPS < 40.0f) - m_dynamicGameLODInfo[DYNAMIC_GAME_LOD_LOW].m_minDynamicParticlePriority = ALWAYS_RENDER; + if (averageFPS < MIN_ACCEPTABLE_FPS && m_lowFPSSecondsCount >= 1) + m_frameSkipEnabled = true; + else if (averageFPS >= MIN_ACCEPTABLE_FPS && m_stableFPSSecondsCount >= 1) + m_frameSkipEnabled = false; + } - - setDynamicLODLevel(DYNAMIC_GAME_LOD_LOW); + bool shouldReduceQuality = (m_lowFPSSecondsCount >= 2 && isInGame); + if (shouldReduceQuality && !m_isQualityReduced) + { TheGameClient->releaseShadows(); TheWritableGlobalData->m_useShadowVolumes = false; TheWritableGlobalData->m_useShadowDecals = false; @@ -834,24 +822,38 @@ void GameLODManager::updateGraphicsQualityState(float averageFPS) m_lowFPSSecondsCount = 0; } - // Restore to user preferences after sustained good performance - else if (!shouldReduceQuality && m_isQualityReduced) + if (m_isQualityReduced) { - if (m_stableFPSDuration > 15) - { - TheWritableGlobalData->m_useShadowVolumes = m_userShadowVolumesEnabled; - TheWritableGlobalData->m_useShadowDecals = m_userShadowDecalsEnabled; - TheWritableGlobalData->m_useHeatEffects = m_userHeatEffectsEnabled; + float particleReductionFactor = max(0.f, min(1.f, (MIN_ACCEPTABLE_FPS - averageFPS) / MIN_ACCEPTABLE_FPS * 5.f)); + int targetCount = max(100, (int)(m_userMaxParticleCount * (1.f - particleReductionFactor))); + int current = TheGlobalData->m_maxParticleCount; - if (TheGameClient) - TheGameClient->allocateShadows(); + if (targetCount < current) + TheWritableGlobalData->m_maxParticleCount = max(100, current + (int)((targetCount - current) * 0.5f)); - DynamicGameLODLevel lod = TheGameLODManager->findDynamicLODLevel(averageFPS); - TheGameLODManager->setDynamicLODLevel(lod); + if (!shouldReduceQuality && m_stableFPSSecondsCount > 15) + { + int newCount = current + (int)((m_userMaxParticleCount - current) * 0.3f); - m_isQualityReduced = false; - m_stableFPSDuration = 0; + if (newCount >= m_userMaxParticleCount || newCount == current) + restoreQualitySettings(); + else + TheWritableGlobalData->m_maxParticleCount = newCount; } } } + +void GameLODManager::restoreQualitySettings() +{ + TheWritableGlobalData->m_useShadowVolumes = m_userShadowVolumesEnabled; + TheWritableGlobalData->m_useShadowDecals = m_userShadowDecalsEnabled; + TheWritableGlobalData->m_useHeatEffects = m_userHeatEffectsEnabled; + TheWritableGlobalData->m_maxParticleCount = m_userMaxParticleCount; + m_stableFPSSecondsCount = 0; + m_lowFPSSecondsCount = 0; + m_frameSkipEnabled = false; + m_isQualityReduced = false; + if (TheGameClient) + TheGameClient->allocateShadows(); +} #endif // GENERALS_ONLINE_HIGH_FPS_SERVER diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h index b037fda1873..577a2e1933d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/W3DDisplay.h @@ -168,6 +168,10 @@ class W3DDisplay : public Display Bool m_isClippedEnabled; ///getFrame() % LOGICFRAMES_PER_SECOND) == 0) - TheGameLODManager->updateGraphicsQualityState(m_averageFPS); + TheGameLODManager->updateGraphicsQualityState(m_averageFPS); + if (TheGameLODManager->getFrameSkipEnabled()) { + float skipRatio = min(0.5f, max(0.0f, ((float)LOGICFRAMES_PER_SECOND - m_averageFPS) / (float)LOGICFRAMES_PER_SECOND * 2.0f)); + m_frameSkipAccumulator += skipRatio; + if (m_frameSkipAccumulator >= 1.0f) { + m_frameSkipAccumulator -= 1.0f; + WW3D::SkipNextRenderFrame = true; + } + } + else + m_frameSkipAccumulator = 0.0f; #else if (TheGlobalData->m_enableDynamicLOD && TheGameLogic->getShowDynamicLOD()) { @@ -2073,6 +2082,10 @@ void W3DDisplay::createLightPulse( const Coord3D *pos, const RGBColor *color, if (innerRadius+attenuationWidth<2.0*PATHFIND_CELL_SIZE_F + 1.0f) { return; // it basically won't make any visual difference. jba. } +#if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) + if (TheGameLODManager && TheGameLODManager->isQualityReduced()) + return; +#endif W3DDynamicLight * theDynamicLight = m_3DScene->getADynamicLight(); // turn it on. theDynamicLight->setEnabled(true); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp index f7535cb6ec9..055622082a8 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.cpp @@ -175,6 +175,9 @@ float WW3D::PixelCenterY = 0.0f; bool WW3D::IsInitted = false; bool WW3D::IsRendering = false; +#if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) +bool WW3D::SkipNextRenderFrame = false; +#endif bool WW3D::IsCapturing = false; bool WW3D::IsScreenUVBiased = false; @@ -852,6 +855,14 @@ WW3DErrorType WW3D::Begin_Render(bool clear,bool clearz,const Vector3 & color, f DX8Wrapper::Clear(clear, clearz, color, dest_alpha); } +#if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) + if (WW3D::SkipNextRenderFrame) { + WW3D::SkipNextRenderFrame = false; + IsRendering = false; + return WW3D_ERROR_GENERIC; + } +#endif + // Notify D3D that we are beginning to render the frame DX8Wrapper::Begin_Scene(); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h index be22b01494f..3fc437f239e 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/ww3d.h @@ -322,6 +322,9 @@ class WW3D // Gamma control static void Set_Gamma(float gamma,float bright,float contrast,bool calibrate=true); +#if defined(GENERALS_ONLINE_HIGH_FPS_SERVER) + static bool SkipNextRenderFrame; +#endif private: