From 7be381c9badeed41c3161f921463be7ec1b89458 Mon Sep 17 00:00:00 2001 From: Bart Trzynadlowski Date: Sun, 12 Jun 2022 17:51:57 +0000 Subject: [PATCH] Added configurable refresh rate. Default is 60 but -true-hz on the command line enables 57.524 Hz mode, which some players report making Spikeout and the timing of combos in VF3 more authentic. The config key is RefreshRate and is specified as a floating point value. Should the desire arise, arbitrary refresh rates can be set in Supermodel.ini using this key but almost no validation of the value is performed. Thanks to forum user trap15 for the original code and PonMi for advocating for this patch to be included. --- Src/OSD/SDL/Main.cpp | 108 +++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 35 deletions(-) diff --git a/Src/OSD/SDL/Main.cpp b/Src/OSD/SDL/Main.cpp index fac66f2..4a0d0de 100644 --- a/Src/OSD/SDL/Main.cpp +++ b/Src/OSD/SDL/Main.cpp @@ -823,16 +823,50 @@ void EndFrameVideo() SDL_GL_SwapWindow(s_window); } -static void SuperSleep(UINT32 time) -{ - UINT32 start = SDL_GetTicks(); - UINT32 tics = start; - while (start + time > tics) { - tics = SDL_GetTicks(); - } +/****************************************************************************** + Frame Timing +******************************************************************************/ + +static uint64_t s_perfCounterFrequency = 0; + +static uint64_t GetDesiredRefreshRateMilliHz() +{ + // The refresh rate is expressed as mHz (millihertz -- Hz * 1000) in order to + // be expressable as an integer. E.g.: 57.524 Hz -> 57524 mHz. + float refreshRateHz = std::abs(s_runtime_config["RefreshRate"].ValueAs()); + uint64_t refreshRateMilliHz = uint64_t(1000.0f * refreshRateHz); + return refreshRateMilliHz; } +static void SuperSleepUntil(uint64_t target) +{ + uint64_t time = SDL_GetPerformanceCounter(); + + // If we're ahead of the target, we're done + if (time > target) + { + return; + } + + // Compute the whole number of millis to sleep. Because OS sleep is not accurate, + // we actually sleep for one less and will spin-wait for the final millisecond. + int64_t numWholeMillisToSleep = int64_t((target - time) * 1000 / s_perfCounterFrequency); + numWholeMillisToSleep -= 1; + if (numWholeMillisToSleep > 0) + { + SDL_Delay(numWholeMillisToSleep); + } + + // Spin until requested time + uint64_t now; + do + { + now = SDL_GetPerformanceCounter(); + } while (now < target); +} + + /****************************************************************************** Main Program Loop ******************************************************************************/ @@ -846,7 +880,7 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In { #endif // SUPERMODEL_DEBUGGER std::string initialState = s_runtime_config["InitStateFile"].ValueAs(); - unsigned prevFPSTicks; + uint64_t prevFPSTicks; unsigned fpsFramesElapsed; bool gameHasLightguns = false; bool quit = false; @@ -902,6 +936,11 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In if (Outputs != NULL) Model3->AttachOutputs(Outputs); + // Frame timing + s_perfCounterFrequency = SDL_GetPerformanceFrequency(); + uint64_t perfCountPerFrame = s_perfCounterFrequency * 1000 / GetDesiredRefreshRateMilliHz(); + uint64_t nextTime = 0; + // Initialize the renderers CRender2D *Render2D = new CRender2D(s_runtime_config); IRender3D *Render3D = s_runtime_config["New3DEngine"].ValueAs() ? ((IRender3D *) new New3D::CNew3D(s_runtime_config, Model3->GetGame().name)) : ((IRender3D *) new Legacy3D::CLegacy3D(s_runtime_config)); @@ -910,7 +949,7 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In if (OKAY != Render3D->Init(xOffset, yOffset, xRes, yRes, totalXRes, totalYRes)) goto QuitError; Model3->AttachRenderers(Render2D,Render3D); - + // Reset emulator Model3->Reset(); @@ -930,7 +969,7 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In // Emulate! fpsFramesElapsed = 0; - prevFPSTicks = SDL_GetTicks(); + prevFPSTicks = SDL_GetPerformanceCounter(); quit = false; paused = false; dumpTimings = false; @@ -943,8 +982,6 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In #endif while (!quit) { - auto startTime = SDL_GetTicks(); - // Render if paused, otherwise run a frame if (paused) Model3->RenderFrame(); @@ -1211,30 +1248,27 @@ int Supermodel(const Game &game, ROMSet *rom_set, IEmulator *Model3, CInputs *In } #endif // SUPERMODEL_DEBUGGER - - // Frame rate and limiting - unsigned currentFPSTicks = SDL_GetTicks(); - if (s_runtime_config["ShowFrameRate"].ValueAs()) - { - ++fpsFramesElapsed; - if((currentFPSTicks-prevFPSTicks) >= 1000) // update FPS every 1 second (each tick is 1 ms) - { - sprintf(titleStr, "%s - %1.1f FPS%s", baseTitleStr, (float)fpsFramesElapsed/((float)(currentFPSTicks-prevFPSTicks)/1000.0f), paused ? " (Paused)" : ""); - SDL_SetWindowTitle(s_window, titleStr); - prevFPSTicks = currentFPSTicks; // reset tick count - fpsFramesElapsed = 0; // reset frame count - } - } - + // Refresh rate (frame limiting) if (paused || s_runtime_config["Throttle"].ValueAs()) { - UINT32 endTime = SDL_GetTicks(); - UINT32 diff = endTime - startTime; - UINT32 frameTime = (UINT32)(1000 / 60.f); // 60 fps, we could roll with 57.5? that would be a jerk fest on 60hz screens though + SuperSleepUntil(nextTime); + nextTime = SDL_GetPerformanceCounter() + perfCountPerFrame; + } - if (diff < frameTime) { - SuperSleep(frameTime - diff); - } + // Measure frame rate + uint64_t currentFPSTicks = SDL_GetPerformanceCounter(); + if (s_runtime_config["ShowFrameRate"].ValueAs()) + { + fpsFramesElapsed += 1; + uint64_t measurementTicks = currentFPSTicks - prevFPSTicks; + if (measurementTicks >= s_perfCounterFrequency) // update FPS every 1 second (s_perfCounterFrequency is how many perf ticks in one second) + { + float fps = float(fpsFramesElapsed) / (float(measurementTicks) / float(s_perfCounterFrequency)); + sprintf(titleStr, "%s - %1.3f FPS%s", baseTitleStr, fps, paused ? " (Paused)" : ""); + SDL_SetWindowTitle(s_window, titleStr); + prevFPSTicks = currentFPSTicks; // reset tick count + fpsFramesElapsed = 0; // reset frame count + } } if (dumpTimings && !paused) @@ -1413,6 +1447,7 @@ static Util::Config::Node DefaultConfig() config.Set("WideBackground", false); config.Set("VSync", true); config.Set("Throttle", true); + config.Set("RefreshRate", 60.0f); config.Set("ShowFrameRate", false); config.Set("Crosshairs", int(0)); config.Set("FlipStereo", false); @@ -1461,7 +1496,7 @@ static void Title(void) { puts("Supermodel: A Sega Model 3 Arcade Emulator (Version " SUPERMODEL_VERSION ")"); puts("Copyright 2011-2022 by Bart Trzynadlowski, Nik Henson, Ian Curtis, Harry Tuttle,"); - puts(" Spindizzi, gm_mathew and njz3\n"); + puts(" Spindizzi, gm_mathew, and njz3\n"); } static void Help(void) @@ -1492,9 +1527,10 @@ static void Help(void) puts(" -wide-bg When wide-screen mode is enabled, also expand the 2D"); puts(" background layer to screen width"); puts(" -stretch Fit viewport to resolution, ignoring aspect ratio"); - puts(" -no-throttle Disable 60 Hz frame rate lock"); + puts(" -no-throttle Disable frame rate lock"); puts(" -vsync Lock to vertical refresh rate [Default]"); puts(" -no-vsync Do not lock to vertical refresh rate"); + puts(" -true-hz Use true Model 3 refresh rate of 57.524 Hz"); puts(" -show-fps Display frame rate in window title bar"); puts(" -crosshairs= Crosshairs configuration for gun games:"); puts(" 0=none [Default], 1=P1 only, 2=P2 only, 3=P1 & P2"); @@ -1714,6 +1750,8 @@ static ParsedCommandLine ParseCommandLine(int argc, char **argv) } } } + else if (arg == "-true-hz") + cmd_line.config.Set("RefreshRate", 57.524f); else if (arg == "-print-gl-info") cmd_line.print_gl_info = true; else if (arg == "-config-inputs")