Julien Jorge's Personal Website

Porting a C++ game to Android, the case of Andy's Super Great Park

Sat Sep 7, 2013
en

A few months ago, when people asked if I would port Andy’s Super great Park (ASGP) to Android, I answered that I was reticent, especially due to the fact that the preferred language of the platform is Java (which is not the language of ASGP) but also because the NDK was advertised as “Not the best tool for Android, try not to use it!”.

To give a quick overview of the game, ASGP is made of 120K lines of C++ code and relies on several external libraries such as Boost, Claw, SDL, OpenGL, FreeType and Gettext. Also, OpenGL is used in direct mode, which is not available in the GLES version. Clearly, I did not want to spent time trying to bring everything to Android. Also, I did not have any Android device.

Then, during August 2013, I decided to jump into the present (by the time your read this, it should be known as “the past”) by buying an Android phone, when suddenly the SDL 2.0 was released with official Android support. Hey! Why not spend a day or two trying to run the game on the phone before deciding to do a complete port or not?

So, I started to look at the NDK, I read SDL’s Migration guide and Phil Hassey’s blog (Phil, I love your banners by the way), and I started to work. Let me tell you how it went…

I thought I could relate the story in a few paragraphs, and I was wrong. So I will split it according to the various subjects. For the most general parts, I will describe the installation of the dependencies, tell how to replace OpenGL direct mode with GLES and how to move from SDL 1.2 to SDL 2. Then I will explain some game specific changes, like how I plugged Android’s log system into the game’s one, how I changed the access to the game’s resources and its configuration files, and how the fingers have replaced the mouse. Finally, I will also describe some optimizations I had to do to make the game run well on my phone.

You’re reading the Part I: Installing the dependencies.

First things first: setting the tools up

Two things are needed to set up the build environment: the Android’s NDK and SDK. The ADT bundle (Eclipse + SDK) is totally optional and will not be mentioned again.

In my $HOME/local directory, I extract the NDK and the SDK:

julien@pc-julien:local$ ls -l
drwxr-xr-x 10 julien julien     4096 juil. 11 20:00 android-ndk-r9
drwxr-x--- 13 julien julien     4096 août  24 19:43 android-sdk-linux

Then I run make-standalone-toolchain.sh, from the NDK, to create a standalone toolchain using gcc 4.8 and targeting Android 4.3 (the platform is “android-18”, not related to DragonBall).

make-standalone-toolchain.sh --platform=android-18 \
    --toolchain=arm-linux-androideabi-4.8

This builds a file located at /tmp/arm-linux-androideabi-4.8.tar.bz2, which I extract in $HOME/local before creating a symlink to it for convenience:

julien@pc-julien:local$ ls -l
drwxr-xr-x 10 julien julien     4096 juil. 11 20:00 android-ndk-r9
drwxr-x--- 13 julien julien     4096 août  24 19:43 android-sdk-linux
lrwxrwxrwx  1 julien julien       44 août  25 11:20 android-toolchain -> /home/julien/local/arm-linux-androideabi-4.8
drwxrwxr-x 11 julien julien     4096 août  25 11:20 arm-linux-androideabi-4.8

The standalone toolchain contains some tools and some kind of system tree where the development headers and libraries are located. This system tree is in the “sysroot” subdirectory and behaves just like the root of your system.

Finally, let’s update the shell to find the tools more easily. In $HOME/.android_dev:

export ANDROID_SDK=$HOME/local/android-sdk-linux
export ANDROID_NDK=$HOME/local/android-ndk-r9
export ANDROID_TOOLCHAIN_ROOT=$HOME/local/android-toolchain/

PATH="$PATH:$ANDROID_SDK/platform-tools:$ANDROID_SDK/tools:$ANDROID_NDK"

export ANDROID_ABI=armeabi-v7a
export ANDROID_CC="$ANDROID_TOOLCHAIN_ROOT/bin/arm-linux-androideabi-gcc"
export ANDROID_CXX="$ANDROID_TOOLCHAIN_ROOT/bin/arm-linux-androideabi-g++"
export ANDROID_AR="$ANDROID_TOOLCHAIN_ROOT/bin/arm-linux-androideabi-ar"

export NDK_TOOLCHAIN_VERSION=4.8

And in $HOME/.bashrc:

. $HOME/.android_dev

OK. Now I have the tools, I can build the libraries.

Compiling Boost for Android

