Native OpenGL, OGRE3D on Android
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!
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)
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();}
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 );
}
#ifdef __cplusplus
#endif
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
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.)
LOCAL_PATH := $(call my-dir)
Name of the library file to be created include $(CLEAR_VARS)
LOCAL_MODULE :=
LOCAL_SRC_FILES := mySourceFile.cpp
Source file in ./jni that contains native code definitions include $(BUILD_SHARED_LIBRARY)
LOCAL_PATH := $(call my-dir) include$(CLEAR_VARS)
LOCAL_LDLIBS += -L./../MyAPI/lib
Additional used libraries
LOCAL_MODULE := OgreJNI
LOCAL_LDLIBS := -landroid -lc -lm -ldl -llog -lEGL -lGLESv2
Additional library path
LOCAL_LDLIBS += -lMyAPI
LOCAL_STATIC_LIBRARIES := cpufeatures
LOCAL_CFLAGS := -I./../MyAPI/include Additional include path
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)
Source file not in ./jni,
Several source files can be listed
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
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
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
"
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;
}
} 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;
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
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();
}
…
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");
}
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()
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;
{ 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;
…
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
}
{ 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);
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
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;
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
} 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
}
…
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
}
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
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;
{ 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));
…
}
{ void render()
{ if(gRenderWnd != NULL && gRenderWnd->isActive()) try
{ gRenderWnd->windowMovedOrResized(); gRoot->renderOneFrame();
} catch(Ogre::RenderingAPIException ex) {}
}
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)
Similar to pure native OpenGL application
Code:
//globals
RenderWindow* gRenderWindow = NULL;
Root* gRoot = NULL; static ANativeWindow *window = NULL;
}
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();
} 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 if(gRoot && gRenderWindow) static_cast<AndroidEGLWindow*>(gRenderWindow)-
>_destroyInternalResources(); break;
} break; case APP_CMD_TERM_WINDOW: