From 8481d3f0a871d0043cbd71d228d77b73c778c6d3 Mon Sep 17 00:00:00 2001 From: offtkp Date: Tue, 26 Sep 2023 12:14:27 +0300 Subject: [PATCH] Hydra core --- .github/workflows/deploy_android.yml | 28 ---- .github/workflows/deploy_freebsd.yml | 29 ---- .github/workflows/deploy_ios.yml | 36 ----- .github/workflows/deploy_linux.yml | 9 +- .github/workflows/deploy_mac.yml | 19 +-- .github/workflows/deploy_web.yml | 55 ------- .github/workflows/deploy_win.yml | 9 +- .gitmodules | 3 + CMakeLists.txt | 17 +- src/hydra/core | 1 + src/hydra/core.cxx | 233 +++++++++++++++++++++++++++ src/main.c | 80 ++++++++- 12 files changed, 344 insertions(+), 175 deletions(-) delete mode 100644 .github/workflows/deploy_android.yml delete mode 100644 .github/workflows/deploy_freebsd.yml delete mode 100644 .github/workflows/deploy_ios.yml delete mode 100644 .github/workflows/deploy_web.yml create mode 100644 .gitmodules create mode 160000 src/hydra/core create mode 100644 src/hydra/core.cxx diff --git a/.github/workflows/deploy_android.yml b/.github/workflows/deploy_android.yml deleted file mode 100644 index f3addea0e..000000000 --- a/.github/workflows/deploy_android.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Build Android -on: [push,pull_request] -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 - - name: Setup Ninja - uses: seanmiddleditch/gha-setup-ninja@master - - name: Setup Java - uses: actions/setup-java@v3 - with: - distribution: 'temurin' # See 'Supported distributions' for available options - java-version: '17' - - uses: sparkfabrik/android-build-action@v1.5.0 - with: - project-path: tools/android_project - output-path: SkyEmu.apk - gradle-task: assemble - - name: GH Release 🚀 - # You may pin to the exact commit or the version. - uses: actions/upload-artifact@v2 - with: - name: AndroidRelease - path: SkyEmu.apk - - diff --git a/.github/workflows/deploy_freebsd.yml b/.github/workflows/deploy_freebsd.yml deleted file mode 100644 index 9ed888d87..000000000 --- a/.github/workflows/deploy_freebsd.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: Build FreeBSD -on: [push,pull_request] -jobs: - build-and-deploy: - runs-on: macos-12 - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 - - name: Configure & Build 🔧 - id: test - uses: vmactions/freebsd-vm@v0 - with: - envs: 'MYTOKEN MYTOKEN2' - usesh: true - prepare: | - pkg install -y curl cmake libX11 libXi libXrandr libXinerama libXcursor libglvnd alsa-lib - - - run: | - mkdir build - cd build - cmake .. && cmake --build . - - name: GH Release 🚀 - # You may pin to the exact commit or the version. - uses: actions/upload-artifact@v2 - with: - name: FreeBSDRelease - path: build/bin/ - diff --git a/.github/workflows/deploy_ios.yml b/.github/workflows/deploy_ios.yml deleted file mode 100644 index 121c4c744..000000000 --- a/.github/workflows/deploy_ios.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build iOS -on: [push,pull_request] -jobs: - build-and-deploy: - runs-on: macos-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 - - name: Configure & Build 🔧 - run: | - mkdir build - cd build - cmake -GXcode -DSOKOL_BACKEND=SOKOL_METAL -DCMAKE_SYSTEM_NAME=iOS ../ - cmake --build . --config Release - mkdir Payload - cp -R bin/Release/SkyEmu.app Payload/SkyEmu.app - zip -r SkyEmu.ipa Payload - - name: GH Release 🚀 - # You may pin to the exact commit or the version. - uses: actions/upload-artifact@v2 - with: - name: iOSRelease - path: build/SkyEmu.ipa - #uses: softprops/action-gh-release@v0.1.5 - #with: - # # Note-worthy description of changes in release - # # body: # optional - # # Path to load note-worthy description of changes in release from - # # body_path: # optional - # # Gives the release a custom name. Defaults to tag name - # name: LinuxRelease - # # Identify the release as a prerelease. Defaults to false - # prerelease: True - # # Newline-delimited list of path globs for asset files to upload - # files: build/bin/* - diff --git a/.github/workflows/deploy_linux.yml b/.github/workflows/deploy_linux.yml index cb9d73788..3a9a75829 100644 --- a/.github/workflows/deploy_linux.yml +++ b/.github/workflows/deploy_linux.yml @@ -1,11 +1,12 @@ name: Build Linux -on: [push,pull_request] +on: [push] jobs: build-and-deploy: runs-on: ubuntu-latest steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 + - uses: actions/checkout@v3 + with: + submodules: 'true' - name: Install dependencies run: | sudo apt-get update @@ -15,7 +16,7 @@ jobs: run: | mkdir build cd build - cmake .. && cmake --build . + cmake -DBUILD_HYDRA_CORE=1 .. && cmake --build . - name: GH Release 🚀 # You may pin to the exact commit or the version. diff --git a/.github/workflows/deploy_mac.yml b/.github/workflows/deploy_mac.yml index 84da505b3..c03e038d2 100644 --- a/.github/workflows/deploy_mac.yml +++ b/.github/workflows/deploy_mac.yml @@ -1,30 +1,23 @@ name: Build macOS -on: [push,pull_request] +on: [push] jobs: build-and-deploy: runs-on: macos-latest steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 + - uses: actions/checkout@v3 + with: + submodules: 'true' - name: Configure & Build 🔧 run: | mkdir build cd build - cmake .. && cmake --build . - - name: Build DMG - run: | - cd build - brew install graphicsmagick imagemagick - npm install --global create-dmg - create-dmg --dmg-title=SkyEmu 'bin/SkyEmu.app' || true - ls - mv SkyEmu*.dmg "SkyEmu.dmg" + cmake -DBUILD_HYDRA_CORE=1 .. && cmake --build . - name: GH Release 🚀 # You may pin to the exact commit or the version. uses: actions/upload-artifact@v2 with: name: MacOSRelease - path: build/SkyEmu.dmg + path: build/libSkyEmu.dylib #uses: softprops/action-gh-release@v0.1.5 #with: # # Note-worthy description of changes in release diff --git a/.github/workflows/deploy_web.yml b/.github/workflows/deploy_web.yml deleted file mode 100644 index dc94c3c30..000000000 --- a/.github/workflows/deploy_web.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Build Web & Deploy to GH -on: [push,pull_request] -jobs: - build-and-deploy: - runs-on: ubuntu-latest - steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 - - name: Setup emsdk - uses: mymindstorm/setup-emsdk@v10 - with: - version: 2.0.21 - actions-cache-folder: 'emsdk-cache' - - name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built. - run: | - mkdir build - cd build - emcmake cmake .. -DPLATFORM=Web && cmake --build . - mkdir website - mv bin/SkyEmu.html bin/index.html - - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: gh-pages # The branch the action should deploy to. - folder: build/bin # The folder the action should deploy. - target-folder: commit/${{ github.sha }} - force: false - clean: false - - name: Post PR Comment - continue-on-error: true - if: github.event_name == 'pull_request' - uses: mshick/add-pr-comment@v1 - with: - message: | - Web build for ${{ github.sha }} will be live at https://web.skyemu.app/commit/${{ github.sha }}/index.html - repo-token: ${{ secrets.GITHUB_TOKEN }} - repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens - allow-repeats: true # This is the default - - name: Deploy to main site 🚀 - if: github.ref == 'refs/heads/main' - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: gh-pages # The branch the action should deploy to. - folder: build/bin # The folder the action should deploy. - force: false - clean: false - - name: Deploy to dev site 🚀 - if: github.ref == 'refs/heads/dev' - uses: JamesIves/github-pages-deploy-action@v4 - with: - branch: gh-pages # The branch the action should deploy to. - folder: build/bin # The folder the action should deploy. - target-folder: branch/dev - force: false - clean: false diff --git a/.github/workflows/deploy_win.yml b/.github/workflows/deploy_win.yml index 8a40f3dd5..9113b0609 100644 --- a/.github/workflows/deploy_win.yml +++ b/.github/workflows/deploy_win.yml @@ -1,11 +1,12 @@ name: Build Windows -on: [push,pull_request] +on: [push] jobs: build-and-deploy: runs-on: windows-2019 steps: - - name: Checkout 🛎️ - uses: actions/checkout@v2.3.1 + - uses: actions/checkout@v3 + with: + submodules: 'true' - name: Get latest CMake and ninja # Using 'latest' branch, the most recent CMake and ninja are installed. uses: lukka/get-cmake@latest @@ -14,7 +15,7 @@ jobs: run: | mkdir build cd build - cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0.19041.0 .. + cmake -G "Visual Studio 16 2019" -A x64 -DCMAKE_SYSTEM_VERSION=10.0.19041.0 -DBUILD_HYDRA_CORE=1 .. cmake --build . --config Release - name: GH Release 🚀 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1a81d791b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/hydra/core"] + path = src/hydra/core + url = https://github.com/hydra-emu/core diff --git a/CMakeLists.txt b/CMakeLists.txt index ed315bc8b..8dcb8cdb9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -110,6 +110,14 @@ if(NOT EMSCRIPTEN) set(ENABLE_HTTP_CONTROL_SERVER 1) endif() +if(BUILD_HYDRA_CORE) + set(STANDALONE_CORE 1) +endif() + +if(STANDALONE_CORE) + set(CMAKE_POSITION_INDEPENDENT_CODE ON) +endif() + #=== LIBRARY: cimgui + Dear ImGui add_library(cimgui STATIC src/cimgui/cimgui.cpp @@ -297,7 +305,7 @@ elseif(MACOS OR IOS) set_property(TARGET ${PROJECT_NAME} PROPERTY MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/src/resources/ios-info.plist.in") set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2") endif() -elseif(ANDROID) +elseif(ANDROID OR STANDALONE_CORE) add_library(${PROJECT_NAME} SHARED ${SKYEMU_SRC}) else() add_executable(${PROJECT_NAME} ${SKYEMU_SRC}) @@ -340,3 +348,10 @@ elseif(EMSCRIPTEN) ${CMAKE_CURRENT_BINARY_DIR}/bin) endif() +if(BUILD_HYDRA_CORE) + project(hydra_core CXX) + add_library(hydra_core SHARED src/hydra/core.cxx) + set_source_files_properties(src/hydra/core.cxx PROPERTIES COMPILE_FLAGS "-std=c++20") + target_include_directories(hydra_core PRIVATE src/hydra/core/include/) + target_link_libraries(hydra_core PRIVATE SkyEmu) +endif() \ No newline at end of file diff --git a/src/hydra/core b/src/hydra/core new file mode 160000 index 000000000..1f567bb2c --- /dev/null +++ b/src/hydra/core @@ -0,0 +1 @@ +Subproject commit 1f567bb2c16082b2fe88a54389a3405832c7799c diff --git a/src/hydra/core.cxx b/src/hydra/core.cxx new file mode 100644 index 000000000..f36e585b0 --- /dev/null +++ b/src/hydra/core.cxx @@ -0,0 +1,233 @@ +#include "hydra/core.hxx" +#include +#include +#include +#include + +extern "C" { + void se_load_rom(const char* path); + void se_reset_core(); + void se_emulate_single_frame(); + void se_screenshot(uint8_t*, int*, int*); + void se_emulate_single_frame(); + int se_get_width(); + int se_get_height(); + void se_touch(float, float); + float* se_get_inputs(); + uint32_t se_add_cheat(uint8_t* data, uint32_t size); + void se_remove_cheat(uint32_t id); + void se_enable_cheat(uint32_t id); + void se_disable_cheat(uint32_t id); + void se_push_all_samples(void(*)(void*, size_t)); + uint32_t se_sample_count(); +} + +class HydraCore : public hydra::IBase, public hydra::ISoftwareRendered, public hydra::IFrontendDriven, public hydra::IInput, public hydra::IAudio, public hydra::ICheat +{ + HYDRA_CLASS +public: + bool loadFile(const char* type, const char* path) override; + void reset() override; + hydra::Size getNativeSize() override; + void setOutputSize(hydra::Size size) override; + + void setVideoCallback(void (*callback)(void* data, hydra::Size size)) override; + + void runFrame() override; + uint16_t getFps() override; + + uint32_t getSampleRate() override { return 48000; } + void setAudioCallback(void (*callback)(void* data, size_t size)) override; + + void setPollInputCallback(void(*callback)()) override; + void setCheckButtonCallback(int32_t (*callback)(uint32_t, hydra::ButtonType)) override; + + uint32_t addCheat(const uint8_t* code, uint32_t size) override; + void removeCheat(uint32_t id) override; + void enableCheat(uint32_t id) override; + void disableCheat(uint32_t id) override; + +private: + void (*video_callback)(void* data, hydra::Size size) = nullptr; + void (*audio_callback)(void* data, size_t size) = nullptr; + void (*poll_callback)() = nullptr; + int32_t (*read_callback)(uint32_t, hydra::ButtonType) = nullptr; +}; + +bool HydraCore::loadFile(const char* type, const char* path) +{ + if (std::string(type) == std::string("rom")) + { + se_load_rom(path); + return true; + } + return false; +} + +void HydraCore::reset() +{ + se_reset_core(); +} + +hydra::Size HydraCore::getNativeSize() +{ + return hydra::Size(se_get_width(), se_get_height()); +} + +void HydraCore::setOutputSize(hydra::Size size) +{ +} + +void HydraCore::setVideoCallback(void (*callback)(void* data, hydra::Size size)) +{ + video_callback = callback; +} + +void HydraCore::setAudioCallback(void (*callback)(void* data, size_t size)) +{ + audio_callback = callback; +} + +#define SE_KEY_A 0 +#define SE_KEY_B 1 +#define SE_KEY_X 2 +#define SE_KEY_Y 3 +#define SE_KEY_UP 4 +#define SE_KEY_DOWN 5 +#define SE_KEY_LEFT 6 +#define SE_KEY_RIGHT 7 +#define SE_KEY_L 8 +#define SE_KEY_R 9 +#define SE_KEY_START 10 +#define SE_KEY_SELECT 11 +#define SE_KEY_FOLD_SCREEN 12 +#define SE_KEY_PEN_DOWN 13 +#define SE_KEY_EMU_PAUSE 14 +#define SE_KEY_EMU_REWIND 15 +#define SE_KEY_EMU_FF_2X 16 +#define SE_KEY_EMU_FF_MAX 17 +#define SE_KEY_CAPTURE_STATE(A) (18+(A)*2) +#define SE_KEY_RESTORE_STATE(A) (18+(A)*2+1) +#define SE_KEY_RESET_GAME 26 +#define SE_KEY_TURBO_A 27 +#define SE_KEY_TURBO_B 28 +#define SE_KEY_TURBO_X 29 +#define SE_KEY_TURBO_Y 30 +#define SE_KEY_TURBO_L 31 +#define SE_KEY_TURBO_R 32 +#define SE_KEY_SOLAR_P 33 +#define SE_KEY_SOLAR_M 34 +#define SE_KEY_TOGGLE_FULLSCREEN 35 +#define SE_NUM_KEYBINDS 36 +void HydraCore::runFrame() +{ + poll_callback(); + float* inputs = se_get_inputs(); + inputs[SE_KEY_LEFT] = !!read_callback(0, hydra::ButtonType::Keypad1Left); + inputs[SE_KEY_RIGHT] = !!read_callback(0, hydra::ButtonType::Keypad1Right); + inputs[SE_KEY_UP] = !!read_callback(0, hydra::ButtonType::Keypad1Up); + inputs[SE_KEY_DOWN] = !!read_callback(0, hydra::ButtonType::Keypad1Down); + inputs[SE_KEY_A] = !!read_callback(0, hydra::ButtonType::A); + inputs[SE_KEY_B] = !!read_callback(0, hydra::ButtonType::B); + inputs[SE_KEY_X] = !!read_callback(0, hydra::ButtonType::X); + inputs[SE_KEY_Y] = !!read_callback(0, hydra::ButtonType::Y); + inputs[SE_KEY_SELECT] = !!read_callback(0, hydra::ButtonType::Select); + inputs[SE_KEY_START] = !!read_callback(0, hydra::ButtonType::Start); + inputs[SE_KEY_L] = !!read_callback(0, hydra::ButtonType::L1); + inputs[SE_KEY_R] = !!read_callback(0, hydra::ButtonType::R1); + + uint32_t touch = read_callback(0, hydra::ButtonType::Touch); + uint16_t x = touch >> 16; + uint16_t y = touch & 0xFFFF; + if (touch != hydra::TOUCH_RELEASED && y > 192) + { + inputs[SE_KEY_PEN_DOWN] = 1; + float fx = (float)x / 256.0f; + float fy = (float)(y - 192) / 192.0f; + se_touch(fx, fy); + } + else + { + inputs[SE_KEY_PEN_DOWN] = 0; + } + + se_emulate_single_frame(); + int out_width = se_get_width(), out_height = se_get_height(); + uint8_t* imdata = (uint8_t*)malloc(out_width * out_height * 4); + se_screenshot(imdata, &out_width, &out_height); + video_callback(imdata, { static_cast(out_width), static_cast(out_height) }); + free(imdata); + + se_push_all_samples(audio_callback); +} + +uint16_t HydraCore::getFps() +{ + return 60; +} + +void HydraCore::setPollInputCallback(void(*callback)()) +{ + poll_callback = callback; +} + +void HydraCore::setCheckButtonCallback(int32_t (*callback)(uint32_t, hydra::ButtonType)) +{ + read_callback = callback; +} + +uint32_t HydraCore::addCheat(const uint8_t* code, uint32_t size) +{ + return se_add_cheat((uint8_t*)code, size); +} + +void HydraCore::removeCheat(uint32_t id) +{ + se_remove_cheat(id); +} + +void HydraCore::enableCheat(uint32_t id) +{ + se_enable_cheat(id); +} + +void HydraCore::disableCheat(uint32_t id) +{ + se_disable_cheat(id); +} + +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 "SkyEmu"; + case hydra::InfoType::SystemName: + return "Gameboy Color, Gameboy Advance, Nintendo DS"; + case hydra::InfoType::Description: + return "Game Boy Advance, Game Boy, Game Boy Color, and Nintendo DS Emulator"; + case hydra::InfoType::Version: + return "4.0"; + case hydra::InfoType::Author: + return "Sky"; + case hydra::InfoType::Extensions: + return "gb,gbc,gba,nds"; + case hydra::InfoType::License: + return "MIT"; + case hydra::InfoType::Website: + return "https://skyemu.app"; + case hydra::InfoType::Firmware: + return ""; + default: return nullptr; + } +} diff --git a/src/main.c b/src/main.c index 0479a443e..81906c4df 100644 --- a/src/main.c +++ b/src/main.c @@ -503,7 +503,8 @@ void se_run_all_ar_cheats(); void se_load_cheats(const char * filename); void se_save_cheats(const char* filename); void se_convert_cheat_code(char * text_code, int cheat_index); -static void se_reset_core(); +void se_reset_core(); +float* se_get_inputs() { return emu_state.joy.inputs; } static bool se_load_theme_from_file(const char * filename); static bool se_draw_theme_region(int region, float x, float y, float w, float h); static bool se_draw_theme_region_tint(int region, float x, float y, float w, float h,uint32_t tint); @@ -2181,7 +2182,7 @@ void se_load_rom(const char *filename){ } return; } -static void se_reset_core(){ +void se_reset_core(){ se_load_rom(gui_state.recently_loaded_games[0].path); } static bool se_write_save_to_disk(const char* path){ @@ -2300,7 +2301,8 @@ se_lcd_info_t se_get_lcd_info(){ .gamma = 2.2 }; } -static void se_emulate_single_frame(){ +void se_emulate_single_frame(){ +emu_state.render_frame = true; if(emu_state.system == SYSTEM_GB){ if(gui_state.test_runner_mode){ uint8_t palette[4*3] = { 0xff,0xff,0xff,0xAA,0xAA,0xAA,0x55,0x55,0x55,0x00,0x00,0x00 }; @@ -2320,7 +2322,7 @@ static void se_emulate_single_frame(){ se_run_all_ar_cheats(); } -static void se_screenshot(uint8_t * output_buffer, int * out_width, int * out_height){ +void se_screenshot(uint8_t * output_buffer, int * out_width, int * out_height){ *out_height=*out_width=0; // output_bufer is always SE_MAX_SCREENSHOT_SIZE bytes. RGB8 if(emu_state.system==SYSTEM_GBA){ @@ -2337,7 +2339,20 @@ static void se_screenshot(uint8_t * output_buffer, int * out_width, int * out_he *out_height = SB_LCD_H; memcpy(output_buffer,scratch.gb.framebuffer,SB_LCD_W*SB_LCD_H*4); } - for(int i=3;i