Boost is a major part of the system. There is already some work done to port it to Android, for example in the Git repository of MysticTreeGame, but it seems not to work with a recent NDK. In my case, I found help in an article of Code Xperiments where the author explains how to build Boost 1.46 for Android. Unfortunately I could not build this version of Boost but after several tries I finally managed to have a compiled version of Boost 1.49. Here are the step to follow:

Extract the source code archive of Boost. I will call $BOOST_DIR the extracted directory. Then open $BOOST_DIR/tools/build/v2/user-config.jam and add the following lines at the end:

modules.poke : NO_BZIP2 : 1 ;

# /!\ Update this with the path to your standalone toolchain /!\
ANDROID_TOOLCHAIN=/home/julien/local/android-toolchain

using gcc : android4.4.3 :
$(ANDROID_TOOLCHAIN)/bin/arm-linux-androideabi-g++ :
<compileflags>--sysroot=$(ANDROID_TOOLCHAIN)/sysroot/
<compileflags>-mthumb
<compileflags>-fno-strict-aliasing
<compileflags>-O3
<compileflags>-DNDEBUG
<compileflags>-lstdc++
<compileflags>-D__GLIBC__
<compileflags>-DBOOST_NO_INTRINSIC_WCHAR_T
<archiver>$(ANDROID_TOOLCHAIN)/bin/arm-linux-androideabi-ar
<ranlib>$(ANDROID_TOOLCHAIN)/bin/arm-linux-androideabi-ranlib

Read this Trac ticket and apply the following changes in $BOOST_DIR/libs/filesystem/v2/src/v2_operations.cpp and $BOOST_DIR/libs/filesystem/v3/src/operations.cpp (search for sys/statvfs.h):

 # else // BOOST_POSIX_API
 #   include <sys/types.h>
-#   if !defined(__APPLE__) && !defined(__OpenBSD__)
+#   if !defined(__APPLE__) && !defined(__OpenBSD__) && !defined( __ANDROID__ )
 #     include <sys/statvfs.h>
 #     define BOOST_STATVFS statvfs
 #     define BOOST_STATVFS_F_FRSIZE vfs.f_frsize
 #   else
+#ifdef __ANDROID__
+#     include <sys/vfs.h>
+#endif
 #ifdef __OpenBSD__
 #     include <sys/param.h>
 #endif

Read this patch from MysticTreeGame and apply the following changes to $BOOST_DIR/include/boost/detail/endian.hpp (it’s near the end of the file):

    || defined(__amd64__) || defined(_M_AMD64) \
    || defined(__x86_64) || defined(__x86_64__) \
    || defined(_M_X64) || defined(__bfin__)
-
+   || defined( __ANDROID__ )
 # define BOOST_LITTLE_ENDIAN
 # define BOOST_BYTE_ORDER 1234
 #else

Compile the library:

bjam --without-python --without-serialization toolset=gcc-android4.4.3 \
    link=static runtime-link=static target-os=linux --stagedir=android

and install:

bjam install --prefix=$ANDROID_TOOLCHAIN_ROOT/sysroot/usr

Well done! It was the most difficult library!

Compiling Gettext on Android

Unfortunately I could not build Gettext for Android. Instead I use libintl-lite which provides a subset of Gettext. Before compiling the library I had to apply the following changes:

in libintl.h, add the following function signatures:

LIBINTL_LITE_API libintl_lite_bool_t
  bindtextdomain(const char* domain, const char* moFilePath);
LIBINTL_LITE_API libintl_lite_bool_t
  bind_textdomain_codeset(const char* domain, const char* moFilePath);

Implement these functions in internal/libintl.cpp:

libintl_lite_bool_t bindtextdomain(const char* domain, const char* moFilePath)
{
  loadMessageCatalog( domain, moFilePath );
}

libintl_lite_bool_t
bind_textdomain_codeset(const char* domain, const char* oFilePath)
{
  // not implemented yet
}

Compile and install the library:

cd internal

$ANDROID_CXX -O3 -c libintl.cpp -o libintl.o
$ANDROID_AR rs ../libintl.a libintl.o

cd ..

INSTALL_PREFIX=$ANDROID_TOOLCHAIN_ROOT/sysroot/usr/

cp libintl.a $INSTALL_PREFIX/lib
cp libintl.h $INSTALL_PREFIX/include

Compiling libjpeg for Android

