From 369d5398c55d936ddfbe71c570269fed7a4065b3 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. --- src/d_clisrv.c | 1 - src/d_main.c | 48 +++++++++++----- src/i_system.h | 4 +- src/i_video.h | 2 + src/m_menu.c | 15 ++--- src/r_fps.c | 30 +++++++++- src/r_fps.h | 5 ++ src/r_main.c | 8 +-- src/r_main.h | 4 -- src/screen.c | 140 ++++++++++++++++++++------------------------- src/screen.h | 5 +- src/sdl/i_system.c | 60 ++++++++++++++++++- src/sdl/i_video.c | 29 +++++++++- 13 files changed, 231 insertions(+), 120 deletions(-) diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 7a6a5bc6b..27511c719 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -5197,7 +5197,6 @@ boolean TryRunTics(tic_t realtics) while (neededtic > gametic) { DEBFILE(va("============ Running tic %d (local %d)\n", gametic, localgametic)); - prev_tics = I_GetTime(); ps_tictime = I_GetPreciseTime(); diff --git a/src/d_main.c b/src/d_main.c index 44e481108..1a24a9375 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -688,6 +688,7 @@ void D_SRB2Loop(void) { tic_t oldentertics = 0, entertic = 0, realtics = 0, rendertimeout = INFTICS; boolean ticked; + boolean interp; if (dedicated) server = true; @@ -759,10 +760,19 @@ void D_SRB2Loop(void) debugload--; #endif - if (!realtics && !singletics && cv_frameinterpolation.value != 1) + interp = R_UsingFrameInterpolation(); + if (interp) { - I_Sleep(); - continue; + if (I_CheckFrameCap()) + continue; + } + else + { + if (!realtics && !singletics) + { + I_Sleep(); + continue; + } } #ifdef HW3SOUND @@ -777,20 +787,22 @@ void D_SRB2Loop(void) // process tics (but maybe not if realtic == 0) ticked = TryRunTics(realtics); - if (cv_frameinterpolation.value == 1 && !(paused || P_AutoPause())) + if (interp && !(paused || P_AutoPause())) { - static float tictime; + static float tictime = 0.0f; float entertime = I_GetTimeFrac(); - + float ticdiff = 0.0f; fixed_t entertimefrac; if (ticked) tictime = entertime; - if (aproxfps < 35.0) + ticdiff = entertime - tictime; + + if (ticdiff >= 1.0f) entertimefrac = FRACUNIT; else - entertimefrac = FLOAT_TO_FIXED(entertime - tictime); + entertimefrac = FLOAT_TO_FIXED(ticdiff); // renderdeltatics is a bit awkard to evaluate, since the system time interface is whole tic-based renderdeltatics = realtics * FRACUNIT; @@ -807,7 +819,7 @@ void D_SRB2Loop(void) renderdeltatics = realtics * FRACUNIT; } - if (cv_frameinterpolation.value == 1) + if (interp) { D_Display(); } @@ -817,9 +829,10 @@ void D_SRB2Loop(void) 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() : (void)0; + if (!interp) + { + D_Display(); + } if (moviemode) M_SaveFrame(); @@ -828,8 +841,10 @@ void D_SRB2Loop(void) } else if (rendertimeout < entertic) // in case the server hang or netsplit { - // (Only display if not already done for frame interp) - cv_frameinterpolation.value == 0 ? D_Display() : (void)0; + if (!interp) + { + D_Display(); + } if (moviemode) M_SaveFrame(); @@ -853,6 +868,11 @@ void D_SRB2Loop(void) Discord_RunCallbacks(); } #endif + + // 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/i_system.h b/src/i_system.h index 4a8bf0c27..1bc2c3892 100644 --- a/src/i_system.h +++ b/src/i_system.h @@ -56,7 +56,7 @@ precise_t I_GetPreciseTime(void); /** \brief Returns the difference between precise times as microseconds. */ -int I_PreciseToMicros(precise_t d); +INT64 I_PreciseToMicros(precise_t d); /** \brief The I_Sleep function @@ -64,6 +64,8 @@ int I_PreciseToMicros(precise_t d); */ void I_Sleep(void); +boolean I_CheckFrameCap(void); + /** \brief Get events Called by D_SRB2Loop, diff --git a/src/i_video.h b/src/i_video.h index ab48881d4..760440209 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 f538defcf..d33387947 100644 --- a/src/m_menu.c +++ b/src/m_menu.c @@ -63,6 +63,7 @@ #include "d_player.h" // KITEM_ constants #include "k_color.h" #include "k_grandprix.h" +#include "r_fps.h" #include "i_joy.h" // for joystick menu controls @@ -1259,18 +1260,18 @@ static menuitem_t OP_VideoOptionsMenu[] = {IT_STRING|IT_CVAR, NULL, "Fullscreen", {.cvar = &cv_fullscreen}, 20}, #endif #ifdef HWRENDER - {IT_STRING | IT_CVAR, NULL, "Renderer", {.cvar = &cv_renderer}, 30}, + {IT_STRING | IT_CVAR, NULL, "Renderer", {.cvar = &cv_renderer}, 30}, #else - {IT_TRANSTEXT | IT_PAIR, "Renderer", "Software", {.cvar = &cv_renderer}, 30}, + {IT_TRANSTEXT | IT_PAIR, "Renderer", "Software", {.cvar = &cv_renderer}, 30}, #endif {IT_STRING | IT_CVAR | IT_CV_SLIDER, - NULL, "Gamma", {.cvar = &cv_globalgamma}, 50}, + NULL, "Gamma", {.cvar = &cv_globalgamma}, 50}, - {IT_STRING | IT_CVAR, NULL, "Show FPS", {.cvar = &cv_ticrate}, 60}, - {IT_STRING | IT_CVAR, NULL, "Vertical Sync", {.cvar = &cv_vidwait}, 70}, + {IT_STRING | IT_CVAR, NULL, "Show FPS", {.cvar = &cv_ticrate}, 60}, + {IT_STRING | IT_CVAR, NULL, "FPS Cap", {.cvar = &cv_fpscap}, 70}, - {IT_STRING | IT_CVAR, NULL, "Draw Distance", {.cvar = &cv_drawdist}, 90}, - {IT_STRING | IT_CVAR, NULL, "Weather Draw Distance", {.cvar = &cv_drawdist_precip}, 100}, + {IT_STRING | IT_CVAR, NULL, "Draw Distance", {.cvar = &cv_drawdist}, 90}, + {IT_STRING | IT_CVAR, NULL, "Weather Draw Distance", {.cvar = &cv_drawdist_precip}, 100}, {IT_STRING | IT_CVAR, NULL, "Skyboxes", {.cvar = &cv_skybox}, 110}, #ifdef HWRENDER diff --git a/src/r_fps.c b/src/r_fps.c index 34e2e09a8..758bbaf39 100644 --- a/src/r_fps.c +++ b/src/r_fps.c @@ -24,6 +24,32 @@ #include "hardware/hw_main.h" // for cv_glshearing #endif +static CV_PossibleValue_t fpscap_cons_t[] = { + {-1, "MIN"}, + {1000, "MAX"}, + //{0, "Uncapped"}, + //{-1, "Refresh"}, + {0, NULL} +}; +consvar_t cv_fpscap = CVAR_INIT ("fpscap", "-1", CV_SAVE, fpscap_cons_t, NULL); + +UINT32 R_GetFramerateCap(void) +{ + if (cv_fpscap.value < 0) + { + return I_GetRefreshRate(); + } + else + { + return cv_fpscap.value; + } +} + +boolean R_UsingFrameInterpolation(void) +{ + return (R_GetFramerateCap() != TICRATE); // maybe use ">" instead? +} + static viewvars_t pview_old[MAXSPLITSCREENPLAYERS]; static viewvars_t pview_new[MAXSPLITSCREENPLAYERS]; static viewvars_t skyview_old[MAXSPLITSCREENPLAYERS]; @@ -156,7 +182,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; } @@ -166,7 +192,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 c93c4701e..952c3c94e 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 6a7dea80b..ebacbf601 100644 --- a/src/r_main.c +++ b/src/r_main.c @@ -104,9 +104,6 @@ lighttable_t *scalelight[LIGHTLEVELS][MAXLIGHTSCALE]; lighttable_t *scalelightfixed[MAXLIGHTSCALE]; lighttable_t *zlight[LIGHTLEVELS][MAXLIGHTZ]; -// Frame interpolation/uncapped -tic_t prev_tics; - // Hack to support extra boom colormaps. extracolormap_t *extra_colormaps = NULL; @@ -175,9 +172,6 @@ consvar_t cv_fov[MAXSPLITSCREENPLAYERS] = { CVAR_INIT ("fov4", "90", CV_FLOAT|CV_CALL, fov_cons_t, Fov_OnChange) }; -// Frame interpolation/uncapped -consvar_t cv_frameinterpolation = CVAR_INIT ("frameinterpolation", "On", CV_SAVE, CV_OnOff, NULL); - // Okay, whoever said homremoval causes a performance hit should be shot. consvar_t cv_homremoval = CVAR_INIT ("homremoval", "Yes", CV_SAVE, homremoval_cons_t, NULL); @@ -1679,5 +1673,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 44d5b7ade..b67eb8c4f 100644 --- a/src/r_main.h +++ b/src/r_main.h @@ -116,10 +116,6 @@ extern consvar_t cv_fov[MAXSPLITSCREENPLAYERS]; extern consvar_t cv_skybox; extern consvar_t cv_tailspickup; -// Frame interpolation (uncapped framerate) -extern tic_t prev_tics; -extern consvar_t cv_frameinterpolation; - // Called by startup code. void R_Init(void); diff --git a/src/screen.c b/src/screen.c index 9b26b4fa1..79b6d7578 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 @@ -68,16 +71,17 @@ viddef_t vid; INT32 setmodeneeded; //video mode change needed if > 0 (the mode number to set + 1) UINT8 setrenderneeded = 0; -static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, {24, "24 bits"}, {32, "32 bits"}, {0, NULL}}; - -static CV_PossibleValue_t shittyscreen_cons_t[] = {{0, "Okay"}, {1, "Shitty"}, {2, "Extra Shitty"}, {0, NULL}}; - //added : 03-02-98: default screen mode, as loaded/saved in config consvar_t cv_scr_width = CVAR_INIT ("scr_width", "640", CV_SAVE, CV_Unsigned, NULL); consvar_t cv_scr_height = CVAR_INIT ("scr_height", "400", CV_SAVE, CV_Unsigned, NULL); + +static CV_PossibleValue_t scr_depth_cons_t[] = {{8, "8 bits"}, {16, "16 bits"}, {24, "24 bits"}, {32, "32 bits"}, {0, 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); consvar_t cv_vhseffect = CVAR_INIT ("vhspause", "On", CV_SAVE, CV_OnOff, NULL); + +static CV_PossibleValue_t shittyscreen_cons_t[] = {{0, "Okay"}, {1, "Shitty"}, {2, "Extra Shitty"}, {0, NULL}}; consvar_t cv_shittyscreen = CVAR_INIT ("televisionsignal", "Okay", CV_NOSHOWHELP, shittyscreen_cons_t, NULL); CV_PossibleValue_t cv_renderer_t[] = { @@ -518,107 +522,87 @@ boolean SCR_IsAspectCorrect(INT32 width, INT32 height) ); } -// XMOD FPS display -// moved out of os-specific code for consistency -static boolean ticsgraph[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 precise_t startTime = 0; + precise_t endTime = 0; - totaltics = 0; + static precise_t updateTime = 0; + INT64 updateElapsed = 0; + int i; - // Update FPS time - if (I_PreciseToMicros(fpstime - lastupdatetime) > 1000000 * FPSUPDATERATE) + endTime = I_GetPreciseTime(); + + updateElapsed = I_PreciseToMicros(endTime - updateTime); + + if (updateElapsed >= FPS_SAMPLE_RATE) { - if (fpssampleslen == FPSMAXSAMPLES) + static int sampleIndex = 0; + INT64 frameElapsed = I_PreciseToMicros(endTime - startTime); + + fps_samples[sampleIndex] = frameElapsed / 1000.0f; + + sampleIndex++; + if (sampleIndex >= NUM_FPS_SAMPLES) + sampleIndex = 0; + + averageFPS = 0.0f; + for (i = 0; i < NUM_FPS_SAMPLES; i++) { - fpssum -= fpssamples[0]; - - for (i = 1; i < fpssampleslen; i++) - fpssamples[i-1] = fpssamples[i]; + averageFPS += fps_samples[i]; } - else - fpssampleslen++; + averageFPS = 1000.0f / (averageFPS / NUM_FPS_SAMPLES); - fpssamples[fpssampleslen-1] = I_GetPreciseTime() - fpstime; - fpssum += fpssamples[fpssampleslen-1]; - - aproxfps = 1000000 / (I_PreciseToMicros(fpssum) / (double)fpssampleslen); - - lastupdatetime = I_GetPreciseTime(); + updateTime = endTime; } - fpstime = I_GetPreciseTime(); - - // Update ticrate time - for (i = lasttic + 1; i < TICRATE+lasttic && i < ontic; ++i) - ticsgraph[i % TICRATE] = false; - - ticsgraph[ontic % TICRATE] = true; - - for (i = 0;i < TICRATE;++i) - if (ticsgraph[i]) - ++totaltics; - - lasttic = ontic; + startTime = endTime; } void SCR_DisplayTicRate(void) { const UINT8 *ticcntcolor = NULL; + UINT32 cap = R_GetFramerateCap(); + UINT32 benchmark = (cap == 0) ? I_GetRefreshRate() : cap; + INT32 x = 318; + double fps = ceil(averageFPS); // draw "FPS" V_DrawFixedPatch(306<= 60.0f) ticcntcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MINT, GTC_CACHE); + if (fps > (benchmark - 5)) + ticcntcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MINT, GTC_CACHE); + else if (fps < 20) + ticcntcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_RASPBERRY, GTC_CACHE); - /* - if (cv_fpscap.value != 0) - { - // draw total frame: - //V_DrawPingNum(318, 190, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_HUDTRANS, cv_fpscap.value, ticcntcolor); - // draw "/" - //V_DrawFixedPatch(306<= TICRATE) ticcntcolor = R_GetTranslationColormap(TC_RAINBOW, SKINCOLOR_MINT, GTC_CACHE); + UINT32 digits = 1; + UINT32 c2 = cap; + + while (c2 > 0) + { + c2 = c2 / 10; + digits++; + } // draw total frame: - V_DrawPingNum(318, 190, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_HUDTRANS, TICRATE, ticcntcolor); + V_DrawPingNum(x, 190, V_SNAPTOBOTTOM|V_SNAPTORIGHT|V_HUDTRANS, cap, ticcntcolor); + x -= digits * 4; + // draw "/" - V_DrawFixedPatch(306< 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 reach just + // that much closer to exactly the FPS cap! +#define DELAY_GRANULARITY 10 // 10ms is the average clock tick of most OS scheduling. (https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdldelay.html) + if (wait >= DELAY_GRANULARITY) + { + SDL_Delay(wait); + } +#undef DELAY_GRANULARITY + + return true; + } + + // Waited enough to draw again. + start = end; + 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 92edf26bd..9f96a2177 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -1293,7 +1293,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; @@ -1583,8 +1583,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); @@ -2064,3 +2071,23 @@ void I_ShutdownGraphics(void) framebuffer = SDL_FALSE; } #endif + +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; +}