From 5685b5a9a13ffe7b28cba5a3cd373b3a1720bc8d Mon Sep 17 00:00:00 2001 From: warmenhoven Date: Mon, 13 Feb 2023 15:49:35 -0500 Subject: [PATCH] mFI on OSX (#14975) This adds mFI as a controller driver for OSX, as well as adding rumble support for mFI controllers. Also add support for the Home button. Also fixed a couple warnings. --- configuration.c | 2 +- gfx/drivers/metal.m | 4 +- input/drivers_joypad/mfi_joypad.m | 235 +++++++++++++++--- input/input_autodetect_builtin.c | 6 +- .../RetroArch_Metal.xcodeproj/project.pbxproj | 10 + .../RetroArch_iOS13.xcodeproj/project.pbxproj | 2 + 6 files changed, 224 insertions(+), 35 deletions(-) diff --git a/configuration.c b/configuration.c index 6c89b29e0a..d4903f1ef7 100644 --- a/configuration.c +++ b/configuration.c @@ -645,7 +645,7 @@ static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_ANDROID; static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_SDL; #elif defined(DJGPP) static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_DOS; -#elif defined(IOS) +#elif defined(HAVE_MFI) static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_MFI; #elif defined(HAVE_HID) static const enum joypad_driver_enum JOYPAD_DEFAULT_DRIVER = JOYPAD_HID; diff --git a/gfx/drivers/metal.m b/gfx/drivers/metal.m index 618c5ecd5e..5405005c13 100644 --- a/gfx/drivers/metal.m +++ b/gfx/drivers/metal.m @@ -320,9 +320,9 @@ [self _drawCore]; [self _drawMenu:video_info]; - id rce = _context.rce; - #ifdef HAVE_OVERLAY + id rce = _context.rce; + if (_overlay.enabled) { [_context resetRenderViewport:_overlay.fullscreen ? kFullscreenViewport : kVideoViewport]; diff --git a/input/drivers_joypad/mfi_joypad.m b/input/drivers_joypad/mfi_joypad.m index 6a86441b81..cb04645ec4 100644 --- a/input/drivers_joypad/mfi_joypad.m +++ b/input/drivers_joypad/mfi_joypad.m @@ -25,6 +25,7 @@ #include "../../tasks/tasks_internal.h" #import +#import #ifndef MAX_MFI_CONTROLLERS #define MAX_MFI_CONTROLLERS 4 @@ -35,19 +36,25 @@ enum GCCONTROLLER_PLAYER_INDEX_UNSET = -1, }; +@class MFIRumbleController; + /* TODO/FIXME - static globals */ static uint32_t mfi_buttons[MAX_USERS]; static int16_t mfi_axes[MAX_USERS][4]; static uint32_t mfi_controllers[MAX_MFI_CONTROLLERS]; +static MFIRumbleController *mfi_rumblers[MAX_MFI_CONTROLLERS]; static NSMutableArray *mfiControllers; +static bool mfi_inited; static bool apple_gamecontroller_available(void) { +#if defined(IOS) int major, minor; get_ios_version(&major, &minor); if (major <= 6) return false; +#endif return true; } @@ -100,20 +107,21 @@ static void apple_gamecontroller_joypad_poll_internal(GCController *controller) *buttons |= gp.rightShoulder.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R) : 0; *buttons |= gp.leftTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L2) : 0; *buttons |= gp.rightTrigger.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R2) : 0; -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 120100 || __TV_OS_VERSION_MAX_ALLOWED >= 120100 - if (@available(iOS 12.1, *)) +#if OSX || __IPHONE_OS_VERSION_MAX_ALLOWED >= 120100 || __TV_OS_VERSION_MAX_ALLOWED >= 120100 + if (@available(iOS 12.1, macOS 10.15, *)) { *buttons |= gp.leftThumbstickButton.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_L3) : 0; *buttons |= gp.rightThumbstickButton.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_R3) : 0; } #endif -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 || __TV_OS_VERSION_MAX_ALLOWED >= 130000 - if (@available(iOS 13, *)) +#if OSX || __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 || __TV_OS_VERSION_MAX_ALLOWED >= 130000 + if (@available(iOS 13, tvOS 13, macOS 10.15, *)) { /* Support "Options" button present in PS4 / XBox One controllers */ *buttons |= gp.buttonOptions.pressed ? (1 << RETRO_DEVICE_ID_JOYPAD_SELECT) : 0; - + *buttons |= gp.buttonHome.pressed ? (1 << RARCH_FIRST_CUSTOM_BIND) : 0; + /* Support buttons that aren't supported by older mFi controller via "hotkey" combinations: * * LS + Menu => Select @@ -171,37 +179,32 @@ static void apple_gamecontroller_joypad_poll(void) apple_gamecontroller_joypad_poll_internal(controller); } -/* GCGamepad is deprecated */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" -static void apple_gamecontroller_joypad_register(GCGamepad *gamepad) +static void apple_gamecontroller_joypad_register(GCController *controller) { #ifdef __IPHONE_14_0 /* Don't let tvOS or iOS do anything with **our** buttons!! * iOS will start a screen recording if you hold or doubleclick * the OPTIONS button, we don't want that. */ - if (@available(iOS 14.0, tvOS 14.0, *)) + if (@available(iOS 14.0, tvOS 14.0, macOS 10.15, *)) { - GCExtendedGamepad *gp = (GCExtendedGamepad *)gamepad.controller.extendedGamepad; + GCExtendedGamepad *gp = (GCExtendedGamepad *)controller.extendedGamepad; gp.buttonOptions.preferredSystemGestureState = GCSystemGestureStateDisabled; gp.buttonMenu.preferredSystemGestureState = GCSystemGestureStateDisabled; gp.buttonHome.preferredSystemGestureState = GCSystemGestureStateDisabled; } #endif - gamepad.valueChangedHandler = ^(GCGamepad *updateGamepad, GCControllerElement *element) - { - apple_gamecontroller_joypad_poll_internal(updateGamepad.controller); - }; - /* controllerPausedHandler is deprecated in favor * of being able to deal with the menu * button as any other button */ if (@available(iOS 13, *)) return; +/* GCGamepad is deprecated */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated" { - gamepad.controller.controllerPausedHandler = ^(GCController *controller) + controller.controllerPausedHandler = ^(GCController *controller) { uint32_t slot = (uint32_t)controller.playerIndex; @@ -256,14 +259,153 @@ static void apple_gamecontroller_joypad_register(GCGamepad *gamepad) }); }; } -} #pragma clang diagnostic pop +} static void mfi_joypad_autodetect_add(unsigned autoconf_pad) { input_autoconfigure_connect("mFi Controller", NULL, mfi_joypad.ident, autoconf_pad, 0, 0); } +#define MFI_RUMBLE_AVAIL API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) +@interface MFIRumbleController : NSObject +@property (nonatomic, strong, readonly) GCController *controller; +@property (nonatomic, strong) CHHapticEngine *engine MFI_RUMBLE_AVAIL; +@property (nonatomic, strong, readonly) id strongPlayer MFI_RUMBLE_AVAIL; +@property (nonatomic, strong, readonly) id weakPlayer MFI_RUMBLE_AVAIL; +@end + +@implementation MFIRumbleController +@synthesize strongPlayer = _strongPlayer; +@synthesize weakPlayer = _weakPlayer; + +- (instancetype)initWithController:(GCController*)controller MFI_RUMBLE_AVAIL +{ + if (self = [super init]) + { + if (!controller.haptics) + return self; + + _controller = controller; + + [self setupEngine]; + if (!self.engine) + return self; + + _strongPlayer = [self createPlayerWithSharpness:1.0f]; + _weakPlayer = [self createPlayerWithSharpness:0.5f]; + } + return self; +} + +- (void)setupEngine MFI_RUMBLE_AVAIL +{ + if (self.engine) + return; + if (!self.controller) + return; + + CHHapticEngine *engine = [self.controller.haptics createEngineWithLocality:GCHapticsLocalityDefault]; + NSError *error; + [engine startAndReturnError:&error]; + if (error) + return; + + self.engine = engine; + + __weak MFIRumbleController *weakSelf = self; + self.engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) { + MFIRumbleController *strongSelf = weakSelf; + if (!strongSelf) + return; + + [strongSelf shutdown]; + }; + self.engine.resetHandler = ^{ + MFIRumbleController *strongSelf = weakSelf; + if (!strongSelf) + return; + + [strongSelf.engine startAndReturnError:nil]; + }; +} + +- (id)createPlayerWithSharpness:(float)sharpness MFI_RUMBLE_AVAIL +{ + if (!self.controller) + return nil; + + [self setupEngine]; + if (!self.engine) + return nil; + + CHHapticEventParameter *sharp, *intense; + CHHapticEvent *event; + CHHapticPattern *pattern; + NSError *error; + + sharp = [[CHHapticEventParameter alloc] + initWithParameterID:CHHapticEventParameterIDHapticSharpness + value:sharpness]; + intense = [[CHHapticEventParameter alloc] + initWithParameterID:CHHapticEventParameterIDHapticIntensity + value:1.0f]; + event = [[CHHapticEvent alloc] + initWithEventType:CHHapticEventTypeHapticContinuous + parameters:[NSArray arrayWithObjects:sharp, intense, nil] + relativeTime:0 + duration:GCHapticDurationInfinite]; + pattern = [[CHHapticPattern alloc] + initWithEvents:[NSArray arrayWithObject:event] + parameters:[[NSArray alloc] init] + error:&error]; + + if (error) + return nil; + + id player = [self.engine createPlayerWithPattern:pattern error:&error]; + if (error) + return nil; + player.isMuted = YES; + return player; +} + +- (id)strongPlayer +{ + _strongPlayer = _strongPlayer ?: [self createPlayerWithSharpness:1.0]; + return _strongPlayer; +} + +- (id)weakPlayer +{ + _weakPlayer = _weakPlayer ?: [self createPlayerWithSharpness:0.5f]; + return _weakPlayer; +} + +- (void)shutdown +{ + if (@available(iOS 14, tvOS 14, macOS 11, *)) { + if (self.weakPlayer) + [self.weakPlayer cancelAndReturnError:nil]; + _weakPlayer = nil; + if (self.strongPlayer) + [self.strongPlayer cancelAndReturnError:nil]; + _strongPlayer = nil; + if (self.engine) + [self.engine stopWithCompletionHandler:nil]; + self.engine = nil; + } +} + +@end + +static void apple_gamecontroller_joypad_setup_haptics(GCController *controller) +{ + if (@available(iOS 14, tvOS 14, macOS 11, *)) { + mfi_rumblers[controller.playerIndex] = [[MFIRumbleController alloc] initWithController:controller]; + } +} + static void apple_gamecontroller_joypad_connect(GCController *controller) { signed desired_index = (int32_t)controller.playerIndex; @@ -298,10 +440,8 @@ static void apple_gamecontroller_joypad_connect(GCController *controller) } } -/* GCGamepad is deprecated */ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated" [mfiControllers addObject:controller]; + /* Move any non-game controllers (like the siri remote) to the end */ if (mfiControllers.count > 1) { @@ -311,8 +451,8 @@ static void apple_gamecontroller_joypad_connect(GCController *controller) for (GCController *connectedController in mfiControllers) { - if ( connectedController.gamepad == nil - && connectedController.extendedGamepad == nil ) + if ( connectedController.microGamepad != nil + || connectedController.extendedGamepad == nil ) connectedNonGameControllerIndex = index; index++; } @@ -327,10 +467,10 @@ static void apple_gamecontroller_joypad_connect(GCController *controller) gc.playerIndex = newPlayerIndex++; } - apple_gamecontroller_joypad_register(controller.gamepad); + apple_gamecontroller_joypad_register(controller); + apple_gamecontroller_joypad_setup_haptics(controller); mfi_joypad_autodetect_add((unsigned)controller.playerIndex); } -#pragma clang diagnostic pop } static void apple_gamecontroller_joypad_disconnect(GCController* controller) @@ -340,6 +480,7 @@ static void apple_gamecontroller_joypad_disconnect(GCController* controller) if (pad == GCCONTROLLER_PLAYER_INDEX_UNSET) return; + mfi_rumblers[pad] = nil; mfi_controllers[pad] = 0; if ([mfiControllers containsObject:controller]) { @@ -350,8 +491,7 @@ static void apple_gamecontroller_joypad_disconnect(GCController* controller) void *apple_gamecontroller_joypad_init(void *data) { - static bool inited = false; - if (inited) + if (mfi_inited) return (void*)-1; if (!apple_gamecontroller_available()) return NULL; @@ -372,7 +512,7 @@ void *apple_gamecontroller_joypad_init(void *data) apple_gamecontroller_joypad_disconnect([note object]); } ]; #endif - + mfi_inited = true; return (void*)-1; } @@ -461,6 +601,43 @@ static int16_t apple_gamecontroller_joypad_state( return ret; } +static bool apple_gamecontroller_joypad_set_rumble(unsigned pad, + enum retro_rumble_effect type, uint16_t strength) +{ + if (pad >= MAX_MFI_CONTROLLERS) + return false; + if (@available(iOS 14, tvOS 14, macOS 11, *)) { + MFIRumbleController *rumble = mfi_rumblers[pad]; + if (!rumble) + return false; + id player = (type == RETRO_RUMBLE_STRONG ? rumble.strongPlayer : rumble.weakPlayer); + if (!player) + return false; + if (strength == 0) + { + player.isMuted = YES; + return true; + } + else + { + player.isMuted = NO; + float str = (float)strength / 65535.0f; + CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] + initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl + value:str + relativeTime:0]; + NSError *error; + [player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error]; + if (!error) + [player startAtTime:0 error:&error]; + return error; + } + return true; + } else { + return false; + } +} + static bool apple_gamecontroller_joypad_query_pad(unsigned pad) { return pad < MAX_USERS; @@ -483,7 +660,7 @@ input_device_driver_t mfi_joypad = { apple_gamecontroller_joypad_get_buttons, apple_gamecontroller_joypad_axis, apple_gamecontroller_joypad_poll, - NULL, + apple_gamecontroller_joypad_set_rumble, NULL, apple_gamecontroller_joypad_name, "mfi", diff --git a/input/input_autodetect_builtin.c b/input/input_autodetect_builtin.c index 5efa9f2795..7f6dce1131 100644 --- a/input/input_autodetect_builtin.c +++ b/input/input_autodetect_builtin.c @@ -703,7 +703,7 @@ DECL_AXIS(r_x_minus, -2) \ DECL_AXIS(r_y_plus, +3) \ DECL_AXIS(r_y_minus, -3) -#define IOS_MFI_DEFAULT_BINDS \ +#define MFI_DEFAULT_BINDS \ DECL_BTN(a, 8) \ DECL_BTN(b, 0) \ DECL_BTN(x, 9) \ @@ -802,8 +802,8 @@ const char* const input_builtin_autoconfs[] = #ifdef EMSCRIPTEN DECL_AUTOCONF_PID(1, 1, "rwebpad", EMSCRIPTEN_DEFAULT_BINDS), #endif -#if TARGET_OS_IPHONE - DECL_AUTOCONF_DEVICE("mFi Controller", "mfi", IOS_MFI_DEFAULT_BINDS), +#if HAVE_MFI + DECL_AUTOCONF_DEVICE("mFi Controller", "mfi", MFI_DEFAULT_BINDS), #endif NULL }; diff --git a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj index 7fc4f527e0..1eda54a591 100644 --- a/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_Metal.xcodeproj/project.pbxproj @@ -40,7 +40,9 @@ 05D7753520A567A400646447 /* griffin_cpp.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 05D7753320A5678300646447 /* griffin_cpp.cpp */; }; 05D7753720A567A700646447 /* griffin_glslang.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 05D7753420A5678400646447 /* griffin_glslang.cpp */; }; 072976DD296284F600D6E00C /* OpenGL.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 072976DC296284F600D6E00C /* OpenGL.framework */; }; + 0746953A2997393000CCB7BD /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 074695362995C03900CCB7BD /* GameController.framework */; }; 079371D0296392420059A71C /* libMoltenVK.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 07B634CF296391FF00B3D78D /* libMoltenVK.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 0795A8C7299A095300D5035D /* CoreHaptics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0795A8C6299A095300D5035D /* CoreHaptics.framework */; }; 07B634D0296391FF00B3D78D /* libMoltenVK.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 07B634CF296391FF00B3D78D /* libMoltenVK.dylib */; }; 5061C8A41AE47E510080AE14 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5061C8A31AE47E510080AE14 /* libz.dylib */; }; 509F0C9D1AA23AFC00619ECC /* griffin_objc.m in Sources */ = {isa = PBXBuildFile; fileRef = 509F0C9C1AA23AFC00619ECC /* griffin_objc.m */; }; @@ -494,6 +496,8 @@ 05F2874020F2BEEA00632D47 /* task_http.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = task_http.c; sourceTree = ""; }; 05F2874120F2BEEA00632D47 /* task_patch.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = task_patch.c; sourceTree = ""; }; 072976DC296284F600D6E00C /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; + 074695362995C03900CCB7BD /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; }; + 0795A8C6299A095300D5035D /* CoreHaptics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreHaptics.framework; path = System/Library/Frameworks/CoreHaptics.framework; sourceTree = SDKROOT; }; 07B634CF296391FF00B3D78D /* libMoltenVK.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libMoltenVK.dylib; path = Frameworks/MoltenVK/dylib/macOS/libMoltenVK.dylib; sourceTree = ""; }; 089C165DFE840E0CC02AAC07 /* InfoPlist.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = InfoPlist.strings; path = OSX/en.lproj/InfoPlist.strings; sourceTree = ""; }; 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; @@ -559,10 +563,12 @@ files = ( D27C508C2228362700113BC0 /* AudioToolbox.framework in Frameworks */, 072976DD296284F600D6E00C /* OpenGL.framework in Frameworks */, + 0746953A2997393000CCB7BD /* GameController.framework in Frameworks */, 07B634D0296391FF00B3D78D /* libMoltenVK.dylib in Frameworks */, D27C508B2228361D00113BC0 /* AVFoundation.framework in Frameworks */, 05A8E23C20A63CF50084ABDA /* QuartzCore.framework in Frameworks */, 05A8E23A20A63CED0084ABDA /* IOSurface.framework in Frameworks */, + 0795A8C7299A095300D5035D /* CoreHaptics.framework in Frameworks */, 05A8E23820A63CB40084ABDA /* Metal.framework in Frameworks */, 05269A6220ABF20500C29F1E /* MetalKit.framework in Frameworks */, 5061C8A41AE47E510080AE14 /* libz.dylib in Frameworks */, @@ -1385,6 +1391,8 @@ 29B97323FDCFA39411CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( + 0795A8C6299A095300D5035D /* CoreHaptics.framework */, + 074695362995C03900CCB7BD /* GameController.framework */, D27C50892228360D00113BC0 /* AudioToolbox.framework */, 07B634CF296391FF00B3D78D /* libMoltenVK.dylib */, 072976DC296284F600D6E00C /* OpenGL.framework */, @@ -1708,6 +1716,7 @@ "-DHAVE_COCOA_METAL", "-DHAVE_OPENGL_CORE", "-DHAVE_VULKAN", + "-DHAVE_MFI", ); OTHER_CODE_SIGN_FLAGS = "--deep --timestamp"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; @@ -1747,6 +1756,7 @@ "-DHAVE_COCOA_METAL", "-DHAVE_OPENGL_CORE", "-DHAVE_VULKAN", + "-DHAVE_MFI", ); OTHER_CODE_SIGN_FLAGS = "--deep --timestamp"; OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; diff --git a/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj b/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj index 4d21e45aca..e39f6060c0 100644 --- a/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj +++ b/pkg/apple/RetroArch_iOS13.xcodeproj/project.pbxproj @@ -1840,6 +1840,7 @@ OTHER_CFLAGS = ( "-DDONT_WANT_ARM_OPTIMIZATIONS", "-DENABLE_HLSL", + "-DGLES_SILENCE_DEPRECATION", "-DGLSLANG_OSINCLUDE_UNIX", "-DHAVE_7ZIP", "-DHAVE_AUDIOMIXER", @@ -1992,6 +1993,7 @@ OTHER_CFLAGS = ( "-DDONT_WANT_ARM_OPTIMIZATIONS", "-DENABLE_HLSL", + "-DGLES_SILENCE_DEPRECATION", "-DGLSLANG_OSINCLUDE_UNIX", "-DHAVE_7ZIP", "-DHAVE_AUDIOMIXER",