Category Archives: Native Development (C, C++)

Calling OpenGL from C on Android, Using the NDK

For this first post in the Developing a Simple Game of Air Hockey Using C++ and OpenGL ES 2 for Android, iOS, and the Web series, we’ll create a simple Android program that initializes OpenGL, then renders simple frames from native code.

Prerequisites

  • The Android SDK & NDK installed, along with a suitable IDE.
  • An emulator or a device supporting OpenGL ES 2.0.

We’ll be using Eclipse in this lesson.

To prepare and test the code for this article, I used revision 22.0.1 of the ADT plugin and SDK tools, and revision 17 of the platform and build tools, along with revision 8e of the NDK and Eclipse Juno Service Pack 2.

Getting started

The first thing to do is create a new Android project in Eclipse, with support for the NDK. You can follow along all of the code at the GitHub project.

Before creating the new project, create a new folder called airhockey, and then create a new Git repository in that folder. Git is a source version control system that will help you keep track of changes to the source and to roll back changes if anything goes wrong. To learn more about how to use Git, see the Git documentation.

To create a new project, select File->New->Android Application Project, and then create a new project called ‘AirHockey’, with the application name set to ‘Air Hockey’ and the package name set to ‘com.learnopengles.airhockey’. Leaving the rest as defaults or filling out as you prefer, save this new project in a new folder called android, inside of the airhockey folder that we created in the previous step.

Once the project has been created, right-click on the project in the Package Explorer, select Android Tools from the drop-down menu, then select Add Native Support…. When asked for the Library Name, enter ‘game’ and hit Finish, so that the library will be called libgame.so. This will create a new folder called jni in the project tree.

Initializing OpenGL

With our project created, we can now edit the default activity and configure it to initialize OpenGL. We’ll first add two member variables to the top of our activity class:

	private GLSurfaceView glSurfaceView;
	private boolean rendererSet;

Now we can set the body of onCreate() as follows:

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		ActivityManager activityManager
			= (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
		ConfigurationInfo configurationInfo = activityManager.getDeviceConfigurationInfo();

		final boolean supportsEs2 =
			configurationInfo.reqGlEsVersion >= 0x20000 || isProbablyEmulator();

		if (supportsEs2) {
			glSurfaceView = new GLSurfaceView(this);

			if (isProbablyEmulator()) {
				// Avoids crashes on startup with some emulator images.
				glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
			}

			glSurfaceView.setEGLContextClientVersion(2);
			glSurfaceView.setRenderer(new RendererWrapper());
			rendererSet = true;
			setContentView(glSurfaceView);
		} else {
			// Should never be seen in production, since the manifest filters
			// unsupported devices.
			Toast.makeText(this, "This device does not support OpenGL ES 2.0.",
					Toast.LENGTH_LONG).show();
			return;
		}
	}

First we check if the device supports OpenGL ES 2.0, and then if it does, we initialize a new GLSurfaceView and configure it to use OpenGL ES 2.0.

The check for configurationInfo.reqGlEsVersion >= 0x20000 doesn’t work on the emulator, so we also call isProbablyEmulator() to see if we’re running on an emulator. Let’s define that method as follows:

	private boolean isProbablyEmulator() {
		return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1
				&& (Build.FINGERPRINT.startsWith("generic")
						|| Build.FINGERPRINT.startsWith("unknown")
						|| Build.MODEL.contains("google_sdk")
						|| Build.MODEL.contains("Emulator")
						|| Build.MODEL.contains("Android SDK built for x86"));
	}

OpenGL ES 2.0 will only work in the emulator if it’s been configured to use the host GPU. For more info, read Android Emulator Now Supports Native OpenGL ES2.0!

Let’s complete the activity by adding the following methods:

	@Override
	protected void onPause() {
		super.onPause();

		if (rendererSet) {
			glSurfaceView.onPause();
		}
	}

	@Override
	protected void onResume() {
		super.onResume();

		if (rendererSet) {
			glSurfaceView.onResume();
		}
	}

We need to handle the Android lifecycle, so we also pause & resume the GLSurfaceView as needed. We only do this if we’ve also called glSurfaceView.setRenderer(); otherwise, calling these methods will cause the application to crash.

For a more detailed introduction to OpenGL ES 2, see Android Lesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide.

Adding a default renderer

Create a new class called RendererWrapper, and add the following code:

public class RendererWrapper implements Renderer {
	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		glClearColor(0.0f, 0.0f, 1.0f, 0.0f);
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		// No-op
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		glClear(GL_COLOR_BUFFER_BIT);
	}
}

