diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index 551f9b41b..481b2e1ed 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -33,10 +33,12 @@ #include #include #include +#include #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" #include "Magnum/Math/ConfigurationValue.h" +#include "Magnum/Math/Time.h" #include "Magnum/Platform/ScreenedApplication.hpp" #include "Magnum/Platform/Implementation/DpiScaling.h" @@ -47,6 +49,7 @@ namespace Magnum { namespace Platform { using namespace Containers::Literals; +using namespace Math::Literals; #ifdef GLFW_TRUE /* The docs say that it's the same, verify that just in case */ @@ -56,9 +59,11 @@ static_assert(GLFW_TRUE == true && GLFW_FALSE == false, "GLFW does not have sane enum class GlfwApplication::Flag: UnsignedByte { Redraw = 1 << 0, TextInputActive = 1 << 1, - Exit = 1 << 2, + VSyncEnabled = 1 << 2, + NoTickEvent = 1 << 3, + Exit = 1 << 4, #ifdef CORRADE_TARGET_APPLE - HiDpiWarningPrinted = 1 << 3 + HiDpiWarningPrinted = 1 << 5 #endif }; @@ -737,6 +742,19 @@ Vector2 GlfwApplication::dpiScaling() const { void GlfwApplication::setSwapInterval(const Int interval) { glfwSwapInterval(interval); + + /* Remember whether VSync is enabled for mainLoopIteration() to use + minimal loop period or not. Unlike SDL2 where it's possible to check + whether the VSync was actually set, here it's purely hope-based. + Sorry. */ + if(interval) _flags |= Flag::VSyncEnabled; + else _flags &= ~Flag::VSyncEnabled; +} + +void GlfwApplication::setMinimalLoopPeriod(const Nanoseconds time) { + CORRADE_ASSERT(time >= 0_nsec, + "Platform::Sdl2Application::setMinimalLoopPeriod(): expected non-negative time, got" << time, ); + _minimalLoopPeriodNanoseconds = Long(time); } void GlfwApplication::redraw() { _flags |= Flag::Redraw; } @@ -776,16 +794,39 @@ bool GlfwApplication::mainLoopIteration() { */ if(glfwGetWindowUserPointer(_window) != this) setupCallbacks(); - /* If redrawing, poll for events immediately after drawEvent() (which could - be setting the Redraw flag again, thus doing constant redraw). If not, - avoid spinning the CPU by waiting for the next input event. */ + const Nanoseconds timeBefore = _minimalLoopPeriodNanoseconds ? glfwGetTime()*1.0_sec : Nanoseconds{}; + + glfwPollEvents(); + + /* Tick event */ + if(!(_flags & Flag::NoTickEvent)) tickEvent(); + + /* Draw event */ if(_flags & Flag::Redraw) { _flags &= ~Flag::Redraw; drawEvent(); - glfwPollEvents(); - } else glfwWaitEvents(); - return !glfwWindowShouldClose(_window); + /* If VSync is not enabled, delay to prevent CPU hogging (if set) */ + if(!(_flags & Flag::VSyncEnabled) && _minimalLoopPeriodNanoseconds) { + const Nanoseconds loopTime = glfwGetTime()*1.0_sec - timeBefore; + if(loopTime < _minimalLoopPeriodNanoseconds*1_nsec) + Utility::System::sleep((_minimalLoopPeriodNanoseconds*1_nsec - loopTime)/1.0_msec); + } + + return !(_flags & Flag::Exit || glfwWindowShouldClose(_window)); + } + + /* If not drawing anything, delay to prevent CPU hogging (if set) */ + if(_minimalLoopPeriodNanoseconds) { + const Nanoseconds loopTime = glfwGetTime()*1.0_sec - timeBefore; + if(loopTime < _minimalLoopPeriodNanoseconds*1_nsec) + Utility::System::sleep((_minimalLoopPeriodNanoseconds*1_nsec - loopTime)/1.0_msec); + } + + /* Then, if the tick event doesn't need to be called periodically, wait + indefinitely for next input event */ + if(_flags & Flag::NoTickEvent) glfwWaitEvents(); + return !(_flags & Flag::Exit || glfwWindowShouldClose(_window)); } void GlfwApplication::exit(int exitCode) { @@ -886,6 +927,12 @@ void GlfwApplication::exitEvent(ExitEvent& event) { event.setAccepted(); } +void GlfwApplication::tickEvent() { + /* If this got called, the tick event is not implemented by user and thus + we don't need to call it ever again */ + _flags |= Flag::NoTickEvent; +} + void GlfwApplication::viewportEvent(ViewportEvent&) {} void GlfwApplication::keyPressEvent(KeyEvent&) {} void GlfwApplication::keyReleaseEvent(KeyEvent&) {} diff --git a/src/Magnum/Platform/GlfwApplication.h b/src/Magnum/Platform/GlfwApplication.h index 830a90a64..4b575da24 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -529,6 +529,25 @@ class GlfwApplication { */ void setSwapInterval(Int interval); + /** + * @brief Set minimal loop period + * @m_since_latest + * + * This setting reduces the main loop frequency in case + * @ref setSwapInterval() wasn't called at all, was called with + * @cpp 0 @ce, or no drawing is done and just @ref tickEvent() is being + * executed. The @p time is expected to be non-negative, default is + * @cpp 0_nsec @ce (i.e., looping at maximum frequency). If the + * application is drawing on the screen and VSync was enabled by + * calling @ref setSwapInterval(), this setting is ignored. + * + * Note that as the VSync default is driver-dependent, + * @ref setSwapInterval() has to be explicitly called to make the + * interaction between the two work correctly. + * @see @ref setSwapInterval() + */ + void setMinimalLoopPeriod(Nanoseconds time); + /** @copydoc Sdl2Application::redraw() */ void redraw(); @@ -739,6 +758,24 @@ class GlfwApplication { * @} */ + protected: + /** + * @brief Tick event + * @m_since_latest + * + * If implemented, this function is called periodically after + * processing all input events and before draw event even though there + * might be no input events and redraw is not requested. Useful e.g. + * for asynchronous task polling. Use @ref setMinimalLoopPeriod() / + * @ref setSwapInterval() to control main loop frequency. + * + * If this implementation gets called from its @cpp override @ce, it + * will effectively stop the tick event from being fired and the app + * returns back to waiting for input events. This can be used to + * disable the tick event when not needed. + */ + virtual void tickEvent(); + private: enum class Flag: UnsignedByte; typedef Containers::EnumSet Flags; @@ -766,6 +803,8 @@ class GlfwApplication { Vector2 _commandLineDpiScaling, _configurationDpiScaling; GLFWwindow* _window{nullptr}; + /* Not using Nanoseconds as that would require including Time.h */ + UnsignedInt _minimalLoopPeriodNanoseconds; Flags _flags; #ifdef MAGNUM_TARGET_GL /* Has to be in an Optional because we delay-create it in a constructor diff --git a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp index 46ef974d5..7444f0391 100644 --- a/src/Magnum/Platform/Test/GlfwApplicationTest.cpp +++ b/src/Magnum/Platform/Test/GlfwApplicationTest.cpp @@ -30,6 +30,7 @@ #include #include "Magnum/ImageView.h" +#include "Magnum/Math/Time.h" #include "Magnum/Math/ConfigurationValue.h" #include "Magnum/Platform/GlfwApplication.h" #include "Magnum/Trade/AbstractImporter.h" @@ -159,6 +160,7 @@ Debug& operator<<(Debug& debug, const Application::KeyEvent::Key value) { } using namespace Containers::Literals; +using namespace Math::Literals; struct GlfwApplicationTest: Platform::Application { explicit GlfwApplicationTest(const Arguments& arguments); @@ -247,6 +249,16 @@ struct GlfwApplicationTest: Platform::Application { Debug{} << "text input event:" << event.text(); } + /* Uncomment to test the tick event. It should run at given minimal loop + period even if not redrawing, it should not run at a different period + when redrawing constantly. */ + #if 0 + void tickEvent() override { + setMinimalLoopPeriod(250.0_msec); + Debug{} << "tick event:" << Seconds{glfwGetTime()*1.0_sec}; + } + #endif + private: bool _redraw = false; bool _vsync = false;