Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Support >60Hz (2nd try) #585

Draft
wants to merge 34 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7d2eeb5
Revert "Soft particles: Disable Particle Stage "softeningRadius" keyw…
DanielGibson Jul 25, 2024
2f1b2b3
Configurable FPS: Add com_gameHz, use for USERCMD_HZ and USERCMD_MSEC
DanielGibson Jun 27, 2024
e082360
Merge fixes from dezo2's 144Hz branch
DanielGibson Jun 27, 2024
edb0b8d
Merge dezo2's change in HUD scaling for d3xp grabber crosshair
DanielGibson Jun 26, 2024
f0c9f4e
Framerate-independent idPlayer::Move() and idAI::AdjustFlyingAngles()
DanielGibson Jun 27, 2024
8911910
Add hacks for replacing some #defines in scripts
DanielGibson Jun 27, 2024
9f45b8c
Add sys.getRawFrameTime() script event for GAME_FRAMETIME
DanielGibson Jun 27, 2024
4c0db21
d3xp: When com_gameHz changes, scale some time values accordingly
DanielGibson Jun 27, 2024
d063a0e
Make sure gameLocal.msec, .gameMsec, etc are always initialized
DanielGibson Jun 27, 2024
ae1b8ee
Change how com_frameTime is updated
DanielGibson Jun 29, 2024
6d6d38d
Dhewm3SettingsMenu: Add setting for com_gameHz
DanielGibson Jul 2, 2024
694e8de
Use r_displayRefresh for real fullscreen mode (SDL2-only)
DanielGibson Jul 2, 2024
caa83c5
Introduce Sys_MillisecondsPrecise() and Sys_SleepUntilPrecise()
DanielGibson Jul 2, 2024
e8368e8
Make the Async Loop more precise
DanielGibson Jul 2, 2024
2cd4b57
Fix Windows build and small adjustments to Sys_MillisecondsPrecise()
DanielGibson Jul 2, 2024
e39e6cb
GLimp_Init(): In fullscreen case log values from SDL
DanielGibson Jul 2, 2024
4c37f64
Support com_showFPS 2, showing also avg/min/max frametimes
DanielGibson Jul 3, 2024
65e4b8d
Fix cursor blinking speed
DanielGibson Jul 3, 2024
5c2bc00
Fix d3xp falling apart when changing com_gameHz while game is running
DanielGibson Jul 3, 2024
a183987
com_showFPS: Use all frames from last half second
DanielGibson Jul 3, 2024
2141198
Fix crane (and potentially other similar problems)
DanielGibson Jul 3, 2024
a6daf55
(Hopefully) fix STOP_SPEED in idPhysics_Rigidbody
DanielGibson Jul 6, 2024
f1ebfc2
Undo changes to physics code for com_gameHz
DanielGibson Jul 12, 2024
da2f017
Merge some physics fixes from The Dark Mod
DanielGibson Jul 12, 2024
6c17461
Fix the crane again
DanielGibson Jul 13, 2024
9e10c90
Fix monsters walking up stairs and noclip speed
DanielGibson Jul 15, 2024
88a5659
Fix d3xp Grabber at high framerates
DanielGibson Jul 20, 2024
4024a2e
Recalculate gameLocal.msec each frame so gameHz frames add up to 1000ms
DanielGibson Jul 20, 2024
4dc3ec7
Pressure user to not set com_gameHzVal to >250Hz
DanielGibson Jul 25, 2024
25ea927
Apply @dezo2's fix for monsters sliding too fast
DanielGibson Jul 25, 2024
c89e261
Apply @dezo2's ragdoll fix
DanielGibson Jul 25, 2024
8451dd0
Bump Version to 1.6.0pre
DanielGibson Jul 25, 2024
b158f24
Fix oxygen consumption code (idPlayer::airTics) for > 60Hz
DanielGibson Aug 16, 2024
7ac7047
Make Sys_Milliseconds() inline
DanielGibson Aug 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions neo/d3xp/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1668,6 +1668,9 @@ bool idActor::StartRagdoll( void ) {
return true;
}

// dezo2, modified entity mass (must be here, mass is zeroed in StartFromCurrentPose)
float mass_mod = GetPhysics()->GetMass() * idMath::Pow( gameLocal.gameTicScale, 2.0f );

// disable the monster bounding box
GetPhysics()->DisableClip();

Expand Down Expand Up @@ -1702,6 +1705,9 @@ bool idActor::StartRagdoll( void ) {

RemoveAttachments();

// dezo2, use modified entity mass to reduce ragdoll movement at high FPS
GetPhysics()->SetMass( mass_mod );

return true;
}