This simple renderer will set the clear color to blue and clear the screen on every frame. Later on, we’ll change these methods to call into native code. To call methods like glClearColor() without prefixing them with GLES20, add import static android.opengl.GLES20.*; to the top of the class file, then select Source->Organize Imports.

If you have any issues in getting the code to compile, ensure that you’ve organized all imports, and that you’ve included the following imports in RendererWrapper:

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLSurfaceView.Renderer;

Updating the manifest to exclude unsupported devices

We should also update the manifest to make sure that we exclude devices that don’t support OpenGL ES 2.0. Add the following somewhere inside AndroidManifest.xml:

    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

Since OpenGL ES 2.0 is only fully supported from Android Gingerbread 2.3.3 (API 10), replace any existing <uses-sdk /> tag with the following:

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="17" />

If we run the app now, we should see a blue screen as follows:

First pass

First pass

Adding native code

We’ve verified that things work from Java, but what we really want to do is to be using OpenGL from native code! In the next few steps, we’ll move the OpenGL code to a set of C files and setup an NDK build for these files.

We’ll be sharing this native code with our future projects for iOS and the web, so let’s create a folder called common located one level above the Android project. What this means is that in your airhockey folder, you should have one folder called android, containing the Android project, and a second folder called common which will contain the common code.

Linking a relative folder that lies outside of the project’s base folder is unfortunately not the easiest thing to do in Eclipse. To accomplish this, we’ll have to follow these steps:

  1. Right-click the project and select Properties. In the window that appears, select Resource->Linked Resources and click New….
  2. Enter ‘COMMON_SRC_LOC’ as the name, and ‘${PROJECT_LOC}\..\common’ as the location. Once that’s done, click OK until the Properties window is closed.
  3. Right-click the project again and select Build Path->Link Source…, select Variables…, select COMMON_SRC_LOC, and select OK. Enter ‘common’ as the folder name and select Finish, then close the Properties window.

You should now see a new folder in your project called common, linked to the folder that we created.

Let’s create two new files in the common folder, game.c and game.h. You can create these files by right-clicking on the folder and selecting New->File. Add the following to game.h:

void on_surface_created();
void on_surface_changed();
void on_draw_frame();

In C, a .h file is known as a header file, and can be considered as an interface for a given .c source file. This header file defines three functions that we’ll be calling from Java.

Let’s add the following implementation to game.c:

#include "game.h"
#include "glwrapper.h"

void on_surface_created() {
	glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
}

void on_surface_changed() {
	// No-op
}

void on_draw_frame() {
	glClear(GL_COLOR_BUFFER_BIT);
}

This code will set the clear color to red, and will clear the screen every time on_draw_frame() is called. We’ll use a special header file called glwrapper.h to wrap the platform-specific OpenGL libraries, as they are often located at a different place for each platform.

Adding platform-specific code and JNI code

To use this code, we still need to add two things: a definition for glwrapper.h, and some JNI glue code so that we can call our C code from Java. JNI stands for Java Native Interface, and it’s how C and Java can talk to each other on Android.

Inside your project, create a new file called glwrapper.h in the jni folder, with the following contents:

#include <GLES2/gl2.h>

That wraps Android’s OpenGL headers. To create the JNI glue, we’ll first need to create a Java class that exposes the native interface that we want. To do this, let’s create a new class called GameLibJNIWrapper, with the following code:

public class GameLibJNIWrapper {
	static {
		System.loadLibrary("game");
	}

	public static native void on_surface_created();

	public static native void on_surface_changed(int width, int height);

	public static native void on_draw_frame();
}

This class will load the native library called libgame.so, which is what we’ll be calling our native library later on when we create the build scripts for it. To create the matching C file for this class, build the project, open up a command prompt, change to the bin/classes folder of your project, and run the following command:

javah -o ../../jni/jni.c com.learnopengles.airhockey.GameLibJNIWrapper

The javah command should be located in your JDKs bin directory. This command will create a jni.c file that will look very messy, with a bunch of stuff that we don’t need. Let’s simplify the file and replace it with the following contents:

#include "../../common/game.h"
#include <jni.h>

JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1created
	(JNIEnv * env, jclass cls) {
	on_surface_created();
}

JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1surface_1changed
	(JNIEnv * env, jclass cls, jint width, jint height) {
	on_surface_changed();
}

JNIEXPORT void JNICALL Java_com_learnopengles_airhockey_GameLibJNIWrapper_on_1draw_1frame
	(JNIEnv * env, jclass cls) {
	on_draw_frame();
}

