Add a project setting and Engine property to toggle fixed FPS at runtime

Non-realtime simulation can now be enabled at runtime and enabled
without having to use a command line argument.
This commit is contained in:
Hugo Locurcio 2022-04-15 23:36:40 +02:00
parent 5f1184e93f
commit 9e710d8ad0
No known key found for this signature in database
GPG key ID: 39E8F8BE30B0A49C
8 changed files with 57 additions and 15 deletions

View file

@ -93,6 +93,15 @@ void Engine::increment_frames_drawn() {
frames_drawn++;
}
void Engine::set_fixed_fps(int p_fps) {
ERR_FAIL_COND_MSG(p_fps <= -1, vformat("Fixed FPS must be a positive value (or 0 to disable fixed FPS), but %d was specified.", p_fps));
fixed_fps = p_fps;
}
int Engine::get_fixed_fps() const {
return fixed_fps;
}
uint64_t Engine::get_frames_drawn() {
return frames_drawn;
}

View file

@ -63,6 +63,7 @@ private:
int ips = 60;
double physics_jitter_fix = 0.5;
double _fps = 1;
int fixed_fps = 0;
int _max_fps = 0;
int _audio_output_latency = 0;
double _time_scale = 1.0;
@ -113,6 +114,9 @@ public:
virtual void set_audio_output_latency(int p_msec);
virtual int get_audio_output_latency() const;
virtual void set_fixed_fps(int p_fps);
virtual int get_fixed_fps() const;
virtual double get_frames_per_second() const { return _fps; }
uint64_t get_frames_drawn();

View file

@ -1652,6 +1652,14 @@ int Engine::get_max_fps() const {
return ::Engine::get_singleton()->get_max_fps();
}
void Engine::set_fixed_fps(int p_fps) {
::Engine::get_singleton()->set_fixed_fps(p_fps);
}
int Engine::get_fixed_fps() const {
return ::Engine::get_singleton()->get_fixed_fps();
}
double Engine::get_frames_per_second() const {
return ::Engine::get_singleton()->get_frames_per_second();
}
@ -1806,6 +1814,8 @@ void Engine::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &Engine::get_physics_interpolation_fraction);
ClassDB::bind_method(D_METHOD("set_max_fps", "max_fps"), &Engine::set_max_fps);
ClassDB::bind_method(D_METHOD("get_max_fps"), &Engine::get_max_fps);
ClassDB::bind_method(D_METHOD("set_fixed_fps", "fixed_fps"), &Engine::set_fixed_fps);
ClassDB::bind_method(D_METHOD("get_fixed_fps"), &Engine::get_fixed_fps);
ClassDB::bind_method(D_METHOD("set_time_scale", "time_scale"), &Engine::set_time_scale);
ClassDB::bind_method(D_METHOD("get_time_scale"), &Engine::get_time_scale);
@ -1851,6 +1861,7 @@ void Engine::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_physics_steps_per_frame"), "set_max_physics_steps_per_frame", "get_max_physics_steps_per_frame");
ADD_PROPERTY(PropertyInfo(Variant::INT, "max_fps"), "set_max_fps", "get_max_fps");
ADD_PROPERTY(PropertyInfo(Variant::INT, "fixed_fps"), "set_fixed_fps", "get_fixed_fps");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "time_scale"), "set_time_scale", "get_time_scale");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "physics_jitter_fix"), "set_physics_jitter_fix", "get_physics_jitter_fix");
}

View file

@ -500,6 +500,9 @@ public:
void set_max_fps(int p_fps);
int get_max_fps() const;
void set_fixed_fps(int p_fps);
int get_fixed_fps() const;
double get_frames_per_second() const;
uint64_t get_physics_frames() const;
uint64_t get_process_frames() const;

View file

