Last modified on 29 July 2014, at 17:48

OpenGL Programming/Installation/Android NDK

Teapot running on Galaxy S

Our GLUT tutorials can be run on Android using the simple provided wrapper.

Note: the wrapper will be integrated in the next official FreeGLUT (version 3.0)!

Making-ofEdit

To understand of the GLUT wrapper works internally, see Android GLUT Wrapper.

DependenciesEdit

First you'll need a minimal Java development environment:

sudo apt-get install openjdk-7-jdk ant

Then get the Android NDK r9d from Android Developers to compile C/C++ code for Android.

Last you need to install the Android API level 10, get the Android SDK from the same site and use the android graphical tool to install it.

The programs themselves may also require the GLM and FreeType libraries - see the dedicated sections below.

Emulator (lack of) support for OpenGL ES 2.0Edit

The Android emulator only supports OpenGL ES 2.0 since April 2012, requires a specific emulator configuration and system image, and doesn't seem to work on all platforms.

Also beware that the "API Demos" applications ships an "OpenGL ES 2.0" sample that silently and confusingly falls back to OpenGL ES 1.0 if 2.0 is not available, so it's not a good test to see if OpenGL ES 2.0 is supported.

It's still best to experiment with OpenGL 2.0 on Android with a supporting device.

Official documentation: https://developer.android.com/tools/devices/emulator.html#accel-graphics

Connecting with USBEdit

When you connect your device through USB, you can use the adb command (from the Android SDK) to browse the filesystem, install applications, debug them, etc.

$ adb devices
List of devices attached 
4520412B47C0207D        device

Using our wrapperEdit

In this wikibook, the samples are based on the GLUT library.

Since GLUT is not ported to Android yet, we wrote a simple GLUT-compatible wrapper for Android (see the code repository).

Note: the wrapper is still in its early life and may change in the near future.

Compile tutorials codeEdit

Look at the 'android_wrapper/' directory.

  • Plug your device (smartphone, tablet...) with USB
  • Add the Android tools to your PATH, for instance:
export PATH="$PATH:/usr/src/android-sdk-linux/tools:/usr/src/android-sdk-linux/platform-tools:/usr/src/android-ndk-r9d"
  • Inside the jni/ directory, make src a symlink to the GLUT code you need to compile (e.g. ln -nfs ../../tut02_clean src)
  • Make assets a symlink to the tutorial you're compiling (e.g. ln -s jni/src assets)
  • Now you can type:
make clean; make && make install
  • You'll get an "OpenGL Wikibook" application on your device, ready to run!

Full-screenEdit

To make your application full-screen, add this attribute in your AndroidManifest.xml

<application
  ...
  android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
>

KeyboardEdit

The default Android keyboard does not have keys such as F1/F2/F3.

Instead, you can use Hacker's Keyboard, an alternative input method with more keys:

IconEdit

Make sure your application has android:icon defined in AndroidManifest.xml:

    <application ...
		 android:icon="@drawable/icon"

Create two icons:

  • res/drawable/icon.png (48x48)
  • res/drawable-hdpi/icon.png (72x72)

Now your application will have a custom icon on the launcher.

DebuggingEdit

Browsing standard outputEdit

If you want to see your program's standard outputs (stdout and stderr), you need to redirect them to the system log :

adb shell stop
adb shell setprop log.redirect-stdio true
adb shell start  # this may restart your Android session

To check the log files, you can use:

  • The command: adb logcat
  • The 'monitor' utility, with its graphical GUI to browse the logs
  • Eclipse, which embeds a LogCat viewer similar to 'monitor'

Checking JNI callsEdit

The following will turn on more checks when calling JNI from C/C++:

adb shell stop
adb shell setprop dalvik.vm.checkjni true
adb shell start

You'll get additional traces on the system log, and JNI will be more strict on what it accepts.

GDBEdit

GDB can be enabled too.

Note: in NDKr7, use the "stabs" format for debug symbols in Android.mk, otherwise GDB will show the wrong source code lines [1]:

LOCAL_CXXFLAGS  := -gstabs+


GDB requires a debug build, add NDK_DEBUG=1 when building your C++ code:

ndk-build NDK_DEBUG=1