We’ve simplified the file greatly, and we’ve also added a reference to game.h so that we can call our game methods. Here’s how it works:

  1. GameLibJNIWrapper defines the native C functions that we want to be able to call from Java.
  2. To be able to call these C functions from Java, they have to be named in a very specific way, and each function also has to have at least two parameters, with a pointer to a JNIEnv as the first parameter, and a jclass as the second parameter. To make life easier, we can use javah to create the appropriate function signatures for us in a file called jni.c.
  3. From jni.c, we call the functions that we declared in game.h and defined in game.c. That completes the connections and allows us to call our native functions from Java.

Compiling the native code

To compile and run the native code, we need to describe our native sources to the NDK build system. We’ll do this with two files that should go in the jni folder: Android.mk and Application.mk. When we added native support to our project, a file called game.cpp was automatically created in the jni folder. We won’t be needing this file, so you can go ahead and delete it.

Let’s set Android.mk to the following contents:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := game
LOCAL_CFLAGS    := -Wall -Wextra
LOCAL_SRC_FILES := ../../common/game.c jni.c
LOCAL_LDLIBS := -lGLESv2

include $(BUILD_SHARED_LIBRARY)

This file describes our sources, and tells the NDK that it should compile game.c and jni.c and build them into a shared library called libgame.so. This shared library will be dynamically linked with libGLESv2.so at runtime.

When specifying this file, be careful not to leave any trailing spaces after any of the commands, as this may cause the build to fail.

The next file, Application.mk, should have the following contents:

APP_PLATFORM := android-10
APP_ABI := armeabi-v7a

This tells the NDK build system to build for Android API 10, so that it doesn’t complain about us using unsupported features not present in earlier versions of Android, and it also tells the build system to generate a library for the ARMv7-A architecture, which supports hardware floating point and which most newer Android devices use.

Updating RendererWrapper

Before we can see our new changes, we have to update RendererWrapper to call into our native code. We can do that by updating it as follows:

	@Override
	public void onSurfaceCreated(GL10 gl, EGLConfig config) {
		GameLibJNIWrapper.on_surface_created();
	}

	@Override
	public void onSurfaceChanged(GL10 gl, int width, int height) {
		GameLibJNIWrapper.on_surface_changed(width, height);
	}

	@Override
	public void onDrawFrame(GL10 gl) {
		GameLibJNIWrapper.on_draw_frame();
	}

The renderer now calls our GameLibJNIWrapper class, which calls the native functions in jni.c, which calls our game functions defined in game.c.

Building and running the application

You should now be able to build and run the application. When you build the application, a new shared library called libgame.so should be created in your project’s /libs/armeabi-v7a/ folder. When you run the application, it should look as follows:

Second pass

Second pass


We know that our native code is being called with the color changing from blue to red.

Exploring further

The full source code for this lesson can be found at the GitHub project. For a more detailed introduction to OpenGL ES 2, see Android Lesson One: Getting Started or OpenGL ES 2 for Android: A Quick-Start Guide.

In the next part of this series, we’ll create an iOS project and we’ll see how easy it is to reuse our code from the common folder and wrap it up in Objective-C. Please let me know if you have any questions or feedback!

Share

Developing a Simple Game of Air Hockey Using C and OpenGL ES 2 for Android, iOS, and the Web

Some of you have been curious about what the air hockey game from the book would be like if we brought it over to other platforms. I would like to find out, myself. :) In the spirit of my last post about cross-platform development, I want to port the air hockey project over to a native cross-platform code base that can be built for Android and iOS, and even the web by using emscripten and WebGL. Everything will be open-source and available on GitHub.

Here are some of the things that we’ll have to figure out and learn along the way:

  • Setting up a simple build system for each platform.
  • Initializing OpenGL.
  • Adding support for basic touch and collision detection.

In the next post, we’ll take a look at setting up a simple build system to initialize OpenGL across these different platforms. Here are all of the posts for the series so far:

Setting up a simple build system

Adding support for PNG loading into a texture

Adding a 3d perspective, mallets, and a puck

Adding touch events and basic collision detection

The code is available on Github, with each section organized by tags.

Share

Site Updates, and Thoughts on Native Development for the Web

I’ve recently been spending time travelling overseas, taking a bit of a break after reaching an important milestone with the book, and also taking a bit of a rest from working for myself! The trip has been good so far, and I’ve even been keeping up to date with items from the RSS feed. Here is some of the news that I wanted to share with y’all, as well as to get your thoughts:

Book nearing production

OpenGL ES for Android: A Quick-Start Guide reached its final beta a couple of weeks ago, and is now being readied to be sent off to the printers. I would like to thank everyone again for their feedback and support; I am so grateful for it, and happy that the book is now going out the door. I’d also like to give a special thanks to Mario Zechner, the creator behind libgdx and Beginning Android Games, for generously contributing his foreword and a lot of valuable feedback!

