source-engine/engine/sys_engine.cpp
2021-11-14 13:21:01 +03:00

524 lines
15 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//===========================================================================//
#include "quakedef.h"
#include <assert.h>
#include "engine_launcher_api.h"
#include "iengine.h"
#include "ivideomode.h"
#include "igame.h"
#include "vmodes.h"
#include "modes.h"
#include "sys.h"
#include "host.h"
#include "keys.h"
#include "cdll_int.h"
#include "host_state.h"
#include "cdll_engine_int.h"
#include "sys_dll.h"
#include "tier0/vprof.h"
#include "profile.h"
#include "gl_matsysiface.h"
#include "vprof_engine.h"
#include "server.h"
#include "cl_demo.h"
#include "toolframework/itoolframework.h"
#include "toolframework/itoolsystem.h"
#include "inputsystem/iinputsystem.h"
#include "gl_cvars.h"
#include "filesystem_engine.h"
#include "tier0/cpumonitoring.h"
#ifndef SWDS
#include "vgui_baseui_interface.h"
#endif
#include "tier0/etwprof.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef POSIX
#include <signal.h>
#endif
//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
void Sys_ShutdownGame( void );
int Sys_InitGame( CreateInterfaceFn appSystemFactory,
char const* pBaseDir, void *pwnd, int bIsDedicated );
// Sleep time when not focus. Set to 0 to not sleep even if app doesn't have focus.
ConVar engine_no_focus_sleep( "engine_no_focus_sleep", "50", FCVAR_ARCHIVE );
// sleep time when not focus
#define NOT_FOCUS_SLEEP 50
#define DEFAULT_FPS_MAX 300
#define DEFAULT_FPS_MAX_S "300"
static int s_nDesiredFPSMax = DEFAULT_FPS_MAX;
static bool s_bFPSMaxDrivenByPowerSavings = false;
//-----------------------------------------------------------------------------
// ConVars and ConCommands
//-----------------------------------------------------------------------------
static void fps_max_callback( IConVar *var, const char *pOldValue, float flOldValue )
{
// Only update s_nDesiredFPSMax when not driven by the mat_powersavingsmode ConVar (see below)
if ( !s_bFPSMaxDrivenByPowerSavings )
{
s_nDesiredFPSMax = ( (ConVar *)var)->GetInt();
}
}
ConVar fps_max( "fps_max", DEFAULT_FPS_MAX_S, FCVAR_NOT_CONNECTED, "Frame rate limiter, cannot be set while connected to a server.", fps_max_callback );
// When set, this ConVar (typically driven from the advanced video settings) will drive fps_max (see above) to
// half of the refresh rate, if the user hasn't otherwise set fps_max (via console, commandline etc)
static void mat_powersavingsmode_callback( IConVar *var, const char *pOldValue, float flOldValue )
{
s_bFPSMaxDrivenByPowerSavings = true;
int nRefresh = s_nDesiredFPSMax;
if ( ( (ConVar *)var)->GetBool() )
{
MaterialVideoMode_t mode;
materials->GetDisplayMode( mode );
nRefresh = MAX( 30, ( mode.m_RefreshRate + 1 ) >> 1 ); // Half of display refresh rate (min of 30Hz)
}
fps_max.SetValue( nRefresh );
s_bFPSMaxDrivenByPowerSavings = false;
}
static ConVar mat_powersavingsmode( "mat_powersavingsmode", "0", FCVAR_ARCHIVE, "Power Savings Mode", mat_powersavingsmode_callback );
#ifndef _RETAIL
static ConVar async_serialize( "async_serialize", "0", 0, "Force async reads to serialize for profiling" );
#define ShouldSerializeAsync() async_serialize.GetBool()
#else
#define ShouldSerializeAsync() false
#endif
extern ConVar host_timer_spin_ms;
extern float host_nexttick;
extern IVEngineClient *engineClient;
#ifdef WIN32
static void cpu_frequency_monitoring_callback( IConVar *var, const char *pOldValue, float flOldValue )
{
// Set the specified interval for CPU frequency monitoring
SetCPUMonitoringInterval( (unsigned)( ( (ConVar *)var)->GetFloat() * 1000 ) );
}
ConVar cpu_frequency_monitoring( "cpu_frequency_monitoring", "0", 0, "Set CPU frequency monitoring interval in seconds. Zero means disabled.", true, 0.0f, true, 10.0f, cpu_frequency_monitoring_callback );
#endif
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CEngine : public IEngine
{
public:
CEngine( void );
virtual ~CEngine( void );
bool Load( bool dedicated, const char *basedir );
virtual void Unload( void );
virtual EngineState_t GetState( void );
virtual void SetNextState( EngineState_t iNextState );
void Frame( void );
float GetFrameTime( void );
float GetCurTime( void );
bool TrapKey_Event( ButtonCode_t key, bool down );
void TrapMouse_Event( int buttons, bool down );
void StartTrapMode( void );
bool IsTrapping( void );
bool CheckDoneTrapping( ButtonCode_t& key );
int GetQuitting( void );
void SetQuitting( int quittype );
private:
bool FilterTime( float t );
int m_nQuitting;
EngineState_t m_nDLLState;
EngineState_t m_nNextDLLState;
double m_flCurrentTime;
double m_flFrameTime;
double m_flPreviousTime;
float m_flFilteredTime;
float m_flMinFrameTime; // Expected duration of a frame, or zero if it is unlimited.
float m_flLastRemainder; // 'Unused' time on the last render loop.
bool m_bCatchupTime;
};
static CEngine g_Engine;
IEngine *eng = ( IEngine * )&g_Engine;
//IEngineAPI *engine = NULL;
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CEngine::CEngine( void )
{
m_nDLLState = DLL_INACTIVE;
m_nNextDLLState = DLL_INACTIVE;
m_flCurrentTime = 0.0;
m_flFrameTime = 0.0f;
m_flPreviousTime = 0.0;
m_flFilteredTime = 0.0f;
m_flMinFrameTime = 0.0f;
m_flLastRemainder = 0.0f;
m_bCatchupTime = false;
m_nQuitting = QUIT_NOTQUITTING;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CEngine::~CEngine( void )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngine::Unload( void )
{
Sys_ShutdownGame();
m_nDLLState = DLL_INACTIVE;
m_nNextDLLState = DLL_INACTIVE;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngine::Load( bool bDedicated, const char *rootdir )
{
bool success = false;
// Activate engine
// NOTE: We must bypass the 'next state' block here for initialization to work properly.
m_nDLLState = m_nNextDLLState = InEditMode() ? DLL_PAUSED : DLL_ACTIVE;
if ( Sys_InitGame(
g_AppSystemFactory,
rootdir,
game->GetMainWindowAddress(),
bDedicated ) )
{
success = true;
UpdateMaterialSystemConfig();
}
return success;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : dt -
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CEngine::FilterTime( float dt )
{
if ( sv.IsDedicated() && !g_bDedicatedServerBenchmarkMode )
{
m_flMinFrameTime = host_nexttick;
return ( dt >= host_nexttick );
}
m_flMinFrameTime = 0.0f;
// Dedicated's tic_rate regulates server frame rate. Don't apply fps filter here.
// Only do this restriction on the client. Prevents clients from accomplishing certain
// hacks by pausing their client for a period of time.
if ( IsPC() && !sv.IsDedicated() && !CanCheat() && fps_max.GetFloat() < 30 )
{
// Don't do anything if fps_max=0 (which means it's unlimited).
if ( fps_max.GetFloat() != 0.0f )
{
Warning( "sv_cheats is 0 and fps_max is being limited to a minimum of 30 (or set to 0).\n" );
fps_max.SetValue( 30.0f );
}
}
float fps = fps_max.GetFloat();
if ( fps > 0.0f )
{
// Limit fps to withing tolerable range
// fps = max( MIN_FPS, fps ); // red herring - since we're only checking if dt < 1/fps, clamping against MIN_FPS has no effect
fps = min( MAX_FPS, (double)fps );
float minframetime = 1.0 / fps;
m_flMinFrameTime = minframetime;
if (
#if !defined(SWDS)
!demoplayer->IsPlayingTimeDemo() &&
#endif
!g_bDedicatedServerBenchmarkMode &&
dt < minframetime )
{
// framerate is too high
return false;
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Output : int
//-----------------------------------------------------------------------------
void CEngine::Frame( void )
{
// yield the CPU for a little while when paused, minimized, or not the focus
// FIXME: Move this to main windows message pump?
if ( IsPC() && !game->IsActiveApp() && !sv.IsDedicated() && engine_no_focus_sleep.GetInt() > 0 )
{
VPROF_BUDGET( "Sleep", VPROF_BUDGETGROUP_SLEEPING );
#if defined( RAD_TELEMETRY_ENABLED )
if( !g_Telemetry.Level )
#endif
g_pInputSystem->SleepUntilInput( engine_no_focus_sleep.GetInt() );
}
if ( m_flPreviousTime == 0 )
{
(void) FilterTime( 0.0f );
m_flPreviousTime = Sys_FloatTime() - m_flMinFrameTime;
}
// Watch for data from the CPU frequency monitoring system and print it to the console.
const CPUFrequencyResults frequency = GetCPUFrequencyResults();
static double s_lastFrequencyTimestamp;
if ( frequency.m_timeStamp > s_lastFrequencyTimestamp )
{
s_lastFrequencyTimestamp = frequency.m_timeStamp;
Msg( "~CPU Freq: %1.3f GHz Percent of requested: %3.1f%% Minimum percent seen: %3.1f%%\n",
frequency.m_GHz, frequency.m_percentage, frequency.m_lowestPercentage );
}
// Loop until it is time for our frame. Don't return early because pumping messages
// and processing console input is expensive (0.1 ms for each call to ProcessConsoleInput).
for (;;)
{
// Get current time
m_flCurrentTime = Sys_FloatTime();
// Determine dt since we last ticked
m_flFrameTime = m_flCurrentTime - m_flPreviousTime;
// This should never happen...
Assert( m_flFrameTime >= 0.0f );
if ( m_flFrameTime < 0.0f )
{
// ... but if the clock ever went backwards due to a bug,
// we'd have no idea how much time has elapsed, so just
// catch up to the next scheduled server tick.
m_flFrameTime = host_nexttick;
}
if ( FilterTime( m_flFrameTime ) )
{
// Time to render our frame.
break;
}
if ( IsPC() && ( !sv.IsDedicated() || host_timer_spin_ms.GetFloat() != 0 ) )
{
// ThreadSleep may be imprecise. On non-dedicated servers, we busy-sleep
// for the last one or two milliseconds to ensure very tight timing.
float fBusyWaitMS = IsWindows() ? 2.25f : 1.5f;
if ( sv.IsDedicated() )
{
fBusyWaitMS = host_timer_spin_ms.GetFloat();
fBusyWaitMS = MAX( fBusyWaitMS, 0.5f );
}
// If we are meeting our frame rate then go idle for a while
// to avoid wasting power and to let other threads/processes run.
// Calculate how long we need to wait.
int nSleepMS = (int)( ( m_flMinFrameTime - m_flFrameTime ) * 1000 - fBusyWaitMS );
if ( nSleepMS > 0 )
ThreadSleep( nSleepMS );
// Go back to the top of the loop and see if it is time yet.
}
else
{
int nSleepMicrosecs = (int) ceilf( clamp( ( m_flMinFrameTime - m_flFrameTime ) * 1000000.f, 1.f, 1000000.f ) );
#ifdef POSIX
usleep( nSleepMicrosecs );
#else
ThreadSleep( (nSleepMicrosecs + 999) / 1000 );
#endif
}
}
if ( ShouldSerializeAsync() )
{
static ConVar *pSyncReportConVar = g_pCVar->FindVar( "fs_report_sync_opens" );
bool bReportingSyncOpens = ( pSyncReportConVar && pSyncReportConVar->GetInt() );
int reportLevel = 0;
if ( bReportingSyncOpens )
{
reportLevel = pSyncReportConVar->GetInt();
pSyncReportConVar->SetValue( 0 );
}
g_pFileSystem->AsyncFinishAll();
if ( bReportingSyncOpens )
{
pSyncReportConVar->SetValue( reportLevel );
}
}
#ifdef VPROF_ENABLED
PreUpdateProfile( m_flFrameTime );
#endif
// Reset swallowed time...
m_flFilteredTime = 0.0f;
#ifndef SWDS
if ( !sv.IsDedicated() )
{
ClientDLL_FrameStageNotify( FRAME_START );
ETWRenderFrameMark( false );
}
#endif
#ifdef VPROF_ENABLED
PostUpdateProfile();
#endif
TelemetryTick();
{ // profile scope
VPROF_BUDGET( "CEngine::Frame", VPROF_BUDGETGROUP_OTHER_UNACCOUNTED );
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
#ifdef RAD_TELEMETRY_ENABLED
TmU64 time0 = tmFastTime();
#endif
switch( m_nDLLState )
{
case DLL_PAUSED: // paused, in hammer
case DLL_INACTIVE: // no dll
break;
case DLL_ACTIVE: // engine is focused
case DLL_CLOSE: // closing down dll
case DLL_RESTART: // engine is shutting down but will restart right away
// Run the engine frame
HostState_Frame( m_flFrameTime );
break;
}
// Has the state changed?
if ( m_nNextDLLState != m_nDLLState )
{
m_nDLLState = m_nNextDLLState;
// Do special things if we change to particular states
switch( m_nDLLState )
{
case DLL_CLOSE:
SetQuitting( QUIT_TODESKTOP );
break;
case DLL_RESTART:
SetQuitting( QUIT_RESTART );
break;
}
}
#ifdef RAD_TELEMETRY_ENABLED
float time = ( tmFastTime() - time0 ) * g_Telemetry.flRDTSCToMilliSeconds;
if( time > 0.5f )
{
tmPlot( TELEMETRY_LEVEL0, TMPT_TIME_MS, 0, time, "CEngine::Frame" );
}
#endif
} // profile scope
// Remember old time
m_flPreviousTime = m_flCurrentTime;
#if defined( VPROF_ENABLED ) && defined( _X360 )
UpdateVXConsoleProfile();
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CEngine::EngineState_t CEngine::GetState( void )
{
return m_nDLLState;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CEngine::SetNextState( EngineState_t iNextState )
{
m_nNextDLLState = iNextState;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CEngine::GetFrameTime( void )
{
return m_flFrameTime;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CEngine::GetCurTime( void )
{
return m_flCurrentTime;
}
//-----------------------------------------------------------------------------
// Purpose: Flag that we are in the process of quiting
//-----------------------------------------------------------------------------
void CEngine::SetQuitting( int quittype )
{
m_nQuitting = quittype;
}
//-----------------------------------------------------------------------------
// Purpose: Check whether we are ready to exit
//-----------------------------------------------------------------------------
int CEngine::GetQuitting( void )
{
return m_nQuitting;
}