3D graphics on Android projects based on native code
Download
Report
Transcript 3D graphics on Android projects based on native code
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)
include $(CLEAR_VARS)
Name of the library file to
be created
Source file in ./jni that
contains native code
definitions
LOCAL_MODULE := myLibName
LOCAL_SRC_FILES := mySourceFile.cpp
include $(BUILD_SHARED_LIBRARY)
Android.mk advanced
Additional used libraries
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
Additional library path
LOCAL_LDLIBS += -lMyAPI
LOCAL_STATIC_LIBRARIES := cpufeatures
Additional include path
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)
Source file not in ./jni,
Several source files can be
$(call import-module,android/cpufeatures)
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
break;
case APP_CMD_TERM_WINDOW:
if(gRoot && gRenderWindow)
static_cast<AndroidEGLWindow*>(gRenderWindow)>_destroyInternalResources();
break;
}
}