From 4059cb46f19e44eaf85da18483c2bc25c989beac Mon Sep 17 00:00:00 2001 From: pelya Date: Mon, 17 May 2010 18:39:02 +0300 Subject: [PATCH] THIS REVISION WILL NOT RUN! Added an own implementation of GLSurfaceView to launch native main() code from it. --- alienblaster/ChangeAppSettings.sh | 9 +- .../sdl/src/video/android/SDL_androidvideo.c | 379 ++--- alienblaster/project/jni/sdl_main/sdl_main.c | 20 +- alienblaster/project/src/DemoActivity.java | 87 +- .../project/src/GLSurfaceView_SDL.java | 1355 +++++++++++++++++ 5 files changed, 1552 insertions(+), 298 deletions(-) create mode 100644 alienblaster/project/src/GLSurfaceView_SDL.java diff --git a/alienblaster/ChangeAppSettings.sh b/alienblaster/ChangeAppSettings.sh index efdef2215..55ccfceea 100755 --- a/alienblaster/ChangeAppSettings.sh +++ b/alienblaster/ChangeAppSettings.sh @@ -61,9 +61,16 @@ cat project/AndroidManifest.xml | \ project/AndroidManifest.xml.1 mv -f project/AndroidManifest.xml.1 project/AndroidManifest.xml +for F in project/src/*.java; do + echo Patching $F + cat $F | \ + sed "s/package .*;/package $AppFullName;/" > \ + $F.1 + mv -f $F.1 $F +done + echo Patching project/src/DemoActivity.java cat project/src/DemoActivity.java | \ - sed "s/package .*;/package $AppFullName;/" | \ sed "s/public static String ApplicationName = .*;/public static String ApplicationName = \"$AppShortName\";/" | \ sed "s^public static String DataDownloadUrl = \".*\";^public static String DataDownloadUrl = \"$AppDataDownloadUrl1\";^" | \ sed "s/public static boolean DownloadToSdcard = .*;/public static boolean DownloadToSdcard = $DownloadToSdcard1;/" > \ diff --git a/alienblaster/project/jni/sdl/src/video/android/SDL_androidvideo.c b/alienblaster/project/jni/sdl/src/video/android/SDL_androidvideo.c index 5b5ab6217..7c6ca3756 100644 --- a/alienblaster/project/jni/sdl/src/video/android/SDL_androidvideo.c +++ b/alienblaster/project/jni/sdl/src/video/android/SDL_androidvideo.c @@ -125,20 +125,20 @@ static void * memBuffer1 = NULL; static void * memBuffer2 = NULL; static void * memBuffer = NULL; static int sdl_opengl = 0; -// We have one Java thread drawing on GL surface, and another native C thread (typically main()) feeding it with video data -extern SDL_Thread * SDL_mainThread; -SDL_Thread * SDL_mainThread = NULL; -// Some wicked multithreading -static SDL_mutex * WaitForNativeRender = NULL; -static SDL_cond * WaitForNativeRender1 = NULL; -static enum { Render_State_Started, Render_State_Processing, Render_State_Finished } - WaitForNativeRenderState = Render_State_Finished; // Some wicked GLES stuff -static enum { GL_State_Init, GL_State_Ready, GL_State_Uninit, GL_State_Uninit2 } openglInitialized = GL_State_Uninit2; static GLuint texture = 0; +// Extremely wicked JNI environment to call Java functions from C code +static JNIEnv* JavaEnv = NULL; +static jclass JavaRendererClass = NULL; +static jobject JavaRenderer = NULL; +static jmethodID JavaSwapBuffers = NULL; + + static SDLKey keymap[KEYCODE_LAST+1]; +static int CallJavaSwapBuffers(); +static void SdlGlRenderInit(); static int processAndroidTrackballKeyDelays( int key, int action ); /* ANDROID driver bootstrap functions */ @@ -272,7 +272,7 @@ SDL_Surface *ANDROID_SetVideoMode(_THIS, SDL_Surface *current, memX = width; memY = height; - //if( ! sdl_opengl ) + if( ! sdl_opengl ) { memBuffer1 = SDL_malloc(memX * memY * (bpp / 8)); if ( ! memBuffer1 ) { @@ -295,8 +295,6 @@ SDL_Surface *ANDROID_SetVideoMode(_THIS, SDL_Surface *current, memBuffer = memBuffer1; } - openglInitialized = GL_State_Init; - /* Allocate the new pixel format for the screen */ if ( ! SDL_ReallocFormat(current, bpp, 0, 0, 0, 0) ) { if(memBuffer) @@ -313,15 +311,9 @@ SDL_Surface *ANDROID_SetVideoMode(_THIS, SDL_Surface *current, current->h = height; current->pitch = memX * (bpp / 8); current->pixels = memBuffer; - - if( ! WaitForNativeRender ) - { - WaitForNativeRender = SDL_CreateMutex(); - WaitForNativeRender1 = SDL_CreateCond(); - } - /* Wait 'till we can draw */ - ANDROID_FlipHWSurface(this, current); + SdlGlRenderInit(); + /* We're done */ return(current); } @@ -331,9 +323,12 @@ SDL_Surface *ANDROID_SetVideoMode(_THIS, SDL_Surface *current, */ void ANDROID_VideoQuit(_THIS) { - openglInitialized = GL_State_Uninit; - while( openglInitialized != GL_State_Uninit2 ) - SDL_Delay(50); + if( ! sdl_opengl ) + { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glDeleteTextures(1, &texture); + } memX = 0; memY = 0; @@ -343,10 +338,6 @@ void ANDROID_VideoQuit(_THIS) if( memBuffer2 ) SDL_free( memBuffer2 ); memBuffer2 = NULL; - SDL_DestroyMutex( WaitForNativeRender ); - WaitForNativeRender = NULL; - SDL_DestroyCond( WaitForNativeRender1 ); - WaitForNativeRender1 = NULL; int i; @@ -397,37 +388,15 @@ static void ANDROID_UpdateRects(_THIS, int numrects, SDL_Rect *rects) static int ANDROID_FlipHWSurface(_THIS, SDL_Surface *surface) { - if( ! WaitForNativeRender ) + if( ! sdl_opengl ) { - __android_log_print(ANDROID_LOG_ERROR, "libSDL", "FlipHWSurface: called before SetVideoMode"); - return 0; - } - SDL_mutexP(WaitForNativeRender); - while( WaitForNativeRenderState != Render_State_Finished ) - { - if( SDL_CondWaitTimeout( WaitForNativeRender1, WaitForNativeRender, 5000 ) != 0 ) - { - __android_log_print(ANDROID_LOG_INFO, "libSDL", "FlipHWSurface: Frame failed to render"); - SDL_mutexV(WaitForNativeRender); - return(0); - } - } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, memX, memY, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, memBuffer); + if( sWindowHeight < memY || sWindowWidth < memX ) + glDrawTexiOES(0, 0, 1, sWindowWidth, sWindowHeight); // Larger than screen - shrink to fit + else + glDrawTexiOES(0, sWindowHeight-memY, 1, memX, memY); // Smaller than screen - do not scale, it's faster that way - WaitForNativeRenderState = Render_State_Started; - - SDL_mutexV(WaitForNativeRender); - SDL_CondSignal(WaitForNativeRender1); - SDL_mutexP(WaitForNativeRender); - - if( WaitForNativeRenderState == Render_State_Started ) - if( SDL_CondWaitTimeout( WaitForNativeRender1, WaitForNativeRender, 5000 ) != 0 ) - { - __android_log_print(ANDROID_LOG_INFO, "libSDL", "FlipHWSurface: Frame rendering timed out"); - } - - if( WaitForNativeRenderState != Render_State_Started ) - { - if( ! sdl_opengl && surface && surface->flags & SDL_DOUBLEBUF ) + if( surface->flags & SDL_DOUBLEBUF ) { if( memBuffer == memBuffer1 ) memBuffer = memBuffer2; @@ -437,9 +406,11 @@ static int ANDROID_FlipHWSurface(_THIS, SDL_Surface *surface) } } - SDL_mutexV(WaitForNativeRender); + CallJavaSwapBuffers(); processAndroidTrackballKeyDelays( -1, 0 ); + + SDL_Delay(10); return(0); }; @@ -447,7 +418,6 @@ static int ANDROID_FlipHWSurface(_THIS, SDL_Surface *surface) void ANDROID_GL_SwapBuffers(_THIS) { ANDROID_FlipHWSurface(this, NULL); - //__android_log_print(ANDROID_LOG_INFO, "libSDL", "GL_SwapBuffers: Frame rendered"); }; int ANDROID_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors) @@ -476,14 +446,15 @@ JAVA_EXPORT_NAME(DemoRenderer_nativeResize) ( JNIEnv* env, jobject thiz, jint extern void JAVA_EXPORT_NAME(DemoRenderer_nativeDone) ( JNIEnv* env, jobject thiz ) { - if( SDL_mainThread ) - { - __android_log_print(ANDROID_LOG_INFO, "libSDL", "quitting..."); - SDL_PrivateQuit(); - SDL_WaitThread(SDL_mainThread, NULL); - SDL_mainThread = NULL; - __android_log_print(ANDROID_LOG_INFO, "libSDL", "quit OK"); - } + __android_log_print(ANDROID_LOG_INFO, "libSDL", "quitting..."); + SDL_PrivateQuit(); + __android_log_print(ANDROID_LOG_INFO, "libSDL", "quit OK"); +} + +extern void +JAVA_EXPORT_NAME(AccelerometerReader_nativeAccelerometer) ( JNIEnv* env, jobject thiz, jfloat accX, jfloat accY, jfloat accZ ) +{ + // TODO: use accelerometer as joystick } enum MOUSE_ACTION { MOUSE_DOWN = 0, MOUSE_UP=1, MOUSE_MOVE=2 }; @@ -518,78 +489,9 @@ static SDL_keysym *TranslateKey(int scancode, SDL_keysym *keysym) return(keysym); } -static int AndroidTrackballKeyDelays[4] = {0,0,0,0}; - -// Key = -1 if we want to send KeyUp events from main loop -static int processAndroidTrackballKeyDelays( int key, int action ) -{ - #if ! defined(SDL_TRACKBALL_KEYUP_DELAY) || (SDL_TRACKBALL_KEYUP_DELAY == 0) - return 0; - #else - // Send Directional Pad Up events with a delay, so app wil lthink we're holding the key a bit - static const int KeysMapping[4] = {KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT}; - int idx, idx2; - SDL_keysym keysym; - - if( key < 0 ) - { - for( idx = 0; idx < 4; idx ++ ) - { - if( AndroidTrackballKeyDelays[idx] > 0 ) - { - AndroidTrackballKeyDelays[idx] --; - if( AndroidTrackballKeyDelays[idx] == 0 ) - SDL_PrivateKeyboard( SDL_RELEASED, TranslateKey(KeysMapping[idx], &keysym) ); - } - } - } - else - { - idx = -1; - // Too lazy to do switch or function - if( key == KEYCODE_DPAD_UP ) - idx = 0; - else if( key == KEYCODE_DPAD_DOWN ) - idx = 1; - else if( key == KEYCODE_DPAD_LEFT ) - idx = 2; - else if( key == KEYCODE_DPAD_RIGHT ) - idx = 3; - if( idx >= 0 ) - { - if( action && AndroidTrackballKeyDelays[idx] == 0 ) - { - // User pressed key for the first time - idx2 = (idx + 2) % 4; // Opposite key for current key - if it's still pressing, release it - if( AndroidTrackballKeyDelays[idx2] > 0 ) - { - AndroidTrackballKeyDelays[idx2] = 0; - SDL_PrivateKeyboard( SDL_RELEASED, TranslateKey(KeysMapping[idx2], &keysym) ); - } - SDL_PrivateKeyboard( SDL_PRESSED, TranslateKey(key, &keysym) ); - } - else if( !action && AndroidTrackballKeyDelays[idx] == 0 ) - { - // User released key - make a delay, do not send release event - AndroidTrackballKeyDelays[idx] = SDL_TRACKBALL_KEYUP_DELAY; - } - else if( action && AndroidTrackballKeyDelays[idx] > 0 ) - { - // User pressed key another time - add some more time for key to be pressed - AndroidTrackballKeyDelays[idx] += SDL_TRACKBALL_KEYUP_DELAY; - if( AndroidTrackballKeyDelays[idx] < SDL_TRACKBALL_KEYUP_DELAY * 4 ) - AndroidTrackballKeyDelays[idx] = SDL_TRACKBALL_KEYUP_DELAY * 4; - } - return 1; - } - } - return 0; - - #endif -} void -JAVA_EXPORT_NAME(DemoGLSurfaceView_nativeKey) ( JNIEnv* env, jobject thiz, jint key, jint action ) +JAVA_EXPORT_NAME(DemoGLSurfaceView_nativeKey) ( JNIEnv* env, jobject thiz, jint key, jint action ) { //__android_log_print(ANDROID_LOG_INFO, "libSDL", "key event %i %s", key, action ? "down" : "up"); SDL_keysym keysym; @@ -597,9 +499,7 @@ JAVA_EXPORT_NAME(DemoGLSurfaceView_nativeKey) ( JNIEnv* env, jobject thiz, jin SDL_PrivateKeyboard( action ? SDL_PRESSED : SDL_RELEASED, TranslateKey(key, &keysym) ); } -// The most wicked routine out there, all wicked multithreading and GL-ES stuff here -extern void -JAVA_EXPORT_NAME(DemoRenderer_nativeRender) ( JNIEnv* env, jobject thiz, jfloat accX, jfloat accY, jfloat accZ ) +void SdlGlRenderInit() { // Set up an array of values to use as the sprite vertices. static GLfloat vertices[] = @@ -629,12 +529,8 @@ JAVA_EXPORT_NAME(DemoRenderer_nativeRender) ( JNIEnv* env, jobject thiz, jfloa int textX, textY; void * memBufferTemp; - if( !sdl_opengl && memBuffer && openglInitialized != GL_State_Uninit2 ) + if( !sdl_opengl && memBuffer ) { - if( openglInitialized == GL_State_Init ) - { - openglInitialized = GL_State_Ready; - // Texture sizes should be 2^n textX = memX; textY = memY; @@ -714,122 +610,24 @@ JAVA_EXPORT_NAME(DemoRenderer_nativeRender) ( JNIEnv* env, jobject thiz, jfloa glFinish(); SDL_free( textBuffer ); - - } - else if( openglInitialized == GL_State_Uninit ) - { - openglInitialized = GL_State_Uninit2; - - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - glDisableClientState(GL_VERTEX_ARRAY); - - glDeleteTextures(1, &texture); - - return; - } - - if( WaitForNativeRender ) - { - SDL_mutexP(WaitForNativeRender); - - WaitForNativeRenderState = Render_State_Finished; - - SDL_mutexV(WaitForNativeRender); - SDL_CondSignal(WaitForNativeRender1); - SDL_mutexP(WaitForNativeRender); - - while( WaitForNativeRenderState != Render_State_Started ) - { - if( SDL_CondWaitTimeout( WaitForNativeRender1, WaitForNativeRender, 5000 ) != 0 ) - { - __android_log_print(ANDROID_LOG_INFO, "libSDL", "nativeRender: Frame failed to render"); - SDL_mutexV(WaitForNativeRender); - return; - } - } - - memBufferTemp = memBuffer; - - WaitForNativeRenderState = Render_State_Processing; - - SDL_mutexV(WaitForNativeRender); - - SDL_CondSignal(WaitForNativeRender1); - } - else - memBufferTemp = memBuffer; - - // TODO: use accelerometer as joystick - - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, memX, memY, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, memBufferTemp); - if( sWindowHeight < memY || sWindowWidth < memX ) - glDrawTexiOES(0, 0, 1, sWindowWidth, sWindowHeight); // Larger than screen - shrink to fit - else - glDrawTexiOES(0, sWindowHeight-memY, 1, memX, memY); // Smaller than screen - do not scale, it's faster that way - - //glFinish(); //glFlush(); - } - else if( sdl_opengl && openglInitialized != GL_State_Uninit2 ) - { - if( openglInitialized == GL_State_Init ) - { - openglInitialized = GL_State_Ready; +} - glViewport(0, 0, memX, memY); - glClearColor(0,0,0,0); - } - else if( openglInitialized == GL_State_Uninit ) - { - openglInitialized = GL_State_Uninit2; - return; - } - - if( WaitForNativeRender ) - { - SDL_mutexP(WaitForNativeRender); - - WaitForNativeRenderState = Render_State_Finished; +void +JAVA_EXPORT_NAME(DemoRenderer_nativeInitJavaCallbacks) ( JNIEnv* env, jobject thiz ) +{ + char classPath[1024]; + JavaEnv = env; + JavaRenderer = thiz; + + JavaRendererClass = (*JavaEnv)->GetObjectClass(JavaEnv, thiz); + JavaSwapBuffers = (*JavaEnv)->GetMethodID(JavaEnv, JavaRendererClass, "swapBuffers", "()I"); +} - SDL_mutexV(WaitForNativeRender); - SDL_CondSignal(WaitForNativeRender1); - SDL_mutexP(WaitForNativeRender); - - while( WaitForNativeRenderState != Render_State_Started ) - { - if( SDL_CondWaitTimeout( WaitForNativeRender1, WaitForNativeRender, 5000 ) != 0 ) - { - __android_log_print(ANDROID_LOG_INFO, "libSDL", "nativeRender: Frame failed to render"); - SDL_mutexV(WaitForNativeRender); - return; - } - } - - //__android_log_print(ANDROID_LOG_INFO, "libSDL", "nativeRender: Frame rendered"); - - WaitForNativeRenderState = Render_State_Processing; - - SDL_mutexV(WaitForNativeRender); - - SDL_CondSignal(WaitForNativeRender1); - } - } - else - { - /* - // Flash the screen - if( clearColor >= 1.0f ) - clearColorDir = -1; - else if( clearColor <= 0.0f ) - clearColorDir = 1; - - clearColor += (float)clearColorDir * 0.01f; - glClearColor(clearColor,clearColor,clearColor,0); - glClear(GL_COLOR_BUFFER_BIT); - SDL_Delay(50); - */ - } +int CallJavaSwapBuffers() +{ + return (*JavaEnv)->CallIntMethod( JavaEnv, JavaRenderer, JavaSwapBuffers ); } void ANDROID_InitOSKeymap(_THIS) @@ -953,3 +751,72 @@ void ANDROID_InitOSKeymap(_THIS) } +static int AndroidTrackballKeyDelays[4] = {0,0,0,0}; + +// Key = -1 if we want to send KeyUp events from main loop +int processAndroidTrackballKeyDelays( int key, int action ) +{ + #if ! defined(SDL_TRACKBALL_KEYUP_DELAY) || (SDL_TRACKBALL_KEYUP_DELAY == 0) + return 0; + #else + // Send Directional Pad Up events with a delay, so app wil lthink we're holding the key a bit + static const int KeysMapping[4] = {KEYCODE_DPAD_UP, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_RIGHT}; + int idx, idx2; + SDL_keysym keysym; + + if( key < 0 ) + { + for( idx = 0; idx < 4; idx ++ ) + { + if( AndroidTrackballKeyDelays[idx] > 0 ) + { + AndroidTrackballKeyDelays[idx] --; + if( AndroidTrackballKeyDelays[idx] == 0 ) + SDL_PrivateKeyboard( SDL_RELEASED, TranslateKey(KeysMapping[idx], &keysym) ); + } + } + } + else + { + idx = -1; + // Too lazy to do switch or function + if( key == KEYCODE_DPAD_UP ) + idx = 0; + else if( key == KEYCODE_DPAD_DOWN ) + idx = 1; + else if( key == KEYCODE_DPAD_LEFT ) + idx = 2; + else if( key == KEYCODE_DPAD_RIGHT ) + idx = 3; + if( idx >= 0 ) + { + if( action && AndroidTrackballKeyDelays[idx] == 0 ) + { + // User pressed key for the first time + idx2 = (idx + 2) % 4; // Opposite key for current key - if it's still pressing, release it + if( AndroidTrackballKeyDelays[idx2] > 0 ) + { + AndroidTrackballKeyDelays[idx2] = 0; + SDL_PrivateKeyboard( SDL_RELEASED, TranslateKey(KeysMapping[idx2], &keysym) ); + } + SDL_PrivateKeyboard( SDL_PRESSED, TranslateKey(key, &keysym) ); + } + else if( !action && AndroidTrackballKeyDelays[idx] == 0 ) + { + // User released key - make a delay, do not send release event + AndroidTrackballKeyDelays[idx] = SDL_TRACKBALL_KEYUP_DELAY; + } + else if( action && AndroidTrackballKeyDelays[idx] > 0 ) + { + // User pressed key another time - add some more time for key to be pressed + AndroidTrackballKeyDelays[idx] += SDL_TRACKBALL_KEYUP_DELAY; + if( AndroidTrackballKeyDelays[idx] < SDL_TRACKBALL_KEYUP_DELAY * 4 ) + AndroidTrackballKeyDelays[idx] = SDL_TRACKBALL_KEYUP_DELAY * 4; + } + return 1; + } + } + return 0; + + #endif +} diff --git a/alienblaster/project/jni/sdl_main/sdl_main.c b/alienblaster/project/jni/sdl_main/sdl_main.c index 1ca6e593e..c76214618 100644 --- a/alienblaster/project/jni/sdl_main/sdl_main.c +++ b/alienblaster/project/jni/sdl_main/sdl_main.c @@ -15,15 +15,6 @@ #endif -extern C_LINKAGE int main( int argc, char ** argv ); -static int SDLCALL MainThreadWrapper(void * dummy) -{ - int argc = 1; - char * argv[] = { "sdl" }; - chdir(SDL_CURDIR_PATH); - return main( argc, argv ); -}; - #ifndef SDL_JAVA_PACKAGE_PATH #error You have to define SDL_JAVA_PACKAGE_PATH to your package path with dots replaced with underscores, for example "com_example_SanAngeles" #endif @@ -31,13 +22,14 @@ static int SDLCALL MainThreadWrapper(void * dummy) #define JAVA_EXPORT_NAME1(name,package) JAVA_EXPORT_NAME2(name,package) #define JAVA_EXPORT_NAME(name) JAVA_EXPORT_NAME1(name,SDL_JAVA_PACKAGE_PATH) -extern C_LINKAGE SDL_Thread * SDL_mainThread; - extern C_LINKAGE void -JAVA_EXPORT_NAME(DemoRenderer_nativeInit) ( JNIEnv* env, jobject thiz ) +JAVA_EXPORT_NAME(DemoRenderer_nativeInit) ( JNIEnv* env, jobject thiz ) { - SDL_mainThread = SDL_CreateThread( MainThreadWrapper, NULL ); -} + int argc = 1; + char * argv[] = { "sdl" }; + chdir(SDL_CURDIR_PATH); + main( argc, argv ); +}; #undef JAVA_EXPORT_NAME #undef JAVA_EXPORT_NAME1 diff --git a/alienblaster/project/src/DemoActivity.java b/alienblaster/project/src/DemoActivity.java index 7e3052e3a..87bcc1839 100644 --- a/alienblaster/project/src/DemoActivity.java +++ b/alienblaster/project/src/DemoActivity.java @@ -1,9 +1,15 @@ // This string is autogenerated by ChangeAppSettings.sh, do not change spaces amount package de.schwardtnet.alienblaster; -import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.opengles.GL10; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; + import android.app.Activity; import android.content.Context; import android.opengl.GLSurfaceView; @@ -65,22 +71,31 @@ class AccelerometerReader implements SensorListener { private long timekeeper; private float [] v; + + private SensorManager _manager = null; public AccelerometerReader(Activity context) { v = new float[3]; - SensorManager sma = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - if( sma != null ) + _manager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + if( _manager != null ) { timekeeper = android.os.SystemClock.uptimeMillis(); int mask = 0; - mask |= SensorManager.SENSOR_ORIENTATION; + //mask |= SensorManager.SENSOR_ORIENTATION; mask |= SensorManager.SENSOR_ACCELEROMETER; - sma.registerListener(this, mask, SensorManager.SENSOR_DELAY_GAME); + _manager.registerListener(this, mask, SensorManager.SENSOR_DELAY_GAME); + } + } + + public synchronized void stop() { + if( _manager != null ) + { + _manager.unregisterListener(this); } } public synchronized void onSensorChanged(int sensor, float[] values) { - if (android.os.SystemClock.uptimeMillis() < timekeeper + 20) return; + //if (android.os.SystemClock.uptimeMillis() < timekeeper + 20) return; timekeeper = android.os.SystemClock.uptimeMillis(); if (sensor == SensorManager.SENSOR_ACCELEROMETER) { @@ -90,8 +105,9 @@ class AccelerometerReader implements SensorListener { v[1] = values[1]; if( values.length >= 3 ) v[2] = values[2]; + nativeAccelerometer(v[0], v[1], v[2]); } - + } public synchronized void onAccuracyChanged(int i, int i1) { @@ -106,15 +122,16 @@ class AccelerometerReader implements SensorListener { ret[2] = v[2]; return ret; }; + + private native void nativeAccelerometer(float accX, float accY, float accZ); } -class DemoRenderer implements GLSurfaceView.Renderer { +class DemoRenderer extends GLSurfaceView_SDL.Renderer { public DemoRenderer(Activity _context) { - super(); context = _context; } @@ -128,30 +145,43 @@ class DemoRenderer implements GLSurfaceView.Renderer { } public void onDrawFrame(GL10 gl) { - if( accelerometer == null) { - accelerometer = new AccelerometerReader(context); - nativeInit(); - } - float [] f = accelerometer.readAccelerometer(); - nativeRender(f[0], f[1], f[2]); + + nativeInitJavaCallbacks(); + + nativeInit(); // Calls main() and never returns, hehe - we'll call eglSwapBuffers() from native code + + } + + public int swapBuffers() // Called from native code, returns 1 on success, 0 when GL context lost (user put app to background) + { + System.out.println("Java: swapBuffers() called"); + return super.SwapBuffers() ? 1 : 0; } public void exitApp() { nativeDone(); }; - private static native void nativeInit(); - private static native void nativeResize(int w, int h); - private static native void nativeRender(float accX, float accY, float accZ); - private static native void nativeDone(); - private AccelerometerReader accelerometer = null; + private native void nativeInitJavaCallbacks(); + private native void nativeInit(); + private native void nativeResize(int w, int h); + private native void nativeDone(); + private Activity context = null; + + private EGL10 mEgl = null; + private EGLDisplay mEglDisplay = null; + private EGLSurface mEglSurface = null; + private EGLContext mEglContext = null; + private int skipFrames = 0; + } -class DemoGLSurfaceView extends GLSurfaceView { +class DemoGLSurfaceView extends GLSurfaceView_SDL { public DemoGLSurfaceView(Activity context) { super(context); mParent = context; + accelerometer = new AccelerometerReader(context); mRenderer = new DemoRenderer(context); setRenderer(mRenderer); } @@ -175,6 +205,8 @@ class DemoGLSurfaceView extends GLSurfaceView { public void exitApp() { mRenderer.exitApp(); + accelerometer.stop(); + accelerometer = null; }; @Override @@ -191,9 +223,10 @@ class DemoGLSurfaceView extends GLSurfaceView { DemoRenderer mRenderer; Activity mParent; + AccelerometerReader accelerometer = null; - public static native void nativeMouse( int x, int y, int action ); - public static native void nativeKey( int keyCode, int down ); + public native void nativeMouse( int x, int y, int action ); + public native void nativeKey( int keyCode, int down ); } class AudioThread extends Thread { @@ -267,10 +300,10 @@ class AudioThread extends Thread { } } - private static native int[] nativeAudioInit(); - private static native int nativeAudioInit2(byte[] buf); - private static native int nativeAudioBufferLock(); - private static native int nativeAudioBufferUnlock(); + private native int[] nativeAudioInit(); + private native int nativeAudioInit2(byte[] buf); + private native int nativeAudioBufferLock(); + private native int nativeAudioBufferUnlock(); } diff --git a/alienblaster/project/src/GLSurfaceView_SDL.java b/alienblaster/project/src/GLSurfaceView_SDL.java new file mode 100644 index 000000000..19f748108 --- /dev/null +++ b/alienblaster/project/src/GLSurfaceView_SDL.java @@ -0,0 +1,1355 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* This is GLSurfaceView class ripped out of Android 2.1 sources, + fixed with a hammer and rasp to work with libSDL port */ + +// This string is autogenerated by ChangeAppSettings.sh, do not change spaces amount +package de.schwardtnet.alienblaster; + +import java.io.Writer; +import java.util.ArrayList; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGL11; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL; +import javax.microedition.khronos.opengles.GL10; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.SurfaceHolder; +import android.view.SurfaceView; + +/** + * An implementation of SurfaceView that uses the dedicated surface for + * displaying OpenGL rendering. + *

+ * A GLSurfaceView provides the following features: + *

+ *

+ * + *

Using GLSurfaceView

+ *

+ * Typically you use GLSurfaceView by subclassing it and overriding one or more of the + * View system input event methods. If your application does not need to override event + * methods then GLSurfaceView can be used as-is. For the most part + * GLSurfaceView behavior is customized by calling "set" methods rather than by subclassing. + * For example, unlike a regular View, drawing is delegated to a separate Renderer object which + * is registered with the GLSurfaceView + * using the {@link #setRenderer(Renderer)} call. + *

+ *

Initializing GLSurfaceView

+ * All you have to do to initialize a GLSurfaceView is call {@link #setRenderer(Renderer)}. + * However, if desired, you can modify the default behavior of GLSurfaceView by calling one or + * more of these methods before calling setRenderer: + * + *

+ *

Choosing an EGL Configuration

+ * A given Android device may support multiple possible types of drawing surfaces. + * The available surfaces may differ in how may channels of data are present, as + * well as how many bits are allocated to each channel. Therefore, the first thing + * GLSurfaceView has to do when starting to render is choose what type of surface to use. + *

+ * By default GLSurfaceView chooses an available surface that's closest to a 16-bit R5G6B5 surface + * with a 16-bit depth buffer and no stencil. If you would prefer a different surface (for example, + * if you do not need a depth buffer) you can override the default behavior by calling one of the + * setEGLConfigChooser methods. + *

+ *

Debug Behavior

+ * You can optionally modify the behavior of GLSurfaceView by calling + * one or more of the debugging methods {@link #setDebugFlags(int)}, + * and {@link #setGLWrapper}. These methods may be called before and/or after setRenderer, but + * typically they are called before setRenderer so that they take effect immediately. + *

+ *

Setting a Renderer

+ * Finally, you must call {@link #setRenderer} to register a {@link Renderer}. + * The renderer is + * responsible for doing the actual OpenGL rendering. + *

+ *

Rendering Mode

+ * Once the renderer is set, you can control whether the renderer draws + * continuously or on-demand by calling + * {@link #setRenderMode}. The default is continuous rendering. + *

+ *

Activity Life-cycle

+ * A GLSurfaceView must be notified when the activity is paused and resumed. GLSurfaceView clients + * are required to call {@link #onPause()} when the activity pauses and + * {@link #onResume()} when the activity resumes. These calls allow GLSurfaceView to + * pause and resume the rendering thread, and also allow GLSurfaceView to release and recreate + * the OpenGL display. + *

+ *

Handling events

+ *

+ * To handle an event you will typically subclass GLSurfaceView and override the + * appropriate method, just as you would with any other View. However, when handling + * the event, you may need to communicate with the Renderer object + * that's running in the rendering thread. You can do this using any + * standard Java cross-thread communication mechanism. In addition, + * one relatively easy way to communicate with your renderer is + * to call + * {@link #queueEvent(Runnable)}. For example: + *

+ * class MyGLSurfaceView extends GLSurfaceView {
+ *
+ *     private MyRenderer mMyRenderer;
+ *
+ *     public void start() {
+ *         mMyRenderer = ...;
+ *         setRenderer(mMyRenderer);
+ *     }
+ *
+ *     public boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             queueEvent(new Runnable() {
+ *                 // This method will be called on the rendering
+ *                 // thread:
+ *                 public void run() {
+ *                     mMyRenderer.handleDpadCenter();
+ *                 }});
+ *             return true;
+ *         }
+ *         return super.onKeyDown(keyCode, event);
+ *     }
+ * }
+ * 
+ * + */ +public class GLSurfaceView_SDL extends SurfaceView implements SurfaceHolder.Callback { + private final static boolean LOG_THREADS = false; + private final static boolean LOG_SURFACE = false; + private final static boolean LOG_RENDERER = false; + // Work-around for bug 2263168 + private final static boolean DRAW_TWICE_AFTER_SIZE_CHANGED = true; + /** + * The renderer only renders + * when the surface is created, or when {@link #requestRender} is called. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + */ + public final static int RENDERMODE_WHEN_DIRTY = 0; + /** + * The renderer is called + * continuously to re-render the scene. + * + * @see #getRenderMode() + * @see #setRenderMode(int) + * @see #requestRender() + */ + public final static int RENDERMODE_CONTINUOUSLY = 1; + + /** + * Check glError() after every GL call and throw an exception if glError indicates + * that an error has occurred. This can be used to help track down which OpenGL ES call + * is causing an error. + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_CHECK_GL_ERROR = 1; + + /** + * Log GL calls to the system log at "verbose" level with tag "GLSurfaceView". + * + * @see #getDebugFlags + * @see #setDebugFlags + */ + public final static int DEBUG_LOG_GL_CALLS = 2; + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView_SDL(Context context) { + super(context); + init(); + } + + /** + * Standard View constructor. In order to render something, you + * must call {@link #setRenderer} to register a renderer. + */ + public GLSurfaceView_SDL(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + // Install a SurfaceHolder.Callback so we get notified when the + // underlying surface is created and destroyed + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + } + + /** + * Set the glWrapper. If the glWrapper is not null, its + * {@link GLWrapper#wrap(GL)} method is called + * whenever a surface is created. A GLWrapper can be used to wrap + * the GL object that's passed to the renderer. Wrapping a GL + * object enables examining and modifying the behavior of the + * GL calls made by the renderer. + *

+ * Wrapping is typically used for debugging purposes. + *

+ * The default value is null. + * @param glWrapper the new GLWrapper + */ + public void setGLWrapper(GLWrapper glWrapper) { + mGLWrapper = glWrapper; + } + + /** + * Set the debug flags to a new value. The value is + * constructed by OR-together zero or more + * of the DEBUG_CHECK_* constants. The debug flags take effect + * whenever a surface is created. The default value is zero. + * @param debugFlags the new debug flags + * @see #DEBUG_CHECK_GL_ERROR + * @see #DEBUG_LOG_GL_CALLS + */ + public void setDebugFlags(int debugFlags) { + mDebugFlags = debugFlags; + } + + /** + * Get the current value of the debug flags. + * @return the current value of the debug flags. + */ + public int getDebugFlags() { + return mDebugFlags; + } + + /** + * Set the renderer associated with this view. Also starts the thread that + * will call the renderer, which in turn causes the rendering to start. + *

This method should be called once and only once in the life-cycle of + * a GLSurfaceView. + *

The following GLSurfaceView methods can only be called before + * setRenderer is called: + *

+ *

+ * The following GLSurfaceView methods can only be called after + * setRenderer is called: + *

+ * + * @param renderer the renderer to use to perform OpenGL drawing. + */ + public void setRenderer(Renderer renderer) { + checkRenderThreadState(); + if (mEGLConfigChooser == null) { + mEGLConfigChooser = new SimpleEGLConfigChooser(true); + } + if (mEGLContextFactory == null) { + mEGLContextFactory = new DefaultContextFactory(); + } + if (mEGLWindowSurfaceFactory == null) { + mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory(); + } + mGLThread = new GLThread(renderer); + mGLThread.start(); + } + + /** + * Install a custom EGLContextFactory. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If this method is not called, then by default + * a context will be created with no shared context and + * with a null attribute list. + */ + public void setEGLContextFactory(EGLContextFactory factory) { + checkRenderThreadState(); + mEGLContextFactory = factory; + } + + /** + * Install a custom EGLWindowSurfaceFactory. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If this method is not called, then by default + * a window surface will be created with a null attribute list. + */ + public void setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory factory) { + checkRenderThreadState(); + mEGLWindowSurfaceFactory = factory; + } + + /** + * Install a custom EGLConfigChooser. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + * @param configChooser + */ + public void setEGLConfigChooser(EGLConfigChooser configChooser) { + checkRenderThreadState(); + mEGLConfigChooser = configChooser; + } + + /** + * Install a config chooser which will choose a config + * as close to 16-bit RGB as possible, with or without an optional depth + * buffer as close to 16-bits as possible. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + * + * @param needDepth + */ + public void setEGLConfigChooser(boolean needDepth) { + setEGLConfigChooser(new SimpleEGLConfigChooser(needDepth)); + } + + /** + * Install a config chooser which will choose a config + * with at least the specified component sizes, and as close + * to the specified component sizes as possible. + *

If this method is + * called, it must be called before {@link #setRenderer(Renderer)} + * is called. + *

+ * If no setEGLConfigChooser method is called, then by default the + * view will choose a config as close to 16-bit RGB as possible, with + * a depth buffer as close to 16 bits as possible. + * + */ + public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + setEGLConfigChooser(new ComponentSizeChooser(redSize, greenSize, + blueSize, alphaSize, depthSize, stencilSize)); + } + /** + * Set the rendering mode. When renderMode is + * RENDERMODE_CONTINUOUSLY, the renderer is called + * repeatedly to re-render the scene. When renderMode + * is RENDERMODE_WHEN_DIRTY, the renderer only rendered when the surface + * is created, or when {@link #requestRender} is called. Defaults to RENDERMODE_CONTINUOUSLY. + *

+ * Using RENDERMODE_WHEN_DIRTY can improve battery life and overall system performance + * by allowing the GPU and CPU to idle when the view does not need to be updated. + *

+ * This method can only be called after {@link #setRenderer(Renderer)} + * + * @param renderMode one of the RENDERMODE_X constants + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public void setRenderMode(int renderMode) { + mGLThread.setRenderMode(renderMode); + } + + /** + * Get the current rendering mode. May be called + * from any thread. Must not be called before a renderer has been set. + * @return the current rendering mode. + * @see #RENDERMODE_CONTINUOUSLY + * @see #RENDERMODE_WHEN_DIRTY + */ + public int getRenderMode() { + return mGLThread.getRenderMode(); + } + + /** + * Request that the renderer render a frame. + * This method is typically used when the render mode has been set to + * {@link #RENDERMODE_WHEN_DIRTY}, so that frames are only rendered on demand. + * May be called + * from any thread. Must not be called before a renderer has been set. + */ + public void requestRender() { + mGLThread.requestRender(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceCreated(SurfaceHolder holder) { + mGLThread.surfaceCreated(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceDestroyed(SurfaceHolder holder) { + // Surface will be destroyed when we return + mGLThread.surfaceDestroyed(); + } + + /** + * This method is part of the SurfaceHolder.Callback interface, and is + * not normally called or subclassed by clients of GLSurfaceView. + */ + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + mGLThread.onWindowResize(w, h); + } + + /** + * Inform the view that the activity is paused. The owner of this view must + * call this method when the activity is paused. Calling this method will + * pause the rendering thread. + * Must not be called before a renderer has been set. + */ + public void onPause() { + mGLThread.onPause(); + } + + /** + * Inform the view that the activity is resumed. The owner of this view must + * call this method when the activity is resumed. Calling this method will + * recreate the OpenGL display and resume the rendering + * thread. + * Must not be called before a renderer has been set. + */ + public void onResume() { + mGLThread.onResume(); + } + + /** + * Queue a runnable to be run on the GL rendering thread. This can be used + * to communicate with the Renderer on the rendering thread. + * Must not be called before a renderer has been set. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + mGLThread.queueEvent(r); + } + + /** + * This method is used as part of the View class and is not normally + * called or subclassed by clients of GLSurfaceView. + * Must not be called before a renderer has been set. + */ + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mGLThread.requestExitAndWait(); + } + + // ---------------------------------------------------------------------- + + /** + * An interface used to wrap a GL interface. + *

Typically + * used for implementing debugging and tracing on top of the default + * GL interface. You would typically use this by creating your own class + * that implemented all the GL methods by delegating to another GL instance. + * Then you could add your own behavior before or after calling the + * delegate. All the GLWrapper would do was instantiate and return the + * wrapper GL instance: + *

+     * class MyGLWrapper implements GLWrapper {
+     *     GL wrap(GL gl) {
+     *         return new MyGLImplementation(gl);
+     *     }
+     *     static class MyGLImplementation implements GL,GL10,GL11,... {
+     *         ...
+     *     }
+     * }
+     * 
+ * @see #setGLWrapper(GLWrapper) + */ + public interface GLWrapper { + /** + * Wraps a gl interface in another gl interface. + * @param gl a GL interface that is to be wrapped. + * @return either the input argument or another GL object that wraps the input argument. + */ + GL wrap(GL gl); + } + + /** + * A generic renderer interface. + *

+ * The renderer is responsible for making OpenGL calls to render a frame. + *

+ * GLSurfaceView clients typically create their own classes that implement + * this interface, and then call {@link GLSurfaceView#setRenderer} to + * register the renderer with the GLSurfaceView. + *

+ *

Threading

+ * The renderer will be called on a separate thread, so that rendering + * performance is decoupled from the UI thread. Clients typically need to + * communicate with the renderer from the UI thread, because that's where + * input events are received. Clients can communicate using any of the + * standard Java techniques for cross-thread communication, or they can + * use the {@link GLSurfaceView#queueEvent(Runnable)} convenience method. + *

+ *

EGL Context Lost

+ * There are situations where the EGL rendering context will be lost. This + * typically happens when device wakes up after going to sleep. When + * the EGL context is lost, all OpenGL resources (such as textures) that are + * associated with that context will be automatically deleted. In order to + * keep rendering correctly, a renderer must recreate any lost resources + * that it still needs. The {@link #onSurfaceCreated(GL10, EGLConfig)} method + * is a convenient place to do this. + * + * + * @see #setRenderer(Renderer) + */ + public static interface SwapBuffersCallback { + public boolean SwapBuffers(); + } + + public static abstract class Renderer { + /** + * Called when the surface is created or recreated. + *

+ * Called when the rendering thread + * starts and whenever the EGL context is lost. The context will typically + * be lost when the Android device awakes after going to sleep. + *

+ * Since this method is called at the beginning of rendering, as well as + * every time the EGL context is lost, this method is a convenient place to put + * code to create resources that need to be created when the rendering + * starts, and that need to be recreated when the EGL context is lost. + * Textures are an example of a resource that you might want to create + * here. + *

+ * Note that when the EGL context is lost, all OpenGL resources associated + * with that context will be automatically deleted. You do not need to call + * the corresponding "glDelete" methods such as glDeleteTextures to + * manually delete these lost resources. + *

+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param config the EGLConfig of the created surface. Can be used + * to create matching pbuffers. + */ + public abstract void onSurfaceCreated(GL10 gl, EGLConfig config); + + /** + * Called when the surface changed size. + *

+ * Called after the surface is created and whenever + * the OpenGL ES surface size changes. + *

+ * Typically you will set your viewport here. If your camera + * is fixed then you could also set your projection matrix here: + *

+         * void onSurfaceChanged(GL10 gl, int width, int height) {
+         *     gl.glViewport(0, 0, width, height);
+         *     // for a fixed camera, set the projection too
+         *     float ratio = (float) width / height;
+         *     gl.glMatrixMode(GL10.GL_PROJECTION);
+         *     gl.glLoadIdentity();
+         *     gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);
+         * }
+         * 
+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + * @param width + * @param height + */ + public abstract void onSurfaceChanged(GL10 gl, int width, int height); + + /** + * Called to draw the current frame. + *

+ * This method is responsible for drawing the current frame. + *

+ * The implementation of this method typically looks like this: + *

+         * void onDrawFrame(GL10 gl) {
+         *     gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
+         *     //... other gl calls to render the scene ...
+         * }
+         * 
+ * @param gl the GL interface. Use instanceof to + * test if the interface supports GL11 or higher interfaces. + */ + public abstract void onDrawFrame(GL10 gl); + + public boolean SwapBuffers() { + if( mSwapBuffersCallback != null ) + return mSwapBuffersCallback.SwapBuffers(); + return false; + } + + public void setSwapBuffersCallback( SwapBuffersCallback c ) { + mSwapBuffersCallback = c; + } + + private SwapBuffersCallback mSwapBuffersCallback = null; + } + + /** + * An interface for customizing the eglCreateContext and eglDestroyContext calls. + *

+ * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLContextFactory(EGLContextFactory)} + */ + public interface EGLContextFactory { + EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig eglConfig); + void destroyContext(EGL10 egl, EGLDisplay display, EGLContext context); + } + + private static class DefaultContextFactory implements EGLContextFactory { + + public EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { + return egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, null); + } + + public void destroyContext(EGL10 egl, EGLDisplay display, + EGLContext context) { + egl.eglDestroyContext(display, context); + } + } + + /** + * An interface for customizing the eglCreateWindowSurface and eglDestroySurface calls. + *

+ * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLWindowSurfaceFactory(EGLWindowSurfaceFactory)} + */ + public interface EGLWindowSurfaceFactory { + EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, + Object nativeWindow); + void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface); + } + + private static class DefaultWindowSurfaceFactory implements EGLWindowSurfaceFactory { + + public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, + EGLConfig config, Object nativeWindow) { + return egl.eglCreateWindowSurface(display, config, nativeWindow, null); + } + + public void destroySurface(EGL10 egl, EGLDisplay display, + EGLSurface surface) { + egl.eglDestroySurface(display, surface); + } + } + + /** + * An interface for choosing an EGLConfig configuration from a list of + * potential configurations. + *

+ * This interface must be implemented by clients wishing to call + * {@link GLSurfaceView#setEGLConfigChooser(EGLConfigChooser)} + */ + public interface EGLConfigChooser { + /** + * Choose a configuration from the list. Implementors typically + * implement this method by calling + * {@link EGL10#eglChooseConfig} and iterating through the results. Please consult the + * EGL specification available from The Khronos Group to learn how to call eglChooseConfig. + * @param egl the EGL10 for the current display. + * @param display the current display. + * @return the chosen configuration. + */ + EGLConfig chooseConfig(EGL10 egl, EGLDisplay display); + } + + private static abstract class BaseConfigChooser + implements EGLConfigChooser { + public BaseConfigChooser(int[] configSpec) { + mConfigSpec = configSpec; + } + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] num_config = new int[1]; + egl.eglChooseConfig(display, mConfigSpec, null, 0, num_config); + + int numConfigs = num_config[0]; + + if (numConfigs <= 0) { + throw new IllegalArgumentException( + "No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(display, mConfigSpec, configs, numConfigs, + num_config); + EGLConfig config = chooseConfig(egl, display, configs); + if (config == null) { + throw new IllegalArgumentException("No config chosen"); + } + return config; + } + + abstract EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs); + + protected int[] mConfigSpec; + } + + private static class ComponentSizeChooser extends BaseConfigChooser { + public ComponentSizeChooser(int redSize, int greenSize, int blueSize, + int alphaSize, int depthSize, int stencilSize) { + super(new int[] { + EGL10.EGL_RED_SIZE, redSize, + EGL10.EGL_GREEN_SIZE, greenSize, + EGL10.EGL_BLUE_SIZE, blueSize, + EGL10.EGL_ALPHA_SIZE, alphaSize, + EGL10.EGL_DEPTH_SIZE, depthSize, + EGL10.EGL_STENCIL_SIZE, stencilSize, + EGL10.EGL_NONE}); + mValue = new int[1]; + mRedSize = redSize; + mGreenSize = greenSize; + mBlueSize = blueSize; + mAlphaSize = alphaSize; + mDepthSize = depthSize; + mStencilSize = stencilSize; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display, + EGLConfig[] configs) { + EGLConfig closestConfig = null; + int closestDistance = 1000; + for(EGLConfig config : configs) { + int d = findConfigAttrib(egl, display, config, + EGL10.EGL_DEPTH_SIZE, 0); + int s = findConfigAttrib(egl, display, config, + EGL10.EGL_STENCIL_SIZE, 0); + if (d >= mDepthSize && s>= mStencilSize) { + int r = findConfigAttrib(egl, display, config, + EGL10.EGL_RED_SIZE, 0); + int g = findConfigAttrib(egl, display, config, + EGL10.EGL_GREEN_SIZE, 0); + int b = findConfigAttrib(egl, display, config, + EGL10.EGL_BLUE_SIZE, 0); + int a = findConfigAttrib(egl, display, config, + EGL10.EGL_ALPHA_SIZE, 0); + int distance = Math.abs(r - mRedSize) + + Math.abs(g - mGreenSize) + + Math.abs(b - mBlueSize) + + Math.abs(a - mAlphaSize); + if (distance < closestDistance) { + closestDistance = distance; + closestConfig = config; + } + } + } + return closestConfig; + } + + private int findConfigAttrib(EGL10 egl, EGLDisplay display, + EGLConfig config, int attribute, int defaultValue) { + + if (egl.eglGetConfigAttrib(display, config, attribute, mValue)) { + return mValue[0]; + } + return defaultValue; + } + + private int[] mValue; + // Subclasses can adjust these values: + protected int mRedSize; + protected int mGreenSize; + protected int mBlueSize; + protected int mAlphaSize; + protected int mDepthSize; + protected int mStencilSize; + } + + /** + * This class will choose a supported surface as close to + * RGB565 as possible, with or without a depth buffer. + * + */ + private static class SimpleEGLConfigChooser extends ComponentSizeChooser { + public SimpleEGLConfigChooser(boolean withDepthBuffer) { + super(4, 4, 4, 0, withDepthBuffer ? 16 : 0, 0); + // Adjust target values. This way we'll accept a 4444 or + // 555 buffer if there's no 565 buffer available. + mRedSize = 5; + mGreenSize = 6; + mBlueSize = 5; + } + } + + /** + * An EGL helper class. + */ + + private class EglHelper { + public EglHelper() { + + } + + /** + * Initialize EGL for a given configuration spec. + * @param configSpec + */ + public void start(){ + /* + * Get an EGL instance + */ + mEgl = (EGL10) EGLContext.getEGL(); + + /* + * Get to the default display. + */ + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + /* + * We can now initialize EGL for that display + */ + int[] version = new int[2]; + mEgl.eglInitialize(mEglDisplay, version); + mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); + + /* + * Create an OpenGL ES context. This must be done only once, an + * OpenGL context is a somewhat heavy object. + */ + mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + throw new RuntimeException("createContext failed"); + } + + mEglSurface = null; + } + + /* + * React to the creation of a new surface by creating and returning an + * OpenGL interface that renders to that surface. + */ + public GL createSurface(SurfaceHolder holder) { + /* + * The window size has changed, so we need to create a new + * surface. + */ + if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + + /* + * Unbind and destroy the old EGL surface, if + * there is one. + */ + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); + } + + /* + * Create an EGL surface we can render into. + */ + mEglSurface = mEGLWindowSurfaceFactory.createWindowSurface(mEgl, + mEglDisplay, mEglConfig, holder); + + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + throwEglException("createWindowSurface"); + } + + /* + * Before we can issue GL commands, we need to make sure + * the context is current and bound to a surface. + */ + if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throwEglException("eglMakeCurrent"); + } + + GL gl = mEglContext.getGL(); + if (mGLWrapper != null) { + gl = mGLWrapper.wrap(gl); + } + + return gl; + } + + /** + * Display the current render surface. + * @return false if the context has been lost. + */ + public boolean swap() { + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + + /* + * Always check for EGL_CONTEXT_LOST, which means the context + * and all associated data were lost (For instance because + * the device went to sleep). We need to sleep until we + * get a new surface. + */ + return mEgl.eglGetError() != EGL11.EGL_CONTEXT_LOST; + } + + public void destroySurface() { + if (mEglSurface != null && mEglSurface != EGL10.EGL_NO_SURFACE) { + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + mEGLWindowSurfaceFactory.destroySurface(mEgl, mEglDisplay, mEglSurface); + mEglSurface = null; + } + } + + public void finish() { + if (mEglContext != null) { + mEGLContextFactory.destroyContext(mEgl, mEglDisplay, mEglContext); + mEglContext = null; + } + if (mEglDisplay != null) { + mEgl.eglTerminate(mEglDisplay); + mEglDisplay = null; + } + } + + private void throwEglException(String function) { + throw new RuntimeException(function + " failed: " + mEgl.eglGetError()); + } + + EGL10 mEgl; + EGLDisplay mEglDisplay; + EGLSurface mEglSurface; + EGLConfig mEglConfig; + EGLContext mEglContext; + } + + /** + * A generic GL Thread. Takes care of initializing EGL and GL. Delegates + * to a Renderer instance to do the actual drawing. Can be configured to + * render continuously or on request. + * + * All potentially blocking synchronization is done through the + * sGLThreadManager object. This avoids multiple-lock ordering issues. + * + */ + class GLThread extends Thread implements SwapBuffersCallback { + GLThread(Renderer renderer) { + super(); + mWidth = 0; + mHeight = 0; + mRequestRender = true; + mRenderMode = RENDERMODE_CONTINUOUSLY; + mRenderer = renderer; + mRenderer.setSwapBuffersCallback(this); + } + + @Override + public void run() { + setName("GLThread " + getId()); + if (LOG_THREADS) { + Log.i("GLThread", "starting tid=" + getId()); + } + + mEglHelper = new EglHelper(); + SwapBuffers(); + SwapBuffers(); + + mRenderer.onDrawFrame(mGL); + + synchronized (sGLThreadManager) { + stopEglLocked(); + } + + sGLThreadManager.threadExiting(this); + } + + /* + * This private method should only be called inside a + * synchronized(sGLThreadManager) block. + */ + private void stopEglLocked() { + if (mHaveEgl) { + mHaveEgl = false; + mEglHelper.destroySurface(); + mEglHelper.finish(); + sGLThreadManager.releaseEglSurfaceLocked(this); + } + } + + public boolean SwapBuffers() { + try { + + boolean createEglSurface = false; + boolean sizeChanged = false; + int w = 0; + int h = 0; + Runnable event = null; + + synchronized (sGLThreadManager) { + while (true) { + if (mShouldExit) { + return false; + } + + if (! mEventQueue.isEmpty()) { + event = mEventQueue.remove(0); + break; + } + + // Do we need to release the EGL surface? + if (mHaveEgl && mPaused) { + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); + } + stopEglLocked(); + } + + // Have we lost the surface view surface? + if ((! mHasSurface) && (! mWaitingForSurface)) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); + } + if (mHaveEgl) { + stopEglLocked(); + } + mWaitingForSurface = true; + sGLThreadManager.notifyAll(); + } + + // Have we acquired the surface view surface? + if (mHasSurface && mWaitingForSurface) { + if (LOG_SURFACE) { + Log.i("GLThread", "noticed surfaceView surface acquired tid=" + getId()); + } + mWaitingForSurface = false; + sGLThreadManager.notifyAll(); + } + + // Ready to draw? + if ((!mPaused) && mHasSurface + && (mWidth > 0) && (mHeight > 0) + && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { + + // If we don't have an egl surface, try to acquire one. + if ((! mHaveEgl) && sGLThreadManager.tryAcquireEglSurfaceLocked(this)) { + mHaveEgl = true; + mEglHelper.start(); + createEglSurface = true; + sizeChanged = true; + sGLThreadManager.notifyAll(); + } + + if (mHaveEgl) { + if (mSizeChanged) { + sizeChanged = true; + w = mWidth; + h = mHeight; + if (DRAW_TWICE_AFTER_SIZE_CHANGED) { + // We keep mRequestRender true so that we draw twice after the size changes. + // (Once because of mSizeChanged, the second time because of mRequestRender.) + // This forces the updated graphics onto the screen. + } else { + mRequestRender = false; + } + mSizeChanged = false; + } else { + mRequestRender = false; + } + sGLThreadManager.notifyAll(); + break; + } + } + + // By design, this is the only place in a GLThread thread where we wait(). + if (LOG_THREADS) { + Log.i("GLThread", "waiting tid=" + getId()); + } + sGLThreadManager.wait(); + } + } // end of synchronized(sGLThreadManager) + + if (event != null) { + event.run(); + event = null; + } + + if (createEglSurface) { + mGL = (GL10) mEglHelper.createSurface(getHolder()); + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceCreated"); + } + mRenderer.onSurfaceCreated(mGL, mEglHelper.mEglConfig); + createEglSurface = false; + } + + if (sizeChanged) { + if (LOG_RENDERER) { + Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); + } + mRenderer.onSurfaceChanged(mGL, w, h); + sizeChanged = false; + } + + if (LOG_RENDERER) { + Log.w("GLThread", "onDrawFrame"); + } + if(!mEglHelper.swap()) { + if (LOG_SURFACE) { + Log.i("GLThread", "egl surface lost tid=" + getId()); + return false; + } + } + + } catch (InterruptedException e) { + return false; + } + return true; + } + + public void setRenderMode(int renderMode) { + if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) { + throw new IllegalArgumentException("renderMode"); + } + synchronized(sGLThreadManager) { + mRenderMode = renderMode; + sGLThreadManager.notifyAll(); + } + } + + public int getRenderMode() { + synchronized(sGLThreadManager) { + return mRenderMode; + } + } + + public void requestRender() { + synchronized(sGLThreadManager) { + mRequestRender = true; + sGLThreadManager.notifyAll(); + } + } + + public void surfaceCreated() { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceCreated tid=" + getId()); + } + mHasSurface = true; + sGLThreadManager.notifyAll(); + } + } + + public void surfaceDestroyed() { + synchronized(sGLThreadManager) { + if (LOG_THREADS) { + Log.i("GLThread", "surfaceDestroyed tid=" + getId()); + } + mHasSurface = false; + sGLThreadManager.notifyAll(); + while((!mWaitingForSurface) && (!mExited)) { + try { + sGLThreadManager.wait(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + } + + public void onPause() { + synchronized (sGLThreadManager) { + mPaused = true; + sGLThreadManager.notifyAll(); + } + } + + public void onResume() { + synchronized (sGLThreadManager) { + mPaused = false; + mRequestRender = true; + sGLThreadManager.notifyAll(); + } + } + + public void onWindowResize(int w, int h) { + synchronized (sGLThreadManager) { + mWidth = w; + mHeight = h; + mSizeChanged = true; + mRequestRender = true; + sGLThreadManager.notifyAll(); + } + } + + public void requestExitAndWait() { + // don't call this from GLThread thread or it is a guaranteed + // deadlock! + synchronized(sGLThreadManager) { + mShouldExit = true; + sGLThreadManager.notifyAll(); + while (! mExited) { + try { + sGLThreadManager.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + } + } + } + } + + /** + * Queue an "event" to be run on the GL rendering thread. + * @param r the runnable to be run on the GL rendering thread. + */ + public void queueEvent(Runnable r) { + if (r == null) { + throw new IllegalArgumentException("r must not be null"); + } + synchronized(sGLThreadManager) { + mEventQueue.add(r); + sGLThreadManager.notifyAll(); + } + } + + // Once the thread is started, all accesses to the following member + // variables are protected by the sGLThreadManager monitor + private boolean mShouldExit; + private boolean mExited; + private boolean mPaused; + private boolean mHasSurface; + private boolean mWaitingForSurface; + private boolean mHaveEgl; + private int mWidth; + private int mHeight; + private int mRenderMode; + private boolean mRequestRender; + private ArrayList mEventQueue = new ArrayList(); + // End of member variables protected by the sGLThreadManager monitor. + + private Renderer mRenderer; + private EglHelper mEglHelper; + private GL10 mGL = null; + } + + static class LogWriter extends Writer { + + @Override public void close() { + flushBuilder(); + } + + @Override public void flush() { + flushBuilder(); + } + + @Override public void write(char[] buf, int offset, int count) { + for(int i = 0; i < count; i++) { + char c = buf[offset + i]; + if ( c == '\n') { + flushBuilder(); + } + else { + mBuilder.append(c); + } + } + } + + private void flushBuilder() { + if (mBuilder.length() > 0) { + Log.v("GLSurfaceView", mBuilder.toString()); + mBuilder.delete(0, mBuilder.length()); + } + } + + private StringBuilder mBuilder = new StringBuilder(); + } + + + private void checkRenderThreadState() { + if (mGLThread != null) { + throw new IllegalStateException( + "setRenderer has already been called for this instance."); + } + } + + private static class GLThreadManager { + + public synchronized void threadExiting(GLThread thread) { + if (LOG_THREADS) { + Log.i("GLThread", "exiting tid=" + thread.getId()); + } + thread.mExited = true; + if (mEglOwner == thread) { + mEglOwner = null; + } + notifyAll(); + } + + /* + * Tries once to acquire the right to use an EGL + * surface. Does not block. Requires that we are already + * in the sGLThreadManager monitor when this is called. + * @return true if the right to use an EGL surface was acquired. + */ + public boolean tryAcquireEglSurfaceLocked(GLThread thread) { + if (mEglOwner == thread || mEglOwner == null) { + mEglOwner = thread; + notifyAll(); + return true; + } + return false; + } + /* + * Releases the EGL surface. Requires that we are already in the + * sGLThreadManager monitor when this is called. + */ + public void releaseEglSurfaceLocked(GLThread thread) { + if (mEglOwner == thread) { + mEglOwner = null; + } + notifyAll(); + } + + private GLThread mEglOwner; + } + + private static final GLThreadManager sGLThreadManager = new GLThreadManager(); + private boolean mSizeChanged = true; + + private GLThread mGLThread; + private EGLConfigChooser mEGLConfigChooser; + private EGLContextFactory mEGLContextFactory; + private EGLWindowSurfaceFactory mEGLWindowSurfaceFactory; + private GLWrapper mGLWrapper; + private int mDebugFlags; +}