diff options
author | Hugo Locurcio <hugo.locurcio@hugo.pro> | 2022-09-15 18:57:34 +0200 |
---|---|---|
committer | Hugo Locurcio <hugo.locurcio@hugo.pro> | 2022-10-09 23:00:09 +0200 |
commit | 66f7c48e39c6479f11c0dd2426c093b0f2910f76 (patch) | |
tree | aeec63f81055de9bcfb37d1a850937ce55662165 | |
parent | ca25c6e0a3f25948ee4a197f3442c66f019e7424 (diff) |
Implement adjusting the maximum number of physics steps per rendered frame
When using high physics FPS (which is a requirement to minimize input
lag and improve precision in simulation racing games), a higher value
prevents the game from slowing down at low rendering FPS.
This can be done via an Engine property for run-time changes,
or a project setting for initial changes.
-rw-r--r-- | core/config/engine.cpp | 9 | ||||
-rw-r--r-- | core/config/engine.h | 4 | ||||
-rw-r--r-- | core/core_bind.cpp | 11 | ||||
-rw-r--r-- | core/core_bind.h | 3 | ||||
-rw-r--r-- | doc/classes/Engine.xml | 5 | ||||
-rw-r--r-- | doc/classes/ProjectSettings.xml | 6 | ||||
-rw-r--r-- | main/main.cpp | 8 |
7 files changed, 43 insertions, 3 deletions
diff --git a/core/config/engine.cpp b/core/config/engine.cpp index 21e910be5b..7aa5f4d06d 100644 --- a/core/config/engine.cpp +++ b/core/config/engine.cpp @@ -48,6 +48,15 @@ int Engine::get_physics_ticks_per_second() const { return ips; } +void Engine::set_max_physics_steps_per_frame(int p_max_physics_steps) { + ERR_FAIL_COND_MSG(p_max_physics_steps <= 0, "Maximum number of physics steps per frame must be greater than 0."); + max_physics_steps_per_frame = p_max_physics_steps; +} + +int Engine::get_max_physics_steps_per_frame() const { + return max_physics_steps_per_frame; +} + void Engine::set_physics_jitter_fix(double p_threshold) { if (p_threshold < 0) { p_threshold = 0; diff --git a/core/config/engine.h b/core/config/engine.h index 21517e46b7..1b179c5727 100644 --- a/core/config/engine.h +++ b/core/config/engine.h @@ -63,6 +63,7 @@ private: int _max_fps = 0; double _time_scale = 1.0; uint64_t _physics_frames = 0; + int max_physics_steps_per_frame = 8; double _physics_interpolation_fraction = 0.0f; bool abort_on_gpu_errors = false; bool use_validation_layers = false; @@ -93,6 +94,9 @@ public: virtual void set_physics_ticks_per_second(int p_ips); virtual int get_physics_ticks_per_second() const; + virtual void set_max_physics_steps_per_frame(int p_max_physics_steps); + virtual int get_max_physics_steps_per_frame() const; + void set_physics_jitter_fix(double p_threshold); double get_physics_jitter_fix() const; diff --git a/core/core_bind.cpp b/core/core_bind.cpp index 7496ba1979..5c4ed9e483 100644 --- a/core/core_bind.cpp +++ b/core/core_bind.cpp @@ -1471,6 +1471,14 @@ int Engine::get_physics_ticks_per_second() const { return ::Engine::get_singleton()->get_physics_ticks_per_second(); } +void Engine::set_max_physics_steps_per_frame(int p_max_physics_steps) { + ::Engine::get_singleton()->set_max_physics_steps_per_frame(p_max_physics_steps); +} + +int Engine::get_max_physics_steps_per_frame() const { + return ::Engine::get_singleton()->get_max_physics_steps_per_frame(); +} + void Engine::set_physics_jitter_fix(double p_threshold) { ::Engine::get_singleton()->set_physics_jitter_fix(p_threshold); } @@ -1622,6 +1630,8 @@ bool Engine::is_printing_error_messages() const { void Engine::_bind_methods() { ClassDB::bind_method(D_METHOD("set_physics_ticks_per_second", "physics_ticks_per_second"), &Engine::set_physics_ticks_per_second); ClassDB::bind_method(D_METHOD("get_physics_ticks_per_second"), &Engine::get_physics_ticks_per_second); + ClassDB::bind_method(D_METHOD("set_max_physics_steps_per_frame", "max_physics_steps"), &Engine::set_max_physics_steps_per_frame); + ClassDB::bind_method(D_METHOD("get_max_physics_steps_per_frame"), &Engine::get_max_physics_steps_per_frame); ClassDB::bind_method(D_METHOD("set_physics_jitter_fix", "physics_jitter_fix"), &Engine::set_physics_jitter_fix); ClassDB::bind_method(D_METHOD("get_physics_jitter_fix"), &Engine::get_physics_jitter_fix); ClassDB::bind_method(D_METHOD("get_physics_interpolation_fraction"), &Engine::get_physics_interpolation_fraction); @@ -1669,6 +1679,7 @@ void Engine::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages"); 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::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"); diff --git a/core/core_bind.h b/core/core_bind.h index 9261698076..5c7702be0b 100644 --- a/core/core_bind.h +++ b/core/core_bind.h @@ -484,6 +484,9 @@ public: void set_physics_ticks_per_second(int p_ips); int get_physics_ticks_per_second() const; + void set_max_physics_steps_per_frame(int p_max_physics_steps); + int get_max_physics_steps_per_frame() const; + void set_physics_jitter_fix(double p_threshold); double get_physics_jitter_fix() const; double get_physics_interpolation_fraction() const; diff --git a/doc/classes/Engine.xml b/doc/classes/Engine.xml index 2b8663e039..4067c8261c 100644 --- a/doc/classes/Engine.xml +++ b/doc/classes/Engine.xml @@ -269,13 +269,16 @@ If [member ProjectSettings.display/window/vsync/vsync_mode] is [code]Disabled[/code], 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]. </member> + <member name="max_physics_steps_per_frame" type="int" setter="set_max_physics_steps_per_frame" getter="get_max_physics_steps_per_frame" default="8"> + Controls the maximum number of physics steps that can be simulated each rendered frame. The default value is tuned to avoid "spiral of death" situations where expensive physics simulations trigger more expensive simulations indefinitely. However, the game will appear to slow down if the rendering FPS is less than [code]1 / max_physics_steps_per_frame[/code] of [member physics_ticks_per_second]. This occurs even if [code]delta[/code] is consistently used in physics calculations. To avoid this, increase [member max_physics_steps_per_frame] if you have increased [member physics_ticks_per_second] significantly above its default value. + </member> <member name="physics_jitter_fix" type="float" setter="set_physics_jitter_fix" getter="get_physics_jitter_fix" default="0.5"> Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of the in-game clock and real clock but smooth out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics_jitter_fix] to [code]0[/code]. </member> <member name="physics_ticks_per_second" type="int" setter="set_physics_ticks_per_second" getter="get_physics_ticks_per_second" default="60"> The number of fixed iterations per second. This controls how often physics simulation and [method Node._physics_process] methods are run. This value should generally always be set to [code]60[/code] or above, as Godot doesn't interpolate the physics step. As a result, values lower than [code]60[/code] will look stuttery. This value can be increased to make input more reactive or work around collision tunneling issues, but keep in mind doing so will increase CPU usage. See also [member max_fps] and [member ProjectSettings.physics/common/physics_ticks_per_second]. - [b]Note:[/b] Only 8 physics ticks may be simulated per rendered frame at most. If more than 8 physics ticks have to be simulated per rendered frame to keep up with rendering, the game will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended not to increase [member physics_ticks_per_second] above 240. Otherwise, the game will slow down when the rendering framerate goes below 30 FPS. + [b]Note:[/b] Only [member 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 max_physics_steps_per_frame] if increasing [member physics_ticks_per_second] significantly above its default value. </member> <member name="print_error_messages" type="bool" setter="set_print_error_messages" getter="is_printing_error_messages" default="true"> If [code]false[/code], stops printing error and warning messages to the console and editor Output log. This can be used to hide error and warning messages during unit test suite runs. This property is equivalent to the [member ProjectSettings.application/run/disable_stderr] project setting. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 214f087d78..d8dad89386 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1770,6 +1770,10 @@ <member name="physics/common/enable_object_picking" type="bool" setter="" getter="" default="true"> Enables [member Viewport.physics_object_picking] on the root viewport. </member> + <member name="physics/common/max_physics_steps_per_frame" type="int" setter="" getter="" default="8"> + Controls the maximum number of physics steps that can be simulated each rendered frame. The default value is tuned to avoid "spiral of death" situations where expensive physics simulations trigger more expensive simulations indefinitely. However, the game will appear to slow down if the rendering FPS is less than [code]1 / max_physics_steps_per_frame[/code] of [member physics/common/physics_ticks_per_second]. This occurs even if [code]delta[/code] is consistently used in physics calculations. To avoid this, increase [member physics/common/max_physics_steps_per_frame] if you have increased [member physics/common/physics_ticks_per_second] significantly above its default value. + [b]Note:[/b] This property is only read when the project starts. To change the maximum number of simulated physics steps per frame at runtime, set [member Engine.max_physics_steps_per_frame] instead. + </member> <member name="physics/common/physics_jitter_fix" type="float" setter="" getter="" default="0.5"> Controls how much physics ticks are synchronized with real time. For 0 or less, the ticks are synchronized. Such values are recommended for network games, where clock synchronization matters. Higher values cause higher deviation of in-game clock and real clock, but allows smoothing out framerate jitters. The default value of 0.5 should be fine for most; values above 2 could cause the game to react to dropped frames with a noticeable delay and are not recommended. [b]Note:[/b] For best results, when using a custom physics interpolation solution, the physics jitter fix should be disabled by setting [member physics/common/physics_jitter_fix] to [code]0[/code]. @@ -1778,7 +1782,7 @@ <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]. [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 8 physics ticks may be simulated per rendered frame at most. If more than 8 physics ticks have to be simulated per rendered frame to keep up with rendering, the game will appear to slow down (even if [code]delta[/code] is used consistently in physics calculations). Therefore, it is recommended not to increase [member physics/common/physics_ticks_per_second] above 240. Otherwise, the game will slow down when the rendering framerate goes below 30 FPS. + [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> <member name="rendering/2d/sdf/oversize" type="int" setter="" getter="" default="1"> </member> diff --git a/main/main.cpp b/main/main.cpp index 08a9b4c310..539a3ad542 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -1774,6 +1774,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph ProjectSettings::get_singleton()->set_custom_property_info("physics/common/physics_ticks_per_second", PropertyInfo(Variant::INT, "physics/common/physics_ticks_per_second", PROPERTY_HINT_RANGE, "1,1000,1")); + + Engine::get_singleton()->set_max_physics_steps_per_frame(GLOBAL_DEF("physics/common/max_physics_steps_per_frame", 8)); + ProjectSettings::get_singleton()->set_custom_property_info("physics/common/max_physics_steps_per_frame", + PropertyInfo(Variant::INT, "physics/common/max_physics_steps_per_frame", + PROPERTY_HINT_RANGE, "1,100,1")); + Engine::get_singleton()->set_physics_jitter_fix(GLOBAL_DEF("physics/common/physics_jitter_fix", 0.5)); Engine::get_singleton()->set_max_fps(GLOBAL_DEF("application/run/max_fps", 0)); ProjectSettings::get_singleton()->set_custom_property_info("application/run/max_fps", @@ -3076,7 +3082,7 @@ bool Main::iteration() { last_ticks = ticks; - static const int max_physics_steps = 8; + const int max_physics_steps = Engine::get_singleton()->get_max_physics_steps_per_frame(); if (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; |