Version 196 (modified by Anders Feder, 12 years ago) ( diff )

--

This page documents how 0 A.D. might be ported to the Android platform.

Plan

  1. Set up a generic Android NDK build environment.
  2. Find and adapt replacement libraries for every dependency in the PC edition.
  3. Port graphics and sound to OpenGL ES and OpenSL ES, respectively.
  4. Come up with a suitable UI for mobile devices.
  5. Implement the new UI on Android.
  6. Build the package with Android NDK and SDK.
  7. Publish the package on Android Market.
  8. Rock.

The port is very much a work-in-progress. If you are technically inclined and want to help out, follow the steps under the Implementation section below, read the error output generated by the compiler, and suggest solutions here or on the forum.

Target devices

Samsung Galaxy Nexus

  • 720×1280 px at 316 ppi
  • !16:9 aspect-ratio
  • 16M colors
  • Multi-touch, capacitive touchscreen
  • 1.2 GHz TI OMAP 4460 ARM Cortex-A9 dual-core CPU
  • 307 MHz PowerVR SGX540 GPU
  • 1 GB RAM

Samsung Galaxy S II

  • 800×480 px at 218 ppi
  • Multi-touch, capacitive touchscreen
  • 1.2 GHz Samsung Exynos ARM Cortex-A9 dual-core CPU
  • ARM Mali-400 MP GPU
  • 1 GB RAM

Design

User interface

Due to the vastly different controls and form factor, the whole user interface of the game needs to be rethought.

In the following, each control primitive is summarized as a bullet point.

We will use the following terminology for basic gestures:

A swipe is a single-point touch motion.

A pinch is a two-point touch motion.

Camera

Camera panning is performed by applying inverse swipe gestures on the screen:

  • Swipe down - pan camera forward.
  • Swipe up - pan camera backwards.
  • Swipe left - pan camera right.
  • Swipe right - pan camera left.

The further from the center of the screen the swiping motion begins, the faster the panning will be.

Camera rotation is performed by applying a circular pinch gesture on the screen:

  • Pinch clockwise - rotate camera counter-clockwise.
  • Pinch counter-clockwise - rotate camera clockwise.

When the circular gesture is complete, the camera shall have completed a full rotation.

Camera zoom is performed by applying a pinching gesture on the screen:

  • Pinch in - zoom out.
  • Pinch out - zoom in.

The camera may also be panned by touching near the edge of the screen:

  • Touch near edge of screen - pan camera in the direction of the given edge.

The camera may also be panned by touching a position on the minimap:

  • Touch position on minimap - pan camera to the corresponding position on the map.

Entity selection

If an entity is tapped once, the entity is immediately selected.

  • Single-tap entity - immediately select entity.

If touch is applied to the game world for more than ~0,25s, a brief tactile feedback vibration is played, and the interface enters 'selective mode.'

  • Touch game world for more than ~0,25s - enter 'selective mode' and play brief tactile feedback vibration.