Site news

Not too long ago, I decided to add a new forums section to the site to hopefully build up some more community involvement and get a two-way dialogue going; unfortunately, things didn’t quite take off. The forums have also suffered from spam and some technical issues, and recently I was even locked out of the forum administration. I have no idea what happened or how to fix it, so since the posting rate was low, I am just putting the forums on ice for now.

I’d still love to find a way to have some more discussions happening on the site. In which other ways do you believe that I could improve the site so that I could encourage this? I’d love to hear your thoughts.

Topics to explore further

I’ve also been thinking about new topics to explore and write about, as a lot of exciting things are happening with 3D on the mobile and web. One big trend that seems to be taking place: Native is making a comeback.

For many years,  C and C++ were proclaimed to be dead languages, lingering around only for legacy reasons, and soon to be replaced by the glorious world of managed languages. Having started out my own development career in Java, I can agree that the Java world does have a lot of advantages. The language is easier to learn than a behemoth like C++, and, at least on the desktop, the performance on the JVM can even come close to rivalling native languages.

So, why the resurgence in C and C++? Here are some of my thoughts:

  • The world is not just limited to the desktop anymore, and there are more important platforms to target than ever before. C and C++ excel at cross-platform portability, as just about every platform has a C/C++ compiler. By contrast, the JVM and .NET runtimes are limited to certain platforms, and Android’s Dalvik VM is not as good as the JVM in producing fast, efficient JIT compiled code. Yes, there are bytecode translators and commercial alternatives such as Xamarin’s Mono platforms for mobile, but this comes with its own set of disadvantages.
  • Resource usage can be more important than programmer productivity. This is true in big, expensive data centers, and it’s also true on mobile, where smaller downloads and lower battery usage can lead to happier customers.
  • C and C++ are still king when it comes to fast, efficient compiled code that can be compiled almost anywhere. Other native would-be competitors lose out because they are either not as fast or not as widely available on the different platforms. When productivity becomes more important than performance, these alternatives also get squeezed out by the managed and scripting languages.

As much as C and C++ excel at the things they’re good at, they also come with a lot of legacy cruft. C++ is a huge language, and it gets larger with each new standard. On the other hand, at least the compilers give you some freedom. Don’t want to use the STL? Roll out your own custom containers. Don’t want the cost/limitations of exception handling and RTTI? Compile with -fno-exceptions and -fno-rtti. Undefined behavior is another nasty issue which can rear its head, though compilers like Clang now feature additional tools to help catch and fix these errors. With data-oriented design and sensible error handling, C++ code can be both fast and maintainable.

Compiling C and C++ to the web

With tools like emscripten, you can now compile your C/C++ code to JavaScript and run it in a browser, and if you use the asm.js subset, it can actually run with very good performance, enough to run a modern 3D game using JavaScript and WebGL. I’ve always been skeptical of the whole “JavaScript everywhere” meme, because how can the web truly become an open computing platform by forcing the use of one language for everything? There’s no way a single language can be equally suitable for all tasks, and why would I want to develop a second code base just for the web? For this reason, I used to believe that Google’s Native Client held more promise, since it can run native code with almost no speed loss. Why use JavaScript when you can just execute directly on the CPU and bring your existing code over?

Now I see things a bit differently and I think that the asm.js approach has a lot of merit to it. NaCl has been around for years now, and it still only runs in Google Chrome, and then only on certain platforms and only if the software is distributed through the Chrome store, or if the user enables a developer flag. The asm.js approach, on the other end, can run on every browser that supports modern JavaScript. This approach is also portable, meaning it will work into the foreseeable future, even on new device architectures. NaCl, on the other hand, is limited to what was compiled. Portable NaCl is supposed to fix this, but it’s been a work-in-progress for years now, and given the experience with NaCl, it may never find its way to another browser besides Google Chrome. Combined with WebGL, compiling to JavaScript really opens up the web to a lot of new possibilities, one where you can deploy across the web without being tied to a single browser or plugin. The BananaBread demo shows just some of what is possible.

I’d like to learn more about writing OpenGL apps that can run on Android, iOS, and the web, all with a single code base in C++. I know that this is also possible with Java by using Google’s Web Toolkit and bytecode translators (after all, this is how libgdx does it), but I’d like to learn something different, outside of the Java sphere. Is this something that you guys would be interested in reading more of? This is all relatively new to me and I’m currently exploring, so as always, looking forward to your feedback. :)

Update: I am now developing an air hockey project here: Developing a Simple Game of Air Hockey Using C++ and OpenGL ES 2 for Android, iOS, and the Web

Share