3 D graphics on Android projects based on
3 D graphics on Android projects based on native code Native Open. GL, OGRE 3 D 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 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)
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. load. Library(String library. Name) 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. load. Library(”my. Lib. Name”); } protected native void my. Native. Func(); protected void my. Func(){my. Native. Func(); } }
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 __cplus extern "C" { #endif JNIEXPORT void JNICALL Java_test_jni_A_my. Native. Func (JNIEnv *, jobject); #ifdef __cplus } #endif
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
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 Net. Beans for e. g. )
Android. mk simple LOCAL_PATH : = $(call my-dir) Name of the library file to be created include $(CLEAR_VARS) Source file in. /jni that contains native code definitions LOCAL_MODULE : = my. Lib. Name LOCAL_SRC_FILES : = my. Source. File. cpp include $(BUILD_SHARED_LIBRARY)
Android. mk advanced Additional used libraries LOCAL_PATH : = $(call my-dir) include$(CLEAR_VARS) LOCAL_MODULE : = Ogre. JNI LOCAL_LDLIBS : = -landroid -lc -lm -ldl -llog -l. EGL -l. GLESv 2 LOCAL_LDLIBS += -L. /. . /My. API/lib Additional library path LOCAL_LDLIBS += -l. My. API LOCAL_STATIC_LIBRARIES : = cpufeatures Additional include path LOCAL_CFLAGS : = -I. /. . /My. API/include LOCAL_CFLAGS += -fexceptions -frtti -x c++ -D___ANDROID___ DANDROID -DZZIP_OMIT_CONFIG_H LOCAL_SRC_FILES : =. /My. Code/Main. Activity. cpp include $(BUILD_SHARED_LIBRARY) Source file not in. /jni, Several source files can be $(call import-module, android/cpufeatures) listed
Net. Beans/Eclipse native support Net. Beans 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
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. Native. Activity android_native_app_glue library
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) Android. Manifest. xml <uses-sdk android: min. Sdk. Version="9" /> <activity android: name="android. app. Native. Activity"
Pure Native Android Application III. Source file example: void android_main(struct android_app* state) { state->on. App. Cmd = handle_cmd; state->on. Input. Event = handle_input; while (1) { // Read all pending events. int ident; int events; struct android_poll_source* source; while ((ident = ALooper_poll. All(-1, NULL, &events, (void**)&source)) >= 0) { // Process this event. if (source != NULL) { source->process(state, source); } } … } } if (state->destroy. Requested) { return; }
Pure Native Android Application IV. void handle_cmd(struct android_app* app, int 32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: … } int 32_t handle_input(struct android_app* app, AInput. Event* event) { if(AInput. Event_get. Type(event) == AINPUT_EVENT_TYPE_MOTION) { float x = AMotion. Event_get. X(event, 0); … return 1; } return 0; }
Open. GL 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
Open. GL in native code II. We need Anctivity with a Surface View: public class Native. Egl. Example extends Activity implements Surface. Holder. Callback { public void on. Create(Bundle saved. Instance. State) { super. on. Create(saved. Instance. State); native. On. Create(); set. Content. View(R. layout. main); Surface. View surface. View = (Surface. View)find. View. By. Id(R. id. surfaceview); surface. View. get. Holder(). add. Callback(this); } protected void on. Resume() { super. on. Resume(); native. On. Resume(); } protected void on. Pause() { super. on. Pause(); native. On. Pause(); } . . . protected void on. Stop() { super. on. Stop(); native. On. Stop(); }
Open. GL in native code III. … public void surface. Changed(Surface. Holder holder, int format, int w, int h) { native. Set. Surface(holder. get. Surface()); } public void surface. Created(Surface. Holder holder) { } public void surface. Destroyed(Surface. Holder holder) { native. Set. Surface(null); } public static native void native. On. Create(); public static native void native. On. Resume(); public static native void native. On. Pause(); public static native void native. On. Stop(); public static native void native. Set. Surface(Surface surface); } static { System. load. Library("nativeegl"); }
Open. GL in native code IV. Native code: void JNICALL …_native. On. Create(JNIEnv* jenv, jobject obj){ //do your initializations } void JNICALL …_native. On. Resume(JNIEnv* jenv, jobject obj){ //do your initializations // you can start a main loop thread here that calls render() }
Open. GL in native code V. static ANative. Window *window = 0; JNIEXPORT void JNICALL …_native. Set. Surface(JNIEnv* jenv, jobject obj, jobject surface) { if (surface == 0) { ANative. Window_release(window); } else { window = ANative. Window_from. Surface(jenv, surface); initialize. GLWindow(); } return; }
Open. GL in native code VI. void initialize. GLWindow() { 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 num. Configs; EGLint format; EGLSurface surface; EGLContext context; EGLint width; EGLint height; GLfloat ratio;
Open. GL in native code VII. … display = egl. Get. Display(EGL_DEFAULT_DISPLAY)) == EGL_NO_DISPLAY); egl. Initialize(display, 0, 0); egl. Choose. Config(display, attribs, &config, 1, &num. Configs); egl. Get. Config. Attrib(display, config, EGL_NATIVE_VISUAL_ID, &format); ANative. Window_set. Buffers. Geometry(window, 0, 0, format); surface = egl. Create. Window. Surface(display, config, window, 0); context = egl. Create. Context(display, config, 0, 0); egl. Make. Current(display, surface, context); egl. Query. Surface(display, surface, EGL_WIDTH, &width); egl. Query. Surface(display, surface, EGL_HEIGHT, &height); gl. Viewport(0, 0, width, height); ratio = (GLfloat) width / height; gl. Matrix. Mode(GL_PROJECTION); gl. Load. Identity(); gl. Frustumf(-ratio, -1, 1, 1, 10); //other GL initialization }
Open. GL in native code VIII. void render() { //regular GL draw calls gl. Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); gl. Matrix. Mode(GL_MODELVIEW); gl. Load. Identity(); gl. Translatef(0, 0, -3. 0 f); //draw with arrays, no gl. Begin/gl. End in GLES gl. Enable. Client. State(GL_VERTEX_ARRAY); gl. Enable. Client. State(GL_COLOR_ARRAY); gl. Front. Face(GL_CW); gl. Vertex. Pointer(3, GL_FIXED, 0, vertices); gl. Color. Pointer(4, GL_FIXED, 0, colors); gl. Draw. Elements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices); } egl. Swap. Buffers(_display, _surface);
Open. GL in native code IX. void JNICALL …_native. On. Stop(JNIEnv* jenv, jobject obj){ //do final cleanup } void JNICALL …_native. On. Pause(JNIEnv* jenv, jobject obj){ //do your app state save if necessary // you can end your main thread here }
Open. GL in native code X. (Threads) Create a thread: #include <pthread. h> pthread_t _thread. Id; pthread_create(&_thread. Id, 0, thread. Start. Callback, 0); Wait thread to terminate: pthread_join(_thread. Id, 0); Render thread example: void* thread. Start. Callback(void *arg) { while(running) //we can terminate this thread if needed render(); pthread_exit(0); return 0; }
Pure native Open. GL 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
Pure native Open. GL app II. static ANative. Window *window = 0; void android_main(struct android_app* state) { state->on. App. Cmd = handle_cmd; state->on. Input. Event = handle_input; while (1) { int ident, events; struct android_poll_source* source; while ((ident = ALooper_poll. All(-1, NULL, &events, (void**)&source)) >= 0) { if (source != NULL) { source->process(state, source); } if (state->destroy. Requested) { return; } } render(); // same as before } } void handle_cmd(struct android_app* app, int 32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: window = state->window; initialize. GLWindow() ; //same as before … }
Using Ogre on Android Now we can use native code in our Android app Why not use Ogre 3 D? It is possible … Still under development (Ogre 1. 9) We have to compile Ogre for Android, instructions: http: //www. ogre 3 d. org/tikiwiki/CMake%20 Quick%20 Start%20 Guide ? tikiversion=Android We should test if Ogre works on our device Run the compiled Ogre. Sample. Browser Also available on Google Play (before any build) In current state (Ogre 1. 9 RC 1) GLES 2 render system is working GLES 1 is not It won’t run on emulator Min Android 2. 3. 3
Ogre 3 D with Java activity I. Similar to native Open. GL with Java Activity Initialization, window initialization and rendering is different Native code: static Ogre: : Root* g. Root = NULL; void JNICALL …_native. On. Create(JNIEnv* jenv, jobject obj, jobject asset. Manager){ g. Root = new Ogre: : Root(); g. GLESPlugin = OGRE_NEW GLES 2 Plugin (); g. Root->install. Plugin(g. GLESPlugin); g. Octree. Plugin = OGRE_NEW Octree. Plugin(); g. Root->install. Plugin(g. Octree. Plugin); //load additional plugins (particlefx, overlay) g. Root->set. Render. System(g. Root->get. Available. Renderers(). at(0)); g. Root->initialise(false); //enable loading media files from the apk asset folder asset. Mgr = AAsset. Manager_from. Java(env, asset. Manager); if (asset. Mgr) { Archive. Manager: : get. Singleton(). add. Archive. Factory( new APKFile. System. Archive. Factory(asset. Mgr) ); Archive. Manager: : get. Singleton(). add. Archive. Factory( new APKZip. Archive. Factory(asset. Mgr) ); } //do your initializations }
Ogre 3 D with Java activity II. static ANative. Window *window = 0; static Ogre: : Render. Window* g. Render. Wnd = NULL; JNIEXPORT void JNICALL …_native. Set. Surface(JNIEnv* jenv, jobject obj, jobject surface) { if (surface == 0) { ANative. Window_release(window); } else { window = ANative. Window_from. Surface(jenv, surface); initialize. Ogre. Window(); } return; }
Ogre 3 D with Java activity III. void initialize. Ogre. Window() { //create render window based on an existing window Ogre: : Name. Value. Pair. List opt; opt["external. Window. Handle"] = Ogre: : String. Converter: : to. String((int)native. Wnd); AConfiguration* config = AConfiguration_new(); AConfiguration_from. Asset. Manager(config, asset. Mgr); opt["android. Config"] = Ogre: : String. Converter: : to. String((int)config); g. Render. Wnd = Ogre: : Root: : get. Singleton(). create. Render. Window("Ogre. Window", 0, 0, false, &opt); Resource. Group. Manager: : get. Singleton(). add. Resource. Location("/models", "APKFile. System"); Resource. Group. Manager: : get. Singleton(). add. Resource. Location("/material", "APKFile. System"); Resource. Group. Manager: : get. Singleton(). initialise. All. Resource. Groups(); } //usual scene graph initialization Ogre: : Scene. Manager* p. Scene. Mgr = g. Root->create. Scene. Manager(Ogre: : ST_GENERIC); Ogre: : Camera* p. Camera = p. Scene. Mgr->create. Camera("My. Cam"); p. Camera->set. Position(100, 500); p. Camera->look. At(0, 0, 0); Ogre: : Viewport* vp = g. Render. Wnd->add. Viewport(p. Camera); vp->set. Background. Colour(Ogre: : Colour. Value(1, 0, 0)); …
Ogre 3 D with Java activity IV. void render() { if(g. Render. Wnd != NULL && g. Render. Wnd->is. Active()) { try { g. Render. Wnd->window. Moved. Or. Resized(); g. Root->render. One. Frame(); } catch(Ogre: : Rendering. APIException ex) {} } }
Ogre 3 D with Java activity V. Android. mk LOCAL_PATH : = $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE : = Ogre. JNI LOCAL_LDLIBS : = -landroid -lc -lm -ldl -llog -l. EGL -l. GLESv 2 LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/lib LOCAL_LDLIBS += -L$(OGRE_ANDROID_PATH)/Android. Dependencies/lib/armeabi-v 7 a LOCAL_LDLIBS += -l. Plugin_Octree. Scene. Manager. Static -l. Render. System_GLES 2 Static -l. Ogre. Main. Static LOCAL_LDLIBS += -lzzip -lz -l. Free. Image -lfreetype -l. OIS LOCAL_LDLIBS += -l$(OGRE_ANDROID_PATH)/systemlibs/armeabi-v 7 a/libsupc++. a LOCAL_LDLIBS += -l$(OGRE_ANDROID_PATH)/systemlibs/armeabi-v 7 a/libstdc++. a LOCAL_STATIC_LIBRARIES : = cpufeatures LOCAL_CFLAGS : = -DGL_GLEXT_PROTOTYPES=1 LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)Ogre. Main/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)Render. Systems/GLES 2/include/EGL LOCAL_CFLAGS += -I$(ANDROID_NDK)/sources/cpufeatures LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)Plug. Ins/Octree. Scene. Manager/include LOCAL_CFLAGS += -I$(OGRE_ANDROID_PATH)Android. Dependencies/include/OIS LOCAL_CFLAGS += -fexceptions -frtti -x c++ -D___ANDROID___ -DANDROID -DZZIP_OMIT_CONFIG_H LOCAL_SRC_FILES : = Main. Activity. cpp include $(BUILD_SHARED_LIBRARY) $(call import-module, android/cpufeatures)
Using Ogre in pure native app I. Similar to pure native Open. GL application Code: //globals Render. Window* g. Render. Window = NULL; Root* g. Root = NULL; static ANative. Window *window = NULL;
Using Ogre in pure native app II. void android_main(struct android_app* state) { app_dummy(); g. Root = new Ogre: : Root(); g. Root >install. Plugin(OGRE_NEW GLES 2 Plugin()); g. Root >install. Plugin(OGRE_NEW Octree. Plugin()); g. Root >set. Render. System(root->get. Available. Renderers(). at(0)); g. Root >initialise(false); Archive. Manager: : get. Singleton(). add. Archive. Factory( new APKFile. System. Archive. Factory(state->activity->asset. Manager) ); Archive. Manager: : get. Singleton(). add. Archive. Factory( new APKZip. Archive. Factory(state->activity->asset. Manager) ); state->on. App. Cmd = handle. Cmd; } int ident, events; struct android_poll_source* source; while (true){ while ((ident = ALooper_poll. All(0, NULL, &events, (void**)&source)) >= 0){ if (source != NULL) source->process(state, source); if (state->destroy. Requested != 0) return; } if(render. Window != NULL && render. Window->is. Active()){ g. Render. Window->window. Moved. Or. Resized(); g. Root->render. One. Frame(); } }
Using Ogre in pure native app III. void handle. Cmd(struct android_app* app, int 32_t cmd){ switch (cmd){ case APP_CMD_SAVE_STATE: break; case APP_CMD_INIT_WINDOW: window = state->window; initialize. Ogre. Window(); //same as above break; case APP_CMD_TERM_WINDOW: if(g. Root && g. Render. Window) static_cast<Android. EGLWindow*>(g. Render. Window)>_destroy. Internal. Resources(); break; } }
The End
- Slides: 36