While in selective mode, a bounding box is drawn with one corner where the touch point was when the mode began ('start position') and the opposite corner where the touch point currently is ('end position'').

While in selective mode, the camera pans slowly in the direction from the start position to the end position. The further the touch point currently is from the point on the screen where it was when the mode began, the faster the camera pans.

When touch is released in selective mode, all units within the drawn bounding box are selected.

Unit orders

If a position in the game world is double tapped, the selected unit is ordered to apply its context-dependent primary action to the position.

  • Double-tap position in game world - apply selected unit's primary action to the position.

Input events

As far as I understand from the wiki, handlers for input events are specified in XML and JavaScript. So I am wondering if it would be a possibility to simply add a range of new events (e.g. TouchDown, TouchUp, etc.) for the engine to handle? Then the port could simply be supplied with a new set of XML and JavaScript files for the UI, but retain most everything else.

The game's input events originate from SDL, so the real question is does SDL support these Android-specific events? At least in the development branch there is an SDL_TouchFingerEvent that should work for this. You only need to add new events to the GUI XML schema if you're going to add new GUI object types, which might be reasonable on an Android port. I'm thinking it would be easiest to avoid that until absolutely necessary. Either way the GUI manager will need to be extended to capture these events and pass them onto scripts like the session input.js, which is where the most interesting event handling occurs (unit selection, building placement, etc.) Unfortunately the GUI engine code is a mess.

Check out HandleEvent in source\gui\CGUI.cpp. See also EGUIMessageType in GUIbase.h. In fact you should be familiar with most of source\gui. For the XML parsing you'd want the CGUI::Xeromyces_* methods at the bottom of CGUI.cpp. For camera movement, see the CGameView class in source\graphics\GameView.cpp.

Graphics

Android includes support for high performance 2D and 3D graphics with the Open Graphics Library (OpenGL), specifically, the OpenGL ES API. Since the game utilizes features of the full, standard OpenGL API, these portions of the code will have to be migrated to OpenGL ES.

I think the best approach is to extend graphics/ShaderProgram.cpp to support GLSL shaders (it was designed with that in mind, but I don't know if it'll actually work without some interface changes); then move all the renderer's existing fixed-function texture-environment setup code into ShaderProgramFFP.cpp and implement GLSL-based equivalents, so that most of the rest of the renderer code doesn't have to care whether it's using FFP or GLSL (it just uses the CShaderProgram interface). Also, change all immediate-mode drawing (glVertex3f etc) to vertex arrays. I think that should deal with the most serious problems, and the code would all be shared between GLES and desktop GL modes (no need for forking or #ifdefs etc) and can be tested with desktop GL. Then there's probably just lots of little issues remaining, which can be addressed as they occur.

See SLESPort.

Other dependencies

Android-compatible replacements must be found for all the dependencies of the PC edition. This is what we have so far:

  • GCC - The Android NDK provides its own toolchain for compiling.
  • Subversion - Assuming this is just for obtaining the source, this can be done on a PC workstation.
  • SDL - This is already ported to Android.
  • Boost - There is an unofficial port.
  • zlib - libz is part of the native NDK.
  • libpng - Compiles natively against the NDK.
  • libxml2 - Compiles natively against the NDK if iconv bindings are disabled.
  • OpenGL - This must be ported to OpenGL ES.
  • OpenAL - This should be ported to OpenSL ES.
  • zip - Not sure which exact library this refers to?
  • libogg - May be covered by Tremor (below).
  • libvorbis - This can be done with Tremor.
  • libcurl - Compiles natively against the NDK.
  • Gamin - This can be disabled at build time. The game will run fine without it.
  • CMake - The port can be integrated with the standard build system using the standalone NDK toolchain.

In addition to the external dependencies above, the following are bundled with the game:

  • NVTT - We don't need any of its fancy features like CUDA support or image-loading tools, just the basic CPU-only compression library, so it should be buildable with no significant external dependencies (though its build system might need fixing).
  • Spidermonkey - Presumably, Spidermonkey has been ported as part of Fennec. We can't use Android's native V8 engine since we rely on some complex API features, so porting would be a huge amount of work. SpiderMonkey isn't API-compatible or behaviour-compatible across versions, so it'd be best to use the same 1.8.5 release as we use on PCs if possible; I think the standard releases are meant to work on ARM, so that should be okay.
  • Enet - From a quick check, I see no dependencies outside of libc, so this should compile natively.
  • FCollada - We use a customised/bugfixed version of FCollada, so a port of the standard version of FCollada probably wouldn't work. Our own version has no significant dependencies other than libxml2.

Optimizations

The target device supports the ARM NEON SIMD instruction set. In turn, it does not support the x86 SSE SIMD instruction set. Hence, it would be relevant to 1) identify portions of the code that use x86 SSE and port them to ARM NEON, and 2) identify portions of the code that lends itself well to SIMD processing, but doesn't currently target either instruction set, and port them to both, and thereby achieve a speedup on both ARM and x86 architectures.

The target device features a dual-core processor. Unfortunately, the game doesn't really take advantage of multicore systems yet. This could be improved with multithreaded AIs and pathfinding.

Implementation

The Android VM allows applications to call methods implemented in native code through JNI. This means we have to produce a native shared library which implements the core game functions as a set of methods (functions) which can be called from a shim running in the standard Android VM. The native library and the shim can then be packaged and distributed together as a standard Android application.

Setting up your workstation