Expand Down
64 changes: 57 additions & 7 deletions neo/d3xp/Game_local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ idGameLocal::idGameLocal
*/
idGameLocal::idGameLocal() {
Clear();
// DG: some sane default values until the proper values are set by SetGameHz()
msec = gameMsec = 16;
gameHz = 60;
gameTicScale = 1.0f;
}

/*
Expand Down Expand Up @@ -626,6 +630,7 @@ void idGameLocal::SaveGame( idFile *f ) {
savegame.WriteInt( time );

#ifdef _D3XP
savegame.WriteInt( gameMsec ); // DG: added with INTERNAL_SAVEGAME_VERSION 2
savegame.WriteInt( msec );
#endif

Expand Down Expand Up @@ -1535,7 +1540,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo
savegame.ReadInt( time );

#ifdef _D3XP
int savedGameMsec = 16;
if( savegame.GetInternalSavegameVersion() > 1 ) {
savegame.ReadInt( savedGameMsec );
}
float gameMsecScale = float(gameMsec) / float(savedGameMsec);

savegame.ReadInt( msec );
// DG: the saved msec must be scaled, in case com_gameHz has a different value now
msec *= gameMsecScale;
#endif

savegame.ReadInt( vacuumAreaNum );
Expand All @@ -1558,12 +1571,15 @@ bool idGameLocal::InitFromSaveGame( const char *mapName, idRenderWorld *renderWo

fast.Restore( &savegame );
slow.Restore( &savegame );
fast.msec *= gameMsecScale; // DG: the saved value must be scaled, in case com_gameHz has a different value now
slow.msec *= gameMsecScale; // same here

int blah;
savegame.ReadInt( blah );
slowmoState = (slowmoState_t)blah;

savegame.ReadFloat( slowmoMsec );
slowmoMsec *= gameMsecScale; // DG: the saved value must be scaled, in case com_gameHz has a different value now
savegame.ReadBool( quickSlowmoReset );

if ( slowmoState == SLOWMO_STATE_OFF ) {
Expand Down Expand Up @@ -2425,11 +2441,11 @@ void idGameLocal::SortActiveEntityList( void ) {
idGameLocal::RunTimeGroup2
================
*/
void idGameLocal::RunTimeGroup2() {
void idGameLocal::RunTimeGroup2( int msec_fast ) { // dezo2: add argument for high-fps support
idEntity *ent;
int num = 0;

fast.Increment();
fast.Increment( msec_fast );
fast.Get( time, previousTime, msec, framenum, realClientTime );

for( ent = activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) {
Expand All @@ -2445,6 +2461,13 @@ void idGameLocal::RunTimeGroup2() {
}
#endif

// DG: returns number of milliseconds for this frame, based on gameLocal.gameHz
// either 1000/gameHz or 1000/gameHz + 1, so the frametimes of gameHz frames add up to 1000ms
static int CalcMSec( long long framenum ) {
long long divisor = 100LL * gameLocal.gameHz;
return int( (framenum * 100000LL) / divisor - ((framenum-1) * 100000LL) / divisor );
}

/*
================
idGameLocal::RunFrame
Expand Down Expand Up @@ -2488,6 +2511,12 @@ gameReturn_t idGameLocal::RunFrame(const usercmd_t* clientCmds) {
// update the game time
framenum++;
previousTime = time;
// dezo2/DG: for high-fps support, calculate the frametime msec every frame
// the length actually varies between 1000/gameHz and (1000/gameHz) + 1
// so the sum of gameHz frames is 1000 (while still keeping integer frametimes)
int msec_fast = CalcMSec( framenum );
if ( slowmoState == SLOWMO_STATE_OFF )
msec = msec_fast;
time += msec;
realClientTime = time;

Expand Down Expand Up @@ -2585,7 +2614,7 @@ gameReturn_t idGameLocal::RunFrame(const usercmd_t* clientCmds) {
}

#ifdef _D3XP
RunTimeGroup2();
RunTimeGroup2( msec_fast ); // dezo2: pass msec_fast for better high-fps support
#endif

// remove any entities that have stopped thinking
Expand Down Expand Up @@ -4913,10 +4942,10 @@ void idGameLocal::ComputeSlowMsec() {

// do any necessary ramping
if ( slowmoState == SLOWMO_STATE_RAMPUP ) {
delta = 4 - slowmoMsec;
delta = gameMsec * 0.25f - slowmoMsec; // DG: adjust to support com_gameHz

if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
slowmoMsec = 4;
slowmoMsec = gameMsec * 0.25f; // DG: adjust to support com_gameHz (was 4 = 16/4)
slowmoState = SLOWMO_STATE_ON;
}
else {
Expand All @@ -4928,10 +4957,10 @@ void idGameLocal::ComputeSlowMsec() {
}
}
else if ( slowmoState == SLOWMO_STATE_RAMPDOWN ) {
delta = 16 - slowmoMsec;
delta = gameMsec - slowmoMsec; // DG: adjust to support com_gameHz

if ( fabs( delta ) < g_slowmoStepRate.GetFloat() ) {
slowmoMsec = 16;
slowmoMsec = gameMsec; // DG: adjust to support com_gameHz
slowmoState = SLOWMO_STATE_OFF;
if ( gameSoundWorld ) {
gameSoundWorld->SetSlowmo( false );
Expand Down Expand Up @@ -5043,3 +5072,24 @@ idGameLocal::GetMapLoadingGUI
===============
*/
void idGameLocal::GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] ) { }

// DG: Added for configurable framerate
void idGameLocal::SetGameHz( float hz, float frametime, float ticScaleFactor )
{
gameHz = hz;
int oldGameMsec = gameMsec;
gameMsec = frametime;
gameTicScale = ticScaleFactor;

if ( slowmoState == SLOWMO_STATE_OFF ) {
// if slowmo is off, msec, slowmoMsec and slow/fast.msec should all be set to gameMsec
msec = slowmoMsec = slow.msec = fast.msec = gameMsec;
} else {
// otherwise the msec values must be scaled accordingly
float gameMsecScale = frametime / float(oldGameMsec);
msec *= gameMsecScale;
slowmoMsec *= gameMsecScale;
fast.msec *= gameMsecScale;
slow.msec *= gameMsecScale;
}
}
14 changes: 11 additions & 3 deletions neo/d3xp/Game_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ struct timeState_t {
void Get( int& t, int& pt, int& ms, int& f, int& rct ) { t = time; pt = previousTime; ms = msec; f = framenum; rct = realClientTime; };
void Save( idSaveGame *savefile ) const { savefile->WriteInt( time ); savefile->WriteInt( previousTime ); savefile->WriteInt( msec ); savefile->WriteInt( framenum ); savefile->WriteInt( realClientTime ); }
void Restore( idRestoreGame *savefile ) { savefile->ReadInt( time ); savefile->ReadInt( previousTime ); savefile->ReadInt( msec ); savefile->ReadInt( framenum ); savefile->ReadInt( realClientTime ); }
void Increment() { framenum++; previousTime = time; time += msec; realClientTime = time; };
void Increment(int _msec) { framenum++; previousTime = time; msec = _msec; time += msec; realClientTime = time; }; // dezo2: update msec
};

enum slowmoState_t {
Expand Down Expand Up @@ -300,6 +300,11 @@ class idGameLocal : public idGame {
int time; // in msec
int msec; // time since last update in milliseconds

// DG: added for configurable framerate
int gameMsec; // length of one frame/tic in milliseconds - TODO: make float?
int gameHz; // current gameHz value (tic-rate, FPS)
float gameTicScale; // gameHz/60 factor to multiply delays in tics (that assume 60fps) with

int vacuumAreaNum; // -1 if level doesn't have any outside areas

gameType_t gameType;
Expand Down Expand Up @@ -341,7 +346,7 @@ class idGameLocal : public idGame {
virtual void GetBestGameType( const char* map, const char* gametype, char buf[ MAX_STRING_CHARS ] );

void ComputeSlowMsec();
void RunTimeGroup2();
void RunTimeGroup2( int msec_fast ); // dezo2: add argument for high-fps support

void ResetSlowTimeVars();
void QuickSlowmoReset();
Expand Down Expand Up @@ -397,6 +402,9 @@ class idGameLocal : public idGame {

virtual void GetMapLoadingGUI( char gui[ MAX_STRING_CHARS ] );

// DG: Added for configurable framerate
virtual void SetGameHz( float hz, float frametime, float ticScaleFactor );

// ---------------------- Public idGameLocal Interface -------------------

void Printf( const char *fmt, ... ) const id_attribute((format(printf,2,3)));
Expand Down Expand Up @@ -512,7 +520,7 @@ class idGameLocal : public idGame {

private:
const static int INITIAL_SPAWN_COUNT = 1;
const static int INTERNAL_SAVEGAME_VERSION = 1; // DG: added this for >= 1305 savegames
const static int INTERNAL_SAVEGAME_VERSION = 2; // DG: added this for >= 1305 savegames

idStr mapFileName; // name of the map, empty string if no map loaded
idMapFile * mapFile; // will be NULL during the game unless in-game editing is used
Expand Down
40 changes: 27 additions & 13 deletions neo/d3xp/Player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2152,7 +2152,7 @@ void idPlayer::Save( idSaveGame *savefile ) const {
savefile->WriteInt( numProjectileHits );

savefile->WriteBool( airless );
savefile->WriteInt( airTics );
savefile->WriteInt( (int)airTics );
savefile->WriteInt( lastAirDamage );

savefile->WriteBool( gibDeath );
Expand Down Expand Up @@ -2420,7 +2420,11 @@ void idPlayer::Restore( idRestoreGame *savefile ) {
savefile->ReadInt( numProjectileHits );

savefile->ReadBool( airless );
savefile->ReadInt( airTics );
// DG: I made made airTics float for high-fps (where we have fractions of 60Hz tics),
// but for saving ints should still suffice (and this preserves savegame compat)
int iairTics;
savefile->ReadInt( iairTics );
airTics = iairTics;
savefile->ReadInt( lastAirDamage );

savefile->ReadBool( gibDeath );
Expand Down Expand Up @@ -3231,9 +3235,14 @@ void idPlayer::DrawHUD( idUserInterface *_hud ) {
if ( weapon.GetEntity()->GetGrabberState() == 1 || weapon.GetEntity()->GetGrabberState() == 2 ) {
cursor->SetStateString( "grabbercursor", "1" );
cursor->SetStateString( "combatcursor", "0" );
cursor->SetStateBool("scaleto43", false); // dezo2, unscaled
cursor->StateChanged(gameLocal.realClientTime); // dezo2, set state
} else {
cursor->SetStateString( "grabbercursor", "0" );
cursor->SetStateString( "combatcursor", "1" );
cursor->SetStateBool("scaleto43", true); // dezo2, scaled
cursor->StateChanged(gameLocal.realClientTime); // dezo2, set state

}
#endif

Expand Down Expand Up @@ -3511,12 +3520,12 @@ bool idPlayer::Give( const char *statname, const char *value ) {
}

} else if ( !idStr::Icmp( statname, "air" ) ) {
if ( airTics >= pm_airTics.GetInteger() ) {
if ( airTics >= pm_airTics.GetFloat() ) { // DG: airTics are floats now for high-fps support
return false;
}
airTics += atoi( value ) / 100.0 * pm_airTics.GetInteger();
if ( airTics > pm_airTics.GetInteger() ) {
airTics = pm_airTics.GetInteger();
airTics += atoi( value ) / 100.0 * pm_airTics.GetFloat();
if ( airTics > pm_airTics.GetFloat() ) {
airTics = pm_airTics.GetFloat();
}
#ifdef _D3XP
} else if ( !idStr::Icmp( statname, "enviroTime" ) ) {
Expand Down Expand Up @@ -6105,7 +6114,8 @@ void idPlayer::UpdateAir( void ) {
hud->HandleNamedEvent( "noAir" );
}
}
airTics--;
// DG: was airTics--, but airTics assume 60Hz tics and we support other ticrates now (com_gameHz)
airTics -= 1.0f / gameLocal.gameTicScale;
if ( airTics < 0 ) {
airTics = 0;
// check for damage
Expand All @@ -6125,16 +6135,16 @@ void idPlayer::UpdateAir( void ) {
hud->HandleNamedEvent( "Air" );
}
}
airTics+=2; // regain twice as fast as lose
if ( airTics > pm_airTics.GetInteger() ) {
airTics = pm_airTics.GetInteger();
airTics += 2.0f / gameLocal.gameTicScale; // regain twice as fast as lose - DG: scale for com_gameHz
if ( airTics > pm_airTics.GetFloat() ) {
airTics = pm_airTics.GetFloat();
}
}

airless = newAirless;

if ( hud ) {
hud->SetStateInt( "player_air", 100 * airTics / pm_airTics.GetInteger() );
hud->SetStateInt( "player_air", 100 * (airTics / pm_airTics.GetFloat()) );
}
}

Expand Down Expand Up @@ -7107,8 +7117,12 @@ void idPlayer::Move( void ) {
if ( spectating ) {
SetEyeHeight( newEyeOffset );
} else {
// DG: make this framerate-independent, code suggested by tyuah8 on Github
// https://en.wikipedia.org/wiki/Exponential_smoothing#Time_constant
const float tau = -16.0f / idMath::Log( pm_crouchrate.GetFloat() );
const float a = 1.0f - idMath::Exp( -gameLocal.gameMsec / tau );
// smooth out duck height changes
SetEyeHeight( EyeHeight() * pm_crouchrate.GetFloat() + newEyeOffset * ( 1.0f - pm_crouchrate.GetFloat() ) );
SetEyeHeight( EyeHeight() * (1.0f - a) + newEyeOffset * a );
}
}

Expand Down Expand Up @@ -7648,7 +7662,7 @@ bool idPlayer::CanGive( const char *statname, const char *value ) {
return true;

} else if ( !idStr::Icmp( statname, "air" ) ) {
if ( airTics >= pm_airTics.GetInteger() ) {
if ( airTics >= pm_airTics.GetFloat() ) {
return false;
}
return true;
Expand Down
3 changes: 2 additions & 1 deletion neo/d3xp/Player.h
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,8 @@ class idPlayer : public idActor {
int numProjectileHits; // number of hits on mobs

bool airless;
int airTics; // set to pm_airTics at start, drops in vacuum
// DG: Note: airTics are tics at 60Hz, so no real tics (unless com_gameHz happens to be 60)
float airTics; // set to pm_airTics at start, drops in vacuum
int lastAirDamage;

bool gibDeath;
Expand Down
11 changes: 9 additions & 2 deletions neo/d3xp/ai/AI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2984,8 +2984,15 @@ void idAI::AdjustFlyingAngles( void ) {
}
}

fly_roll = fly_roll * 0.95f + roll * 0.05f;
fly_pitch = fly_pitch * 0.95f + pitch * 0.05f;
// DG: make this framerate-independent, code suggested by tyuah8 on Github
// https://en.wikipedia.org/wiki/Exponential_smoothing#Time_constant
static const float tau = -16.0f / idMath::Log( 0.95f );
// TODO: use gameLocal.gameMsec instead, so it's not affected by slow motion?
// enemies turning slower in slowmo seems logical, but the original code
// just increased every tic and thus was independent of slowmo
const float a = 1.0f - idMath::Exp( -gameLocal.msec / tau );
fly_roll = fly_roll * (1.0f - a) + roll * a;
fly_pitch = fly_pitch * (1.0f - a) + pitch * a;

if ( flyTiltJoint != INVALID_JOINT ) {
animator.SetJointAxis( flyTiltJoint, JOINTMOD_WORLD, idAngles( fly_pitch, 0.0f, fly_roll ).ToMat3() );
Expand Down
2 changes: 2 additions & 0 deletions neo/d3xp/physics/Force_Drag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ If you have questions concerning this license or the applicable additional terms

#include "physics/Force_Drag.h"

#include "Game_local.h"

CLASS_DECLARATION( idForce, idForce_Drag )
END_CLASS

Expand Down
18 changes: 17 additions & 1 deletion neo/d3xp/physics/Force_Grab.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,23 @@ idForce_Grab::Init
*/
void idForce_Grab::Init( float damping ) {
if ( damping >= 0.0f && damping < 1.0f ) {
this->damping = damping;
/* DG: in Evaluate(), the linear velocity (or actually momentum) of this->physics
* is multiplied with damping (0.5 by default) each frame.
* So how quickly the velocity is reduced per second depended on the framerate,
* and at higher framerates the grabbed item is slowed down too much and
* because of that sometimes even drops on the floor (gravity stronger than
* the force dragging it towards the player, or something like that).
* To fix that, damping must be adjusted depending on the framerate.
* The fixed code below is the result of this math (figuring out fixeddamping;
* note that here a^b means pow(a, b)):
* // we want velocity multiplied with damping 60 times per second to have the
* // same value as velocity multiplied with fixeddamping gameHz times per second
* velocity * damping^60 = velocity * fixeddamping^gameHz
* <=> damping^60 = fixeddamping^gameHz // divided by velocity
* <=> gameHz-th-root-of( damping^60 ) = fixeddamping // took gameHz-th root
* <=> fixeddamping = damping^( 60/gameHz ) // n-th-root-of(x^m) == x^(m/n)
*/
this->damping = idMath::Pow( damping, 60.0f/gameLocal.gameHz );
}
}

Expand Down
Loading