diff --git a/src/Magnum/Platform/GlfwApplication.cpp b/src/Magnum/Platform/GlfwApplication.cpp index e1d635775..980a763df 100644 --- a/src/Magnum/Platform/GlfwApplication.cpp +++ b/src/Magnum/Platform/GlfwApplication.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include "Magnum/ImageView.h" #include "Magnum/PixelFormat.h" @@ -56,9 +57,10 @@ 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, + NoTickEvent = 1 << 2, + Exit = 1 << 3, #ifdef CORRADE_TARGET_APPLE - HiDpiWarningPrinted = 1 << 3 + HiDpiWarningPrinted = 1 << 4 #endif }; @@ -75,7 +77,7 @@ GlfwApplication::GlfwApplication(const Arguments& arguments, const Configuration #endif GlfwApplication::GlfwApplication(const Arguments& arguments, NoCreateT): - _flags{Flag::Redraw} + _minimalLoopPeriod{0}, _flags{Flag::Redraw} { Utility::Arguments args{Implementation::windowScalingArguments()}; #ifdef MAGNUM_TARGET_GL @@ -762,15 +764,36 @@ 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. */ - if (mainLoopDrawEventIteration()) - glfwPollEvents(); - else - glfwWaitEvents(); + const UnsignedInt timeBefore = _minimalLoopPeriod ? glfwGetTime() : 0; - return !glfwWindowShouldClose(_window); + glfwPollEvents(); + + /* Tick event */ + if(!(_flags & Flag::NoTickEvent)) tickEvent(); + + /* drawEvent() was called */ + if (mainLoopDrawEventIteration()) { + /* delay to prevent CPU hogging (if set) */ + if(!(_minimalLoopPeriod) { + const UnsignedInt loopTime = glfwGetTime() - timeBefore; + if(loopTime < _minimalLoopPeriod) + Utility::System::sleep(_minimalLoopPeriod - loopTime); + } + return !glfwWindowShouldClose(_window); + } + + /* If not drawing anything, delay to prevent CPU hogging (if set) */ + if(_minimalLoopPeriod) { + const UnsignedInt loopTime = glfwGetTime() - timeBefore; + if(loopTime < _minimalLoopPeriod) + Utility::System::sleep(_minimalLoopPeriod - loopTime); + } + + /* 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) { @@ -871,6 +894,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 5fcae13ce..048fb758b 100644 --- a/src/Magnum/Platform/GlfwApplication.h +++ b/src/Magnum/Platform/GlfwApplication.h @@ -543,6 +543,21 @@ class GlfwApplication { */ void setSwapInterval(Int interval); + /** + * @brief Set minimal loop period + * + * This setting reduces the main loop frequency in case VSync is + * not/cannot be enabled or no drawing is done. Default is @cpp 0 @ce + * (i.e. looping at maximum frequency). If the application is drawing + * on the screen and VSync is enabled, this setting is ignored. + * @note Not available in @ref CORRADE_TARGET_EMSCRIPTEN "Emscripten", + * the browser is managing the frequency instead. + * @see @ref setSwapInterval() + */ + void setMinimalLoopPeriod(UnsignedInt milliseconds) { + _minimalLoopPeriod = milliseconds; + } + /** @copydoc Sdl2Application::redraw() */ void redraw(); @@ -753,6 +768,23 @@ class GlfwApplication { * @} */ + protected: + /** + * @brief Tick event + * + * 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; @@ -778,6 +810,7 @@ class GlfwApplication { Vector2 _dpiScaling; GLFWwindow* _window{nullptr}; + UnsignedInt _minimalLoopPeriod; Flags _flags; #ifdef MAGNUM_TARGET_GL /* Has to be in an Optional because we delay-create it in a constructor