diff --git a/.github/linux/appimage.sh b/.github/linux/appimage.sh index 6905528..807b1f7 100755 --- a/.github/linux/appimage.sh +++ b/.github/linux/appimage.sh @@ -1,10 +1,10 @@ ARCH=$(uname -m) LINUX_DEPLOY_ARCH=$(uname -m) -if [ "$ARCH" == "x86_64" ]; then +if [ "$ARCH" = "x86_64" ]; then ARCH="x86_64" LINUX_DEPLOY_ARCH="x86_64" -elif [ "$ARCH" == "aarch64" ]; then +elif [ "$ARCH" = "aarch64" ]; then ARCH="arm_aarch64" LINUX_DEPLOY_ARCH="aarch64" else @@ -14,11 +14,13 @@ fi curl -sSfLO "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$LINUX_DEPLOY_ARCH.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 gamecontrollerdb.txt AppDir/usr/bin/ cp icons/512.png AppDir/Zelda64Recompiled.png cp .github/linux/Zelda64Recompiled.desktop AppDir/ @@ -34,4 +36,10 @@ echo 'else' >> AppDir/AppRun echo ' cd "$this_dir"/usr/bin/' >> AppDir/AppRun echo ' ./Zelda64Recompiled' >> AppDir/AppRun echo 'fi' >> AppDir/AppRun + +# Remove conflicting libraries +rm -rf AppDir/usr/lib/libgmodule* +rm -rf AppDir/usr/lib/gio/modules/*.so +rm -rf AppDir/usr/lib/libwayland* + ./deploy/usr/bin/linuxdeploy-plugin-appimage --appdir=AppDir diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 8eacd1a..c8e1d21 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -1,6 +1,19 @@ name: validate on: workflow_call: + inputs: + SDL2_VERSION: + type: string + required: false + default: '2.28.5' + N64RECOMP_COMMIT: + type: string + required: false + default: '2a2df89349ff25a3afb3a09617deb3a166efe2f3' + DXC_CHECKSUM: + type: string + required: false + default: '4e6f4e52989aca69739880b40b9f988357f15d10ca03284377b81f1502463ff5' secrets: ZRE_REPO_WITH_PAT: required: true @@ -9,13 +22,90 @@ concurrency: cancel-in-progress: true jobs: build-linux: - runs-on: ${{ matrix.arch == 'x64' && matrix.os || format('blaze/{0}', matrix.os) }} + runs-on: ${{ matrix.os }} + container: + image: dcvz/n64recomp:0.0.1-ubuntu-18.04 strategy: matrix: type: [ Debug, Release ] os: [ ubuntu-22.04 ] - arch: [ x64, arm64 ] - name: ${{ matrix.os }} (${{ matrix.arch }}, ${{ matrix.type }}) + name: ubuntu-18.04 (x64, ${{ matrix.type }}) + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.sha || github.ref }} + submodules: recursive + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2.11 + with: + key: ${{ matrix.os }}-z64re-ccache-${{ matrix.type }}-x64-${{ inputs.N64RECOMP_COMMIT }} + - name: Prepare Build + run: |- + git clone ${{ secrets.ZRE_REPO_WITH_PAT }} + unzip zre/files.zip > /dev/null 2>&1 + - name: Build N64Recomp & RSPRecomp + run: | + git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource + cd N64RecompSource + git checkout ${{ inputs.N64RECOMP_COMMIT }} + git submodule update --init --recursive + + # enable ccache + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + + # Build N64Recomp & RSPRecomp + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build + cmake --build cmake-build --config Release --target N64Recomp -j $(nproc) + cmake --build cmake-build --config Release --target RSPRecomp -j $(nproc) + + # Copy N64Recomp & RSPRecomp to root directory + cp cmake-build/N64Recomp .. + cp cmake-build/RSPRecomp .. + - name: Run N64Recomp & RSPRecomp + run: | + ./N64Recomp us.rev1.toml + ./RSPRecomp aspMain.us.rev1.toml + ./RSPRecomp njpgdspMain.us.rev1.toml + - name: Hotpatch DXC into RT64's contrib + run: | + # check if dxc was updated before we replace it, to detect changes + echo ${{ inputs.DXC_CHECKSUM }} ./lib/rt64/src/contrib/dxc/bin/x64/dxc | sha256sum --status -c - + + cp -v /usr/local/lib/libdxcompiler.so ./lib/rt64/src/contrib/dxc/lib/x64/libdxcompiler.so + cp -v /usr/local/bin/dxc ./lib/rt64/src/contrib/dxc/bin/x64/dxc + - name: Build ZeldaRecomp + run: |- + # enable ccache + export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" + + cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang++-17 -DCMAKE_C_COMPILER=clang-17 -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DPATCHES_C_COMPILER=clang-17 -DPATCHES_LD=ld.lld-17 -DPATCHES_OBJCOPY=llvm-objcopy-17 + cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j $(nproc) + - name: Prepare Archive + run: | + mv cmake-build/Zelda64Recompiled Zelda64Recompiled + rm -rf assets/scss + tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ gamecontrollerdb.txt + - name: Archive Zelda64Recomp + uses: actions/upload-artifact@v3 + with: + name: Zelda64Recompiled-${{ runner.os }}-X64-${{ matrix.type }} + path: Zelda64Recompiled.tar.gz + - name: Build AppImage + run: |- + ./.github/linux/appimage.sh + - name: Zelda64Recomp AppImage + uses: actions/upload-artifact@v3 + with: + name: Zelda64Recompiled-AppImage-X64-${{ matrix.type }} + path: Zelda64Recompiled-*.AppImage + build-linux-arm64: + runs-on: ${{ format('blaze/{0}', matrix.os) }} + strategy: + matrix: + type: [ Debug, Release ] + os: [ ubuntu-22.04 ] + name: ${{ matrix.os }} (arm64, ${{ matrix.type }}) steps: - name: Checkout uses: actions/checkout@v4 @@ -25,7 +115,7 @@ jobs: - name: ccache uses: hendrikmuhs/ccache-action@v1.2 with: - key: ${{ matrix.os }}-z64re-ccache-${{ matrix.type }}-${{ matrix.arch }} + key: ${{ matrix.os }}-z64re-ccache-${{ matrix.type }}-arm64-${{ inputs.N64RECOMP_COMMIT }} - name: Install Linux Dependencies run: | sudo apt-get update @@ -37,13 +127,13 @@ jobs: # Enable ccache export PATH="/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" - wget https://github.com/libsdl-org/SDL/releases/download/release-2.26.1/SDL2-2.26.1.tar.gz - tar -xzf SDL2-2.26.1.tar.gz - cd SDL2-2.26.1 + wget https://github.com/libsdl-org/SDL/releases/download/release-${{ inputs.SDL2_VERSION }}/SDL2-${{ inputs.SDL2_VERSION }}.tar.gz + tar -xzf SDL2-${{ inputs.SDL2_VERSION }}.tar.gz + cd SDL2-${{ inputs.SDL2_VERSION }} ./configure make -j 10 sudo make install - sudo cp -av /usr/local/lib/libSDL* /lib/x86_64-linux-gnu/ + sudo cp -av /usr/local/lib/libSDL* /lib/aarch64-linux-gnu/ echo ::endgroup:: - name: Prepare Build run: |- @@ -54,7 +144,7 @@ jobs: run: | git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource cd N64RecompSource - git checkout 2a2df89349ff25a3afb3a09617deb3a166efe2f3 + git checkout ${{ inputs.N64RECOMP_COMMIT }} git submodule update --init --recursive # enable ccache @@ -84,18 +174,18 @@ jobs: run: | mv cmake-build/Zelda64Recompiled Zelda64Recompiled rm -rf assets/scss - tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ + tar -czf Zelda64Recompiled.tar.gz Zelda64Recompiled assets/ gamecontrollerdb.txt - name: Archive Zelda64Recomp uses: actions/upload-artifact@v4 with: - name: Zelda64Recompiled-${{ runner.os }}-${{ runner.arch }}-${{ matrix.type }} + name: Zelda64Recompiled-${{ runner.os }}-ARM64-${{ matrix.type }} path: Zelda64Recompiled.tar.gz - name: Prepare AppImage run: ./.github/linux/appimage.sh - name: Zelda64Recomp AppImage uses: actions/upload-artifact@v4 with: - name: Zelda64Recompiled-AppImage-${{ runner.arch }}-${{ matrix.type }} + name: Zelda64Recompiled-AppImage-ARM64-${{ matrix.type }} path: Zelda64Recompiled-*.AppImage build-windows: runs-on: windows-latest @@ -128,7 +218,7 @@ jobs: run: | git clone https://github.com/Mr-Wiseguy/N64Recomp.git --recurse-submodules N64RecompSource cd N64RecompSource - git checkout 2a2df89349ff25a3afb3a09617deb3a166efe2f3 + git checkout ${{ inputs.N64RECOMP_COMMIT }} git submodule update --init --recursive # enable ccache @@ -161,6 +251,7 @@ jobs: env: CXXFLAGS: --target=amd64-pc-windows-msvc -fdiagnostics-absolute-paths CFLAGS: --target=amd64-pc-windows-msvc -fdiagnostics-absolute-paths + SDL2_VERSION: ${{ inputs.SDL2_VERSION }} - name: Prepare Archive run: | Move-Item -Path "cmake-build/Zelda64Recompiled.exe" -Destination "Zelda64Recompiled.exe" @@ -178,3 +269,4 @@ jobs: dxil.dll SDL2.dll assets/ + gamecontrollerdb.txt diff --git a/.gitignore b/.gitignore index a718c25..6ff3307 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,6 @@ node_modules/ # Recompiler Linux binary N64Recomp .DS_Store + +# Controller mappings file +gamecontrollerdb.txt diff --git a/.gitmodules b/.gitmodules index 383eb4e..191d5a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,4 +18,4 @@ url = https://github.com/DLTcollab/sse2neon.git [submodule "lib/N64ModernRuntime"] path = lib/N64ModernRuntime - url = git@github.com:N64Recomp/N64ModernRuntime.git + url = https://github.com/N64Recomp/N64ModernRuntime.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 1f470e9..159a3e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,8 +80,20 @@ target_sources(PatchesLib PRIVATE set_source_files_properties(${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c PROPERTIES COMPILE_FLAGS -fno-strict-aliasing) # Build patches elf +if(NOT DEFINED PATCHES_C_COMPILER) + set(PATCHES_C_COMPILER clang) +endif() + +if(NOT DEFINED PATCHES_LD) + set(PATCHES_LD ld.lld) +endif() + +if(NOT DEFINED PATCHES_OBJCOPY) + set(PATCHES_OBJCOPY llvm-objcopy) +endif() + add_custom_target(PatchesBin - COMMAND make + COMMAND ${CMAKE_COMMAND} -E env CC=${PATCHES_C_COMPILER} LD=${PATCHES_LD} OBJCOPY=${PATCHES_OBJCOPY} make WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/patches BYPRODUCTS ${CMAKE_SOURCE_DIR}/patches/patches.bin ) @@ -103,8 +115,19 @@ add_custom_command(OUTPUT DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.bin ) +# Download controller db file for controller support via SDL2 +set(GAMECONTROLLERDB_COMMIT "b1e4090b3d4266e55feb0793efa35792e05faf66") +set(GAMECONTROLLERDB_URL "https://raw.githubusercontent.com/gabomdq/SDL_GameControllerDB/${GAMECONTROLLERDB_COMMIT}/gamecontrollerdb.txt") + +file(DOWNLOAD ${GAMECONTROLLERDB_URL} ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt + TLS_VERIFY ON) + +add_custom_target(DownloadGameControllerDB + DEPENDS ${CMAKE_SOURCE_DIR}/gamecontrollerdb.txt) + # Main executable add_executable(Zelda64Recompiled) +add_dependencies(Zelda64Recompiled DownloadGameControllerDB) # Generate mm_shader_cache.c from the MM shader cache if it exists if (EXISTS ${CMAKE_SOURCE_DIR}/shadercache/mm_shader_cache.bin) @@ -120,6 +143,7 @@ set (SOURCES ${CMAKE_SOURCE_DIR}/src/main/main.cpp ${CMAKE_SOURCE_DIR}/src/main/register_overlays.cpp ${CMAKE_SOURCE_DIR}/src/main/register_patches.cpp + ${CMAKE_SOURCE_DIR}/src/main/rt64_render_context.cpp ${CMAKE_SOURCE_DIR}/src/game/input.cpp ${CMAKE_SOURCE_DIR}/src/game/controls.cpp @@ -179,11 +203,17 @@ endif() if (WIN32) include(FetchContent) + + if (DEFINED ENV{SDL2_VERSION}) + set(SDL2_VERSION $ENV{SDL2_VERSION}) + else() + set(SDL2_VERSION "2.30.3") + endif() + # Fetch SDL2 on windows FetchContent_Declare( sdl2 - URL https://github.com/libsdl-org/SDL/releases/download/release-2.28.5/SDL2-devel-2.28.5-VC.zip - URL_HASH MD5=d8173db078e54040c666f411c5a6afff + URL https://github.com/libsdl-org/SDL/releases/download/release-${SDL2_VERSION}/SDL2-devel-${SDL2_VERSION}-VC.zip ) FetchContent_MakeAvailable(sdl2) target_include_directories(Zelda64Recompiled PRIVATE @@ -281,15 +311,12 @@ if (${WIN32}) set (DXC "${PROJECT_SOURCE_DIR}/lib/rt64/src/contrib/dxc/bin/x64/dxc.exe") add_compile_definitions(NOMINMAX) else() - 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() + if (APPLE) + # Apple's binary is universal, so it'll work on both x86_64 and arm64 + 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() - 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") + if(CMAKE_SIZEOF_VOID_P EQUAL 8 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|amd64|AMD64") + 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") 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() @@ -297,7 +324,7 @@ else() endif() build_vertex_shader(Zelda64Recompiled "shaders/InterfaceVS.hlsl" "shaders/InterfaceVS.hlsl") -build_pixel_shader (Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl") +build_pixel_shader(Zelda64Recompiled "shaders/InterfacePS.hlsl" "shaders/InterfacePS.hlsl") target_sources(Zelda64Recompiled PRIVATE ${SOURCES}) diff --git a/include/recomp_input.h b/include/recomp_input.h index 785294f..83b4d5a 100644 --- a/include/recomp_input.h +++ b/include/recomp_input.h @@ -9,6 +9,8 @@ #include #include +#include "ultramodern/input.hpp" + #include "json/json.hpp" namespace recomp { @@ -150,10 +152,12 @@ namespace recomp { InputField& get_input_binding(GameInput input, size_t binding_index, InputDevice device); void set_input_binding(GameInput input, size_t binding_index, InputDevice device, InputField value); - void get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out); - void set_rumble(bool); + bool get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out); + void set_rumble(int controller_num, bool); void update_rumble(); void handle_events(); + + ultramodern::input::connected_device_info_t get_connected_device_info(int controller_num); // Rumble strength ranges from 0 to 100. int get_rumble_strength(); diff --git a/include/recomp_ui.h b/include/recomp_ui.h index a8d42a4..b2eaba4 100644 --- a/include/recomp_ui.h +++ b/include/recomp_ui.h @@ -119,7 +119,9 @@ namespace recompui { void set_cont_active(bool active); void activate_mouse(); - void message_box(const char* msg); + void message_box(const char* msg); + + void set_render_hooks(); } #endif diff --git a/include/zelda_render.h b/include/zelda_render.h new file mode 100644 index 0000000..684672b --- /dev/null +++ b/include/zelda_render.h @@ -0,0 +1,42 @@ +#ifndef __ZELDA_RENDER_H__ +#define __ZELDA_RENDER_H__ + +#include "common/rt64_user_configuration.h" +#include "ultramodern/renderer_context.hpp" + +namespace RT64 { + struct Application; +} + +namespace zelda64 { + namespace renderer { + class RT64Context : public ultramodern::renderer::RendererContext { + public: + ~RT64Context() override; + RT64Context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); + + bool valid() override { return static_cast(app); } + + bool update_config(const ultramodern::renderer::GraphicsConfig &old_config, const ultramodern::renderer::GraphicsConfig &new_config) override; + + void enable_instant_present() override; + void send_dl(const OSTask *task) override; + void update_screen(uint32_t vi_origin) override; + void shutdown() override; + uint32_t get_display_framerate() const override; + float get_resolution_scale() const override; + void load_shader_cache(std::span cache_binary) override; + + protected: + std::unique_ptr app; + }; + + std::unique_ptr create_render_context(uint8_t *rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode); + + RT64::UserConfiguration::Antialiasing RT64MaxMSAA(); + bool RT64SamplePositionsSupported(); + bool RT64HighPrecisionFBEnabled(); + } +} + +#endif diff --git a/lib/N64ModernRuntime b/lib/N64ModernRuntime index ec7e81b..0c1811c 160000 --- a/lib/N64ModernRuntime +++ b/lib/N64ModernRuntime @@ -1 +1 @@ -Subproject commit ec7e81b45d9a622cb3e45865ce5d7d3536b26534 +Subproject commit 0c1811ca6f8291c6608f1d6626a73e863902ece9 diff --git a/patches/Makefile b/patches/Makefile index 0416f19..21363fe 100644 --- a/patches/Makefile +++ b/patches/Makefile @@ -1,8 +1,8 @@ TARGET = patches.elf -CC := clang -LD := ld.lld -OBJCOPY := llvm-objcopy +CC ?= clang +LD ?= ld.lld +OBJCOPY ?= llvm-objcopy CFLAGS := -target mips -mips2 -mabi=32 -O2 -G0 -mno-abicalls -mno-odd-spreg -mno-check-zero-division \ -fomit-frame-pointer -ffast-math -fno-unsafe-math-optimizations -fno-builtin-memset \ diff --git a/src/game/config.cpp b/src/game/config.cpp index 9ee73ee..ece24b6 100644 --- a/src/game/config.cpp +++ b/src/game/config.cpp @@ -1,6 +1,7 @@ #include "zelda_config.h" #include "recomp_input.h" #include "zelda_sound.h" +#include "zelda_render.h" #include "ultramodern/config.hpp" #include "librecomp/files.hpp" #include @@ -20,21 +21,21 @@ constexpr std::u8string_view controls_filename = u8"controls.json"; constexpr std::u8string_view sound_filename = u8"sound.json"; constexpr std::u8string_view program_id = u8"Zelda64Recompiled"; -constexpr auto res_default = ultramodern::Resolution::Auto; -constexpr auto hr_default = ultramodern::HUDRatioMode::Clamp16x9; -constexpr auto api_default = ultramodern::GraphicsApi::Auto; -constexpr auto ar_default = RT64::UserConfiguration::AspectRatio::Expand; -constexpr auto msaa_default = RT64::UserConfiguration::Antialiasing::MSAA2X; -constexpr auto rr_default = RT64::UserConfiguration::RefreshRate::Display; -constexpr auto hpfb_default = ultramodern::HighPrecisionFramebuffer::Auto; +constexpr auto res_default = ultramodern::renderer::Resolution::Auto; +constexpr auto hr_default = ultramodern::renderer::HUDRatioMode::Clamp16x9; +constexpr auto api_default = ultramodern::renderer::GraphicsApi::Auto; +constexpr auto ar_default = ultramodern::renderer::AspectRatio::Expand; +constexpr auto msaa_default = ultramodern::renderer::Antialiasing::MSAA2X; +constexpr auto rr_default = ultramodern::renderer::RefreshRate::Display; +constexpr auto hpfb_default = ultramodern::renderer::HighPrecisionFramebuffer::Auto; constexpr int ds_default = 1; constexpr int rr_manual_default = 60; constexpr bool developer_mode_default = false; static bool is_steam_deck = false; -ultramodern::WindowMode wm_default() { - return is_steam_deck ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed; +ultramodern::renderer::WindowMode wm_default() { + return is_steam_deck ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed; } #ifdef __gnu_linux__ @@ -86,7 +87,7 @@ void call_if_key_exists(void (*func)(T), const json& j, const std::string& key) } namespace ultramodern { - void to_json(json& j, const GraphicsConfig& config) { + void to_json(json& j, const renderer::GraphicsConfig& config) { j = json{ {"res_option", config.res_option}, {"wm_option", config.wm_option}, @@ -102,7 +103,7 @@ namespace ultramodern { }; } - void from_json(const json& j, GraphicsConfig& config) { + void from_json(const json& j, renderer::GraphicsConfig& config) { config.res_option = from_or_default(j, "res_option", res_default); config.wm_option = from_or_default(j, "wm_option", wm_default()); config.hr_option = from_or_default(j, "hr_option", hr_default); @@ -302,7 +303,7 @@ void zelda64::reset_kb_input_bindings() { } void reset_graphics_options() { - ultramodern::GraphicsConfig new_config{}; + ultramodern::renderer::GraphicsConfig new_config{}; new_config.res_option = res_default; new_config.wm_option = wm_default(); new_config.hr_option = hr_default; @@ -313,12 +314,12 @@ void reset_graphics_options() { new_config.hpfb_option = hpfb_default; new_config.rr_manual_value = rr_manual_default; new_config.developer_mode = developer_mode_default; - ultramodern::set_graphics_config(new_config); + ultramodern::renderer::set_graphics_config(new_config); } bool save_graphics_config(const std::filesystem::path& path) { nlohmann::json config_json{}; - ultramodern::to_json(config_json, ultramodern::get_graphics_config()); + ultramodern::to_json(config_json, ultramodern::renderer::get_graphics_config()); return save_json_with_backups(path, config_json); } @@ -328,9 +329,9 @@ bool load_graphics_config(const std::filesystem::path& path) { return false; } - ultramodern::GraphicsConfig new_config{}; + ultramodern::renderer::GraphicsConfig new_config{}; ultramodern::from_json(config_json, new_config); - ultramodern::set_graphics_config(new_config); + ultramodern::renderer::set_graphics_config(new_config); return true; } diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 0a6f9f3..0bb3857 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -75,10 +75,14 @@ void recomp::set_input_binding(recomp::GameInput input, size_t binding_index, re } } -void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) { +bool recomp::get_n64_input(int controller_num, uint16_t* buttons_out, float* x_out, float* y_out) { uint16_t cur_buttons = 0; float cur_x = 0.0f; float cur_y = 0.0f; + + if (controller_num != 0) { + return false; + } if (!recomp::game_input_disabled()) { for (size_t i = 0; i < n64_button_values.size(); i++) { @@ -107,4 +111,6 @@ void recomp::get_n64_input(uint16_t* buttons_out, float* x_out, float* y_out) { *buttons_out = cur_buttons; *x_out = std::clamp(cur_x, -1.0f, 1.0f); *y_out = std::clamp(cur_y, -1.0f, 1.0f); + + return true; } diff --git a/src/game/input.cpp b/src/game/input.cpp index 677fb31..b625782 100644 --- a/src/game/input.cpp +++ b/src/game/input.cpp @@ -446,8 +446,25 @@ void recomp::poll_inputs() { #endif } -void recomp::set_rumble(bool on) { - InputState.rumble_active = on; +void recomp::set_rumble(int controller_num, bool on) { + if (controller_num == 0) { + InputState.rumble_active = on; + } +} + +ultramodern::input::connected_device_info_t recomp::get_connected_device_info(int controller_num) { + switch (controller_num) { + case 0: + return ultramodern::input::connected_device_info_t { + .connected_device = ultramodern::input::Device::Controller, + .connected_pak = ultramodern::input::Pak::RumblePak, + }; + } + + return ultramodern::input::connected_device_info_t { + .connected_device = ultramodern::input::Device::None, + .connected_pak = ultramodern::input::Pak::None, + }; } static float smoothstep(float from, float to, float amount) { diff --git a/src/game/recomp_api.cpp b/src/game/recomp_api.cpp index 042788d..28b4779 100644 --- a/src/game/recomp_api.cpp +++ b/src/game/recomp_api.cpp @@ -5,6 +5,7 @@ #include "zelda_config.h" #include "recomp_input.h" #include "recomp_ui.h" +#include "zelda_render.h" #include "zelda_sound.h" #include "librecomp/helpers.hpp" #include "../patches/input.h" @@ -12,7 +13,6 @@ #include "../patches/sound.h" #include "ultramodern/ultramodern.hpp" #include "ultramodern/config.hpp" -#include "ultramodern/rt64_layer.hpp" extern "C" void recomp_update_inputs(uint8_t* rdram, recomp_context* ctx) { recomp::poll_inputs(); @@ -59,17 +59,17 @@ extern "C" void recomp_get_target_framerate(uint8_t* rdram, recomp_context* ctx) } extern "C" void recomp_get_aspect_ratio(uint8_t* rdram, recomp_context* ctx) { - ultramodern::GraphicsConfig graphics_config = ultramodern::get_graphics_config(); + ultramodern::renderer::GraphicsConfig graphics_config = ultramodern::renderer::get_graphics_config(); float original = _arg<0, float>(rdram, ctx); int width, height; recompui::get_window_size(width, height); switch (graphics_config.ar_option) { - case RT64::UserConfiguration::AspectRatio::Original: + case ultramodern::renderer::AspectRatio::Original: default: _return(ctx, original); return; - case RT64::UserConfiguration::AspectRatio::Expand: + case ultramodern::renderer::AspectRatio::Expand: _return(ctx, std::max(static_cast(width) / height, original)); return; } @@ -104,7 +104,7 @@ extern "C" void recomp_load_overlays(uint8_t * rdram, recomp_context * ctx) { } extern "C" void recomp_high_precision_fb_enabled(uint8_t * rdram, recomp_context * ctx) { - _return(ctx, static_cast(ultramodern::RT64HighPrecisionFBEnabled())); + _return(ctx, static_cast(zelda64::renderer::RT64HighPrecisionFBEnabled())); } extern "C" void recomp_get_resolution_scale(uint8_t* rdram, recomp_context* ctx) { diff --git a/src/main/main.cpp b/src/main/main.cpp index b28f19c..d3961a2 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -24,6 +24,7 @@ #include "recomp_input.h" #include "zelda_config.h" #include "zelda_sound.h" +#include "zelda_render.h" #include "ovl_patches.hpp" #include "librecomp/game.hpp" @@ -40,9 +41,6 @@ #define STB_IMAGE_IMPLEMENTATION #include "../../lib/rt64/src/contrib/stb/stb_image.h" -extern "C" void init(); -/*extern "C"*/ void start(ultramodern::WindowHandle window_handle, const ultramodern::audio_callbacks_t* audio_callbacks, const ultramodern::input_callbacks_t* input_callbacks); - template void exit_error(const char* str, Ts ...args) { // TODO pop up an error @@ -121,11 +119,11 @@ bool SetImageAsIcon(const char* filename, SDL_Window* window) SDL_Window* window; -ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) { +ultramodern::renderer::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t) { window = SDL_CreateWindow("Zelda 64: Recompiled", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1600, 960, SDL_WINDOW_RESIZABLE ); #if defined(__linux__) SetImageAsIcon("icons/512.png",window); - if (ultramodern::get_graphics_config().wm_option == ultramodern::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux + if (ultramodern::renderer::get_graphics_config().wm_option == ultramodern::renderer::WindowMode::Fullscreen) { // TODO: Remove once RT64 gets native fullscreen support on Linux SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(window,0); @@ -141,7 +139,7 @@ ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t SDL_GetWindowWMInfo(window, &wmInfo); #if defined(_WIN32) - return ultramodern::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() }; + return ultramodern::renderer::WindowHandle{ wmInfo.info.win.window, GetCurrentThreadId() }; #elif defined(__ANDROID__) static_assert(false && "Unimplemented"); #elif defined(__linux__) @@ -149,7 +147,7 @@ ultramodern::WindowHandle create_window(ultramodern::gfx_callbacks_t::gfx_data_t 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 }; + return ultramodern::renderer::WindowHandle{ wmInfo.info.x11.display, wmInfo.info.x11.window }; #else static_assert(false && "Unimplemented"); #endif @@ -375,6 +373,11 @@ int main(int argc, char** argv) { SDL_InitSubSystem(SDL_INIT_AUDIO); reset_audio(48000); + // Source controller mappings file + if (SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt") < 0) { + fprintf(stderr, "Failed to load controller mappings: %s\n", SDL_GetError()); + } + // Register supported games and patches for (const auto& game : supported_games) { recomp::register_game(game); @@ -390,6 +393,10 @@ int main(int argc, char** argv) { .get_rsp_microcode = get_rsp_microcode, }; + ultramodern::renderer::callbacks_t renderer_callbacks{ + .create_render_context = zelda64::renderer::create_render_context, + }; + ultramodern::gfx_callbacks_t gfx_callbacks{ .create_gfx = create_gfx, .create_window = create_window, @@ -402,10 +409,11 @@ int main(int argc, char** argv) { .set_frequency = set_frequency, }; - ultramodern::input_callbacks_t input_callbacks{ + ultramodern::input::callbacks_t input_callbacks{ .poll_input = recomp::poll_inputs, .get_input = recomp::get_n64_input, .set_rumble = recomp::set_rumble, + .get_connected_device_info = recomp::get_connected_device_info, }; ultramodern::events::callbacks_t thread_callbacks{ @@ -417,7 +425,7 @@ int main(int argc, char** argv) { .message_box = recompui::message_box, }; - recomp::start({}, rsp_callbacks, audio_callbacks, input_callbacks, gfx_callbacks, thread_callbacks, error_handling_callbacks); + recomp::start({}, rsp_callbacks, renderer_callbacks, audio_callbacks, input_callbacks, gfx_callbacks, thread_callbacks, error_handling_callbacks); NFD_Quit(); diff --git a/src/main/register_overlays.cpp b/src/main/register_overlays.cpp index 034f64f..04ccbc0 100644 --- a/src/main/register_overlays.cpp +++ b/src/main/register_overlays.cpp @@ -4,16 +4,16 @@ #include "librecomp/overlays.hpp" void zelda64::register_overlays() { - recomp::overlay_section_table_data_t sections { + recomp::overlays::overlay_section_table_data_t sections { .code_sections = section_table, .num_code_sections = ARRLEN(section_table), .total_num_sections = num_sections, }; - recomp::overlays_by_index_t overlays { + recomp::overlays::overlays_by_index_t overlays { .table = overlay_sections_by_index, .len = ARRLEN(overlay_sections_by_index), }; - recomp::register_overlays(sections, overlays); + recomp::overlays::register_overlays(sections, overlays); } diff --git a/src/main/register_patches.cpp b/src/main/register_patches.cpp index 5076868..71b9972 100644 --- a/src/main/register_patches.cpp +++ b/src/main/register_patches.cpp @@ -6,7 +6,5 @@ #include "librecomp/game.hpp" void zelda64::register_patches() { - // TODO: This is a bit awful, maybe provide only one functions that does both in librecomp? - recomp::register_patch(mm_patches_bin, sizeof(mm_patches_bin)); - recomp::register_patch_section(section_table); + recomp::overlays::register_patches(mm_patches_bin, sizeof(mm_patches_bin), section_table); } diff --git a/src/main/rt64_render_context.cpp b/src/main/rt64_render_context.cpp new file mode 100644 index 0000000..497e504 --- /dev/null +++ b/src/main/rt64_render_context.cpp @@ -0,0 +1,378 @@ +#include +#include + +#define HLSL_CPU +#include "hle/rt64_application.h" +#include "rt64_render_hooks.h" + +#include "ultramodern/ultramodern.hpp" +#include "ultramodern/config.hpp" + +#include "zelda_render.h" +#include "recomp_ui.h" + +static RT64::UserConfiguration::Antialiasing device_max_msaa = RT64::UserConfiguration::Antialiasing::None; +static bool sample_positions_supported = false; +static bool high_precision_fb_enabled = false; + +static uint8_t DMEM[0x1000]; +static uint8_t IMEM[0x1000]; + +unsigned int MI_INTR_REG = 0; + +unsigned int DPC_START_REG = 0; +unsigned int DPC_END_REG = 0; +unsigned int DPC_CURRENT_REG = 0; +unsigned int DPC_STATUS_REG = 0; +unsigned int DPC_CLOCK_REG = 0; +unsigned int DPC_BUFBUSY_REG = 0; +unsigned int DPC_PIPEBUSY_REG = 0; +unsigned int DPC_TMEM_REG = 0; + +unsigned int VI_STATUS_REG = 0; +unsigned int VI_ORIGIN_REG = 0; +unsigned int VI_WIDTH_REG = 0; +unsigned int VI_INTR_REG = 0; +unsigned int VI_V_CURRENT_LINE_REG = 0; +unsigned int VI_TIMING_REG = 0; +unsigned int VI_V_SYNC_REG = 0; +unsigned int VI_H_SYNC_REG = 0; +unsigned int VI_LEAP_REG = 0; +unsigned int VI_H_START_REG = 0; +unsigned int VI_V_START_REG = 0; +unsigned int VI_V_BURST_REG = 0; +unsigned int VI_X_SCALE_REG = 0; +unsigned int VI_Y_SCALE_REG = 0; + +void dummy_check_interrupts() {} + +RT64::UserConfiguration::Antialiasing compute_max_supported_aa(RT64::RenderSampleCounts bits) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_2) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_4) { + if (bits & RT64::RenderSampleCount::Bits::COUNT_8) { + return RT64::UserConfiguration::Antialiasing::MSAA8X; + } + return RT64::UserConfiguration::Antialiasing::MSAA4X; + } + return RT64::UserConfiguration::Antialiasing::MSAA2X; + }; + return RT64::UserConfiguration::Antialiasing::None; +} + +RT64::UserConfiguration::AspectRatio to_rt64(ultramodern::renderer::AspectRatio option) { + switch (option) { + case ultramodern::renderer::AspectRatio::Original: + return RT64::UserConfiguration::AspectRatio::Original; + case ultramodern::renderer::AspectRatio::Expand: + return RT64::UserConfiguration::AspectRatio::Expand; + case ultramodern::renderer::AspectRatio::Manual: + return RT64::UserConfiguration::AspectRatio::Manual; + case ultramodern::renderer::AspectRatio::OptionCount: + return RT64::UserConfiguration::AspectRatio::OptionCount; + } +} + +RT64::UserConfiguration::Antialiasing to_rt64(ultramodern::renderer::Antialiasing option) { + switch (option) { + case ultramodern::renderer::Antialiasing::None: + return RT64::UserConfiguration::Antialiasing::None; + case ultramodern::renderer::Antialiasing::MSAA2X: + return RT64::UserConfiguration::Antialiasing::MSAA2X; + case ultramodern::renderer::Antialiasing::MSAA4X: + return RT64::UserConfiguration::Antialiasing::MSAA4X; + case ultramodern::renderer::Antialiasing::MSAA8X: + return RT64::UserConfiguration::Antialiasing::MSAA8X; + case ultramodern::renderer::Antialiasing::OptionCount: + return RT64::UserConfiguration::Antialiasing::OptionCount; + } +} + +RT64::UserConfiguration::RefreshRate to_rt64(ultramodern::renderer::RefreshRate option) { + switch (option) { + case ultramodern::renderer::RefreshRate::Original: + return RT64::UserConfiguration::RefreshRate::Original; + case ultramodern::renderer::RefreshRate::Display: + return RT64::UserConfiguration::RefreshRate::Display; + case ultramodern::renderer::RefreshRate::Manual: + return RT64::UserConfiguration::RefreshRate::Manual; + case ultramodern::renderer::RefreshRate::OptionCount: + return RT64::UserConfiguration::RefreshRate::OptionCount; + } +} + +RT64::UserConfiguration::InternalColorFormat to_rt64(ultramodern::renderer::HighPrecisionFramebuffer option) { + switch (option) { + case ultramodern::renderer::HighPrecisionFramebuffer::Off: + return RT64::UserConfiguration::InternalColorFormat::Standard; + case ultramodern::renderer::HighPrecisionFramebuffer::On: + return RT64::UserConfiguration::InternalColorFormat::High; + case ultramodern::renderer::HighPrecisionFramebuffer::Auto: + return RT64::UserConfiguration::InternalColorFormat::Automatic; + case ultramodern::renderer::HighPrecisionFramebuffer::OptionCount: + return RT64::UserConfiguration::InternalColorFormat::OptionCount; + } +} + +void set_application_user_config(RT64::Application* application, const ultramodern::renderer::GraphicsConfig& config) { + switch (config.res_option) { + default: + case ultramodern::renderer::Resolution::Auto: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::WindowIntegerScale; + application->userConfig.downsampleMultiplier = 1; + break; + case ultramodern::renderer::Resolution::Original: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; + application->userConfig.resolutionMultiplier = std::max(config.ds_option, 1); + application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); + break; + case ultramodern::renderer::Resolution::Original2x: + application->userConfig.resolution = RT64::UserConfiguration::Resolution::Manual; + application->userConfig.resolutionMultiplier = 2.0 * std::max(config.ds_option, 1); + application->userConfig.downsampleMultiplier = std::max(config.ds_option, 1); + break; + } + + switch (config.hr_option) { + default: + case ultramodern::renderer::HUDRatioMode::Original: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Original; + break; + case ultramodern::renderer::HUDRatioMode::Clamp16x9: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Manual; + application->userConfig.extAspectTarget = 16.0/9.0; + break; + case ultramodern::renderer::HUDRatioMode::Full: + application->userConfig.extAspectRatio = RT64::UserConfiguration::AspectRatio::Expand; + break; + } + + application->userConfig.aspectRatio = to_rt64(config.ar_option); + application->userConfig.antialiasing = to_rt64(config.msaa_option); + application->userConfig.refreshRate = to_rt64(config.rr_option); + application->userConfig.refreshRateTarget = config.rr_manual_value; + application->userConfig.internalColorFormat = to_rt64(config.hpfb_option); +} + +ultramodern::renderer::SetupResult map_setup_result(RT64::Application::SetupResult rt64_result) { + switch (rt64_result) { + case RT64::Application::SetupResult::Success: + return ultramodern::renderer::SetupResult::Success; + case RT64::Application::SetupResult::DynamicLibrariesNotFound: + return ultramodern::renderer::SetupResult::DynamicLibrariesNotFound; + case RT64::Application::SetupResult::InvalidGraphicsAPI: + return ultramodern::renderer::SetupResult::InvalidGraphicsAPI; + case RT64::Application::SetupResult::GraphicsAPINotFound: + return ultramodern::renderer::SetupResult::GraphicsAPINotFound; + case RT64::Application::SetupResult::GraphicsDeviceNotFound: + return ultramodern::renderer::SetupResult::GraphicsDeviceNotFound; + } + + fprintf(stderr, "Unhandled `RT64::Application::SetupResult` ?\n"); + assert(false); + std::exit(EXIT_FAILURE); +} + +zelda64::renderer::RT64Context::RT64Context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool debug) { + static unsigned char dummy_rom_header[0x40]; + recompui::set_render_hooks(); + + // Set up the RT64 application core fields. + RT64::Application::Core appCore{}; +#if defined(_WIN32) + appCore.window = window_handle.window; +#elif defined(__ANDROID__) + assert(false && "Unimplemented"); +#elif defined(__linux__) + appCore.window.display = window_handle.display; + appCore.window.window = window_handle.window; +#elif defined(__APPLE__) + appCore.window.window = window_handle.window; + appCore.window.view = window_handle.view; +#endif + + appCore.checkInterrupts = dummy_check_interrupts; + + appCore.HEADER = dummy_rom_header; + appCore.RDRAM = rdram; + appCore.DMEM = DMEM; + appCore.IMEM = IMEM; + + appCore.MI_INTR_REG = &MI_INTR_REG; + + appCore.DPC_START_REG = &DPC_START_REG; + appCore.DPC_END_REG = &DPC_END_REG; + appCore.DPC_CURRENT_REG = &DPC_CURRENT_REG; + appCore.DPC_STATUS_REG = &DPC_STATUS_REG; + appCore.DPC_CLOCK_REG = &DPC_CLOCK_REG; + appCore.DPC_BUFBUSY_REG = &DPC_BUFBUSY_REG; + appCore.DPC_PIPEBUSY_REG = &DPC_PIPEBUSY_REG; + appCore.DPC_TMEM_REG = &DPC_TMEM_REG; + + appCore.VI_STATUS_REG = &VI_STATUS_REG; + appCore.VI_ORIGIN_REG = &VI_ORIGIN_REG; + appCore.VI_WIDTH_REG = &VI_WIDTH_REG; + appCore.VI_INTR_REG = &VI_INTR_REG; + appCore.VI_V_CURRENT_LINE_REG = &VI_V_CURRENT_LINE_REG; + appCore.VI_TIMING_REG = &VI_TIMING_REG; + appCore.VI_V_SYNC_REG = &VI_V_SYNC_REG; + appCore.VI_H_SYNC_REG = &VI_H_SYNC_REG; + appCore.VI_LEAP_REG = &VI_LEAP_REG; + appCore.VI_H_START_REG = &VI_H_START_REG; + appCore.VI_V_START_REG = &VI_V_START_REG; + appCore.VI_V_BURST_REG = &VI_V_BURST_REG; + appCore.VI_X_SCALE_REG = &VI_X_SCALE_REG; + appCore.VI_Y_SCALE_REG = &VI_Y_SCALE_REG; + + // Set up the RT64 application configuration fields. + RT64::ApplicationConfiguration appConfig; + appConfig.useConfigurationFile = false; + + // Create the RT64 application. + app = std::make_unique(appCore, appConfig); + + // Set initial user config settings based on the current settings. + auto& cur_config = ultramodern::renderer::get_graphics_config(); + set_application_user_config(app.get(), cur_config); + app->userConfig.developerMode = debug; + // Force gbi depth branches to prevent LODs from kicking in. + app->enhancementConfig.f3dex.forceBranch = true; + // Scale LODs based on the output resolution. + app->enhancementConfig.textureLOD.scale = true; + // Pick an API if the user has set an override. + switch (cur_config.api_option) { + case ultramodern::renderer::GraphicsApi::D3D12: + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::D3D12; + break; + case ultramodern::renderer::GraphicsApi::Vulkan: + app->userConfig.graphicsAPI = RT64::UserConfiguration::GraphicsAPI::Vulkan; + break; + default: + case ultramodern::renderer::GraphicsApi::Auto: + // Don't override if auto is selected. + break; + } + + // Set up the RT64 application. + uint32_t thread_id = 0; +#ifdef _WIN32 + thread_id = window_handle.thread_id; +#endif + setup_result = map_setup_result(app->setup(thread_id)); + if (setup_result != ultramodern::renderer::SetupResult::Success) { + app = nullptr; + return; + } + + // Set the application's fullscreen state. + app->setFullScreen(cur_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); + + // Check if the selected device actually supports MSAA sample positions and MSAA for for the formats that will be used + // and downgrade the configuration accordingly. + if (app->device->getCapabilities().sampleLocations) { + RT64::RenderSampleCounts color_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::R8G8B8A8_UNORM); + RT64::RenderSampleCounts depth_sample_counts = app->device->getSampleCountsSupported(RT64::RenderFormat::D32_FLOAT); + RT64::RenderSampleCounts common_sample_counts = color_sample_counts & depth_sample_counts; + device_max_msaa = compute_max_supported_aa(common_sample_counts); + sample_positions_supported = true; + } + else { + device_max_msaa = RT64::UserConfiguration::Antialiasing::None; + sample_positions_supported = false; + } + + high_precision_fb_enabled = app->shaderLibrary->usesHDR; +} + +zelda64::renderer::RT64Context::~RT64Context() = default; + +void zelda64::renderer::RT64Context::send_dl(const OSTask* task) { + app->state->rsp->reset(); + app->interpreter->loadUCodeGBI(task->t.ucode & 0x3FFFFFF, task->t.ucode_data & 0x3FFFFFF, true); + app->processDisplayLists(app->core.RDRAM, task->t.data_ptr & 0x3FFFFFF, 0, true); +} + +void zelda64::renderer::RT64Context::update_screen(uint32_t vi_origin) { + VI_ORIGIN_REG = vi_origin; + + app->updateScreen(); +} + +void zelda64::renderer::RT64Context::shutdown() { + if (app != nullptr) { + app->end(); + } +} + +bool zelda64::renderer::RT64Context::update_config(const ultramodern::renderer::GraphicsConfig& old_config, const ultramodern::renderer::GraphicsConfig& new_config) { + if (old_config == new_config) { + return false; + } + + if (new_config.wm_option != old_config.wm_option) { + app->setFullScreen(new_config.wm_option == ultramodern::renderer::WindowMode::Fullscreen); + } + + set_application_user_config(app.get(), new_config); + + app->updateUserConfig(true); + + if (new_config.msaa_option != old_config.msaa_option) { + app->updateMultisampling(); + } + return true; +} + +void zelda64::renderer::RT64Context::enable_instant_present() { + // Enable the present early presentation mode for minimal latency. + app->enhancementConfig.presentation.mode = RT64::EnhancementConfiguration::Presentation::Mode::PresentEarly; + + app->updateEnhancementConfig(); +} + +uint32_t zelda64::renderer::RT64Context::get_display_framerate() const { + return app->presentQueue->ext.sharedResources->swapChainRate; +} + +float zelda64::renderer::RT64Context::get_resolution_scale() const { + constexpr int ReferenceHeight = 240; + switch (app->userConfig.resolution) { + case RT64::UserConfiguration::Resolution::WindowIntegerScale: + if (app->sharedQueueResources->swapChainHeight > 0) { + return std::max(float((app->sharedQueueResources->swapChainHeight + ReferenceHeight - 1) / ReferenceHeight), 1.0f); + } + else { + return 1.0f; + } + case RT64::UserConfiguration::Resolution::Manual: + return float(app->userConfig.resolutionMultiplier); + case RT64::UserConfiguration::Resolution::Original: + default: + return 1.0f; + } +} + +void zelda64::renderer::RT64Context::load_shader_cache(std::span cache_binary) { + // TODO figure out how to avoid a copy here. + std::istringstream cache_stream{std::string{cache_binary.data(), cache_binary.size()}}; + + if (!app->rasterShaderCache->loadOfflineList(cache_stream)) { + printf("Failed to preload shader cache!\n"); + assert(false); + } +} + +RT64::UserConfiguration::Antialiasing zelda64::renderer::RT64MaxMSAA() { + return device_max_msaa; +} + +std::unique_ptr zelda64::renderer::create_render_context(uint8_t* rdram, ultramodern::renderer::WindowHandle window_handle, bool developer_mode) { + return std::make_unique(rdram, window_handle, developer_mode); +} + +bool zelda64::renderer::RT64SamplePositionsSupported() { + return sample_positions_supported; +} + +bool zelda64::renderer::RT64HighPrecisionFBEnabled() { + return high_precision_fb_enabled; +} diff --git a/src/ui/ui_config.cpp b/src/ui/ui_config.cpp index e6b9c1c..6869731 100644 --- a/src/ui/ui_config.cpp +++ b/src/ui/ui_config.cpp @@ -3,13 +3,13 @@ #include "zelda_sound.h" #include "zelda_config.h" #include "zelda_debug.h" +#include "zelda_render.h" #include "promptfont.h" #include "ultramodern/config.hpp" #include "ultramodern/ultramodern.hpp" #include "RmlUi/Core.h" -#include "ultramodern/rt64_layer.hpp" -ultramodern::GraphicsConfig new_options; +ultramodern::renderer::GraphicsConfig new_options; Rml::DataModelHandle nav_help_model_handle; Rml::DataModelHandle general_model_handle; Rml::DataModelHandle controls_model_handle; @@ -214,9 +214,9 @@ extern SDL_Window* window; #endif void apply_graphics_config(void) { - ultramodern::set_graphics_config(new_options); + ultramodern::renderer::set_graphics_config(new_options); #if defined(__linux__) // TODO: Remove once RT64 gets native fullscreen support on Linux - if (new_options.wm_option == ultramodern::WindowMode::Fullscreen) { + if (new_options.wm_option == ultramodern::renderer::WindowMode::Fullscreen) { SDL_SetWindowFullscreen(window,SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(window,0); @@ -225,7 +225,7 @@ void apply_graphics_config(void) { } void close_config_menu() { - if (ultramodern::get_graphics_config() != new_options) { + if (ultramodern::renderer::get_graphics_config() != new_options) { prompt_context.open_prompt( "Graphics options have changed", "Would you like to apply or discard the changes?", @@ -237,7 +237,7 @@ void close_config_menu() { close_config_menu_impl(); }, []() { - new_options = ultramodern::get_graphics_config(); + new_options = ultramodern::renderer::get_graphics_config(); graphics_model_handle.DirtyAllVariables(); close_config_menu_impl(); }, @@ -612,7 +612,7 @@ public: } ultramodern::sleep_milliseconds(50); - new_options = ultramodern::get_graphics_config(); + new_options = ultramodern::renderer::get_graphics_config(); bind_config_list_events(constructor); constructor.BindFunc("res_option", @@ -639,7 +639,7 @@ public: }); constructor.BindFunc("ds_option", [](Rml::Variant& out) { - if (new_options.res_option == ultramodern::Resolution::Auto) { + if (new_options.res_option == ultramodern::renderer::Resolution::Auto) { out = 1; } else { out = new_options.ds_option; @@ -658,23 +658,23 @@ public: constructor.BindFunc("options_changed", [](Rml::Variant& out) { - out = (ultramodern::get_graphics_config() != new_options); + out = (ultramodern::renderer::get_graphics_config() != new_options); }); constructor.BindFunc("ds_info", [](Rml::Variant& out) { switch (new_options.res_option) { default: - case ultramodern::Resolution::Auto: + case ultramodern::renderer::Resolution::Auto: out = "Downsampling is not available at auto resolution"; return; - case ultramodern::Resolution::Original: + case ultramodern::renderer::Resolution::Original: if (new_options.ds_option == 2) { out = "Rendered in 480p and scaled to 240p"; } else if (new_options.ds_option == 4) { out = "Rendered in 960p and scaled to 240p"; } return; - case ultramodern::Resolution::Original2x: + case ultramodern::renderer::Resolution::Original2x: if (new_options.ds_option == 2) { out = "Rendered in 960p and scaled to 480p"; } else if (new_options.ds_option == 4) { @@ -973,7 +973,7 @@ public: void make_bindings(Rml::Context* context) override { // initially set cont state for ui help - recomp::config_menu_set_cont_or_kb(recompui::get_cont_active()); + recomp::config_menu_set_cont_or_kb(recompui::get_cont_active()); make_nav_help_bindings(context); make_general_bindings(context); make_controls_bindings(context); @@ -1000,18 +1000,18 @@ void zelda64::set_debug_mode_enabled(bool enabled) { } void recompui::update_supported_options() { - msaa2x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X; - msaa4x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X; - msaa8x_supported = ultramodern::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X; - sample_positions_supported = ultramodern::RT64SamplePositionsSupported(); + msaa2x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA2X; + msaa4x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA4X; + msaa8x_supported = zelda64::renderer::RT64MaxMSAA() >= RT64::UserConfiguration::Antialiasing::MSAA8X; + sample_positions_supported = zelda64::renderer::RT64SamplePositionsSupported(); - new_options = ultramodern::get_graphics_config(); + new_options = ultramodern::renderer::get_graphics_config(); graphics_model_handle.DirtyAllVariables(); } void recompui::toggle_fullscreen() { - new_options.wm_option = (new_options.wm_option == ultramodern::WindowMode::Windowed) ? ultramodern::WindowMode::Fullscreen : ultramodern::WindowMode::Windowed; + new_options.wm_option = (new_options.wm_option == ultramodern::renderer::WindowMode::Windowed) ? ultramodern::renderer::WindowMode::Fullscreen : ultramodern::renderer::WindowMode::Windowed; apply_graphics_config(); graphics_model_handle.DirtyVariable("wm_option"); } diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 08a3d21..41b8003 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -19,7 +19,6 @@ #include "concurrentqueue.h" -#include "ultramodern/rt64_layer.hpp" #include "rt64_render_hooks.h" #include "rt64_render_interface_builders.h" @@ -1458,7 +1457,7 @@ void deinit_hook() { ui_context.reset(); } -void set_rt64_hooks() { +void recompui::set_render_hooks() { RT64::SetRenderHooks(init_hook, draw_hook, deinit_hook); }