Skip to content

Commit

Permalink
Add renderer low latency mode
Browse files Browse the repository at this point in the history
  • Loading branch information
KeyboardDanni committed Dec 4, 2024
1 parent 1f47e4c commit 1b981ff
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 0 deletions.
8 changes: 8 additions & 0 deletions core/config/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ int Engine::get_audio_output_latency() const {
return _audio_output_latency;
}

void Engine::set_render_latency_mode(Engine::RenderLatencyMode p_latency_mode) {
_renderer_latency_mode = p_latency_mode;
}

Engine::RenderLatencyMode Engine::get_render_latency_mode() const {
return _renderer_latency_mode;
}

void Engine::increment_frames_drawn() {
if (frame_server_synced) {
server_syncs++;
Expand Down
9 changes: 9 additions & 0 deletions core/config/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ class Engine {
Singleton(const StringName &p_name = StringName(), Object *p_ptr = nullptr, const StringName &p_class_name = StringName());
};

enum RenderLatencyMode {
RENDER_LATENCY_PRIORITIZE_FRAMERATE,
RENDER_LATENCY_PRIORITIZE_LOW_LATENCY,
};

private:
friend class Main;

Expand All @@ -65,6 +70,7 @@ class Engine {
double _fps = 1;
int _max_fps = 0;
int _audio_output_latency = 0;
RenderLatencyMode _renderer_latency_mode;
double _time_scale = 1.0;
uint64_t _physics_frames = 0;
int max_physics_steps_per_frame = 8;
Expand Down Expand Up @@ -119,6 +125,9 @@ class Engine {
virtual void set_audio_output_latency(int p_msec);
virtual int get_audio_output_latency() const;

virtual void set_render_latency_mode(RenderLatencyMode p_latency_mode);
virtual RenderLatencyMode get_render_latency_mode() const;

virtual double get_frames_per_second() const { return _fps; }

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

void Engine::set_render_latency_mode(Engine::RenderLatencyMode p_latency_mode) {
::Engine::get_singleton()->set_render_latency_mode((::Engine::RenderLatencyMode)p_latency_mode);
}

Engine::RenderLatencyMode Engine::get_render_latency_mode() const {
return (RenderLatencyMode)::Engine::get_singleton()->get_render_latency_mode();
}

void Engine::set_max_fps(int p_fps) {
::Engine::get_singleton()->set_max_fps(p_fps);
}
Expand Down Expand Up @@ -1946,6 +1954,9 @@ void Engine::_bind_methods() {
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_render_latency_mode", "render_latency_mode"), &Engine::set_render_latency_mode);
ClassDB::bind_method(D_METHOD("get_render_latency_mode"), &Engine::get_render_latency_mode);

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);

Expand Down Expand Up @@ -1989,6 +2000,9 @@ void Engine::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_print_error_messages", "enabled"), &Engine::set_print_error_messages);
ClassDB::bind_method(D_METHOD("is_printing_error_messages"), &Engine::is_printing_error_messages);

BIND_ENUM_CONSTANT(RENDER_LATENCY_PRIORITIZE_FRAMERATE);
BIND_ENUM_CONSTANT(RENDER_LATENCY_PRIORITIZE_LOW_LATENCY);

ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_error_messages"), "set_print_error_messages", "is_printing_error_messages");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "print_to_stdout"), "set_print_to_stdout", "is_printing_to_stdout");
ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_ticks_per_second"), "set_physics_ticks_per_second", "get_physics_ticks_per_second");
Expand Down
10 changes: 10 additions & 0 deletions core/core_bind.h
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,11 @@ class Engine : public Object {
static Engine *singleton;

public:
enum RenderLatencyMode {
RENDER_LATENCY_PRIORITIZE_FRAMERATE,
RENDER_LATENCY_PRIORITIZE_LOW_LATENCY,
};

static Engine *get_singleton() { return singleton; }
void set_physics_ticks_per_second(int p_ips);
int get_physics_ticks_per_second() const;
Expand All @@ -546,6 +551,9 @@ class Engine : public Object {
double get_physics_jitter_fix() const;
double get_physics_interpolation_fraction() const;

void set_render_latency_mode(RenderLatencyMode p_latency_mode);
RenderLatencyMode get_render_latency_mode() const;

void set_max_fps(int p_fps);
int get_max_fps() const;

Expand Down Expand Up @@ -653,6 +661,8 @@ class EngineDebugger : public Object {

} // namespace core_bind

VARIANT_ENUM_CAST(core_bind::Engine::RenderLatencyMode);

VARIANT_ENUM_CAST(core_bind::ResourceLoader::ThreadLoadStatus);
VARIANT_ENUM_CAST(core_bind::ResourceLoader::CacheMode);

Expand Down
23 changes: 23 additions & 0 deletions doc/classes/Engine.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@
[/codeblocks]
</description>
</method>
<method name="get_render_latency_mode" qualifiers="const">
<return type="int" enum="Engine.RenderLatencyMode" />
<description>
Gets the low latency mode for the renderer. See [enum RenderLatencyMode] for options.
[b]Note:[/b] This setting should be used with care, as low latency mode prevents the CPU and GPU from running in parallel, which may adversely affect performance by as much as 30-50% under load. It is recommended that this option is exposed in your game's settings menu, so that users can select the best mode based on their preferences and hardware.
</description>
</method>
<method name="get_script_language" qualifiers="const">
<return type="ScriptLanguage" />
<param index="0" name="index" type="int" />
Expand Down Expand Up @@ -277,6 +284,14 @@
Registers the given [Object] [param instance] as a singleton, available globally under [param name]. Useful for plugins.
</description>
</method>
<method name="set_render_latency_mode">
<return type="void" />
<param index="0" name="render_latency_mode" type="int" enum="Engine.RenderLatencyMode" />
<description>
Sets the low latency mode for the renderer. See [enum RenderLatencyMode] for options. Equivalent to [member ProjectSettings.rendering/latency/low_latency_mode].
[b]Note:[/b] This setting should be used with care, as low latency mode prevents the CPU and GPU from running in parallel, which may adversely affect performance by as much as 30-50% under load. It is recommended that this option is exposed in your game's settings menu, so that users can select the best mode based on their preferences and hardware.
</description>
</method>
<method name="unregister_script_language">
<return type="int" enum="Error" />
<param index="0" name="language" type="ScriptLanguage" />
Expand Down Expand Up @@ -335,4 +350,12 @@
[b]Note:[/b] This does not automatically adjust [member physics_ticks_per_second]. With values above [code]1.0[/code] physics simulation may become less precise, as each physics tick will stretch over a larger period of engine time. If you're modifying [member Engine.time_scale] to speed up simulation by a large factor, consider also increasing [member physics_ticks_per_second] to make the simulation more reliable.
</member>
</members>
<constants>
<constant name="RENDER_LATENCY_PRIORITIZE_FRAMERATE" value="0" enum="RenderLatencyMode">
Tells the renderer to prioritize higher framerate by allowing the CPU to queue up additional frames before they're rendered by the GPU. This allows the CPU and GPU to work in tandem, improving the framerate and framepacing in complex scenes at the expense of input latency. Use this setting for slower-paced games, or intensive 3D applications including benchmarks and open-world games.
</constant>
<constant name="RENDER_LATENCY_PRIORITIZE_LOW_LATENCY" value="1" enum="RenderLatencyMode">
Tells the renderer to prioritize lower display latency by limiting how far the CPU is allowed to go ahead of the GPU when queueing frames. This can greatly help with input lag, at the cost of significantly reduced framerate in complex scenes. Use this setting for games with simple graphics where responsive control is important. Your results may vary based on platform, drivers, and scene contents.
</constant>
</constants>
</class>
9 changes: 9 additions & 0 deletions doc/classes/ProjectSettings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2590,6 +2590,15 @@
The VoxelGI quality to use. High quality leads to more precise lighting and better reflections, but is slower to render. This setting does not affect the baked data and doesn't require baking the [VoxelGI] again to apply.
[b]Note:[/b] This property is only read when the project starts. To control VoxelGI quality at runtime, call [method RenderingServer.voxel_gi_set_quality] instead.
</member>
<member name="rendering/latency/low_latency_mode" type="int" setter="" getter="" default="0">
Sets the low latency mode used by the renderer:
- [b]Prioritize Framerate[/b] tells the renderer to prioritize higher framerate by allowing the CPU to queue up additional frames before they're rendered by the GPU. This allows the CPU and GPU to work in tandem, improving the framerate and framepacing in complex scenes at the expense of input latency. Use this setting for slower-paced games, or intensive 3D applications including benchmarks and open-world games.
- [b]Prioritize Low Latency[/b] tells the renderer to prioritize lower display latency by limiting how far the CPU is allowed to go ahead of the GPU when queueing frames. This can greatly help with input lag, at the cost of significantly reduced framerate in complex scenes. Use this setting for games with simple graphics where responsive control is important. Your results may vary based on platform, drivers, and scene contents.
[b]Note:[/b] This setting should be used with care, as low latency mode prevents the CPU and GPU from running in parallel, which may adversely affect performance by as much as 30-50% under load. It is recommended that this option is exposed in your game's settings menu, so that users can select the best mode based on their preferences and hardware.
[b]Note:[/b] This property may be overridden with the [code]--renderer-latency[/code] command-line argument. When this argument is used, this project setting is ignored.
[b]Note:[/b] This property is only read when the project starts. To change the latency mode at runtime, call [method Engine.set_render_latency_mode] instead.
See also [member rendering/rendering_device/vsync/frame_queue_size] and [member rendering/rendering_device/vsync/swapchain_image_count].
</member>
<member name="rendering/lightmapping/bake_performance/max_rays_per_pass" type="int" setter="" getter="" default="32">
The maximum number of rays that can be thrown per pass when baking lightmaps with [LightmapGI]. Depending on the scene, adjusting this value may result in higher GPU utilization when baking lightmaps, leading to faster bake times.
</member>
Expand Down
4 changes: 4 additions & 0 deletions drivers/gles3/rasterizer_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ void RasterizerGLES3::end_frame(bool p_swap_buffers) {
void RasterizerGLES3::gl_end_frame(bool p_swap_buffers) {
if (p_swap_buffers) {
DisplayServer::get_singleton()->swap_buffers();

if (Engine::get_singleton()->get_render_latency_mode() == Engine::RENDER_LATENCY_PRIORITIZE_LOW_LATENCY) {
glFinish();
}
} else {
glFinish();
}
Expand Down
26 changes: 26 additions & 0 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ void Main::print_help(const char *p_binary) {
print_help_option("--rendering-method <renderer>", "Renderer name. Requires driver support.\n");
print_help_option("--rendering-driver <driver>", "Rendering driver (depends on display driver).\n");
print_help_option("--gpu-index <device_index>", "Use a specific GPU (run with --verbose to get a list of available devices).\n");
print_help_option("--renderer-latency <mode>", "Override the renderer low latency mode [\"high_framerate\", \"low_latency\"].\n");
print_help_option("--text-driver <driver>", "Text driver (used for font rendering, bidirectional support and shaping).\n");
print_help_option("--tablet-driver <driver>", "Pen tablet input driver.\n");
print_help_option("--headless", "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script.\n");
Expand Down Expand Up @@ -1020,6 +1021,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
String default_renderer = "";
String default_renderer_mobile = "";
String renderer_hints = "";
int renderer_latency_mode = -1;

packed_data = PackedData::get_singleton();
if (!packed_data) {
Expand Down Expand Up @@ -1217,6 +1219,22 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->print("Missing rendering driver argument, aborting.\n");
goto error;
}
} else if (arg == "--renderer-latency") {
if (N) {
if (N->get() == "high_framerate") {
renderer_latency_mode = Engine::RenderLatencyMode::RENDER_LATENCY_PRIORITIZE_FRAMERATE;
} else if (N->get() == "low_latency") {
renderer_latency_mode = Engine::RenderLatencyMode::RENDER_LATENCY_PRIORITIZE_LOW_LATENCY;
} else {
OS::get_singleton()->print("Unknown renderer latency mode, aborting.\nValid options are 'high_framerate' and 'low_latency'.\n");
goto error;
}

N = N->next();
} else {
OS::get_singleton()->print("Missing renderer latency mode argument, aborting.\n");
goto error;
}
} else if (arg == "-f" || arg == "--fullscreen") { // force fullscreen
init_fullscreen = true;
window_mode = DisplayServer::WINDOW_MODE_FULLSCREEN;
Expand Down Expand Up @@ -2583,6 +2601,14 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
}

GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "rendering/latency/low_latency_mode", PROPERTY_HINT_ENUM, "Prioritize Framerate,Prioritize Low Latency"), 0);

if (renderer_latency_mode >= 0) {
Engine::get_singleton()->set_render_latency_mode(Engine::RenderLatencyMode(renderer_latency_mode));
} else {
Engine::get_singleton()->set_render_latency_mode(Engine::RenderLatencyMode((int)GLOBAL_GET("rendering/latency/low_latency_mode")));
}

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.
GLOBAL_DEF_RST("audio/driver/output_latency.web", 50);
Expand Down
1 change: 1 addition & 0 deletions misc/dist/shell/_godot.zsh-completion
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ _arguments \
"--rendering-method[set the renderer]:renderer name:((forward_plus\:'Desktop renderer' mobile\:'Desktop and mobile renderer' gl_compatibility\:'Desktop, mobile and web renderer'))" \
"--rendering-driver[set the rendering driver]:rendering driver name:((vulkan\:'Vulkan renderer' opengl3\:'OpenGL ES 3.0 renderer' dummy\:'Dummy renderer'))" \
"--gpu-index[use a specific GPU (run with --verbose to get available device list)]:device index" \
"--renderer-latency[set the renderer latency mode]:latency mode:((high_framerate\:'Prioritize high framerate' low_latency\:'Prioritize low latency'))" \
'--text-driver[set the text driver]:text driver name' \
'--tablet-driver[set the pen tablet input driver]:tablet driver name' \
'--headless[enable headless mode (--display-driver headless --audio-driver Dummy), useful for servers and with --script]' \
Expand Down
5 changes: 5 additions & 0 deletions misc/dist/shell/godot.bash-completion
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ _complete_godot_options() {
--rendering-method
--rendering-driver
--gpu-index
--renderer-latency
--text-driver
--tablet-driver
--headless
Expand Down Expand Up @@ -129,6 +130,10 @@ _complete_godot_bash() {
local IFS=$' \n\t'
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "vulkan opengl3 dummy" -- "$cur"))
elif [[ $prev == "--renderer-latency" ]]; then
local IFS=$' \n\t'
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "high_framerate low_latency" -- "$cur"))
elif [[ $prev == "--xr-mode" ]]; then
local IFS=$' \n\t'
# shellcheck disable=SC2207
Expand Down
7 changes: 7 additions & 0 deletions misc/dist/shell/godot.fish
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ function godot_rendering_driver_args
echo -e "dummy\tDummy renderer"
end

function godot_renderer_latency_args
# Use a function instead of a fixed string to customize the argument descriptions.
echo -e "high_framerate\tPrioritize high framerate"
echo -e "low_latency\tPrioritize low latency"
end

# Erase existing completions for Godot.
complete -c godot -e

Expand Down Expand Up @@ -64,6 +70,7 @@ complete -c godot -l display-driver -d "Set the display driver" -x
complete -c godot -l rendering-method -d "Set the renderer" -x -a "(godot_rendering_method_args)"
complete -c godot -l rendering-driver -d "Set the rendering driver" -x -a "(godot_rendering_driver_args)"
complete -c godot -l gpu-index -d "Use a specific GPU (run with --verbose to get available device list)" -x
complete -c godot -l renderer-latency -d "Set the renderer latency mode" -x -a "(godot_renderer_latency_args)"
complete -c godot -l text-driver -d "Set the text driver" -x
complete -c godot -l tablet-driver -d "Set the pen tablet input driver" -x
complete -c godot -l headless -d "Enable headless mode (--display-driver headless --audio-driver Dummy). Useful for servers and with --script"
Expand Down
4 changes: 4 additions & 0 deletions servers/rendering/rendering_device.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5763,6 +5763,10 @@ void RenderingDevice::swap_buffers() {
_end_frame();
_execute_frame(true);

if (Engine::get_singleton()->get_render_latency_mode() == Engine::RENDER_LATENCY_PRIORITIZE_LOW_LATENCY) {
_stall_for_previous_frames();
}

// Advance to the next frame and begin recording again.
frame = (frame + 1) % frames.size();
_begin_frame();
Expand Down

0 comments on commit 1b981ff

Please sign in to comment.