From 83f500dc0801c8dbd91595d9251c38a366a770ab Mon Sep 17 00:00:00 2001 From: Yamagi Date: Mon, 31 Aug 2020 16:00:32 +0200 Subject: [PATCH 1/5] Implement Sys_Microseconds() and warp Sys_Milliseconds() around it. To separate the client into time zones (planned are are at least two, one for the renderer and one for everything else) precise time counter independent of the complicated and delay prone timer framework are needed. * Move Sys_Milliseconds() from threads.cpp (were it doesn't belong) into the system depended code. * Implement Sys_Microseconds() for Windows and Posix plattform. * Make Sys_Milliseconds() a wrapper around Sys_Microseconds(). * Add header entries for Sys_Microseconds(). --- neo/CMakeLists.txt | 2 + neo/framework/Common.cpp | 1 - neo/sys/posix/posix_shared.cpp | 98 ++++++++++++++++++++++++++++++++++ neo/sys/sys_public.h | 8 +-- neo/sys/threads.cpp | 9 ---- neo/sys/win32/win_shared.cpp | 35 ++++++++++++ 6 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 neo/sys/posix/posix_shared.cpp diff --git a/neo/CMakeLists.txt b/neo/CMakeLists.txt index 78e8ff4ed..6248c5e57 100644 --- a/neo/CMakeLists.txt +++ b/neo/CMakeLists.txt @@ -875,6 +875,7 @@ elseif(APPLE) sys/sys_local.cpp sys/posix/posix_net.cpp sys/posix/posix_main.cpp + sys/posix/posix_shared.cpp ) set(src_sys_core @@ -927,6 +928,7 @@ else() sys/sys_local.cpp sys/posix/posix_net.cpp sys/posix/posix_main.cpp + sys/posix/posix_shared.cpp sys/linux/main.cpp ) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 87d9df69c..e3692eb92 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -2386,7 +2386,6 @@ idCommonLocal::Frame */ void idCommonLocal::Frame( void ) { try { - // pump all the events Sys_GenerateEvents(); diff --git a/neo/sys/posix/posix_shared.cpp b/neo/sys/posix/posix_shared.cpp new file mode 100644 index 000000000..f13ec3b6d --- /dev/null +++ b/neo/sys/posix/posix_shared.cpp @@ -0,0 +1,98 @@ +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include +#include + +/* +================ +Sys_Microseconds +================ +*/ +unsigned long long Sys_Microseconds() { +#ifdef __APPLE__ + // OSX didn't have clock_gettime() until recently, so use Mach's clock_get_time() + // instead. fortunately its mach_timespec_t seems identical to POSIX struct timespec + // so lots of code can be shared + clock_serv_t cclock; + mach_timespec_t now; + static mach_timespec_t first; + + host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock); + clock_get_time(cclock, &now); + mach_port_deallocate(mach_task_self(), cclock); + +#else // not __APPLE__ - other Unix-likes will hopefully support clock_gettime() + + struct timespec now; + static struct timespec first; +#ifdef _POSIX_MONOTONIC_CLOCK + clock_gettime(CLOCK_MONOTONIC, &now); +#else + clock_gettime(CLOCK_REALTIME, &now); +#endif + +#endif // not __APPLE__ + + if(first.tv_sec == 0) + { + long long nsec = now.tv_nsec; + long long sec = now.tv_sec; + // set back first by 1ms so neither this function nor Sys_Milliseconds() + // (which calls this) will ever return 0 + nsec -= 1000000; + if(nsec < 0) + { + nsec += 1000000000ll; // 1s in ns => definitely positive now + --sec; + } + + first.tv_sec = sec; + first.tv_nsec = nsec; + } + + long long sec = now.tv_sec - first.tv_sec; + long long nsec = now.tv_nsec - first.tv_nsec; + + if(nsec < 0) + { + nsec += 1000000000ll; // 1s in ns + --sec; + } + + return sec*1000000ll + nsec/1000ll; +} + +/* +================ +Sys_Milliseconds +================ +*/ +unsigned int Sys_Milliseconds() { + return (int)(Sys_Microseconds()/1000ll); +} diff --git a/neo/sys/sys_public.h b/neo/sys/sys_public.h index fbac1a886..5eda327d2 100644 --- a/neo/sys/sys_public.h +++ b/neo/sys/sys_public.h @@ -115,9 +115,11 @@ void Sys_DebugVPrintf( const char *fmt, va_list arg ); // NOTE: due to SDL_TIMESLICE this is very bad portability karma, and should be completely removed void Sys_Sleep( int msec ); -// Sys_Milliseconds should only be used for profiling purposes, -// any game related timing information should come from event timestamps -unsigned int Sys_Milliseconds( void ); +// Sys_Milliseconds and Sys_Microseconds are used in the main loop and fpr +// profiling purposes. any game related timing information should come from +// event timestamps +unsigned long long Sys_Microseconds( void ); +unsigned int Sys_Milliseconds( void ); // returns a selection of the CPUID_* flags int Sys_GetProcessorId( void ); diff --git a/neo/sys/threads.cpp b/neo/sys/threads.cpp index a0fe7c8db..a7f3fe09f 100644 --- a/neo/sys/threads.cpp +++ b/neo/sys/threads.cpp @@ -53,15 +53,6 @@ void Sys_Sleep(int msec) { SDL_Delay(msec); } -/* -================ -Sys_Milliseconds -================ -*/ -unsigned int Sys_Milliseconds() { - return SDL_GetTicks(); -} - /* ================== Sys_InitThreads diff --git a/neo/sys/win32/win_shared.cpp b/neo/sys/win32/win_shared.cpp index 7c6f09e26..0fae8fa03 100644 --- a/neo/sys/win32/win_shared.cpp +++ b/neo/sys/win32/win_shared.cpp @@ -101,3 +101,38 @@ Sys_SetPhysicalWorkMemory void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ) { ::SetProcessWorkingSetSize( GetCurrentProcess(), minBytes, maxBytes ); } + +/* +================ +Sys_Microseconds +================ +*/ +unsigned long long Sys_Microseconds() { + static LARGE_INTEGER freq = { 0 }; + static LARGE_INTEGER base = { 0 }; + + if (!freq.QuadPart) + { + QueryPerformanceFrequency(&freq); + } + + if (!base.QuadPart) + { + QueryPerformanceCounter(&base); + base.QuadPart -= 1001; + } + + LARGE_INTEGER cur; + QueryPerformanceCounter(&cur); + + return (cur.QuadPart - base.QuadPart) * 1000000 / freq.QuadPart; +} + +/* +================ +Sys_Milliseconds +================ +*/ +unsigned int Sys_Milliseconds() { + return (int)(Sys_Microseconds()/1000ll); +} From 064bb3f3e8bc3067db05097cf06b04bf8670a98f Mon Sep 17 00:00:00 2001 From: Yamagi Date: Mon, 31 Aug 2020 16:30:30 +0200 Subject: [PATCH 2/5] Split the client into two timezones. * Implement some kind of break, i.e. slow the main loop a little bit down and use special OPCODES to tell the CPU that we are in a very tight spin loop. This prevent overheating. * Introduce the render timezone for the renderer, call the renderer up to 300 times a second. This is currently hartcoded, a cvar will be introduced in a later commit. * Everything else is the game timezone, hartcoded to 60 frames. The game can't run faster anyways. * COmment old timeer based break. The code isn't removed because we need it to optionally support the old behaviour without time zones. --- neo/framework/Common.cpp | 116 ++++++++++++++++++++++++++++---------- neo/framework/Session.cpp | 4 ++ 2 files changed, 91 insertions(+), 29 deletions(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index e3692eb92..2fb9ae79f 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -2385,51 +2385,109 @@ idCommonLocal::Frame ================= */ void idCommonLocal::Frame( void ) { - try { - // pump all the events - Sys_GenerateEvents(); + // Slow things a little bit down. + unsigned long long spintime = Sys_Microseconds(); - // write config file if anything changed - WriteConfiguration(); + while ( 1 ) { +#if defined (__GNUC__) && (__i386 || __x86_64__) + asm("pause"); +#elif defined(__aarch64__) || (defined(__ARM_ARCH) && __ARM_ARCH >= 7) || defined(__ARM_ARCH_6K__) + asm("yield"); +#endif - // change SIMD implementation if required - if ( com_forceGenericSIMD.IsModified() ) { - InitSIMD(); + if ( Sys_Microseconds() >= ( spintime - 5 ) ) { + break; } + } - eventLoop->RunEventLoop(); + // Calculate timings. + static unsigned long long oldframetime; + unsigned long long newframetime = Sys_Microseconds(); + unsigned long long frametime = newframetime - oldframetime; + oldframetime = newframetime; - com_frameTime = com_ticNumber * USERCMD_MSEC; + static int clientdelta = 1000000; + static int renderdelta = 1000000; + clientdelta += frametime; + renderdelta += frametime; - idAsyncNetwork::RunFrame(); + bool clientframe = true; + bool renderframe = true; + + if ( clientdelta < ( 1000000 / 60 ) ) { + clientframe = false; + } else { + clientdelta = 0; + } + + if ( renderdelta < ( 1000000 / 300 ) ) { + renderframe = false; + } else { + renderdelta = 0; + } + + // Early return if no work has to be done. + if ( !clientframe && !renderframe ) { + return; + } + + // Rund the game. + try { + if ( clientframe ) { + // Pump the events. + Sys_GenerateEvents(); + + // Write config file (if anything changed). + WriteConfiguration(); + + // Change SIMD implementation if required. + if ( com_forceGenericSIMD.IsModified() ) { + InitSIMD(); + } + + eventLoop->RunEventLoop(); + com_frameTime = com_ticNumber * USERCMD_MSEC; + idAsyncNetwork::RunFrame(); + } if ( idAsyncNetwork::IsActive() ) { if ( idAsyncNetwork::serverDedicated.GetInteger() != 1 ) { - session->GuiFrameEvents(); - session->UpdateScreen( false ); + if ( clientframe ) { + session->GuiFrameEvents(); + } + + if ( renderframe ) { + session->UpdateScreen( false ); + } } } else { - session->Frame(); + if ( clientframe ) { + session->Frame(); + } - // normal, in-sequence screen update - session->UpdateScreen( false ); + if ( renderframe ) { + // normal, in-sequence screen update + session->UpdateScreen( false ); + } } - // report timing information - if ( com_speeds.GetBool() ) { - static int lastTime; - int nowTime = Sys_Milliseconds(); - int com_frameMsec = nowTime - lastTime; - lastTime = nowTime; - Printf( "frame:%i all:%3i gfr:%3i rf:%3i bk:%3i\n", com_frameNumber, com_frameMsec, time_gameFrame, time_frontend, time_backend ); - time_gameFrame = 0; - time_gameDraw = 0; - } + if ( clientframe ) { + // report timing information + if ( com_speeds.GetBool() ) { + static int lastTime; + int nowTime = Sys_Milliseconds(); + int com_frameMsec = nowTime - lastTime; + lastTime = nowTime; + Printf( "frame:%i all:%3i gfr:%3i rf:%3i bk:%3i\n", com_frameNumber, com_frameMsec, time_gameFrame, time_frontend, time_backend ); + time_gameFrame = 0; + time_gameDraw = 0; + } - com_frameNumber++; + com_frameNumber++; - // set idLib frame number for frame based memory dumps - idLib::frameNumber = com_frameNumber; + // set idLib frame number for frame based memory dumps + idLib::frameNumber = com_frameNumber; + } } catch( idException & ) { diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index e1fee6220..702c7d278 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -2606,6 +2606,7 @@ void idSessionLocal::Frame() { minTic = latchedTicNumber; } + /* while( 1 ) { latchedTicNumber = com_ticNumber; if ( latchedTicNumber >= minTic ) { @@ -2613,6 +2614,9 @@ void idSessionLocal::Frame() { } Sys_WaitForEvent( TRIGGER_EVENT_ONE ); } + */ + + latchedTicNumber = com_ticNumber; if ( authEmitTimeout ) { // waiting for a game auth From 02a4ba72f6cbd0d0aa16f9747aa9910216b9fe3b Mon Sep 17 00:00:00 2001 From: Yamagi Date: Fri, 4 Sep 2020 10:09:06 +0200 Subject: [PATCH 3/5] Add com_renderFPS cvar, defaults to 300. --- neo/framework/Common.cpp | 3 ++- neo/framework/Common.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 2fb9ae79f..a9fda608a 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -97,6 +97,7 @@ idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time idCVar com_timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "scales the time", 0.1f, 10.0f ); idCVar com_makingBuild( "com_makingBuild", "0", CVAR_BOOL | CVAR_SYSTEM, "1 when making a build" ); idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "update the load size after loading a map" ); +idCVar com_renderFPS( "com_renderFPS", "300", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "frames per second to render" ); idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); @@ -2420,7 +2421,7 @@ void idCommonLocal::Frame( void ) { clientdelta = 0; } - if ( renderdelta < ( 1000000 / 300 ) ) { + if ( renderdelta < ( 1000000 / cvarSystem->GetCVarInteger( "com_renderFPS" ) ) ) { renderframe = false; } else { renderdelta = 0; diff --git a/neo/framework/Common.h b/neo/framework/Common.h index c0400bb47..1b0a48bcc 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -73,6 +73,7 @@ extern idCVar com_showAsyncStats; extern idCVar com_showSoundDecoders; extern idCVar com_makingBuild; extern idCVar com_updateLoadSize; +extern idCVar com_renderFPS; extern int time_gameFrame; // game logic time extern int time_gameDraw; // game present time From 14b5bd247b56cc3010bd9cf8c2a6fcb3b613fd0d Mon Sep 17 00:00:00 2001 From: Yamagi Date: Fri, 4 Sep 2020 10:16:37 +0200 Subject: [PATCH 4/5] Hide the async client behind com_asyncClient, defaults to 0. --- neo/framework/Common.cpp | 26 +++++++++++++++----------- neo/framework/Common.h | 1 + neo/framework/Session.cpp | 16 ++++++++-------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index a9fda608a..097c39f47 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -97,6 +97,7 @@ idCVar com_timestampPrints( "com_timestampPrints", "0", CVAR_SYSTEM, "print time idCVar com_timescale( "timescale", "1", CVAR_SYSTEM | CVAR_FLOAT, "scales the time", 0.1f, 10.0f ); idCVar com_makingBuild( "com_makingBuild", "0", CVAR_BOOL | CVAR_SYSTEM, "1 when making a build" ); idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "update the load size after loading a map" ); +idCVar com_asyncClient ( "com_asyncClient", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_ARCHIVE, "run client and renderer asynchronous" ); idCVar com_renderFPS( "com_renderFPS", "300", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "frames per second to render" ); idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); @@ -2409,22 +2410,25 @@ void idCommonLocal::Frame( void ) { static int clientdelta = 1000000; static int renderdelta = 1000000; - clientdelta += frametime; - renderdelta += frametime; bool clientframe = true; bool renderframe = true; - if ( clientdelta < ( 1000000 / 60 ) ) { - clientframe = false; - } else { - clientdelta = 0; - } + if ( cvarSystem->GetCVarBool( "com_asyncClient" ) == true ) { + clientdelta += frametime; + renderdelta += frametime; - if ( renderdelta < ( 1000000 / cvarSystem->GetCVarInteger( "com_renderFPS" ) ) ) { - renderframe = false; - } else { - renderdelta = 0; + if ( clientdelta < ( 1000000 / 60 ) ) { + clientframe = false; + } else { + clientdelta = 0; + } + + if ( renderdelta < ( 1000000 / cvarSystem->GetCVarInteger( "com_renderFPS" ) ) ) { + renderframe = false; + } else { + renderdelta = 0; + } } // Early return if no work has to be done. diff --git a/neo/framework/Common.h b/neo/framework/Common.h index 1b0a48bcc..a1afd5110 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -73,6 +73,7 @@ extern idCVar com_showAsyncStats; extern idCVar com_showSoundDecoders; extern idCVar com_makingBuild; extern idCVar com_updateLoadSize; +extern idCVar com_asyncClient; extern idCVar com_renderFPS; extern int time_gameFrame; // game logic time diff --git a/neo/framework/Session.cpp b/neo/framework/Session.cpp index 702c7d278..93357c4b8 100644 --- a/neo/framework/Session.cpp +++ b/neo/framework/Session.cpp @@ -2606,17 +2606,17 @@ void idSessionLocal::Frame() { minTic = latchedTicNumber; } - /* - while( 1 ) { + if ( cvarSystem->GetCVarBool( "com_asyncClient" ) == true ) { latchedTicNumber = com_ticNumber; - if ( latchedTicNumber >= minTic ) { - break; + } else { + while( 1 ) { + latchedTicNumber = com_ticNumber; + if ( latchedTicNumber >= minTic ) { + break; + } + Sys_WaitForEvent( TRIGGER_EVENT_ONE ); } - Sys_WaitForEvent( TRIGGER_EVENT_ONE ); } - */ - - latchedTicNumber = com_ticNumber; if ( authEmitTimeout ) { // waiting for a game auth From 9c29a25f287ea6dcd929c477e981f69b79cf905e Mon Sep 17 00:00:00 2001 From: Yamagi Date: Sat, 12 Sep 2020 17:29:51 +0200 Subject: [PATCH 5/5] Add com_clientFPS, defaults to 60. --- neo/framework/Common.cpp | 3 ++- neo/framework/Common.h | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/neo/framework/Common.cpp b/neo/framework/Common.cpp index 097c39f47..2963f1a65 100644 --- a/neo/framework/Common.cpp +++ b/neo/framework/Common.cpp @@ -99,6 +99,7 @@ idCVar com_makingBuild( "com_makingBuild", "0", CVAR_BOOL | CVAR_SYSTEM, "1 when idCVar com_updateLoadSize( "com_updateLoadSize", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_NOCHEAT, "update the load size after loading a map" ); idCVar com_asyncClient ( "com_asyncClient", "0", CVAR_BOOL | CVAR_SYSTEM | CVAR_ARCHIVE, "run client and renderer asynchronous" ); idCVar com_renderFPS( "com_renderFPS", "300", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "frames per second to render" ); +idCVar com_clientFPS( "com_clientFPS", "60", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "times per second the game is called" ); idCVar com_product_lang_ext( "com_product_lang_ext", "1", CVAR_INTEGER | CVAR_SYSTEM | CVAR_ARCHIVE, "Extension to use when creating language files." ); @@ -2418,7 +2419,7 @@ void idCommonLocal::Frame( void ) { clientdelta += frametime; renderdelta += frametime; - if ( clientdelta < ( 1000000 / 60 ) ) { + if ( clientdelta < ( 1000000 / cvarSystem->GetCVarInteger( "com_clientFPS" ) ) ) { clientframe = false; } else { clientdelta = 0; diff --git a/neo/framework/Common.h b/neo/framework/Common.h index a1afd5110..dd45bab4f 100644 --- a/neo/framework/Common.h +++ b/neo/framework/Common.h @@ -75,6 +75,7 @@ extern idCVar com_makingBuild; extern idCVar com_updateLoadSize; extern idCVar com_asyncClient; extern idCVar com_renderFPS; +extern idCVar com_clientFPS; extern int time_gameFrame; // game logic time extern int time_gameDraw; // game present time