@ -310,11 +310,17 @@
</method>
</methods>
<members>
<member name="fixed_fps" type="int" setter="set_fixed_fps" getter="get_fixed_fps" default="0">
If set to a value greater than [code]0[/code], this forces the engine to simulate as fast as possible while respecting the given FPS as a [code]delta[/code] value passed to [method Node._process] functions. This can be used for non-realtime simulation such as offline video recording, benchmarking or AI training. See also [member ProjectSettings.application/run/fixed_fps] and [member physics_ticks_per_second].
[member fixed_fps] is [b]not[/b] a FPS limiter (use [member max_fps] instead).
Non-realtime simulation can also be enabled using the [code]--fixed-fps &lt;fps&gt;[/code] command line argument.
</member>
<member name="max_fps" type="int" setter="set_max_fps" getter="get_max_fps" default="0">
The maximum number of frames that can be rendered every second (FPS). A value of [code]0[/code] means the framerate is uncapped.
Limiting the FPS can be useful to reduce the host machine's power consumption, which reduces heat, noise emissions, and improves battery life.
If [member ProjectSettings.display/window/vsync/vsync_mode] is [b]Enabled[/b] or [b]Adaptive[/b], the setting takes precedence and the max FPS number cannot exceed the monitor's refresh rate.
The maximum number of frames per second that can be rendered (FPS). A value of [code]0[/code] means "no limit". The actual number of frames per second may still be below this value if the CPU or GPU cannot keep up with the project logic and rendering.
Limiting the FPS can be useful to reduce system power consumption, which reduces heat and noise emissions (and improves battery life on mobile devices). See also [member physics_ticks_per_second], [member fixed_fps] and [member ProjectSettings.application/run/max_fps].
If [member ProjectSettings.display/window/vsync/vsync_mode] is [b]Enabled[/b] or [b]Adaptive[/b], it takes precedence and the forced FPS number cannot exceed the monitor's refresh rate.
If [member ProjectSettings.display/window/vsync/vsync_mode] is [b]Enabled[/b], on monitors with variable refresh rate enabled (G-Sync/FreeSync), using an FPS limit a few frames lower than the monitor's refresh rate will [url=https://blurbusters.com/howto-low-lag-vsync-on/]reduce input lag while avoiding tearing[/url].
If [member ProjectSettings.display/window/vsync/vsync_mode] is [b]Disabled[/b], limiting the FPS to a high value that can be consistently reached on the system can reduce input lag compared to an uncapped framerate. Since this works by ensuring the GPU load is lower than 100%, this latency reduction is only effective in GPU-bottlenecked scenarios, not CPU-bottlenecked scenarios.
See also [member physics_ticks_per_second] and [member ProjectSettings.application/run/max_fps].
[b]Note:[/b] The actual number of frames per second may still be below this value if the CPU or GPU cannot keep up with the project's logic and rendering.
[b]Note:[/b] If [member ProjectSettings.display/window/vsync/vsync_mode] is [b]Disabled[/b], limiting the FPS to a high value that can be consistently reached on the system can reduce input lag compared to an uncapped framerate. Since this works by ensuring the GPU load is lower than 100%, this latency reduction is only effective in GPU-bottlenecked scenarios, not CPU-bottlenecked scenarios.

View file

