Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,84 @@ jobs:
name: cemu-installer-windows-x64
path: ./src/resource/cemu-${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}-windows-x64-installer.exe

build-windows-arm64:
continue-on-error: true
runs-on: windows-11-arm
steps:
- name: "Checkout repo"
uses: actions/checkout@v6
with:
submodules: "recursive"

- name: Setup release mode parameters
run: |
echo "BUILD_MODE=release" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "BUILD_FLAGS=" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
echo "Build mode is release"

- name: Setup build flags for version
if: ${{ inputs.next_version_major != '' }}
run: |
echo "[INFO] Version ${{ inputs.next_version_major }}.${{ inputs.next_version_minor }}"
echo "BUILD_FLAGS=${{ env.BUILD_FLAGS }} -DEMULATOR_VERSION_MAJOR=${{ inputs.next_version_major }} -DEMULATOR_VERSION_MINOR=${{ inputs.next_version_minor }}" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append

- name: "Ensure clang-cl (LLVM) toolset is installed in Visual Studio"
run: |
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
$vsPath = & $vswhere -latest -products * -property installationPath
$setup = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\setup.exe"
$modifyArgs = "modify --installPath `"$vsPath`" --add Microsoft.VisualStudio.Component.VC.Llvm.Clang --add Microsoft.VisualStudio.Component.VC.Llvm.ClangToolset --quiet --norestart"
Start-Process -FilePath $setup -Wait -ArgumentList $modifyArgs

- name: "Setup MSVC environment (arm64)"
uses: ilammy/msvc-dev-cmd@v1
with:
arch: arm64

- name: "Setup Ninja"
run: choco install ninja -y

- name: "Setup cmake"
uses: jwlawson/actions-setup-cmake@v2
with:
cmake-version: '3.29.0'

- name: "Bootstrap vcpkg"
run: |
./dependencies/vcpkg/bootstrap-vcpkg.bat

- name: 'Setup NuGet Credentials for vcpkg'
shell: 'bash'
run: |
`./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \
sources add \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json" \
-storepasswordincleartext \
-name "GitHub" \
-username "${{ github.repository_owner }}" \
-password "${{ secrets.GITHUB_TOKEN }}"
`./dependencies/vcpkg/vcpkg.exe fetch nuget | tail -n 1` \
setapikey "${{ secrets.GITHUB_TOKEN }}" \
-source "https://nuget.pkg.github.com/${{ github.repository_owner }}/index.json"

- name: "cmake"
run: |
echo "[INFO] BUILD_FLAGS: ${{ env.BUILD_FLAGS }}"
cmake -S . -B build -G Ninja ${{ env.BUILD_FLAGS }} -DCMAKE_BUILD_TYPE=${{ env.BUILD_MODE }} -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_LINKER_TYPE=MSVC -DVCPKG_INSTALL_OPTIONS="--clean-after-build"

- name: "Build Cemu"
run: |
cmake --build build

- name: Prepare artifact
run: Rename-Item bin/Cemu_release.exe Cemu.exe

- name: Upload artifact
uses: actions/upload-artifact@v6
with:
name: cemu-bin-windows-arm64
path: ./bin/Cemu.exe

build-macos:
runs-on: macos-14
strategy:
Expand Down
30 changes: 30 additions & 0 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,36 @@ Instructions for Visual Studio 2022:

Any other IDE should also work as long as it has CMake and MSVC support. CLion and Visual Studio Code have been confirmed to work.

### Windows on ARM64 (Snapdragon)

Native ARM64 builds are supported and use **clang-cl**. The recompiler uses the
AArch64 backend (`xbyak_aarch64`), so it runs natively rather than under x64
emulation.

Prerequisites (Visual Studio 2022 or Build Tools 2022 with these components):
- MSVC v143 - ARM64/ARM64EC build tools
- C++ Clang Compiler for Windows and *MSBuild support for LLVM (clang-cl) toolset*
- C++ CMake tools for Windows (CMake 3.29 or newer)
- Windows 11 SDK

