From 8ab5fd9b406408fdab50a52a37871aeb69fb326a Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 11 Feb 2021 18:19:55 +0100 Subject: [PATCH] Fix frame rate limit Measure time at the computation end but before sleep. This allows to adjust sleep interval for the next frame in case sleep is not precise due to syscall overhead or too low timer resolution. Remove old frame limiting mechanism. --- apps/openmw/engine.cpp | 14 ++++--- apps/openmw/mwbase/environment.cpp | 15 ------- apps/openmw/mwbase/environment.hpp | 1 - apps/openmw/mwgui/windowmanagerimp.cpp | 15 ++++--- components/misc/frameratelimiter.hpp | 56 ++++++++++++++++++++++++++ 5 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 components/misc/frameratelimiter.hpp diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 0e4cf762ac..d7c315323d 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -38,6 +38,8 @@ #include +#include + #include "mwinput/inputmanagerimp.hpp" #include "mwgui/windowmanagerimp.hpp" @@ -918,13 +920,15 @@ void OMW::Engine::go() } // Start the main rendering loop - osg::Timer frameTimer; double simulationTime = 0.0; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(mEnvironment.getFrameRateLimit()); + const std::chrono::steady_clock::duration maxSimulationInterval(std::chrono::milliseconds(200)); while (!mViewer->done() && !mEnvironment.getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); - dt = std::min(dt, 0.2); + const double dt = std::chrono::duration_cast>(std::min( + frameRateLimiter.getLastFrameDuration(), + maxSimulationInterval + )).count(); mViewer->advance(simulationTime); @@ -960,7 +964,7 @@ void OMW::Engine::go() } } - mEnvironment.limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } // Save user settings diff --git a/apps/openmw/mwbase/environment.cpp b/apps/openmw/mwbase/environment.cpp index 9014fafff9..b7235edd4b 100644 --- a/apps/openmw/mwbase/environment.cpp +++ b/apps/openmw/mwbase/environment.cpp @@ -1,8 +1,6 @@ #include "environment.hpp" #include -#include -#include #include @@ -98,19 +96,6 @@ float MWBase::Environment::getFrameRateLimit() const return mFrameRateLimit; } -void MWBase::Environment::limitFrameRate(double dt) const -{ - if (mFrameRateLimit > 0.f) - { - double thisFrameTime = dt; - double minFrameTime = 1.0 / static_cast(mFrameRateLimit); - if (thisFrameTime < minFrameTime) - { - std::this_thread::sleep_for(std::chrono::duration(minFrameTime - thisFrameTime)); - } - } -} - MWBase::World *MWBase::Environment::getWorld() const { assert (mWorld); diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index 7871153cc5..3b57e4e7c1 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -83,7 +83,6 @@ namespace MWBase void setFrameRateLimit(float frameRateLimit); float getFrameRateLimit() const; - void limitFrameRate(double dt) const; World *getWorld() const; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index bfce908103..7da8cb7bd8 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -50,6 +50,7 @@ #include #include +#include #include "../mwbase/inputmanager.hpp" #include "../mwbase/statemanager.hpp" @@ -710,12 +711,11 @@ namespace MWGui if (block) { - osg::Timer frameTimer; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mMessageBoxManager->readPressedButton(false) == -1 && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); + const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); mKeyboardNavigation->onFrame(); mMessageBoxManager->onFrame(dt); @@ -734,7 +734,7 @@ namespace MWGui // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } } } @@ -1750,11 +1750,10 @@ namespace MWGui ~MWSound::Type::Movie & MWSound::Type::Mask ); - osg::Timer frameTimer; + Misc::FrameRateLimiter frameRateLimiter = Misc::makeFrameRateLimiter(MWBase::Environment::get().getFrameRateLimit()); while (mVideoWidget->update() && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) { - double dt = frameTimer.time_s(); - frameTimer.setStartTick(); + const double dt = std::chrono::duration_cast>(frameRateLimiter.getLastFrameDuration()).count(); MWBase::Environment::get().getInputManager()->update(dt, true, false); @@ -1777,7 +1776,7 @@ namespace MWGui // refer to the advance() and frame() order in Engine::go() mViewer->advance(mViewer->getFrameStamp()->getSimulationTime()); - MWBase::Environment::get().limitFrameRate(frameTimer.time_s()); + frameRateLimiter.limit(); } mVideoWidget->stop(); diff --git a/components/misc/frameratelimiter.hpp b/components/misc/frameratelimiter.hpp new file mode 100644 index 0000000000..b8e2101651 --- /dev/null +++ b/components/misc/frameratelimiter.hpp @@ -0,0 +1,56 @@ +#ifndef OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H +#define OPENMW_COMPONENTS_MISC_FRAMERATELIMITER_H + +#include +#include + +namespace Misc +{ + class FrameRateLimiter + { + public: + template + explicit FrameRateLimiter(std::chrono::duration maxFrameDuration, + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) + : mMaxFrameDuration(std::chrono::duration_cast(maxFrameDuration)) + , mLastMeasurement(now) + {} + + std::chrono::steady_clock::duration getLastFrameDuration() const + { + return mLastFrameDuration; + } + + void limit(std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now()) + { + const auto passed = now - mLastMeasurement; + const auto left = mMaxFrameDuration - passed; + if (left > left.zero()) + { + std::this_thread::sleep_for(left); + mLastMeasurement = now + left; + mLastFrameDuration = mMaxFrameDuration; + } + else + { + mLastMeasurement = now; + mLastFrameDuration = passed; + } + } + + private: + std::chrono::steady_clock::duration mMaxFrameDuration; + std::chrono::steady_clock::time_point mLastMeasurement; + std::chrono::steady_clock::duration mLastFrameDuration; + }; + + inline Misc::FrameRateLimiter makeFrameRateLimiter(float frameRateLimit) + { + if (frameRateLimit > 0.0f) + return Misc::FrameRateLimiter(std::chrono::duration(1.0f / frameRateLimit)); + else + return Misc::FrameRateLimiter(std::chrono::steady_clock::duration::zero()); + } +} + +#endif