Skip to content

Commit 31b8c0f

Browse files
committed
Added a EMA convenience class & FPS counter to example1
1 parent 73702c4 commit 31b8c0f

File tree

9 files changed

+120
-8
lines changed

9 files changed

+120
-8
lines changed

include/nanogui/ema.h

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#pragma once
2+
3+
#include <nanogui/common.h>
4+
#include <cmath>
5+
6+
NAMESPACE_BEGIN(nanogui)
7+
8+
/// Exponentially weighted moving average accumulator with bias correction
9+
template <typename Value> class EMA {
10+
public:
11+
/// Construct with given weight for old samples
12+
/// The default weight (0.983) results in a ~1 second time constant at 60 FPS
13+
EMA(Value weight = 0.983f)
14+
: m_weights{weight, 1.f-weight}, m_value(0.f),
15+
m_sample_count(0) {
16+
if (weight < 0.f || weight >= 1.f)
17+
throw std::invalid_argument("Weight must be in range [0, 1)");
18+
}
19+
20+
/// Reset the accumulator to initial state
21+
void reset() { m_value = 0.f; m_sample_count = 0; }
22+
23+
/// Add a new sample to the accumulator
24+
void put(Value sample) {
25+
m_value = std::fma(m_weights[0], m_value, sample * m_weights[1]);
26+
m_sample_count++;
27+
}
28+
29+
/// Get the bias-corrected accumulated value
30+
Value value() const {
31+
if (m_sample_count == 0)
32+
return 0.f;
33+
return m_value / (1.f - std::pow(m_weights[0], m_sample_count));
34+
}
35+
36+
/// Get the current weight
37+
Value weight() const { return m_weights[0]; }
38+
39+
/// Get the number of samples accumulated
40+
size_t sample_count() const { return m_sample_count; }
41+
42+
private:
43+
Value m_weights[2];
44+
Value m_value;
45+
size_t m_sample_count;
46+
};
47+
48+
NAMESPACE_END(nanogui)

include/nanogui/screen.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <nanogui/widget.h>
1818
#include <nanogui/texture.h>
1919
#include <nanogui/colorpass.h>
20+
#include <nanogui/ema.h>
2021

2122
NAMESPACE_BEGIN(nanogui)
2223