@ -329,6 +329,13 @@
[b]Note:[/b] When the menu is displayed, project execution will pause until the menu is [i]fully[/i] closed due to Windows behavior. Consider this when enabling this setting in a networked multiplayer game. The menu is only considered fully closed when an option is selected, when the user clicks outside, or when [kbd]Escape[/kbd] is pressed after bringing up the window menu [i]and[/i] another key is pressed afterwards.
[b]Note:[/b] This setting is implemented only on Windows.
</member>
<member name="application/run/fixed_fps" type="int" setter="" getter="" default="0">
If set to a value greater than [code]0[/code], this forces the engine to simulate as fast as possible while respecting the given FPS as a [code]delta[/code] value passed to [method Node._process] functions. This can be used for non-realtime simulation such as benchmarking or AI training.
[member application/run/fixed_fps] is [b]not[/b] a FPS limiter (use [member application/run/max_fps] instead). See also [member physics/common/physics_ticks_per_second].
Non-realtime simulation can also be enabled using the [code]--fixed-fps &lt;fps&gt;[/code] command line argument, which takes priority over [member application/run/fixed_fps].
[b]Note:[/b] This property is only read when the project starts. To toggle non-realtime simulation at runtime, set [member Engine.fixed_fps] instead.
[b]Note:[/b] When running the project from the editor with Movie Maker mode enabled, [member editor/movie_writer/fps] takes priority over [member application/run/fixed_fps].
</member>
<member name="application/run/flush_stdout_on_print" type="bool" setter="" getter="" default="false">
If [code]true[/code], flushes the standard output stream every time a line is printed. This affects both terminal logging and file logging.
When running a project, this setting must be enabled if you want logs to be collected by service managers such as systemd/journalctl. This setting is disabled by default on release builds, since flushing on every printed line will negatively affect performance if lots of lines are printed in a rapid succession. Also, if this setting is enabled, logged files will still be written successfully if the application crashes or is otherwise killed by the user (without being closed "normally").
@ -356,7 +363,7 @@
Path to the main scene file that will be loaded when the project runs.
</member>
<member name="application/run/max_fps" type="int" setter="" getter="" default="0">
Maximum number of frames per second allowed. A value of [code]0[/code] means "no limit". The actual number of frames per second may still be below this value if the CPU or GPU cannot keep up with the project logic and rendering.
Maximum number of frames per second allowed. A value of [code]0[/code] means "no limit". The actual number of frames per second may still be below this value if the CPU or GPU cannot keep up with the project logic and rendering. See also [member physics/common/physics_ticks_per_second] and [member application/run/fixed_fps].
Limiting the FPS can be useful to reduce system power consumption, which reduces heat and noise emissions (and improves battery life on mobile devices).
If [member display/window/vsync/vsync_mode] is set to [code]Enabled[/code] or [code]Adaptive[/code], it takes precedence and the forced FPS number cannot exceed the monitor's refresh rate.
If [member display/window/vsync/vsync_mode] is [code]Enabled[/code], on monitors with variable refresh rate enabled (G-Sync/FreeSync), using an FPS limit a few frames lower than the monitor's refresh rate will [url=https://blurbusters.com/howto-low-lag-vsync-on/]reduce input lag while avoiding tearing[/url].
@ -935,7 +942,7 @@
</member>
<member name="editor/movie_writer/fps" type="int" setter="" getter="" default="60">
The number of frames per second to record in the video when writing a movie. Simulation speed will adjust to always match the specified framerate, which means the engine will appear to run slower at higher [member editor/movie_writer/fps] values. Certain FPS values will require you to adjust [member editor/movie_writer/mix_rate] to prevent audio from desynchronizing over time.
This can be specified manually on the command line using the [code]--fixed-fps &lt;fps&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url].
This can be specified manually on the command line using the [code]--fixed-fps &lt;fps&gt;[/code] [url=$DOCS_URL/tutorials/editor/command_line_tutorial.html]command line argument[/url]. See also [member application/run/fixed_fps], which also applies when not using Movie Maker mode. [member editor/movie_writer/fps] takes priority over [member application/run/fixed_fps] when running the project from the editor with Movie Maker mode enabled.
</member>
<member name="editor/movie_writer/mix_rate" type="int" setter="" getter="" default="48000">
The audio mix rate to use in the recorded audio when writing a movie (in Hz). This can be different from [member audio/driver/mix_rate], but this value must be divisible by [member editor/movie_writer/fps] to prevent audio from desynchronizing over time.
@ -2307,7 +2314,7 @@
[b]Note:[/b] This property is only read when the project starts. To change the physics jitter fix at runtime, set [member Engine.physics_jitter_fix] instead.
</member>
<member name="physics/common/physics_ticks_per_second" type="int" setter="" getter="" default="60">
The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. See also [member application/run/max_fps].
The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. See also [member application/run/max_fps] and [member application/run/fixed_fps].
[b]Note:[/b] This property is only read when the project starts. To change the physics FPS at runtime, set [member Engine.physics_ticks_per_second] instead.
[b]Note:[/b] Only [member physics/common/max_physics_steps_per_frame] physics ticks may be simulated per rendered frame at most. If more physics ticks have to be simulated per rendered frame to keep up with rendering, the project will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended to also increase [member physics/common/max_physics_steps_per_frame] if increasing [member physics/common/physics_ticks_per_second] significantly above its default value.
</member>

