diff --git a/.gitmodules b/.gitmodules index 462d67260..8a6cac496 100644 --- a/.gitmodules +++ b/.gitmodules @@ -40,3 +40,6 @@ [submodule "third_party/mio"] path = third_party/mio url = https://github.com/vimpunk/mio +[submodule "third_party/hydra_core"] + path = third_party/hydra_core + url = https://github.com/hydra-emu/core diff --git a/CMakeLists.txt b/CMakeLists.txt index cf8e28c4f..4371fbc12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ option(ENABLE_HTTP_SERVER "Enable HTTP server. Used for Discord bot support" OFF option(ENABLE_DISCORD_RPC "Compile with Discord RPC support (disabled by default)" ON) option(ENABLE_LUAJIT "Enable scripting with the Lua programming language" ON) option(ENABLE_QT_GUI "Enable the Qt GUI. If not selected then the emulator uses a minimal SDL-based UI instead" OFF) +option(BUILD_HYDRA_CORE "Build a Hydra core" OFF) include_directories(${PROJECT_SOURCE_DIR}/include/) include_directories(${PROJECT_SOURCE_DIR}/include/kernel) @@ -59,6 +60,10 @@ add_compile_definitions(NOMINMAX) # Make windows.h not define min/ma add_compile_definitions(WIN32_LEAN_AND_MEAN) # Make windows.h not include literally everything add_compile_definitions(SDL_MAIN_HANDLED) +if(BUILD_HYDRA_CORE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + if(ENABLE_DISCORD_RPC AND NOT ANDROID) add_subdirectory(third_party/discord-rpc) include_directories(third_party/discord-rpc/include) @@ -373,7 +378,13 @@ if(ENABLE_VULKAN) set(ALL_SOURCES ${ALL_SOURCES} ${RENDERER_VK_SOURCE_FILES}) endif() -add_executable(Alber ${ALL_SOURCES}) +if(BUILD_HYDRA_CORE) + include_directories(third_party/hydra_core/include) + add_library(Alber SHARED ${ALL_SOURCES} src/hydra_core.cpp) + target_compile_definitions(Alber PRIVATE PANDA3DS_HYDRA_CORE=1) +else() + add_executable(Alber ${ALL_SOURCES}) +endif() if(ENABLE_LTO OR ENABLE_USER_BUILD) set_target_properties(Alber PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE) diff --git a/include/emulator.hpp b/include/emulator.hpp index c1d228e77..745ede6d1 100644 --- a/include/emulator.hpp +++ b/include/emulator.hpp @@ -114,6 +114,7 @@ class Emulator { void setOutputSize(u32 width, u32 height) { gpu.setOutputSize(width, height); } EmulatorConfig& getConfig() { return config; } + ServiceManager& getServiceManager() { return kernel.getServiceManager(); } RendererType getRendererType() const { return config.rendererType; } Renderer* getRenderer() { return gpu.getRenderer(); } }; diff --git a/include/helpers.hpp b/include/helpers.hpp index afebc9594..9082cc549 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -83,6 +83,13 @@ namespace Helpers { return false; } + static constexpr bool isHydraCore() { +#ifdef PANDA3DS_HYDRA_CORE + return true; +#endif + return false; + } + static void debug_printf(const char* fmt, ...) { if constexpr (buildingInDebugMode()) { std::va_list args; diff --git a/include/services/hid.hpp b/include/services/hid.hpp index 1db0fa5dc..febd7bd68 100644 --- a/include/services/hid.hpp +++ b/include/services/hid.hpp @@ -91,6 +91,7 @@ class HIDService { void pressKey(u32 mask) { newButtons |= mask; } void releaseKey(u32 mask) { newButtons &= ~mask; } + void setKey(u32 mask, bool pressed) { pressed ? pressKey(mask) : releaseKey(mask); } u32 getOldButtons() const { return oldButtons; } s16 getCirclepadX() const { return circlePadX; } diff --git a/src/core/renderer_gl/renderer_gl.cpp b/src/core/renderer_gl/renderer_gl.cpp index 107b88061..a37431262 100644 --- a/src/core/renderer_gl/renderer_gl.cpp +++ b/src/core/renderer_gl/renderer_gl.cpp @@ -522,9 +522,11 @@ void RendererGL::display() { OpenGL::draw(OpenGL::TriangleStrip, 4); } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - screenFramebuffer.bind(OpenGL::ReadFramebuffer); - glBlitFramebuffer(0, 0, 400, 480, 0, 0, outputWindowWidth, outputWindowHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + if constexpr (!Helpers::isHydraCore()) { + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + screenFramebuffer.bind(OpenGL::ReadFramebuffer); + glBlitFramebuffer(0, 0, 400, 480, 0, 0, outputWindowWidth, outputWindowHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } } void RendererGL::clearBuffer(u32 startAddress, u32 endAddress, u32 value, u32 control) { @@ -799,4 +801,4 @@ void RendererGL::screenshot(const std::string& name) { } stbi_write_png(name.c_str(), width, height, 4, flippedPixels.data(), 0); -} +} \ No newline at end of file diff --git a/src/http_server.cpp b/src/http_server.cpp index f595a25cd..41c7ae82e 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -228,7 +228,7 @@ void HttpServer::startHttpServer() { } std::string HttpServer::status() { - HIDService& hid = emulator->kernel.getServiceManager().getHID(); + HIDService& hid = emulator->getServiceManager().getHID(); std::stringstream stringStream; stringStream << "Panda3DS\n"; @@ -271,7 +271,7 @@ void HttpServer::processActions() { return; } - HIDService& hid = emulator->kernel.getServiceManager().getHID(); + HIDService& hid = emulator->getServiceManager().getHID(); while (!actionQueue.empty()) { std::unique_ptr action = std::move(actionQueue.front()); diff --git a/src/hydra_core.cpp b/src/hydra_core.cpp new file mode 100644 index 000000000..f71fc0bf5 --- /dev/null +++ b/src/hydra_core.cpp @@ -0,0 +1,122 @@ +#include +#include +#include + +class HC_GLOBAL HydraCore final : public hydra::IBase, public hydra::IOpenGlRendered, public hydra::IFrontendDriven, public hydra::IInput { + HYDRA_CLASS + public: + HydraCore(); + + private: + // IBase + bool loadFile(const char* type, const char* path) override; + void reset() override; + hydra::Size getNativeSize() override; + void setOutputSize(hydra::Size size) override; + + // IOpenGlRendered + void setFbo(unsigned handle) override; + void setContext(void* context) override; + void setGetProcAddress(void* function) override; + + // IFrontendDriven + void runFrame() override; + + // IInput + void setPollInputCallback(void (*callback)()) override; + void setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) override; + + std::unique_ptr emulator; + RendererGL* renderer; + void (*pollInputCallback)() = nullptr; + int32_t (*checkButtonCallback)(uint32_t player, hydra::ButtonType button) = nullptr; +}; + +HydraCore::HydraCore() : emulator(new Emulator) { + if (emulator->getRendererType() != RendererType::OpenGL) { + throw std::runtime_error("HydraCore: Renderer is not OpenGL"); + } + renderer = static_cast(emulator->getRenderer()); +} + +bool HydraCore::loadFile(const char* type, const char* path) { + if (std::string(type) == "rom") { + return emulator->loadROM(path); + } else { + return false; + } +} + +void HydraCore::runFrame() { + renderer->resetStateManager(); + + pollInputCallback(); + HIDService& hid = emulator->getServiceManager().getHID(); + hid.setKey(HID::Keys::A, checkButtonCallback(0, hydra::ButtonType::A)); + hid.setKey(HID::Keys::B, checkButtonCallback(0, hydra::ButtonType::B)); + hid.setKey(HID::Keys::X, checkButtonCallback(0, hydra::ButtonType::X)); + hid.setKey(HID::Keys::Y, checkButtonCallback(0, hydra::ButtonType::Y)); + hid.setKey(HID::Keys::L, checkButtonCallback(0, hydra::ButtonType::L1)); + hid.setKey(HID::Keys::R, checkButtonCallback(0, hydra::ButtonType::R1)); + hid.setKey(HID::Keys::Start, checkButtonCallback(0, hydra::ButtonType::Start)); + hid.setKey(HID::Keys::Select, checkButtonCallback(0, hydra::ButtonType::Select)); + hid.setKey(HID::Keys::Up, checkButtonCallback(0, hydra::ButtonType::Keypad1Up)); + hid.setKey(HID::Keys::Down, checkButtonCallback(0, hydra::ButtonType::Keypad1Down)); + hid.setKey(HID::Keys::Left, checkButtonCallback(0, hydra::ButtonType::Keypad1Left)); + hid.setKey(HID::Keys::Right, checkButtonCallback(0, hydra::ButtonType::Keypad1Right)); + + int x = !!checkButtonCallback(0, hydra::ButtonType::Analog1Right) - !!checkButtonCallback(0, hydra::ButtonType::Analog1Left); + int y = !!checkButtonCallback(0, hydra::ButtonType::Analog1Up) - !!checkButtonCallback(0, hydra::ButtonType::Analog1Down); + hid.setCirclepadX(x * 0x9C); + hid.setCirclepadY(y * 0x9C); + + emulator->runFrame(); +} + +void HydraCore::reset() { emulator->reset(Emulator::ReloadOption::Reload); } + +hydra::Size HydraCore::getNativeSize() { return {400, 480}; } + +// Size doesn't matter as the glBlitFramebuffer call is commented out for the core +void HydraCore::setOutputSize(hydra::Size size) {} + +void HydraCore::setGetProcAddress(void* function) { +#ifdef __ANDROID__ + if (!gladLoadGLES2Loader(reinterpret_cast(function))) { + Helpers::panic("OpenGL ES init failed"); + } +#else + if (!gladLoadGLLoader(reinterpret_cast(function))) { + Helpers::panic("OpenGL init failed"); + } +#endif + // SDL_Window is not used, so we pass nullptr + emulator->initGraphicsContext(nullptr); +} + +void HydraCore::setContext(void*) {} + +void HydraCore::setFbo(unsigned handle) { renderer->setFBO(handle); } + +void HydraCore::setPollInputCallback(void (*callback)()) { pollInputCallback = callback; } + +void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t player, hydra::ButtonType button)) { checkButtonCallback = callback; } + +HC_API hydra::IBase* createEmulator() { return new HydraCore; } + +HC_API void destroyEmulator(hydra::IBase* emulator) { delete emulator; } + +HC_API const char* getInfo(hydra::InfoType type) { + switch (type) { + case hydra::InfoType::CoreName: return "Panda3DS"; + case hydra::InfoType::SystemName: return "Nintendo 3DS"; + case hydra::InfoType::Description: return "HLE 3DS emulator. There's a little Alber in your computer and he runs Nintendo 3DS games."; + case hydra::InfoType::Author: return "wheremyfoodat (Peach)"; + case hydra::InfoType::Version: return "0.7"; + case hydra::InfoType::License: return "GPLv3"; + case hydra::InfoType::Website: return "https://panda3ds.com/"; + case hydra::InfoType::Extensions: return "3ds,cci,cxi,app,3dsx,elf,axf"; + case hydra::InfoType::Firmware: return ""; + default: return nullptr; + } +} diff --git a/third_party/hydra_core b/third_party/hydra_core new file mode 160000 index 000000000..090c8a74e --- /dev/null +++ b/third_party/hydra_core @@ -0,0 +1 @@ +Subproject commit 090c8a74e0a970052ca8c63050a17e918036d3d8