When starting gdb, make sure your AndroidManifest.xml mentions it's debuggable, otherwise gdb will behave badly (lack of thread information, crash, etc.):

    <application ...
            android:hasCode="true" android:debuggable="true"

The gdb-server needs a few seconds to start on the device, so your program will start running before it can be paused by the debugger. A work-around is to add a wait in your android_main function:

  sleep(5);

To start the debug session, type:

ndk-gdb --start

Unable to load native libraryEdit

If you get errors such as:

E/AndroidRuntime( 3021): java.lang.RuntimeException: Unable to start activity
ComponentInfo{org.wikibooks.OpenGL/android.app.NativeActivity}: java.lang.IllegalArgumentException:
Unable to load native library: /data/data/org.wikibook.OpenGL/lib/libnative-activity.so

the system couldn't load your .so due to a low-level reason.

To get more information, you need to create a minimal Java application that loads the library manually:

  • src/com/example/test_native_activity/Main.java
package com.example.test_native_activity;
 
import android.app.Activity;
import android.os.Bundle;
 
public class Main extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.loadLibrary("native-activity");
    }
}
  • AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.test_native_activity"
    android:versionCode="1"
    android:versionName="1.0" >
 
    <uses-sdk android:minSdkVersion="7" />
 
    <application android:label="test_native_activity">
        <activity android:name=".Main">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Compile, install and prepare it:

android update project --name test_native_activity --path . --target "android-10"
ant debug
ant installd
 
adb shell
su -c bash
cd /data/data/
cp -a org.wikibooks.OpenGL/lib/libnative-activity.so com.example.test_native_activity/lib/

When you run this application, you'll get a more precise error in the Android logs, such as a wrong STL implementation:

E/AndroidRuntime(3009): java.lang.UnsatisfiedLinkError: Cannot load library:  reloc_library[1311]:
2323 cannot locate '_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc'...

or a missing dependency:

E/AndroidRuntime( 3327): java.lang.UnsatisfiedLinkError: Cannot load library:link_image[1962]:
2323 could not load needed library 'libglut.so.3' for 'libnative-activity.so'
(load_library[1104]: Library 'libglut.so.3' not found)


In the worst case, the library might not even load properly. This may happen e.g. when the C++ constructor of a global static variable crashes while it is called at library loading time, even before your application is started. You'll need to reproduce library loading at the C level:

#include <stdio.h>
#include <dlfcn.h>
 
int main(int argc, char* argv[]) {
  const char* err = NULL;
  const char* filename = "/data/data/org.wikibooks.OpenGL/lib/libnative-activity.so";
 
  if (argc == 2)
    filename = argv[1];
 
  printf("Clearing errors: "); fflush(stdout);
  err = dlerror();
  printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);
 
  printf("Loading library: "); fflush(stdout);
  void* handle = dlopen(filename, RTLD_LAZY);
  err = dlerror();
  printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);
 
  if (handle != NULL) {
    printf("Loading symbol: "); fflush(stdout);
    dlsym(handle, "ANativeActivity_onCreate");
    err = dlerror();
    printf("%s\n", (err == NULL) ? "OK" : err); fflush(stdout);
  }
}

Then send it to the device and execute it:

$ arm-linux-androideabi-gcc test-dlsym.c
$ adb push a.out /
$ adb shell
# /a.out
Clearing errors: OK
Loading library: OK
Loading symbol: OK

You can also use strace for more precision:

# strace /a.out


There is no ldd for Android by default, but you can simulate it using:

arm-linux-androideabi-objdump -x libs/armeabi/libnative-activity.so | grep NEEDED
# or
arm-linux-androideabi-readelf -d libs/armeabi/libnative-activity.so | grep NEEDED

Abstracting differences between OpenGL and GLES2Edit

When you only use GLES2 functions, your application is nearly portable to both desktops and mobile devices. There are still a couple issues to address:

  • The GLSL #version is different
  • GLES2 requires precision hints that are not compatible with OpenGL 2.1.

See the Basic Tutorials 02 and 03 for details and a proposed solution.

GLMEdit

To install GLM, you just need to extract the latest release in /usr/src/glm (such that /usr/src/glm/glm/glm.hpp exists). It is a header-only library that doesn't require separate compilation.

