Setting up Native OpenGL ES* on
Android* Platforms
Abstract
This document details the necessary steps for creating a native OpenGL ES 2.0 graphics application on
the Android platform. The process for setting up an OpenGL ES 2.0 application using the Java* API will
be discussed followed by a conversion of the application to use native rendering inside a shared library.
Note: In order to support the largest audience, no IDE will be used in this document. All project creation,
maintenance, and deployment will be performed through the command line tool set provided by Google.
Project Setup
To create a default Android project, execute the following command at a command prompt.
android create project --target <TID> \
--name <ProjName> \
--path <FolderPath> \
--activity <ActivityName> \
--package <Package>
Note: The target ID will be different depending on how the Android SDK is configured. Retrieve a list of
available targets by executing android list targets at a command prompt.
The following settings will be used for this project:
--name DemoProject
--activity DemoProjectActivity
--package demo.project
The project generated by the Android utility is the default ”Hello World” project. Verify the project was
initialized correctly by executing ant debug at a command prompt while in the project's root directory.
Adding OpenGL ES Support
Android exposes the OpenGL ES API directly to Java for application developers. The classes and methods
used to setup OpenGL are used whether leveraging the Java API or performing native rendering.
Therefore, the details of rendering a simple scene using the Java API will be discussed beforehand.
Following this, the sample application will be modified to use native rendering.
Removing the Title Bar
When executing the Hello World application, the application title bar is visible during execution. This
behavior is typically undesirable when programming OpenGL ES applications.
This behavior may be disabled by adding the appropriate theme attribute of the activity tag inside the
AndroidManifest.xml file.
android:theme=”@android:style/Theme.NoTitleBar.Fullscreen”
Alternatively, the following code can be executed inside the onCreate method of the activity class.
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
Replacing the Default View
The Hello World project uses the overload of setContentView, which takes a resource identifier to the
XML resource to inflate and set as the current activity view. This behavior must to be replaced with a
view surface capable of rendering an OpenGL ES context. Android provides a Java class, GLSurfaceView,
which encapsulates a rendering surface and EGL display.
It's safe to remove the ``res/layout/main.xml'' file from the project because the view will be created
programmatically.
GLSurfaceView
The two import methods for GLSurfaceView are setEGLContextClientVersion and setRenderer.
The setEGLContextClientVersion method sets the ES version that should be used. For this project,
OpenGL ES 2.0 is used.
Rendering to the context is handled by a class instance that inherits from GLSurfaceView.Renderer. The
renderer instance is set by calling the setRenderer method.
The onPause and onResume methods of the Activity class must be overridden to call the respective
methods on the GLSurfaceView class to pause/resume the rendering thread.
Here is the complete DemoActivity class with a GLSurfaceView:
public class DemoActivity extends Activity
{
private GLSurfaceView view;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
view = new GLSurfaceView();
// Tell EGL to use a ES 2.0 Context
view.setupEGLContextClientVersion(2);
// Set the renderer
view.setRenderer(new DemoProjectRenderer());
setContentView(view);
}
@Override
protected void onPause()
{
super.onPause();
view.onPause();
}
@Override
protected void onResume()
{
super.onResume();
view.onResume();
}
}
GLSurfaceView.Renderer
Classes inheriting from GLSurfaceView.Renderer are responsible for drawing to the OpenGL context.
GLSurfaceView.Renderer exposes three abstract methods that must be implemented in the inherited
classes: onSurfaceCreated, onSurfaceChanged, and onDrawFrame.
When Android applications are paused or suspended, the OpenGL context and all the resources
associated with it will be destroyed. Therefore, initialization and resource loading for the application
should be handled in the onSurfaceCreated function.
Note: A method, setPreserveEGLContextOnPause, exists that will tell EGL to try and preserve the context
across pause/resume boundaries. However, be aware that this function relies on the hardware's support
for multiple EGL contexts, so calling it will not guarantee the context will be preserved.
The demo project will use the Java OpenGL ES API to clear the color buffer magenta.
The GLSurfaceView.Renderer derived class for the demo project is listed below:
class DemoProjectRenderer implements GLSurfaceView.Renderer
{
public void onDrawFrame(GL10 gl)
{
gl.glClear(gl.GL_COLOR_BUFFER_BIT);
}
public void onSurfaceChanged(GL10 gl, int width, int height)
{
gl.glViewport(0, 0, width, height);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
gl.glClearColor(1.0f, 0.0f, 1.0f, 1.0f);
}
}
AndroidManifest.xml
The manifest file should specify the need for OpenGL ES 2 by adding the following XML element under
the manifest element.
<uses-feature android:glEsVersion=``0x00020000'' android:required=``true'' />
Moving to Native Rendering
Rather than re-implementing portions of existing code to use the Java interfaces, it’s possible to
leverage existing C/C++ code bases by calling native code from Java classes. Native code is compiled into
a shared library and deployed with the APK file.
All native code is placed under the jni folder inside the project root directory.
JNI Basics
Native code is exposed to Java classes via the JNI framework. For a more detailed account of the JNI
framework, see http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html.
The JNI naming convention must be followed for the loader to properly find native methods. All native
methods:



