void idBoundsTrack::Test() { ClearAll(); idRandom r; for( int i = 0 ; i < 1800 ; i++ ) { idBounds b; for( int j = 0 ; j < 3 ; j++ ) { b[0][j] = r.RandomInt( 20000 ) - 10000; b[1][j] = b[0][j] + r.RandomInt( 1000 ); } SetIndex( i, b ); } const idBounds testBounds( idVec3( -1000, 2000, -3000 ), idVec3( 1500, 4500, -500 ) ); SetIndex( 1800, testBounds ); SetIndex( 0, testBounds ); const shortBounds_t shortTestBounds( testBounds ); int intersectedIndexes1[ MAX_BOUNDS_TRACK_INDEXES ]; const int numHits1 = FindBoundsIntersectionsTEST( shortTestBounds, boundsList, maxIndex, intersectedIndexes1 ); int intersectedIndexes2[ MAX_BOUNDS_TRACK_INDEXES ]; const int numHits2 = FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); idLib::Printf( "%i intersections\n", numHits1 ); if( numHits1 != numHits2 ) { idLib::Printf( "different results\n" ); } else { for( int i = 0 ; i < numHits1 ; i++ ) { if( intersectedIndexes1[i] != intersectedIndexes2[i] ) { idLib::Printf( "different results\n" ); break; } } } // run again for debugging failure FindBoundsIntersectionsTEST( shortTestBounds, boundsList, maxIndex, intersectedIndexes1 ); FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); // timing const int64 start = Sys_Microseconds(); for( int i = 0 ; i < 40 ; i++ ) { FindBoundsIntersectionsSimSIMD( shortTestBounds, boundsList, maxIndex, intersectedIndexes2 ); } const int64 stop = Sys_Microseconds(); idLib::Printf( "%" PRId64 " microseconds for 40 itterations\n", stop - start ); }
/* * CL_MouseExtrapolate * asymptotic extrapolation function */ static void CL_MouseExtrapolate( int mx, int my, float *extra_x, float *extra_y ) { static unsigned int frameNo = 0; static float sub_x = 0, sub_y = 0; static int64_t lastMicros = 0; static int64_t avgMicros = 0; float add_x = 0.0, add_y = 0.0; float decay = 1.0; float decaySum = buf_size > 1 ? 0.0 : decay; unsigned int i; int64_t micros; if( !lastMicros ) lastMicros = Sys_Microseconds() - 10000; // start at 100 FPS micros = Sys_Microseconds(); // get current time in us avgMicros = ( avgMicros + ( micros - lastMicros ) ) / 2; // calc running avg of us per frame assert( buf_size ); frameNo = ( frameNo + 1 ) % buf_size; // we use the buffer in a cyclic fashion // normalize mouse movement to pixels per microsecond buf_x[frameNo] = mx / (float) avgMicros; buf_y[frameNo] = my / (float) avgMicros; // calculate asymptotically weighted sum of movement over the last few frames assert( buf_decay >= 0.0 ); for( i = 0; i < buf_size; ++i ) { const unsigned int f = ( frameNo-i ) % buf_size; assert( f <= buf_size ); add_x += buf_x[f] * decay; add_y += buf_y[f] * decay; decaySum += decay; decay *= buf_decay; } assert( decaySum >= 1.0 ); add_x /= decaySum; add_y /= decaySum; // calculate difference to last frame and re-weigh it with avg us per frame // we need to extrapolate the delta, not the momentum alone, so the mouse will come to // rest at the same spot ultimately, regardless of extrapolation on or off *extra_x = ( add_x - sub_x ) * avgMicros; *extra_y = ( add_y - sub_y ) * avgMicros; sub_x = add_x; sub_y = add_y; lastMicros = micros; }
/* ======================== idParallelJobList_Threads::RunJobs ======================== */ int idParallelJobList_Threads::RunJobs( unsigned int threadNum, threadJobListState_t& state, bool singleJob ) { uint64 start = Sys_Microseconds(); numThreadsExecuting.Increment(); int result = RunJobsInternal( threadNum, state, singleJob ); numThreadsExecuting.Decrement(); deferredThreadStats.threadTotalTime[threadNum] += Sys_Microseconds() - start; return result; }
/* ======================== idLobby::UpdateSnaps ======================== */ void idLobby::UpdateSnaps() { assert( lobbyType == GetActingGameStateLobbyType() ); SCOPED_PROFILE_EVENT( "UpdateSnaps" ); #if 0 uint64 startTimeMicroSec = Sys_Microseconds(); #endif haveSubmittedSnaps = false; if( !SendCompletedSnaps() ) { // If we weren't able to send all the submitted snaps, we need to wait till we can. // We can't start new jobs until they are all sent out. return; } for( int p = 0; p < peers.Num(); p++ ) { peer_t& peer = peers[p]; if( !peer.IsConnected() ) { continue; } if( peer.needToSubmitPendingSnap ) { // Submit the snap if( SubmitPendingSnap( p ) ) { peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap } } } #if 0 uint64 endTimeMicroSec = Sys_Microseconds(); if( endTimeMicroSec - startTimeMicroSec > 200 ) // .2 ms { idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", ( float )( endTimeMicroSec - startTimeMicroSec ) / 1000.0f ); } #endif }
/* ======================== GLimp_TestSwapBuffers ======================== */ void GLimp_TestSwapBuffers( const idCmdArgs &args ) { #if !NGD_USE_OPENGL_ES_2_0 idLib::Printf( "GLimp_TimeSwapBuffers\n" ); static const int MAX_FRAMES = 5; uint64 timestamps[MAX_FRAMES]; glDisable( GL_SCISSOR_TEST ); int frameMilliseconds = 16; for ( int swapInterval = 2 ; swapInterval >= -1 ; swapInterval-- ) { wglSwapIntervalEXT( swapInterval ); for ( int i = 0 ; i < MAX_FRAMES ; i++ ) { if ( swapInterval == -1 ) { Sys_Sleep( frameMilliseconds ); } if ( i & 1 ) { glClearColor( 0, 1, 0, 1 ); } else { glClearColor( 1, 0, 0, 1 ); } glClear( GL_COLOR_BUFFER_BIT ); ::SwapBuffers( win32.hDC ); glFinish(); timestamps[i] = Sys_Microseconds(); } idLib::Printf( "\nswapinterval %i\n", swapInterval ); for ( int i = 1 ; i < MAX_FRAMES ; i++ ) { idLib::Printf( "%i microseconds\n", (int)(timestamps[i] - timestamps[i-1]) ); } } #else NGD_MISSING_FUNCTIONALITY; #endif // !NGD_USE_OPENGL_ES_2_0 }
/* ======================== PC_BeginNamedEvent FIXME: this is not thread safe on the PC ======================== */ void PC_BeginNamedEvent( const char *szName, ... ) { #if 0 if ( !r_pix.GetBool() ) { return; } if ( !pixEvents ) { // lazy allocation to not waste memory pixEvents = (pixEvent_t *)Mem_ClearedAlloc( sizeof( *pixEvents ) * MAX_PIX_EVENTS, TAG_CRAP ); } if ( numPixEvents >= MAX_PIX_EVENTS ) { idLib::FatalError( "PC_BeginNamedEvent: event overflow" ); } if ( ++numPixLevels > 1 ) { return; // only get top level timing information } if ( !glGetQueryObjectui64vEXT ) { return; } GL_CheckErrors(); if ( timeQueryIds[0] == 0 ) { glGenQueries( MAX_PIX_EVENTS, timeQueryIds ); } glFinish(); glBeginQuery( GL_TIME_ELAPSED_EXT, timeQueryIds[numPixEvents] ); GL_CheckErrors(); pixEvent_t *ev = &pixEvents[numPixEvents++]; strncpy( ev->name, szName, sizeof( ev->name ) - 1 ); ev->cpuTime = Sys_Microseconds(); #endif }
/* ======================== idRenderLog::StartFrame ======================== */ void idRenderLog::StartFrame() { if ( r_logFile.GetInteger() == 0 ) { return; } // open a new logfile indentLevel = 0; indentString[0] = '\0'; activeLevel = r_logLevel.GetInteger(); struct tm *newtime; time_t aclock; char ospath[ MAX_OSPATH ]; char qpath[128]; sprintf( qpath, "renderlogPC_%04i.txt", r_logFile.GetInteger() ); idStr finalPath = fileSystem->RelativePathToOSPath( qpath ); sprintf( ospath, "%s", finalPath.c_str() ); /* for ( int i = 0; i < 9999 ; i++ ) { char qpath[128]; sprintf( qpath, "renderlog_%04i.txt", r_logFile.GetInteger() ); idStr finalPath = fileSystem->RelativePathToOSPath( qpath ); fileSystem->RelativePathToOSPath( qpath, ospath, MAX_OSPATH ,FSPATH_BASE ); if ( !fileSystem->FileExists( finalPath.c_str() ) ) { break; // use this name } } */ common->SetRefreshOnPrint( false ); // problems are caused if this print causes a refresh... if ( logFile != NULL ) { fileSystem->CloseFile( logFile ); logFile = NULL; } logFile = fileSystem->OpenFileWrite( ospath ); if ( logFile == NULL ) { idLib::Warning( "Failed to open logfile %s", ospath ); return; } idLib::Printf( "Opened logfile %s\n", ospath ); // write the time out to the top of the file time( &aclock ); newtime = localtime( &aclock ); const char *str = asctime( newtime ); logFile->Printf( "// %s", str ); logFile->Printf( "// %s\n\n", com_version.GetString() ); frameStartTime = Sys_Microseconds(); closeBlockTime = frameStartTime; OpenBlock( "Frame" ); }
/* ======================== idParallelJobList_Threads::Submit ======================== */ void idParallelJobList_Threads::Submit( idParallelJobList_Threads* waitForJobList, int parallelism ) { assert( done ); assert( numSyncs <= maxSyncs ); assert( ( unsigned int ) jobList.Num() <= maxJobs + numSyncs * 2 ); assert( fetchLock.GetValue() == 0 ); done = false; currentJob.SetValue( 0 ); memset( &deferredThreadStats, 0, sizeof( deferredThreadStats ) ); deferredThreadStats.numExecutedJobs = jobList.Num() - numSyncs * 2; deferredThreadStats.numExecutedSyncs = numSyncs; deferredThreadStats.submitTime = Sys_Microseconds(); deferredThreadStats.startTime = 0; deferredThreadStats.endTime = 0; deferredThreadStats.waitTime = 0; if( jobList.Num() == 0 ) { return; } if( waitForJobList != NULL ) { waitForGuard = & waitForJobList->doneGuards[waitForJobList->currentDoneGuard]; } else { waitForGuard = NULL; } currentDoneGuard = ( currentDoneGuard + 1 ) & ( NUM_DONE_GUARDS - 1 ); doneGuards[currentDoneGuard].SetValue( 1 ); signalJobCount.Alloc(); signalJobCount[signalJobCount.Num() - 1].SetValue( jobList.Num() - lastSignalJob ); job_t& job = jobList.Alloc(); job.function = Nop; job.data = & JOB_LIST_DONE; if( threaded ) { // hand over to the manager void SubmitJobList( idParallelJobList_Threads * jobList, int parallelism ); SubmitJobList( this, parallelism ); } else { // run all the jobs right here threadJobListState_t state( GetVersion() ); RunJobs( 0, state, false ); } }
int Sys_Milliseconds (void) { /* ioq3-urt: We have moved initialization to Sys_InitTimers() notice SDL puts timeGetTime's precision to 1ms with timeBeginTime(1) */ if (clu.sys_microGranularity->integer) return Sys_Microseconds() / 1000; // hopefully, but somewhat unlikely, more precise else return timeGetTime() - microdifference; }
/* ======================== idRenderLog::LogCloseBlock ======================== */ void idRenderLog::LogCloseBlock( renderLogIndentLabel_t label ) { closeBlockTime = Sys_Microseconds(); assert( logLevel > 0 ); logLevel--; Outdent( label ); if ( logFile != NULL ) { } }
static dynvar_get_status_t Com_Sys_Uptime_f( void **val ) { static char buf[32]; const quint64 us = Sys_Microseconds(); const unsigned int h = us / 3600000000u; const unsigned int min = ( us / 60000000 ) % 60; const unsigned int sec = ( us / 1000000 ) % 60; const unsigned int usec = us % 1000000; sprintf( buf, "%02u:%02u:%02u.%06u", h, min, sec, usec ); *val = buf; return DYNVAR_GET_OK; }
/* ======================== idParallelJobList_Threads::Wait ======================== */ void idParallelJobList_Threads::Wait() { if( jobList.Num() > 0 ) { // don't lock up but return if the job list was never properly submitted if( !verify( !done && signalJobCount.Num() > 0 ) ) { return; } bool waited = false; uint64 waitStart = Sys_Microseconds(); while( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { Sys_Yield(); waited = true; } version.Increment(); while( numThreadsExecuting.GetValue() > 0 ) { Sys_Yield(); waited = true; } jobList.SetNum( 0 ); signalJobCount.SetNum( 0 ); numSyncs = 0; lastSignalJob = 0; uint64 waitEnd = Sys_Microseconds(); deferredThreadStats.waitTime = waited ? ( waitEnd - waitStart ) : 0; } memcpy( & threadStats, & deferredThreadStats, sizeof( threadStats ) ); done = true; }
/* ======================== idRenderLog::LogOpenBlock ======================== */ void idRenderLog::LogOpenBlock( renderLogIndentLabel_t label, const char * fmt, va_list args ) { uint64 now = Sys_Microseconds(); if ( logFile != NULL ) { if ( now - closeBlockTime >= 1000 ) { logFile->Printf( "%s%1.1f msec gap from last closeblock\n", indentString, ( now - closeBlockTime ) * ( 1.0f / 1000.0f ) ); } logFile->Printf( "%s", indentString ); logFile->VPrintf( fmt, args ); logFile->Printf( " {\n" ); } Indent( label ); if ( logLevel >= MAX_LOG_LEVELS ) { idLib::Warning( "logLevel %d >= MAX_LOG_LEVELS", logLevel ); } logLevel++; }
/* ======================== PC_EndNamedEvent ======================== */ void PC_EndNamedEvent() { #if 0 if ( !r_pix.GetBool() ) { return; } if ( numPixLevels <= 0 ) { idLib::FatalError( "PC_EndNamedEvent: level underflow" ); } if ( --numPixLevels > 0 ) { // only do timing on top level events return; } if ( !glGetQueryObjectui64vEXT ) { return; } pixEvent_t *ev = &pixEvents[numPixEvents-1]; ev->cpuTime = Sys_Microseconds() - ev->cpuTime; GL_CheckErrors(); glEndQuery( GL_TIME_ELAPSED_EXT ); GL_CheckErrors(); #endif }
/* * Windows main function. Containts the * initialization code and the main loop */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; long long oldtime, newtime; /* Previous instances do not exist in Win32 */ if (hPrevInstance) { return 0; } /* Make the current instance global */ global_hInstance = hInstance; /* Setup FPU if necessary */ Sys_SetupFPU(); /* Force DPI awareness */ Sys_SetHighDPIMode(); /* Parse the command line arguments */ ParseCommandLine(lpCmdLine); /* Are we portable? */ for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-portable") == 0) { is_portable = true; } } /* Need to redirect stdout before anything happens. */ #ifndef DEDICATED_ONLY Sys_RedirectStdout(); #endif printf("Yamagi Quake II v%s\n", YQ2VERSION); printf("=====================\n\n"); #ifndef DEDICATED_ONLY printf("Client build options:\n"); #ifdef SDL2 printf(" + SDL2\n"); #else printf(" - SDL2 (using 1.2)\n"); #endif #ifdef CDA printf(" + CD audio\n"); #else printf(" - CD audio\n"); #endif #ifdef OGG printf(" + OGG/Vorbis\n"); #else printf(" - OGG/Vorbis\n"); #endif #ifdef USE_OPENAL printf(" + OpenAL audio\n"); #else printf(" - OpenAL audio\n"); #endif #ifdef ZIP printf(" + Zip file support\n"); #else printf(" - Zip file support\n"); #endif #endif printf("Platform: %s\n", YQ2OSTYPE); printf("Architecture: %s\n", YQ2ARCH); /* Seed PRNG */ randk_seed(); /* Call the initialization code */ Qcommon_Init(argc, argv); /* Save our time */ oldtime = Sys_Microseconds(); /* The legendary main loop */ while (1) { /* If at a full screen console, don't update unless needed */ if (Minimized || (dedicated && dedicated->value)) { Sleep(1); } while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, 0, 0)) { Com_Quit(); } sys_msg_time = msg.time; TranslateMessage(&msg); DispatchMessage(&msg); } // Throttle the game a little bit Sys_Nanosleep(5000); newtime = Sys_Microseconds(); Qcommon_Frame(newtime - oldtime); oldtime = newtime; } /* never gets here */ return TRUE; }
/* ================= R_AddLights ================= */ void R_AddLights() { SCOPED_PROFILE_EVENT( "R_AddLights" ); //------------------------------------------------- // check each light individually, possibly in parallel //------------------------------------------------- if( r_useParallelAddLights.GetBool() ) { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )R_AddSingleLight, vLight ); } tr.frontEndJobList->Submit(); tr.frontEndJobList->Wait(); } else { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { R_AddSingleLight( vLight ); } } //------------------------------------------------- // cull lights from the list if they turned out to not be needed //------------------------------------------------- tr.pc.c_viewLights = 0; viewLight_t** ptr = &tr.viewDef->viewLights; while( *ptr != NULL ) { viewLight_t* vLight = *ptr; if( vLight->removeFromList ) { vLight->lightDef->viewCount = -1; // this probably doesn't matter with current code *ptr = vLight->next; continue; } ptr = &vLight->next; // serial work tr.pc.c_viewLights++; for( shadowOnlyEntity_t* shadEnt = vLight->shadowOnlyViewEntities; shadEnt != NULL; shadEnt = shadEnt->next ) { // this will add it to the viewEntities list, but with an empty scissor rect R_SetEntityDefViewEntity( shadEnt->edef ); } if( r_showLightScissors.GetBool() ) { R_ShowColoredScreenRect( vLight->scissorRect, vLight->lightDef->index ); } } //------------------------------------------------- // Add jobs to setup pre-light shadow volumes. //------------------------------------------------- if( r_useParallelAddShadows.GetInteger() == 1 ) { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { for( preLightShadowVolumeParms_t* shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )PreLightShadowVolumeJob, shadowParms ); } vLight->preLightShadowVolumes = NULL; } } else { int start = Sys_Microseconds(); for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { for( preLightShadowVolumeParms_t* shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { PreLightShadowVolumeJob( shadowParms ); } vLight->preLightShadowVolumes = NULL; } int end = Sys_Microseconds(); tr.backend.pc.shadowMicroSec += end - start; } }
int Sys_Milliseconds(void) { return (int)(Sys_Microseconds()/1000ll); }
/* ======================== idCommonLocal::RunNetworkSnapshotFrame ======================== */ void idCommonLocal::RunNetworkSnapshotFrame() { // Process any reliable messages we've received for ( int i = 0; i < reliableQueue.Num(); i++ ) { game->ProcessReliableMessage( reliableQueue[i].client, reliableQueue[i].type, idBitMsg( (const byte *)reliableQueue[i].data, reliableQueue[i].dataSize ) ); Mem_Free( reliableQueue[i].data ); } reliableQueue.Clear(); // abuse the game timing to time presentable thinking on clients time_gameFrame = Sys_Microseconds(); time_maxGameFrame = 0; count_numGameFrames = 0; if ( snapPrevious.serverTime >= 0 ) { int msec_interval = 1 + idMath::Ftoi( (float)initialBaseTicksPerSec ); static int clientTimeResidual = 0; static int lastTime = Sys_Milliseconds(); int currentTime = Sys_Milliseconds(); int deltaFrameTime = idMath::ClampInt( 1, 33, currentTime - lastTime ); clientTimeResidual += idMath::ClampInt( 0, 50, currentTime - lastTime ); lastTime = currentTime; extern idCVar com_fixedTic; if ( com_fixedTic.GetBool() ) { clientTimeResidual = 0; } do { // If we are extrapolating and have fresher snapshots, then use the freshest one while ( ( snapCurrentTime >= snapRate || com_forceLatestSnap.GetBool() ) && readSnapshotIndex < writeSnapshotIndex ) { snapCurrentTime -= snapRate; ProcessNextSnapshot(); } // this only matters when running < 60 fps // JAF Game()->GetRenderWorld()->UpdateDeferredPositions(); // Clamp the current time so that it doesn't fall outside of our extrapolation bounds snapCurrentTime = idMath::ClampInt( 0, snapRate + Min( (int)snapRate, (int)net_maxExtrapolationInMS.GetInteger() ), snapCurrentTime ); if ( snapRate <= 0 ) { idLib::Warning("snapRate <= 0. Resetting to 100"); snapRate = 100; } float fraction = (float)snapCurrentTime / (float)snapRate; if ( !IsValid( fraction ) ) { idLib::Warning("Interpolation Fraction invalid: snapCurrentTime %d / snapRate %d", (int)snapCurrentTime, (int)snapRate ); fraction = 0.0f; } InterpolateSnapshot( snapPrevious, snapCurrent, fraction, true ); // Default to a snap scale of 1 float snapRateScale = net_interpolationBaseRate.GetFloat(); snapTimeBuffered = CalcSnapTimeBuffered( totalBufferedTime, totalRecvTime ); effectiveSnapRate = static_cast< float > ( totalBufferedTime ) / static_cast< float > ( totalRecvTime ); if ( net_minBufferedSnapPCT_Static.GetFloat() > 0.0f ) { optimalPCTBuffer = session->GetTitleStorageFloat( "net_minBufferedSnapPCT_Static", net_minBufferedSnapPCT_Static.GetFloat() ); } // Calculate optimal amount of buffered time we want if ( net_optimalDynamic.GetBool() ) { optimalTimeBuffered = idMath::ClampInt( 0, net_maxBufferedSnapMS.GetInteger(), snapRate * optimalPCTBuffer ); optimalTimeBufferedWindow = snapRate * net_minBufferedSnapWinPCT_Static.GetFloat(); } else { optimalTimeBuffered = net_optimalSnapTime.GetFloat(); optimalTimeBufferedWindow = net_optimalSnapWindow.GetFloat(); } // Scale snapRate based on where we are in the buffer if ( snapTimeBuffered <= optimalTimeBuffered ) { if ( snapTimeBuffered <= idMath::FLT_SMALLEST_NON_DENORMAL ) { snapRateScale = 0; } else { snapRateScale = net_interpolationFallbackRate.GetFloat(); // When we interpolate past our cushion of buffered snapshot, we want to slow smoothly slow the // rate of interpolation. frac will go from 1.0 to 0.0 (if snapshots stop coming in). float startSlowdown = ( net_interpolationSlowdownStart.GetFloat() * optimalTimeBuffered ); if ( startSlowdown > 0 && snapTimeBuffered < startSlowdown ) { float frac = idMath::ClampFloat( 0.0f, 1.0f, snapTimeBuffered / startSlowdown ); if ( !IsValid( frac ) ) { frac = 0.0f; } snapRateScale = Square( frac ) * snapRateScale; if ( !IsValid( snapRateScale ) ) { snapRateScale = 0.0f; } } } } else if ( snapTimeBuffered > optimalTimeBuffered + optimalTimeBufferedWindow ) { // Go faster snapRateScale = net_interpolationCatchupRate.GetFloat(); } float delta_interpolate = (float)initialBaseTicksPerSec * snapRateScale; if ( net_effectiveSnapRateEnable.GetBool() ) { float deltaFrameGameMS = static_cast<float>( initialBaseTicksPerSec ) * static_cast<float>( deltaFrameTime / 1000.0f ); delta_interpolate = ( deltaFrameGameMS * snapRateScale * effectiveSnapRate ) + snapCurrentResidual; if ( !IsValid( delta_interpolate ) ) { delta_interpolate = 0.0f; } snapCurrentResidual = idMath::Frac( delta_interpolate ); // fixme: snapCurrentTime should just be a float, but would require changes in d4 too if ( !IsValid( snapCurrentResidual ) ) { snapCurrentResidual = 0.0f; } if ( net_effectiveSnapRateDebug.GetBool() ) { idLib::Printf("%d/%.2f snapRateScale: %.2f effectiveSR: %.2f d.interp: %.2f snapTimeBuffered: %.2f res: %.2f\n", deltaFrameTime, deltaFrameGameMS, snapRateScale, effectiveSnapRate, delta_interpolate, snapTimeBuffered, snapCurrentResidual ); } } assert( IsValid( delta_interpolate ) ); int interpolate_interval = idMath::Ftoi( delta_interpolate ); snapCurrentTime += interpolate_interval; // advance interpolation time by the scaled interpolate_interval clientTimeResidual -= msec_interval; // advance local client residual time (fixed step) } while ( clientTimeResidual >= msec_interval ); if ( clientTimeResidual < 0 ) { clientTimeResidual = 0; } } time_gameFrame = Sys_Microseconds() - time_gameFrame; }
/* =============== idGameThread::Run Run in a background thread for performance, but can also be called directly in the foreground thread for comparison. =============== */ int idGameThread::Run() { commonLocal.frameTiming.startGameTime = Sys_Microseconds(); // debugging tool to test frame dropping behavior if ( com_sleepGame.GetInteger() ) { Sys_Sleep( com_sleepGame.GetInteger() ); } if ( numGameFrames == 0 ) { // Ensure there's no stale gameReturn data from a paused game ret = gameReturn_t(); } if ( isClient ) { // run the game logic for ( int i = 0; i < numGameFrames; i++ ) { SCOPED_PROFILE_EVENT( "Client Prediction" ); if ( userCmdMgr ) { game->ClientRunFrame( *userCmdMgr, ( i == numGameFrames - 1 ), ret ); } if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { break; } } } else { // run the game logic for ( int i = 0; i < numGameFrames; i++ ) { SCOPED_PROFILE_EVENT( "GameTic" ); if ( userCmdMgr ) { game->RunFrame( *userCmdMgr, ret ); } if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { break; } } } // we should have consumed all of our usercmds if ( userCmdMgr ) { if ( userCmdMgr->HasUserCmdForPlayer( game->GetLocalClientNum() ) && common->GetCurrentGame() == DOOM3_BFG ) { idLib::Printf( "idGameThread::Run: didn't consume all usercmds\n" ); } } commonLocal.frameTiming.finishGameTime = Sys_Microseconds(); SetThreadGameTime( ( commonLocal.frameTiming.finishGameTime - commonLocal.frameTiming.startGameTime ) / 1000 ); // build render commands and geometry { SCOPED_PROFILE_EVENT( "Draw" ); commonLocal.Draw(); } commonLocal.frameTiming.finishDrawTime = Sys_Microseconds(); SetThreadRenderTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.finishGameTime ) / 1000 ); SetThreadTotalTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.startGameTime ) / 1000 ); return 0; }
/* ======================== idParallelJobList_Threads::RunJobsInternal ======================== */ int idParallelJobList_Threads::RunJobsInternal( unsigned int threadNum, threadJobListState_t& state, bool singleJob ) { if( state.version != version.GetValue() ) { // trying to run an old version of this list that is already done return RUN_DONE; } assert( threadNum < MAX_THREADS ); if( deferredThreadStats.startTime == 0 ) { deferredThreadStats.startTime = Sys_Microseconds(); // first time any thread is running jobs from this list } int result = RUN_OK; do { // run through all signals and syncs before the last job that has been or is being executed // this loop is really an optimization to minimize the time spent in the fetchLock section below for( ; state.lastJobIndex < ( int ) currentJob.GetValue() && state.lastJobIndex < jobList.Num(); state.lastJobIndex++ ) { if( jobList[state.lastJobIndex].data == & JOB_SIGNAL ) { state.signalIndex++; assert( state.signalIndex < signalJobCount.Num() ); } else if( jobList[state.lastJobIndex].data == & JOB_SYNCHRONIZE ) { assert( state.signalIndex > 0 ); if( signalJobCount[state.signalIndex - 1].GetValue() > 0 ) { // stalled on a synchronization point return ( result | RUN_STALLED ); } } else if( jobList[state.lastJobIndex].data == & JOB_LIST_DONE ) { if( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { // stalled on a synchronization point return ( result | RUN_STALLED ); } } } // try to lock to fetch a new job if( fetchLock.Increment() == 1 ) { // grab a new job state.nextJobIndex = currentJob.Increment() - 1; // run through any remaining signals and syncs (this should rarely iterate more than once) for( ; state.lastJobIndex <= state.nextJobIndex && state.lastJobIndex < jobList.Num(); state.lastJobIndex++ ) { if( jobList[state.lastJobIndex].data == & JOB_SIGNAL ) { state.signalIndex++; assert( state.signalIndex < signalJobCount.Num() ); } else if( jobList[state.lastJobIndex].data == & JOB_SYNCHRONIZE ) { assert( state.signalIndex > 0 ); if( signalJobCount[state.signalIndex - 1].GetValue() > 0 ) { // return this job to the list currentJob.Decrement(); // release the fetch lock fetchLock.Decrement(); // stalled on a synchronization point return ( result | RUN_STALLED ); } } else if( jobList[state.lastJobIndex].data == & JOB_LIST_DONE ) { if( signalJobCount[signalJobCount.Num() - 1].GetValue() > 0 ) { // return this job to the list currentJob.Decrement(); // release the fetch lock fetchLock.Decrement(); // stalled on a synchronization point return ( result | RUN_STALLED ); } // decrement the done count doneGuards[currentDoneGuard].Decrement(); } } // release the fetch lock fetchLock.Decrement(); } else { // release the fetch lock fetchLock.Decrement(); // another thread is fetching right now so consider stalled return ( result | RUN_STALLED ); } // if at the end of the job list we're done if( state.nextJobIndex >= jobList.Num() ) { return ( result | RUN_DONE ); } // execute the next job { uint64 jobStart = Sys_Microseconds(); jobList[state.nextJobIndex].function( jobList[state.nextJobIndex].data ); jobList[state.nextJobIndex].executed = 1; uint64 jobEnd = Sys_Microseconds(); deferredThreadStats.threadExecTime[threadNum] += jobEnd - jobStart; #ifndef _DEBUG if( jobs_longJobMicroSec.GetInteger() > 0 ) { if( jobEnd - jobStart > jobs_longJobMicroSec.GetInteger() && GetId() != JOBLIST_UTILITY ) { longJobTime = ( jobEnd - jobStart ) * ( 1.0f / 1000.0f ); longJobFunc = jobList[state.nextJobIndex].function; longJobData = jobList[state.nextJobIndex].data; const char* jobName = GetJobName( jobList[state.nextJobIndex].function ); const char* jobListName = GetJobListName( GetId() ); idLib::Printf( "%1.1f milliseconds for a single '%s' job from job list %s on thread %d\n", longJobTime, jobName, jobListName, threadNum ); } } #endif } result |= RUN_PROGRESS; // decrease the job count for the current signal if( signalJobCount[state.signalIndex].Decrement() == 0 ) { // if this was the very last job of the job list if( state.signalIndex == signalJobCount.Num() - 1 ) { deferredThreadStats.endTime = Sys_Microseconds(); return ( result | RUN_DONE ); } } } while( ! singleJob ); return result; }
/* =================== R_AddModels The end result of running this is the addition of drawSurf_t to the tr.viewDef->drawSurfs[] array and light link chains, along with frameData and vertexCache allocations to support the drawSurfs. =================== */ void R_AddModels() { SCOPED_PROFILE_EVENT( "R_AddModels" ); tr.viewDef->viewEntitys = R_SortViewEntities( tr.viewDef->viewEntitys ); //------------------------------------------------- // Go through each view entity that is either visible to the view, or to // any light that intersects the view (for shadows). //------------------------------------------------- if( r_useParallelAddModels.GetBool() ) { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )R_AddSingleModel, vEntity ); } tr.frontEndJobList->Submit(); tr.frontEndJobList->Wait(); } else { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { R_AddSingleModel( vEntity ); } } //------------------------------------------------- // Kick off jobs to setup static and dynamic shadow volumes. //------------------------------------------------- if( r_useParallelAddShadows.GetInteger() == 1 ) { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( staticShadowVolumeParms_t* shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )StaticShadowVolumeJob, shadowParms ); } for( dynamicShadowVolumeParms_t* shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )DynamicShadowVolumeJob, shadowParms ); } vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; } tr.frontEndJobList->Submit(); // wait here otherwise the shadow volume index buffer may be unmapped before all shadow volumes have been constructed tr.frontEndJobList->Wait(); } else { int start = Sys_Microseconds(); for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( staticShadowVolumeParms_t* shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { StaticShadowVolumeJob( shadowParms ); } for( dynamicShadowVolumeParms_t* shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { DynamicShadowVolumeJob( shadowParms ); } vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; } int end = Sys_Microseconds(); backEnd.pc.shadowMicroSec += end - start; } //------------------------------------------------- // Move the draw surfs to the view. //------------------------------------------------- tr.viewDef->numDrawSurfs = 0; // clear the ambient surface list tr.viewDef->maxDrawSurfs = 0; // will be set to INITIAL_DRAWSURFS on R_LinkDrawSurfToView for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( drawSurf_t* ds = vEntity->drawSurfs; ds != NULL; ) { drawSurf_t* next = ds->nextOnLight; if( ds->linkChain == NULL ) { R_LinkDrawSurfToView( ds, tr.viewDef ); } else { ds->nextOnLight = *ds->linkChain; *ds->linkChain = ds; } ds = next; } vEntity->drawSurfs = NULL; } }
/* * Windows main function. Containts the * initialization code and the main loop */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; long long oldtime, newtime; /* Previous instances do not exist in Win32 */ if (hPrevInstance) { return 0; } /* Make the current instance global */ global_hInstance = hInstance; /* Setup FPU if necessary */ Sys_SetupFPU(); /* Force DPI awareness */ Sys_SetHighDPIMode(); /* Parse the command line arguments */ ParseCommandLine(lpCmdLine); /* Are we portable? */ for (int i = 0; i < argc; i++) { if (strcmp(argv[i], "-portable") == 0) { is_portable = true; } } /* Need to redirect stdout before anything happens. */ #ifndef DEDICATED_ONLY Sys_RedirectStdout(); #endif /* Seed PRNG */ randk_seed(); /* Call the initialization code */ Qcommon_Init(argc, argv); /* Save our time */ oldtime = Sys_Microseconds(); /* The legendary main loop */ while (1) { /* If at a full screen console, don't update unless needed */ if (Minimized || (dedicated && dedicated->value)) { Sleep(1); } while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (!GetMessage(&msg, NULL, 0, 0)) { Com_Quit(); } sys_msg_time = msg.time; TranslateMessage(&msg); DispatchMessage(&msg); } // Throttle the game a little bit Sys_Nanosleep(5000); newtime = Sys_Microseconds(); Qcommon_Frame(newtime - oldtime); oldtime = newtime; } /* never gets here */ return TRUE; }
/* * Sys_Milliseconds */ unsigned int Sys_Milliseconds( void ) { return Sys_Microseconds() / 1000; }
bool RB_RenderShadowMaps( viewLight_t* vLight ) { const idMaterial* lightShader = vLight->lightShader; if (lightShader->IsFogLight() || lightShader->IsBlendLight()) { return true; } if (!vLight->localInteractions && !vLight->globalInteractions && !vLight->translucentInteractions) { return true; } if (!vLight->lightShader->LightCastsShadows()) { return true; } int lod = r_smForceLod.GetInteger(); if(lod < 0) { lod = vLight->shadowMapLod; } lod = Max( 0, Min( lod, 2 ) ); const uint64 startTime = Sys_Microseconds(); const float polygonOffsetBias = vLight->lightDef->ShadowPolygonOffsetBias(); const float polygonOffsetFactor = vLight->lightDef->ShadowPolygonOffsetFactor(); glEnable( GL_POLYGON_OFFSET_FILL ); glPolygonOffset( polygonOffsetFactor, polygonOffsetBias ); switch (r_smFaceCullMode.GetInteger()) { case 0: glEnable( GL_CULL_FACE ); glFrontFace( GL_CCW ); break; case 1: glEnable( GL_CULL_FACE ); glFrontFace( GL_CW ); break; case 2: default: glDisable( GL_CULL_FACE ); break; } ShadowRenderList renderlist; int numShadowMaps = 0; if (vLight->lightDef->parms.parallel) { assert( vLight->lightDef->numShadowMapFrustums == 1 ); shadowMapFrustum_t& frustum = vLight->lightDef->shadowMapFrustums[0]; if (!shadowMapAllocator.Allocate( 0, 6, vLight->shadowCoords )) { return false; } const float cascadeDistances[6] = { r_smCascadeDistance0.GetFloat(), r_smCascadeDistance1.GetFloat(), r_smCascadeDistance2.GetFloat(), r_smCascadeDistance3.GetFloat(), r_smCascadeDistance4.GetFloat(), 100000 }; lod = 0; float nextNearDistance = 1; for (int c = 0; c < 6; ++c) { idFrustum viewFrustum = backEnd.viewDef->viewFrustum; viewFrustum.MoveFarDistance( cascadeDistances[c] ); //move far before near, so far is always greater than near viewFrustum.MoveNearDistance( nextNearDistance ); nextNearDistance = cascadeDistances[c]; idVec3 viewCorners[8]; viewFrustum.ToPoints( viewCorners ); for (int i = 0; i < 8; ++i) { viewCorners[i] = frustum.viewMatrix * viewCorners[i]; } idVec2 viewMinimum = viewCorners[0].ToVec2(); idVec2 viewMaximum = viewCorners[0].ToVec2(); for (int i = 1; i < 8; ++i) { const float x = viewCorners[i].x; const float y = viewCorners[i].y; viewMinimum.x = Min( viewMinimum.x, x ); viewMinimum.y = Min( viewMinimum.y, y ); viewMaximum.x = Max( viewMaximum.x, x ); viewMaximum.y = Max( viewMaximum.y, y ); } idVec2 minimum, maximum; if (c < r_smViewDependendCascades.GetInteger()) { minimum.x = Max( frustum.viewSpaceBounds[0].x, viewMinimum.x ); minimum.y = Max( frustum.viewSpaceBounds[0].y, viewMinimum.y ); maximum.x = Min( frustum.viewSpaceBounds[1].x, viewMaximum.x ); maximum.y = Min( frustum.viewSpaceBounds[1].y, viewMaximum.y ); } else { minimum = frustum.viewSpaceBounds[0].ToVec2(); maximum = frustum.viewSpaceBounds[1].ToVec2(); } float r = idMath::Abs( maximum.x - minimum.x ) * 0.5f; float l = -r; float t = idMath::Abs( maximum.y - minimum.y ) * 0.5f; float b = -t; vLight->viewMatrices[c] = frustum.viewMatrix; vLight->viewMatrices[c][12] = -(maximum.x + minimum.x) * 0.5f; vLight->viewMatrices[c][13] = -(maximum.y + minimum.y) * 0.5f; vLight->viewMatrices[c][14] = -(frustum.viewSpaceBounds[1].z + 1); const float f = fabs( frustum.viewSpaceBounds[1].z - frustum.viewSpaceBounds[0].z ); const float n = 1; vLight->projectionMatrices[c] = fhRenderMatrix::identity; vLight->projectionMatrices[c][0] = 2.0f / (r - l); vLight->projectionMatrices[c][5] = 2.0f / (b - t); vLight->projectionMatrices[c][10] = -2.0f / (f - n); vLight->projectionMatrices[c][12] = -((r + l) / (r - l)); vLight->projectionMatrices[c][13] = -((t + b) / (t - b)); vLight->projectionMatrices[c][14] = -((f + n) / (f - n)); vLight->projectionMatrices[c][15] = 1.0f; vLight->viewProjectionMatrices[c] = vLight->projectionMatrices[c] * vLight->viewMatrices[c]; vLight->width[c] = fabs( r * 2 ); vLight->height[c] = fabs( t * 2 ); vLight->culled[c] = false; } renderlist.AddInteractions( vLight, nullptr, 0 ); numShadowMaps = 6; } else if (vLight->lightDef->parms.pointLight) { assert( vLight->lightDef->numShadowMapFrustums == 6 ); idVec3 viewCorners[8]; backEnd.viewDef->viewFrustum.ToPoints( viewCorners ); for (int i = 0; i < 6; ++i) { if (r_smLightSideCulling.GetBool()) { vLight->culled[i] = vLight->lightDef->shadowMapFrustums[i].Cull(viewCorners); } else { vLight->culled[i] = false; } if (vLight->culled[i]) { continue; } if (!shadowMapAllocator.Allocate(lod, 1, &vLight->shadowCoords[i])) { return false; } vLight->viewMatrices[i] = vLight->lightDef->shadowMapFrustums[i].viewMatrix; vLight->projectionMatrices[i] = vLight->lightDef->shadowMapFrustums[i].projectionMatrix; vLight->viewProjectionMatrices[i] = vLight->lightDef->shadowMapFrustums[i].viewProjectionMatrix; } renderlist.AddInteractions( vLight, vLight->lightDef->shadowMapFrustums, vLight->lightDef->numShadowMapFrustums ); numShadowMaps = 6; } else { //projected light if (!shadowMapAllocator.Allocate( lod, 1, vLight->shadowCoords )) { return false; } assert( vLight->lightDef->numShadowMapFrustums == 1 ); vLight->viewMatrices[0] = vLight->lightDef->shadowMapFrustums[0].viewMatrix; vLight->projectionMatrices[0] = vLight->lightDef->shadowMapFrustums[0].projectionMatrix; vLight->viewProjectionMatrices[0] = vLight->lightDef->shadowMapFrustums[0].viewProjectionMatrix; vLight->culled[0] = false; renderlist.AddInteractions( vLight, &vLight->lightDef->shadowMapFrustums[0], 1 ); numShadowMaps = 1; } for (int side = 0; side < numShadowMaps; side++) { if(vLight->culled[side]) { continue; } const fhFramebuffer* framebuffer = fhFramebuffer::shadowmapFramebuffer; const int width = framebuffer->GetWidth() * vLight->shadowCoords[side].scale.x; const int height = framebuffer->GetHeight() * vLight->shadowCoords[side].scale.y; const int offsetX = framebuffer->GetWidth() * vLight->shadowCoords[side].offset.x; const int offsetY = framebuffer->GetHeight() * vLight->shadowCoords[side].offset.y; glViewport( offsetX, offsetY, width, height ); glScissor( offsetX, offsetY, width, height ); renderlist.Submit( vLight->viewMatrices[side].ToFloatPtr(), vLight->projectionMatrices[side].ToFloatPtr(), side, lod ); backEnd.stats.groups[backEndGroup::ShadowMap0 + lod].passes += 1; } const uint64 endTime = Sys_Microseconds(); backEnd.stats.groups[backEndGroup::ShadowMap0 + lod].time += static_cast<uint32>(endTime - startTime); return true; }
void JoystickSamplingThread( void* data ) { static int prevTime = 0; static uint64 nextCheck[MAX_JOYSTICKS] = { 0 }; const uint64 waitTime = 5000000; // poll every 5 seconds to see if a controller was connected while( 1 ) { // hopefully we see close to 4000 usec each loop int now = Sys_Microseconds(); int delta; if( prevTime == 0 ) { delta = 4000; } else { delta = now - prevTime; } prevTime = now; threadTimeDeltas[threadCount & 255] = delta; threadCount++; { XINPUT_STATE joyData[MAX_JOYSTICKS]; bool validData[MAX_JOYSTICKS]; for( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) { if( now >= nextCheck[i] ) { // XInputGetState might block... for a _really_ long time.. validData[i] = XInputGetState( i, &joyData[i] ) == ERROR_SUCCESS; // allow an immediate data poll if the input device is connected else // wait for some time to see if another device was reconnected. // Checking input state infrequently for newly connected devices prevents // severe slowdowns on PC, especially on WinXP64. if( validData[i] ) { nextCheck[i] = 0; } else { nextCheck[i] = now + waitTime; } } } // do this short amount of processing inside a critical section idScopedCriticalSection cs( win32.g_Joystick.mutexXis ); for( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) { controllerState_t* cs = &win32.g_Joystick.controllers[i]; if( !validData[i] ) { cs->valid = false; continue; } cs->valid = true; XINPUT_STATE& current = joyData[i]; cs->current = current; // Switch from using cs->current to current to reduce chance of Load-Hit-Store on consoles threadPacket[threadCount & 255] = current.dwPacketNumber; #if 0 if( xis.dwPacketNumber == oldXis[ inputDeviceNum ].dwPacketNumber ) { return numEvents; } #endif cs->buttonBits |= current.Gamepad.wButtons; } } // we want this to be processed at least 250 times a second WaitForSingleObject( win32.g_Joystick.timer, INFINITE ); } }
/* ================= idCommonLocal::Frame ================= */ void idCommonLocal::Frame() { try { SCOPED_PROFILE_EVENT( "Common::Frame" ); // This is the only place this is incremented idLib::frameNumber++; // allow changing SIMD usage on the fly if ( com_forceGenericSIMD.IsModified() ) { idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() ); com_forceGenericSIMD.ClearModified(); } // Do the actual switch between Doom 3 and the classics here so // that things don't get confused in the middle of the frame. PerformGameSwitch(); // pump all the events Sys_GenerateEvents(); // write config file if anything changed WriteConfiguration(); eventLoop->RunEventLoop(); // Activate the shell if it's been requested if ( showShellRequested && game ) { game->Shell_Show( true ); showShellRequested = false; } // if the console or another gui is down, we don't need to hold the mouse cursor bool chatting = false; if ( console->Active() || Dialog().IsDialogActive() || session->IsSystemUIShowing() || ( game && game->InhibitControls() && !IsPlayingDoomClassic() ) ) { Sys_GrabMouseCursor( false ); usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true ); chatting = true; } else { Sys_GrabMouseCursor( true ); usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false ); } const bool pauseGame = ( !mapSpawned || ( !IsMultiplayer() && ( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || ( game && game->Shell_IsActive() ) ) ) ) && !IsPlayingDoomClassic(); // save the screenshot and audio from the last draw if needed if ( aviCaptureMode ) { idStr name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviDemoFrameCount++ ); renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); // remove any printed lines at the top before taking the screenshot console->ClearNotifyLines(); // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1 renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); } //-------------------------------------------- // wait for the GPU to finish drawing // // It is imporant to minimize the time spent between this // section and the call to renderSystem->RenderCommandBuffers(), // because the GPU is completely idle. //-------------------------------------------- // this should exit right after vsync, with the GPU idle and ready to draw // This may block if the GPU isn't finished renderng the previous frame. frameTiming.startSyncTime = Sys_Microseconds(); const emptyCommand_t * renderCommands = NULL; if ( com_smp.GetBool() ) { renderCommands = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); } else { // the GPU will stay idle through command generation for minimal // input latency renderSystem->SwapCommandBuffers_FinishRendering( &time_frontend, &time_backend, &time_shadows, &time_gpu ); } frameTiming.finishSyncTime = Sys_Microseconds(); //-------------------------------------------- // Determine how many game tics we are going to run, // now that the previous frame is completely finished. // // It is important that any waiting on the GPU be done // before this, or there will be a bad stuttering when // dropping frames for performance management. //-------------------------------------------- // input: // thisFrameTime // com_noSleep // com_engineHz // com_fixedTic // com_deltaTimeClamp // IsMultiplayer // // in/out state: // gameFrame // gameTimeResidual // lastFrameTime // syncNextFrame // // Output: // numGameFrames // How many game frames to run int numGameFrames = 0; for(;;) { const int thisFrameTime = Sys_Milliseconds(); static int lastFrameTime = thisFrameTime; // initialized only the first time const int deltaMilliseconds = thisFrameTime - lastFrameTime; lastFrameTime = thisFrameTime; // if there was a large gap in time since the last frame, or the frame // rate is very very low, limit the number of frames we will run const int clampedDeltaMilliseconds = Min( deltaMilliseconds, com_deltaTimeClamp.GetInteger() ); gameTimeResidual += clampedDeltaMilliseconds * timescale.GetFloat(); // don't run any frames when paused if ( pauseGame ) { gameFrame++; gameTimeResidual = 0; break; } // debug cvar to force multiple game tics if ( com_fixedTic.GetInteger() > 0 ) { numGameFrames = com_fixedTic.GetInteger(); gameFrame += numGameFrames; gameTimeResidual = 0; break; } if ( syncNextGameFrame ) { // don't sleep at all syncNextGameFrame = false; gameFrame++; numGameFrames++; gameTimeResidual = 0; break; } for ( ;; ) { // How much time to wait before running the next frame, // based on com_engineHz const int frameDelay = FRAME_TO_MSEC( gameFrame + 1 ) - FRAME_TO_MSEC( gameFrame ); if ( gameTimeResidual < frameDelay ) { break; } gameTimeResidual -= frameDelay; gameFrame++; numGameFrames++; // if there is enough residual left, we may run additional frames } if ( numGameFrames > 0 ) { // ready to actually run them break; } // if we are vsyncing, we always want to run at least one game // frame and never sleep, which might happen due to scheduling issues // if we were just looking at real time. if ( com_noSleep.GetBool() ) { numGameFrames = 1; gameFrame += numGameFrames; gameTimeResidual = 0; break; } // not enough time has passed to run a frame, as might happen if // we don't have vsync on, or the monitor is running at 120hz while // com_engineHz is 60, so sleep a bit and check again Sys_Sleep( 0 ); } //-------------------------------------------- // It would be better to push as much of this as possible // either before or after the renderSystem->SwapCommandBuffers(), // because the GPU is completely idle. //-------------------------------------------- // Update session and syncronize to the new session state after sleeping session->UpdateSignInManager(); session->Pump(); session->ProcessSnapAckQueue(); if ( session->GetState() == idSession::LOADING ) { // If the session reports we should be loading a map, load it! ExecuteMapChange(); mapSpawnData.savegameFile = NULL; mapSpawnData.persistentPlayerInfo.Clear(); return; } else if ( session->GetState() != idSession::INGAME && mapSpawned ) { // If the game is running, but the session reports we are not in a game, disconnect // This happens when a server disconnects us or we sign out LeaveGame(); return; } if ( mapSpawned && !pauseGame ) { if ( IsClient() ) { RunNetworkSnapshotFrame(); } } ExecuteReliableMessages(); // send frame and mouse events to active guis GuiFrameEvents(); //-------------------------------------------- // Prepare usercmds and kick off the game processing // in a background thread //-------------------------------------------- // get the previous usercmd for bypassed head tracking transform const usercmd_t previousCmd = usercmdGen->GetCurrentUsercmd(); // build a new usercmd int deviceNum = session->GetSignInManager().GetMasterInputDevice(); usercmdGen->BuildCurrentUsercmd( deviceNum ); if ( deviceNum == -1 ) { for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { Sys_PollJoystickInputEvents( i ); Sys_EndJoystickInputEvents(); } } if ( pauseGame ) { usercmdGen->Clear(); } usercmd_t newCmd = usercmdGen->GetCurrentUsercmd(); // Store server game time - don't let time go past last SS time in case we are extrapolating if ( IsClient() ) { newCmd.serverGameMilliseconds = std::min( Game()->GetServerGameTimeMs(), Game()->GetSSEndTime() ); } else { newCmd.serverGameMilliseconds = Game()->GetServerGameTimeMs(); } userCmdMgr.MakeReadPtrCurrentForPlayer( Game()->GetLocalClientNum() ); // Stuff a copy of this userCmd for each game frame we are going to run. // Ideally, the usercmds would be built in another thread so you could // still get 60hz control accuracy when the game is running slower. for ( int i = 0 ; i < numGameFrames ; i++ ) { newCmd.clientGameMilliseconds = FRAME_TO_MSEC( gameFrame-numGameFrames+i+1 ); userCmdMgr.PutUserCmdForPlayer( game->GetLocalClientNum(), newCmd ); } // If we're in Doom or Doom 2, run tics and upload the new texture. if ( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() ) ) { RunDoomClassicFrame(); } // start the game / draw command generation thread going in the background gameReturn_t ret = gameThread.RunGameAndDraw( numGameFrames, userCmdMgr, IsClient(), gameFrame - numGameFrames ); if ( !com_smp.GetBool() ) { // in non-smp mode, run the commands we just generated, instead of // frame-delayed ones from a background thread renderCommands = renderSystem->SwapCommandBuffers_FinishCommandBuffers(); } //---------------------------------------- // Run the render back end, getting the GPU busy with new commands // ASAP to minimize the pipeline bubble. //---------------------------------------- frameTiming.startRenderTime = Sys_Microseconds(); renderSystem->RenderCommandBuffers( renderCommands ); if ( com_sleepRender.GetInteger() > 0 ) { // debug tool to test frame adaption Sys_Sleep( com_sleepRender.GetInteger() ); } frameTiming.finishRenderTime = Sys_Microseconds(); // make sure the game / draw thread has completed // This may block if the game is taking longer than the render back end gameThread.WaitForThread(); // Send local usermds to the server. // This happens after the game frame has run so that prediction data is up to date. SendUsercmds( Game()->GetLocalClientNum() ); // Now that we have an updated game frame, we can send out new snapshots to our clients session->Pump(); // Pump to get updated usercmds to relay SendSnapshots(); // Render the sound system using the latest commands from the game thread if ( pauseGame ) { soundWorld->Pause(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); } else { soundWorld->UnPause(); soundSystem->SetPlayingSoundWorld( soundWorld ); } soundSystem->Render(); // process the game return for map changes, etc ProcessGameReturn( ret ); idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); if ( lobby.HasActivePeers() ) { if ( net_drawDebugHud.GetInteger() == 1 ) { lobby.DrawDebugNetworkHUD(); } if ( net_drawDebugHud.GetInteger() == 2 ) { lobby.DrawDebugNetworkHUD2(); } lobby.DrawDebugNetworkHUD_ServerSnapshotMetrics( net_drawDebugHud.GetInteger() == 3 ); } // report timing information if ( com_speeds.GetBool() ) { static int lastTime = Sys_Milliseconds(); int nowTime = Sys_Milliseconds(); int com_frameMsec = nowTime - lastTime; lastTime = nowTime; Printf( "frame:%d all:%3d gfr:%3d rf:%3lld bk:%3lld\n", idLib::frameNumber, com_frameMsec, time_gameFrame, time_frontend / 1000, time_backend / 1000 ); time_gameFrame = 0; time_gameDraw = 0; } // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack if ( !Sys_FPU_StackIsEmpty() ) { Printf( Sys_FPU_GetState() ); FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" ); } mainFrameTiming = frameTiming; session->GetSaveGameManager().Pump(); } catch( idException & ) { return; // an ERP_DROP was thrown } }