View file

@ -236,7 +236,6 @@ static int max_fps = -1;
static int frame_delay = 0;
static int audio_output_latency = 0;
static bool disable_render_loop = false;
static int fixed_fps = -1;
static MovieWriter *movie_writer = nullptr;
static bool disable_vsync = false;
static bool print_fps = false;
@ -1649,7 +1648,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
disable_render_loop = true;
} else if (arg == "--fixed-fps") {
if (N) {
fixed_fps = N->get().to_int();
Engine::get_singleton()->set_fixed_fps(N->get().to_int());
N = N->next();
} else {
OS::get_singleton()->print("Missing fixed-fps argument, aborting.\n");
@ -1659,8 +1658,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (N) {
Engine::get_singleton()->set_write_movie_path(N->get());
N = N->next();
if (fixed_fps == -1) {
fixed_fps = 60;
if (Engine::get_singleton()->get_fixed_fps() < 1) {
Engine::get_singleton()->set_fixed_fps(60);
}
OS::get_singleton()->_writing_movie = true;
} else {
@ -2370,6 +2369,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
Engine::get_singleton()->set_max_physics_steps_per_frame(GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "physics/common/max_physics_steps_per_frame", PROPERTY_HINT_RANGE, "1,100,1"), 8));
Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5));
Engine::get_singleton()->set_max_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/max_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0));
if (!Engine::get_singleton()->is_editor_hint() && Engine::get_singleton()->get_fixed_fps() < 1) { // Not manually overridden, and do not apply the project setting to the editor.
Engine::get_singleton()->set_fixed_fps(GLOBAL_DEF(PropertyInfo(Variant::INT, "application/run/fixed_fps", PROPERTY_HINT_RANGE, "0,1000,1"), 0));
}
GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "audio/driver/output_latency", PROPERTY_HINT_RANGE, "1,100,1"), 15);
// Use a safer default output_latency for web to avoid audio cracking on low-end devices, especially mobile.
@ -3935,7 +3937,7 @@ int Main::start() {
}
if (movie_writer) {
movie_writer->begin(DisplayServer::get_singleton()->window_get_size(), fixed_fps, Engine::get_singleton()->get_write_movie_path());
movie_writer->begin(DisplayServer::get_singleton()->window_get_size(), Engine::get_singleton()->get_fixed_fps(), Engine::get_singleton()->get_write_movie_path());
}
if (minimum_time_msec) {
@ -3988,7 +3990,7 @@ bool Main::iteration() {
const uint64_t ticks = OS::get_singleton()->get_ticks_usec();
Engine::get_singleton()->_frame_ticks = ticks;
main_timer_sync.set_cpu_ticks_usec(ticks);
main_timer_sync.set_fixed_fps(fixed_fps);
main_timer_sync.set_fixed_fps(Engine::get_singleton()->get_fixed_fps());
const uint64_t ticks_elapsed = ticks - last_ticks;
@ -4013,7 +4015,7 @@ bool Main::iteration() {
last_ticks = ticks;
const int max_physics_steps = Engine::get_singleton()->get_max_physics_steps_per_frame();
if (fixed_fps == -1 && advance.physics_steps > max_physics_steps) {
if (Engine::get_singleton()->get_fixed_fps() < 1 && advance.physics_steps > max_physics_steps) {
process_step -= (advance.physics_steps - max_physics_steps) * physics_step;
advance.physics_steps = max_physics_steps;
}
@ -4177,7 +4179,7 @@ bool Main::iteration() {
}
#endif
if (fixed_fps != -1) {
if (Engine::get_singleton()->get_fixed_fps() >= 1) {
return exit;
}

View file

@ -423,7 +423,7 @@ MainFrameTime MainTimerSync::advance_core(double p_physics_step, int p_physics_t
// calls advance_core, keeps track of deficit it adds to animaption_step, make sure the deficit sum stays close to zero
MainFrameTime MainTimerSync::advance_checked(double p_physics_step, int p_physics_ticks_per_second, double p_process_step) {
if (fixed_fps != -1) {
if (fixed_fps >= 1) {
p_process_step = 1.0 / fixed_fps;
}