The Visual Studio CMake generator cannot assemble the codebase, so build from the
command line with Ninja. Open an **"ARM64 Native Tools Command Prompt for VS 2022"**
(or run `vcvarsarm64.bat`), then:

```
git clone --recursive https://github.com/cemu-project/Cemu
cd Cemu
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=release ^
-DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl ^
-DCMAKE_LINKER_TYPE=MSVC
cmake --build build
```

`-DCMAKE_LINKER_TYPE=MSVC` makes clang link with the MSVC `link.exe` (required so
that vcpkg's static libraries link correctly). The resulting binary is
`bin/Cemu_release.exe`. Note: on ARM64 the bundled H.264 decoder (ih264d) is built
as portable C, and libusb is used via the standard vcpkg package.

## Linux

To compile Cemu, a recent enough compiler and STL with C++20 support is required! Clang-15 or higher is what we recommend.
Expand Down
45 changes: 41 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,17 @@ if (ENABLE_VCPKG)
# CONFIG option
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
if (WIN32)
set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "")
# Auto-select the vcpkg triplet based on the target architecture.
# Override on the command line with -DVCPKG_TARGET_TRIPLET=...
if (NOT DEFINED VCPKG_TARGET_TRIPLET)
if (CMAKE_GENERATOR_PLATFORM MATCHES "(ARM64|arm64)" OR
CMAKE_SYSTEM_PROCESSOR MATCHES "(ARM64|arm64|aarch64)" OR
"$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "arm64")
set(VCPKG_TARGET_TRIPLET "arm64-windows-static" CACHE STRING "")
else()
set(VCPKG_TARGET_TRIPLET "x64-windows-static" CACHE STRING "")
endif()
endif()
endif()
endif()

Expand All @@ -72,9 +82,13 @@ add_definitions(-DEMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH})

set_property(GLOBAL PROPERTY USE_FOLDERS ON)

# enable link time optimization for release builds
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON)
# enable link time optimization for release builds.
# Skip it for clang-cl on Windows: IPO forces the LLVM linker (lld-link), which
# cannot consume the MSVC /GL objects inside vcpkg static libraries.
if(NOT (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang"))
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELWITHDEBINFO ON)
endif()

if (MSVC)
set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT CemuBin)
Expand Down Expand Up @@ -256,11 +270,34 @@ add_subdirectory("dependencies/ih264d" EXCLUDE_FROM_ALL)

