From 71dddc002014ff9a859e7700f609602f035f896f Mon Sep 17 00:00:00 2001 From: Nadia Holmquist Pedersen Date: Tue, 27 Jun 2023 22:40:38 +0200 Subject: [PATCH] Add basic controller input using the SDL2 GameController API --- include/emulator.hpp | 20 +++++++- include/services/hid.hpp | 10 +++- include/services/service_manager.hpp | 8 ++-- src/emulator.cpp | 70 +++++++++++++++++++++++++++- 4 files changed, 102 insertions(+), 6 deletions(-) diff --git a/include/emulator.hpp b/include/emulator.hpp index 46aa0ade..37429358 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -23,6 +23,8 @@ class Emulator { SDL_Window* window; SDL_GLContext glContext; + SDL_GameController* gameController; + int gameControllerID; static constexpr u32 width = 400; static constexpr u32 height = 240 * 2; // * 2 because 2 screens @@ -40,6 +42,13 @@ public: Helpers::panic("Failed to initialize SDL2"); } + // Make SDL use consistent positional button mapping + SDL_SetHint(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, "0"); + + if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) { + Helpers::warn("Failed to initialize SDL2 GameController: %s", SDL_GetError()); + } + // Request OpenGL 4.1 Core (Max available on MacOS) // MacOS gets mad if we don't explicitly demand a core profile SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); @@ -61,6 +70,15 @@ public: Helpers::panic("OpenGL init failed: %s", SDL_GetError()); } + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + gameController = SDL_GameControllerOpen(0); + + if (gameController != nullptr) { + SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); + gameControllerID = SDL_JoystickInstanceID(stick); + } + } + reset(); } @@ -75,4 +93,4 @@ public: bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); void initGraphicsContext() { gpu.initGraphicsContext(); } -}; \ No newline at end of file +}; diff --git a/include/services/hid.hpp b/include/services/hid.hpp index ea36bb0d..28b840cb 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -87,6 +87,14 @@ public: void pressKey(u32 mask) { newButtons |= mask; } void releaseKey(u32 mask) { newButtons &= ~mask; } + s16 getCirclepadX() { + return circlePadX; + } + + s16 getCirclepadY() { + return circlePadY; + } + void setCirclepadX(s16 x) { circlePadX = x; @@ -129,4 +137,4 @@ public: void releaseTouchScreen() { touchScreenPressed = false; } -}; \ No newline at end of file +}; diff --git a/include/services/service_manager.hpp b/include/services/service_manager.hpp index e09fd455..ee2677ad 100644 --- a/include/services/service_manager.hpp +++ b/include/services/service_manager.hpp @@ -90,9 +90,11 @@ class ServiceManager { // Input function wrappers void pressKey(u32 key) { hid.pressKey(key); } void releaseKey(u32 key) { hid.releaseKey(key); } - void setCirclepadX(u16 x) { hid.setCirclepadX(x); } - void setCirclepadY(u16 y) { hid.setCirclepadY(y); } + s16 getCirclepadX() { return hid.getCirclepadX(); } + s16 getCirclepadY() { return hid.getCirclepadY(); } + void setCirclepadX(s16 x) { hid.setCirclepadX(x); } + void setCirclepadY(s16 y) { hid.setCirclepadY(y); } void updateInputs(u64 currentTimestamp) { hid.updateInputs(currentTimestamp); } void setTouchScreenPress(u16 x, u16 y) { hid.setTouchScreenPress(x, y); } void releaseTouchScreen() { hid.releaseTouchScreen(); } -}; \ No newline at end of file +}; diff --git a/src/emulator.cpp b/src/emulator.cpp index 8141a94a..ba8fdb3f 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -117,7 +117,75 @@ void Emulator::run() { srv.releaseTouchScreen(); } break; + + case SDL_CONTROLLERDEVICEADDED: + if (gameController != nullptr) { + break; + } + + gameController = SDL_GameControllerOpen(event.cdevice.which); + break; + + case SDL_CONTROLLERDEVICEREMOVED: + if (event.cdevice.which == gameControllerID) { + SDL_GameControllerClose(gameController); + gameController = nullptr; + gameControllerID = 0; + } + + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: { + u32 key = 0; + + switch (event.cbutton.button) { + case SDL_CONTROLLER_BUTTON_A: key = Keys::B; break; + case SDL_CONTROLLER_BUTTON_B: key = Keys::A; break; + case SDL_CONTROLLER_BUTTON_X: key = Keys::Y; break; + case SDL_CONTROLLER_BUTTON_Y: key = Keys::X; break; + case SDL_CONTROLLER_BUTTON_LEFTSHOULDER: key = Keys::L; break; + case SDL_CONTROLLER_BUTTON_RIGHTSHOULDER: key = Keys::R; break; + case SDL_CONTROLLER_BUTTON_DPAD_LEFT: key = Keys::Left; break; + case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: key = Keys::Right; break; + case SDL_CONTROLLER_BUTTON_DPAD_UP: key = Keys::Up; break; + case SDL_CONTROLLER_BUTTON_DPAD_DOWN: key = Keys::Down; break; + case SDL_CONTROLLER_BUTTON_BACK: key = Keys::Select; break; + case SDL_CONTROLLER_BUTTON_START: key = Keys::Start; break; + } + + if (key != 0) { + if (event.cbutton.state == SDL_PRESSED) { + srv.pressKey(key); + } else { + srv.releaseKey(key); + } + } } + } + } + + if (gameController != nullptr) { + const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + const s16 deadzone = 3276; + const s16 maxValue = 0x9C; + const s16 div = 0x8000 / maxValue; + + if (abs(stickX) < deadzone) { + // Avoid overriding the keyboard's circlepad input + if (abs(srv.getCirclepadX()) != maxValue) { + srv.setCirclepadX(0); + } + } else { + srv.setCirclepadX(stickX / div); + } + + if (abs(stickY) < deadzone) { + if (abs(srv.getCirclepadY()) != maxValue) { + srv.setCirclepadY(0); + } + } else { + srv.setCirclepadY(-(stickY / div)); + } } // Update inputs in the HID module @@ -191,4 +259,4 @@ bool Emulator::loadELF(std::ifstream& file) { Helpers::panic("Misaligned ELF entrypoint. TODO: Check if ELFs can boot in thumb mode"); } return true; -} \ No newline at end of file +}