diff --git a/core/config/engine.h b/core/config/engine.h
index 7e617d8773a..ddb11116d1f 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -62,7 +62,9 @@ private:
int ips = 60;
double physics_jitter_fix = 0.5;
- double _fps = 1;
+ // Assume to be rendering at 60 FPS at first, so that the FPS counter can
+ // smoothly interpolate from that value (which is more realistic than going up from 1 FPS).
+ double _fps = 60;
int _max_fps = 0;
int _audio_output_latency = 0;
double _time_scale = 1.0;
diff --git a/core/math/math_defs.h b/core/math/math_defs.h
index fe53121a038..cb0b9a7067b 100644
--- a/core/math/math_defs.h
+++ b/core/math/math_defs.h
@@ -56,7 +56,7 @@
#define UNIT_EPSILON 0.001
#endif
-#define USEC_TO_SEC(m_usec) ((m_usec) / 1000000.0)
+#define USEC_TO_SEC(m_usec) ((m_usec) * 0.000'001)
enum ClockDirection {
CLOCKWISE,
diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml
index ca78054875b..b85bf2c3e49 100644
--- a/doc/classes/Engine.xml
+++ b/doc/classes/Engine.xml
@@ -64,7 +64,8 @@
- Returns the average frames rendered every second (FPS), also known as the framerate.
+ Returns the average frames rendered every second (FPS), also known as the framerate. This uses an averaging algorithm to smooth out the reported FPS over (roughly) the last second.ยจ
+ [b]Note:[/b] Comparing performance is usually better done using [i]milliseconds per frame[/i] (mspf) instead of [i]frames per second[/i], as mspf measurements are linear across the entire spectrum of values. To get the milliseconds per frame, use [code]1000.0 / Engine.get_frames_per_second()[/code].
diff --git a/main/main.cpp b/main/main.cpp
index e1d53e7f1be..ea35cadb07b 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -4141,7 +4141,13 @@ bool Main::iteration() {
frames++;
Engine::get_singleton()->_process_frames++;
- if (frame > 1000000) {
+ // Smoothly update FPS over time, while ensuring the speed at which FPS stabilizes is not dependent on FPS.
+ // The FPS smooth factor is tuned to always *mostly* converge within a second.
+ constexpr int FPS_SMOOTH_FACTOR = 5;
+ const double ratio = MIN(1.0, FPS_SMOOTH_FACTOR * USEC_TO_SEC(ticks_elapsed));
+ Engine::get_singleton()->_fps = Engine::get_singleton()->_fps * (1 - ratio) + ratio * 1'000'000.0 / ticks_elapsed;
+
+ if (frame > 1'000'000) {
// Wait a few seconds before printing FPS, as FPS reporting just after the engine has started is inaccurate.
if (hide_print_fps_attempts == 0) {
if (editor || project_manager) {
@@ -4155,7 +4161,6 @@ bool Main::iteration() {
hide_print_fps_attempts--;
}
- Engine::get_singleton()->_fps = frames;
performance->set_process_time(USEC_TO_SEC(process_max));
performance->set_physics_process_time(USEC_TO_SEC(physics_process_max));
performance->set_navigation_process_time(USEC_TO_SEC(navigation_process_max));
@@ -4163,7 +4168,7 @@ bool Main::iteration() {
physics_process_max = 0;
navigation_process_max = 0;
- frame %= 1000000;
+ frame %= 1'000'000;
frames = 0;
}
diff --git a/main/performance.cpp b/main/performance.cpp
index 8eda697b16d..3c66a40ba2e 100644
--- a/main/performance.cpp
+++ b/main/performance.cpp
@@ -150,7 +150,7 @@ String Performance::get_monitor_name(Monitor p_monitor) const {
double Performance::get_monitor(Monitor p_monitor) const {
switch (p_monitor) {
case TIME_FPS:
- return Engine::get_singleton()->get_frames_per_second();
+ return Math::round(Engine::get_singleton()->get_frames_per_second());
case TIME_PROCESS:
return _process_time;
case TIME_PHYSICS_PROCESS: