Skip to content

Commit 8ef9d91

Browse files
committed
bugfix(fps): Fix inaccurate frame rate cap
1 parent fc71ca4 commit 8ef9d91

File tree

6 files changed

+104
-37
lines changed

6 files changed

+104
-37
lines changed

Core/GameEngine/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(GAMEENGINE_SRC
3939
# Include/Common/Errors.h
4040
Include/Common/file.h
4141
Include/Common/FileSystem.h
42+
Include/Common/FrameRateLimit.h
4243
# Include/Common/FunctionLexicon.h
4344
Include/Common/GameAudio.h
4445
# Include/Common/GameCommon.h
@@ -569,6 +570,7 @@ set(GAMEENGINE_SRC
569570
# Source/Common/DamageFX.cpp
570571
# Source/Common/Dict.cpp
571572
# Source/Common/DiscreteCircle.cpp
573+
Source/Common/FrameRateLimit.cpp
572574
# Source/Common/GameEngine.cpp
573575
# Source/Common/GameLOD.cpp
574576
# Source/Common/GameMain.cpp
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#pragma once
20+
21+
class FrameRateLimit
22+
{
23+
public:
24+
FrameRateLimit();
25+
26+
Real wait(UnsignedInt maxFps);
27+
28+
private:
29+
LARGE_INTEGER m_freq;
30+
LARGE_INTEGER m_start;
31+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "PreRTS.h"
20+
#include "Common/FrameRateLimit.h"
21+
22+
23+
FrameRateLimit::FrameRateLimit()
24+
{
25+
QueryPerformanceFrequency(&m_freq);
26+
QueryPerformanceCounter(&m_start);
27+
}
28+
29+
Real FrameRateLimit::wait(UnsignedInt maxFps)
30+
{
31+
LARGE_INTEGER tick;
32+
QueryPerformanceCounter(&tick);
33+
double elapsedSeconds = static_cast<double>(tick.QuadPart - m_start.QuadPart) / m_freq.QuadPart;
34+
const double targetSeconds = 1.0 / maxFps;
35+
const double sleepSeconds = targetSeconds - elapsedSeconds - 0.002; // leave ~2ms for spin wait
36+
37+
if (sleepSeconds > 0.0)
38+
{
39+
// Non busy wait with Munkee sleep
40+
DWORD dwMilliseconds = static_cast<DWORD>(sleepSeconds * 1000);
41+
Sleep(dwMilliseconds);
42+
}
43+
44+
// Busy wait for remaining time
45+
do
46+
{
47+
QueryPerformanceCounter(&tick);
48+
elapsedSeconds = static_cast<double>(tick.QuadPart - m_start.QuadPart) / m_freq.QuadPart;
49+
}
50+
while (elapsedSeconds < targetSeconds);
51+
52+
m_start = tick;
53+
return (Real)elapsedSeconds;
54+
}

GeneralsMD/Code/GameEngine/Source/Common/GameEngine.cpp

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "Common/ThingFactory.h"
4545
#include "Common/file.h"
4646
#include "Common/FileSystem.h"
47+
#include "Common/FrameRateLimit.h"
4748
#include "Common/ArchiveFileSystem.h"
4849
#include "Common/LocalFileSystem.h"
4950
#include "Common/CDManager.h"
@@ -842,8 +843,8 @@ extern HWND ApplicationHWnd;
842843
*/
843844
void GameEngine::execute( void )
844845
{
846+
FrameRateLimit* frameRateLimit = new FrameRateLimit();
845847

846-
DWORD prevTime = timeGetTime();
847848
#if defined(RTS_DEBUG)
848849
DWORD startTime = timeGetTime() / 1000;
849850
#endif
@@ -917,35 +918,28 @@ void GameEngine::execute( void )
917918
if (TheTacticalView->getTimeMultiplier()<=1 && !TheScriptEngine->isTimeFast())
918919
{
919920

920-
// I'm disabling this in internal because many people need alt-tab capability. If you happen to be
921+
// I'm disabling this in debug because many people need alt-tab capability. If you happen to be
921922
// doing performance tuning, please just change this on your local system. -MDC
922923
#if defined(RTS_DEBUG)
923924
::Sleep(1); // give everyone else a tiny time slice.
924925
#endif
925926

926927

927928
#if defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE)
928-
if ( ! TheGlobalData->m_TiVOFastMode )
929-
#else //always allow this cheatkey if we're in a replaygame.
930-
if ( ! (TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()))
929+
if ( ! TheGlobalData->m_TiVOFastMode )
930+
#else //always allow this cheat key if we're in a replay game.
931+
if ( ! (TheGlobalData->m_TiVOFastMode && TheGameLogic->isInReplayGame()))
931932
#endif
932-
{
933-
// limit the framerate
934-
DWORD now = timeGetTime();
935-
DWORD limit = (1000.0f/m_maxFPS)-1;
936-
while (TheGlobalData->m_useFpsLimit && (now - prevTime) < limit)
937-
{
938-
::Sleep(0);
939-
now = timeGetTime();
940-
}
941-
//Int slept = now - prevTime;
942-
//DEBUG_LOG(("delayed %d",slept));
943-
944-
prevTime = now;
945-
946-
}
947-
948-
}
933+
{
934+
// TheSuperHackers @bugfix xezon 05/08/2025 Re-implements the frame rate limiter
935+
// with higher resolution counters to cap the frame rate more accurately to the desired limit.
936+
if (TheGlobalData->m_useFpsLimit)
937+
{
938+
frameRateLimit->wait(m_maxFPS);
939+
}
940+
}
941+
942+
}
949943
}
950944

951945
} // perfgather for execute_loop
@@ -961,6 +955,7 @@ void GameEngine::execute( void )
961955

962956
}
963957

958+
delete frameRateLimit;
964959
}
965960

966961
/** -----------------------------------------------------------------------------------------------

GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/W3DDisplay.cpp

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,19 +1855,6 @@ void W3DDisplay::draw( void )
18551855

18561856
do {
18571857

1858-
{
1859-
if(TheGlobalData->m_loadScreenRender != TRUE)
1860-
{
1861-
1862-
// limit the framerate
1863-
while(TheGlobalData->m_useFpsLimit && (now - prevTime) < minTime-1)
1864-
{
1865-
now = timeGetTime();
1866-
}
1867-
prevTime = now;
1868-
}
1869-
}
1870-
18711858
// update all views of the world - recomputes data which will affect drawing
18721859
if (DX8Wrapper::_Get_D3D_Device8() && (DX8Wrapper::_Get_D3D_Device8()->TestCooperativeLevel()) == D3D_OK)
18731860
{ //Checking if we have the device before updating views because the heightmap crashes otherwise while

GeneralsMD/Code/Tools/GUIEdit/Source/GUIEdit.cpp

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,6 @@
7777

7878
#include "W3DDevice/Common/W3DFunctionLexicon.h"
7979
#include "W3DDevice/GameClient/W3DGameWindowManager.h"
80-
#include "W3DDevice/GameClient/W3DDisplay.h"
81-
#include "W3DDevice/GameClient/W3DGameWindowManager.h"
8280
#include "W3DDevice/GameClient/W3DGameFont.h"
8381
#include "W3DDevice/GameClient/W3DDisplayStringManager.h"
8482
#include "GameClient/Keyboard.h"

0 commit comments

Comments
 (0)