Must begin with the prefix Java_
Followed with the fully-qualified mangled class name
Followed with the fully-qualified mangled method name
For example, below is a simple native C function:
void nativeLibraryFunction(int width, int height);
To expose this method to Java code without requiring modifications to the existing function, include the
JNI header and create a wrapper method that calls the existing native method.
#include <jni.h>
JNIEXPORT void JNICALL Java_demo_project_LibraryClass_LibraryFunctionWrapper(JNIEnv*
env, jobject obj, jint width, jint height)
{
nativeLibraryFunction(width, height);
}
The above function assumes that the package for the project is demo.project and a Java class named
LibraryClass will be used to expose the native function.
The first parameter in all exported JNI functions is of type “JNIEnv*.” This parameter has several
purposes, the most notable of which is allowing native code to make callbacks to Java code. This
behavior will not be discussed here.
For static functions, the jobject parameter is a reference to the calling Java class.
The remaining parameter types are exposed from the JNI framework. For built-in C types the JNI
framework handles the conversion automatically. For arrays and complex types consult the
documentation.
On the Java side, the shared library must be imported. This is achieved by a call to System.loadLibrary.
This is typically done in a static initializer for the class.
Additionally, the class must declare the interface for the exported methods. The methods are static and
declared as native.
class LibraryClass
{
static
{
System.loadLibrary("demo");
}
public static native void LibraryFunctionWrapper(int width, int height);
}
The important part is that the LibraryClass name matches the name exposed by the exported native
method. Also, the definition of the exported function needs to be marked as native.
Here the library follows the convention for shared libraries on the target platform. For Unix-based (i.e.,
Android) targets, the library name is prefixed with “lib” and “.so” is appended. For Windows*-based
targets, an extension of “dll” is appended.
Note: For more complex function declarations, Java provides a utility, javah, that generates header
definitions for native functions given a Java class file.
Now the method can be called as a static Java method from anywhere in your code. For a concrete
example DemoProject will now move to native rendering.
Migrating the Initialization Code
Create a C file inside the projects jni folder. Here the file will be “nativelibrary.c.”
The file will use all native OpenGL ES calls.
void InitializeOpenGL()
{
glClearColor(1.0f, 1.0f, 0.0f, 1.0f);
}
void resizeViewport(int newWidth, int newHeight)
{
glViewport(0, 0, newWidth, newHeight);
}
void renderFrame()
{
glClear(GL_COLOR_BUFFER_BIT);
}
Assume these methods already existed inside the project, or in a static library. To exercise these
methods, JNI wrapper methods are created for each of the three methods. These methods go in another
C file, jniExports.c.
JNIEXPORT void JNICALL Java_demo_project_SomeClass_init(JNIEnv*)
{
InitializeOpenGL();
}
JNIEXPORT void JNICALL Java_demo_project_SomeClass_resize(JNIEnv*, jint width, jint height)
{
resizeViewport(width, height);
}
JNIEXPORT void JNICALL Java_demo_project_SomeClass_render(JNIEnv*)
{
renderFrame();
}
On the Java side the exported methods are declared in the library class.
class LibraryClass
{
static
{
System.loadLibrary("demo");
}
public static native void init();
public static native void resize(int width, int height);
public static native void render();
}
Now the GLSurfaceView.Renderer derived class is modified to call the native methods:
class DemoProjectRenderer extends GLSurfaceView.Renderer
{
public void onDrawFrame(GL10 gl)
{
LibraryClass.render();
}
public void onSurfaceChanged(GL10 gl, int width, int height)
{
LibraryClass.resize(width, height);
}
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
LibraryClass.init();
}
}
Configuring the Native Build
Android.mk
Inside the “jni” folder, a makefile, “Android.mk,” is created to instruct the NDK on how to compile native
modules. An example Android.mk used in the particle sample project is listed below:
# Tell Android where to locate source files
# "my-dir" is a macro function which will return the path of the current
directory(where Android.mk resides)
LOCAL_PATH := $(call my-dir)
# Clear contents of the LOCAL_* variables
include $(CLEAR_VARS)
# All the source files to include in this module
LOCAL_SRC_FILES := nativeLibrary.c \
jniExports.c
# The name of the module
LOCAL_MODULE := libdemo
# Compilation flags
LOCAL_CFLAGS := -Werror
# Static libraries to link with
LOCAL_LDLIBS := -llog –landroid -lGLESv2
# Build the module
include $(BUILD_SHARED_LIBRARY)
Execute the ndk-build command from a command prompt to build the shared library. This step should
be performed before building the Android application with ant.
Reading Assets from the APK
Accessing assets is one area of moving to native rendering that deserves special attention. The asset
manager is passed from Java to the native code. For example, in the activities onCreate method, the
following lines of code are added:
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
assetManager = getAssets();
LibraryClass.initializeAssetManager(assetManager);
view = new GLSurfaceView();
// Tell EGL to use a ES 2.0 Context
view.setupEGLContextClientVersion(2);
// Set the renderer
view.setRenderer(new DemoProjectRenderer());
setContentView(view);
}
JNIEXPORT void JNICALL Java_demo_project_LibraryClass_initializeAssetManager(JNIEnv*
env, jobject obj, jobject assetManager)
{
AAssetManager* mgr = AAssetManager_fromJava(env, assetManager);
}
The AAssetManager_fromJava function is provided by the NDK to get a native pointer to an asset
manager instance. Assets stored inside the APK are accessible via the AAssetManager API. For an
example of reading files using the AAssetManager API, see util/file.c in the Particle Sample source code.
Particle Sample Code
The text above details a minimal set of steps for enabling a native OpenGL ES workload on Android
platforms. For a more complete example, source code for a native OpenGL ES 2.0 particle system can be
found at http://software.intel.com/en-us/articles/Android-code-samples. The portions of the source code
relevant to the document will be mentioned below.
ParticlesActivity.java
This class houses the main application activity. It is responsible for creating the GLSurfaceView derived
class, as well as passing the application’s asset manager to the native C code by calling
createAssetManager.
ParticlesView.java
In the particle sample an explicit class is inherited from GLSurfaceView to set the OpenGL ES client
version. Additionally, the surface render is implemented as an inner class.
ParticlesLib.java
This class imports the shared library and declares the three native methods used by the sample: init,
step, and createAssetManager.
Shared Library Code
Code for the shared library used in the sample is found under the jni directory.
The exported C function hooks are located in jni/gl_code.cpp. They expose the initialization, frame step,
and asset creation functions. Implementations of these methods are found in jni/App.c.
The particle system used in the sample can be found in jni/Particles.c and jni/Particles.h.
The remaining files located in the sample are utility methods for math functions, simple geometry
creation, and texture loading. This code is located under the jni/util directory.
One file of particular note is a simple utility function for reading asset data, which can be found in
util/file.c.
About the Author
Chris Kirkpatrick is a software applications engineer working in the Intel Software and Services Group
supporting Intel graphics solutions in the Personal Form Factors division. He holds a B.Sc. in Computer
Science from Oregon State University.
Notices
INFORMATION IN THIS DOCUMENT IS PROVIDED IN CONNECTION WITH INTEL PRODUCTS. NO LICENSE, EXPRESS
OR IMPLIED, BY ESTOPPEL OR OTHERWISE, TO ANY INTELLECTUAL PROPERTY RIGHTS IS GRANTED BY THIS
DOCUMENT. EXCEPT AS PROVIDED IN INTEL'S TERMS AND CONDITIONS OF SALE FOR SUCH PRODUCTS, INTEL
ASSUMES NO LIABILITY WHATSOEVER AND INTEL DISCLAIMS ANY EXPRESS OR IMPLIED WARRANTY, RELATING TO
SALE AND/OR USE OF INTEL PRODUCTS INCLUDING LIABILITY OR WARRANTIES RELATING TO FITNESS FOR A
PARTICULAR PURPOSE, MERCHANTABILITY, OR INFRINGEMENT OF ANY PATENT, COPYRIGHT OR OTHER
INTELLECTUAL PROPERTY RIGHT.
UNLESS OTHERWISE AGREED IN WRITING BY INTEL, THE INTEL PRODUCTS ARE NOT DESIGNED NOR INTENDED FOR
ANY APPLICATION IN WHICH THE FAILURE OF THE INTEL PRODUCT COULD CREATE A SITUATION WHERE PERSONAL
INJURY OR DEATH MAY OCCUR.
Intel may make changes to specifications and product descriptions at any time, without notice. Designers must not
rely on the absence or characteristics of any features or instructions marked "reserved" or "undefined." Intel
reserves these for future definition and shall have no responsibility whatsoever for conflicts or incompatibilities
arising from future changes to them. The information here is subject to change without notice. Do not finalize a
design with this information.
The products described in this document may contain design defects or errors known as errata which may cause
the product to deviate from published specifications. Current characterized errata are available on request.
Contact your local Intel sales office or your distributor to obtain the latest specifications and before placing your
product order.
Copies of documents which have an order number and are referenced in this document, or other Intel literature,
may be obtained by calling 1-800-548-4725, or go to: http://www.intel.com/design/literature.htm
Software and workloads used in performance tests may have been optimized for performance only on Intel
microprocessors. Performance tests, such as SYSmark and MobileMark, are measured using specific computer
systems, components, software, operations, and functions. Any change to any of those factors may cause the
results to vary. You should consult other information and performance tests to assist you in fully evaluating your
contemplated purchases, including the performance of that product when combined with other products.
Any software source code reprinted in this document is furnished under a software license and may only be used
or copied in accordance with the terms of that license.
Intel and the Intel logo are trademarks of Intel Corporation in the US and/or other countries.
OpenGL is a registered trademark and the OpenGL ES logo is a trademark of Silicon Graphics Inc. used by
permission by Khronos.
Copyright © 2012 Intel Corporation. All rights reserved.
*Other names and brands may be claimed as the property of others.