if (CMAKE_OSX_ARCHITECTURES)
set(CEMU_ARCHITECTURE ${CMAKE_OSX_ARCHITECTURES})
elseif (WIN32 AND (CMAKE_GENERATOR_PLATFORM MATCHES "(ARM64|arm64)" OR "$ENV{VSCMD_ARG_TGT_ARCH}" STREQUAL "arm64"))
# Windows ARM64: the host toolchain / emulated shell can misreport the
# processor, so trust the requested target platform instead.
set(CEMU_ARCHITECTURE "aarch64")
else()
set(CEMU_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
endif()
if(CEMU_ARCHITECTURE MATCHES "(aarch64)|(AARCH64)|(arm64)|(ARM64)")
add_subdirectory("dependencies/xbyak_aarch64" EXCLUDE_FROM_ALL)
if(MSVC)
# Match the static CRT used by the rest of the project (vcpkg static triplet)
set_property(TARGET xbyak_aarch64 PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
if(MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# clang-cl emits calls to compiler-rt builtins (e.g. __udivti3 for 128-bit
# integer division) that lld-link does not link automatically. Link the
# clang_rt.builtins library that ships next to the compiler.
get_filename_component(_llvm_bin "${CMAKE_CXX_COMPILER}" DIRECTORY)
get_filename_component(_llvm_root "${_llvm_bin}" DIRECTORY)
file(GLOB _clang_rt_builtins "${_llvm_root}/lib/clang/*/lib/windows/clang_rt.builtins-aarch64.lib")
if(_clang_rt_builtins)
list(GET _clang_rt_builtins 0 _clang_rt_builtins)
link_libraries("${_clang_rt_builtins}")
message(STATUS "Linking compiler-rt builtins: ${_clang_rt_builtins}")
else()
message(WARNING "clang_rt.builtins-aarch64.lib not found; link may fail with undefined __udivti3")
endif()
endif()
endif()

find_package(ZArchive QUIET)
Expand Down
20 changes: 19 additions & 1 deletion dependencies/ih264d/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,12 @@ else()
set(IH264D_ARCHITECTURE ${CMAKE_SYSTEM_PROCESSOR})
endif()

if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64" OR IH264D_ARCHITECTURE STREQUAL "AMD64")
# Normalize the architecture string so matching is case-insensitive.
# MSVC/CMake reports "ARM64"/"AMD64" (uppercase) whereas the checks below use
# the lowercase names emitted by GCC/Clang toolchains.
string(TOLOWER "${IH264D_ARCHITECTURE}" IH264D_ARCHITECTURE)

if (IH264D_ARCHITECTURE STREQUAL "x86_64" OR IH264D_ARCHITECTURE STREQUAL "amd64")
set(LIBAVCDEC_X86_INCLUDES "common/x86" "decoder/x86")
include_directories("common/" "decoder/" ${LIBAVCDEC_X86_INCLUDES})
target_sources(ih264d PRIVATE
Expand All @@ -147,6 +152,18 @@ target_sources(ih264d PRIVATE
"decoder/x86/ih264d_function_selector_ssse3.c"
)
elseif(IH264D_ARCHITECTURE STREQUAL "aarch64" OR IH264D_ARCHITECTURE STREQUAL "arm64")
if(MSVC)
# Windows on ARM64: build the generic C decoder. The NEON .s files are GAS syntax
# and use ELF-only ":got:" relocations that cannot be assembled for COFF targets,
# so we fall back to portable C (only affects H.264/FMV decode performance).
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
target_sources(ih264d PRIVATE
"common/armv8/ih264_platform_macros.h"
"decoder/arm/ih264d_function_selector.c"
)
target_compile_definitions(ih264d PRIVATE ARMV8 DISABLE_NEON DEFAULT_ARCH=D_ARCH_ARM_NONEON)
else()
enable_language( C CXX ASM )
set(LIBAVCDEC_ARM_INCLUDES "common/armv8" "decoder/arm")
include_directories("common/" "decoder/" ${LIBAVCDEC_ARM_INCLUDES})
Expand Down Expand Up @@ -189,6 +206,7 @@ endif()
if(APPLE)
target_sources(ih264d PRIVATE "common/armv8/macos_arm_symbol_aliases.s")
endif()
endif()
else()
message(FATAL_ERROR "ih264d unknown architecture: ${IH264D_ARCHITECTURE}")
endif()
Expand Down
2 changes: 1 addition & 1 deletion dependencies/ih264d/decoder/arm/ih264d_function_selector.c
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void ih264d_init_function_ptr(dec_struct_t *ps_codec)
ih264d_init_function_ptr_generic(ps_codec);
switch(e_proc_arch)
{
#if defined(ARMV8)
#if defined(ARMV8) && !defined(DISABLE_NEON)
case ARCH_ARMV8_GENERIC:
default:
ih264d_init_function_ptr_av8(ps_codec);
Expand Down
2 changes: 1 addition & 1 deletion dist/windows/Cemu.manifest
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="amd64" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"></assemblyIdentity>
</dependentAssembly>
</dependency>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"><security><requestedPrivileges>
Expand Down
7 changes: 6 additions & 1 deletion src/Cafe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,12 @@ endif()
if (ENABLE_LIBUSB)
if (ENABLE_VCPKG)
if(WIN32)
set(PKG_CONFIG_EXECUTABLE "${VCPKG_INSTALLED_DIR}/x64-windows/tools/pkgconf/pkgconf.exe")
# pkgconf is a host tool; locate it under whichever host triplet vcpkg
# used (x64-windows on Intel hosts, arm64-windows on ARM64 hosts).
file(GLOB _cemu_pkgconf "${VCPKG_INSTALLED_DIR}/*/tools/pkgconf/pkgconf.exe")
if(_cemu_pkgconf)
list(GET _cemu_pkgconf 0 PKG_CONFIG_EXECUTABLE)
endif()
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(libusb REQUIRED IMPORTED_TARGET libusb-1.0)
Expand Down
2 changes: 1 addition & 1 deletion src/Cafe/HW/Espresso/Debugger/Debugger.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ void debugger_updateMemoryBreakpoint(DebuggerBreakpoint* bp)
{
std::vector<std::thread::native_handle_type> schedulerThreadHandles = coreinit::OSGetSchedulerThreads();

#if BOOST_OS_WINDOWS
#if BOOST_OS_WINDOWS && defined(ARCH_X86_64)
s_debuggerState.activeMemoryBreakpoint = bp;
for (auto& hThreadNH : schedulerThreadHandles)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Cafe/HW/Latte/Core/LatteTextureCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ uint32 LatteTexture_CalculateTextureDataHash(LatteTexture* hostTexture)
bool isCompressedFormat = hostTexture->IsCompressedFormat();
if( isCompressedFormat == false )
{
#if BOOST_OS_WINDOWS
#if BOOST_OS_WINDOWS && defined(ARCH_X86_64)
if (g_CPUFeatures.x86.avx2)
{
__m256i h256 = { 0 };
Expand Down
16 changes: 16 additions & 0 deletions src/Common/ExceptionHandler/ExceptionHandler_win32.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ void createCrashlog(EXCEPTION_POINTERS* e, PCONTEXT context)
// register info
sprintf(dumpLine, "\n");
cemuLog_writePlainToLog(dumpLine);
#if defined(ARCH_X86_64)
sprintf(dumpLine, "RAX=%016I64x RBX=%016I64x RCX=%016I64x RDX=%016I64x\n", context->Rax, context->Rbx, context->Rcx, context->Rdx);
cemuLog_writePlainToLog(dumpLine);
sprintf(dumpLine, "RSP=%016I64x RBP=%016I64x RDI=%016I64x RSI=%016I64x\n", context->Rsp, context->Rbp, context->Rdi, context->Rsi);
Expand All @@ -217,6 +218,17 @@ void createCrashlog(EXCEPTION_POINTERS* e, PCONTEXT context)
cemuLog_writePlainToLog(dumpLine);
sprintf(dumpLine, "R12=%016I64x R13=%016I64x R14=%016I64x R15=%016I64x\n", context->R12, context->R13, context->R14, context->R15);
cemuLog_writePlainToLog(dumpLine);
#else // AArch64
for (int i = 0; i < 28; i += 4)
{
sprintf(dumpLine, "X%-2d=%016I64x X%-2d=%016I64x X%-2d=%016I64x X%-2d=%016I64x\n",
i, context->X[i], i + 1, context->X[i + 1], i + 2, context->X[i + 2], i + 3, context->X[i + 3]);
cemuLog_writePlainToLog(dumpLine);
}
sprintf(dumpLine, "X28=%016I64x FP =%016I64x LR =%016I64x SP =%016I64x PC =%016I64x\n",
context->X[28], context->Fp, context->Lr, context->Sp, context->Pc);
cemuLog_writePlainToLog(dumpLine);
#endif

CrashLog_SetOutputChannels(false, true);
ExceptionHandler_LogGeneralInfo();
Expand Down Expand Up @@ -264,11 +276,15 @@ LONG WINAPI VectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
if (r != EXCEPTION_CONTINUE_SEARCH)
return r;

#if defined(ARCH_X86_64)
if (GetBits(pExceptionInfo->ContextRecord->Dr6, 0, 1) || GetBits(pExceptionInfo->ContextRecord->Dr6, 1, 1))
debugger_handleSingleStepException(pExceptionInfo->ContextRecord->Dr6);
else if (GetBits(pExceptionInfo->ContextRecord->Dr6, 2, 1) || GetBits(pExceptionInfo->ContextRecord->Dr6, 3, 1))
g_gdbstub->HandleAccessException(pExceptionInfo->ContextRecord->Dr6);
return EXCEPTION_CONTINUE_EXECUTION;
#else // AArch64: hardware debug registers (Dr6) not available; debugger single-step unsupported for now
return EXCEPTION_CONTINUE_SEARCH;
#endif
}
return EXCEPTION_CONTINUE_SEARCH;
}
Expand Down
25 changes: 18 additions & 7 deletions src/Common/precompiled.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ typedef union _LARGE_INTEGER {
inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }
#endif

#if defined(_MSC_VER) && defined(_M_ARM64)
// _umul128 is an x86-64 intrinsic and is not provided by <intrin.h> on AArch64
inline uint64 _umul128(uint64 multiplier, uint64 multiplicand, uint64 *highProduct) {
unsigned __int128 x = (unsigned __int128)multiplier * (unsigned __int128)multiplicand;
*highProduct = (uint64)(x >> 64);
return (uint64)(x & 0xFFFFFFFFFFFFFFFF);
}
#endif

template<typename T>
inline T GetBits(T value, uint32 index, uint32 numBits)
{
Expand Down Expand Up @@ -343,10 +352,10 @@ inline uint64 _udiv128(uint64 highDividend, uint64 lowDividend, uint64 divisor,

FORCE_INLINE int BSF(uint32 v) // returns index of first bit set, counting from LSB. If v is 0 then result is undefined
{
#if defined(_MSC_VER)
#if defined(__GNUC__) || defined(__clang__)
return __builtin_ctz(v); // clang-cl also defines _MSC_VER, so check this first
#elif defined(_MSC_VER) && (defined(_M_X64) || defined(_M_IX86))
return _tzcnt_u32(v); // TZCNT requires BMI1. But if not supported it will execute as BSF
#elif defined(__GNUC__) || defined(__clang__)
return __builtin_ctz(v);
#else
return std::countr_zero(v);
#endif
Expand Down Expand Up @@ -610,12 +619,14 @@ inline uint32 GetTitleIdLow(uint64 titleId)
return titleId & 0xFFFFFFFF;
}

#if defined(__GNUC__)
#define memcpy_dwords(__dest, __src, __numDwords) memcpy((__dest), (__src), (__numDwords) * sizeof(uint32))
#define memcpy_qwords(__dest, __src, __numQwords) memcpy((__dest), (__src), (__numQwords) * sizeof(uint64))
#else
// __movsd/__movsq are x86-only MSVC intrinsics; use memcpy everywhere else
// (clang-cl does not define __GNUC__, and AArch64 has no such intrinsics)
#if defined(_MSC_VER) && !defined(__clang__) && defined(ARCH_X86_64)
#define memcpy_dwords(__dest, __src, __numDwords) __movsd((unsigned long*)(__dest), (const unsigned long*)(__src), __numDwords)
#define memcpy_qwords(__dest, __src, __numQwords) __movsq((unsigned long long*)(__dest), (const unsigned long long*)(__src), __numQwords)
#else
#define memcpy_dwords(__dest, __src, __numDwords) memcpy((__dest), (__src), (__numDwords) * sizeof(uint32))
#define memcpy_qwords(__dest, __src, __numQwords) memcpy((__dest), (__src), (__numQwords) * sizeof(uint64))
#endif

// PPC context and memory functions
Expand Down
Loading