From c186d6402b77a52a7839ed69790d964be6af639e Mon Sep 17 00:00:00 2001 From: Sally Coolatta Date: Sat, 26 Mar 2022 23:48:08 -0400 Subject: [PATCH] Lots of FPS stuff - Disabled VSync, due to the numerous problems it has. - Instead, added an FPS cap. - Frame interpolation is now tied to fpscap != 35. - By default, the FPS cap is set to the monitor's refresh rate. - Rewrote the FPS counter. (This also consolidates several more commits ahead of this fixing various issues. -eid) --- src/d_clisrv.c | 2 +- src/d_main.c | 195 ++++++++++++++++++++++++++++------------- src/d_netcmd.c | 2 +- src/hardware/hw_main.c | 6 +- src/hardware/hw_md2.c | 2 +- src/i_system.h | 4 +- src/i_video.h | 2 + src/m_menu.c | 13 ++- src/r_fps.c | 46 +++++++++- src/r_fps.h | 5 ++ src/r_main.c | 9 +- src/r_main.h | 3 - src/r_things.c | 12 +-- src/screen.c | 102 ++++++++++----------- src/screen.h | 5 +- src/sdl/i_system.c | 52 +++++++++++ src/sdl/i_video.c | 29 +++++- 17 files changed, 342 insertions(+), 147 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 2972502a7..74f3dd959 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5233,7 +5233,7 @@ boolean TryRunTics(tic_t realtics) if (demoplayback) { - neededtic = gametic + (realtics * cv_playbackspeed.value); + neededtic = gametic + realtics; // start a game after a demo maketic += realtics; firstticstosend = maketic; diff --git a/src/d_main.c b/src/d_main.c index 155913320..bb01976cd 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -477,7 +477,7 @@ static void D_Display(void) if (!automapactive && !dedicated && cv_renderview.value) { - R_ApplyLevelInterpolators(cv_frameinterpolation.value == 1 ? rendertimefrac : FRACUNIT); + R_ApplyLevelInterpolators(R_UsingFrameInterpolation() ? rendertimefrac : FRACUNIT); PS_START_TIMING(ps_rendercalltime); if (players[displayplayer].mo || players[displayplayer].playerstate == PST_DEAD) { @@ -687,6 +687,29 @@ static void D_Display(void) } } +static boolean D_CheckFrameCap(void) +{ + static boolean init = false; + static precise_t startCap = 0; + precise_t endCap = 0; + + endCap = I_GetPreciseTime(); + + if (init == false) + { + startCap = endCap; + init = true; + } + else if (I_CheckFrameCap(startCap, endCap)) + { + // Framerate should be capped. + return true; + } + + startCap = endCap; + return false; +} + // ========================================================================= // D_SRB2Loop // ========================================================================= @@ -698,6 +721,8 @@ void D_SRB2Loop(void) tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS; static lumpnum_t gstartuplumpnum; boolean ticked; + boolean interp; + boolean doDisplay = false; if (dedicated) server = true; @@ -762,14 +787,25 @@ void D_SRB2Loop(void) refreshdirmenu = 0; // not sure where to put this, here as good as any? + if (demoplayback && gamestate == GS_LEVEL) + { + // Nicer place to put this. + realtics = realtics * cv_playbackspeed.value; + } + #ifdef DEBUGFILE if (!realtics) if (debugload) debugload--; #endif - if (!realtics && !singletics && cv_frameinterpolation.value != 1) + interp = R_UsingFrameInterpolation(); + doDisplay = false; + ticked = false; + + if (!realtics && !singletics && !interp) { + // Non-interp sleep I_Sleep(); continue; } @@ -778,85 +814,115 @@ void D_SRB2Loop(void) HW3S_BeginFrameUpdate(); #endif - // don't skip more than 10 frames at a time - // (fadein / fadeout cause massive frame skip!) - if (realtics > 8) - realtics = 1; - - // process tics (but maybe not if realtic == 0) - ticked = TryRunTics(realtics); - - if (cv_frameinterpolation.value == 1 && !(paused || P_AutoPause())) + if (realtics > 0 || singletics) { - static float tictime; + // don't skip more than 10 frames at a time + // (fadein / fadeout cause massive frame skip!) + if (realtics > 8) + realtics = 1; + + // process tics (but maybe not if realtic == 0) + ticked = TryRunTics(realtics); + + if (lastdraw || singletics || gametic > rendergametic) + { + rendergametic = gametic; + rendertimeout = entertic+TICRATE/17; + + doDisplay = true; + } + else if (rendertimeout < entertic) // in case the server hang or netsplit + { + // Lagless camera! Yay! + if (gamestate == GS_LEVEL && netgame) + { + // Evaluate the chase cam once for every local realtic + // This might actually be better suited inside G_Ticker or TryRunTics + for (tic_t chasecamtics = 0; chasecamtics < realtics; chasecamtics++) + { + if (splitscreen && camera2.chase) + P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false); + if (camera.chase) + P_MoveChaseCamera(&players[displayplayer], &camera, false); + } + R_UpdateViewInterpolation(); + } + + doDisplay = true; + } + } + + if (interp) + { + static float tictime = 0.0f; + static float prevtime = 0.0f; float entertime = I_GetTimeFrac(); - fixed_t entertimefrac; + fixed_t entertimefrac = FRACUNIT; if (ticked) + { tictime = entertime; + } - entertimefrac = FLOAT_TO_FIXED(entertime - tictime); + // Handle interp sleep / framerate cap here. + // TryRunTics needs ran if possible to prevent lagged map changes, + // (and if that runs, the code above needs to also run) + // so this is done here after TryRunTics. + if (D_CheckFrameCap()) + { + continue; + } - // renderdeltatics is a bit awkard to evaluate, since the system time interface is whole tic-based - renderdeltatics = realtics * FRACUNIT; - if (entertimefrac > rendertimefrac) - renderdeltatics += entertimefrac - rendertimefrac; - else - renderdeltatics -= rendertimefrac - entertimefrac; + if (!(paused || P_AutoPause())) + { +#if 0 + CONS_Printf("prevtime = %f\n", prevtime); + CONS_Printf("entertime = %f\n", entertime); + CONS_Printf("tictime = %f\n", tictime); + CONS_Printf("entertime - prevtime = %f\n", entertime - prevtime); + CONS_Printf("entertime - tictime = %f\n", entertime - tictime); + CONS_Printf("========\n"); +#endif - rendertimefrac = entertimefrac; + if (entertime - prevtime >= 1.0f) + { + // Lagged for more frames than a gametic... + // No need for interpolation. + entertimefrac = FRACUNIT; + } + else + { + entertimefrac = min(FRACUNIT, FLOAT_TO_FIXED(entertime - tictime)); + } + + // renderdeltatics is a bit awkard to evaluate, since the system time interface is whole tic-based + renderdeltatics = realtics * FRACUNIT; + if (entertimefrac > rendertimefrac) + renderdeltatics += entertimefrac - rendertimefrac; + else + renderdeltatics -= rendertimefrac - entertimefrac; + + rendertimefrac = entertimefrac; + } + + prevtime = entertime; } else { - rendertimefrac = FRACUNIT; renderdeltatics = realtics * FRACUNIT; + rendertimefrac = FRACUNIT; } - if (cv_frameinterpolation.value == 1) + if (interp || doDisplay) { D_Display(); } - if (lastdraw || singletics || gametic > rendergametic) - { - rendergametic = gametic; - rendertimeout = entertic+TICRATE/17; - - // Update display, next frame, with current state. - // (Only display if not already done for frame interp) - cv_frameinterpolation.value == 0 ? D_Display() : 0; - - if (moviemode) - M_SaveFrame(); - if (takescreenshot) // Only take screenshots after drawing. - M_DoScreenShot(); - } - else if (rendertimeout < entertic) // in case the server hang or netsplit - { - // Lagless camera! Yay! - if (gamestate == GS_LEVEL && netgame) - { - // Evaluate the chase cam once for every local realtic - // This might actually be better suited inside G_Ticker or TryRunTics - for (tic_t chasecamtics = 0; chasecamtics < realtics; chasecamtics++) - { - if (splitscreen && camera2.chase) - P_MoveChaseCamera(&players[secondarydisplayplayer], &camera2, false); - if (camera.chase) - P_MoveChaseCamera(&players[displayplayer], &camera, false); - } - R_UpdateViewInterpolation(); - - } - // (Only display if not already done for frame interp) - cv_frameinterpolation.value == 0 ? D_Display() : 0; - - if (moviemode) - M_SaveFrame(); - if (takescreenshot) // Only take screenshots after drawing. - M_DoScreenShot(); - } + if (moviemode) + M_SaveFrame(); + if (takescreenshot) // Only take screenshots after drawing. + M_DoScreenShot(); // consoleplayer -> displayplayer (hear sounds from viewpoint) S_UpdateSounds(); // move positional sounds @@ -867,6 +933,11 @@ void D_SRB2Loop(void) #endif LUA_Step(); + + // Moved to here from I_FinishUpdate. + // It doesn't track fades properly anymore by being here (might be easy fix), + // but it's a little more accurate for actual game logic when its here. + SCR_CalculateFPS(); } } diff --git a/src/d_netcmd.c b/src/d_netcmd.c index d9080d342..9b14e0913 100644 --- a/src/d_netcmd.c +++ b/src/d_netcmd.c @@ -191,7 +191,7 @@ static CV_PossibleValue_t joyport_cons_t[] = {{1, "/dev/js0"}, {2, "/dev/js1"}, static CV_PossibleValue_t teamscramble_cons_t[] = {{0, "Off"}, {1, "Random"}, {2, "Points"}, {0, NULL}}; static CV_PossibleValue_t startingliveslimit_cons_t[] = {{1, "MIN"}, {99, "MAX"}, {0, NULL}}; -static CV_PossibleValue_t sleeping_cons_t[] = {{-1, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}}; +static CV_PossibleValue_t sleeping_cons_t[] = {{0, "MIN"}, {1000/TICRATE, "MAX"}, {0, NULL}}; static CV_PossibleValue_t competitionboxes_cons_t[] = {{0, "Normal"}, {1, "Mystery"}, //{2, "Teleport"}, {3, "None"}, {0, NULL}}; diff --git a/src/hardware/hw_main.c b/src/hardware/hw_main.c index 829d9b04b..f56856bb4 100644 --- a/src/hardware/hw_main.c +++ b/src/hardware/hw_main.c @@ -3648,7 +3648,7 @@ static void HWR_DrawDropShadow(mobj_t *thing, fixed_t scale) // uncapped/interpolation interpmobjstate_t interp = {0}; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -5089,7 +5089,7 @@ static void HWR_ProjectSprite(mobj_t *thing) dispoffset = thing->info->dispoffset; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -5502,7 +5502,7 @@ static void HWR_ProjectPrecipitationSprite(precipmobj_t *thing) interpmobjstate_t interp = {0}; // do interpolation - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolatePrecipMobjState(thing, rendertimefrac, &interp); } diff --git a/src/hardware/hw_md2.c b/src/hardware/hw_md2.c index 7bc218d14..67bed7ee3 100644 --- a/src/hardware/hw_md2.c +++ b/src/hardware/hw_md2.c @@ -1344,7 +1344,7 @@ boolean HWR_DrawModel(gl_vissprite_t *spr) float finalscale; interpmobjstate_t interp; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(spr->mobj, rendertimefrac, &interp); } diff --git a/src/i_system.h b/src/i_system.h index 93bb34a21..fefe0a7c1 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -56,7 +56,7 @@ precise_t I_GetPreciseTime(void); /** \brief Converts a precise_t to microseconds and casts it to a 32 bit integer. */ -int I_PreciseToMicros(precise_t); +int I_PreciseToMicros(precise_t d); /** \brief The I_Sleep function @@ -64,6 +64,8 @@ int I_PreciseToMicros(precise_t); */ void I_Sleep(void); +boolean I_CheckFrameCap(precise_t start, precise_t end); + /** \brief Get events Called by D_SRB2Loop, diff --git a/src/i_video.h b/src/i_video.h index 638fcb668..d66b2d95f 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -151,4 +151,6 @@ void I_BeginRead(void); */ void I_EndRead(void); +UINT32 I_GetRefreshRate(void); + #endif diff --git a/src/m_menu.c b/src/m_menu.c index 368e7778e..6383c106d 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -22,6 +22,7 @@ #include "d_main.h" #include "d_netcmd.h" #include "console.h" +#include "r_fps.h" #include "r_local.h" #include "hu_stuff.h" #include "g_game.h" @@ -1380,16 +1381,14 @@ static menuitem_t OP_VideoOptionsMenu[] = {IT_HEADER, NULL, "Diagnostic", NULL, 184}, {IT_STRING | IT_CVAR, NULL, "Show FPS", &cv_ticrate, 190}, - {IT_STRING | IT_CVAR, NULL, "Clear Before Redraw", &cv_homremoval, 195}, - {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 200}, + {IT_STRING | IT_CVAR, NULL, "FPS Cap", &cv_fpscap, 195}, + {IT_STRING | IT_CVAR, NULL, "Clear Before Redraw", &cv_homremoval, 200}, + {IT_STRING | IT_CVAR, NULL, "Show \"FOCUS LOST\"", &cv_showfocuslost, 205}, #ifdef HWRENDER - {IT_HEADER, NULL, "Renderer", NULL, 208}, - {IT_CALL | IT_STRING, NULL, "OpenGL Options...", M_OpenGLOptionsMenu, 214}, + {IT_HEADER, NULL, "Renderer", NULL, 213}, + {IT_CALL | IT_STRING, NULL, "OpenGL Options...", M_OpenGLOptionsMenu, 219}, #endif - - {IT_HEADER, NULL, "Experimental", NULL, 222}, - {IT_STRING | IT_CVAR, NULL, "Frame Interpolation", &cv_frameinterpolation, 228}, }; static menuitem_t OP_VideoModeMenu[] = diff --git a/src/r_fps.c b/src/r_fps.c index 708add820..9c3a9db53 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -21,10 +21,52 @@ #include "p_spec.h" #include "r_state.h" #include "z_zone.h" +#include "console.h" // con_startup_loadprogress #ifdef HWRENDER #include "hardware/hw_main.h" // for cv_glshearing #endif +static CV_PossibleValue_t fpscap_cons_t[] = { + {-1, "Match refresh rate"}, + {0, "Unlimited"}, +#ifdef DEVELOP + // Lower values are actually pretty useful for debugging interp problems! + {1, "One Singular Frame"}, + {10, "10"}, + {20, "20"}, + {25, "25"}, + {30, "30"}, +#endif + {35, "35"}, + {50, "50"}, + {60, "60"}, + {70, "70"}, + {75, "75"}, + {90, "90"}, + {100, "100"}, + {120, "120"}, + {144, "144"}, + {200, "200"}, + {240, "240"}, + {0, NULL} +}; +consvar_t cv_fpscap = CVAR_INIT ("fpscap", "Match refresh rate", CV_SAVE, fpscap_cons_t, NULL); + +UINT32 R_GetFramerateCap(void) +{ + if (cv_fpscap.value < 0) + { + return I_GetRefreshRate(); + } + + return cv_fpscap.value; +} + +boolean R_UsingFrameInterpolation(void) +{ + return (R_GetFramerateCap() != TICRATE); // maybe use ">" instead? +} + static viewvars_t p1view_old; static viewvars_t p1view_new; static viewvars_t p2view_old; @@ -179,7 +221,7 @@ void R_SetViewContext(enum viewcontext_e _viewcontext) fixed_t R_InterpolateFixed(fixed_t from, fixed_t to) { - if (cv_frameinterpolation.value == 0) + if (!R_UsingFrameInterpolation()) { return to; } @@ -189,7 +231,7 @@ fixed_t R_InterpolateFixed(fixed_t from, fixed_t to) angle_t R_InterpolateAngle(angle_t from, angle_t to) { - if (cv_frameinterpolation.value == 0) + if (!R_UsingFrameInterpolation()) { return to; } diff --git a/src/r_fps.h b/src/r_fps.h index 1eb53b346..75d9ead3d 100644 --- a/src/r_fps.h +++ b/src/r_fps.h @@ -19,6 +19,11 @@ #include "p_local.h" #include "r_state.h" +extern consvar_t cv_fpscap; + +UINT32 R_GetFramerateCap(void); +boolean R_UsingFrameInterpolation(void); + enum viewcontext_e { VIEWCONTEXT_PLAYER1 = 0, diff --git a/src/r_main.c b/src/r_main.c index e7f567b58..896b6a29b 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -168,9 +168,6 @@ consvar_t cv_drawdist_precip = CVAR_INIT ("drawdist_precip", "1024", CV_SAVE, dr //consvar_t cv_precipdensity = CVAR_INIT ("precipdensity", "Moderate", CV_SAVE, precipdensity_cons_t, NULL); consvar_t cv_fov = CVAR_INIT ("fov", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange); -// Frame interpolation/uncapped -consvar_t cv_frameinterpolation = {"frameinterpolation", "On", CV_SAVE, CV_OnOff, NULL, 0, NULL, NULL, 0, 0, NULL}; - // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = CVAR_INIT ("homremoval", "No", CV_SAVE, homremoval_cons_t, NULL); @@ -1199,7 +1196,7 @@ void R_SetupFrame(player_t *player) // newview->sin = FINESINE(viewangle>>ANGLETOFINESHIFT); // newview->cos = FINECOSINE(viewangle>>ANGLETOFINESHIFT); - R_InterpolateView(cv_frameinterpolation.value == 1 ? rendertimefrac : FRACUNIT); + R_InterpolateView(R_UsingFrameInterpolation() ? rendertimefrac : FRACUNIT); } void R_SkyboxFrame(player_t *player) @@ -1343,7 +1340,7 @@ void R_SkyboxFrame(player_t *player) // newview->sin = FINESINE(viewangle>>ANGLETOFINESHIFT); // newview->cos = FINECOSINE(viewangle>>ANGLETOFINESHIFT); - R_InterpolateView(cv_frameinterpolation.value == 1 ? rendertimefrac : FRACUNIT); + R_InterpolateView(R_UsingFrameInterpolation() ? rendertimefrac : FRACUNIT); } boolean R_ViewpointHasChasecam(player_t *player) @@ -1627,5 +1624,5 @@ void R_RegisterEngineStuff(void) CV_RegisterVar(&cv_movebob); // Frame interpolation/uncapped - CV_RegisterVar(&cv_frameinterpolation); + CV_RegisterVar(&cv_fpscap); } diff --git a/src/r_main.h b/src/r_main.h index 08654a694..ccbc0ad8c 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -119,9 +119,6 @@ extern consvar_t cv_fov; extern consvar_t cv_skybox; extern consvar_t cv_tailspickup; -// Frame interpolation (uncapped framerate) -extern consvar_t cv_frameinterpolation; - // Called by startup code. void R_Init(void); diff --git a/src/r_things.c b/src/r_things.c index bdbb6b95f..a7df5adef 100644 --- a/src/r_things.c +++ b/src/r_things.c @@ -1148,7 +1148,7 @@ fixed_t R_GetShadowZ(mobj_t *thing, pslope_t **shadowslope) // for frame interpolation interpmobjstate_t interp = {0}; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -1281,7 +1281,7 @@ static void R_SkewShadowSprite( // for frame interpolation interpmobjstate_t interp = {0}; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -1324,7 +1324,7 @@ static void R_ProjectDropShadow(mobj_t *thing, vissprite_t *vis, fixed_t scale, if (abs(groundz-viewz)/tz > 4) return; // Prevent stretchy shadows and possible crashes - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -1530,7 +1530,7 @@ static void R_ProjectSprite(mobj_t *thing) interpmobjstate_t interp = {0}; // do interpolation - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -1850,7 +1850,7 @@ static void R_ProjectSprite(mobj_t *thing) fixed_t linkscale; thing = thing->tracer; - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolateMobjState(thing, rendertimefrac, &interp); } @@ -2210,7 +2210,7 @@ static void R_ProjectPrecipitationSprite(precipmobj_t *thing) interpmobjstate_t interp = {0}; // do interpolation - if (cv_frameinterpolation.value == 1 && !paused) + if (R_UsingFrameInterpolation() && !paused) { R_InterpolatePrecipMobjState(thing, rendertimefrac, &interp); } diff --git a/src/screen.c b/src/screen.c index f81a6563d..88898c503 100644 --- a/src/screen.c +++ b/src/screen.c @@ -33,12 +33,15 @@ #include "s_sound.h" // ditto #include "g_game.h" // ditto #include "p_local.h" // P_AutoPause() + #ifdef HWRENDER #include "hardware/hw_main.h" #include "hardware/hw_light.h" #include "hardware/hw_model.h" #endif +// SRB2Kart +#include "r_fps.h" // R_GetFramerateCap #if defined (USEASM) && !defined (NORUSEASM)//&& (!defined (_MSC_VER) || (_MSC_VER <= 1200)) #define RUSEASM //MSC.NET can't patch itself @@ -67,6 +70,7 @@ static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, consvar_t cv_scr_width = CVAR_INIT ("scr_width", "1280", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_scr_height = CVAR_INIT ("scr_height", "800", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_scr_depth = CVAR_INIT ("scr_depth", "16 bits", CV_SAVE, scr_depth_cons_t, NULL); + consvar_t cv_renderview = CVAR_INIT ("renderview", "On", 0, CV_OnOff, NULL); CV_PossibleValue_t cv_renderer_t[] = { @@ -447,86 +451,82 @@ boolean SCR_IsAspectCorrect(INT32 width, INT32 height) ); } -// XMOD FPS display -// moved out of os-specific code for consistency -static boolean fpsgraph[TICRATE]; -static tic_t lasttic; static tic_t totaltics; +double averageFPS = 0.0f; -static UINT32 fpstime = 0; -static UINT32 lastupdatetime = 0; +#define FPS_SAMPLE_RATE (50000) // How often to update FPS samples, in microseconds +#define NUM_FPS_SAMPLES 16 // Number of samples to store -#define FPSUPDATERATE 1/20 // What fraction of a second to update at. The fraction will not simplify to 0, trust me. -#define FPSMAXSAMPLES 16 +static double fps_samples[NUM_FPS_SAMPLES]; -static UINT32 fpssamples[FPSMAXSAMPLES]; -static UINT32 fpssampleslen = 0; -static UINT32 fpssum = 0; -double aproxfps = 0.0f; - -void SCR_CalcAproxFps(void) +void SCR_CalculateFPS(void) { - tic_t i = 0; - tic_t ontic = I_GetTime(); + static boolean init = false; - totaltics = 0; + static precise_t startTime = 0; + precise_t endTime = 0; - // Update FPS time - if (I_PreciseToMicros(fpstime - lastupdatetime) > 1000000 * FPSUPDATERATE) + static precise_t updateTime = 0; + int updateElapsed = 0; + int i; + + endTime = I_GetPreciseTime(); + + if (init == false) { - if (fpssampleslen == FPSMAXSAMPLES) - { - fpssum -= fpssamples[0]; - - for (i = 1; i < fpssampleslen; i++) - fpssamples[i-1] = fpssamples[i]; - } - else - fpssampleslen++; - - fpssamples[fpssampleslen-1] = I_GetPreciseTime() - fpstime; - fpssum += fpssamples[fpssampleslen-1]; - - aproxfps = 1000000 / (I_PreciseToMicros(fpssum) / (double)fpssampleslen); - - lastupdatetime = I_GetPreciseTime(); + startTime = updateTime = endTime; + init = true; + return; } - fpstime = I_GetPreciseTime(); + updateElapsed = I_PreciseToMicros(endTime - updateTime); - // Update ticrate time - for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i) - fpsgraph[i % TICRATE] = false; + if (updateElapsed >= FPS_SAMPLE_RATE) + { + static int sampleIndex = 0; + int frameElapsed = I_PreciseToMicros(endTime - startTime); - fpsgraph[ontic % TICRATE] = true; + fps_samples[sampleIndex] = frameElapsed / 1000.0f; - for (i = 0;i < TICRATE;++i) - if (fpsgraph[i]) - ++totaltics; + sampleIndex++; + if (sampleIndex >= NUM_FPS_SAMPLES) + sampleIndex = 0; - lasttic = ontic; + averageFPS = 0.0f; + for (i = 0; i < NUM_FPS_SAMPLES; i++) + { + averageFPS += fps_samples[i]; + } + averageFPS = 1000.0f / (averageFPS / NUM_FPS_SAMPLES); + + updateTime = endTime; + } + + startTime = endTime; } void SCR_DisplayTicRate(void) { INT32 ticcntcolor = 0; const INT32 h = vid.height-(8*vid.dupy); + UINT32 cap = R_GetFramerateCap(); + double fps = ceil(averageFPS); if (gamestate == GS_NULL) return; - if (totaltics <= TICRATE/2) ticcntcolor = V_REDMAP; - else if (totaltics == TICRATE) ticcntcolor = V_GREENMAP; + if (totaltics <= cap/2) ticcntcolor = V_REDMAP; + else if (totaltics >= cap) ticcntcolor = V_GREENMAP; if (cv_ticrate.value == 2) // compact counter - V_DrawString(vid.width-(24*vid.dupx), h, - ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%03.0f", aproxfps)); + V_DrawString(vid.width-(32*vid.dupx), h, + ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%04.0f", fps)); else if (cv_ticrate.value == 1) // full counter { - V_DrawString(vid.width-(88*vid.dupx), h, + V_DrawString(vid.width-(104*vid.dupx), h, V_YELLOWMAP|V_NOSCALESTART|V_USERHUDTRANS, "FPS:"); - V_DrawString(vid.width-(56*vid.dupx), h, - ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%03.0f/%03u", aproxfps, TICRATE)); + V_DrawString(vid.width-(72*vid.dupx), h, + ticcntcolor|V_NOSCALESTART|V_USERHUDTRANS, va("%4.0f/%4u", fps, cap)); } } diff --git a/src/screen.h b/src/screen.h index 425d10954..bfb9e7fbf 100644 --- a/src/screen.h +++ b/src/screen.h @@ -181,7 +181,8 @@ extern boolean R_SSE2; extern viddef_t vid; extern INT32 setmodeneeded; // mode number to set if needed, or 0 extern UINT8 setrenderneeded; -extern double aproxfps; + +extern double averageFPS; void SCR_ChangeRenderer(void); @@ -212,7 +213,7 @@ void SCR_CheckDefaultMode(void); // Set the mode number which is saved in the config void SCR_SetDefaultMode(void); -void SCR_CalcAproxFps(void); +void SCR_CalculateFPS(void); FUNCMATH boolean SCR_IsAspectCorrect(INT32 width, INT32 height); diff --git a/src/sdl/i_system.c b/src/sdl/i_system.c index bf2e42cfd..6b9b11327 100644 --- a/src/sdl/i_system.c +++ b/src/sdl/i_system.c @@ -186,6 +186,7 @@ static char returnWadPath[256]; #include "../m_argv.h" #include "../r_main.h" // Frame interpolation/uncapped +#include "../r_fps.h" #ifdef MAC_ALERT #include "macosx/mac_alert.h" @@ -2200,6 +2201,57 @@ void I_Sleep(void) SDL_Delay(cv_sleep.value); } +boolean I_CheckFrameCap(precise_t start, precise_t end) +{ + UINT32 capFrames = R_GetFramerateCap(); + int capMicros = 0; + + int elapsed; + + if (capFrames == 0) + { + // We don't want to cap. + return false; + } + + elapsed = I_PreciseToMicros(end - start); + capMicros = 1000000 / capFrames; + + if (elapsed < capMicros) + { + // Wait to draw the next frame. + UINT32 wait = ((capMicros - elapsed) / 1000); + + if (cv_sleep.value > 1) + { + // 1 is the default, and in non-interpolated mode is just the bare minimum wait. + // Since we're already adding some wait with an FPS cap, only apply when it's above 1. + wait += cv_sleep.value - 1; + } + + // If the wait's greater than our granularity value, + // we'll just burn the couple extra cycles in the main loop + // in order to get to the next frame. + // This makes us get to the exact FPS cap more often. + + // Higher values have more wasted CPU cycles, but the in-game frame performance is better. + // 10ms is the average clock tick of most OS scheduling. + // 15ms is a little more than that, for leniency on slow machines. (This helps mine reach a stable 60, at least!) + // (https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdldelay.html) +#define DELAY_GRANULARITY 15 + if (wait >= DELAY_GRANULARITY) + { + SDL_Delay(wait); + } +#undef DELAY_GRANULARITY + + return true; + } + + // Waited enough to draw again. + return false; +} + #ifdef NEWSIGNALHANDLER static void newsignalhandler_Warn(const char *pr) { diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index d8899e932..eedb60e09 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -1214,7 +1214,7 @@ void I_FinishUpdate(void) if (rendermode == render_none) return; //Alam: No software or OpenGl surface - SCR_CalcAproxFps(); + //SCR_CalculateFPS(); // Moved to main loop if (I_SkipFrame()) return; @@ -1475,8 +1475,15 @@ static SDL_bool Impl_CreateContext(void) int flags = 0; // Use this to set SDL_RENDERER_* flags now if (usesdl2soft) flags |= SDL_RENDERER_SOFTWARE; +#if 0 + // This shit is BROKEN. + // - The version of SDL we're using cannot toggle VSync at runtime. We'll need a new SDL version implemented to have this work properly. + // - cv_vidwait is initialized before config is loaded, so it's forced to default value at runtime, and forced off when switching. The config loading code would need restructured. + // - With both this & frame interpolation on, I_FinishUpdate takes x10 longer. At this point, it is simpler to use a standard FPS cap. + // So you can probably guess why I'm kinda over this, I'm just disabling it. else if (cv_vidwait.value) flags |= SDL_RENDERER_PRESENTVSYNC; +#endif if (!renderer) renderer = SDL_CreateRenderer(window, -1, flags); @@ -1961,3 +1968,23 @@ void I_GetCursorPosition(INT32 *x, INT32 *y) { SDL_GetMouseState(x, y); } + +UINT32 I_GetRefreshRate(void) +{ + int index = SDL_GetWindowDisplayIndex(window); + SDL_DisplayMode m; + + if (SDL_WasInit(SDL_INIT_VIDEO) == 0) + { + // Video not init yet. + return 0; + } + + if (SDL_GetDesktopDisplayMode(index, &m) != 0) + { + // Error has occurred. + return 0; + } + + return m.refresh_rate; +}