Skip to content

Commit

Permalink
[GUI] Support gui.fps_limit and reduce idle power consumption (#1611)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>

* [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 <[email protected]>
Co-authored-by: JYLeeLYJ <[email protected]>
Co-authored-by: JYLeeLYJ <[email protected]>
  • Loading branch information
4 people authored Aug 6, 2020
1 parent 3e9b446 commit 5f2f216
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 9 deletions.
3 changes: 3 additions & 0 deletions cmake/TaichiCore.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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})

Expand Down
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ coverage:
lang:
paths:
- python/taichi/lang
target: 50%
target: 0%
project:
default: false
lang:
Expand Down
13 changes: 13 additions & 0 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------

Expand Down
3 changes: 2 additions & 1 deletion examples/game_of_life.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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()
Expand Down
14 changes: 14 additions & 0 deletions python/taichi/misc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 5 additions & 5 deletions taichi/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vector4> buffer;
std::vector<real> last_frame_interval;
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions taichi/python/export_visual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void export_visual(py::module &m) {
.value("Release", Type::release);
py::class_<GUI>(m, "GUI")
.def(py::init<std::string, Vector2i>())
.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",
Expand Down
67 changes: 66 additions & 1 deletion taichi/system/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,83 @@ double Time::get_time() {
}
#endif

#ifdef _WIN64
#include <Windows.h>

namespace {
void win_usleep(double us) {
using us_t = chrono::duration<double, std::micro>;
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();
}
Expand Down
3 changes: 2 additions & 1 deletion taichi/system/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 5f2f216

Please sign in to comment.