Index: Src/Model3/Model3.cpp =================================================================== --- Src/Model3/Model3.cpp (revision 793) +++ Src/Model3/Model3.cpp (working copy) @@ -2077,7 +2077,7 @@ // Compute display and VBlank timings unsigned ppcCycles = m_config["PowerPCFrequency"].ValueAs() * 1000000; - unsigned frameCycles = ppcCycles / 60; + unsigned frameCycles = (uint64_t)ppcCycles * 1000ULL / m_config["RefreshRate"].ValueAs(); unsigned gapCycles = (unsigned)((float)frameCycles * 2.5f / 100.0f); // we need a gap between asserting irq2 & irq 0x40 unsigned offsetCycles = (unsigned)((float)frameCycles * 33.f / 100.0f); unsigned dispCycles = frameCycles - gapCycles - offsetCycles; Index: Src/OSD/SDL/Main.cpp =================================================================== --- Src/OSD/SDL/Main.cpp (revision 793) +++ Src/OSD/SDL/Main.cpp (working copy) @@ -810,16 +810,37 @@ SDL_GL_SwapWindow(s_window); } -static void SuperSleep(UINT32 time) +static uint64_t perfctrFreq = 0; +static uint64_t perfctrStep; + +static uint64_t SuperGetTime(void) { - UINT32 start = SDL_GetTicks(); - UINT32 tics = start; + return SDL_GetPerformanceCounter(); +} - while (start + time > tics) { - tics = SDL_GetTicks(); +static void SuperSleepUntil(uint64_t tgt) +{ + // If we're ahead of the target, we're done. + if (SuperGetTime() > tgt) { + return; } + + int64_t whole = (tgt - SuperGetTime()) * 1000 / perfctrFreq; + whole--; // OS sleep isn't accurate, so give an extra millisecond of spinloop + + // Use OS's sleep for whole milliseconds + if (whole > 0) { + SDL_Delay(whole); } + // Spin until requested time + uint64_t now; + do { + now = SuperGetTime(); + } while (now < tgt); +} + + /****************************************************************************** Main Program Loop ******************************************************************************/ @@ -839,6 +860,7 @@ bool quit = false; bool paused = false; bool dumpTimings = false; + uint64_t nextTime; // Initialize and load ROMs if (OKAY != Model3->Init()) @@ -927,10 +949,11 @@ quit = true; } #endif + perfctrFreq = SDL_GetPerformanceFrequency(); + perfctrStep = perfctrFreq * 1000 / s_runtime_config["RefreshRate"].ValueAs(); + nextTime = 0; while (!quit) { - auto startTime = SDL_GetTicks(); - // Render if paused, otherwise run a frame if (paused) Model3->RenderFrame(); @@ -1192,6 +1215,12 @@ } #endif // SUPERMODEL_DEBUGGER + // Throttle to appropriate speed + if (paused || s_runtime_config["Throttle"].ValueAs()) + { + SuperSleepUntil(nextTime); + nextTime = SuperGetTime() + perfctrStep; + } // Frame rate and limiting unsigned currentFPSTicks = SDL_GetTicks(); @@ -1198,9 +1227,9 @@ if (s_runtime_config["ShowFrameRate"].ValueAs()) { ++fpsFramesElapsed; - if((currentFPSTicks-prevFPSTicks) >= 1000) // update FPS every 1 second (each tick is 1 ms) + 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)" : ""); + sprintf(titleStr, "%s - %1.2f 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 @@ -1207,17 +1236,6 @@ } } - 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 - - if (diff < frameTime) { - SuperSleep(frameTime - diff); - } - } - if (dumpTimings && !paused) { CModel3 *M = dynamic_cast(Model3); @@ -1384,6 +1402,7 @@ config.Set("QuadRendering", false); config.Set("XResolution", "496"); config.Set("YResolution", "384"); + config.Set("RefreshRate", "57524"); config.Set("FullScreen", false); config.Set("WideScreen", false); config.Set("Stretch", false); @@ -1446,10 +1465,11 @@ puts(" -fullscreen Full screen mode"); puts(" -wide-screen Expand 3D field of view to screen width"); puts(" -stretch Fit viewport to resolution, ignoring aspect ratio"); - puts(" -no-throttle Disable 60 Hz frame rate lock"); - puts(" -vsync Lock to vertical refresh rate [Default]"); - puts(" -no-vsync Do not lock to vertical refresh rate"); - puts(" -show-fps Display frame rate in window title bar"); + puts(" -no-throttle Disable frame rate lock"); + puts(" -vsync Lock to display's vertical refresh rate [Default]"); + puts(" -no-vsync Do not lock to display's vertical refresh rate"); + printf(" -refresh-rate= Set emulated vertical refresh rate in mHz (millihertz) [Default: %d]\n", defaultConfig["RefreshRate"].ValueAs()); + puts(" -show-fps Display emulated 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"); puts(" -new3d New 3D engine by Ian Curtis [Default]"); @@ -1530,6 +1550,7 @@ { "-frag-shader-fog", "FragmentShaderFog" }, { "-vert-shader-2d", "VertexShader2D" }, { "-frag-shader-2d", "FragmentShader2D" }, + { "-refresh-rate", "RefreshRate" }, { "-sound-volume", "SoundVolume" }, { "-music-volume", "MusicVolume" }, { "-balance", "Balance" },