From 4a5f302ce11748595e01b7890f66ab441f146446 Mon Sep 17 00:00:00 2001 From: elexis Date: Wed, 3 May 2017 23:44:13 +0000 Subject: [PATCH] Optional FPS Limiter for running games too, use the Slider GUI object in the config to specify custom limits. Make it way more accurate by excluding the SDL_Wait time and microsecond precision. Differential Revision: https://code.wildfiregames.com/D109 Refs #2882 Reviewed By: Vladislav This was SVN commit r19507. --- binaries/data/config/default.cfg | 6 +-- .../data/mods/public/gui/options/options.js | 1 + .../data/mods/public/gui/options/options.json | 14 +++-- source/main.cpp | 53 ++++++++++++------- 4 files changed, 48 insertions(+), 26 deletions(-) diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 444b6e62d1..4d2f84cde9 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -125,6 +125,9 @@ forcealphatest = false ; Color of the sky (in "r g b" format) skycolor = "0 0 0" +[adaptivefps] +session = 60 ; Throttle FPS in running games (prevents 100% CPU workload). +menu = 30 ; Throttle FPS in menus only. [hotkey] ; Each one of the specified keys will trigger the action on the left @@ -329,9 +332,6 @@ scale = 1.0 ; GUI scaling factor, for improved compatibili [gui.gamesetup] enabletips = true ; Enable/Disable tips during gamesetup (for newcomers) -[gui.menu] -limitfps = true ; Limit FPS in the menus and loading screen - [gui.session] camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one? timeelapsedcounter = false ; Show the game duration in the top right corner diff --git a/binaries/data/mods/public/gui/options/options.js b/binaries/data/mods/public/gui/options/options.js index 11054e0347..c51ab15292 100644 --- a/binaries/data/mods/public/gui/options/options.js +++ b/binaries/data/mods/public/gui/options/options.js @@ -171,6 +171,7 @@ function setupControl(option, i, category) if (callbackFunction) Engine[callbackFunction](+this.value); updateOptionPanel(); + control.tooltip = this.value; }; }(key, callbackFunction, minvalue, maxvalue); diff --git a/binaries/data/mods/public/gui/options/options.json b/binaries/data/mods/public/gui/options/options.json index 36b439fa9f..8d890d5fb8 100644 --- a/binaries/data/mods/public/gui/options/options.json +++ b/binaries/data/mods/public/gui/options/options.json @@ -205,10 +205,16 @@ "parameters": { "config": "vsync" } }, { - "type": "boolean", - "label": "Limit FPS in Menus", - "tooltip": "Limit FPS to 50 in all menus, to save power.", - "parameters": { "config": "gui.menu.limitfps" } + "type": "slider", + "label": "FPS throttling in menus", + "tooltip": "To save CPU workload, throttle render frequency in all menus. Set to maximum to disable throttling.", + "parameters": { "config": "adaptivefps.menu", "min": 20, "max": 100 } + }, + { + "type": "slider", + "label": "FPS throttling in games", + "tooltip": "To save CPU workload, throttle render frequency in running games. Set to maximum to disable throttling.", + "parameters": { "config": "adaptivefps.session", "min": 20, "max": 100 } } ], "soundSetting": diff --git a/source/main.cpp b/source/main.cpp index 6c2db5d43a..5015dab4bd 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -31,6 +31,8 @@ that of Atlas depending on commandline parameters. #define MINIMAL_PCH 2 #include "lib/precompiled.h" +#include + #include "lib/debug.h" #include "lib/status.h" #include "lib/secure_crt.h" @@ -92,6 +94,8 @@ void kill_mainloop(); static int g_ResizedW; static int g_ResizedH; +static std::chrono::high_resolution_clock::time_point lastFrameTime; + // main app message handler static InReaction MainInputHandler(const SDL_Event_* ev) { @@ -178,6 +182,31 @@ static void PumpEvents() g_TouchInput.Frame(); } +/** + * Optionally throttle the render frequency in order to + * prevent 100% workload of the currently used CPU core. + */ +inline static void LimitFPS() +{ + if (g_VSync) + return; + + double fpsLimit = 0.0; + CFG_GET_VAL(g_Game && g_Game->IsGameStarted() ? "adaptivefps.session" : "adaptivefps.menu", fpsLimit); + + // Keep in sync with options.json + if (fpsLimit < 20.0 || fpsLimit >= 100.0) + return; + + double wait = 1000.0 / fpsLimit - + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - lastFrameTime).count() / 1000.0; + + if (wait > 0.0) + SDL_Delay(wait); + + lastFrameTime = std::chrono::high_resolution_clock::now(); +} static int ProgressiveLoad() { @@ -275,7 +304,7 @@ static void Frame() // If we are not running a multiplayer game, disable updates when the game is // minimized or out of focus and relinquish the CPU a bit, in order to make // debugging easier. - if(g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus) + if (g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus) { PROFILE3("non-focus delay"); need_update = false; @@ -283,22 +312,6 @@ static void Frame() SDL_Delay(10); } - // Throttling: limit update and render frequency to the minimum to 50 FPS - // in the "inactive" state, so that other windows get enough CPU time, - // (and it's always nice for power+thermal management). - // TODO: when the game performance is high enough, implementing a limit for - // in-game framerate might be sensible. - const float maxFPSMenu = 50.0; - bool limit_fps = false; - CFG_GET_VAL("gui.menu.limitfps", limit_fps); - if (limit_fps && (!g_Game || !g_Game->IsGameStarted())) - { - float remainingFrameTime = (1000.0 / maxFPSMenu) - realTimeSinceLastFrame; - if (remainingFrameTime > 0) - SDL_Delay(remainingFrameTime); - } - - // this scans for changed files/directories and reloads them, thus // allowing hotloading (changes are immediately assimilated in-game). ReloadChangedFiles(); @@ -351,7 +364,7 @@ static void Frame() g_Console->Update(realTimeSinceLastFrame); ogl_WarnIfError(); - if(need_render) + if (need_render) { Render(); @@ -363,6 +376,8 @@ static void Frame() g_Profiler.Frame(); g_GameRestarted = false; + + LimitFPS(); }