diff options
Diffstat (limited to 'backends/platform/android')
-rw-r--r-- | backends/platform/android/README.build | 3 | ||||
-rw-r--r-- | backends/platform/android/android.cpp | 10 | ||||
-rw-r--r-- | backends/platform/android/org/inodes/gus/scummvm/ScummVM.java | 123 | ||||
-rw-r--r-- | backends/platform/android/video.cpp | 40 |
4 files changed, 157 insertions, 19 deletions
diff --git a/backends/platform/android/README.build b/backends/platform/android/README.build index 1c407bd469..f3fb77cbcf 100644 --- a/backends/platform/android/README.build +++ b/backends/platform/android/README.build @@ -82,7 +82,8 @@ Then build ScummVM: export ANDROID_TOP=<root of built Android source> - ./configure --backend=android --host=android --enable-zlib #and any other flags + ./configure --backend=android --host=android --enable-zlib --disable-timidity + # ... and any other configure flags you want make scummvm.apk This will build a "monolithic" ScummVM package, with the engines diff --git a/backends/platform/android/android.cpp b/backends/platform/android/android.cpp index dcc4e37458..38f387b201 100644 --- a/backends/platform/android/android.cpp +++ b/backends/platform/android/android.cpp @@ -109,7 +109,7 @@ static void JNU_ThrowByName(JNIEnv* env, const char* name, const char* msg) { env->DeleteLocalRef(cls); } -// floating point. use sparingly. +// floating point. use sparingly. template <class T> static inline T scalef(T in, float numerator, float denominator) { return static_cast<float>(in) * numerator / denominator; @@ -177,7 +177,6 @@ private: GLESPaletteTexture* _game_texture; int _shake_offset; Common::Rect _focus_rect; - bool _full_screen_dirty; // Overlay layer GLES4444Texture* _overlay_texture; @@ -320,7 +319,6 @@ OSystem_Android::OSystem_Android(jobject am) _fsFactory(new POSIXFilesystemFactory()), _asset_archive(new AndroidAssetArchive(am)), _shake_offset(0), - _full_screen_dirty(false), _event_queue_lock(createMutex()) { } @@ -862,6 +860,9 @@ void OSystem_Android::hideOverlay() { void OSystem_Android::clearOverlay() { ENTER("clearOverlay()"); _overlay_texture->fillBuffer(0); + + // Shouldn't need this, but works around a 'blank screen' bug on Nexus1 + updateScreen(); } void OSystem_Android::grabOverlay(OverlayColor *buf, int pitch) { @@ -887,6 +888,9 @@ void OSystem_Android::copyRectToOverlay(const OverlayColor *buf, int pitch, // This 'pitch' is pixels not bytes _overlay_texture->updateBuffer(x, y, w, h, buf, pitch * sizeof(buf[0])); + + // Shouldn't need this, but works around a 'blank screen' bug on Nexus1? + updateScreen(); } int16 OSystem_Android::getOverlayHeight() { diff --git a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java index d39aa363ef..6986f3988d 100644 --- a/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java +++ b/backends/platform/android/org/inodes/gus/scummvm/ScummVM.java @@ -23,6 +23,8 @@ import javax.microedition.khronos.egl.EGLSurface; import java.io.File; import java.util.concurrent.Semaphore; +import java.util.Map; +import java.util.LinkedHashMap; // At least in Android 2.1, eglCreateWindowSurface() requires an @@ -109,6 +111,51 @@ public class ScummVM implements SurfaceHolder.Callback { } } + // For debugging + private static final Map<String, Integer> attribs; + static { + attribs = new LinkedHashMap<String, Integer>(); + attribs.put("CONFIG_ID", EGL10.EGL_CONFIG_ID); + attribs.put("BUFFER_SIZE", EGL10.EGL_BUFFER_SIZE); + attribs.put("RED_SIZE", EGL10.EGL_RED_SIZE); + attribs.put("GREEN_SIZE", EGL10.EGL_GREEN_SIZE); + attribs.put("BLUE_SIZE", EGL10.EGL_BLUE_SIZE); + attribs.put("ALPHA_SIZE", EGL10.EGL_ALPHA_SIZE); + //attribs.put("BIND_TO_RGB", EGL10.EGL_BIND_TO_TEXTURE_RGB); + //attribs.put("BIND_TO_RGBA", EGL10.EGL_BIND_TO_TEXTURE_RGBA); + attribs.put("CONFIG_CAVEAT", EGL10.EGL_CONFIG_CAVEAT); + attribs.put("DEPTH_SIZE", EGL10.EGL_DEPTH_SIZE); + attribs.put("LEVEL", EGL10.EGL_LEVEL); + attribs.put("MAX_PBUFFER_WIDTH", EGL10.EGL_MAX_PBUFFER_WIDTH); + attribs.put("MAX_PBUFFER_HEIGHT", EGL10.EGL_MAX_PBUFFER_HEIGHT); + attribs.put("MAX_PBUFFER_PIXELS", EGL10.EGL_MAX_PBUFFER_PIXELS); + //attribs.put("MAX_SWAP_INTERVAL", EGL10.EGL_MAX_SWAP_INTERVAL); + //attribs.put("MIN_SWAP_INTERVAL", EGL10.EGL_MIN_SWAP_INTERVAL); + attribs.put("NATIVE_RENDERABLE", EGL10.EGL_NATIVE_RENDERABLE); + attribs.put("NATIVE_VISUAL_ID", EGL10.EGL_NATIVE_VISUAL_ID); + attribs.put("NATIVE_VISUAL_TYPE", EGL10.EGL_NATIVE_VISUAL_TYPE); + attribs.put("SAMPLE_BUFFERS", EGL10.EGL_SAMPLE_BUFFERS); + attribs.put("SAMPLES", EGL10.EGL_SAMPLES); + attribs.put("STENCIL_SIZE", EGL10.EGL_STENCIL_SIZE); + attribs.put("SURFACE_TYPE", EGL10.EGL_SURFACE_TYPE); + attribs.put("TRANSPARENT_TYPE", EGL10.EGL_TRANSPARENT_TYPE); + attribs.put("TRANSPARENT_RED_VALUE", EGL10.EGL_TRANSPARENT_RED_VALUE); + attribs.put("TRANSPARENT_GREEN_VALUE", EGL10.EGL_TRANSPARENT_GREEN_VALUE); + attribs.put("TRANSPARENT_BLUE_VALUE", EGL10.EGL_TRANSPARENT_BLUE_VALUE); + } + private void dumpEglConfig(EGLConfig config) { + int[] value = new int[1]; + for (Map.Entry<String, Integer> entry : attribs.entrySet()) { + egl.eglGetConfigAttrib(eglDisplay, config, + entry.getValue(), value); + if (value[0] == EGL10.EGL_NONE) + Log.d(LOG_TAG, entry.getKey() + ": NONE"); + else + Log.d(LOG_TAG, String.format("%s: %d", + entry.getKey(), value[0])); + } + } + // Called by ScummVM thread (from initBackend) private void createScummVMGLContext() { egl = (EGL10)EGLContext.getEGL(); @@ -125,10 +172,75 @@ public class ScummVM implements SurfaceHolder.Callback { EGLConfig[] configs = new EGLConfig[numConfigs]; egl.eglChooseConfig(eglDisplay, configSpec, configs, numConfigs, num_config); - eglConfig = configs[0]; + + 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) { + int best = 0; + int bestScore = -1; + int[] value = new int[1]; + for (int i = 0; i < configs.length; i++) { + EGLConfig config = configs[i]; + int score = 10000; + egl.eglGetConfigAttrib(eglDisplay, config, + EGL10.EGL_SURFACE_TYPE, value); + if ((value[0] & EGL10.EGL_WINDOW_BIT) == 0) + continue; // must have + + egl.eglGetConfigAttrib(eglDisplay, config, + EGL10.EGL_CONFIG_CAVEAT, value); + if (value[0] != EGL10.EGL_NONE) + score -= 1000; + + // Must be at least 555, but then smaller is better + final int[] colorBits = {EGL10.EGL_RED_SIZE, + EGL10.EGL_GREEN_SIZE, + EGL10.EGL_BLUE_SIZE, + EGL10.EGL_ALPHA_SIZE}; + for (int component : colorBits) { + egl.eglGetConfigAttrib(eglDisplay, config, + component, value); + if (value[0] >= 5) + score += 10; // boost if >5 bits accuracy + score -= value[0]; // penalize for wasted bits + } + + egl.eglGetConfigAttrib(eglDisplay, config, + EGL10.EGL_DEPTH_SIZE, value); + score -= value[0]; // penalize for wasted bits + + if (score > bestScore) { + best = i; + bestScore = score; + } + } + + if (bestScore < 0) { + Log.e(LOG_TAG, "Unable to find an acceptable EGL config, expect badness."); + return configs[0]; + } + + return configs[best]; } // Called by ScummVM thread @@ -137,12 +249,13 @@ public class ScummVM implements SurfaceHolder.Callback { try { surfaceLock.acquire(); } catch (InterruptedException e) { - Log.e(this.toString(), - "Interrupted while waiting for surface lock", 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(); @@ -302,8 +415,8 @@ public class ScummVM implements SurfaceHolder.Callback { if (buf_size < 0) { int guess = AUDIO_FRAME_SIZE * sample_rate / 100; // 10ms of audio Log.w(LOG_TAG, String.format( - "Unable to get min audio buffer size (error %d). Guessing %dB.", - buf_size, guess)); + "Unable to get min audio buffer size (error %d). Guessing %dB.", + buf_size, guess)); buf_size = guess; } Log.d(LOG_TAG, String.format("Using %dB buffer for %dHZ audio", diff --git a/backends/platform/android/video.cpp b/backends/platform/android/video.cpp index d4c002fbd0..81a8f7fbc7 100644 --- a/backends/platform/android/video.cpp +++ b/backends/platform/android/video.cpp @@ -38,6 +38,9 @@ #include "backends/platform/android/video.h" +// Unfortunately, Android devices are too varied to make broad assumptions :/ +#define TEXSUBIMAGE_IS_EXPENSIVE 0 + #undef LOG_TAG #define LOG_TAG "ScummVM-video" @@ -158,13 +161,11 @@ void GLESTexture::allocBuffer(GLuint w, GLuint h) { // later (perhaps with multiple TexSubImage2D operations). CHECK_GL_ERROR(); glBindTexture(GL_TEXTURE_2D, _texture_name); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); CHECK_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - CHECK_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - CHECK_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - CHECK_GL_ERROR(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); CHECK_GL_ERROR(); glTexImage2D(GL_TEXTURE_2D, 0, glFormat(), @@ -177,6 +178,7 @@ void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, const void* buf, int pitch) { ENTER("updateBuffer(%u, %u, %u, %u, %p, %d)", x, y, w, h, buf, pitch); glBindTexture(GL_TEXTURE_2D, _texture_name); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); setDirtyRect(Common::Rect(x, y, x+w, y+h)); @@ -185,7 +187,25 @@ void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, glFormat(), glType(), buf); } else { // GLES removed the ability to specify pitch, so we - // have to do this row by row. + // have to do this ourselves. + if (h == 0) + return; + +#if TEXSUBIMAGE_IS_EXPENSIVE + byte tmpbuf[w * h * bytesPerPixel()]; + const byte* src = static_cast<const byte*>(buf); + byte* dst = tmpbuf; + GLuint count = h; + do { + memcpy(dst, src, w * bytesPerPixel()); + dst += w * bytesPerPixel(); + src += pitch; + } while (--count); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, + glFormat(), glType(), tmpbuf); +#else + // This version avoids the intermediate copy at the expense of + // repeat glTexSubImage2D calls. On some devices this is worse. const byte* src = static_cast<const byte*>(buf); do { glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, @@ -193,16 +213,15 @@ void GLESTexture::updateBuffer(GLuint x, GLuint y, GLuint w, GLuint h, ++y; src += pitch; } while (--h); +#endif } } void GLESTexture::fillBuffer(byte x) { - byte tmpbuf[_surface.h * _surface.w * bytesPerPixel()]; - memset(tmpbuf, 0, _surface.h * _surface.w * bytesPerPixel()); - glBindTexture(GL_TEXTURE_2D, _texture_name); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _surface.w, _surface.h, - glFormat(), glType(), tmpbuf); - setDirty(); + int rowbytes = _surface.w * bytesPerPixel(); + byte tmpbuf[_surface.h * rowbytes]; + memset(tmpbuf, x, _surface.h * rowbytes); + updateBuffer(0, 0, _surface.w, _surface.h, tmpbuf, rowbytes); } void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { @@ -215,6 +234,7 @@ void GLESTexture::drawTexture(GLshort x, GLshort y, GLshort w, GLshort h) { //glTexEnvx(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); const GLint crop[4] = {0, _surface.h, _surface.w, -_surface.h}; glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_CROP_RECT_OES, crop); + glColor4ub(0xff, 0xff, 0xff, 0xff); // Android GLES bug? glDrawTexiOES(x, y, 0, w, h); } else #endif |