From 2333a32697cda8f5f73861856889001839f38f25 Mon Sep 17 00:00:00 2001 From: dhewg Date: Sun, 27 Feb 2011 20:13:48 +0100 Subject: ANDROID: Untangle JNI interweaving - make the startup sequence more linear - use SurfaceHolder events - get rid of the surface lock - remove unnecessary JNI calls - make the ScummVM class implement Runnable - cleanup --- backends/platform/android/android.cpp | 44 +- backends/platform/android/android.h | 9 +- backends/platform/android/gfx.cpp | 23 +- backends/platform/android/jni.cpp | 192 +++++---- backends/platform/android/jni.h | 48 +-- .../android/org/inodes/gus/scummvm/ScummVM.java | 478 +++++++++++---------- .../org/inodes/gus/scummvm/ScummVMActivity.java | 91 +--- 7 files changed, 443 insertions(+), 442 deletions(-) (limited to 'backends/platform') diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index f886347685..2af367e903 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -103,6 +103,8 @@ OSystem_Android::OSystem_Android(int audio_sample_rate, int audio_buffer_size) : _audio_sample_rate(audio_sample_rate), _audio_buffer_size(audio_buffer_size), _screen_changeid(0), + _egl_surface_width(0), + _egl_surface_height(0), _force_redraw(false), _game_texture(0), _overlay_texture(0), @@ -126,11 +128,9 @@ OSystem_Android::~OSystem_Android() { delete _overlay_texture; delete _mouse_texture; - JNI::destroySurface(); - delete _savefile; - delete _mixer; delete _timer; + delete _mixer; delete _fsFactory; deleteMutex(_event_queue_lock); @@ -303,21 +303,19 @@ void OSystem_Android::initBackend() { _mixer = new Audio::MixerImpl(this, _audio_sample_rate); _mixer->setReady(true); - JNI::initBackend(); - _timer_thread_exit = false; pthread_create(&_timer_thread, 0, timerThreadFunc, this); _audio_thread_exit = false; pthread_create(&_audio_thread, 0, audioThreadFunc, this); - OSystem::initBackend(); - setupSurface(); // renice this thread to boost the audio thread if (setpriority(PRIO_PROCESS, 0, 19) < 0) warning("couldn't renice the main thread"); + + JNI::setReadyForEvents(true); } void OSystem_Android::addPluginDirectories(Common::FSList &dirs) const { @@ -385,6 +383,28 @@ void OSystem_Android::setupKeymapper() { bool OSystem_Android::pollEvent(Common::Event &event) { //ENTER(); + if (pthread_self() == _main_thread) { + if (_screen_changeid != JNI::surface_changeid) { + if (JNI::egl_surface_width > 0 && JNI::egl_surface_height > 0) { + LOGD("initializing surface"); + + JNI::deinitSurface(); + setupSurface(); + + event.type = Common::EVENT_SCREEN_CHANGED; + + return true; + } + + LOGD("deinitialiting surface"); + + _screen_changeid = JNI::surface_changeid; + JNI::deinitSurface(); + + // TODO prevent swapBuffers + } + } + lockMutex(_event_queue_lock); if (_event_queue.empty()) { @@ -433,12 +453,6 @@ bool OSystem_Android::pollEvent(Common::Event &event) { } break; } - case Common::EVENT_SCREEN_CHANGED: - debug("EVENT_SCREEN_CHANGED"); - _screen_changeid++; - JNI::destroySurface(); - setupSurface(); - break; default: break; } @@ -525,11 +539,15 @@ void OSystem_Android::deleteMutex(MutexRef mutex) { void OSystem_Android::quit() { ENTER(); + JNI::setReadyForEvents(false); + _audio_thread_exit = true; pthread_join(_audio_thread, 0); _timer_thread_exit = true; pthread_join(_timer_thread, 0); + + JNI::deinitSurface(); } void OSystem_Android::setWindowCaption(const char *caption) { diff --git a/backends/platform/android/android.h b/backends/platform/android/android.h index f4e9d611d4..aa6016e3d2 100644 --- a/backends/platform/android/android.h +++ b/backends/platform/android/android.h @@ -151,10 +151,6 @@ public: virtual void initBackend(); void addPluginDirectories(Common::FSList &dirs) const; void enableZoning(bool enable) { _enable_zoning = enable; } - void setSurfaceSize(int width, int height) { - _egl_surface_width = width; - _egl_surface_height = height; - } virtual bool hasFeature(Feature f); virtual void setFeatureState(Feature f, bool enable); @@ -166,10 +162,7 @@ public: virtual int getGraphicsMode() const; virtual void initSize(uint width, uint height, const Graphics::PixelFormat *format); - - virtual int getScreenChangeID() const { - return _screen_changeid; - } + virtual int getScreenChangeID() const; virtual int16 getHeight(); virtual int16 getWidth(); diff --git a/backends/platform/android/gfx.cpp b/backends/platform/android/gfx.cpp index 8601a3bfca..49f3d40e39 100644 --- a/backends/platform/android/gfx.cpp +++ b/backends/platform/android/gfx.cpp @@ -65,8 +65,13 @@ int OSystem_Android::getGraphicsMode() const { void OSystem_Android::setupSurface() { ENTER(); - if (!JNI::setupSurface()) - return; + _screen_changeid = JNI::surface_changeid; + JNI::initSurface(); + + _egl_surface_width = JNI::egl_surface_width; + _egl_surface_height = JNI::egl_surface_height; + + assert(_egl_surface_width > 0 && _egl_surface_height > 0); // EGL set up with a new surface. Initialise OpenGLES context. GLESTexture::initGLExtensions(); @@ -148,6 +153,10 @@ void OSystem_Android::initSize(uint width, uint height, _mouse_texture->allocBuffer(20, 20); } +int OSystem_Android::getScreenChangeID() const { + return _screen_changeid; +} + int16 OSystem_Android::getHeight() { return _game_texture->height(); } @@ -279,10 +288,14 @@ void OSystem_Android::updateScreen() { GLCALL(glPopMatrix()); - if (!JNI::swapBuffers()) { - // Context lost -> need to reinit GL - JNI::destroySurface(); + int res = JNI::swapBuffers(); + + if (res) { + warning("swapBuffers returned 0x%x", res); +#if 0 + JNI::initSurface(); setupSurface(); +#endif } } diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp index 99934fb640..28a03d0555 100644 --- a/backends/platform/android/jni.cpp +++ b/backends/platform/android/jni.cpp @@ -44,6 +44,11 @@ jobject JNI::_jobj_audio_track = 0; Common::Archive *JNI::_asset_archive = 0; OSystem_Android *JNI::_system = 0; +int JNI::surface_changeid = 0; +int JNI::egl_surface_width = 0; +int JNI::egl_surface_height = 0; +bool JNI::_ready_for_events = 0; + jfieldID JNI::_FID_Event_type = 0; jfieldID JNI::_FID_Event_synthetic = 0; jfieldID JNI::_FID_Event_kbd_keycode = 0; @@ -55,13 +60,12 @@ jfieldID JNI::_FID_Event_mouse_relative = 0; jmethodID JNI::_MID_displayMessageOnOSD = 0; jmethodID JNI::_MID_setWindowCaption = 0; -jmethodID JNI::_MID_initBackend = 0; jmethodID JNI::_MID_showVirtualKeyboard = 0; jmethodID JNI::_MID_getSysArchives = 0; jmethodID JNI::_MID_getPluginDirectories = 0; -jmethodID JNI::_MID_setupScummVMSurface = 0; -jmethodID JNI::_MID_destroyScummVMSurface = 0; jmethodID JNI::_MID_swapBuffers = 0; +jmethodID JNI::_MID_initSurface = 0; +jmethodID JNI::_MID_deinitSurface = 0; jmethodID JNI::_MID_AudioTrack_flush = 0; jmethodID JNI::_MID_AudioTrack_pause = 0; @@ -73,16 +77,16 @@ const JNINativeMethod JNI::_natives[] = { { "create", "(Landroid/content/res/AssetManager;" "Landroid/media/AudioTrack;II)V", (void *)JNI::create }, - { "nativeDestroy", "()V", + { "destroy", "()V", (void *)JNI::destroy }, - { "scummVMMain", "([Ljava/lang/String;)I", + { "setSurface", "(II)V", + (void *)JNI::setSurface }, + { "main", "([Ljava/lang/String;)I", (void *)JNI::main }, { "pushEvent", "(Lorg/inodes/gus/scummvm/Event;)V", (void *)JNI::pushEvent }, { "enableZoning", "(Z)V", (void *)JNI::enableZoning }, - { "setSurfaceSize", "(II)V", - (void *)JNI::setSurfaceSize }, }; JNI::JNI() { @@ -178,6 +182,10 @@ void JNI::detachThread() { } } +void JNI::setReadyForEvents(bool ready) { + _ready_for_events = ready; +} + void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { jclass cls = env->FindClass(name); @@ -188,61 +196,26 @@ void JNI::throwByName(JNIEnv *env, const char *name, const char *msg) { env->DeleteLocalRef(cls); } -// calls to the dark side - -void JNI::initBackend() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_initBackend); - - if (env->ExceptionCheck()) { - error("Error in Java initBackend"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - - // TODO now what? - } +void JNI::throwRuntimeException(JNIEnv *env, const char *msg) { + throwByName(env, "java/lang/RuntimeException", msg); } -void JNI::getPluginDirectories(Common::FSList &dirs) { +// calls to the dark side + +void JNI::displayMessageOnOSD(const char *msg) { JNIEnv *env = JNI::getEnv(); + jstring java_msg = env->NewStringUTF(msg); - jobjectArray array = - (jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories); + env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); if (env->ExceptionCheck()) { - LOGE("Error finding plugin directories"); + LOGE("Failed to display OSD message"); env->ExceptionDescribe(); env->ExceptionClear(); - - return; } - jsize size = env->GetArrayLength(array); - for (jsize i = 0; i < size; ++i) { - jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); - - if (path_obj == 0) - continue; - - const char *path = env->GetStringUTFChars(path_obj, 0); - - if (path == 0) { - LOGE("Error getting string characters from plugin directory"); - - env->ExceptionClear(); - env->DeleteLocalRef(path_obj); - - continue; - } - - dirs.push_back(Common::FSNode(path)); - - env->ReleaseStringUTFChars(path_obj, path); - env->DeleteLocalRef(path_obj); - } + env->DeleteLocalRef(java_msg); } void JNI::setWindowCaption(const char *caption) { @@ -261,22 +234,6 @@ void JNI::setWindowCaption(const char *caption) { env->DeleteLocalRef(java_caption); } -void JNI::displayMessageOnOSD(const char *msg) { - JNIEnv *env = JNI::getEnv(); - jstring java_msg = env->NewStringUTF(msg); - - env->CallVoidMethod(_jobj, _MID_displayMessageOnOSD, java_msg); - - if (env->ExceptionCheck()) { - LOGE("Failed to display OSD message"); - - env->ExceptionDescribe(); - env->ExceptionClear(); - } - - env->DeleteLocalRef(java_msg); -} - void JNI::showVirtualKeyboard(bool enable) { JNIEnv *env = JNI::getEnv(); @@ -321,6 +278,72 @@ void JNI::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) { } } +void JNI::getPluginDirectories(Common::FSList &dirs) { + JNIEnv *env = JNI::getEnv(); + + jobjectArray array = + (jobjectArray)env->CallObjectMethod(_jobj, _MID_getPluginDirectories); + + if (env->ExceptionCheck()) { + LOGE("Error finding plugin directories"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + + return; + } + + jsize size = env->GetArrayLength(array); + for (jsize i = 0; i < size; ++i) { + jstring path_obj = (jstring)env->GetObjectArrayElement(array, i); + + if (path_obj == 0) + continue; + + const char *path = env->GetStringUTFChars(path_obj, 0); + + if (path == 0) { + LOGE("Error getting string characters from plugin directory"); + + env->ExceptionClear(); + env->DeleteLocalRef(path_obj); + + continue; + } + + dirs.push_back(Common::FSNode(path)); + + env->ReleaseStringUTFChars(path_obj, path); + env->DeleteLocalRef(path_obj); + } +} + +void JNI::initSurface() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_initSurface); + + if (env->ExceptionCheck()) { + LOGE("initSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + +void JNI::deinitSurface() { + JNIEnv *env = JNI::getEnv(); + + env->CallVoidMethod(_jobj, _MID_deinitSurface); + + if (env->ExceptionCheck()) { + LOGE("deinitSurface failed"); + + env->ExceptionDescribe(); + env->ExceptionClear(); + } +} + void JNI::setAudioPause() { JNIEnv *env = JNI::getEnv(); @@ -396,13 +419,12 @@ void JNI::create(JNIEnv *env, jobject self, jobject am, jobject at, FIND_METHOD(setWindowCaption, "(Ljava/lang/String;)V"); FIND_METHOD(displayMessageOnOSD, "(Ljava/lang/String;)V"); - FIND_METHOD(initBackend, "()V"); FIND_METHOD(showVirtualKeyboard, "(Z)V"); FIND_METHOD(getSysArchives, "()[Ljava/lang/String;"); FIND_METHOD(getPluginDirectories, "()[Ljava/lang/String;"); - FIND_METHOD(setupScummVMSurface, "()V"); - FIND_METHOD(destroyScummVMSurface, "()V"); - FIND_METHOD(swapBuffers, "()Z"); + FIND_METHOD(swapBuffers, "()I"); + FIND_METHOD(initSurface, "()V"); + FIND_METHOD(deinitSurface, "()V"); #undef FIND_METHOD @@ -428,16 +450,12 @@ void JNI::create(JNIEnv *env, jobject self, jobject am, jobject at, } void JNI::destroy(JNIEnv *env, jobject self) { - if (!_system) - return; + delete _asset_archive; + _asset_archive = 0; - OSystem_Android *tmp = _system; + delete _system; g_system = 0; _system = 0; - delete tmp; - - delete _asset_archive; - _asset_archive = 0; // see above //JNI::getEnv()->DeleteWeakGlobalRef(_jobj); @@ -445,6 +463,12 @@ void JNI::destroy(JNIEnv *env, jobject self) { JNI::getEnv()->DeleteGlobalRef(_jobj); } +void JNI::setSurface(JNIEnv *env, jobject self, jint width, jint height) { + egl_surface_width = width; + egl_surface_height = height; + surface_changeid++; +} + jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { assert(_system); @@ -489,7 +513,7 @@ jint JNI::main(JNIEnv *env, jobject self, jobjectArray args) { res = scummvm_main(argc, argv); - LOGI("Exiting scummvm_main"); + LOGI("scummvm_main exited with code %d", res); _system->quit(); @@ -514,6 +538,10 @@ cleanup: } void JNI::pushEvent(JNIEnv *env, jobject self, jobject java_event) { + // drop events until we're ready and after we quit + if (!_ready_for_events) + return; + assert(_system); Common::Event event; @@ -565,11 +593,5 @@ void JNI::enableZoning(JNIEnv *env, jobject self, jboolean enable) { _system->enableZoning(enable); } -void JNI::setSurfaceSize(JNIEnv *env, jobject self, jint width, jint height) { - assert(_system); - - _system->setSurfaceSize(width, height); -} - #endif diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h index 0bc64980e2..0005136966 100644 --- a/backends/platform/android/jni.h +++ b/backends/platform/android/jni.h @@ -41,6 +41,10 @@ private: virtual ~JNI(); public: + static int surface_changeid; + static int egl_surface_width; + static int egl_surface_height; + static jint onLoad(JavaVM *vm); static JNIEnv *getEnv(); @@ -48,16 +52,17 @@ public: static void attachThread(); static void detachThread(); - static void initBackend(); + static void setReadyForEvents(bool ready); + static void getPluginDirectories(Common::FSList &dirs); static void setWindowCaption(const char *caption); static void displayMessageOnOSD(const char *msg); static void showVirtualKeyboard(bool enable); static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority); - static inline bool setupSurface(); - static inline void destroySurface(); - static inline bool swapBuffers(); + static inline int swapBuffers(); + static void initSurface(); + static void deinitSurface(); static void setAudioPause(); static void setAudioPlay(); @@ -75,6 +80,8 @@ private: static Common::Archive *_asset_archive; static OSystem_Android *_system; + static bool _ready_for_events; + static jfieldID _FID_Event_type; static jfieldID _FID_Event_synthetic; static jfieldID _FID_Event_kbd_keycode; @@ -87,13 +94,12 @@ private: static jmethodID _MID_displayMessageOnOSD; static jmethodID _MID_setWindowCaption; - static jmethodID _MID_initBackend; static jmethodID _MID_showVirtualKeyboard; static jmethodID _MID_getSysArchives; static jmethodID _MID_getPluginDirectories; - static jmethodID _MID_setupScummVMSurface; - static jmethodID _MID_destroyScummVMSurface; static jmethodID _MID_swapBuffers; + static jmethodID _MID_initSurface; + static jmethodID _MID_deinitSurface; static jmethodID _MID_AudioTrack_flush; static jmethodID _MID_AudioTrack_pause; @@ -104,40 +110,24 @@ private: static const JNINativeMethod _natives[]; static void throwByName(JNIEnv *env, const char *name, const char *msg); + static void throwRuntimeException(JNIEnv *env, const char *msg); // natives for the dark side static void create(JNIEnv *env, jobject self, jobject am, jobject at, jint sample_rate, jint buffer_size); static void destroy(JNIEnv *env, jobject self); + + static void setSurface(JNIEnv *env, jobject self, jint width, jint height); static jint main(JNIEnv *env, jobject self, jobjectArray args); + static void pushEvent(JNIEnv *env, jobject self, jobject java_event); - static void setConfManInt(JNIEnv *env, jclass cls, jstring key_obj, - jint value); - static void setConfManString(JNIEnv *env, jclass cls, jstring key_obj, - jstring value_obj); static void enableZoning(JNIEnv *env, jobject self, jboolean enable); - static void setSurfaceSize(JNIEnv *env, jobject self, jint width, - jint height); }; -inline bool JNI::setupSurface() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_setupScummVMSurface); - - return !env->ExceptionCheck(); -} - -inline void JNI::destroySurface() { - JNIEnv *env = JNI::getEnv(); - - env->CallVoidMethod(_jobj, _MID_destroyScummVMSurface); -} - -inline bool JNI::swapBuffers() { +inline int JNI::swapBuffers() { JNIEnv *env = JNI::getEnv(); - return env->CallBooleanMethod(_jobj, _MID_swapBuffers); + return env->CallIntMethod(_jobj, _MID_swapBuffers); } inline int JNI::writeAudio(JNIEnv *env, jbyteArray &data, int offset, int size) { diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java index ae7204f752..db83303c7d 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -1,18 +1,12 @@ package org.inodes.gus.scummvm; -import android.content.Context; +import android.util.Log; import android.content.res.AssetManager; +import android.view.SurfaceHolder; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Process; -import android.util.Log; -import android.view.Surface; -import android.view.SurfaceHolder; -import javax.microedition.khronos.opengles.GL; import javax.microedition.khronos.opengles.GL10; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -22,28 +16,242 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import java.io.File; -import java.util.concurrent.Semaphore; import java.util.Map; import java.util.LinkedHashMap; +public abstract class ScummVM implements SurfaceHolder.Callback, Runnable { + final protected static String LOG_TAG = "ScummVM"; + final private AssetManager asset_manager; + final private Object sem_surface; + + private EGL10 egl; + private EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY; + private EGLConfig eglConfig; + private EGLContext eglContext = EGL10.EGL_NO_CONTEXT; + private EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; + + private SurfaceHolder surface_holder; + private AudioTrack audio_track; + private int sample_rate = 0; + private int buffer_size = 0; + + private String[] args; + + final private native void create(AssetManager asset_manager, + AudioTrack audio_track, + int sample_rate, int buffer_size); + final private native void destroy(); + final private native void setSurface(int width, int height); + final private native int main(String[] args); + + // Set scummvm config options + final public native void enableZoning(boolean enable); + // Feed an event to ScummVM. Safe to call from other threads. + final public native void pushEvent(Event e); + + // Callbacks from C++ peer instance + abstract protected void displayMessageOnOSD(String msg); + abstract protected void setWindowCaption(String caption); + abstract protected String[] getPluginDirectories(); + abstract protected void showVirtualKeyboard(boolean enable); + abstract protected String[] getSysArchives(); + + final protected int swapBuffers() { + if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) + return egl.eglGetError(); + + return 0; + } + + public ScummVM(AssetManager asset_manager, SurfaceHolder holder) { + this.asset_manager = asset_manager; + sem_surface = new Object(); + + holder.addCallback(this); + } + + // SurfaceHolder callback + final public void surfaceCreated(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceCreated"); + + // no need to do anything, surfaceChanged() will be called in any case + } + + // SurfaceHolder callback + final public void surfaceChanged(SurfaceHolder holder, int format, + int width, int height) { + Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)", + width, height, format)); + + synchronized(sem_surface) { + surface_holder = holder; + sem_surface.notifyAll(); + } + + // store values for the native code + setSurface(width, height); + } + + // SurfaceHolder callback + final public void surfaceDestroyed(SurfaceHolder holder) { + Log.d(LOG_TAG, "surfaceDestroyed"); + + synchronized(sem_surface) { + surface_holder = null; + sem_surface.notifyAll(); + } + + // clear values for the native code + setSurface(0, 0); + } + + final public void setArgs(String[] args) { + this.args = args; + } + + final public void run() { + try { + initAudio(); + initEGL(); + + // wait for the surfaceChanged callback + synchronized(sem_surface) { + while (surface_holder == null) + sem_surface.wait(); + } + } catch (Exception e) { + deinitEGL(); + deinitAudio(); + + throw new RuntimeException("Error preparing the ScummVM thread", e); + } + + create(asset_manager, audio_track, sample_rate, buffer_size); + + int res = main(args); + + destroy(); + + deinitEGL(); + deinitAudio(); + + // On exit, tear everything down for a fresh restart next time. + System.exit(res); + } + + public void pause() { + // TODO + } + + public void resume() { + // TODO + } + + final private void initEGL() throws Exception { + egl = (EGL10)EGLContext.getEGL(); + eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + int[] version = new int[2]; + egl.eglInitialize(eglDisplay, version); + + int[] num_config = new int[1]; + egl.eglChooseConfig(eglDisplay, configSpec, null, 0, num_config); + + final int numConfigs = num_config[0]; + + if (numConfigs <= 0) + throw new IllegalArgumentException("No configs match configSpec"); + + EGLConfig[] configs = new EGLConfig[numConfigs]; + egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs, + num_config); + + if (false) { + Log.d(LOG_TAG, String.format("Found %d EGL configurations.", + numConfigs)); + for (EGLConfig config : configs) + dumpEglConfig(config); + } + + // Android's eglChooseConfig is busted in several versions and + // devices so we have to filter/rank the configs again ourselves. + eglConfig = chooseEglConfig(configs); + + if (false) { + Log.d(LOG_TAG, String.format("Chose from %d EGL configs", + numConfigs)); + dumpEglConfig(eglConfig); + } + + eglContext = egl.eglCreateContext(eglDisplay, eglConfig, + EGL10.EGL_NO_CONTEXT, null); + + if (eglContext == EGL10.EGL_NO_CONTEXT) + throw new Exception(String.format("Failed to create context: 0x%x", + egl.eglGetError())); + } + + // Callback from C++ peer instance + final protected void initSurface() throws Exception { + eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, + surface_holder, null); + + if (eglSurface == EGL10.EGL_NO_SURFACE) + throw new Exception(String.format( + "eglCreateWindowSurface failed: 0x%x", egl.eglGetError())); + + egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); + + GL10 gl = (GL10)eglContext.getGL(); + + Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", + egl.eglQueryString(eglDisplay, EGL10.EGL_VERSION), + egl.eglQueryString(eglDisplay, EGL10.EGL_VENDOR), + gl.glGetString(GL10.GL_VERSION), + gl.glGetString(GL10.GL_RENDERER), + gl.glGetString(GL10.GL_VENDOR))); + } + + // Callback from C++ peer instance + final protected void deinitSurface() { + if (eglDisplay != EGL10.EGL_NO_DISPLAY) { + egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + + if (eglSurface != EGL10.EGL_NO_SURFACE) + egl.eglDestroySurface(eglDisplay, eglSurface); + } + + eglSurface = EGL10.EGL_NO_SURFACE; + } -// At least in Android 2.1, eglCreateWindowSurface() requires an -// EGLNativeWindowSurface object, which is hidden deep in the bowels -// of libui. Until EGL is properly exposed, it's probably safer to -// use the Java versions of most EGL functions :( + final private void deinitEGL() { + if (eglDisplay != EGL10.EGL_NO_DISPLAY) { + egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); -public class ScummVM implements SurfaceHolder.Callback { - protected final static String LOG_TAG = "ScummVM"; + if (eglSurface != EGL10.EGL_NO_SURFACE) + egl.eglDestroySurface(eglDisplay, eglSurface); + + if (eglContext != EGL10.EGL_NO_CONTEXT) + egl.eglDestroyContext(eglDisplay, eglContext); + + egl.eglTerminate(eglDisplay); + } - private native void create(AssetManager am, AudioTrack audio_track, - int sample_rate, int buffer_size); + eglSurface = EGL10.EGL_NO_SURFACE; + eglContext = EGL10.EGL_NO_CONTEXT; + eglConfig = null; + eglDisplay = EGL10.EGL_NO_DISPLAY; + egl = null; + } - public ScummVM(Context context) throws Exception { - int sample_rate = AudioTrack.getNativeOutputSampleRate( - AudioManager.STREAM_MUSIC); - int buffer_size = AudioTrack.getMinBufferSize(sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT); + final private void initAudio() throws Exception { + sample_rate = AudioTrack.getNativeOutputSampleRate( + AudioManager.STREAM_MUSIC); + buffer_size = AudioTrack.getMinBufferSize(sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT); // ~100ms int buffer_size_want = (sample_rate * 2 * 2 / 10) & ~1023; @@ -58,48 +266,28 @@ public class ScummVM implements SurfaceHolder.Callback { Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio", buffer_size, sample_rate)); - AudioTrack audio_track = - new AudioTrack(AudioManager.STREAM_MUSIC, - sample_rate, - AudioFormat.CHANNEL_CONFIGURATION_STEREO, - AudioFormat.ENCODING_PCM_16BIT, - buffer_size, - AudioTrack.MODE_STREAM); + audio_track = new AudioTrack(AudioManager.STREAM_MUSIC, + sample_rate, + AudioFormat.CHANNEL_CONFIGURATION_STEREO, + AudioFormat.ENCODING_PCM_16BIT, + buffer_size, + AudioTrack.MODE_STREAM); if (audio_track.getState() != AudioTrack.STATE_INITIALIZED) throw new Exception( String.format("Error initialising AudioTrack: %d", audio_track.getState())); - - // Init C++ code - create(context.getAssets(), audio_track, sample_rate, buffer_size); } - private native void nativeDestroy(); + final private void deinitAudio() { + if (audio_track != null) + audio_track.stop(); - public synchronized void destroy() { - nativeDestroy(); + audio_track = null; + buffer_size = 0; + sample_rate = 0; } - protected void finalize() { - destroy(); - } - - // Surface creation: - // GUI thread: create surface, release lock - // ScummVM thread: acquire lock (block), read surface - - // Surface deletion: - // GUI thread: post event, acquire lock (block), return - // ScummVM thread: read event, free surface, release lock - - // In other words, ScummVM thread does this: - // acquire lock - // setup surface - // when SCREEN_CHANGED arrives: - // destroy surface - // release lock - // back to acquire lock static final int configSpec[] = { EGL10.EGL_RED_SIZE, 5, EGL10.EGL_GREEN_SIZE, 5, @@ -109,33 +297,6 @@ public class ScummVM implements SurfaceHolder.Callback { EGL10.EGL_NONE, }; - EGL10 egl; - EGLDisplay eglDisplay = EGL10.EGL_NO_DISPLAY; - EGLConfig eglConfig; - EGLContext eglContext = EGL10.EGL_NO_CONTEXT; - EGLSurface eglSurface = EGL10.EGL_NO_SURFACE; - Semaphore surfaceLock = new Semaphore(0, true); - SurfaceHolder nativeSurface; - - public void surfaceCreated(SurfaceHolder holder) { - nativeSurface = holder; - surfaceLock.release(); - } - - public void surfaceChanged(SurfaceHolder holder, int format, - int width, int height) { - // Disabled while I debug GL problems - pushEvent(new Event(Event.EVENT_SCREEN_CHANGED)); - } - - public void surfaceDestroyed(SurfaceHolder holder) { - try { - surfaceLock.acquire(); - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); - } - } - // For debugging private static final Map attribs; @@ -170,7 +331,7 @@ public class ScummVM implements SurfaceHolder.Callback { attribs.put("TRANSPARENT_BLUE_VALUE", EGL10.EGL_TRANSPARENT_BLUE_VALUE); } - private void dumpEglConfig(EGLConfig config) { + final private void dumpEglConfig(EGLConfig config) { int[] value = new int[1]; for (Map.Entry entry : attribs.entrySet()) { @@ -184,49 +345,7 @@ public class ScummVM implements SurfaceHolder.Callback { } } - // Called by ScummVM thread (from initBackend) - private void createScummVMGLContext() { - egl = (EGL10)EGLContext.getEGL(); - eglDisplay = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - int[] version = new int[2]; - egl.eglInitialize(eglDisplay, version); - - int[] num_config = new int[1]; - egl.eglChooseConfig(eglDisplay, configSpec, null, 0, num_config); - - final int numConfigs = num_config[0]; - - if (numConfigs <= 0) - throw new IllegalArgumentException("No configs match configSpec"); - - EGLConfig[] configs = new EGLConfig[numConfigs]; - egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs, - num_config); - - if (false) { - Log.d(LOG_TAG, String.format("Found %d EGL configurations.", numConfigs)); - for (EGLConfig config : configs) - dumpEglConfig(config); - } - - // Android's eglChooseConfig is busted in several versions and - // devices so we have to filter/rank the configs again ourselves. - eglConfig = chooseEglConfig(configs); - - if (false) { - Log.d(LOG_TAG, String.format("Chose EGL config from %d possibilities.", numConfigs)); - dumpEglConfig(eglConfig); - } - - eglContext = egl.eglCreateContext(eglDisplay, eglConfig, - EGL10.EGL_NO_CONTEXT, null); - - if (eglContext == EGL10.EGL_NO_CONTEXT) - throw new RuntimeException("Failed to create context"); - } - - private EGLConfig chooseEglConfig(EGLConfig[] configs) { + final private EGLConfig chooseEglConfig(EGLConfig[] configs) { int best = 0; int bestScore = -1; int[] value = new int[1]; @@ -286,111 +405,6 @@ public class ScummVM implements SurfaceHolder.Callback { return configs[best]; } - // Called by ScummVM thread - static private boolean _log_version = true; - - protected void setupScummVMSurface() { - try { - surfaceLock.acquire(); - } catch (InterruptedException e) { - Log.e(LOG_TAG, "Interrupted while waiting for surface lock", e); - return; - } - - eglSurface = egl.eglCreateWindowSurface(eglDisplay, eglConfig, - nativeSurface, null); - - if (eglSurface == EGL10.EGL_NO_SURFACE) - Log.e(LOG_TAG, "CreateWindowSurface failed!"); - - egl.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext); - - GL10 gl = (GL10)eglContext.getGL(); - - if (_log_version) { - Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)", - egl.eglQueryString(eglDisplay, EGL10.EGL_VERSION), - egl.eglQueryString(eglDisplay, EGL10.EGL_VENDOR), - gl.glGetString(GL10.GL_VERSION), - gl.glGetString(GL10.GL_RENDERER), - gl.glGetString(GL10.GL_VENDOR))); - - // only log this once - _log_version = false; - } - - int[] value = new int[1]; - egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_WIDTH, value); - - int width = value[0]; - egl.eglQuerySurface(eglDisplay, eglSurface, EGL10.EGL_HEIGHT, value); - - int height = value[0]; - Log.i(LOG_TAG, String.format("New surface is %dx%d", width, height)); - setSurfaceSize(width, height); - } - - // Called by ScummVM thread - protected void destroyScummVMSurface() { - if (eglSurface != null) { - egl.eglMakeCurrent(eglDisplay, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - - egl.eglDestroySurface(eglDisplay, eglSurface); - eglSurface = EGL10.EGL_NO_SURFACE; - } - - surfaceLock.release(); - } - - public void setSurface(SurfaceHolder holder) { - holder.addCallback(this); - } - - final public boolean swapBuffers() { - if (!egl.eglSwapBuffers(eglDisplay, eglSurface)) { - int error = egl.eglGetError(); - Log.w(LOG_TAG, String.format("eglSwapBuffers exited with error 0x%x", error)); - if (error == EGL11.EGL_CONTEXT_LOST) - return false; - } - - return true; - } - - // Set scummvm config options - final public native void enableZoning(boolean enable); - final public native void setSurfaceSize(int width, int height); - - // Feed an event to ScummVM. Safe to call from other threads. - final public native void pushEvent(Event e); - - // Runs the actual ScummVM program and returns when it does. - // This should not be called from multiple threads simultaneously... - final public native int scummVMMain(String[] argv); - - // Callbacks from C++ peer instance - //protected GraphicsMode[] getSupportedGraphicsModes() {} - protected void displayMessageOnOSD(String msg) {} - protected void setWindowCaption(String caption) {} - protected void showVirtualKeyboard(boolean enable) {} - protected String[] getSysArchives() { return new String[0]; } - protected String[] getPluginDirectories() { return new String[0]; } - - protected void initBackend() { - createScummVMGLContext(); - } - - public void pause() { - // TODO: need to pause audio - // TODO: need to pause engine - } - - public void resume() { - // TODO: need to resume audio - // TODO: need to resume engine - } - static { // For grabbing with gdb... final boolean sleep_for_debugger = false; @@ -401,10 +415,10 @@ public class ScummVM implements SurfaceHolder.Callback { } } - //System.loadLibrary("scummvm"); File cache_dir = ScummVMApplication.getLastCacheDir(); String libname = System.mapLibraryName("scummvm"); File libpath = new File(cache_dir, libname); + System.load(libpath.getPath()); } } diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java index fb6020cf1c..92247dea96 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVMActivity.java @@ -3,6 +3,7 @@ package org.inodes.gus.scummvm; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; +import android.content.res.AssetManager; import android.content.res.Configuration; import android.media.AudioManager; import android.os.Bundle; @@ -14,6 +15,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceView; +import android.view.SurfaceHolder; import android.view.View; import android.view.ViewConfiguration; import android.view.inputmethod.InputMethodManager; @@ -30,8 +32,6 @@ public class ScummVMActivity extends Activity { private final static int TRACKBALL_SCALE = 2; private class MyScummVM extends ScummVM { - private boolean scummvmRunning = false; - private boolean usingSmallScreen() { // Multiple screen sizes came in with Android 1.6. Have // to use reflection in order to continue supporting 1.5 @@ -49,8 +49,8 @@ public class ScummVMActivity extends Activity { } } - public MyScummVM() throws Exception { - super(ScummVMActivity.this); + public MyScummVM(SurfaceHolder holder) { + super(ScummVMActivity.this.getAssets(), holder); // Enable ScummVM zoning on 'small' screens. // FIXME make this optional for the user @@ -58,23 +58,6 @@ public class ScummVMActivity extends Activity { //enableZoning(usingSmallScreen()); } - @Override - protected void initBackend() { - synchronized (this) { - scummvmRunning = true; - notifyAll(); - } - - super.initBackend(); - } - - public void waitUntilRunning() throws InterruptedException { - synchronized (this) { - while (!scummvmRunning) - wait(); - } - } - @Override protected void displayMessageOnOSD(String msg) { Log.i(LOG_TAG, "OSD: " + msg); @@ -105,6 +88,12 @@ public class ScummVMActivity extends Activity { } }); } + + @Override + protected String[] getSysArchives() { + return new String[0]; + } + } private MyScummVM scummvm; @@ -140,74 +129,36 @@ public class ScummVMActivity extends Activity { } SurfaceView main_surface = (SurfaceView)findViewById(R.id.main_surface); + main_surface.setOnTouchListener(new View.OnTouchListener() { public boolean onTouch(View v, MotionEvent event) { return onTouchEvent(event); } }); + main_surface.setOnKeyListener(new View.OnKeyListener() { public boolean onKey(View v, int code, KeyEvent ev) { return onKeyDown(code, ev); } }); - main_surface.requestFocus(); - // Start ScummVM - try { - scummvm = new MyScummVM(); - } catch (Exception e) { - Log.e(ScummVM.LOG_TAG, "Fatal error", e); - finish(); - return; - } - - scummvm_thread = new Thread(new Runnable() { - public void run() { - try { - runScummVM(); - } catch (Exception e) { - Log.e(ScummVM.LOG_TAG, "Fatal error in ScummVM thread", e); - new AlertDialog.Builder(ScummVMActivity.this) - .setTitle("Error") - .setMessage(e.toString()) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); - finish(); - } - } - }, "ScummVM"); - - scummvm_thread.start(); + main_surface.requestFocus(); - // Block UI thread until ScummVM has started. In particular, - // this means that surface and event callbacks should be safe - // after this point. - try { - scummvm.waitUntilRunning(); - } catch (InterruptedException e) { - Log.e(ScummVM.LOG_TAG, "Interrupted while waiting for ScummVM.initBackend", e); - finish(); - } + getFilesDir().mkdirs(); - scummvm.setSurface(main_surface.getHolder()); - } + // Start ScummVM + scummvm = new MyScummVM(main_surface.getHolder()); - // Runs in another thread - private void runScummVM() throws IOException { - getFilesDir().mkdirs(); - String[] args = { - "ScummVM-lib", + scummvm.setArgs(new String[] { + "ScummVM", "--config=" + getFileStreamPath("scummvmrc").getPath(), "--path=" + Environment.getExternalStorageDirectory().getPath(), "--gui-theme=scummmodern", "--savepath=" + getDir("saves", 0).getPath() - }; + }); - int ret = scummvm.scummVMMain(args); - - // On exit, tear everything down for a fresh - // restart next time. - System.exit(ret); + scummvm_thread = new Thread(scummvm, "ScummVM"); + scummvm_thread.start(); } private boolean was_paused = false; -- cgit v1.2.3