Cleanup Android input on render thread settings

Follow up to https://github.com/godotengine/godot/pull/93933
Clean up the set of settings use to control whether Android input should be dispatched on the render thread.

Addresses comments in https://github.com/godotengine/godot/pull/93933#issuecomment-2210437977
This commit is contained in:
Fredia Huya-Kouadio 2024-07-07 14:16:29 -07:00
parent f3af22b10b
commit 5e59819727
17 changed files with 63 additions and 89 deletions

View File

@ -1022,7 +1022,7 @@ void Input::parse_input_event(const Ref<InputEvent> &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) {

View File

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

View File

@ -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].
</member>
<member name="input/buffering/agile_event_flushing" type="bool" setter="" getter="">
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.
</member>
<member name="input/buffering/use_accumulated_input" type="bool" setter="" getter="">
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.
</member>
<member name="interface/editor/accept_dialog_cancel_ok_buttons" type="int" setter="" getter="">
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.

View File

@ -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.
</member>
<member name="input_devices/buffering/android/use_accumulated_input" type="bool" setter="" getter="" default="true">
If [code]true[/code], multiple input events will be accumulated into a single input event when possible.
</member>
<member name="input_devices/buffering/android/use_input_buffering" type="bool" setter="" getter="" default="true">
If [code]true[/code], input events will be buffered prior to being dispatched.
</member>
<member name="input_devices/compatibility/legacy_just_pressed_behavior" type="bool" setter="" getter="" default="false">
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.

View File

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

View File

@ -474,11 +474,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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<ConfigFile> 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")

View File

@ -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) {

View File

@ -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();
}

View File

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

View File

@ -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;
}

View File

@ -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

View File

@ -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.

View File

@ -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)
}
}
}

View File

@ -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();
}

View File

@ -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();
}
/**

View File

@ -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;
}
}

View File

@ -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