From f15614af589c591e951113fa4a817a8ea1d6ad40 Mon Sep 17 00:00:00 2001 From: offtkp Date: Wed, 18 Oct 2023 01:12:59 +0300 Subject: [PATCH] Decouple emulator and frontend code --- CMakeLists.txt | 4 +- include/emulator.hpp | 19 +- src/emulator.cpp | 343 ------------------------------- src/panda_qt/frontend_qt.cpp | 26 +++ src/panda_qt/main_window.cpp | 2 +- src/panda_sdl/frontend_sdl.cpp | 359 +++++++++++++++++++++++++++++++++ src/panda_sdl/main.cpp | 4 +- 7 files changed, 392 insertions(+), 365 deletions(-) create mode 100644 src/panda_qt/frontend_qt.cpp create mode 100644 src/panda_sdl/frontend_sdl.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 80c70c1af..4d30d1daa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,14 +180,14 @@ set(RENDERER_SW_SOURCE_FILES src/core/renderer_sw/renderer_sw.cpp) # Frontend source files if(ENABLE_QT_GUI) - set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp) + set(FRONTEND_SOURCE_FILES src/panda_qt/main.cpp src/panda_qt/screen.cpp src/panda_qt/main_window.cpp src/panda_qt/frontend_qt.cpp) set(FRONTEND_HEADER_FILES include/panda_qt/screen.hpp include/panda_qt/main_window.hpp) source_group("Source Files\\Qt" FILES ${FRONTEND_SOURCE_FILES}) source_group("Header Files\\Qt" FILES ${FRONTEND_HEADER_FILES}) include_directories(${Qt6Gui_PRIVATE_INCLUDE_DIRS}) else() - set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp) + set(FRONTEND_SOURCE_FILES src/panda_sdl/main.cpp src/panda_sdl/frontend_sdl.cpp) set(FRONTEND_HEADER_FILES "") endif() diff --git a/include/emulator.hpp b/include/emulator.hpp index 1901e4254..6e1318884 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -21,10 +21,6 @@ #include "http_server.hpp" #endif -#ifdef PANDA3DS_FRONTEND_QT -#include "gl/context.h" -#endif - enum class ROMType { None, ELF, @@ -42,14 +38,6 @@ class Emulator { Crypto::AESEngine aesEngine; Cheats cheats; -#ifdef PANDA3DS_FRONTEND_SDL - SDL_Window* window; - -#ifdef PANDA3DS_ENABLE_OPENGL - SDL_GLContext glContext; -#endif -#endif - SDL_GameController* gameController = nullptr; int gameControllerID; @@ -114,12 +102,7 @@ class Emulator { bool loadELF(const std::filesystem::path& path); bool loadELF(std::ifstream& file); -#ifdef PANDA3DS_FRONTEND_QT - // For passing the GL context from Qt to the renderer - void initGraphicsContext(GL::Context* glContext) { gpu.initGraphicsContext(nullptr); } -#else - void initGraphicsContext() { gpu.initGraphicsContext(window); } -#endif + void initFrontend(); RomFS::DumpingResult dumpRomFS(const std::filesystem::path& path); void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } diff --git a/src/emulator.cpp b/src/emulator.cpp index 7fee0b8de..d81e392d9 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -1,6 +1,5 @@ #include "emulator.hpp" -#include #include #ifdef _WIN32 @@ -20,73 +19,12 @@ Emulator::Emulator() , httpServer(this) #endif { - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { - 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()); - } - #ifdef PANDA3DS_ENABLE_DISCORD_RPC if (config.discordRpcEnabled) { discordRpc.init(); updateDiscord(); } #endif - - // We need OpenGL for software rendering or for OpenGL if it's enabled - bool needOpenGL = config.rendererType == RendererType::Software; -#ifdef PANDA3DS_ENABLE_OPENGL - needOpenGL = needOpenGL || (config.rendererType == RendererType::OpenGL); -#endif - - // Only create SDL Window for SDL frontend -#ifdef PANDA3DS_FRONTEND_SDL - if (needOpenGL) { - // Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (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); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.rendererType == RendererType::Software ? 3 : 4); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.rendererType == RendererType::Software ? 3 : 1); - window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); - - if (window == nullptr) { - Helpers::panic("Window creation failed: %s", SDL_GetError()); - } - - glContext = SDL_GL_CreateContext(window); - if (glContext == nullptr) { - Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); - } - - if (!gladLoadGLLoader(reinterpret_cast(SDL_GL_GetProcAddress))) { - Helpers::panic("OpenGL init failed: %s", SDL_GetError()); - } - } - -#ifdef PANDA3DS_ENABLE_VULKAN - if (config.rendererType == RendererType::Vulkan) { - window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); - - if (window == nullptr) { - Helpers::warn("Window creation failed: %s", SDL_GetError()); - } - } -#endif -#endif - - if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { - gameController = SDL_GameControllerOpen(0); - - if (gameController != nullptr) { - SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); - gameControllerID = SDL_JoystickInstanceID(stick); - } - } - reset(ReloadOption::NoReload); } @@ -131,287 +69,6 @@ void Emulator::reset(ReloadOption reload) { void Emulator::step() {} void Emulator::render() {} -// Main loop for the SDL frontend. TODO: Move it to a dedicated file -#ifdef PANDA3DS_FRONTEND_SDL -void Emulator::run() { - programRunning = true; - - while (programRunning) { -#ifdef PANDA3DS_ENABLE_HTTP_SERVER - httpServer.processActions(); -#endif - - runFrame(); - HIDService& hid = kernel.getServiceManager().getHID(); - - SDL_Event event; - while (SDL_PollEvent(&event)) { - namespace Keys = HID::Keys; - - switch (event.type) { - case SDL_QUIT: - printf("Bye :(\n"); - programRunning = false; - return; - - case SDL_KEYDOWN: - if (romType == ROMType::None) break; - - switch (event.key.keysym.sym) { - case SDLK_l: hid.pressKey(Keys::A); break; - case SDLK_k: hid.pressKey(Keys::B); break; - case SDLK_o: hid.pressKey(Keys::X); break; - case SDLK_i: hid.pressKey(Keys::Y); break; - - case SDLK_q: hid.pressKey(Keys::L); break; - case SDLK_p: hid.pressKey(Keys::R); break; - - case SDLK_RIGHT: hid.pressKey(Keys::Right); break; - case SDLK_LEFT: hid.pressKey(Keys::Left); break; - case SDLK_UP: hid.pressKey(Keys::Up); break; - case SDLK_DOWN: hid.pressKey(Keys::Down); break; - - case SDLK_w: - hid.setCirclepadY(0x9C); - keyboardAnalogY = true; - break; - - case SDLK_a: - hid.setCirclepadX(-0x9C); - keyboardAnalogX = true; - break; - - case SDLK_s: - hid.setCirclepadY(-0x9C); - keyboardAnalogY = true; - break; - - case SDLK_d: - hid.setCirclepadX(0x9C); - keyboardAnalogX = true; - break; - - case SDLK_RETURN: hid.pressKey(Keys::Start); break; - case SDLK_BACKSPACE: hid.pressKey(Keys::Select); break; - - // Use the F4 button as a hot-key to pause or resume the emulator - // We can't use the audio play/pause buttons because it's annoying - case SDLK_F4: { - togglePause(); - break; - } - - // Use F5 as a reset button - case SDLK_F5: { - reset(ReloadOption::Reload); - break; - } - } - break; - - case SDL_KEYUP: - if (romType == ROMType::None) break; - - switch (event.key.keysym.sym) { - case SDLK_l: hid.releaseKey(Keys::A); break; - case SDLK_k: hid.releaseKey(Keys::B); break; - case SDLK_o: hid.releaseKey(Keys::X); break; - case SDLK_i: hid.releaseKey(Keys::Y); break; - - case SDLK_q: hid.releaseKey(Keys::L); break; - case SDLK_p: hid.releaseKey(Keys::R); break; - - case SDLK_RIGHT: hid.releaseKey(Keys::Right); break; - case SDLK_LEFT: hid.releaseKey(Keys::Left); break; - case SDLK_UP: hid.releaseKey(Keys::Up); break; - case SDLK_DOWN: hid.releaseKey(Keys::Down); break; - - // Err this is probably not ideal - case SDLK_w: - case SDLK_s: - hid.setCirclepadY(0); - keyboardAnalogY = false; - break; - - case SDLK_a: - case SDLK_d: - hid.setCirclepadX(0); - keyboardAnalogX = false; - break; - - case SDLK_RETURN: hid.releaseKey(Keys::Start); break; - case SDLK_BACKSPACE: hid.releaseKey(Keys::Select); break; - } - break; - - case SDL_MOUSEBUTTONDOWN: - if (romType == ROMType::None) break; - - if (event.button.button == SDL_BUTTON_LEFT) { - const s32 x = event.button.x; - const s32 y = event.button.y; - - // Check if touch falls in the touch screen area - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - hid.setTouchScreenPress(x_converted, y_converted); - } else { - hid.releaseTouchScreen(); - } - } else if (event.button.button == SDL_BUTTON_RIGHT) { - holdingRightClick = true; - } - - break; - - case SDL_MOUSEBUTTONUP: - if (romType == ROMType::None) break; - - if (event.button.button == SDL_BUTTON_LEFT) { - hid.releaseTouchScreen(); - } else if (event.button.button == SDL_BUTTON_RIGHT) { - holdingRightClick = false; - } - break; - - case SDL_CONTROLLERDEVICEADDED: - if (gameController == nullptr) { - gameController = SDL_GameControllerOpen(event.cdevice.which); - gameControllerID = event.cdevice.which; - } - break; - - case SDL_CONTROLLERDEVICEREMOVED: - if (event.cdevice.which == gameControllerID) { - SDL_GameControllerClose(gameController); - gameController = nullptr; - gameControllerID = 0; - } - break; - - case SDL_CONTROLLERBUTTONUP: - case SDL_CONTROLLERBUTTONDOWN: { - if (romType == ROMType::None) break; - 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) { - hid.pressKey(key); - } else { - hid.releaseKey(key); - } - } - break; - } - - // Detect mouse motion events for gyroscope emulation - case SDL_MOUSEMOTION: { - if (romType == ROMType::None) break; - - // Handle "dragging" across the touchscreen - if (hid.isTouchScreenPressed()) { - const s32 x = event.motion.x; - const s32 y = event.motion.y; - - // Check if touch falls in the touch screen area and register the new touch screen position - if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { - // Convert to 3DS coordinates - u16 x_converted = static_cast(x) - 40; - u16 y_converted = static_cast(y) - 240; - - hid.setTouchScreenPress(x_converted, y_converted); - } - } - - // We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation - if (holdingRightClick) { - // Relative motion since last mouse motion event - const s32 motionX = event.motion.xrel; - const s32 motionY = event.motion.yrel; - - // The gyroscope involves lots of weird math I don't want to bother with atm - // So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion - const s32 roll = motionX > 0 ? 0x7f : -0x7f; - const s32 pitch = motionY > 0 ? 0x7f : -0x7f; - hid.setRoll(roll); - hid.setPitch(pitch); - } - break; - } - - case SDL_DROPFILE: { - char* droppedDir = event.drop.file; - - if (droppedDir) { - const std::filesystem::path path(droppedDir); - - if (path.extension() == ".amiibo") { - loadAmiibo(path); - } else if (path.extension() == ".lua") { - lua.loadFile(droppedDir); - } else { - loadROM(path); - } - - SDL_free(droppedDir); - } - break; - } - } - } - - // Update controller analog sticks and HID service - if (romType != ROMType::None) { - if (gameController != nullptr) { - const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); - const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); - constexpr s16 deadzone = 3276; - constexpr s16 maxValue = 0x9C; - constexpr s16 div = 0x8000 / maxValue; - - // Avoid overriding the keyboard's circlepad input - if (abs(stickX) < deadzone && !keyboardAnalogX) { - hid.setCirclepadX(0); - } else { - hid.setCirclepadX(stickX / div); - } - - if (abs(stickY) < deadzone && !keyboardAnalogY) { - hid.setCirclepadY(0); - } else { - hid.setCirclepadY(-(stickY / div)); - } - } - - hid.updateInputs(cpu.getTicks()); - } - // TODO: Should this be uncommented? - // kernel.evalReschedule(); - - // Update inputs in the HID module - SDL_GL_SwapWindow(window); - } -} -#endif - // Only resume if a ROM is properly loaded void Emulator::resume() { running = (romType != ROMType::None); } void Emulator::pause() { running = false; } diff --git a/src/panda_qt/frontend_qt.cpp b/src/panda_qt/frontend_qt.cpp new file mode 100644 index 000000000..bb1e6de44 --- /dev/null +++ b/src/panda_qt/frontend_qt.cpp @@ -0,0 +1,26 @@ +#include "emulator.hpp" + +void Emulator::initFrontend() +{ + // Initialize common SDL2 subsystems + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { + 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()); + } + + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + gameController = SDL_GameControllerOpen(0); + + if (gameController != nullptr) { + SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); + gameControllerID = SDL_JoystickInstanceID(stick); + } + } + + gpu.initGraphicsContext(nullptr); +} \ No newline at end of file diff --git a/src/panda_qt/main_window.cpp b/src/panda_qt/main_window.cpp index 2c2cc64f9..1c4cec7ba 100644 --- a/src/panda_qt/main_window.cpp +++ b/src/panda_qt/main_window.cpp @@ -57,7 +57,7 @@ MainWindow::MainWindow(QApplication* app, QWidget* parent) : QMainWindow(parent) glContext->MakeCurrent(); glContext->SetSwapInterval(1); - emu->initGraphicsContext(glContext); + emu->initFrontend(); } else if (usingVk) { Helpers::panic("Vulkan on Qt is currently WIP, try the SDL frontend instead!"); } else { diff --git a/src/panda_sdl/frontend_sdl.cpp b/src/panda_sdl/frontend_sdl.cpp new file mode 100644 index 000000000..dd146ac5e --- /dev/null +++ b/src/panda_sdl/frontend_sdl.cpp @@ -0,0 +1,359 @@ +#include "emulator.hpp" +#include +#include + +SDL_Window* window; +#ifdef PANDA3DS_ENABLE_OPENGL +SDL_GLContext glContext; +#endif + +void loadGlFunctions(void *(*loadproc)(const char *)) { + #ifdef __ANDROID__ + if (!gladLoadGLES2Loader(loadproc)) { + Helpers::panic("OpenGL ES init failed"); + } + #else + if (!gladLoadGLLoader(reinterpret_cast(loadproc))) { + Helpers::panic("OpenGL init failed"); + } + #endif +} + +void Emulator::initFrontend() +{ + // Initialize common SDL2 subsystems + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { + 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()); + } + + if (SDL_WasInit(SDL_INIT_GAMECONTROLLER)) { + gameController = SDL_GameControllerOpen(0); + + if (gameController != nullptr) { + SDL_Joystick* stick = SDL_GameControllerGetJoystick(gameController); + gameControllerID = SDL_JoystickInstanceID(stick); + } + } + + // We need OpenGL for software rendering or for OpenGL if it's enabled + bool needOpenGL = config.rendererType == RendererType::Software; +#ifdef PANDA3DS_ENABLE_OPENGL + needOpenGL = needOpenGL || (config.rendererType == RendererType::OpenGL); +#endif + + if (needOpenGL) { + // Demand 3.3 core for software renderer, or 4.1 core for OpenGL renderer (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); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, config.rendererType == RendererType::Software ? 3 : 4); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, config.rendererType == RendererType::Software ? 3 : 1); + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_OPENGL); + + if (window == nullptr) { + Helpers::panic("Window creation failed: %s", SDL_GetError()); + } + + glContext = SDL_GL_CreateContext(window); + if (glContext == nullptr) { + Helpers::panic("OpenGL context creation failed: %s", SDL_GetError()); + } + + loadGlFunctions(SDL_GL_GetProcAddress); + } + +#ifdef PANDA3DS_ENABLE_VULKAN + if (config.rendererType == RendererType::Vulkan) { + window = SDL_CreateWindow("Alber", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_VULKAN); + + if (window == nullptr) { + Helpers::warn("Window creation failed: %s", SDL_GetError()); + } + } +#endif + + gpu.initGraphicsContext(window); +} + +void Emulator::run() { + programRunning = true; + + while (programRunning) { +#ifdef PANDA3DS_ENABLE_HTTP_SERVER + httpServer.processActions(); +#endif + + runFrame(); + HIDService& hid = kernel.getServiceManager().getHID(); + + SDL_Event event; + while (SDL_PollEvent(&event)) { + namespace Keys = HID::Keys; + + switch (event.type) { + case SDL_QUIT: + printf("Bye :(\n"); + programRunning = false; + return; + + case SDL_KEYDOWN: + if (romType == ROMType::None) break; + + switch (event.key.keysym.sym) { + case SDLK_l: hid.pressKey(Keys::A); break; + case SDLK_k: hid.pressKey(Keys::B); break; + case SDLK_o: hid.pressKey(Keys::X); break; + case SDLK_i: hid.pressKey(Keys::Y); break; + + case SDLK_q: hid.pressKey(Keys::L); break; + case SDLK_p: hid.pressKey(Keys::R); break; + + case SDLK_RIGHT: hid.pressKey(Keys::Right); break; + case SDLK_LEFT: hid.pressKey(Keys::Left); break; + case SDLK_UP: hid.pressKey(Keys::Up); break; + case SDLK_DOWN: hid.pressKey(Keys::Down); break; + + case SDLK_w: + hid.setCirclepadY(0x9C); + keyboardAnalogY = true; + break; + + case SDLK_a: + hid.setCirclepadX(-0x9C); + keyboardAnalogX = true; + break; + + case SDLK_s: + hid.setCirclepadY(-0x9C); + keyboardAnalogY = true; + break; + + case SDLK_d: + hid.setCirclepadX(0x9C); + keyboardAnalogX = true; + break; + + case SDLK_RETURN: hid.pressKey(Keys::Start); break; + case SDLK_BACKSPACE: hid.pressKey(Keys::Select); break; + + // Use the F4 button as a hot-key to pause or resume the emulator + // We can't use the audio play/pause buttons because it's annoying + case SDLK_F4: { + togglePause(); + break; + } + + // Use F5 as a reset button + case SDLK_F5: { + reset(ReloadOption::Reload); + break; + } + } + break; + + case SDL_KEYUP: + if (romType == ROMType::None) break; + + switch (event.key.keysym.sym) { + case SDLK_l: hid.releaseKey(Keys::A); break; + case SDLK_k: hid.releaseKey(Keys::B); break; + case SDLK_o: hid.releaseKey(Keys::X); break; + case SDLK_i: hid.releaseKey(Keys::Y); break; + + case SDLK_q: hid.releaseKey(Keys::L); break; + case SDLK_p: hid.releaseKey(Keys::R); break; + + case SDLK_RIGHT: hid.releaseKey(Keys::Right); break; + case SDLK_LEFT: hid.releaseKey(Keys::Left); break; + case SDLK_UP: hid.releaseKey(Keys::Up); break; + case SDLK_DOWN: hid.releaseKey(Keys::Down); break; + + // Err this is probably not ideal + case SDLK_w: + case SDLK_s: + hid.setCirclepadY(0); + keyboardAnalogY = false; + break; + + case SDLK_a: + case SDLK_d: + hid.setCirclepadX(0); + keyboardAnalogX = false; + break; + + case SDLK_RETURN: hid.releaseKey(Keys::Start); break; + case SDLK_BACKSPACE: hid.releaseKey(Keys::Select); break; + } + break; + + case SDL_MOUSEBUTTONDOWN: + if (romType == ROMType::None) break; + + if (event.button.button == SDL_BUTTON_LEFT) { + const s32 x = event.button.x; + const s32 y = event.button.y; + + // Check if touch falls in the touch screen area + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; + + hid.setTouchScreenPress(x_converted, y_converted); + } else { + hid.releaseTouchScreen(); + } + } else if (event.button.button == SDL_BUTTON_RIGHT) { + holdingRightClick = true; + } + + break; + + case SDL_MOUSEBUTTONUP: + if (romType == ROMType::None) break; + + if (event.button.button == SDL_BUTTON_LEFT) { + hid.releaseTouchScreen(); + } else if (event.button.button == SDL_BUTTON_RIGHT) { + holdingRightClick = false; + } + break; + + case SDL_CONTROLLERDEVICEADDED: + if (gameController == nullptr) { + gameController = SDL_GameControllerOpen(event.cdevice.which); + gameControllerID = event.cdevice.which; + } + break; + + case SDL_CONTROLLERDEVICEREMOVED: + if (event.cdevice.which == gameControllerID) { + SDL_GameControllerClose(gameController); + gameController = nullptr; + gameControllerID = 0; + } + break; + + case SDL_CONTROLLERBUTTONUP: + case SDL_CONTROLLERBUTTONDOWN: { + if (romType == ROMType::None) break; + 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) { + hid.pressKey(key); + } else { + hid.releaseKey(key); + } + } + break; + } + + // Detect mouse motion events for gyroscope emulation + case SDL_MOUSEMOTION: { + if (romType == ROMType::None) break; + + // Handle "dragging" across the touchscreen + if (hid.isTouchScreenPressed()) { + const s32 x = event.motion.x; + const s32 y = event.motion.y; + + // Check if touch falls in the touch screen area and register the new touch screen position + if (y >= 240 && y <= 480 && x >= 40 && x < 40 + 320) { + // Convert to 3DS coordinates + u16 x_converted = static_cast(x) - 40; + u16 y_converted = static_cast(y) - 240; + + hid.setTouchScreenPress(x_converted, y_converted); + } + } + + // We use right click to indicate we want to rotate the console. If right click is not held, then this is not a gyroscope rotation + if (holdingRightClick) { + // Relative motion since last mouse motion event + const s32 motionX = event.motion.xrel; + const s32 motionY = event.motion.yrel; + + // The gyroscope involves lots of weird math I don't want to bother with atm + // So up until then, we will set the gyroscope euler angles to fixed values based on the direction of the relative motion + const s32 roll = motionX > 0 ? 0x7f : -0x7f; + const s32 pitch = motionY > 0 ? 0x7f : -0x7f; + hid.setRoll(roll); + hid.setPitch(pitch); + } + break; + } + + case SDL_DROPFILE: { + char* droppedDir = event.drop.file; + + if (droppedDir) { + const std::filesystem::path path(droppedDir); + + if (path.extension() == ".amiibo") { + loadAmiibo(path); + } else if (path.extension() == ".lua") { + lua.loadFile(droppedDir); + } else { + loadROM(path); + } + + SDL_free(droppedDir); + } + break; + } + } + } + + // Update controller analog sticks and HID service + if (romType != ROMType::None) { + if (gameController != nullptr) { + const s16 stickX = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTX); + const s16 stickY = SDL_GameControllerGetAxis(gameController, SDL_CONTROLLER_AXIS_LEFTY); + constexpr s16 deadzone = 3276; + constexpr s16 maxValue = 0x9C; + constexpr s16 div = 0x8000 / maxValue; + + // Avoid overriding the keyboard's circlepad input + if (abs(stickX) < deadzone && !keyboardAnalogX) { + hid.setCirclepadX(0); + } else { + hid.setCirclepadX(stickX / div); + } + + if (abs(stickY) < deadzone && !keyboardAnalogY) { + hid.setCirclepadY(0); + } else { + hid.setCirclepadY(-(stickY / div)); + } + } + + hid.updateInputs(cpu.getTicks()); + } + // TODO: Should this be uncommented? + // kernel.evalReschedule(); + + // Update inputs in the HID module + SDL_GL_SwapWindow(window); + } +} diff --git a/src/panda_sdl/main.cpp b/src/panda_sdl/main.cpp index e24d47449..e1961c52a 100644 --- a/src/panda_sdl/main.cpp +++ b/src/panda_sdl/main.cpp @@ -1,8 +1,10 @@ #include "emulator.hpp" +extern SDL_Window* window; + int main(int argc, char *argv[]) { Emulator emu; - emu.initGraphicsContext(); + emu.initFrontend(); if (argc > 1) { auto romPath = std::filesystem::current_path() / argv[1];