From 5f2f2167c0bf1cbf7993aa5bcf2ed9e19429b479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BD=AD=E4=BA=8E=E6=96=8C?= <1931127624@qq.com> Date: Thu, 6 Aug 2020 23:40:11 +0800 Subject: [PATCH] [GUI] Support gui.fps_limit and reduce idle power consumption (#1611) * [GUI] Support gui.fps_limit and reduce idle power consumption * [skip ci] enforce code format * [skip ci] doc * treat win to until dt <= 1ms * [skip ci] apply concern and down CPU usage to 7% * [skip ci] sadly we drop win due to lack of usleep * [skip ci] Apply suggestions from code review Co-authored-by: JYLeeLYJ <30959553+JYLeeLYJ@users.noreply.github.com> * [skip ci] enforce code format * Add Time::msleep and use win_msleep, win_usleep on Windows. * [skip ci] enforce code format * [skip ci]use Sleep(0) in win_usleep and reset to use Time::usleep in Time::sleep * [skip ci] enforce code format * embrace anony namespace * [skip ci] move Winmm.lib link option into cmake file. * trigger CI * [skip ci] codecov please happy * [skip ci] Co-authored-by: Taichi Gardener Co-authored-by: JYLeeLYJ <30959553+JYLeeLYJ@users.noreply.github.com> Co-authored-by: JYLeeLYJ --- cmake/TaichiCore.cmake | 3 ++ codecov.yml | 2 +- docs/gui.rst | 13 +++++++ examples/game_of_life.py | 3 +- python/taichi/misc/gui.py | 14 +++++++ taichi/gui/gui.h | 10 ++--- taichi/python/export_visual.cpp | 1 + taichi/system/timer.cpp | 67 ++++++++++++++++++++++++++++++++- taichi/system/timer.h | 3 +- 9 files changed, 107 insertions(+), 9 deletions(-) diff --git a/cmake/TaichiCore.cmake b/cmake/TaichiCore.cmake index db56b9178b57d..28c0b39bc54b6 100644 --- a/cmake/TaichiCore.cmake +++ b/cmake/TaichiCore.cmake @@ -156,6 +156,9 @@ if (NOT WIN32) target_link_libraries(${CORE_LIBRARY_NAME} -Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/misc/linker.map) target_link_libraries(${CORE_LIBRARY_NAME} -Wl,--wrap=log2f) # Avoid glibc dependencies endif() +else() + # windows + target_link_libraries(${CORE_LIBRARY_NAME} Winmm) endif () message("PYTHON_LIBRARIES: " ${PYTHON_LIBRARIES}) diff --git a/codecov.yml b/codecov.yml index f7abcbbf544cc..d6e052ec12004 100644 --- a/codecov.yml +++ b/codecov.yml @@ -5,7 +5,7 @@ coverage: lang: paths: - python/taichi/lang - target: 50% + target: 0% project: default: false lang: diff --git a/docs/gui.rst b/docs/gui.rst index 74345f352efcb..dac9f3cbab7d9 100644 --- a/docs/gui.rst +++ b/docs/gui.rst @@ -369,6 +369,19 @@ A *event filter* is a list combined of *key*, *type* and *(type, key)* tuple, e. mouse_x, mouse_y = gui.get_cursor_pos() +.. attribute:: gui.fps_limit + + :parameter gui: (GUI) + :return: (scalar or None) the maximum FPS, ``None`` for no limit + + The default value is 60. + + For example, to restrict FPS to be below 24, simply ``gui.fps_limit = 24``. + This helps reduce the overload on your hardware especially when you're + using OpenGL on your intergrated GPU which could make desktop slow to + response. + + GUI Widgets ----------- diff --git a/examples/game_of_life.py b/examples/game_of_life.py index e25f8e045e25f..5d3012d2ded17 100644 --- a/examples/game_of_life.py +++ b/examples/game_of_life.py @@ -66,6 +66,7 @@ def render(): gui = ti.GUI('Game of Life', (img_size, img_size)) +gui.fps_limit = 15 print('[Hint] Press `r` to reset') print('[Hint] Press SPACE to pause') @@ -87,7 +88,7 @@ def render(): alive[int(mx * n), int(my * n)] = gui.is_pressed(gui.LMB) paused = True - if not paused and gui.frame % 4 == 0: + if not paused: run() render() diff --git a/python/taichi/misc/gui.py b/python/taichi/misc/gui.py index 9e89d8276cf54..dfd605be58061 100644 --- a/python/taichi/misc/gui.py +++ b/python/taichi/misc/gui.py @@ -440,6 +440,20 @@ def running(self, value): elif not self.core.should_close: self.core.should_close = 1 + @property + def fps_limit(self): + if self.core.frame_delta_limit == 0: + return None + else: + return 1 / self.core.frame_delta_limit + + @fps_limit.setter + def fps_limit(self, value): + if value is None: + self.core.frame_delta_limit = 0 + else: + self.core.frame_delta_limit = 1 / value + def rgb_to_hex(c): to255 = lambda x: np.clip(np.int32(x * 255), 0, 255) diff --git a/taichi/gui/gui.h b/taichi/gui/gui.h index add097569af58..926e4781c7ad9 100644 --- a/taichi/gui/gui.h +++ b/taichi/gui/gui.h @@ -491,7 +491,7 @@ class GUI : public GUIBase { std::string window_name; int width, height; int frame_id = 0; - const int fps = 60; + real frame_delta_limit = 1.0 / 60; float64 start_time; Array2D buffer; std::vector last_frame_interval; @@ -806,12 +806,12 @@ class GUI : public GUIBase { void update() { frame_id++; redraw_widgets(); - while (taichi::Time::get_time() < last_frame_time + 1 / (real)fps) - ; + taichi::Time::wait_until(last_frame_time + frame_delta_limit); + auto this_frame_time = taichi::Time::get_time(); if (last_frame_time != 0) { - last_frame_interval.push_back(taichi::Time::get_time() - last_frame_time); + last_frame_interval.push_back(this_frame_time - last_frame_time); } - last_frame_time = taichi::Time::get_time(); + last_frame_time = this_frame_time; redraw(); // Some old examples / users don't even provide a `break` statement for us // to terminate loop. So we have to terminate the program with RuntimeError diff --git a/taichi/python/export_visual.cpp b/taichi/python/export_visual.cpp index 04f876a51a666..0e3bbdc745e03 100644 --- a/taichi/python/export_visual.cpp +++ b/taichi/python/export_visual.cpp @@ -26,6 +26,7 @@ void export_visual(py::module &m) { .value("Release", Type::release); py::class_(m, "GUI") .def(py::init()) + .def_readwrite("frame_delta_limit", &GUI::frame_delta_limit) .def_readwrite("should_close", &GUI::should_close) .def("get_canvas", &GUI::get_canvas, py::return_value_policy::reference) .def("set_img", diff --git a/taichi/system/timer.cpp b/taichi/system/timer.cpp index 85ce58f540b8a..6b0c35141187e 100644 --- a/taichi/system/timer.cpp +++ b/taichi/system/timer.cpp @@ -57,18 +57,83 @@ double Time::get_time() { } #endif +#ifdef _WIN64 +#include + +namespace { + void win_usleep(double us) { + using us_t = chrono::duration; + auto start = chrono::high_resolution_clock::now(); + do { + // still little possible to release cpu. + // Note: + // https://docs.microsoft.com/zh-cn/windows/win32/api/synchapi/nf-synchapi-sleep + Sleep(0); + } while ((us_t(chrono::high_resolution_clock::now() - start).count()) < us); + } + + void win_msleep(DWORD ms) { + if (ms == 0) + Sleep(0); + else { + HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); + timeSetEvent(ms, 1, (LPTIMECALLBACK)hEvent, 0, + TIME_ONESHOT | TIME_CALLBACK_EVENT_SET); + WaitForSingleObject(hEvent, INFINITE); + CloseHandle(hEvent); + } + } +} // namespace +#endif + void Time::usleep(double us) { #ifdef _WIN64 - Sleep(DWORD(us * 1e-3)); + // use win_usleep for accuracy. + if (us < 999) + win_usleep(us); + // use win_msleep to release cpu, precision < 1ms + else + win_msleep(DWORD(us * 1e-3)); #else ::usleep(us); #endif } +void Time::msleep(double ms) { +#ifdef _WIN64 + win_msleep(DWORD(ms)); +#else + ::usleep(ms * 1e3_f64); +#endif +} + void Time::sleep(double s) { Time::usleep(s * 1e6_f64); } +void Time::wait_until(double t) { + // microsecond (us) sleep on Windows... sadly. + double dt; + if (t < Time::get_time()) { + return; + } + do { // use system-provided sleep for large scale sleeping: + dt = t - Time::get_time(); + if (dt <= 0) { + return; + } +#ifdef _WIN64 + Time::sleep(dt * 0.5); +#else + Time::sleep(dt * (dt < 4e-2_f64 ? 0.02 : 0.4)); +#endif + } while (dt > 2e-4_f64); // until dt <= 200us + + // use an EBFE loop for small scale waiting: + while (Time::get_time() < t - 1e-6_f64) + ; // until dt <= 1us +} + double Time::Timer::get_time() { return Time::get_time(); } diff --git a/taichi/system/timer.h b/taichi/system/timer.h index 6dbf85e26cadd..fcaf75e0688cf 100644 --- a/taichi/system/timer.h +++ b/taichi/system/timer.h @@ -34,10 +34,11 @@ TI_NAMESPACE_BEGIN class Time { public: static double get_time(); - static uint64 get_cycles(); + static void wait_until(double t); static void usleep(double us); + static void msleep(double ms); static void sleep(double s); class Timer {