For everything short of actually running the application, we will use a PC workstation. This section will assume your workstation runs Ubuntu Linux, but the steps should be relatively easy to adapt to other platforms. Begin by setting up a working directory for the project:

$ mkdir ~/android

Next, install the Android SDK. Download the SDK package from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf android-sdk_r16-linux.tgz

Now complete the installation by running the android tool:

$ ~/android/android-sdk-linux/tools/android

A window with installable packages should open. In addition to any packages checked by default, make sure 'Android SDK Platform-tools' is checked and click 'Install packages'.

Google publishes a Native Development Kit (NDK), which is a set of tools for building native applications for the Android platform. Unfortunately, the official NDK does not and will not support the std::wstring (wide characters) datatype, which is required by the game, so we'll have to use the unofficial Crystax NDK. This is merely an extension of the official NDK, adding support for wide characters and other features maligned by Google.

Currently, the port of the Boost C++ Libraries that we use require us to use the r5 version of the NDK. Download the package from this page to the working directory and then unpack there, e.g.:

$ cd ~/android
$ tar -xvf android-ndk-r5-crystax-2-linux-x86.tar.bz2

Now use the NDK to set up a standalone toolchain:

$ ~/android/android-ndk-r5-crystax-2/build/tools/make-standalone-toolchain.sh --platform=android-9 --install-dir=$HOME/android/toolchain

You also must install a Java Development Kit (JDK) if you haven't already, e.g. the standard JDK from Ubuntu Software Center:

$ sudo apt-get install default-jdk

Finally, make sure Ant and Subversion is installed:

$ sudo apt-get install ant subversion

On 64-bit Ubuntu you may need to install some 32-bit libraries:

$ sudo apt-get install ia32-libs

Installing replacement libraries

The game has a range of external dependencies. Each of these has to be compiled for the target device's processor architecture and installed in the compiler toolchain's SYSROOT, so we can link against them when building the game itself.

Download the MysticTreeGames port of the Boost C++ Libraries to the working directory and unpack it there, e.g.:

$ cd ~/android
$ tar -xvf MysticTreeGames-Boost-for-Android-70838fc.tar.gz

Now change directory to the directory that was unpacked, e.g.:

$ cd MysticTreeGames-Boost-for-Android-70838fc

Then execute the following command to download the Boost C++ Libraries package and compile it for the target device:

$ ./build-android.sh ~/android/android-ndk-r5-crystax-2

Copy the resulting headers and library files into the usr directory in the toolchain's SYSROOT:

$ cp -r build/* ~/android/toolchain/sysroot/usr/

Set up the environment for cross-compiling using the standalone toolchain:

$ HOSTCONF=arm-eabi-linux
$ BUILDCONF=i686-pc-linux-gnu
$ NDK=$HOME/android/android-ndk-r5-crystax-2
$ TOOLCHAIN=$HOME/android/toolchain
$ export ARCH=armv7-a
$ export SYSROOT=$TOOLCHAIN/sysroot
$ export PATH=$PATH:$TOOLCHAIN/bin:$SYSROOT/usr/local/bin
$ export CROSS_COMPILE=arm-linux-androideabi
$ export CC=${CROSS_COMPILE}-gcc
$ export CXX=${CROSS_COMPILE}-g++
$ export AR=${CROSS_COMPILE}-ar
$ export AS=${CROSS_COMPILE}-as
$ export LD=${CROSS_COMPILE}-ld
$ export RANLIB=${CROSS_COMPILE}-ranlib
$ export NM=${CROSS_COMPILE}-nm
$ export STRIP=${CROSS_COMPILE}-strip
$ export CFLAGS="-DANDROID -mandroid -fomit-frame-pointer --sysroot $SYSROOT -march=$ARCH -mfloat-abi=softfp -mfpu=vfp -mthumb"
$ export CXXFLAGS=$CFLAGS
$ export LDFLAGS="-L${NDK}/sources/crystax/libs/armeabi-v7a -lcrystax"

Download the development snapshot for SDL 1.3 from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf SDL-1.3.tar.gz

Now change directory to the directory that was unpacked, e.g.:

$ cd ~/android/SDL-1.3.0-6235

Then execute the following commands to compile the package for the target device:

$ ./configure --host=$HOSTCONF --build=$BUILDCONF  --with-sysroot=$SYSROOT --prefix=$SYSROOT/usr/local --disable-joystick
$ make
$ make install

Download the latest version of cURL from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf curl-7.24.0.tar.bz2

Now change directory to the directory that was unpacked, e.g.:

$ cd ~/android/curl-7.24.0

Then execute the following commands to compile the package for the target device:

$ ./configure --host=$HOSTCONF --build=$BUILDCONF  --with-sysroot=$SYSROOT --prefix=$SYSROOT/usr/local
$ make
$ make install

Download the latest version of libpng from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf libpng-1.5.7.tar.xz

Now change directory to the directory that was unpacked, e.g.:

$ cd ~/android/libpng-1.5.7

Then execute the following commands to compile the package for the target device:

$ ./configure --host=$HOSTCONF --build=$BUILDCONF  --with-sysroot=$SYSROOT --prefix=$SYSROOT/usr
$ make
$ make install

The game depends on libjpeg. We will use libjpeg-turbo, an ABI-compatible fork of libjpeg that adds support for SIMD processing, giving us maximal performance with the target processor's ARM NEON engine.

Download the latest version of libjpeg-turbo from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf libjpeg-turbo-1.1.1.tar.gz

Now change directory to the directory that was unpacked, e.g.:

$ cd ~/android/libjpeg-turbo-1.1.1

Then execute the following commands to compile the package for the target device:

$ ./configure --host=$HOSTCONF --build=$BUILDCONF  --with-sysroot=$SYSROOT --prefix=$SYSROOT/usr
$ make
$ make install

Download the latest version of libxml2 from this page to the working directory and then unpack it there, e.g.:

$ cd ~/android
$ tar -xvf libxml2-2.7.8.tar.gz

Now change directory to the directory that was unpacked, e.g.:

$ cd ~/android/libxml2-2.7.8

Then execute the following command to configure the package for the target device:

$ ./configure --host=$HOSTCONF --build=$BUILDCONF --prefix=$SYSROOT/usr --without-threads --without-iconv --without-python --enable-shared

Hand-edit the Makefile to remove runtest$(EXEEXT) and testrecurse$(EXEEXT) from the noinst_PROGRAMS definition, e.g.

noinst_PROGRAMS = testSchemas$(EXEEXT) testRelax$(EXEEXT) \
        testSAX$(EXEEXT) testHTML$(EXEEXT) testXPath$(EXEEXT) \
        testURI$(EXEEXT) testThreads$(EXEEXT) testC14N$(EXEEXT) \
        testAutomata$(EXEEXT) testRegexp$(EXEEXT) testReader$(EXEEXT) \
        testapi$(EXEEXT) testModule$(EXEEXT) \
        runsuite$(EXEEXT) testchar$(EXEEXT) testdict$(EXEEXT) \
        runxmlconf$(EXEEXT)

Finally, execute the following commands to compile the package:

$ make
$ make install

Building the game

The native library (i.e. the actual game) is built using the standard Premake build system as follows.

(Note: You currently need to reset your environment if it is still set up for cross-compiling from the previous section. Close any open terminal windows and open a new one.) '

Download the game from SVN to any location, e.g.:

$ svn co http://svn.wildfiregames.com/public/ps/trunk/ ~/android/0ad-game

Build the game as normal, but with:

$ cd ~/android/0ad-game/build/workspaces
$ ./update-workspaces.sh --gles --android --without-fam --without-audio --disable-atlas
$ TOOLCHAIN=$HOME/android/toolchain
$ export SYSROOT=$TOOLCHAIN/sysroot
$ export PATH=$PATH:$TOOLCHAIN/bin:$SYSROOT/usr/local/bin
$ export CXX=arm-linux-androideabi-g++
$ export PKG_CONFIG_SYSROOT_DIR=$SYSROOT
$ cd gcc
$ make config=debug -k pyrogenesis

See loads of build errors.

Fix all the build errors.

Fix the build system.

Setting up an Android project

All data that is to be compiled and packaged into the Android application must be assembled in an Android project.

We'll use the template provided by SDL. Move the project template to a fitting name under the working directory, e.g.:

$ mv SDL-1.3.0-6172/android-project/ ~/android/0ad

This is your project directory.

Note: See TracWiki for help on using the wiki.