SOILEdit

  • Download the Simple OpenGL Image Library from http://lonesock.net/soil.html
  • Apply patch from android_wrapper/soil.patch.
  • Follow instructions in projects/Android/Makefile.
  • NDK module ready in src/build/soil/.

FreeTypeEdit

If you need FreeType (a library to render fonts), you'll need to cross-compile it. Note: The Android system uses FreeType but internally it doesn't expose it to native apps.

First, prepare the cross-compiler from the NDK:

/usr/src/android-ndk-r9d/build/tools/make-standalone-toolchain.sh --platform=android-9 \
  --install-dir=/usr/src/ndk-standalone-9
PATH=/usr/src/ndk-standalone-9/bin:$PATH

Then use it to cross-compile freetype:

tar xf freetype-2.5.3.tar.bz2
cd freetype-2.5.3/
./configure --host=arm-linux-androideabi --prefix=/freetype --without-zlib --with-png=no
make -j4
make install DESTDIR=$(pwd)

Then write an Android.mk file in the new freetype/ directory:

LOCAL_PATH:= $(call my-dir)
 
include $(CLEAR_VARS)
 
LOCAL_MODULE := freetype
LOCAL_SRC_FILES := lib/libfreetype.a
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include $(LOCAL_PATH)/include/freetype2
 
include $(PREBUILT_STATIC_LIBRARY)

See docs/STANDALONE-TOOLCHAIN.html and docs/PREBUILTS.html in the NDK for details. The CLEAR_VARS bit is not documented, but is necessary to avoid mixed-up paths; it is used in the native_app_glue NDK module.

(Alternatively you can install it in --prefix=/usr/src/ndk-standalone-9/sysroot/usr if you don't plan to use the NDK build system.)

To use FreeType in your project, edit your Android.mk:

...
LOCAL_STATIC_LIBRARIES := ... freetype
...
$(call import-module,freetype)

You will need to specify the freetype directory location as well, using the NDK_MODULE_PATH variable. For instance, you can copy freetype in your project directory and build using:

	ndk-build NDK_MODULE_PATH=. ...

Our wrapper already defines NDK_MODULE_PATH=$$NDK_MODULE_PATH:..

TroubleshootingsEdit

Device is marked offlineEdit

The first time you connect your device to a new computer, it will ask to to confirm the computer fingerprint. Until you accept on the device screen, it's marked offline.

If you are not asked for confirmation, make sure your adb is up-to-date.

Allow USB access for non-root usersEdit

These days, Android devices are references in your /lib/udev/rules.d/, but adb gets permission issues with your new device:

  • See if it works when running adb as root.
  • To allow unprivileged users to securely connect to the device, create an udev rule as follow.

First, determine your device's idVendor, by typing 'dmesg' after you plug it; check for :

usb 2-1: New USB device found, idVendor=18d1, idProduct=4e22

Then create an udev rule as below, for instance in /etc/udev/rules.d/51-android.rules :

# Galaxy S
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666", GROUP="plugdev" 

(This can be further specialized by idProduct.)

Then restart your udev deamon:

/etc/init.d/udev restart

If you plug your device, the USB character device should have the "plugdev" group:

# ll /dev/bus/usb/002/
total 0
crw-rw-r-T 1 root plugdev 189, 140 janv. 19 21:50 013

Issue with zlibEdit

In FreeType, --without-zlib uses the internal copy of zlib inside freetype rather than one independently recompiled for Android. Otherwise you'd get errors such as:

./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_init':
ftgzip.c:(.text+0x3c4): undefined reference to `inflateInit2_'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_done':
ftgzip.c:(.text+0x43c): undefined reference to `inflateEnd'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_reset':
ftgzip.c:(.text+0x514): undefined reference to `inflateReset'
./obj/local/armeabi/libfreetype.a(ftgzip.o): In function `ft_gzip_file_fill_output':
ftgzip.c:(.text+0x780): undefined reference to `inflate'

See alsoEdit

Gnash compilation instructions for Android, with use similar techniques and dependencies:

ReferencesEdit

  1. http://groups.google.com/group/android-ndk/browse_thread/thread/ebd969a055af3196

< OpenGL Programming/Installation

Browse & download complete code