From 93ce97e1839fa77654a7eef6e7ddaec9099a4cc5 Mon Sep 17 00:00:00 2001 From: XProger Date: Fri, 11 Jan 2019 06:00:05 +0300 Subject: [PATCH] #15 add osx gamepad suppport (xbox 360 mapping) --- src/input.h | 2 +- .../osx/OpenLara.xcodeproj/project.pbxproj | 16 +- src/platform/osx/main.mm | 169 +++++++++++++++--- 3 files changed, 151 insertions(+), 36 deletions(-) diff --git a/src/input.h b/src/input.h index ce2970a4..89575a3e 100644 --- a/src/input.h +++ b/src/input.h @@ -128,7 +128,7 @@ namespace Input { case jkRT : joy[index].RT = pos.x; break; default : return; } - setJoyDown(index, key, pos.x > 0.0f); // gamepad LT, RT auto-down state + setJoyDown(index, key, pos.x > EPS); // gamepad LT, RT auto-down state } void setJoyVibration(int playerIndex, float L, float R) { diff --git a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj index ef21f876..ab8d3ca0 100644 --- a/src/platform/osx/OpenLara.xcodeproj/project.pbxproj +++ b/src/platform/osx/OpenLara.xcodeproj/project.pbxproj @@ -8,7 +8,7 @@ /* Begin PBXBuildFile section */ 523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */; }; - 523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 523F97E51DDF926E006FE2FC /* QuartzCore.framework */; }; + 9938F1A421E830250056E172 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9938F1A021E81EFE0056E172 /* IOKit.framework */; }; 99713CC9204CAD9900006FAC /* tinflate.c in Sources */ = {isa = PBXBuildFile; fileRef = 99713CC8204CAD9900006FAC /* tinflate.c */; }; 99BF38CD21E7176900D90A38 /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = 99BF38CC21E7176900D90A38 /* main.mm */; }; 99BF38D221E7202500D90A38 /* audio in Resources */ = {isa = PBXBuildFile; fileRef = 99BF38CE21E7202500D90A38 /* audio */; }; @@ -23,7 +23,7 @@ /* Begin PBXFileReference section */ 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; - 523F97E51DDF926E006FE2FC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; + 9938F1A021E81EFE0056E172 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; }; 995C97EF1E91A857003825B2 /* shaders */ = {isa = PBXFileReference; lastKnownFileType = folder; name = shaders; path = ../../shaders; sourceTree = ""; }; 99713CAE204CA3DA00006FAC /* animation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = animation.h; path = ../../animation.h; sourceTree = ""; }; 99713CAF204CA3DA00006FAC /* cache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cache.h; path = ../../cache.h; sourceTree = ""; }; @@ -60,12 +60,8 @@ 99BFB6A81DCA87BF00E2E997 /* stb_vorbis.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = stb_vorbis.c; path = ../../libs/stb_vorbis/stb_vorbis.c; sourceTree = ""; }; 99BFB6AA1DCA87C500E2E997 /* minimp3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = minimp3.cpp; path = ../../libs/minimp3/minimp3.cpp; sourceTree = ""; }; 99C4C0931796A96F0032DE85 /* OpenGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGL.framework; path = System/Library/Frameworks/OpenGL.framework; sourceTree = SDKROOT; }; - 99C4C0951796A9730032DE85 /* AGL.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AGL.framework; path = System/Library/Frameworks/AGL.framework; sourceTree = SDKROOT; }; 99C4C09B1796AACF0032DE85 /* OpenLara.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenLara.app; sourceTree = BUILT_PRODUCTS_DIR; }; 99C4C0A41796AACF0032DE85 /* OpenLara-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "OpenLara-Info.plist"; sourceTree = ""; }; - 99C4C0B81796AB7B0032DE85 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; }; - 99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudio.framework; path = System/Library/Frameworks/CoreAudio.framework; sourceTree = SDKROOT; }; - 99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreAudioKit.framework; path = System/Library/Frameworks/CoreAudioKit.framework; sourceTree = SDKROOT; }; 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AudioToolbox.framework; path = System/Library/Frameworks/AudioToolbox.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -74,7 +70,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 523F97E61DDF926E006FE2FC /* QuartzCore.framework in Frameworks */, + 9938F1A421E830250056E172 /* IOKit.framework in Frameworks */, 523F97E41DDF7AA5006FE2FC /* Cocoa.framework in Frameworks */, 99DF7BC41DCBA30B00C40D0A /* AudioToolbox.framework in Frameworks */, 99C4C0BA1796AB810032DE85 /* OpenGL.framework in Frameworks */, @@ -139,14 +135,10 @@ 99C4C0821796A8230032DE85 /* Frameworks */ = { isa = PBXGroup; children = ( - 523F97E51DDF926E006FE2FC /* QuartzCore.framework */, + 9938F1A021E81EFE0056E172 /* IOKit.framework */, 523F97E31DDF7AA5006FE2FC /* Cocoa.framework */, 99DF7BC31DCBA30B00C40D0A /* AudioToolbox.framework */, - 99DF7BC11DCBA2D100C40D0A /* CoreAudioKit.framework */, - 99DF7BBF1DCBA2BF00C40D0A /* CoreAudio.framework */, - 99C4C0B81796AB7B0032DE85 /* Carbon.framework */, 99C4C0931796A96F0032DE85 /* OpenGL.framework */, - 99C4C0951796A9730032DE85 /* AGL.framework */, ); name = Frameworks; sourceTree = ""; diff --git a/src/platform/osx/main.mm b/src/platform/osx/main.mm index acd5de3f..2042580c 100644 --- a/src/platform/osx/main.mm +++ b/src/platform/osx/main.mm @@ -1,15 +1,28 @@ #include #include #include +#include #include "game.h" +// timing +int osGetTime() { + static mach_timebase_info_data_t timebaseInfo; + if (timebaseInfo.denom == 0) { + mach_timebase_info(&timebaseInfo); + } + + uint64_t absolute = mach_absolute_time(); + uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL); + return int(milliseconds); +} + // sound #define SND_SIZE 2352 static AudioQueueRef audioQueue; -void soundFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { +void sndFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) { void* frames = inBuffer->mAudioData; UInt32 count = inBuffer->mAudioDataBytesCapacity / 4; Sound::fill((Sound::Frame*)frames, count); @@ -17,7 +30,7 @@ void soundFill(void* inUserData, AudioQueueRef inAQ, AudioQueueBufferRef inBuffe AudioQueueEnqueueBuffer(audioQueue, inBuffer, 0, NULL); } -void soundInit() { +void sndInit() { AudioStreamBasicDescription deviceFormat; deviceFormat.mSampleRate = 44100; deviceFormat.mFormatID = kAudioFormatLinearPCM; @@ -29,12 +42,12 @@ void soundInit() { deviceFormat.mBitsPerChannel = 16; deviceFormat.mReserved = 0; - AudioQueueNewOutput(&deviceFormat, soundFill, NULL, NULL, NULL, 0, &audioQueue); + AudioQueueNewOutput(&deviceFormat, sndFill, NULL, NULL, NULL, 0, &audioQueue); for (int i = 0; i < 2; i++) { AudioQueueBufferRef mBuffer; AudioQueueAllocateBuffer(audioQueue, SND_SIZE, &mBuffer); - soundFill(NULL, audioQueue, mBuffer); + sndFill(NULL, audioQueue, mBuffer); } AudioQueueStart(audioQueue, NULL); } @@ -51,7 +64,6 @@ InputKey keyToInputKey(int code) { for (int i = 0; i < sizeof(codes) / sizeof(codes[0]); i++) if (codes[i] == code) { return (InputKey)(ikLeft + i); - LOG("%d\n", code); } return ikNone; } @@ -65,24 +77,133 @@ InputKey mouseToInputKey(int btn) { return ikNone; } +IOHIDDeviceRef joyDevices[4] = { 0 }; +IOHIDManagerRef hidManager; + +JoyKey joyButtonToKey(uint32_t button) { + static const JoyKey keys[] = { jkA, jkB, jkX, jkY, jkLB, jkRB, jkLT, jkRT, jkStart, jkSelect, jkNone, jkUp, jkDown, jkLeft, jkRight }; + + if (button >= 0 || button < COUNT(keys)) + return keys[button]; + return jkNone; +} + bool osJoyReady(int index) { - return false; + if (index < 0 || index >= COUNT(joyDevices)) + return false; + return joyDevices[index] != NULL; } void osJoyVibrate(int index, float L, float R) { // TODO } -// timing -int osGetTime() { - static mach_timebase_info_data_t timebaseInfo; - if (timebaseInfo.denom == 0) { - mach_timebase_info(&timebaseInfo); +float joyAxisValue(IOHIDValueRef value) { + IOHIDElementRef element = IOHIDValueGetElement(value); + CFIndex val = IOHIDValueGetIntegerValue(value); + CFIndex min = IOHIDElementGetLogicalMin(element); + CFIndex max = IOHIDElementGetLogicalMax(element); + + float v = float(val - min) / float(max - min); + if (v < 0.2f) v = 0.0f; // check for deadzone + return v * 2.0f - 1.0f; +} + +void hidValueCallback(void *context, IOReturn result, void *sender, IOHIDValueRef value) { + if (result != kIOReturnSuccess) + return; + + IOHIDElementRef element = IOHIDValueGetElement(value); + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + + int joyIndex = -1; + for (int i = 0; i < COUNT(joyDevices); i++) { + if (joyDevices[i] == device) { + joyIndex = i; + break; + } } - uint64_t absolute = mach_absolute_time(); - uint64_t milliseconds = absolute * timebaseInfo.numer / (timebaseInfo.denom * 1000000ULL); - return int(milliseconds); + if (joyIndex == -1) return; + + // TODO: add mapping for most popular gamepads by kIOHIDVendorIDKey / kIOHIDProductIDKey + switch (IOHIDElementGetUsagePage(element)) { + case kHIDPage_GenericDesktop : { + switch (IOHIDElementGetUsage(IOHIDValueGetElement(value))) { + case kHIDUsage_GD_X : + Input::setJoyPos(joyIndex, jkL, vec2(joyAxisValue(value), Input::joy[joyIndex].L.y)); + break; + case kHIDUsage_GD_Y : + Input::setJoyPos(joyIndex, jkL, vec2(Input::joy[joyIndex].L.x, joyAxisValue(value))); + break; + case kHIDUsage_GD_Rx : + Input::setJoyPos(joyIndex, jkR, vec2(joyAxisValue(value), Input::joy[joyIndex].R.y)); + break; + case kHIDUsage_GD_Ry : + Input::setJoyPos(joyIndex, jkR, vec2(Input::joy[joyIndex].R.x, joyAxisValue(value))); + break; + case kHIDUsage_GD_Z : + Input::setJoyPos(joyIndex, jkLT, vec2(joyAxisValue(value) * 0.5f + 0.5f, 0.0f)); + break; + case kHIDUsage_GD_Rz : + Input::setJoyPos(joyIndex, jkRT, vec2(joyAxisValue(value) * 0.5f + 0.5f, 0.0f)); + break; + default : LOG("! joy: unknown joy 0x%x (%d)\n", IOHIDElementGetUsage(IOHIDValueGetElement(value)), (int)IOHIDValueGetIntegerValue(value)); + } + break; + } + case kHIDPage_Button : { + uint32_t button = IOHIDElementGetUsage(IOHIDValueGetElement(value)) - kHIDUsage_Button_1; + bool down = IOHIDValueGetIntegerValue(value) != 0; + JoyKey key = joyButtonToKey(button); + Input::setJoyDown(joyIndex, key, down); + if (key == jkNone) LOG("! joy: unknown button %d\n", button); + break; + } + default : ; + } +} + +void joyAdd(void* context, IOReturn, void*, IOHIDDeviceRef device) { + for (int i = 0; i < COUNT(joyDevices); i++) { + if (joyDevices[i] == NULL) { + joyDevices[i] = device; + break; + } + } +} + +void joyRemove(void* context, IOReturn, void*, IOHIDDeviceRef device) { + for (int i = 0; i < COUNT(joyDevices); i++) { + if (joyDevices[i] == device) { + joyDevices[i] = NULL; + break; + } + } +} + +void joyInit() { + hidManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone); + + NSDictionary *matchingGamepad = @{ + @(kIOHIDDeviceUsagePageKey): @(kHIDPage_GenericDesktop), + @(kIOHIDDeviceUsageKey): @(kHIDUsage_GD_GamePad) + }; + NSArray *matchDicts = @[ matchingGamepad ]; + + IOHIDManagerSetDeviceMatchingMultiple(hidManager, (__bridge CFArrayRef) matchDicts); + IOHIDManagerRegisterDeviceMatchingCallback(hidManager, joyAdd, NULL); + IOHIDManagerRegisterDeviceRemovalCallback(hidManager, joyRemove, NULL); + IOHIDManagerScheduleWithRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerOpen(hidManager, kIOHIDOptionsTypeNone); + IOHIDManagerRegisterInputValueCallback(hidManager, hidValueCallback, NULL); +} + +void joyFree() { + IOHIDManagerUnscheduleFromRunLoop(hidManager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerRegisterDeviceMatchingCallback(hidManager, NULL, 0); + IOHIDManagerRegisterDeviceRemovalCallback(hidManager, NULL, 0); + IOHIDManagerClose(hidManager, kIOHIDOptionsTypeNone); } @interface OpenLaraGLView : NSOpenGLView @@ -216,10 +337,10 @@ int main() { // init window NSRect rect = NSMakeRect(0, 0, 1280, 720); - NSWindow *mainWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:YES]; - mainWindow.title = @"OpenLara"; - mainWindow.acceptsMouseMovedEvents = YES; - mainWindow.delegate = [[OpenLaraWindowDelegate alloc] init]; + NSWindow *window = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask backing:NSBackingStoreBuffered defer:YES]; + window.title = @"OpenLara"; + window.acceptsMouseMovedEvents = YES; + window.delegate = [[OpenLaraWindowDelegate alloc] init]; // init OpenGL context NSOpenGLPixelFormatAttribute attribs[] = { @@ -232,9 +353,9 @@ int main() { }; NSOpenGLPixelFormat *format = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; - OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:mainWindow.contentLayoutRect pixelFormat:format]; + OpenLaraGLView *view = [[OpenLaraGLView alloc] initWithFrame:window.contentLayoutRect pixelFormat:format]; view.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable; - mainWindow.contentView = view; + window.contentView = view; [view.openGLContext makeCurrentContext]; // get path to game content @@ -244,10 +365,11 @@ int main() { strcat(contentDir, "/"); // show window - [mainWindow center]; - [mainWindow makeKeyAndOrderFront:nil]; + [window center]; + [window makeKeyAndOrderFront:nil]; - soundInit(); + joyInit(); + sndInit(); Game::init(); if (!Core::isQuit) { @@ -255,6 +377,7 @@ int main() { } Game::deinit(); + joyFree(); // TODO: sndFree return 0;