diff --git a/.github/linux/Zelda64Recompiled.desktop b/.github/linux/Zelda64Recompiled.desktop new file mode 100644 index 0000000..83aefcf --- /dev/null +++ b/.github/linux/Zelda64Recompiled.desktop @@ -0,0 +1,9 @@ +[Desktop Entry] +Name=Zelda64Recompiled +Type=Application +Terminal=false +Icon=Zelda64Recompiled +Exec=Zelda64Recompiled +GenericName=Zelda64Recompiled +Categories=Game; + diff --git a/.github/linux/appimage.sh b/.github/linux/appimage.sh new file mode 100755 index 0000000..e1c23cf --- /dev/null +++ b/.github/linux/appimage.sh @@ -0,0 +1,17 @@ +curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-static-x86_64.AppImage" +curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy-plugin-gtk/raw/master/linuxdeploy-plugin-gtk.sh" +chmod a+x linuxdeploy* + +mkdir -p AppDir/usr/bin +cp Zelda64Recompiled AppDir/usr/bin/ +cp -r assets/ AppDir/usr/bin/ +cp icons/512.png AppDir/Zelda64Recompiled.png +cp .github/linux/Zelda64Recompiled.desktop AppDir/ + +./linuxdeploy-static-x86_64.AppImage --appimage-extract +mv squashfs-root/ deploy +ARCH=x86_64 ./deploy/AppRun --appdir=AppDir/ -d AppDir/Zelda64Recompiled.desktop -i AppDir/Zelda64Recompiled.png -e AppDir/usr/bin/Zelda64Recompiled --plugin gtk +sed -i 's/exec/#exec/g' AppDir/AppRun +echo 'cd "$this_dir"/usr/bin/' >> AppDir/AppRun +echo './Zelda64Recompiled' >> AppDir/AppRun +ARCH=x86_64 ./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 050d51e..bd339b0 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -2,14 +2,23 @@ name: validate on: push: branches: - - main - pull_request: + - dev + pull_request_target: types: [opened, synchronize] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: + authorize: + environment: + ${{ github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository && + 'external' || 'internal' }} + runs-on: ubuntu-latest + steps: + - run: echo ✓ build-unix: + needs: authorize runs-on: ubuntu-22.04 strategy: matrix: @@ -18,6 +27,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} submodules: recursive - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -27,7 +37,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build libsdl2-dev libgtk-3-dev lld llvm clang-15 + sudo apt-get install -y ninja-build libsdl2-dev libgtk-3-dev lld llvm clang-15 libfuse2 # Install SDL2 echo ::group::install SDL2 @@ -45,10 +55,8 @@ jobs: echo ::endgroup:: - name: Prepare Build run: |- - git clone https://${{ secrets.PAT }}@github.com/dcvz/zre.git + git clone ${{ secrets.ZRE_REPO_WITH_PAT }} ./zre/process.sh - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - name: Build N64Recomp & RSPRecomp if: runner.os != 'Windows' run: | @@ -82,14 +90,21 @@ jobs: run: | mv cmake-build/Zelda64Recompiled Zelda64Recompiled rm -rf assets/scss + tar -czf Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}.tar.gz Zelda64Recompiled assets/ - name: Archive Zelda64Recomp uses: actions/upload-artifact@v4 with: name: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }} - path: | - Zelda64Recompiled - assets/ + path: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }}.tar.gz + - name: Prepare AppImage + run: ./.github/linux/appimage.sh + - name: Zelda64Recomp AppImage + uses: actions/upload-artifact@v4 + with: + name: Zelda64Recompiled-AppImage-${{ matrix.type }} + path: Zelda64Recompiled-x86_64.AppImage build-windows: + needs: authorize runs-on: windows-latest strategy: matrix: @@ -98,6 +113,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} submodules: recursive - name: ccache uses: hendrikmuhs/ccache-action@v1.2 @@ -111,10 +127,8 @@ jobs: uses: ilammy/msvc-dev-cmd@v1 - name: Prepare Build run: |- - git clone https://${{ secrets.PAT }}@github.com/dcvz/zre.git + git clone ${{ secrets.ZRE_REPO_WITH_PAT }} ./zre/process.ps1 - env: - LARGE_SECRET_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - name: Build N64Recomp & RSPRecomp run: | git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource @@ -145,6 +159,9 @@ jobs: - name: Prepare Archive run: | Move-Item -Path "cmake-build/Zelda64Recompiled.exe" -Destination "Zelda64Recompiled.exe" + Move-Item -Path "cmake-build/dxcompiler.dll" -Destination "dxcompiler.dll" + Move-Item -Path "cmake-build/dxil.dll" -Destination "dxil.dll" + Move-Item -Path "cmake-build/SDL2.dll" -Destination "SDL2.dll" Remove-Item -Path "assets/scss" -Recurse -Force - name: Archive Zelda64Recomp uses: actions/upload-artifact@v4 @@ -152,4 +169,7 @@ jobs: name: Zelda64Recompiled-${{ runner.os }}-${{ matrix.type }} path: | Zelda64Recompiled.exe + dxcompiler.dll + dxil.dll + SDL2.dll assets/ diff --git a/.gitmodules b/.gitmodules index 25e5d53..34f6676 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "lib/lunasvg"] path = lib/lunasvg url = https://github.com/sammycage/lunasvg +[submodule "lib/sse2neon"] + path = lib/sse2neon + url = https://github.com/DLTcollab/sse2neon.git diff --git a/BUILDING.md b/BUILDING.md index 24cceee..b622ab9 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -74,7 +74,7 @@ Finally, you can build the project! :rocket: On Windows, you can open the repository folder with Visual Studio, and you'll be able to `[build / run / debug]` the project from there. If you prefer the commandline or you're on a Unix platform you can build the project using CMake: ```bash -cmake -S . -B cmake-build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -G Ninja -DCMAKE_BUILD_TYPE=Release # or Debug if you want to debug +cmake -S . -B build-cmake -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_C_COMPILER=clang -G Ninja -DCMAKE_BUILD_TYPE=Release # or Debug if you want to debug cmake --build build-cmake --target Zelda64Recompiled -j$(nproc) --config Release # or Debug ``` diff --git a/CMakeLists.txt b/CMakeLists.txt index 243ba12..299dadf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -87,11 +87,15 @@ add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches_bin.c DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin ) -# Generate mm_shader_cache.c from the MM shader cache -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h - COMMAND file_to_c ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin mm_shader_cache_bytes ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h - DEPENDS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin -) +# Generate mm_shader_cache.c from the MM shader cache if it exists +if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin) + set(HAS_MM_SHADER_CACHE TRUE) + add_compile_definitions(HAS_MM_SHADER_CACHE) + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h + COMMAND file_to_c ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin mm_shader_cache_bytes ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.h + DEPENDS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin + ) +endif() # Recompile patches elf into patches.c add_custom_command(OUTPUT @@ -159,10 +163,12 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/rsp/njpgdspMain.cpp ${CMAKE_SOURCE_DIR}/lib/RmlUi/Backends/RmlUi_Platform_SDL.cpp - - ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c ) +if (HAS_MM_SHADER_CACHE) + list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/mm_shader_cache.c) +endif() + target_include_directories(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/lib/concurrentqueue @@ -175,17 +181,25 @@ target_include_directories(Zelda64Recompiled PRIVATE ${CMAKE_SOURCE_DIR}/lib/rt64/src ${CMAKE_SOURCE_DIR}/lib/rt64/src/rhi ${CMAKE_SOURCE_DIR}/lib/rt64/src/render + ${CMAKE_SOURCE_DIR}/lib/sse2neon ${CMAKE_SOURCE_DIR}/lib/freetype-windows-binaries/include ${CMAKE_SOURCE_DIR}/lib/rt64/src/contrib/nativefiledialog-extended/src/include ${CMAKE_BINARY_DIR}/shaders ${CMAKE_CURRENT_BINARY_DIR} ) -target_compile_options(Zelda64Recompiled PRIVATE - -march=nehalem - -fno-strict-aliasing - -fms-extensions -) +if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + target_compile_options(Zelda64Recompiled PRIVATE + -march=nehalem + -fno-strict-aliasing + -fms-extensions + ) +else() + target_compile_options(Zelda64Recompiled PRIVATE + -fno-strict-aliasing + -fms-extensions + ) +endif() if (WIN32) include(FetchContent) @@ -289,7 +303,19 @@ if (${WIN32}) set (DXC "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc.exe") add_compile_definitions(NOMINMAX) else() - set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc") + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + if (APPLE) + set (DXC "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc") + else() + set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/x64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc") + endif() + else() + if (APPLE) + set (DXC "DYLD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-macos") + else() + set (DXC "LD_LIBRARY_PATH=${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/lib/arm64" "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/arm64/dxc-linux") + endif() + endif() endif() build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl") diff --git a/README.md b/README.md index 8997e1a..f457f7c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Zelda 64: Recompiled Zelda 64: Recompiled is a project that uses [N64: Recompiled](https://github.com/Mr-Wiseguy/N64Recomp) to **statically recompile** Majora's Mask (and soon Ocarina of Time) into a native port with many new features and enhancements. This project uses [RT64](https://github.com/rt64/rt64) as the rendering engine to provide some of these enhancements. -### [Check out the latest release here](https://github.com/Mr-Wiseguy/Zelda64Recomp/releases/tag/v1.0.0). +### [Check out the latest release here](https://github.com/Mr-Wiseguy/Zelda64Recomp/releases/latest). ### **This repository and its releases do not contain game assets. The original game is required to build or run this project.** @@ -102,6 +102,11 @@ You'll probably also want to change the default behavior so that you don't need - Windows: `%LOCALAPPDATA%\Zelda64Recompiled\saves` - Linux: `~/.config/Zelda64Recompiled/saves` +#### How do I choose a different ROM? +**You don't.** This project is **only** a port of Majora's Mask (and Ocarina of Time in the future), and it will only accept one specific ROM: the US version of the N64 release of Majora's Mask. ROMs in formats other than .z64 will be automatically converted, as long as it is the correct ROM. **It is not an emulator and it cannot run any arbitrary ROM.** + +If you want to play a modded ROM or in another language, note that support for modding and other languages will be added to the project itself in the future and will not rely on you supplying a different ROM. + ## Known Issues * The motion blur effect used by the game was capped to prevent ghosting at incredibly high framerates, which causes it to be less noticeable (this is only really noticeable above 120FPS). This may be fixed in the future by offering the option to render to an HDR framebuffer internally, which would allow it to be uncapped. * Intel GPUs on Linux may not currently work. If you have experience with Vulkan development on Linux, help here would be greatly appreciated! diff --git a/include/recomp_config.h b/include/recomp_config.h index fa5e42d..e616a80 100644 --- a/include/recomp_config.h +++ b/include/recomp_config.h @@ -8,6 +8,7 @@ namespace recomp { constexpr std::u8string_view program_id = u8"Zelda64Recompiled"; constexpr std::u8string_view mm_game_id = u8"mm.n64.us.1.0"; + constexpr std::string_view program_name = "Zelda 64: Recompiled"; void load_config(); void save_config(); diff --git a/include/rsp_vu.h b/include/rsp_vu.h index 8be1b1c..f98fdc7 100644 --- a/include/rsp_vu.h +++ b/include/rsp_vu.h @@ -20,14 +20,13 @@ // ---------------------------------------------------------------------- #include -#define ARCHITECTURE_AMD64 +#if defined(__x86_64__) || defined(_M_X64) #define ARCHITECTURE_SUPPORTS_SSE4_1 1 - -#if defined(ARCHITECTURE_AMD64) #include using v128 = __m128i; -#elif defined(ARCHITECTURE_ARM64) -#include +#elif defined(__aarch64__) || defined(_M_ARM64) +#define ARCHITECTURE_SUPPORTS_SSE4_1 1 +#include "sse2neon.h" using v128 = __m128i; #endif diff --git a/include/rt64_layer.h b/include/rt64_layer.h index 1b38ed6..a138785 100644 --- a/include/rt64_layer.h +++ b/include/rt64_layer.h @@ -9,12 +9,21 @@ namespace RT64 { } namespace ultramodern { + enum class RT64SetupResult { + Success, + DynamicLibrariesNotFound, + InvalidGraphicsAPI, + GraphicsAPINotFound, + GraphicsDeviceNotFound + }; + struct WindowHandle; struct RT64Context { public: ~RT64Context(); RT64Context(uint8_t* rdram, WindowHandle window_handle, bool developer_mode); bool valid() { return static_cast(app); } + RT64SetupResult get_setup_result() { return setup_result; } void update_config(const GraphicsConfig& old_config, const GraphicsConfig& new_config); void enable_instant_present(); @@ -25,6 +34,7 @@ namespace ultramodern { uint32_t get_display_framerate(); void load_shader_cache(std::span cache_binary); private: + RT64SetupResult setup_result; std::unique_ptr app; }; diff --git a/lib/rt64 b/lib/rt64 index ecdd609..1dd8012 160000 --- a/lib/rt64 +++ b/lib/rt64 @@ -1 +1 @@ -Subproject commit ecdd609c49fb5f10e3040335901d8860ef259f67 +Subproject commit 1dd801264dbbf7a2f4e8fc483d28664852f50082 diff --git a/lib/sse2neon b/lib/sse2neon new file mode 160000 index 0000000..42c7047 --- /dev/null +++ b/lib/sse2neon @@ -0,0 +1 @@ +Subproject commit 42c704755d3ec218ed9126a122f0a667beeb630a diff --git a/patches/autosaving.c b/patches/autosaving.c index 2b056eb..56da1a0 100644 --- a/patches/autosaving.c +++ b/patches/autosaving.c @@ -70,7 +70,18 @@ void Sram_SyncWriteToFlash(SramContext* sramCtx, s32 curPage, s32 numPages); void autosave_reset_timer(); void autosave_reset_timer_slow(); -void do_autosave(SramContext* sramCtx) { +void do_autosave(PlayState* play) { + // Transfer the scene flags into the cycle flags. + Play_SaveCycleSceneFlags(&play->state); + // Transfer the cycle flags into the save buffer. Logic copied from func_8014546C. + for (s32 i = 0; i < ARRAY_COUNT(gSaveContext.cycleSceneFlags); i++) { + gSaveContext.save.saveInfo.permanentSceneFlags[i].chest = gSaveContext.cycleSceneFlags[i].chest; + gSaveContext.save.saveInfo.permanentSceneFlags[i].switch0 = gSaveContext.cycleSceneFlags[i].switch0; + gSaveContext.save.saveInfo.permanentSceneFlags[i].switch1 = gSaveContext.cycleSceneFlags[i].switch1; + gSaveContext.save.saveInfo.permanentSceneFlags[i].clearedRoom = gSaveContext.cycleSceneFlags[i].clearedRoom; + gSaveContext.save.saveInfo.permanentSceneFlags[i].collectible = gSaveContext.cycleSceneFlags[i].collectible; + } + s32 fileNum = gSaveContext.fileNum; gSaveContext.save.isOwlSave = SAVE_TYPE_AUTOSAVE; @@ -78,6 +89,7 @@ void do_autosave(SramContext* sramCtx) { gSaveContext.save.saveInfo.checksum = 0; gSaveContext.save.saveInfo.checksum = Sram_CalcChecksum(&gSaveContext, offsetof(SaveContext, fileNum)); + SramContext* sramCtx = &play->sramCtx; // Copy the saved parts of the global save context into the sram saving buffer. Lib_MemCpy(sramCtx->saveBuf, &gSaveContext, offsetof(SaveContext, fileNum)); // Synchronously save into the owl save slot and the backup owl save slot. @@ -413,7 +425,7 @@ void autosave_post_play_update(PlayState* play) { frames_since_autosave_ready >= MIN_FRAMES_SINCE_READY && time_now - last_autosave_time > (OS_USEC_TO_CYCLES(1000 * (recomp_autosave_interval() + extra_autosave_delay_milliseconds))) ) { - do_autosave(&play->sramCtx); + do_autosave(play); show_autosave_icon(); autosave_reset_timer(); } diff --git a/shadercache/.gitkeep b/shadercache/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/shadercache/mm_shader_cache.bin b/shadercache/mm_shader_cache.bin deleted file mode 100644 index d07bdd9..0000000 Binary files a/shadercache/mm_shader_cache.bin and /dev/null differ diff --git a/src/main/main.cpp b/src/main/main.cpp index 826533e..42b382f 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -51,6 +51,11 @@ ultramodern::gfx_callbacks_t::gfx_data_t create_gfx() { SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + +#if defined(__linux__) + SDL_SetHint(SDL_HINT_VIDEODRIVER, "x11"); +#endif + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER) > 0) { exit_error("Failed to initialize SDL2: %s\n", SDL_GetError()); } @@ -133,6 +138,10 @@ ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t #elif defined(__ANDROID__) static_assert(false && "Unimplemented"); #elif defined(__linux__) + if (wmInfo.subsystem != SDL_SYSWM_X11) { + exit_error("Unsupported SDL2 video driver \"%s\". Only X11 is supported on Linux.\n", SDL_GetCurrentVideoDriver()); + } + return ultramodern::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window }; #else static_assert(false && "Unimplemented"); diff --git a/src/recomp/recomp.cpp b/src/recomp/recomp.cpp index 13661e3..8135514 100644 --- a/src/recomp/recomp.cpp +++ b/src/recomp/recomp.cpp @@ -17,7 +17,9 @@ #include "xxHash/xxh3.h" #include "../ultramodern/ultramodern.hpp" #include "../../RecompiledPatches/patches_bin.h" +#ifdef HAS_MM_SHADER_CACHE #include "mm_shader_cache.h" +#endif #ifdef _MSC_VER inline uint32_t byteswap(uint32_t val) { @@ -411,7 +413,11 @@ void recomp::start(ultramodern::WindowHandle window_handle, const ultramodern::a if (!recomp::load_stored_rom(recomp::Game::MM)) { recomp::message_box("Error opening stored ROM! Please restart this program."); } + + #ifdef HAS_MM_SHADER_CACHE ultramodern::load_shader_cache({mm_shader_cache_bytes, sizeof(mm_shader_cache_bytes)}); + #endif + init(rdram, &context); try { recomp_entrypoint(rdram, &context); diff --git a/src/ui/ui_launcher.cpp b/src/ui/ui_launcher.cpp index 13cbc47..5c25218 100644 --- a/src/ui/ui_launcher.cpp +++ b/src/ui/ui_launcher.cpp @@ -6,7 +6,7 @@ #include "nfd.h" #include -std::string version_number = "v1.0.0"; +std::string version_number = "v1.0.1"; Rml::DataModelHandle model_handle; bool mm_rom_valid = false; diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index f62c992..c885184 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -8,6 +8,7 @@ #include "recomp_ui.h" #include "recomp_input.h" #include "recomp_game.h" +#include "recomp_config.h" #include "ui_rml_hacks.hpp" #include "concurrentqueue.h" @@ -1464,5 +1465,5 @@ recomp::Menu recomp::get_current_menu() { } void recomp::message_box(const char* msg) { - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", msg, nullptr); + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, recomp::program_name.data(), msg, nullptr); } diff --git a/ultramodern/events.cpp b/ultramodern/events.cpp index 1597f76..957d704 100644 --- a/ultramodern/events.cpp +++ b/ultramodern/events.cpp @@ -16,6 +16,7 @@ #include "config.hpp" #include "rt64_layer.h" #include "recomp.h" +#include "recomp_game.h" #include "recomp_ui.h" #include "recomp_input.h" #include "rsp.h" @@ -301,6 +302,8 @@ void ultramodern::load_shader_cache(std::span cache_data) { events_context.action_queue.enqueue(LoadShaderCacheAction{cache_data}); } +std::atomic rt64_setup_result = ultramodern::RT64SetupResult::Success; + void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_ready, ultramodern::WindowHandle window_handle) { bool enabled_instant_present = false; using namespace std::chrono_literals; @@ -313,7 +316,11 @@ void gfx_thread_func(uint8_t* rdram, moodycamel::LightweightSemaphore* thread_re ultramodern::RT64Context rt64{rdram, window_handle, cur_config.load().developer_mode}; if (!rt64.valid()) { - throw std::runtime_error("Failed to initialize RT64!"); + // TODO move recomp code out of ultramodern. + rt64_setup_result.store(rt64.get_setup_result()); + // Notify the caller thread that this thread is ready. + thread_ready->signal(); + return; } // TODO move recomp code out of ultramodern. @@ -537,6 +544,27 @@ void ultramodern::send_si_message(RDRAM_ARG1) { osSendMesg(PASS_RDRAM events_context.si.mq, events_context.si.msg, OS_MESG_NOBLOCK); } +std::string get_graphics_api_name(ultramodern::GraphicsApi api) { + if (api == ultramodern::GraphicsApi::Auto) { +#if defined(_WIN32) + api = ultramodern::GraphicsApi::D3D12; +#elif defined(__gnu_linux__) + api = ultramodern::GraphicsApi::Vulkan; +#else + static_assert(false && "Unimplemented") +#endif + } + + switch (api) { + case ultramodern::GraphicsApi::D3D12: + return "D3D12"; + case ultramodern::GraphicsApi::Vulkan: + return "Vulkan"; + default: + return "[Unknown graphics API]"; + } +} + void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) { moodycamel::LightweightSemaphore gfx_thread_ready; moodycamel::LightweightSemaphore task_thread_ready; @@ -549,6 +577,30 @@ void ultramodern::init_events(RDRAM_ARG ultramodern::WindowHandle window_handle) gfx_thread_ready.wait(); task_thread_ready.wait(); + ultramodern::RT64SetupResult setup_result = rt64_setup_result.load(); + if (rt64_setup_result != ultramodern::RT64SetupResult::Success) { + auto show_rt64_error = [](const std::string& msg) { + // TODO move recomp code out of ultramodern (message boxes). + recomp::message_box(("An error has been encountered on startup: " + msg).c_str()); + }; + const std::string driver_os_suffix = "\nPlease make sure your GPU drivers and your OS are up to date."; + switch (rt64_setup_result) { + case ultramodern::RT64SetupResult::DynamicLibrariesNotFound: + show_rt64_error("Failed to load dynamic libraries. Make sure the DLLs are next to the recomp executable."); + break; + case ultramodern::RT64SetupResult::InvalidGraphicsAPI: + show_rt64_error(get_graphics_api_name(cur_config.load().api_option) + " is not supported on this platform. Please select a different graphics API."); + break; + case ultramodern::RT64SetupResult::GraphicsAPINotFound: + show_rt64_error("Unable to initialize " + get_graphics_api_name(cur_config.load().api_option) + "." + driver_os_suffix); + break; + case ultramodern::RT64SetupResult::GraphicsDeviceNotFound: + show_rt64_error("Unable to find compatible graphics device." + driver_os_suffix); + break; + } + throw std::runtime_error("Failed to initialize RT64"); + } + events_context.vi.thread = std::thread{ vi_thread_func }; } diff --git a/ultramodern/rt64_layer.cpp b/ultramodern/rt64_layer.cpp index db7a1bd..611e1eb 100644 --- a/ultramodern/rt64_layer.cpp +++ b/ultramodern/rt64_layer.cpp @@ -97,6 +97,21 @@ void set_application_user_config(RT64::Application* application, const ultramode application->userConfig.refreshRateTarget = config.rr_manual_value; } +ultramodern::RT64SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { + switch (rt64_result) { + case RT64::Application::SetupResult::Success: + return ultramodern::RT64SetupResult::Success; + case RT64::Application::SetupResult::DynamicLibrariesNotFound: + return ultramodern::RT64SetupResult::DynamicLibrariesNotFound; + case RT64::Application::SetupResult::InvalidGraphicsAPI: + return ultramodern::RT64SetupResult::InvalidGraphicsAPI; + case RT64::Application::SetupResult::GraphicsAPINotFound: + return ultramodern::RT64SetupResult::GraphicsAPINotFound; + case RT64::Application::SetupResult::GraphicsDeviceNotFound: + return ultramodern::RT64SetupResult::GraphicsDeviceNotFound; + } +} + ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle window_handle, bool debug) { static unsigned char dummy_rom_header[0x40]; set_rt64_hooks(); @@ -179,7 +194,8 @@ ultramodern::RT64Context::RT64Context(uint8_t* rdram, ultramodern::WindowHandle #ifdef _WIN32 thread_id = window_handle.thread_id; #endif - if (app->setup(thread_id) != RT64::Application::SetupResult::Success) { + setup_result = map_setup_result(app->setup(thread_id)); + if (setup_result != ultramodern::RT64SetupResult::Success) { app = nullptr; return; }