3D graphics on Android projects based on native code Now that we can create 3D graphics in our Android applications using the Java OpenGL interface provided by Android, we can examine the possibility to do the 3D programming in C or C++ to access the OpenGL API directly. To do this we will need native code development on Android. We will also examine the possibility to use the Ogre3D engine in our applications to access its high level features. Native OpenGL, OGRE3D on Android
Native code? What is native code? C/C++ code Using C/C++ API-s directly Why we need it, what should be moved into native In most situations native code should be avoided Performance critical parts Using external cross platform APIs Native calls have a cost! Native code in an Android project means code that was written in C or C++. These code can access C or C++ APIs directly. In most of the situations native code should be avoided as the Java environment is the main platform for Android development. However in some situations where performance has great importance we can consider implementing the performance critical parts in native code. This also means that if our favorite APIs that we use for PC development can be compiled for the Android platform, nothing can stop us to use them in our Android applications. This widens our possibilities greatly, and can speed up development drastically. When designing which part of our routines should be moved to the native side we should always consider, that native calls have relatively high costs. This means that rarely used routines that has high computational costs can be moved to the native side, while frequently called simple routines should be not.
Native code in an Android project Need separate SDK: Android NDK Cross compiling tools: ndk-build Must be familiar with Java Native Interface (JNI) Have to place native code in ./jni folder Need special files in ./jni: Android.mk Application.mk (optional) Have to call ndk-build before project build Should load the compiled library in Java code Use the library through native functions (JNI) To begin native code development for Android first we need an other SDK the Android NDK, which contains the cross compiling tools for native library building. We should also be familiar with the basics of Java Native Interface (JNI). Note, that JNI is not Android related, it is the main tool for native calls from Java applications on the PC platform too. Our source files containing the native code is usually placed in the “jni” folder under our Android project folder. We should also put special files in this folder namely Android.mk and Application.mk (the later is optional). Android.mk is basically a special make file used by the Android NDK compiler tool: ndk-build. To compile our source files we should call ndk-build from the project directory (we should add the NDK path to our PATH system variable). This build tool will compile our source files and create a library file, that can be loaded later by the Android system. This library file will be packed into our application package during the build of our Android application. In our Java source we should load this library once to make the functions declared in it visible to our application. After library load these functions can be called through native function calls. Next we will examine the required steps in more details.
Java Native Interface I. Allows execution of native code from Java Native code is compiled into a dynamic library Library should be loaded with System.loadLibrary(String libraryName) Library is accesed through native function calls Native functions does not have definition, they are implemented in the native code: package test.jni; class A{ static{ System.loadLibrary(”myLibName”);} protected native void myNativeFunc(); protected void myFunc(){myNativeFunc();} } First we should talk about Java Native Interface. JNI allows the execution of native code from a Java application. All native calls are compiled to exported functions of a dynamically loaded library. To make these functions visible we should load the library file in Java code, which can be done with the static function: System.loadLibrary(). This function asks for the name of the library (the filename without pre and post-fixes) to load. Each exported function in the library file should have a corresponding special unimplemented Java function declared with the native keyword. This slide shows a simple sample of declaring and calling a native function in Java, and also the library loading routine (which should be done only once, so it is placed in the static initializer).
Java Native Interface II. Native function declarations are special: Name expresses the package and class that declared it have special input parameters Can be generated with javah: javah test.jni.A (A.class should be generated first) javah output: #ifdef __cplusplus extern "C" { #endif JNIEXPORT void JNICALL Java_test_jni_A_myNativeFunc (JNIEnv *, jobject); } The name of the function in our native code should express the full path of the corresponding Java native function, namely it should contain the package name class name and the function name. See the native code example of our previous A class: it starts with java, then comes the package names and finally the class name and function name each separated with an underline. It also has two special arguments. (Built in types has their corresponding types in native code: jint for int, jfloat for float. All classes are passed with jobject type.) This special function naming is important, so to avoid mistakes we can automatically generate the native declarations of our native functions declared in a Java class with the javah tool. Only the name of the class(es) should be passed to javah (our .java files should be compiled first) and it creates the corresponding header file containing the correct declaration of the functions. (we can copy these to our source files and even delete the generated header file) If we are using C++, we should place extern “C” around the functions to be exported by the library (i.e. our native functions).
Java Native Interface III. Native code should be compiled into a library Compiled library should be accessible by the Java application in case of Android it should be packed into the apk After completing our functions with the definitions we should compile it to a library (for Android ndk-build will do that), and make it accessible by the Java applications (in case of android it should be packed into a correct subdirectory in our apk package file, this is done by the Android build tool we used for Java only applications)
Android native support javah can be used to generate native function declarations ndk-build can be used to compile the native code to a library file Ndk-build needs: Source files containing native function definitions They are typically located in ./jni Android.mk configuration file in ./jni This configuration file should be properly filled After ndk-build the project can be built as usual (with NetBeans for e.g.) How Android supports native library usage? Javah can be used to generate declarations. The ndk-build tool can compile our source files for the given Android platform. To do this it needs our source files, which are typically located in the jni folder of our project folder, it needs a build configuration file in the jni folder named Android.mk. We should fill this file properly. After ndk-build the project can be built as usual (with NetBeans for e.g.).
Android.mk simple LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := myLibName LOCAL_SRC_FILES := mySourceFile.cpp include $(BUILD_SHARED_LIBRARY) Name of the library file to be created Source file in ./jni that contains native code definitions Now we should have a look at the Android.mk file, since it is essential for successful building. We can see a very simple example, where only one source file is compiled, it is located in the jni folder and does not require any additional headers or libraries. Here what the user should fill is the LOCAL_MODULE variable, which gives the name of the library file to be created (this name should be passed to System.loadLibrary()); and the LOCAL_SRC_FILES variable which lists the source files that should be compiled (in this case only one source file exists).
Android.mk advanced LOCAL_PATH := $(call my-dir) include$(CLEAR_VARS) LOCAL_MODULE := OgreJNI LOCAL_LDLIBS := -landroid -lc -lm -ldl -llog -lEGL -lGLESv2 LOCAL_LDLIBS += -L./../MyAPI/lib LOCAL_LDLIBS += -lMyAPI LOCAL_STATIC_LIBRARIES := cpufeatures LOCAL_CFLAGS := -I./../MyAPI/include LOCAL_CFLAGS += -fexceptions -frtti -x c++ -D___ANDROID___ -DANDROID -DZZIP_OMIT_CONFIG_H LOCAL_SRC_FILES := ./MyCode/MainActivity.cpp include $(BUILD_SHARED_LIBRARY) $(call import-module,android/cpufeatures) Additional used libraries Additional library path In this more complicated example our code uses external libraries, so all additional library names should be given. These can be libraries provided by the Android NDK or our own libraries compiled for Android. If they are not built in libraries provided by Android NDK, their path should also be given. We should also set the include path for external includes. Under LOCAL_SOURCE_FILES files can be given with relative or absolute path (if only the filename is given, it is searched in the jni folder). We can also link to static libraries provided by Android NDK like cpufeatures in this example. Additional include path Source file not in ./jni, Several source files can be listed
NetBeans/Eclipse native support NetBeans does not provide additional support for native code in Android Eclipse Right click on project – Android Tools – Add native support jni folder, Android.mk, empty cpp file created automatically Android.mk refers to new cpp file Ndk-build called automatically before Java build Unfortunatelly NetBeans does not provide any additional support for Android native development, so we have to call ndk-build our own. If we use eclipse we can add native support to our project, which will create the jni folder for us. It will place an empty source file in it, and a properly filled Android.mk that refers to this source file (it is similar to our simple Android.mk example). The Eclipse tool also calls ndk-build before Java build automatically.
Pure Native Android Application I. At least we need a dummy Java Activity to call our native functions We also have a built in „dummy” activity Calls „android_main” native function Separate thread Callbacks for window and input commands android.app.NativeActivity android_native_app_glue library JNI is a powerful tool, but as we could see, we always need a Java framework that can invoke native method calls. If we place all the logic of our application into native code, we still need a dummy Java Activity that can call our native start function. To overcome this the Android NDK has a built in Activity, that implements this framework for us. It calls the android_main native function in its separate thread. A separate thread is needed, as the main activity is still continuously looking for user events. We can set callback functions in our native code for Android events, like window events or user input events. To use this built in functionality we should implement the android_main function and we should tell, that the built in android.app.NativeActivity should be launched when our application starts. We will use the so called android_native_app_glue library for this.
Pure Native Android Application II. Android .mk LOCAL_STATIC_LIBRARIES := android_native_app_glue … include $(BUILD_SHARED_LIBRARY) $(call import-module,android/native_app_glue) AndroidManifest.xml <uses-sdk android:minSdkVersion="9" /> <activity android:name="android.app.NativeActivity" First we should use the app glue static library: place “LOCAL_STATIC_LIBRARIES := android_native_app_glue” before and “$(call import-module,android/native_app_glue)” after “include $(BUILD_SHARED_LIBRARY)” in Android.mk . This functionality is available from sdk version 9, so place this information in AndroidManifest.xml. And finally we should tell that our main Activity to launch is the android.app.NativeActivity.
Pure Native Android Application III. Source file example: void android_main(struct android_app* state) { state->onAppCmd = handle_cmd; state->onInputEvent = handle_input; while (1) { // Read all pending events. int ident; int events; struct android_poll_source* source; while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } if (state->destroyRequested) { return; … Our minimal android_main function should look like this. We can set callback functions for application and input events. We should start an infinite loop, that listens for system events, end exists if requested.
Pure Native Android Application IV. void handle_cmd(struct android_app* app, int32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: … } int32_t handle_input(struct android_app* app, AInputEvent* event) { if(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION) { float x = AMotionEvent_getX(event, 0); return 1; return 0; Our event handling functions can get the type of the event that arrived and handle it as needed.
OpenGL in native code I. Move performance critical rendering parts to native code Keep the window and GUI on the Java side Some features are easier to access from Java Now we are familiar with writing native code for Android. One idea that we immediately have is to program our OpenGL based application entirely in C++. However before we do this we should note that window creation is done on the Java side, and so far this window is not OpenGL capable, so we have to learn how to set it up correctly. This will be explained soon. On the other hand we should stop a little and consider not using a pure native application, as most of the Android features are much more easy to access from Java than from the C API. A typical example is the user interface, which usually takes important role in many 3D applications (and also in games). Thus it can easily turn out, that the mixed Java-native development is the most effective way of implementation. So we will start with a Java activity that passes all the rendering tasks to the native side, but can handle GUI and much more.
OpenGL in native code II. We need Anctivity with a Surface View: public class NativeEglExample extends Activity implements SurfaceHolder.Callback { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); nativeOnCreate(); setContentView(R.layout.main); SurfaceView surfaceView = (SurfaceView)findViewById(R.id.surfaceview); surfaceView.getHolder().addCallback(this); } protected void onResume() { super.onResume(); nativeOnResume(); protected void onPause() { super.onPause(); nativeOnPause(); protected void onStop() { super.onStop(); nativeOnStop(); ... We need an Activity with a SurfaceView. All handler functions will also call a corresponding native handler function too.
OpenGL in native code III. … public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { nativeSetSurface(holder.getSurface()); } public void surfaceCreated(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) { nativeSetSurface(null); public static native void nativeOnCreate(); public static native void nativeOnResume(); public static native void nativeOnPause(); public static native void nativeOnStop(); public static native void nativeSetSurface(Surface surface); static { System.loadLibrary("nativeegl"); Here are the rest of the handler functions and the native function declarations. Note that in surfaceChanged we pass the surface object to the nativeSetSurface native function. The native library is also loaded.
OpenGL in native code IV. void JNICALL …_nativeOnCreate(JNIEnv* jenv, jobject obj){ //do your initializations } void JNICALL …_nativeOnResume(JNIEnv* jenv, jobject obj){ // you can start a main loop thread here that calls render() On the native side we can do initialization in …nativeOnCreate. When onResume is called the application is in the foreground so we can start a main rendering loop here, but this should be started in a separate thread as the onResume handler should not block.
OpenGL in native code V. static ANativeWindow *window = 0; JNIEXPORT void JNICALL …_nativeSetSurface(JNIEnv* jenv, jobject obj, jobject surface) { if (surface == 0) { ANativeWindow_release(window); } else { window = ANativeWindow_fromSurface(jenv, surface); initializeGLWindow(); return; If a window and its surface is created we can get a reference of the window with ANativeWindow_fromSurface(). initGLWindow will do the main window preparation tasks.
OpenGL in native code VI. void initializeGLWindow() { const EGLint attribs[] = { EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_NONE }; EGLDisplay display; EGLConfig config; EGLint numConfigs; EGLint format; EGLSurface surface; EGLContext context; EGLint width; EGLint height; GLfloat ratio; … First we declare our constants.
OpenGL in native code VII. … display = eglGetDisplay(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY); eglInitialize(display, 0, 0); eglChooseConfig(display, attribs, &config, 1, &numConfigs); eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(window, 0, 0, format); surface = eglCreateWindowSurface(display, config, window, 0); context = eglCreateContext(display, config, 0, 0); eglMakeCurrent(display, surface, surface, context); eglQuerySurface(display, surface, EGL_WIDTH, &width); eglQuerySurface(display, surface, EGL_HEIGHT, &height); glViewport(0, 0, width, height); ratio = (GLfloat) width / height; glMatrixMode(GL_PROJECTION); glLoadIdentity(); glFrustumf(-ratio, ratio, -1, 1, 1, 10); //other GL initialization } And here is an example of making a window an OpenGL rendertarget. After the OpenGL context is created and it is made the active context we can call our usual OpenGL initializations (z-test, culling, viewport, projection matrix etc.)
OpenGL in native code VIII. void render() { //regular GL draw calls glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0, 0, -3.0f); //draw with arrays, no glBegin/glEnd in GLES glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glFrontFace(GL_CW); glVertexPointer(3, GL_FIXED, 0, vertices); glColorPointer(4, GL_FIXED, 0, colors); glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices); eglSwapBuffers(_display, _surface); } Our main rendering thread calls this render() function, which is basically the same as the OpenGL calls we use for PC development. We should note that glBegin-glEnd type of drawing is not allowed, we have to use arrays instead. Double buffer swapping is done with eglSwapBuffers(.);
OpenGL in native code IX. void JNICALL …_nativeOnStop(JNIEnv* jenv, jobject obj){ //do final cleanup } void JNICALL …_nativeOnPause(JNIEnv* jenv, jobject obj){ //do your app state save if necessary // you can end your main thread here We can stop our thread in onPause, and do final cleanup in onStop
OpenGL in native code X. (Threads) Create a thread: #include <pthread.h> pthread_t _threadId; pthread_create(&_threadId, 0, threadStartCallback, 0); Wait thread to terminate: pthread_join(_threadId, 0); Render thread example: void* threadStartCallback(void *arg) { while(running) //we can terminate this thread if needed render(); pthread_exit(0); return 0; } Here we give some hints on how to create our main rendering thread. All threads will have a thread id and can be created with pthread_create. This function asks for a callBack function that will be run in a separate thread. This is the function where we placed our rendering loop.
Pure native OpenGL app I. No Java code is written We use the native app glue android_main is called in its separate thread no additional threading needed on the native side GUI creation and accessing Android features is much harder If we wish, we can create a purely native application using the native_app_glue. As we mentioned before android_main is called in its own thread so no addition threading is needed here. This makes native implementation cleaner, but remember that GUI and accessing android features is harder than from Java.
Pure native OpenGL app II. static ANativeWindow *window = 0; void android_main(struct android_app* state) { state->onAppCmd = handle_cmd; state->onInputEvent = handle_input; while (1) { int ident, events; struct android_poll_source* source; while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) { if (source != NULL) { source->process(state, source); } if (state->destroyRequested) { return; render(); // same as before void handle_cmd(struct android_app* app, int32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: window = state->window; initializeGLWindow() ;//same as before … Here is our pure native OpenGL application. The main event processing loop is the same as in our clean pure native example, the only difference is that the render function is called in the main loop (the implementation of the render function can be the same as before). We receive an event, when the window is inited. Here the android_app struc containes the reference of the created window which is used by initializeGLWindow and its implementation is the same as before.
Using Ogre on Android Now we can use native code in our Android app Why not use Ogre3D? It is possible … Still under development (Ogre 1.9) We have to compile Ogre for Android, instructions: http://www.ogre3d.org/tikiwiki/CMake%20Quick%20Start%20Guide?tikiversion=Android We should test if Ogre works on our device Run the compiled OgreSampleBrowser Also available on Google Play (before any build) In current state (Ogre 1.9 RC1) GLES2 render system is working GLES1 is not It won’t run on emulator Min Android 2.3.3 Now that we see how to use native code in our application, and we used native library like OpenGL we can think about using some larger libraries like Ogre3D. Fortunately Ogre is designed to be multiplatform so it is possible to use Ogre on Android. We should note, that the Android port of Ogre is relatively new and still under heavy development, it is only included in v1.9. First of all we should compile the Ogre libraries for the Android platform. We can find a good guide that lists the necessary steps of this building process. However before building Ogre we should consider some important things. Currently only the GLES2 render system seems to work, GLES1 is not. This means, that we need a capable device to run our application. Android emulators can’t emulate GLES2 and shader functionalities, thus running in emulators will probably not work. The minimum Android SDK version is 2.3.3. If we have an Android device with the required capability, we can move further to the building and developing part, but if we don’t, probably we should stop here. It is also a good idea to test the OgreSampleBrowser application provided with the Ogre SDK to test Ogre and its building procedure on our system. This application can also be downloaded from google play, so the compatibility can be checked easily before any build.
Ogre3D with Java activity I. Similar to native OpenGL with Java Activity Initialization, window initialization and rendering is different Native code: static Ogre::Root* gRoot = NULL; void JNICALL …_nativeOnCreate(JNIEnv* jenv, jobject obj, jobject assetManager){ gRoot = new Ogre::Root(); gGLESPlugin = OGRE_NEW GLES2Plugin (); gRoot->installPlugin(gGLESPlugin); gOctreePlugin = OGRE_NEW OctreePlugin(); gRoot->installPlugin(gOctreePlugin); //load additional plugins (particlefx, overlay) gRoot->setRenderSystem(gRoot->getAvailableRenderers().at(0)); gRoot->initialise(false); //enable loading media files from the apk asset folder assetMgr = AAssetManager_fromJava(env, assetManager); if (assetMgr) { ArchiveManager::getSingleton().addArchiveFactory( new APKFileSystemArchiveFactory(assetMgr) ); ArchiveManager::getSingleton().addArchiveFactory( new APKZipArchiveFactory(assetMgr) ); } //do your initializations First we can have a look at an application that uses Java for window creation (and can add GUI too), and uses Ogre in native code for rendering. This is similar to our native OpenGL with Java activity. However initialization, window initialization and rendering is a bit different. OnCreate was used for application initialization, here we can add the Ogre root object initialization, plugin loading, setting the render system. These are similar codes to the calls used in the PC version. Which is important for an Android application is to tell Ogre, where to search for media files. In Android everything will be packed to an apk package and we can use its asset folder to store additional data. We will store our media files here. The AssetManager can be used to access files in the asset folder. The Android port of Ogre provides resource location managers for the asset folders which should be initialized.
Ogre3D with Java activity II. static ANativeWindow *window = 0; static Ogre::RenderWindow* gRenderWnd = NULL; JNIEXPORT void JNICALL …_nativeSetSurface(JNIEnv* jenv, jobject obj, jobject surface) { if (surface == 0) { ANativeWindow_release(window); } else { window = ANativeWindow_fromSurface(jenv, surface); initializeOgreWindow(); return; We used the setSurface function to call the window initializer, now it is the same except it calls initializeOgreWindow()
Ogre3D with Java activity III. void initializeOgreWindow() { //create render window based on an existing window Ogre::NameValuePairList opt; opt["externalWindowHandle"] = Ogre::StringConverter::toString((int)nativeWnd); AConfiguration* config = AConfiguration_new(); AConfiguration_fromAssetManager(config, assetMgr); opt["androidConfig"] = Ogre::StringConverter::toString((int)config); gRenderWnd = Ogre::Root::getSingleton().createRenderWindow("OgreWindow", 0, 0, false, &opt); ResourceGroupManager::getSingleton().addResourceLocation("/models", "APKFileSystem"); ResourceGroupManager::getSingleton().addResourceLocation("/material", "APKFileSystem"); ResourceGroupManager::getSingleton().initialiseAllResourceGroups(); //usual scene graph initialization Ogre::SceneManager* pSceneMgr = gRoot->createSceneManager(Ogre::ST_GENERIC); Ogre::Camera* pCamera = pSceneMgr->createCamera("MyCam"); pCamera->setPosition(100,100,500); pCamera->lookAt(0,0,0); Ogre::Viewport* vp = gRenderWnd->addViewport(pCamera); vp->setBackgroundColour(Ogre::ColourValue(1,0,0)); … } The initializeOgreWindow() is responsible to make the window an OpenGL render target and create an Ogre RenderWindow that uses it as render target. Ogre RenderWindows can be created based on an existing window. To do this we have to pass a window handle to Ogre when calling createRenderWindow and Ogre will render to the window with the given handle. We can add resource locations naming the subfolders of the asset folder that contains media files. Here the type of the resource path should be "APKFileSystem". Scene manager creation and filling up the scene graph can be done with the usual Ogre calls.
Ogre3D with Java activity IV. void render() { if(gRenderWnd != NULL && gRenderWnd->isActive()) try gRenderWnd->windowMovedOrResized(); gRoot->renderOneFrame(); } catch(Ogre::RenderingAPIException ex) {} The render function that is called in the main rendering loop calls RenderWindow::windowMovedOrResized and Root::renderOneFrame, which renders the contents of the scene graph.
Ogre3D with Java activity V. Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := OgreJNI LOCAL_LDLIBS := -landroid -lc -lm -ldl -llog -lEGL -lGLESv2 LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/lib LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/AndroidDependencies/lib/armeabi-v7a LOCAL_LDLIBS += -lPlugin_OctreeSceneManagerStatic -lRenderSystem_GLES2Static -lOgreMainStatic LOCAL_LDLIBS += -lzzip -lz -lFreeImage -lfreetype -lOIS LOCAL_LDLIBS += -l$(OGRE_ANDROID_PATH)/systemlibs/armeabi-v7a/libsupc++.a LOCAL_LDLIBS += -l$(OGRE_ANDROID_PATH)/systemlibs/armeabi-v7a/libstdc++.a LOCAL_STATIC_LIBRARIES := cpufeatures LOCAL_CFLAGS := -DGL_GLEXT_PROTOTYPES=1 LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)OgreMain/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)RenderSystems/GLES2/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)RenderSystems/GLES2/include/EGL LOCAL_CFLAGS += -I$(ANDROID_NDK)/sources/cpufeatures LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)PlugIns/OctreeSceneManager/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)AndroidDependencies/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)AndroidDependencies/include/OIS LOCAL_CFLAGS += -fexceptions -frtti -x c++ -D___ANDROID___ -DANDROID -DZZIP_OMIT_CONFIG_H LOCAL_SRC_FILES := MainActivity.cpp include $(BUILD_SHARED_LIBRARY) $(call import-module,android/cpufeatures) Here we can find a sample Android.mk configuration file for an Android project. The OGRE_ANDROID_PATH variable should be set to the folder of the compiled Ogre Android SDK. Necessary used libraries with their location paths are given, as well as the include paths.
Using Ogre in pure native app I. Similar to pure native OpenGL application Code: //globals RenderWindow* gRenderWindow = NULL; Root* gRoot = NULL; static ANativeWindow *window = NULL; We can use Ogre in a pure native application too. This will be similar to our pure native OpenGL application.
Using Ogre in pure native app II. void android_main(struct android_app* state) { app_dummy(); gRoot = new Ogre::Root(); gRoot >installPlugin(OGRE_NEW GLES2Plugin()); gRoot >installPlugin(OGRE_NEW OctreePlugin()); gRoot >setRenderSystem(root->getAvailableRenderers().at(0)); gRoot >initialise(false); ArchiveManager::getSingleton().addArchiveFactory( new APKFileSystemArchiveFactory(state->activity->assetManager) ); ArchiveManager::getSingleton().addArchiveFactory( new APKZipArchiveFactory(state->activity->assetManager) ); state->onAppCmd = handleCmd; int ident, events; struct android_poll_source* source; while (true){ while ((ident = ALooper_pollAll(0, NULL, &events, (void**)&source)) >= 0){ if (source != NULL) source->process(state, source); if (state->destroyRequested != 0) return; } if(renderWindow != NULL && renderWindow->isActive()){ gRenderWindow->windowMovedOrResized(); gRoot->renderOneFrame(); Root initialization, plugin loading media file location settings can be done right at the beginning of android_main. The android_main function also defines a main event loop as usual and calls windowMovedOrResized and renderOneFrame just in case of Java+native Ogre application.
Using Ogre in pure native app III. void handleCmd(struct android_app* app, int32_t cmd){ switch (cmd){ case APP_CMD_SAVE_STATE: break; case APP_CMD_INIT_WINDOW: window = state->window; initializeOgreWindow(); //same as above case APP_CMD_TERM_WINDOW: if(gRoot && gRenderWindow) static_cast<AndroidEGLWindow*>(gRenderWindow)->_destroyInternalResources(); } The event handler function is also similar to the one listed in case of pure native OpenGL application, but initializeOgreWindow is called when the window is inited (the implementation is the same as above). The Android.mk is basically the same as in case of the previous Ogre application, but we should also link to the native_app_glue, and here we also should set to use android.app.NativeActivity as main activity in AndroidManifest.xml (see slide 12).
The End Now that we can create 3D graphics in our Android applications using the Java OpenGL interface provided by Android, we can examine the possibility to do the 3D programming in C or C++ to access the OpenGL API directly. To do this we will need native code development on Android. We will also examine the possibility to use the Ogre3D engine in our applications to access its high level features.