diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82d6f160f1..7ae9fdcc8f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -279,6 +279,13 @@ jobs: args: ./b.sh --loongarch64 PPSSPPHeadless id: loongarch64 + - os: ubuntu-26.04 + extra: riscv64 + cc: gcc + cxx: g++ + args: ./b.sh --riscv64 PPSSPPHeadless + id: riscv64 + - os: macos-latest extra: test cc: clang @@ -318,7 +325,7 @@ jobs: ndk-version: r29 - name: Install Linux dependencies - if: runner.os == 'Linux' && matrix.extra != 'android' && matrix.extra != 'loongarch64' + if: runner.os == 'Linux' && matrix.extra != 'android' && matrix.extra != 'loongarch64' && matrix.extra != 'riscv64' run: | sudo apt-get update -y -qq sudo apt-get install libsdl3-dev libgl1-mesa-dev libglu1-mesa-dev libsdl3-ttf-dev libfontconfig1-dev libcurl4-openssl-dev @@ -327,12 +334,12 @@ jobs: if: runner.os == 'macOS' && matrix.id == 'macos' run: brew install sdl3 sdl3_ttf - - name: Install loongarch64 cross-compilation dependencies - if: matrix.extra == 'loongarch64' + - name: Install cross-compilation dependencies + if: matrix.extra == 'loongarch64' || matrix.extra == 'riscv64' run: | sudo apt-get update -y -qq - sudo apt-get install -y gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu libgl-dev libglu1-mesa-dev - sudo cmake/scripts/setup-loongarch64-cross.sh + sudo apt-get install -y gcc-14-${{ matrix.extra }}-linux-gnu g++-14-${{ matrix.extra }}-linux-gnu binutils-${{ matrix.extra }}-linux-gnu libgl-dev libglu1-mesa-dev + sudo cmake/scripts/setup-cross.sh ${{ matrix.extra }} - name: Install iOS dependencies if: matrix.id == 'ios' diff --git a/CMakeLists.txt b/CMakeLists.txt index c81d415524..8a13d80564 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,8 @@ option(USE_VULKAN_DISPLAY_KHR "Enable or disable full screen display of Vulkan" # :: Frontends option(MOBILE_DEVICE "Set to ON when targeting a mobile device" ${MOBILE_DEVICE}) option(HEADLESS "Set to OFF to not generate the PPSSPPHeadless target" ${HEADLESS}) +# Set by b.sh for the loongarch64/riscv64 cross builds, whose sysroots have no SDL3. +option(HEADLESS_CROSS "Set to ON for a headless-only cross build (no SDL frontend)" ${HEADLESS_CROSS}) option(ATLAS_TOOL "Set to OFF to not generate the ATLAS_TOOL target" ${ATLAS_TOOL}) option(UNITTEST "Set to ON to generate the unittest target" ${UNITTEST}) option(SIMULATOR "Set to ON when targeting an x86 simulator of an ARM platform" ${SIMULATOR}) @@ -331,7 +333,7 @@ set(SDL_LIB_TARGET "") set(SDL_TTF_LIB_TARGET "") set(FONTCONFIG_LIB_TARGET "") -if(NOT LIBRETRO AND NOT IOS AND NOT LOONGARCH64_DEVICE AND NOT ANDROID) +if(NOT LIBRETRO AND NOT IOS AND NOT HEADLESS_CROSS AND NOT ANDROID) find_package(SDL3 QUIET) if(NOT SDL3_FOUND) if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND (EXISTS "/usr/bin/apt" OR EXISTS "/usr/bin/apt-get" OR EXISTS "/etc/debian_version")) @@ -1001,9 +1003,11 @@ elseif(WIN32) # Don't care about SDL. set(TargetBin PPSSPPWindows) elseif(LIBRETRO) -elseif(LOONGARCH64_DEVICE) - # Cross-compiling for loongarch64 currently only builds PPSSPPHeadless, and there's - # no SDL3 available for that target, so skip the desktop SDL frontend entirely. +elseif(HEADLESS_CROSS) + # The loongarch64/riscv64 cross builds only build PPSSPPHeadless, and there's no + # SDL3 available in those sysroots, so skip the desktop SDL frontend entirely. + # Note this is deliberately not keyed off the target architecture: a native build + # on such a machine can have SDL3 and should still get the normal frontend. else() if(GOLD) set(TargetBin PPSSPPGold) diff --git a/b.sh b/b.sh index 2cae01608b..ff80e9bdd1 100755 --- a/b.sh +++ b/b.sh @@ -31,9 +31,14 @@ do CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/raspberry.armv8.cmake ${CMAKE_ARGS}" ;; --loongarch64) - CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/loongarch64-linux-gnu.cmake -DHEADLESS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}" + CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/loongarch64-linux-gnu.cmake -DHEADLESS=ON -DHEADLESS_CROSS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}" TARGET_OS=loongarch64 - LOONGARCH64_BUILD=1 + CROSS_STUB_CC=loongarch64-linux-gnu-gcc-14 + ;; + --riscv64) + CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/riscv64-linux-gnu.cmake -DHEADLESS=ON -DHEADLESS_CROSS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}" + TARGET_OS=riscv64 + CROSS_STUB_CC=riscv64-linux-gnu-gcc-14 ;; --android) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=android/android.toolchain.cmake ${CMAKE_ARGS}" TARGET_OS=Android @@ -122,16 +127,16 @@ echo Building with $CORES_COUNT threads mkdir -p ${BUILD_DIR} -# For loongarch64 cross-compilation, build a comprehensive GL/GLX stub into +# For the headless cross targets, build a comprehensive GL/GLX stub into # /stublibs/libGL.so so GLEW's static archive can resolve its symbols -# via PLT entries (a direct B26 branch to address 0 overflows on LoongArch). +# via PLT entries (a direct branch to address 0 overflows the relocation). # This stub is always (re)generated to pick up any new needed symbols. -if [ ! -z "$LOONGARCH64_BUILD" ]; then +if [ ! -z "$CROSS_STUB_CC" ]; then STUB_DIR=${BUILD_DIR}/stublibs STUB_GL=${STUB_DIR}/libGL.so mkdir -p "${STUB_DIR}" STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c) - echo "/* Loongarch64 GL/GLX stub - cross-compilation only */" > "$STUB_C" + echo "/* ${TARGET_OS} GL/GLX stub - cross-compilation only */" > "$STUB_C" # Collect all T (exported) symbols from GL/GLX libs, deduplicate, emit stubs HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null) { @@ -144,7 +149,7 @@ if [ ! -z "$LOONGARCH64_BUILD" ]; then printf '%s\n' glXGetProcAddressARB glXGetClientString glXQueryVersion \ glBindTexture glGetString glGetIntegerv } | sort -u | awk '{ print "void "$1"(void){}" }' >> "$STUB_C" - loongarch64-linux-gnu-gcc-14 -shared -fPIC -Wno-implicit-function-declaration \ + $CROSS_STUB_CC -shared -fPIC -Wno-implicit-function-declaration \ -o "${STUB_GL}" "$STUB_C" rm "$STUB_C" fi diff --git a/cmake/Toolchains/loongarch64-linux-gnu.cmake b/cmake/Toolchains/loongarch64-linux-gnu.cmake index b39ed83905..014630439c 100644 --- a/cmake/Toolchains/loongarch64-linux-gnu.cmake +++ b/cmake/Toolchains/loongarch64-linux-gnu.cmake @@ -17,8 +17,8 @@ set(USING_X11_VULKAN OFF CACHE BOOL "" FORCE) # Use legacy GL preference so find_package(OpenGL) looks for a single libGL.so # rather than the GLVND split (libOpenGL.so + libGLX.so), which we don't have. # A stub libGL.so + GL headers must be present in the sysroot; they are placed -# there by cmake/scripts/setup-loongarch64-cross.sh (run once with sudo, or -# automatically by the CI install step). +# there by cmake/scripts/setup-cross.sh (run once with sudo, or automatically +# by the CI install step). set(OpenGL_GL_PREFERENCE LEGACY CACHE STRING "" FORCE) # b.sh also creates a stub in /stublibs/ as a local fallback so the @@ -27,14 +27,18 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/stublibs" CACHE STRING "" FORCE) -# Allow CMake's compile-check programs to run via QEMU. -# The /lib64 symlink is created by the setup script; without it, pass -L -# explicitly so QEMU can find the loongarch64 dynamic linker. -if(EXISTS "/lib64/ld-linux-loongarch-lp64d.so.1") - set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-loongarch64-static" - CACHE STRING "" FORCE) -else() - set(CMAKE_CROSSCOMPILING_EMULATOR - "qemu-loongarch64-static;-L;/usr/loongarch64-linux-gnu" - CACHE STRING "" FORCE) +# Allow CMake's compile-check programs to run via QEMU, if it's installed. +# Debian/Ubuntu ship the static binaries in qemu-user-static and the dynamic +# ones in qemu-user; either will do, so take whichever is present. +find_program(QEMU_LOONGARCH64 NAMES qemu-loongarch64-static qemu-loongarch64) +if(QEMU_LOONGARCH64) + # The /lib64 symlink is created by the setup script; without it, pass -L + # explicitly so QEMU can find the loongarch64 dynamic linker. + if(EXISTS "/lib64/ld-linux-loongarch-lp64d.so.1") + set(CMAKE_CROSSCOMPILING_EMULATOR "${QEMU_LOONGARCH64}" CACHE STRING "" FORCE) + else() + set(CMAKE_CROSSCOMPILING_EMULATOR + "${QEMU_LOONGARCH64};-L;/usr/loongarch64-linux-gnu" + CACHE STRING "" FORCE) + endif() endif() diff --git a/cmake/Toolchains/riscv64-linux-gnu.cmake b/cmake/Toolchains/riscv64-linux-gnu.cmake new file mode 100644 index 0000000000..a80150c6e8 --- /dev/null +++ b/cmake/Toolchains/riscv64-linux-gnu.cmake @@ -0,0 +1,44 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR riscv64) + +set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc-14) +set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++-14) +set(CMAKE_ASM_COMPILER riscv64-linux-gnu-gcc-14) + +set(CMAKE_FIND_ROOT_PATH /usr/riscv64-linux-gnu) +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +# No X11 in the sysroot; disable the X11 Vulkan path to avoid include-dir errors. +set(USING_X11_VULKAN OFF CACHE BOOL "" FORCE) + +# Use legacy GL preference so find_package(OpenGL) looks for a single libGL.so +# rather than the GLVND split (libOpenGL.so + libGLX.so), which we don't have. +# A stub libGL.so + GL headers must be present in the sysroot; they are placed +# there by cmake/scripts/setup-cross.sh (run once with sudo, or automatically +# by the CI install step). +set(OpenGL_GL_PREFERENCE LEGACY CACHE STRING "" FORCE) + +# b.sh also creates a stub in /stublibs/ as a local fallback so the +# linker can resolve GL/GLX calls from GLEW's static archive via PLT entries. +set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/stublibs" + CACHE STRING "" FORCE) + +# Allow CMake's compile-check programs to run via QEMU, if it's installed. +# Debian/Ubuntu ship the static binaries in qemu-user-static and the dynamic +# ones in qemu-user; either will do, so take whichever is present. +find_program(QEMU_RISCV64 NAMES qemu-riscv64-static qemu-riscv64) +if(QEMU_RISCV64) + # The /lib64 symlink is created by the setup script; without it, pass -L + # explicitly so QEMU can find the riscv64 dynamic linker. + if(EXISTS "/lib64/ld-linux-riscv64-lp64d.so.1") + set(CMAKE_CROSSCOMPILING_EMULATOR "${QEMU_RISCV64}" CACHE STRING "" FORCE) + else() + set(CMAKE_CROSSCOMPILING_EMULATOR + "${QEMU_RISCV64};-L;/usr/riscv64-linux-gnu" + CACHE STRING "" FORCE) + endif() +endif() diff --git a/cmake/scripts/setup-loongarch64-cross.sh b/cmake/scripts/setup-cross.sh similarity index 63% rename from cmake/scripts/setup-loongarch64-cross.sh rename to cmake/scripts/setup-cross.sh index e6a6323dd6..ea9b8df86b 100755 --- a/cmake/scripts/setup-loongarch64-cross.sh +++ b/cmake/scripts/setup-cross.sh @@ -1,15 +1,34 @@ #!/bin/bash -# Sets up the loongarch64 cross-compilation environment. -# Run once with sudo. After this, ./b.sh --loongarch64 works on a fresh checkout. +# Sets up a cross-compilation environment for one of the headless-only targets. +# Run once with sudo. After this, ./b.sh -- works on a fresh checkout. +# +# Usage: sudo cmake/scripts/setup-cross.sh set -e -SYSROOT=/usr/loongarch64-linux-gnu -CROSS_GCC=loongarch64-linux-gnu-gcc-14 +TARGET="$1" + +case "$TARGET" in + loongarch64) + TRIPLE=loongarch64-linux-gnu + LD_SO=ld-linux-loongarch-lp64d.so.1 + ;; + riscv64) + TRIPLE=riscv64-linux-gnu + LD_SO=ld-linux-riscv64-lp64d.so.1 + ;; + *) + echo "Usage: sudo $0 " + exit 1 + ;; +esac + +SYSROOT=/usr/$TRIPLE +CROSS_GCC=$TRIPLE-gcc-14 if ! command -v $CROSS_GCC &>/dev/null; then echo "Error: $CROSS_GCC not found. Install it first:" - echo " sudo apt install gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu" + echo " sudo apt install gcc-14-$TRIPLE g++-14-$TRIPLE binutils-$TRIPLE" exit 1 fi @@ -18,6 +37,8 @@ if [ "$(id -u)" -ne 0 ]; then exit 1 fi +HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null) + # ── OpenGL headers ─────────────────────────────────────────────────────────── # GL headers are platform-independent C headers; copy from the host. echo "Installing GL headers into sysroot..." @@ -27,13 +48,11 @@ for h in gl.h glext.h glcorearb.h glu.h; do done # ── GL stub library ────────────────────────────────────────────────────────── -# Generate a loongarch64 libGL.so that exports all symbols from the host +# Generate a libGL.so for the target that exports all symbols from the host # libGL so that GLEW's static archive can be linked via PLT entries (a direct -# B26 branch to address 0 would overflow on LoongArch). +# branch to address 0 would overflow the relocation on these targets). echo "Generating libGL.so stub..." -HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null) - HOST_GL="" for candidate in \ /usr/lib/$HOST_MULTIARCH/libGL.so.1 \ @@ -43,8 +62,8 @@ for candidate in \ done STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c) -echo "/* Loongarch64 GL/GLX stub - for cross-compilation only */" > "$STUB_C" -echo "void __loongarch64_gl_placeholder(void) {}" >> "$STUB_C" +echo "/* $TARGET GL/GLX stub - for cross-compilation only */" > "$STUB_C" +echo "void __cross_gl_placeholder(void) {}" >> "$STUB_C" if [ -n "$HOST_GL" ]; then nm -D "$HOST_GL" 2>/dev/null | awk '/^[0-9a-f]+ T /{ print "void "$3"(void){}" }' >> "$STUB_C" @@ -62,16 +81,16 @@ rm "$STUB_C" echo " Installed to $SYSROOT/lib/libGL.so" # ── Dynamic linker symlink ─────────────────────────────────────────────────── -# Without this, binfmt_misc can't find the loongarch64 dynamic linker. -LD_LINUX=$SYSROOT/lib/ld-linux-loongarch-lp64d.so.1 +# Without this, binfmt_misc can't find the target's dynamic linker. +LD_LINUX=$SYSROOT/lib/$LD_SO if [ -f "$LD_LINUX" ]; then mkdir -p /lib64 - ln -sf "$LD_LINUX" /lib64/ld-linux-loongarch-lp64d.so.1 - echo "Created /lib64/ld-linux-loongarch-lp64d.so.1 -> $LD_LINUX" + ln -sf "$LD_LINUX" /lib64/$LD_SO + echo "Created /lib64/$LD_SO -> $LD_LINUX" fi echo "" echo "Setup complete." -echo "Build: ./b.sh --loongarch64" -echo "Run: qemu-loongarch64-static -L $SYSROOT " -echo " (after setup the /lib64 symlink lets you run loongarch64 binaries directly)" +echo "Build: ./b.sh --$TARGET" +echo "Run: qemu-$TARGET -L $SYSROOT " +echo " (after setup the /lib64 symlink lets you run $TARGET binaries directly)"