@@ -220,6 +221,12 @@ class NANOGUI_EXPORT Screen : public Widget {
220221
/// Does the framebuffer use a floating point representation
221222
bool has_float_buffer() const { return m_float_buffer; }
222223

224+
/// Get the index of the last (or current) frame being rendered
225+
uint64_t frame_index() const { return m_frame_index; }
226+
227+
/// Get a smoothed estimate of the rendering time per frame (second-based)
228+
double frame_time() const { return m_frame_timer.value(); }
229+
223230
/// Does the screen apply color management as a post processing shader?
224231
bool applies_color_management() const {
225232
#if defined(NANOGUI_USE_METAL)
@@ -316,6 +323,7 @@ class NANOGUI_EXPORT Screen : public Widget {
316323
bool m_drag_active;
317324
Widget *m_drag_widget = nullptr;
318325
double m_last_interaction;
326+
double m_last_draw;
319327
Color m_background;
320328
std::string m_caption;
321329
bool m_shutdown_glfw;
@@ -332,6 +340,8 @@ class NANOGUI_EXPORT Screen : public Widget {
332340
ref<Texture> m_depth_stencil_texture;
333341
ref<RestartableTimer> m_tooltip_timer;
334342
bool m_tooltip_force_visible = false;
343+
EMA<double> m_frame_timer;
344+
uint64_t m_frame_index;
335345
#if defined(NANOGUI_USE_METAL)
336346
void *m_metal_texture = nullptr;
337347
void *m_metal_drawable = nullptr;

src/example1.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,12 @@ class ExampleApplication : public Screen {
572572
m_shader->end();
573573

574574
m_render_pass->end();
575+
576+
if (m_frame_index % 60 == 59) {
577+
char caption[128];
578+
snprintf(caption, 128, "NanoGUI test (%.2f FPS)", 1.f / m_frame_timer.value());
579+
set_caption(caption);
580+
}
575581
}
576582
private:
577583
ProgressBar *m_progress;
@@ -591,7 +597,7 @@ int main(int /* argc */, char ** /* argv */) {
591597
ref<ExampleApplication> app = new ExampleApplication();
592598
app->dec_ref();
593599
app->set_visible(true);
594-
nanogui::run(RunMode::Eager);
600+
nanogui::run(RunMode::VSync);
595601
}
596602

597603
nanogui::shutdown();

src/python/ema.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#ifdef NANOGUI_PYTHON
2+
3+
#include "python.h"
4+
#include <nanogui/ema.h>
5+
6+
void register_ema(nb::module_ &m) {
7+
nb::class_<EMA>(m, "EMA", D(EMA))
8+
.def(nb::init<float>(), "weight"_a = 0.983f, D(EMA, EMA))
9+
.def("reset", &EMA::reset, D(EMA, reset))
10+
.def("put", &EMA::put, "sample"_a, D(EMA, put))
11+
.def("value", &EMA::value, D(EMA, value))
12+
.def("weight", &EMA::weight, D(EMA, weight))
13+
.def("sample_count", &EMA::sample_count, D(EMA, sample_count));
14+
}
15+
16+
#endif

src/python/example1.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import nanogui as ng
1313
import math
1414
import time
15-
import gc
1615
import numpy as np
1716

1817
from nanogui import glfw, icons
@@ -460,6 +459,9 @@ def draw_contents(self):
460459
with self.shader:
461460
self.shader.draw_array(ng.Shader.PrimitiveType.Triangle, 0, 6, True)
462461

462+
if self.frame_index() % 60 == 59:
463+
self.set_caption("NanoGUI test (%.2f FPS)" % (1 / self.frame_time()))
464+
463465

464466
def keyboard_event(self, key, scancode, action, modifiers):
465467
if super(TestApp, self).keyboard_event(key, scancode,
@@ -470,12 +472,12 @@ def keyboard_event(self, key, scancode, action, modifiers):
470472
return True
471473
return False
472474

473-
if __name__ == "__main__":
475+
def main():
474476
ng.init()
475477
test = TestApp()
476-
test.draw_all()
477478
test.set_visible(True)
478479
ng.run()
479-
del test
480-
gc.collect()
481480
ng.shutdown()
481+
482+
if __name__ == "__main__":
483+
main()

src/python/main.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extern void register_nanovg(nb::module_ &m);
2929
extern void register_render(nb::module_ &m);
3030
extern void register_quad(nb::module_ &m);
3131
extern void register_chroma(nb::module_ &m);
32+
extern void register_ema(nb::module_ &m);
3233

3334
#if defined(__APPLE__) || defined(__linux__)
3435
static void (*sigint_handler_prev)(int) = nullptr;

src/python/py_doc.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,20 @@ static const char *__doc_nanogui_Cursor_IBeam = R"doc(< The I-beam cursor.)doc";
864864

865865
static const char *__doc_nanogui_Cursor_VResize = R"doc(< The vertical resize cursor.)doc";
866866

867+
static const char *__doc_nanogui_EMA = R"doc(Exponentially weighted moving average accumulator with bias correction)doc";
868+
869+
static const char *__doc_nanogui_EMA_EMA = R"doc(Construct with given weight for old samples (default 0.983 = ~1 second at 60 FPS))doc";
870+
871+
static const char *__doc_nanogui_EMA_put = R"doc(Add a new sample to the accumulator)doc";
872+
873+
static const char *__doc_nanogui_EMA_reset = R"doc(Reset the accumulator to initial state)doc";
874+
875+
static const char *__doc_nanogui_EMA_sample_count = R"doc(Get the number of samples accumulated)doc";
876+
877+
static const char *__doc_nanogui_EMA_value = R"doc(Get the bias-corrected accumulated value)doc";
878+
879+
static const char *__doc_nanogui_EMA_weight = R"doc(Get the current weight)doc";
880+
867881
static const char *__doc_nanogui_FloatBox =
868882
R"doc(\class FloatBox textbox.h nanogui/textbox.h
869883
@@ -1896,6 +1910,12 @@ finally draw_teardown().
18961910
See also:
18971911
redraw)doc";
18981912

1913+
static const char *__doc_nanogui_Screen_frame_index =
1914+
"Get the index of the last (or current) frame being rendered";
1915+
1916+
static const char *__doc_nanogui_Screen_frame_time =
1917+
"Get a smoothed estimate of the rendering time per frame (second-based)";
1918+
18991919
static const char *__doc_nanogui_Screen_draw_contents =
19001920
R"doc(Calls clear() and draws the window contents --- put your rendering
19011921
code here.)doc";

src/python/widget.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ void register_widget(nb::module_ &m) {
238238
.def("nvg_flush", &Screen::nvg_flush, D(Screen, nvg_flush))
239239
.def("mouse_motion_event_f", &Screen::mouse_motion_event_f, "p"_a, "rel"_a,
240240
"button"_a, "modifiers"_a, D(Screen, mouse_motion_event_f))
241+
.def("frame_time", &Screen::frame_time, D(Screen, frame_time))
242+
.def("frame_index", &Screen::frame_index, D(Screen, frame_index))
241243
#if defined(NANOGUI_USE_METAL)
242244
.def("metal_layer", &Screen::metal_layer)
243245
.def("metal_texture", &Screen::metal_texture)

src/screen.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,9 @@ void Screen::initialize(GLFWwindow *window, bool shutdown_glfw) {
510510
m_mouse_pos = Vector2i(0);
511511
m_mouse_state = m_modifiers = 0;
512512
m_drag_active = false;
513-
m_last_interaction = glfwGetTime();
513+
m_last_interaction = 0.0;
514+
m_last_draw = 0.0;
515+
m_frame_index = 0;
514516
m_redraw = true;
515517
__nanogui_screens.emplace_back(m_glfw_window, this);
516518

@@ -761,6 +763,11 @@ void Screen::draw_all() {
761763
#if defined(NANOGUI_USE_METAL)
762764
void *pool = autorelease_init();
763765
#endif
766+
float current_time = glfwGetTime();
767+
if (m_last_draw != 0.0)
768+
m_frame_timer.put(current_time - m_last_draw);
769+
m_last_draw = current_time;
770+
m_frame_index++;
764771

765772
draw_setup();
766773
draw_contents();
@@ -801,7 +808,7 @@ void Screen::draw_tooltip() {
801808
return;
802809
} else {
803810
// Otherwise, decide based on timing information
804-
double elapsed = glfwGetTime() - m_last_interaction;
811+
double elapsed = m_last_draw - m_last_interaction;
805812
if (elapsed <= TOOLTIP_DELAY_SEC)
806813
return;
807814
}

0 commit comments

Comments
 (0)