diff options
| author | Mako <137927095+Takoroka@users.noreply.github.com> | 2026-11-01 10:18:27 -0500 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-11-01 10:18:27 -0500 |
| commit | 89dd760cc44aec0a9401809f3837917645d8af25 (patch) | |
| tree | 41d6ea03c7be56feb0b2573ab199a43c7c9ab5e0 | |
| parent | adae7635708e155d967e84d7ad84bf63c5203409 (diff) | |
| parent | 76adfd3e9508bf685b0dc5354d6821148139cef1 (diff) | |
Merge pull request #143 from Sssnowyyy/macos
macOS (Apple Silicon) build fixes and release builds
| -rw-r--r-- | .github/workflows/release-binaries.yml | 100 | ||||
| -rw-r--r-- | dist/eglantine.icns | bin | 0 -> 620877 bytes | |||
| -rw-r--r-- | externals/cmake-modules/DetectArchitecture.cmake | 13 | ||||
| -rw-r--r-- | src/common/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | 4 | ||||
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 4 | ||||
| -rw-r--r-- | src/yuzu/Info.plist | 2 | ||||
| -rw-r--r-- | src/yuzu_cmd/emu_window/emu_window_sdl3.cpp | 14 | ||||
| -rw-r--r-- | src/yuzu_cmd/emu_window/emu_window_sdl3.h | 6 | ||||
| -rw-r--r-- | src/yuzu_cmd/yuzu.cpp | 3 |
10 files changed, 140 insertions, 8 deletions
diff --git a/.github/workflows/release-binaries.yml b/.github/workflows/release-binaries.yml index 8835844060..20aa57f7aa 100644 --- a/.github/workflows/release-binaries.yml +++ b/.github/workflows/release-binaries.yml @@ -67,6 +67,8 @@ jobs: - platform: linux-aarch64-pgo runner: ubuntu-24.04-arm pgo: true + - platform: macos-arm64 + runner: macos-15 steps: - name: Check out source @@ -87,6 +89,17 @@ jobs: libboost-dev libboost-fiber-dev libboost-context-dev \ libopus-dev libasound2-dev ccache + # The packages docs/Deps.md lists for macOS, plus ccache. + - name: Install macOS dependencies + if: startsWith(matrix.platform, 'macos') + shell: bash + env: + HOMEBREW_NO_INSTALL_CLEANUP: 1 + run: | + brew install autoconf automake boost ffmpeg fmt glslang hidapi libtool libusb lz4 ninja \ + nlohmann-json openssl pkg-config qt@6 sdl3 speexdsp zlib zstd cmake catch2 molten-vk \ + vulkan-loader spirv-tools vulkan-utility-libraries ccache + - name: Install Qt on Linux x86_64 if: startsWith(matrix.platform, 'linux') && runner.arch != 'ARM64' uses: jurplel/install-qt-action@v4 @@ -266,6 +279,27 @@ jobs: -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache + - name: Configure macOS + if: startsWith(matrix.platform, 'macos') + shell: bash + run: | + set -euo pipefail + cmake -S . -B build/release -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_TESTING=OFF \ + -DYUZU_TESTS=OFF \ + -DYUZU_CMD=ON \ + -DYUZU_ROOM=OFF \ + -DYUZU_ROOM_STANDALONE=OFF \ + -DYUZU_USE_BUNDLED_QT=OFF \ + "-DCMAKE_PREFIX_PATH=$(brew --prefix qt@6);$(brew --prefix zlib)" \ + "-DOPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" \ + -DVulkanHeaders_FORCE_BUNDLED=ON \ + -DVulkanUtilityLibraries_FORCE_BUNDLED=ON \ + -DENABLE_UPDATE_CHECKER=OFF \ + -DUSE_DISCORD_PRESENCE=OFF \ + -DUSE_CCACHE=ON + # MinGW build: system Qt and libraries from MSYS2, clang plus Eden's PGO profile. The profile # path is converted to its Windows form because clang runs with the MSYS mount underneath. - name: Configure MinGW PGO @@ -411,6 +445,58 @@ jobs: deps "$dll" done + # A self-contained app bundle: macdeployqt copies Qt and every other Homebrew library both + # programs load into the bundle, then the leftover Homebrew search paths are removed so a + # machine with Homebrew still uses the bundled copies. Ad-hoc signed, not notarized. + - name: Package macOS app + if: startsWith(matrix.platform, 'macos') + shell: bash + run: | + set -euo pipefail + app=build/release/bin/eglantine.app + test -x "$app/Contents/MacOS/eglantine" + test -x build/release/bin/eden-cli + cp build/release/bin/eden-cli "$app/Contents/MacOS/eden-cli" + "$(brew --prefix qt@6)/bin/macdeployqt" "$app" \ + -executable="$app/Contents/MacOS/eden-cli" -libpath="$(brew --prefix)/lib" \ + -always-overwrite -verbose=1 + # Qt's virtual keyboard input plugin pulls in the QML stack, which the emulator doesn't use. + rm -f "$app/Contents/PlugIns/platforminputcontexts/libqtvirtualkeyboardplugin.dylib" + binaries=$(find "$app/Contents" -type f \( -perm -u+x -o -name '*.dylib' \) \ + -exec sh -c 'file -b "$1" | grep -q Mach-O' _ {} \; -print) + for binary in $binaries; do + otool -l "$binary" | awk '/cmd LC_RPATH/ {getline; getline; print $2}' | + { grep -E '^/(opt/homebrew|usr/local)/' || true; } | while read -r rpath; do + install_name_tool -delete_rpath "$rpath" "$binary" 2> /dev/null + done + done + # Libraries that load their dependencies through @rpath find them in the bundle. + for program in eglantine eden-cli; do + install_name_tool -add_rpath @executable_path/../Frameworks \ + "$app/Contents/MacOS/$program" 2> /dev/null + done + leaks=$(for binary in $binaries; do + otool -L "$binary" | grep -E '^\s+/(opt/homebrew|usr/local)/' + otool -l "$binary" | awk '/cmd LC_RPATH/ {getline; getline; print $2}' | + grep -E '^/(opt/homebrew|usr/local)/' + done || true) + if [ -n "$leaks" ]; then + echo "References still pointing outside the bundle:" + echo "$leaks" | sort -u + exit 1 + fi + # macdeployqt invalidates the signatures. The programs get the entitlements when the + # tree has them (the Hypervisor CPU backend needs com.apple.security.hypervisor). + codesign --force --deep --sign - "$app" + if [ -f dist/eglantine.entitlements ]; then + codesign --force --sign - --entitlements dist/eglantine.entitlements \ + "$app/Contents/MacOS/eden-cli" + codesign --force --sign - --entitlements dist/eglantine.entitlements "$app" + fi + codesign --verify --deep --strict "$app" + ditto -c -k --keepParent "$app" "eglantine-${GITHUB_SHA}-${{ matrix.platform }}.zip" + ls -la "eglantine-${GITHUB_SHA}-${{ matrix.platform }}.zip" + # The artifact is the bin folder itself: GitHub zips artifacts on download, and uploading a # zip gave a zip inside a zip. The release job zips it once for the release. - name: Upload Windows binary @@ -444,6 +530,17 @@ jobs: compression-level: 0 retention-days: 7 + # Zipped with ditto beforehand: the bundle's framework symlinks don't survive artifact zipping. + - name: Upload macOS app + if: startsWith(matrix.platform, 'macos') + uses: actions/upload-artifact@v4 + with: + name: eglantine-${{ matrix.platform }}-${{ github.sha }} + path: eglantine-${{ github.sha }}-${{ matrix.platform }}.zip + if-no-files-found: error + compression-level: 0 + retention-days: 7 + # Publishes every binary as a GitHub pre-release once every platform built: on pushes to nx2 # and nx2-experimental, and on manual runs unless "Publish the binaries as a GitHub release" is # unticked. @@ -471,6 +568,8 @@ jobs: if compgen -G "$artifact*.AppImage" > /dev/null; then # Linux: ship the AppImage and the tar.gz as they are. cp "$artifact"/*.AppImage "$artifact"/*.tar.gz dist/ + elif compgen -G "$artifact*.zip" > /dev/null; then + cp "$artifact"/*.zip dist/ else (cd "$artifact" && zip -qr "${GITHUB_WORKSPACE}/dist/${name}.zip" .) fi @@ -492,6 +591,7 @@ jobs: Windows: extract the zip and run eglantine.exe, or eden-cli.exe from a terminal. Linux (AppImage): make it executable (chmod +x) and run it; it bundles its libraries and needs glibc 2.39 or newer (Ubuntu 24.04, Fedora 40, Debian 13, Arch). Linux (tar.gz): extract it and run ./eglantine or ./eden-cli; it uses your system libraries, so Qt 6 and the usual libraries must be installed. + macOS: Apple Silicon and macOS 15 or newer. Unzip and move eglantine.app to Applications; eden-cli is inside it, in Contents/MacOS. The app is not notarized, so the first time right-click it and choose Open (or run xattr -dr com.apple.quarantine eglantine.app). The -pgo builds are the same programs compiled with profile-guided optimization (clang plus Eden's profile); prefer them when your CPU supports it. The Windows PGO build is a MinGW build, so it carries its own DLLs and needs no Visual C++ redistributable." - name: Ping Discord diff --git a/dist/eglantine.icns b/dist/eglantine.icns Binary files differnew file mode 100644 index 0000000000..812334cd28 --- /dev/null +++ b/dist/eglantine.icns diff --git a/externals/cmake-modules/DetectArchitecture.cmake b/externals/cmake-modules/DetectArchitecture.cmake index 105963c8c2..be84109994 100644 --- a/externals/cmake-modules/DetectArchitecture.cmake +++ b/externals/cmake-modules/DetectArchitecture.cmake @@ -36,12 +36,17 @@ This file is based off of Yuzu and Dynarmic. # should NOT be too dependent on the architecture # otherwise, you may end up with duplicate code if (CMAKE_OSX_ARCHITECTURES) - set(MULTIARCH_BUILD 1) + list(LENGTH CMAKE_OSX_ARCHITECTURES architecture_count) + if (architecture_count GREATER 1) + set(MULTIARCH_BUILD 1) + else() + set(MULTIARCH_BUILD 0) + endif() set(ARCHITECTURE "${CMAKE_OSX_ARCHITECTURES}") # hope and pray the architecture names match - foreach(ARCH IN ${CMAKE_OSX_ARCHITECTURES}) - set(ARCHITECTURE_${ARCH} 1 PARENT_SCOPE) + foreach(ARCH IN LISTS CMAKE_OSX_ARCHITECTURES) + set(ARCHITECTURE_${ARCH} 1) add_definitions(-DARCHITECTURE_${ARCH}=1) endforeach() @@ -218,4 +223,4 @@ if (NOT DEFINED ARCHITECTURE) add_definitions(-DARCHITECTURE_GENERIC=1) endif() -message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}")
\ No newline at end of file +message(STATUS "[DetectArchitecture] Target architecture: ${ARCHITECTURE}") diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d3e7b03cd2..76c5019788 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -152,7 +152,7 @@ add_library( set_source_files_properties(zbic_compression.cpp PROPERTIES INCLUDE_DIRECTORIES "${ZBIC_INCLUDE_DIR}" - COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>;$<$<CXX_COMPILER_ID:MSVC>:/wd4505;/wd5245>") + COMPILE_OPTIONS "$<$<CXX_COMPILER_ID:AppleClang,Clang,GNU>:-Wno-unused-function;-Wno-missing-declarations;-Wno-shadow>;$<$<CXX_COMPILER_ID:MSVC>:/wd4505;/wd5245>") if(WIN32) target_sources(common PRIVATE windows/timer_resolution.cpp diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2c64f3375b..05511c10e1 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -256,6 +256,10 @@ Shader::RuntimeInfo MakeRuntimeInfo(std::span<const Shader::IR::Program> program if (device.IsMoltenVK()) { for (size_t i = 0; i < 8; ++i) { const auto format = static_cast<Tegra::RenderTargetFormat>(key.state.color_formats[i]); + if (format == Tegra::RenderTargetFormat::NONE) { + info.color_output_types[i] = Shader::AttributeType::Float; + continue; + } const auto pixel_format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(format); if (VideoCore::Surface::IsPixelFormatInteger(pixel_format)) { if (VideoCore::Surface::IsPixelFormatSignedInteger(pixel_format)) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index c8aaf8dff5..3b783fc6c6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -3063,6 +3063,10 @@ template <class P> void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<ImageCopy> copies) { Image& dst = slot_images[dst_id]; Image& src = slot_images[src_id]; + if (True(dst.flags & ImageFlagBits::UnwrittenSparse) || + True(src.flags & ImageFlagBits::UnwrittenSparse)) { + return; + } if (dst.info.astc_opaque != src.info.astc_opaque) { // BC1 and BC3 blocks differ in size, so the host can't copy between them; both hold the // same guest data, which is uploaded from guest memory instead. diff --git a/src/yuzu/Info.plist b/src/yuzu/Info.plist index cf6f597e99..9b6d49b87a 100644 --- a/src/yuzu/Info.plist +++ b/src/yuzu/Info.plist @@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later <key>CFBundleDevelopmentRegion</key> <string>English</string> <key>CFBundleExecutable</key> - <string>${EXECUTABLE_NAME}</string> + <string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string> <key>CFBundleGetInfoString</key> <string></string> <key>CFBundleIconFile</key> diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp index ecb8171ac7..93bbbfb8fc 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.cpp @@ -38,8 +38,10 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co titlebar_timer = SDL_AddTimer(2000, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 { auto* this_ = (EmuWindow_SDL3*)userdata; auto const results = this_->system.GetAndResetPerfStats(); - auto const title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0f); - SDL_SetWindowTitle(this_->render_window, title.c_str()); + auto* const title = new std::string(fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0f)); + // Timers run on their own thread and AppKit throws when a title is set off the main + // thread: ApplyPendingTitle sets it from the main loop. + delete this_->pending_title.exchange(title); return 2000; }, this); mouse_timer = SDL_AddTimer(100, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 { @@ -55,6 +57,7 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co EmuWindow_SDL3::~EmuWindow_SDL3() { SDL_RemoveTimer(titlebar_timer); SDL_RemoveTimer(mouse_timer); + delete pending_title.exchange(nullptr); system.HIDCore().UnloadInputDevices(); input_subsystem->Shutdown(); SDL_Quit(); @@ -154,6 +157,13 @@ bool EmuWindow_SDL3::IsOpen() const { return is_open; } +void EmuWindow_SDL3::ApplyPendingTitle() { + if (std::string* const title = pending_title.exchange(nullptr)) { + SDL_SetWindowTitle(render_window, title->c_str()); + delete title; + } +} + bool EmuWindow_SDL3::IsShown() const { return is_shown; } diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl3.h b/src/yuzu_cmd/emu_window/emu_window_sdl3.h index 0dc2a4983a..a76a2bfd85 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl3.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl3.h @@ -6,7 +6,9 @@ #pragma once +#include <atomic> #include <cstdlib> +#include <string> #include <tuple> #include <utility> @@ -41,6 +43,9 @@ public: /// Whether the window is still open, and a close request hasn't yet been sent bool IsOpen() const; + /// Sets the title the titlebar timer prepared; call from the main thread. + void ApplyPendingTitle(); + /// Returns if window is shown (not minimized) bool IsShown() const override; @@ -92,6 +97,7 @@ protected: /// Periodic changer of titlebar (independent of event loop) SDL_TimerID titlebar_timer; + std::atomic<std::string*> pending_title{}; // Mouse resetter once it SDL_TimerID mouse_timer; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 22abe6a759..4d71a29993 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -481,6 +481,9 @@ static void RunKeyScript(InputCommon::InputSubsystem& input_subsystem) { extern "C" SDL_AppResult SDL_AppIterate(void *appstate) { SdlState *state = (SdlState *)appstate; RunKeyScript(state->input_subsystem); + if (state->emu_window) { + state->emu_window->ApplyPendingTitle(); + } return state->emu_window && state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS; } extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { |
