3D graphics on Android projects based on native code

advertisement

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

 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.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();}

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 );

}

#ifdef __cplusplus

#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

NetBeans for e.g.)

Android.mk simple

LOCAL_PATH := $(call my-dir)

Name of the library file to be created include $(CLEAR_VARS)

LOCAL_MODULE :=

myLibName

LOCAL_SRC_FILES := mySourceFile.cpp

Source file in ./jni that contains native code definitions include $(BUILD_SHARED_LIBRARY)

Android.mk advanced

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/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

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

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

"

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;

}

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;

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

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();

}

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");

}

OpenGL 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()

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;

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;

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

}

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);

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

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;

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

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

}

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

}

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

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;

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));

Ogre3D with Java activity IV.

}

{ void render()

{ if(gRenderWnd != NULL && gRenderWnd->isActive()) try

{ gRenderWnd->windowMovedOrResized(); gRoot->renderOneFrame();

} catch(Ogre::RenderingAPIException ex) {}

}

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)

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;

}

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();

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 if(gRoot && gRenderWindow) static_cast<AndroidEGLWindow*>(gRenderWindow)-

>_destroyInternalResources(); break;

} break; case APP_CMD_TERM_WINDOW:

Download