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

Add an Engine method to get the 1% percentile low FPS #67136

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class Engine {
int ips = 60;
double physics_jitter_fix = 0.5;
double _fps = 1;
double _fps_1_percent_low = 1;
int _max_fps = 0;
int _audio_output_latency = 0;
double _time_scale = 1.0;
Expand Down Expand Up @@ -114,6 +115,7 @@ class Engine {
virtual int get_audio_output_latency() const;

virtual double get_frames_per_second() const { return _fps; }
virtual double get_frames_per_second_1_percent_low() const { return _fps_1_percent_low; }

uint64_t get_frames_drawn();

Expand Down
5 changes: 5 additions & 0 deletions core/core_bind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,10 @@ double Engine::get_frames_per_second() const {
return ::Engine::get_singleton()->get_frames_per_second();
}

double Engine::get_frames_per_second_1_percent_low() const {
return ::Engine::get_singleton()->get_frames_per_second_1_percent_low();
}

uint64_t Engine::get_physics_frames() const {
return ::Engine::get_singleton()->get_physics_frames();
}
Expand Down Expand Up @@ -1827,6 +1831,7 @@ void Engine::_bind_methods() {

ClassDB::bind_method(D_METHOD("get_frames_drawn"), &Engine::get_frames_drawn);
ClassDB::bind_method(D_METHOD("get_frames_per_second"), &Engine::get_frames_per_second);
ClassDB::bind_method(D_METHOD("get_frames_per_second_1_percent_low"), &Engine::get_frames_per_second_1_percent_low);
ClassDB::bind_method(D_METHOD("get_physics_frames"), &Engine::get_physics_frames);
ClassDB::bind_method(D_METHOD("get_process_frames"), &Engine::get_process_frames);

Expand Down
1 change: 1 addition & 0 deletions core/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,7 @@ class Engine : public Object {
int get_max_fps() const;

double get_frames_per_second() const;
double get_frames_per_second_1_percent_low() const;
uint64_t get_physics_frames() const;
uint64_t get_process_frames() const;

Expand Down
6 changes: 6 additions & 0 deletions doc/classes/Engine.xml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@
Returns the average frames rendered every second (FPS), also known as the framerate.
</description>
</method>
<method name="get_frames_per_second_1_percent_low" qualifiers="const">
<return type="float" />
<description>
Returns the lowest 1 percentile of the running project's framerate as a rolling value. In other words, this returns the FPS equivalent to the [i]worst[/i] (highest) frametime in the last 100 rendered frames. This value is generally a better indicator of overall smoothness compared to [method get_frames_per_second]. The value of [method get_frames_per_second_1_percent_low] should ideally always be greater than or equal to the target framerate to ensure a smooth experience (typically 60 FPS).
</description>
</method>
<method name="get_license_info" qualifiers="const">
<return type="Dictionary" />
<description>
Expand Down
9 changes: 6 additions & 3 deletions doc/classes/Performance.xml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@
</methods>
<constants>
<constant name="TIME_FPS" value="0" enum="Monitor">
The number of frames rendered in the last second. This metric is only updated once per second, even if queried more often. [i]Higher is better.[/i]
The number of frames rendered in the last second (identical to [method Engine.get_frames_per_second]). This metric is only updated once per second, even if queried more often. [i]Higher is better.[/i]
</constant>
<constant name="TIME_PROCESS" value="1" enum="Monitor">
Time it took to complete one frame, in seconds. [i]Lower is better.[/i]
Expand Down Expand Up @@ -192,7 +192,7 @@
Number of islands in the 3D physics engine. [i]Lower is better.[/i]
</constant>
<constant name="AUDIO_OUTPUT_LATENCY" value="23" enum="Monitor">
Output latency of the [AudioServer]. Equivalent to calling [method AudioServer.get_output_latency], it is not recommended to call this every frame.
Output latency of the [AudioServer]. Equivalent to calling [method AudioServer.get_output_latency], it is not recommended to call this every frame. [i]Lower is better.[/i]
</constant>
<constant name="NAVIGATION_ACTIVE_MAPS" value="24" enum="Monitor">
Number of active navigation maps in the [NavigationServer3D]. This also includes the two empty default navigation maps created by World2D and World3D.
Expand Down Expand Up @@ -221,7 +221,10 @@
<constant name="NAVIGATION_EDGE_FREE_COUNT" value="32" enum="Monitor">
Number of navigation mesh polygon edges that could not be merged in the [NavigationServer3D]. The edges still may be connected by edge proximity or with links.
</constant>
<constant name="MONITOR_MAX" value="33" enum="Monitor">
<constant name="TIME_FPS_1_PERCENT_LOW" value="33" enum="Monitor">
The worst 1% percentile of frametimes rendered in the last second, converted to a FPS value (identical to [method Engine.get_frames_per_second_1_percent_low]). This metric may be updated less than once per second at low framerates, even if queried more often. [i]Higher is better.[/i]
</constant>
<constant name="MONITOR_MAX" value="34" enum="Monitor">
Represents the size of the [enum Monitor] enum.
</constant>
</constants>
Expand Down
23 changes: 21 additions & 2 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3978,6 +3978,7 @@ uint64_t Main::last_ticks = 0;
uint32_t Main::frames = 0;
uint32_t Main::hide_print_fps_attempts = 3;
uint32_t Main::frame = 0;
int Main::recent_frametimes[100] = {};
bool Main::force_redraw_requested = false;
int Main::iterating = 0;

Expand Down Expand Up @@ -4141,15 +4142,33 @@ bool Main::iteration() {
frames++;
Engine::get_singleton()->_process_frames++;

// Determine the frame that took the most time to render in the last 100 rendered frames.
recent_frametimes[Engine::get_singleton()->get_frames_drawn() % 100] = ticks_elapsed;
int frametime_1_percent_low = 1;
for (int i = 0; i < 100; i++) {
frametime_1_percent_low = MAX(frametime_1_percent_low, recent_frametimes[i]);
}
Engine::get_singleton()->_fps_1_percent_low = 1'000'000.0 / frametime_1_percent_low;
Copy link
Contributor

Choose a reason for hiding this comment

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

  • There's an interesting comment on line 3173 about FPS reporting. Based on that, shouldn't this code be inside the IF from line 3173 (the if below)?
  • Suggestion: Since it is already printing the FPS below (L3178 and 3181), WDYT aboud add the 1% low FPT there too?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's an interesting comment on line 3173 about FPS reporting. Based on that, shouldn't this code be inside the IF from line 3173 (the if below)?

1% low FPS reporting is designed to be updated as often as possible, rather than just once per second. This makes the value less stable when you print out its value in _process, but it makes changes more reactive, which I think is better overall.

The editor performance monitor and --print-fps will still only update the displayed value once per second due to how they work (see the graph screenshot in OP).

In the long term, we could look into adding a smoothing algorithm like #63356, but I think this is better left for a future PR.

Suggestion: Since it is already printing the FPS below (L3178 and 3181), WDYT aboud add the 1% low FPT there too?

That's a good idea. I implemented this 🙂


if (frame > 1000000) {
// 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) {
if (print_fps) {
print_line(vformat("Editor FPS: %d (%s mspf)", frames, rtos(1000.0 / frames).pad_decimals(2)));
print_line(
vformat("Editor FPS: %d (%s mspf) - 1%% low: %d (%s mspf)",
frames,
rtos(1000.0 / frames).pad_decimals(2),
Engine::get_singleton()->_fps_1_percent_low,
rtos(1000.0 / Engine::get_singleton()->_fps_1_percent_low).pad_decimals(2)));
}
} else if (print_fps || GLOBAL_GET("debug/settings/stdout/print_fps")) {
print_line(vformat("Project FPS: %d (%s mspf)", frames, rtos(1000.0 / frames).pad_decimals(2)));
print_line(
vformat("Project FPS: %d (%s mspf) - 1%% low: %d (%s mspf)",
frames,
rtos(1000.0 / frames).pad_decimals(2),
Engine::get_singleton()->_fps_1_percent_low,
rtos(1000.0 / Engine::get_singleton()->_fps_1_percent_low).pad_decimals(2)));
}
} else {
hide_print_fps_attempts--;
Expand Down
1 change: 1 addition & 0 deletions main/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Main {
static uint32_t hide_print_fps_attempts;
static uint32_t frames;
static uint32_t frame;
static int recent_frametimes[100];
static bool force_redraw_requested;
static int iterating;

Expand Down
8 changes: 5 additions & 3 deletions main/performance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ void Performance::_bind_methods() {
BIND_ENUM_CONSTANT(NAVIGATION_EDGE_MERGE_COUNT);
BIND_ENUM_CONSTANT(NAVIGATION_EDGE_CONNECTION_COUNT);
BIND_ENUM_CONSTANT(NAVIGATION_EDGE_FREE_COUNT);
BIND_ENUM_CONSTANT(TIME_FPS_1_PERCENT_LOW);
BIND_ENUM_CONSTANT(MONITOR_MAX);
}

Expand Down Expand Up @@ -141,7 +142,7 @@ String Performance::get_monitor_name(Monitor p_monitor) const {
PNAME("navigation/edges_merged"),
PNAME("navigation/edges_connected"),
PNAME("navigation/edges_free"),

PNAME("time/fps_1_percent_low"),
};

return names[p_monitor];
Expand Down Expand Up @@ -225,7 +226,8 @@ double Performance::get_monitor(Monitor p_monitor) const {
return NavigationServer3D::get_singleton()->get_process_info(NavigationServer3D::INFO_EDGE_CONNECTION_COUNT);
case NAVIGATION_EDGE_FREE_COUNT:
return NavigationServer3D::get_singleton()->get_process_info(NavigationServer3D::INFO_EDGE_FREE_COUNT);

case TIME_FPS_1_PERCENT_LOW:
return Math::round(Engine::get_singleton()->get_frames_per_second_1_percent_low());
default: {
}
}
Expand Down Expand Up @@ -272,7 +274,7 @@ Performance::MonitorType Performance::get_monitor_type(Monitor p_monitor) const
MONITOR_TYPE_QUANTITY,
MONITOR_TYPE_QUANTITY,
MONITOR_TYPE_QUANTITY,

MONITOR_TYPE_QUANTITY,
};

return types[p_monitor];
Expand Down
1 change: 1 addition & 0 deletions main/performance.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ class Performance : public Object {
NAVIGATION_EDGE_MERGE_COUNT,
NAVIGATION_EDGE_CONNECTION_COUNT,
NAVIGATION_EDGE_FREE_COUNT,
TIME_FPS_1_PERCENT_LOW,
MONITOR_MAX
};

Expand Down
Loading