This one was easy, I just had to follow this answer on StackOverflow.

Get the source code from the Git repository of Benjamin Gaignard:

git clone git://git.linaro.org/people/tomgall/libjpeg-turbo/libjpeg-turbo.git \
    -b linaro-android

Edit file Android.mk to add the following lines just after LOCAL_MODULE != libjpeg:

ifeq ($(notdir $(MAKECMDGOALS)),libjpeg.a)
  LOCAL_SRC_FILES +=  $(libsimd_SOURCES_DIST)
  include $(BUILD_STATIC_LIBRARY)
  include $(CLEAR_VARS)
  LOCAL_MODULE := dummy
endif

Compile the library:

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk \
    APP_PLATFORM=android-18 obj/local/armeabi/libjpeg.a

Install the library:

cp jconfig.h jerror.h jmorecfg.h jpegint.h jpeglib.h turbojpeg.h \
    $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/include/
cp obj/local/armeabi/libjpeg.a \
    $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/lib/

Compiling libpng for Android

Another easy one. Great!

Get the source from julienr’s repository on GitHub

git clone images/libpng-android.git

Compile the library using the script included in the repository:

./build.sh

Install the library:

cp jni/png*.h $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/include/
cp obj/local/armeabi/libpng.a $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/lib/

Compiling FreeType for Android

For this one, the information was on Wikibooks. First of all, download the source code from FreeType’s website and extract it on your hard drive. Then configure, compile and install the library:

CXX=$ANDROID_CXX CC=$ANDROID_CC ./configure --host=arm-linux-androidabi \
    --prefix=$ANDROID_TOOLCHAIN_ROOT/sysroot/usr --without-zlib
make
make install
``

# Compiling Claw for Android

At this step I started to become quite confident. Download the source
code from [Claw's website](http://libclaw.sourceforge.net/) and Just
type the following command to configure, compile and install the
library:

```console
CXX=$ANDROID_CXX cmake . \
    -DCMAKE_INSTALL_PREFIX=$ANDROID_TOOLCHAIN_ROOT/sysroot/usr \
    -DCMAKE_BUILD_TYPE=release
make
make install

Compiling the SDL for Android

SDL 2 has official support for Android, thus the compilation is quite easy. Since I prefer to build the Android version of my game using static libraries instead of shared ones, I have edited Android.mk to replace the last line with:

include $(BUILD_STATIC_LIBRARY)

Then I compiled the library using ndk-build:

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk \
    APP_PLATFORM=android-18

And I installed the library:

mkdir $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/include/SDL/
cp include/* $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/include/SDL/
cp obj/local/armeabi/libSDL2.a $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/lib/

Compiling SDL Mixer for Android

After having downloaded the source code from SDL Mixer’s website and extracted it on my hard drive, the first changes I did were to disable the modules I do not need for my game, in Android.mk:

SUPPORT_MOD_MODPLUG := false
SUPPORT_MOD_MIKMOD := false
SUPPORT_MP3_SMPEG := false

Then I had to adjust the paths to the main SDL library:

LOCAL_C_INCLUDES := $(LOCAL_PATH) $(ANDROID_TOOLCHAIN_ROOT)/sysroot/usr/include/SDL/
LOCAL_LDLIBS := $(ANDROID_TOOLCHAIN_ROOT)/sysroot/usr/lib/libSDL2.a
LOCAL_SHARED_LIBRARIES :=

Finally, I replaced some LOCAL_SHARED_LIBRARIES with LOCAL_STATIC_LIBRARIES in order to build a static library instead of a dynamic one:

LOCAL_STATIC_LIBRARIES += mikmod
LOCAL_STATIC_LIBRARIES += smpeg2
include $(BUILD_STATIC_LIBRARY)

Then I executed the now classical ndk-build command:

ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk \
   APP_PLATFORM=android-18

And I installed the library:

cp SDL_mixer.h $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/include/SDL/
cp obj/local/armeabi/libSDL2_mixer.a $ANDROID_TOOLCHAIN_ROOT/sysroot/usr/lib/

What’s next?

OK, now I have a compiled version of each library required to run the game. Thus I will just have to compile the game and link these libraries and it will run on my phone. Almost… The engine of the game uses SDL 1.2 and, as stated before, OpenGL in direct mode. Consequently I will have to replace the calls to these libraries with equivalents in SDL2 and GLES respectively. But this is another story, which I will tell later…