Cross Compile MAME for Raspberry Pi

Last updated May 2021

Compiling MAME on Raspberry Pi is quite slow. Particularly for 64bit (aarch64) builds with GCC10, the RAM requirements extend past the most recent RPi4's 8GB when compiling on multiple processes using the -j4 flag (around 2-3GB per process, so 12-15GB required for make -j4), and compiling with just -j1 is VERY slow.

Cross-compiling allows compilation on a faster x86_64 machine, which will build binaries for 32bit RPi 2/3/4 (armhf) or 64bit RPi3/4 (aarch64).

These steps are lengthy, complex, and will very likely break in mere months after I've written this guide. But here's what worked for me around May 2021.

My Intel Core i5 4690, Turbo Boost to 3.7GHz, 32GB RAM, compiles MAME 0.230 for 32bit armhf via GCC8 in 67 minutes.

Assumptions, software, caveats

  • My x86_64 build machine is a 4 core i5 running Ubuntu Linux 20.04 LTS "focal".
  • My Raspberry Pis vary. I tend to test a model 3B+ and a model 4B, and use either Raspbian 10 Buster or Raspbian 11 Bullseye, and jump between 32bit and 64bit.
  • This guide currently targets Raspberry Pi 3 and 4, 32bit, running Raspbian Buster (based on Debian 10 Buster). This is the same software running on the current RetroPie build for RPi model 2 and up.
  • For GCC8 32bit, aim for about 2GB RAM per make job, peak.
  • For GCC10 64bit, aim for about 2.5GB RAM per make job, peak.
  • You can potentially adapt this guide to numerous other platforms, but I'm not going to cover them here.

Host machine pre-requisites

  • Even while cross-compiling, the build system appears to want to have all the pre-requisites for building MAME locally.
  • On Ubuntu this is nice and easy to configure:
    • Edit your /etc/apt/sources.list file
    • For each line that starts with deb, copy that line and make the word at the beginning deb-src
    • Save your new sources.list
    • Run
      sudo apt-get update
      sudo apt-get build-dep -y mame
  • This will install everything necessary to build the version of MAME bundled with Ubuntu 20.04LTS. While that's quite a bit older than what we're building in this guide, the compilers, headers and libraries are all still relevant.
  • If you're absolutely stuck, here's a minimal example /etc/apt/sources.list for Ubuntu 20.04 LTS "Focal Fossa". I recommend using mirrors closer to your physical location (substite archive.ubuntu.com for us.archive.ubuntu.com, au.archive.ubuntu.com, uk.archive.ubuntu.com, de.archive.ubuntu.com, etc).
    # Binary packages
    deb http://archive.ubuntu.com/ubuntu focal main restricted universe multiverse
    deb http://archive.ubuntu.com/ubuntu focal-updates main restricted universe multiverse
    deb http://archive.ubuntu.com/ubuntu focal-security main restricted universe multiverse
    # Source packages
    deb-src http://archive.ubuntu.com/ubuntu focal main restricted universe multiverse
    deb-src http://archive.ubuntu.com/ubuntu focal-updates main restricted universe multiverse
    deb-src http://archive.ubuntu.com/ubuntu focal-security main restricted universe multiverse

