Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GUI] Support gui.fps_limit and reduce idle power consumption #1611

Merged
merged 20 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/gui.rst
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,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 @@ -414,6 +414,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
21 changes: 21 additions & 0 deletions taichi/system/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,27 @@ void Time::sleep(double s) {
Time::usleep(s * 1e6_f64);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a mush more accurate milliseconds level sleep implementation instead of Sleep(DWORD(us * 1e-3)) on windows .

#ifdef _WIN64

#include <Windows.h>
#pragma comment(lib, "Winmm.lib")

void win_sleep(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);
  }
}
#endif

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds cool! But is that possible to sleep for microseconds (us) on Windows?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds cool! But is that possible to sleep for microseconds (us) on Windows?

It's sad truth that sleep accuracy can not be guaranteed when delay time is lower than 1ms :(
The only possible approach is to use high resolution clock to continually counting in a for loop , but it 's unable to release CPU time slice ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sleeping for us seems tricky on win, how about just:

#ifndef _WIN64
} while (dt > 1e-4_f64);  // until dt <= 100us
#else
} while (dt > 1e-2_f64);  // until dt <= 10ms
#endif

Copy link
Contributor

@JYLeeLYJ JYLeeLYJ Jul 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1ms is more accurate ( 1/60 = 16.6 ms , 1/120 = 8.3ms , 1/40 = 25 ms )

#else
} while (dt > 1e-3_f64);  // until dt <= 1ms
#endif


void Time::wait_until(double t) {
#ifndef _WIN64 // we can't release CPU slice due to the lack of
archibate marked this conversation as resolved.
Show resolved Hide resolved
// 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;
}
Time::sleep(dt * (dt < 4e-2_f64 ? 0.02 : 0.4));
Copy link
Contributor

@JYLeeLYJ JYLeeLYJ Aug 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is any difference here ( using 0.02 or 0.4 )?
Why not just use :

Suggested change
Time::sleep(dt * (dt < 4e-2_f64 ? 0.02 : 0.4));
Time::sleep(dt * 0.4);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that the dt of sleep is more accurate when small; without this, I found the FPS to be around 59; with this, I found the FPS to be accurately 60.00.

} while (dt > 2e-4_f64); // until dt <= 200us
#endif
archibate marked this conversation as resolved.
Show resolved Hide resolved

// 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
2 changes: 1 addition & 1 deletion taichi/system/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ 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 sleep(double s);
Expand Down