Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg +++ ps/trunk/binaries/data/config/default.cfg @@ -125,6 +125,9 @@ ; 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 @@ [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 Index: ps/trunk/binaries/data/mods/public/gui/options/options.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.js +++ ps/trunk/binaries/data/mods/public/gui/options/options.js @@ -171,6 +171,7 @@ if (callbackFunction) Engine[callbackFunction](+this.value); updateOptionPanel(); + control.tooltip = this.value; }; }(key, callbackFunction, minvalue, maxvalue); Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json +++ ps/trunk/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": Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp +++ ps/trunk/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 @@ #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 @@ 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 @@ 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 @@ // 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 @@ 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 @@ g_Console->Update(realTimeSinceLastFrame); ogl_WarnIfError(); - if(need_render) + if (need_render) { Render(); @@ -363,6 +376,8 @@ g_Profiler.Frame(); g_GameRestarted = false; + + LimitFPS(); }