crosstool-ng

  • crosstool-ng builds cross compilation environments. So we have to build the tool that builds the tools that builds MAME. Fun times ahead.
  • Grab a copy, build and install it
    mkdir ~/src
    cd ~/src
    git clone https://github.com/crosstool-ng/crosstool-ng
    cd crosstool-ng
    ./bootstrap
    ./configure --prefix=${HOME}/ct-ng
    make -j4
    make install
  • The tool is now installed. Time to build an environment. First, set up your path
    export PATH=${HOME}/ct-ng/bin:$PATH
  • Choose an environment to build
    mkdir ~/ctng_rpi_armhf
    cd ~/ctng_rpi_armhf
    ct-ng list-samples
  • You'll see a whole bunch of environments to choose from. When I wrote this guide, for a RPi with a 32bit OS, the best match was armv8-rpi3-linux-gnueabihf. You can see what that will install with the following:
    ct-ng show-armv8-rpi3-linux-gnueabihf
  • For RPi with a 64bit OS, aarch64-rpi3-linux-gnu is environment needed. This guide will continue on with the 32bit however, so just substitute as needed for your environment.
  • Now let's copy that 32bit profile and prepare it.
    ct-ng armv8-rpi3-linux-gnueabihf
  • This will create a file named .config (note the period in front, you'll need to ls -la to see this). You can see the versions of things it will install like so:
    $ ct-ng show-config
    [l...]   armv8-rpi3-linux-gnueabihf
    Languages       : C,C++
    OS              : linux-5.11.6
    Binutils        : binutils-2.36.1
    Compiler        : gcc-10.3.0
    C library       : glibc-2.33
    Debug tools     : gdb-9.2
    Companion libs  : expat-2.3.0 gettext-0.20.1 gmp-6.2.1 isl-0.22 libiconv-1.16 mpc-1.2.0 mpfr-4.1.0 ncurses-6.2 zlib-1.2.11
    Companion tools :
  • If you want to change these versions, edit them in .config. For example, I changed the OS to linux-5.10.17 to match my RPi (run uname -r on the RPi to find out your running kernel). Likewise I changed gcc to 8.3.0, glibc to 2.28, binutils to 2.31.1. This is accurate to the date at the top of this page. You can find versions of what you need by looking at package lists online, for example
  • Debian Buster
  • Debian Bullseye
  • Inside the .config file, change the following variables to the versions required. For example:
    CT_LINUX_VERSION="5.10.17"
    CT_GLIBC_MIN_KERNEL="5.10.17"
    CT_BINUTILS_VERSION="2.31.1"
    CT_GCC_VERSION="8.3.0"
    CT_GLIBC_VERSION="2.28"
  • Once modified, re-check:
    $ ct-ng show-config
    [l...]   armv8-rpi3-linux-gnueabihf
    Languages       : C,C++
    OS              : linux-5.10.17
    Binutils        : binutils-2.31.1
    Compiler        : gcc-8.3.0
    C library       : glibc-2.28
    Debug tools     : gdb-9.2
    Companion libs  : expat-2.3.0 gettext-0.20.1 gmp-6.2.1 isl-0.22 libiconv-1.16 mpc-1.2.0 mpfr-4.1.0 ncurses-6.2 zlib-1.2.11
    Companion tools :
  • Now build the lot.
    ct-ng build
  • You'll see a considerable amount of output as ct-ng builds the entire cross-compile environment. With any luck, you'll end up with these tools installed to ~/x-tools/armv8-rpi3-linux-gnueabihf, which we'll use to build MAME next.

  • Download the config files (you'll need to rename them to .config):

ARM-Linux Header files and libraries

  • The build process will need a number of header files and libraries, and the easiest way is to grab these directly from your Raspberry Pi itself. Bring up a shell on the Pi, and run
    sudo apt-get clean
    sudo apt-get update
    mkdir -p ~/armhf/debs
    cd ~/armhf/debs
    apt-get download -y \
    apulse libasound2 libasound2-dev libasyncns0 libbrotli1 libbsd0 libcap2 \
    libcom-err2 libdbus-1-3 libdouble-conversion1 libdrm2 libexpat1 libffi6 \
    libflac8 libfontconfig1 libfontconfig1-dev libfreetype6 libfreetype6-dev \
    libgbm1 libgbm-dev libgcrypt20 libgl1 libgl-dev libgles1 libgles2 libgles2-mesa \
    libglew2.1 libglib2.0-0 libglib2.0-dev libglu1-mesa libglvnd0 libglx0 \
    libglx-dev libglx-mesa0 libgpg-error0 libgraphite2-3 libgssapi-krb5-2 \
    libharfbuzz0b libice6 libicu63 libk5crypto3 libkeyutils1 libkrb5-3 \
    libkrb5support0 liblz4-1 liblzma5 libmd0 libogg0 libopus0 libpcre2-16-0 \
    libpcre3 libpng16-16 libpulse0 libqt5core5a libqt5gui5 libqt5widgets5 \
    libraspberrypi0 libraspberrypi-bin libraspberrypi-dev libsdl1.2debian \
    libsdl2-2.0-0 libsdl2-dev libsdl2-ttf-2.0-0 libsdl2-ttf-dev libsdl-image1.2 \
    libsdl-mixer1.2 libsdl-ttf2.0-0 libsm6 libsndfile1 libsndio7.0 libsystemd0 \
    libtirpc3 libuuid1 libvorbis0a libvorbisenc2 libwayland-client0 \
    libwayland-cursor0 libwayland-egl1 libwayland-server0 libwrap0 libx11-6 \
    libx11-data libx11-dev libx11-xcb1 libx264-155 libx265-165 libxau6 libxau-dev \
    libxaw7 libxcb1 libxcb1-dev libxcb-composite0 libxcb-dri2-0 libxcb-dri3-0 \
    libxcb-glx0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-present0 \
    libxcb-randr0 libxcb-render0 libxcb-render-util0 libxcb-shape0 libxcb-shm0 \
    libxcb-sync1 libxcb-util0 libxcb-xfixes0 libxcb-xinerama0 libxcb-xinput0 \
    libxcb-xkb1 libxcb-xv0 libxcomposite1 libxcursor1 libxcursor-dev libxdamage1 \
    libxdmcp6 libxdmcp-dev libxext6 libxext-dev libxfconf-0-2 libxfixes3 \
    libxfixes-dev libxfont2 libxft2 libxi6 libxi-dev libxinerama1 libxinerama-dev \
    libxkbcommon0 libxkbcommon-dev libxkbcommon-x11-0 libxkbfile1 libxklavier16 \
    libxml2 libxmu6 libxmuu1 libxpm4 libxrandr2 libxrandr-dev libxrender1 \
    libxrender-dev libxres1 libxshmfence1 libxslt1.1 libxss1 libxss-dev libxt6 \
    libxtables12 libxt-dev libxtst6 libxv1 libxv-dev libxvidcore4 libxxf86dga1 \
    libxxf86vm1 libxxf86vm-dev libxxhash0 libzadc4 libzstd1 qtbase5-dev \
    x11proto-dev zlib1g libpulse-dev
  • This will download a number of deb files (Debian package files), most of which will have armhf or aarch64 in the name, depending on whether you're building 32bit or 64bit.
  • Download these scripts (chmmod +x to make executable and run on your Pi):
  • Copy these files to your x86_64 machine. If you have SSH set up, rsync is convenient. Run from the Pi (substitute username and buildmachine for your details):
    rsync -avrhP ~/armhf username@buildmachine:~/
  • On the x86_64 build machine, extract the deb files:
    cd ~/armhf
    ls -1d debs/*.deb | while read DEB ; do echo "${DEB}" ; dpkg -x "${DEB}" . ; done
  • This will extract the contents of each deb file into the ~/armhf directory, and create a folder structure we can use later during the compile.

MAME compile

  • Grab the MAME source code. I'm going to build 0.230 here (since writing this, I've compiled backwards from 0.230 to 0.209, and only 2 builds have failed out of the 21, where it appeared the "NOASM=1" flag was ignored in both cases). Whether this guide works on much older versions, or much newer versions, I have no idea.

    mkdir ~/src
    cd ~/src
    git clone https://github.com/mamedev/mame.git
    cd mame
    git checkout mame0230
  • Next, let's make a script to do the compile. Let's create a file named ~/mame_compile_armhf.sh (keep it out of your ~/src/mame directory), and it'll look like this:
#!/bin/bash
# force qt5
export QT_SELECT=5
# set paths
export MXTARCH=armv8-rpi3-linux-gnueabihf
export MXTOOLS=${HOME}/x-tools/${MXTARCH}
export MARCHDIR=${HOME}/armhf
export RPIARCH=arm-linux-gnueabihf
cd ~/src/mame
#make clean
make \
 CROSS_BUILD=1 \
 NOWERROR=1 \
 PLATFORM=arm \
 CFLAGS+="-I ${MARCHDIR}/usr/include" \
 CFLAGS+="-I ${MARCHDIR}/usr/include/${RPIARCH}" \
 CFLAGS+="-L ${MARCHDIR}/usr/lib" \
 CFLAGS+="-L ${MARCHDIR}/usr/lib/${RPIARCH}" \
 CPPFLAGS+="-I ${MARCHDIR}/usr/include" \
 CPPFLAGS+="-I ${MARCHDIR}/usr/include/${RPIARCH}" \
 CPPFLAGS+="-L ${MARCHDIR}/usr/lib" \
 CPPFLAGS+="-L ${MARCHDIR}/usr/lib/${RPIARCH}" \
 LDFLAGS+="-L ${MARCHDIR}/usr/lib" \
 LDFLAGS+="-L ${MARCHDIR}/usr/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/usr/lib" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/usr/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/lib" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/usr/lib/${RPIARCH}/pulseaudio" \
 ARCHOPTS+="-Wl,-R,${MARCHDIR}/opt/vc/lib" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/usr/lib" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/usr/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/lib" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/usr/lib/${RPIARCH}/pulseaudio" \
 ARCHOPTS+="-Wl,-rpath,${MARCHDIR}/opt/vc/lib" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/usr/lib" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/usr/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/lib" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/lib/${RPIARCH}" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/usr/lib/${RPIARCH}/pulseaudio" \
 ARCHOPTS+="-Wl,-rpath-link,${MARCHDIR}/opt/vc/lib" \
 TARGETOS=linux \
 NOASM=1 \
 OVERRIDE_CC="${MXTOOLS}/bin/${MXTARCH}-gcc" \
 OVERRIDE_LD="${MXTOOLS}/bin/${MXTARCH}-ld" \
 OVERRIDE_CXX="${MXTOOLS}/bin/${MXTARCH}-c++" \
 -j5
  • If you're building for 64bit, PLATFORM=arm becomes PLATFORM=arm64, and obviously all of the paths will point to different tools.
  • Set the -j (number of make jobs) flag to a sensible number.
    • Compiling via GCC8 for 32bit armhf takes a peak of 2GB RAM per make job
    • Compiling via GCC10 for 64bit aarch64 takes a peak of 2.5GB RAM per make job
    • If you have enough RAM, a sensible number is how many cores you have plus one.
  • chmod a+x the script.
  • Run the script with ~/mame_compile_armhf.sh
  • Download the compile script:
  • After some time, you should see a binary mame file appear in ~/src/mame
  • Verify the file is for the right architecture, for example 32bit RPi armhf:
    $ file mame
    mame: ELF 32-bit LSB executable, ARM, EABI5 version 1 (GNU/Linux), dynamically linked, 
    interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 5.10.17, with debug_info, not stripped
  • Or 64bit RPi aarch64:
    $ file mame
    mame: ELF 64-bit LSB executable, ARM aarch64, version 1 (GNU/Linux), dynamically linked,
    interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 5.10.17, with debug_info, not stripped
  • Copy it to your RPi and run it.
  • Good luck!