36
3D graphics on Android projects based on native code Native OpenGL, OGRE3D on Android

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

Embed Size (px)

Citation preview

Billboard, Particle System

3D graphics on Android projects based on native codeNative OpenGL, OGRE3D on AndroidNow 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.1Native code?What is native code?C/C++ codeUsing C/C++ API-s directlyWhy we need it, what should be moved into nativeIn most situations native code should be avoidedPerformance critical partsUsing external cross platform APIsNative 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.2Native code in an Android projectNeed separate SDK: Android NDKCross compiling tools: ndk-buildMust be familiar with Java Native Interface (JNI)Have to place native code in ./jni folderNeed special files in ./jni:Android.mkApplication.mk (optional)Have to call ndk-build before project buildShould load the compiled library in Java codeUse 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.3Java Native Interface I.Allows execution of native code from JavaNative code is compiled into a dynamic libraryLibrary should be loaded with System.loadLibrary(String libraryName)Library is accesed through native function callsNative 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).4Java Native Interface II.Native function declarations are special:Name expresses the package and class that declared ithave special input parametersCan be generated with javah:javah test.jni.A (A.class should be generated first)javah output:

#ifdef __cplusplusextern "C" {#endifJNIEXPORT void JNICALL Java_test_jni_A_myNativeFunc (JNIEnv *, jobject);#ifdef __cplusplus}#endif

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).5Java Native Interface III.Native code should be compiled into a libraryCompiled library should be accessible by the Java applicationin case of Android it should be packed into the apkAfter 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)6Android native supportjavah can be used to generate native function declarationsndk-build can be used to compile the native code to a library fileNdk-build needs:Source files containing native function definitions They are typically located in ./jniAndroid.mk configuration file in ./jniThis configuration file should be properly filledAfter 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.).7Android.mk simpleLOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := myLibNameLOCAL_SRC_FILES := mySourceFile.cpp

include $(BUILD_SHARED_LIBRARY)Name of the library file to be createdSource file in ./jni that contains native code definitionsNow 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).8Android.mk advancedLOCAL_PATH := $(call my-dir)include$(CLEAR_VARS)LOCAL_MODULE := OgreJNI LOCAL_LDLIBS:= -landroid -lc -lm -ldl -llog -lEGL -lGLESv2LOCAL_LDLIBS+= -L./../MyAPI/libLOCAL_LDLIBS+= -lMyAPILOCAL_STATIC_LIBRARIES := cpufeaturesLOCAL_CFLAGS := -I./../MyAPI/includeLOCAL_CFLAGS += -fexceptions -frtti -x c++ -D___ANDROID___ -DANDROID -DZZIP_OMIT_CONFIG_HLOCAL_SRC_FILES := ./MyCode/MainActivity.cppinclude $(BUILD_SHARED_LIBRARY)$(call import-module,android/cpufeatures)Additional used librariesAdditional library pathAdditional include pathSource file not in ./jni,Several source files can be listedIn 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.9NetBeans/Eclipse native supportNetBeans does not provide additional support for native code in AndroidEclipseRight click on project Android Tools Add native supportjni folder, Android.mk, empty cpp file created automaticallyAndroid.mk refers to new cpp fileNdk-build called automatically before Java buildUnfortunatelly 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.10Pure Native Android Application I.At least we need a dummy Java Activity to call our native functionsWe also have a built in dummy activityCalls android_main native functionSeparate threadCallbacks for window and input commandsandroid.app.NativeActivityandroid_native_app_glue libraryJNI 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.11Pure Native Android Application II.Android .mkLOCAL_STATIC_LIBRARIES := android_native_app_glueinclude $(BUILD_SHARED_LIBRARY)$(call import-module,android/native_app_glue)AndroidManifest.xml

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.13Pure 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.14OpenGL in native code I.Move performance critical rendering parts to native codeKeep the window and GUI on the Java sideSome 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.15OpenGL 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.16OpenGL 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.17OpenGL in native code IV.Native code:void JNICALL _nativeOnCreate(JNIEnv* jenv, jobject obj){//do your initializations}void JNICALL _nativeOnResume(JNIEnv* jenv, jobject obj){//do your initializations// 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.18OpenGL 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.19OpenGL 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.20OpenGL 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.)21OpenGL 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(.);22OpenGL 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 onStop23OpenGL in native code X. (Threads)Create a thread:#include 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 neededrender(); 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.24Pure native OpenGL app I.No Java code is writtenWe use the native app glueandroid_main is called in its separate threadno additional threading needed on the native sideGUI creation and accessing Android features is much harderIf 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.25Pure 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.26Using Ogre on AndroidNow we can use native code in our Android appWhy 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=AndroidWe should test if Ogre works on our deviceRun the compiled OgreSampleBrowserAlso available on Google Play (before any build)In current state (Ogre 1.9 RC1) GLES2 render system is working GLES1 is notIt wont run on emulatorMin 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 cant 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 dont, 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.27Ogre3D with Java activity I.Similar to native OpenGL with Java ActivityInitialization, window initialization and rendering is differentNative 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 folderassetMgr = 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.28Ogre3D 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()29Ogre3D with Java activity III.void initializeOgreWindow(){//create render window based on an existing windowOgre::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 initializationOgre::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.30Ogre3D 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.31Ogre3D with Java activity V.Android.mk

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := OgreJNILOCAL_LDLIBS := -landroid -lc -lm -ldl -llog -lEGL -lGLESv2LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/lib LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/AndroidDependencies/lib/armeabi-v7aLOCAL_LDLIBS += -lPlugin_OctreeSceneManagerStatic -lRenderSystem_GLES2Static -lOgreMainStaticLOCAL_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.32Using Ogre in pure native app I.Similar to pure native OpenGL applicationCode://globalsRenderWindow* 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.33Using 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.34Using 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 break;case APP_CMD_TERM_WINDOW:if(gRoot && gRenderWindow)static_cast(gRenderWindow)->_destroyInternalResources();break;}}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).35The EndNow 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.36