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…