aboutsummaryrefslogtreecommitdiff
path: root/backends/platform/android
diff options
context:
space:
mode:
Diffstat (limited to 'backends/platform/android')
-rw-r--r--backends/platform/android/README.build3
-rw-r--r--backends/platform/android/android.cpp10
-rw-r--r--backends/platform/android/org/inodes/gus/scummvm/ScummVM.java123
-rw-r--r--backends/platform/android/video.cpp40
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