From f1fd8b12e2f0428d4dea31e80ca89db09f8fddf5 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 15 Aug 2024 17:25:02 +0200 Subject: [PATCH] android: do all rumble/haptic on background thread Haptic effects for the virtual gamepad must be done on a background thread. Not sure about gamepad rumble but it shouldn't hurt. Reuse haptic duration for haptic power (0-100). Fix crash when power is zero (createOneShot needs power >= 1) --- core/ui/gui.cpp | 2 +- .../java/com/flycast/emulator/Emulator.java | 6 +-- .../emulator/periph/InputDeviceManager.java | 51 +++++++++++-------- .../emulator/periph/VibratorThread.java | 19 ++++--- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 270ad1156..58291ad8c 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -1441,7 +1441,7 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) if (gamepad->is_virtual_gamepad()) { header("Haptic"); - OptionSlider("Power", config::VirtualGamepadVibration, 0, 60, "Haptic feedback power"); + OptionSlider("Power", config::VirtualGamepadVibration, 0, 100, "Haptic feedback power", "%d%%"); } else if (gamepad->is_rumble_enabled()) { diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java index 1bddf2a31..28d8ba14e 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/Emulator.java @@ -22,7 +22,7 @@ public class Emulator extends Application { public static final int MDT_Microphone = 2; public static final int MDT_None = 8; - public static int vibrationDuration = 20; + public static int vibrationPower = 80; public static int[] maple_devices = { MDT_None, @@ -42,7 +42,7 @@ public class Emulator extends Application { * */ public void getConfigurationPrefs() { - Emulator.vibrationDuration = JNIdc.getVirtualGamepadVibration(); + Emulator.vibrationPower = JNIdc.getVirtualGamepadVibration(); JNIdc.getControllers(maple_devices, maple_expansion_devices); } @@ -54,7 +54,7 @@ public class Emulator extends Application { { Log.i("flycast", "SaveAndroidSettings: saving preferences"); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); - Emulator.vibrationDuration = JNIdc.getVirtualGamepadVibration(); + Emulator.vibrationPower = JNIdc.getVirtualGamepadVibration(); JNIdc.getControllers(maple_devices, maple_expansion_devices); prefs.edit() diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java index 65ef5e60a..a8078f063 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/InputDeviceManager.java @@ -105,12 +105,31 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene } } + private void vibrate(Vibrator vibrator, long duration_ms, float power) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + int ipow = Math.min((int)(power * 255), 255); + if (ipow >= 1) + vibrator.vibrate(VibrationEffect.createOneShot(duration_ms, ipow)); + else + vibrator.cancel(); + } + else + vibrator.vibrate(duration_ms); + } + // Called from native code + // returns false if the device has no vibrator private boolean rumble(int i, float power, float inclination, int duration_ms) { Vibrator vibrator = getVibrator(i); if (vibrator == null) return false; + if (i == VIRTUAL_GAMEPAD_ID) { + if (Emulator.vibrationPower == 0) + return true; + power *= Emulator.vibrationPower / 100.f; + } VibrationParams params; synchronized (this) { @@ -120,25 +139,15 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene vibParams.put(i, params); } } - if (power == 0) { - if (params.power != 0) - vibrator.cancel(); - } else { - if (inclination > 0) { - params.inclination = inclination * power; - params.stopTime = System.currentTimeMillis() + duration_ms; - } - else { - params.inclination = 0; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - vibrator.vibrate(VibrationEffect.createOneShot(duration_ms, (int)(power * 255))); - else - vibrator.vibrate(duration_ms); + if (power != 0) { + params.stopTime = System.currentTimeMillis() + duration_ms; if (inclination > 0) - VibratorThread.getInstance().setVibrating(); + params.inclination = inclination * power; + else + params.inclination = 0; } params.power = power; + VibratorThread.getInstance().setVibrating(); return true; } @@ -164,19 +173,19 @@ public final class InputDeviceManager implements InputManager.InputDeviceListene synchronized (this) { params = vibParams.get(i); } - if (vibrator == null || params == null || params.power == 0 || params.inclination == 0) + if (vibrator == null || params == null) return false; long remTime = params.stopTime - System.currentTimeMillis(); - if (remTime <= 0) { + if (remTime <= 0 || params.power == 0) { params.power = 0; params.inclination = 0; vibrator.cancel(); return false; } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - vibrator.vibrate(VibrationEffect.createOneShot(remTime, (int)(params.inclination * remTime * 255))); + if (params.inclination > 0) + vibrate(vibrator, remTime, params.inclination * remTime); else - vibrator.vibrate(remTime); + vibrate(vibrator, remTime, params.power); return true; } diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VibratorThread.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VibratorThread.java index b7e7b1051..9b7068c7d 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VibratorThread.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/periph/VibratorThread.java @@ -36,6 +36,7 @@ public class VibratorThread extends Thread { private VibrationEffect clickEffect = null; int clickDuration = 0; private static VibratorThread INSTANCE = null; + private static final int LEGACY_VIBRATION_DURATION = 20; // ms public static VibratorThread getInstance() { synchronized (VibratorThread.class) { @@ -52,7 +53,11 @@ public class VibratorThread extends Thread { private Vibrator getVibrator(int i) { if (i == InputDeviceManager.VIRTUAL_GAMEPAD_ID) { - return (Vibrator) Emulator.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); + if (Emulator.vibrationPower > 0) + return (Vibrator) Emulator.getAppContext().getSystemService(Context.VIBRATOR_SERVICE); + else + // vibration disabled + return null; } else { InputDevice device = InputDevice.getDevice(i); @@ -111,7 +116,7 @@ public class VibratorThread extends Thread { } public void click() { - if (Emulator.vibrationDuration > 0) { + if (Emulator.vibrationPower > 0) { synchronized (this) { click = true; notify(); @@ -126,17 +131,16 @@ public class VibratorThread extends Thread { return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - if (clickEffect == null || clickDuration != Emulator.vibrationDuration) + if (clickEffect == null) { - clickDuration = Emulator.vibrationDuration; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) clickEffect = VibrationEffect.createPredefined(VibrationEffect.EFFECT_HEAVY_CLICK); else - clickEffect = VibrationEffect.createOneShot(clickDuration, VibrationEffect.DEFAULT_AMPLITUDE); + clickEffect = VibrationEffect.createOneShot(LEGACY_VIBRATION_DURATION, VibrationEffect.DEFAULT_AMPLITUDE); } vibrator.vibrate(clickEffect); } else { - vibrator.vibrate(Emulator.vibrationDuration); + vibrator.vibrate(LEGACY_VIBRATION_DURATION); } } @@ -144,8 +148,7 @@ public class VibratorThread extends Thread { { // FIXME possible race condition synchronized (this) { - if (nextRumbleUpdate == 0) - nextRumbleUpdate = System.currentTimeMillis() + 16667; + nextRumbleUpdate = 1; notify(); } }