diff --git a/core/input/input.cpp b/core/input/input.cpp index 56f616fac4b..a4f6d969113 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -1022,7 +1022,7 @@ void Input::parse_input_event(const Ref &p_event) { if (buffered_events.is_empty() || !buffered_events.back()->get()->accumulate(p_event)) { buffered_events.push_back(p_event); } - } else if (use_input_buffering) { + } else if (agile_input_event_flushing) { buffered_events.push_back(p_event); } else { _parse_input_event_impl(p_event, false); @@ -1053,12 +1053,12 @@ void Input::flush_buffered_events() { } } -bool Input::is_using_input_buffering() { - return use_input_buffering; +bool Input::is_agile_input_event_flushing() { + return agile_input_event_flushing; } -void Input::set_use_input_buffering(bool p_enable) { - use_input_buffering = p_enable; +void Input::set_agile_input_event_flushing(bool p_enable) { + agile_input_event_flushing = p_enable; } void Input::set_use_accumulated_input(bool p_enable) { diff --git a/core/input/input.h b/core/input/input.h index 4daea0c9e83..89e48f53d74 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -128,7 +128,7 @@ private: bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; - bool use_input_buffering = false; + bool agile_input_event_flushing = false; bool use_accumulated_input = true; int mouse_from_touch_index = -1; @@ -367,8 +367,8 @@ public: void flush_frame_parsed_events(); #endif void flush_buffered_events(); - bool is_using_input_buffering(); - void set_use_input_buffering(bool p_enable); + bool is_agile_input_event_flushing(); + void set_agile_input_event_flushing(bool p_enable); void set_use_accumulated_input(bool p_enable); bool is_using_accumulated_input(); diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index c7ff543b664..e4dab85038e 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -596,6 +596,16 @@ The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url]. To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser]. + + If [code]true[/code], input events will be flushed just before every idle and physics frame. + If [code]false[/code], these events will be flushed only once per process frame, between iterations of the engine. + Enabling this setting can greatly improve input responsiveness, especially in devices that struggle to run at the project's intended frame rate. + + + If [code]true[/code], similar input events sent by the operating system are accumulated. When input accumulation is enabled, all input events generated during a frame will be merged and emitted when the frame is done rendering. Therefore, this limits the number of input method calls per second to the rendering FPS. + Input accumulation can be disabled to get slightly more precise/reactive input at the cost of increased CPU usage. + [b]Note:[/b] Input accumulation is [i]enabled[/i] by default. + How to position the Cancel and OK buttons in the editor's [AcceptDialog]s. Different platforms have different standard behaviors for this, which can be overridden using this setting. This is useful if you use Godot both on Windows and macOS/Linux and your Godot muscle memory is stronger than your OS specific one. - [b]Auto[/b] follows the platform convention: Cancel first on macOS and Linux, OK first on Windows. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 037f2f38110..a30d71a0174 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1394,12 +1394,6 @@ Enabling this can greatly improve the responsiveness to input, specially in devices that need to run multiple physics frames per visible (process) frame, because they can't run at the target frame rate. [b]Note:[/b] Currently implemented only on Android. - - If [code]true[/code], multiple input events will be accumulated into a single input event when possible. - - - If [code]true[/code], input events will be buffered prior to being dispatched. - If [code]true[/code], [method Input.is_action_just_pressed] and [method Input.is_action_just_released] will only return [code]true[/code] if the action is still in the respective state, i.e. an action that is pressed [i]and[/i] released on the same frame will be missed. If [code]false[/code], no input will be lost. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 632b36c7059..85847c6d486 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -6431,7 +6431,6 @@ EditorNode::EditorNode() { // No scripting by default if in editor (except for tool). ScriptServer::set_scripting_enabled(false); - Input::get_singleton()->set_use_accumulated_input(true); if (!DisplayServer::get_singleton()->is_touchscreen_available()) { // Only if no touchscreen ui hint, disable emulation just in case. Input::get_singleton()->set_emulate_touch_from_mouse(false); @@ -6480,6 +6479,14 @@ EditorNode::EditorNode() { SurfaceUpgradeTool::get_singleton()->begin_upgrade(); } + { + bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing"); + bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input"); + + Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing); + Input::get_singleton()->set_use_accumulated_input(use_accumulated_input); + } + { int display_scale = EDITOR_GET("interface/editor/display_scale"); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 5d3cc80da9a..d96e2b71961 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -474,11 +474,6 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/editor/vsync_mode", 1, "Disabled,Enabled,Adaptive,Mailbox") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/update_continuously", false, "") -#ifdef ANDROID_ENABLED - EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) - EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "interface/editor/android/use_input_buffering", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) -#endif - // Inspector EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "interface/inspector/max_array_dictionary_items_per_page", 20, "10,100,1") EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/inspector/show_low_level_opentype_features", false, "") @@ -866,6 +861,9 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { /* Extra config */ + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "input/buffering/agile_event_flushing", false, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + EDITOR_SETTING_USAGE(Variant::BOOL, PROPERTY_HINT_NONE, "input/buffering/use_accumulated_input", true, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED) + // TRANSLATORS: Project Manager here refers to the tool used to create/manage Godot projects. EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/sorting_order", 0, "Last Edited,Name,Path") EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "project_manager/directory_naming_convention", 1, "No convention,kebab-case,snake_case,camelCase,PascalCase,Title Case") diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 52d7b14b655..59f45ef5dbd 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -1053,6 +1053,14 @@ ProjectManager::ProjectManager() { } EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come. + { + bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing"); + bool use_accumulated_input = EDITOR_GET("input/buffering/use_accumulated_input"); + + Input::get_singleton()->set_agile_input_event_flushing(agile_input_event_flushing); + Input::get_singleton()->set_use_accumulated_input(use_accumulated_input); + } + int display_scale = EDITOR_GET("interface/editor/display_scale"); switch (display_scale) { diff --git a/main/main.cpp b/main/main.cpp index e6be23034d5..404ae84f759 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -2514,7 +2514,6 @@ error: memdelete(message_queue); } - OS::get_singleton()->benchmark_end_measure("Startup", "Core"); OS::get_singleton()->benchmark_end_measure("Startup", "Main::Setup"); #if defined(STEAMAPI_ENABLED) @@ -2926,7 +2925,8 @@ Error Main::setup2(bool p_show_boot_logo) { Input *id = Input::get_singleton(); if (id) { - agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); + bool agile_input_event_flushing = GLOBAL_DEF("input_devices/buffering/agile_event_flushing", false); + id->set_agile_input_event_flushing(agile_input_event_flushing); if (bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_touch_from_mouse", false)) && !(editor || project_manager)) { @@ -2939,8 +2939,6 @@ Error Main::setup2(bool p_show_boot_logo) { id->set_emulate_mouse_from_touch(bool(GLOBAL_DEF_BASIC("input_devices/pointing/emulate_mouse_from_touch", true))); } - GLOBAL_DEF("input_devices/buffering/android/use_accumulated_input", true); - GLOBAL_DEF("input_devices/buffering/android/use_input_buffering", true); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_long_press_as_right_click", false); GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); @@ -3977,7 +3975,6 @@ uint32_t Main::hide_print_fps_attempts = 3; uint32_t Main::frame = 0; bool Main::force_redraw_requested = false; int Main::iterating = 0; -bool Main::agile_input_event_flushing = false; bool Main::is_iterating() { return iterating > 0; @@ -4038,7 +4035,7 @@ bool Main::iteration() { NavigationServer3D::get_singleton()->sync(); for (int iters = 0; iters < advance.physics_steps; ++iters) { - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { + if (Input::get_singleton()->is_agile_input_event_flushing()) { Input::get_singleton()->flush_buffered_events(); } @@ -4095,7 +4092,7 @@ bool Main::iteration() { Engine::get_singleton()->_in_physics = false; } - if (Input::get_singleton()->is_using_input_buffering() && agile_input_event_flushing) { + if (Input::get_singleton()->is_agile_input_event_flushing()) { Input::get_singleton()->flush_buffered_events(); } @@ -4167,11 +4164,6 @@ bool Main::iteration() { iterating--; - // Needed for OSs using input buffering regardless accumulation (like Android) - if (Input::get_singleton()->is_using_input_buffering() && !agile_input_event_flushing) { - Input::get_singleton()->flush_buffered_events(); - } - if (movie_writer) { movie_writer->add_frame(); } diff --git a/main/main.h b/main/main.h index b1cfcd3c2d3..6dd2ff7d7a8 100644 --- a/main/main.h +++ b/main/main.h @@ -58,7 +58,6 @@ class Main { static uint32_t frame; static bool force_redraw_requested; static int iterating; - static bool agile_input_event_flushing; public: static bool is_cmdline_tool(); diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp index 06b304dcde3..8dc0e869d0e 100644 --- a/platform/android/display_server_android.cpp +++ b/platform/android/display_server_android.cpp @@ -651,7 +651,6 @@ DisplayServerAndroid::DisplayServerAndroid(const String &p_rendering_driver, Dis #endif Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events); - Input::get_singleton()->set_use_input_buffering(true); // Needed because events will come directly from the UI thread r_error = OK; } diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt index dad397de610..5515347bd61 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt @@ -117,10 +117,6 @@ open class GodotEditor : GodotActivity() { val longPressEnabled = enableLongPressGestures() val panScaleEnabled = enablePanAndScaleGestures() - val useInputBuffering = useInputBuffering() - val useAccumulatedInput = useAccumulatedInput() - GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) - checkForProjectPermissionsToEnable() runOnUiThread { @@ -128,7 +124,6 @@ open class GodotEditor : GodotActivity() { godotFragment?.godot?.renderView?.inputHandler?.apply { enableLongPress(longPressEnabled) enablePanningAndScalingGestures(panScaleEnabled) - enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) } } } @@ -279,13 +274,6 @@ open class GodotEditor : GodotActivity() { protected open fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/touchscreen/enable_pan_and_scale_gestures")) - /** - * Use input buffering for the Godot Android editor. - */ - protected open fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_input_buffering")) - - protected open fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getEditorSetting("interface/editor/android/use_accumulated_input")) - /** * Whether we should launch the new godot instance in an adjacent window * @see https://developer.android.com/reference/android/content/Intent#FLAG_ACTIVITY_LAUNCH_ADJACENT diff --git a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt index f50b5577c30..2bcfba559c5 100644 --- a/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt +++ b/platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt @@ -45,10 +45,6 @@ class GodotGame : GodotEditor() { override fun enablePanAndScaleGestures() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - override fun useInputBuffering() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) - - override fun useAccumulatedInput() = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) - override fun checkForProjectPermissionsToEnable() { // Nothing to do.. by the time we get here, the project permissions will have already // been requested by the Editor window. diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt index c188a97ca53..7e2a44ab390 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/Godot.kt +++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.kt @@ -628,26 +628,19 @@ class Godot(private val context: Context) : SensorEventListener { private fun onGodotSetupCompleted() { Log.v(TAG, "OnGodotSetupCompleted") - if (!isEditorBuild()) { - // These properties are defined after Godot setup completion, so we retrieve them here. - val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) - val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) - val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") + // These properties are defined after Godot setup completion, so we retrieve them here. + val longPressEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_long_press_as_right_click")) + val panScaleEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/pointing/android/enable_pan_and_scale_gestures")) + val rotaryInputAxisValue = GodotLib.getGlobal("input_devices/pointing/android/rotary_input_scroll_axis") - val useInputBuffering = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_input_buffering")) - val useAccumulatedInput = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("input_devices/buffering/android/use_accumulated_input")) - GodotLib.updateInputDispatchSettings(useAccumulatedInput, useInputBuffering) - - runOnUiThread { - renderView?.inputHandler?.apply { - enableLongPress(longPressEnabled) - enablePanningAndScalingGestures(panScaleEnabled) - enableInputDispatchToRenderThread(!useInputBuffering && !useAccumulatedInput) - try { - setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) - } catch (e: NumberFormatException) { - Log.w(TAG, e) - } + runOnUiThread { + renderView?.inputHandler?.apply { + enableLongPress(longPressEnabled) + enablePanningAndScalingGestures(panScaleEnabled) + try { + setRotaryInputAxis(Integer.parseInt(rotaryInputAxisValue)) + } catch (e: NumberFormatException) { + Log.w(TAG, e) } } } diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java index 37e889daf7c..909daf05c97 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java +++ b/platform/android/java/lib/src/org/godotengine/godot/GodotLib.java @@ -242,9 +242,8 @@ public class GodotLib { public static native void onRendererPaused(); /** - * Invoked on the GL thread to update the input dispatch settings - * @param useAccumulatedInput True to use accumulated input, false otherwise - * @param useInputBuffering True to use input buffering, false otherwise + * @return true if input must be dispatched from the render thread. If false, input is + * dispatched from the UI thread. */ - public static native void updateInputDispatchSettings(boolean useAccumulatedInput, boolean useInputBuffering); + public static native boolean shouldDispatchInputToRenderThread(); } diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java index 889618914d6..afe570dcc6b 100644 --- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java +++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotInputHandler.java @@ -77,8 +77,6 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { private int rotaryInputAxis = ROTARY_INPUT_VERTICAL_AXIS; - private boolean dispatchInputToRenderThread = false; - public GodotInputHandler(GodotRenderView godotView) { final Context context = godotView.getView().getContext(); mRenderView = godotView; @@ -110,20 +108,12 @@ public class GodotInputHandler implements InputManager.InputDeviceListener { this.godotGestureHandler.setPanningAndScalingEnabled(enable); } - /** - * Specifies whether input should be dispatch on the UI thread or on the Render thread. - * @param enable true to dispatch input on the Render thread, false to dispatch input on the UI thread - */ - public void enableInputDispatchToRenderThread(boolean enable) { - this.dispatchInputToRenderThread = enable; - } - /** * @return true if input must be dispatched from the render thread. If false, input is * dispatched from the UI thread. */ private boolean shouldDispatchInputToRenderThread() { - return dispatchInputToRenderThread; + return GodotLib.shouldDispatchInputToRenderThread(); } /** diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp index 87d4281c5a8..11e897facf1 100644 --- a/platform/android/java_godot_lib_jni.cpp +++ b/platform/android/java_godot_lib_jni.cpp @@ -550,10 +550,11 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIE } } -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering) { - if (Input::get_singleton()) { - Input::get_singleton()->set_use_accumulated_input(p_use_accumulated_input); - Input::get_singleton()->set_use_input_buffering(p_use_input_buffering); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz) { + Input *input = Input::get_singleton(); + if (input) { + return !input->is_agile_input_event_flushing(); } + return false; } } diff --git a/platform/android/java_godot_lib_jni.h b/platform/android/java_godot_lib_jni.h index 852c475e7e9..d027da31fa4 100644 --- a/platform/android/java_godot_lib_jni.h +++ b/platform/android/java_godot_lib_jni.h @@ -69,7 +69,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResu JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onNightModeChanged(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererResumed(JNIEnv *env, jclass clazz); JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_onRendererPaused(JNIEnv *env, jclass clazz); -JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_updateInputDispatchSettings(JNIEnv *env, jclass clazz, jboolean p_use_accumulated_input, jboolean p_use_input_buffering); +JNIEXPORT jboolean JNICALL Java_org_godotengine_godot_GodotLib_shouldDispatchInputToRenderThread(JNIEnv *env, jclass clazz); } #endif // JAVA_GODOT_LIB_JNI_H