@@ -750,20 +750,128 @@ CLIENT SIDE DEMO PLAYBACK
750750=======================================================================
751751*/
752752
753+ /*
754+ ====================
755+ CL_StartBenchmark_f
756+ ====================
757+ */
758+ static void CL_StartBenchmark_f (void ) {
759+ if (Cmd_Argc () < 2 ) {
760+ Com_Printf ("benchmark <demoname>\n" );
761+ return ;
762+ }
763+
764+ // enable timedemo if it isn't set already (and keep track if we changed the value, so we can restore it)
765+ if (!Cvar_VariableIntegerValue ("timedemo" )) {
766+ Cvar_Set ("timedemo" , "1" );
767+ cls .resetTimedemoCvar = qtrue ;
768+ }
769+
770+ cls .benchmarking = qtrue ;
771+ Cbuf_ExecuteText (EXEC_APPEND , va ("demo %s" , Cmd_Argv (1 )));
772+ }
773+
774+ /*
775+ =================
776+ CL_CompareFrametimes
777+ =================
778+ */
779+ static int CL_CompareFrametimes (const void * a , const void * b ) {
780+ const uint32_t arg1 = * (uint32_t * )a ;
781+ const uint32_t arg2 = * (uint32_t * )b ;
782+
783+ if (arg1 > arg2 ) {
784+ return 1 ;
785+ } else if (arg2 > arg1 ) {
786+ return -1 ;
787+ } else {
788+ return 0 ;
789+ }
790+ }
791+
792+ /*
793+ =================
794+ CL_TimedemoResults
795+ =================
796+ */
797+ static void CL_TimedemoResults (void ) {
798+ int64_t time ;
799+ int i , numFrames ;
800+ uint32_t sortedFrametimes [MAX_TIMEDEMO_FRAMES ];
801+ int onePercentIdx , pointOnePercentIdx ;
802+ float fps , minFps , maxFps ;
803+ char onePercent [8 ], pointOnePercent [8 ];
804+
805+ time = Sys_Microseconds () - clc .timedemo .timeDemoStart ;
806+
807+ if (time <= 0 ) {
808+ return ;
809+ }
810+
811+ // timeFrames gets incremented before we get here, but we never have a chance to measure the frametime
812+ // since the playback ends, therefore scrap the last frame entirely as it never gets stored
813+ numFrames = clc .timedemo .timeDemoFrames - 1 ;
814+
815+ fps = numFrames * 1000000.0f / time ;
816+
817+ Com_Memcpy (sortedFrametimes , clc .timedemo .frametime , numFrames * sizeof (uint32_t ));
818+ qsort (sortedFrametimes , numFrames , sizeof (uint32_t ), CL_CompareFrametimes );
819+
820+ minFps = 1000000.0f / sortedFrametimes [numFrames - 1 ];
821+ maxFps = 1000000.0f / sortedFrametimes [0 ];
822+
823+ // filter out potential 0ms anomalies for maxfps
824+ if (sortedFrametimes [0 ] == 0 ) {
825+ for (i = 0 ; i < numFrames ; i ++ ) {
826+ if (sortedFrametimes [i ] != 0 ) {
827+ maxFps = 1000000.0f / sortedFrametimes [i ];
828+ break ;
829+ }
830+ }
831+ }
832+
833+ onePercentIdx = (int )(0.01f * numFrames );
834+
835+ // make sure we have enough total frames to display 1% lows
836+ if (onePercentIdx ) {
837+ Com_sprintf (onePercent , sizeof (onePercent ), "%3.2f" , 1000000.0f / sortedFrametimes [numFrames - 1 - onePercentIdx ]);
838+ }
839+
840+ pointOnePercentIdx = (int )(0.001f * numFrames );
841+
842+ // make sure we have enough total frames to display 0.1% lows
843+ if (pointOnePercentIdx ) {
844+ Com_sprintf (pointOnePercent , sizeof (pointOnePercent ), "%3.2f" , 1000000.0f / sortedFrametimes [numFrames - 1 - pointOnePercentIdx ]);
845+ }
846+
847+ Com_Printf ("\n----- Benchmark results -----\n" );
848+ Com_Printf ("\n%-18s %3.2f sec\n%-18s %i\n%-18s %3.2f\n%-18s %3.2f\n%-18s %3.2f\n%-18s %s\n%-18s %s\n" ,
849+ "Time elapsed:" , time / 1000000.0f ,
850+ "Total frames:" , numFrames ,
851+ "Minimum fps:" , minFps ,
852+ "Maximum fps:" , maxFps ,
853+ "Average fps:" , fps ,
854+ "99th pct. min:" , onePercentIdx ? onePercent : "--" ,
855+ "99.9th pct. min:" , pointOnePercentIdx ? pointOnePercent : "--" );
856+ Com_Printf ("\n-----------------------------\n\n" );
857+ }
858+
753859/*
754860=================
755861CL_DemoCompleted
756862=================
757863*/
758864static void CL_DemoCompleted ( void ) {
759865 if ( com_timedemo -> integer ) {
760- int time ;
866+ CL_TimedemoResults () ;
761867
762- time = Sys_Milliseconds () - clc .timeDemoStart ;
763- if ( time > 0 ) {
764- Com_Printf ( "%i frames, %3.*f seconds: %3.1f fps\n" , clc .timeDemoFrames ,
765- time > 10000 ? 1 : 2 , time /1000.0 , clc .timeDemoFrames * 1000.0 / time );
868+ // reset timedemo cvar if 'benchmark' command forcibly enabled it
869+ if (cls .benchmarking && cls .resetTimedemoCvar ) {
870+ Cvar_Set ("timedemo" , "0" );
766871 }
872+
873+ cls .benchmarking = qfalse ;
874+ cls .resetTimedemoCvar = qfalse ;
767875 }
768876
769877 // fretn
@@ -3316,6 +3424,11 @@ void CL_Frame( int msec, int realMsec ) {
33163424 float fps ;
33173425 float frameDuration ;
33183426 aviRecordingState_t aviRecord = AVIDEMO_NONE ;
3427+ int64_t frameStart = 0 ;
3428+
3429+ if (clc .demoplaying && com_timedemo && com_timedemo -> integer ) {
3430+ frameStart = Sys_Microseconds ();
3431+ }
33193432
33203433 CL_TrackCvarChanges ( qfalse );
33213434
@@ -3468,6 +3581,12 @@ void CL_Frame( int msec, int realMsec ) {
34683581 cls .discordInit = qfalse ;
34693582 }
34703583#endif
3584+
3585+ if (clc .demoplaying && clc .timedemo .timeDemoFrames && com_timedemo && com_timedemo -> integer ) {
3586+ // use circular buffer to store frametimes, the array should hold enough frames for ~3h of demo playback,
3587+ // but in case of extremely long demos, just start dropping frames from the beginning
3588+ clc .timedemo .frametime [(clc .timedemo .timeDemoFrames - 1 ) % MAX_TIMEDEMO_FRAMES ] = Sys_Microseconds () - frameStart ;
3589+ }
34713590}
34723591
34733592
@@ -4392,6 +4511,7 @@ static void CL_InitGLimp_Cvars( void )
43924511
43934512static const cmdListItem_t cl_cmds [] = {
43944513 { "addFavorite" , CL_AddFavorite_f , NULL },
4514+ { "benchmark" , CL_StartBenchmark_f , CL_CompleteDemoName },
43954515 { "cache_endgather" , CL_Cache_EndGather_f , NULL },
43964516 { "cache_mapchange" , CL_Cache_MapChange_f , NULL },
43974517 { "cache_setindex" , CL_Cache_SetIndex_f , NULL },
0 commit comments