455 lines
12 KiB
Java
455 lines
12 KiB
Java
package org.residualvm.residualvm;
|
|
|
|
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 javax.microedition.khronos.opengles.GL10;
|
|
import javax.microedition.khronos.egl.EGL10;
|
|
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 java.io.File;
|
|
import java.util.LinkedHashMap;
|
|
|
|
public abstract class ResidualVM implements SurfaceHolder.Callback, Runnable {
|
|
final protected static String LOG_TAG = "ResidualVM";
|
|
final private AssetManager _asset_manager;
|
|
final private Object _sem_surface;
|
|
|
|
private EGL10 _egl;
|
|
private EGLDisplay _egl_display = EGL10.EGL_NO_DISPLAY;
|
|
private EGLConfig _egl_config;
|
|
private EGLContext _egl_context = EGL10.EGL_NO_CONTEXT;
|
|
private EGLSurface _egl_surface = 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,
|
|
EGL10 egl, EGLDisplay egl_display,
|
|
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);
|
|
|
|
// pause the engine and all native threads
|
|
final public native void setPause(boolean pause);
|
|
final public native void enableZoning(boolean enable);
|
|
// Feed an event to ResidualVM. Safe to call from other threads.
|
|
final public native void pushEvent(int type, int arg1, int arg2, int arg3,
|
|
int arg4, int arg5, int arg6);
|
|
|
|
// Callbacks from C++ peer instance
|
|
abstract protected void getDPI(float[] values);
|
|
abstract protected void displayMessageOnOSD(String msg);
|
|
abstract protected void setWindowCaption(String caption);
|
|
abstract protected void showVirtualKeyboard(boolean enable);
|
|
abstract protected String[] getSysArchives();
|
|
|
|
public ResidualVM(AssetManager asset_manager, SurfaceHolder holder) {
|
|
_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) {
|
|
// the orientation may reset on standby mode and the theme manager
|
|
// could assert when using a portrait resolution. so lets not do that.
|
|
if (height > width) {
|
|
Log.d(LOG_TAG, String.format("Ignoring surfaceChanged: %dx%d (%d)",
|
|
width, height, format));
|
|
return;
|
|
}
|
|
|
|
Log.d(LOG_TAG, String.format("surfaceChanged: %dx%d (%d)",
|
|
width, height, format));
|
|
|
|
// store values for the native code
|
|
// make sure to do it before notifying the lock
|
|
// as it leads to a race condition otherwise
|
|
setSurface(width, height);
|
|
|
|
synchronized(_sem_surface) {
|
|
_surface_holder = holder;
|
|
_sem_surface.notifyAll();
|
|
}
|
|
}
|
|
|
|
// 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) {
|
|
_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 ResidualVM thread", e);
|
|
}
|
|
|
|
create(_asset_manager, _egl, _egl_display,
|
|
_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);
|
|
}
|
|
|
|
final private void initEGL() throws Exception {
|
|
_egl = (EGL10)EGLContext.getEGL();
|
|
_egl_display = _egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
|
|
|
|
int[] version = new int[2];
|
|
_egl.eglInitialize(_egl_display, version);
|
|
|
|
int[] num_config = new int[1];
|
|
int[] config_attrib_list = {
|
|
EGL10.EGL_RENDERABLE_TYPE, 4, // ES2
|
|
EGL10.EGL_RED_SIZE, 5,
|
|
EGL10.EGL_GREEN_SIZE, 6,
|
|
EGL10.EGL_BLUE_SIZE, 5,
|
|
EGL10.EGL_NONE
|
|
};
|
|
_egl.eglChooseConfig(_egl_display, config_attrib_list, null, 0, num_config);
|
|
|
|
final int numConfigs = num_config[0];
|
|
|
|
if (numConfigs <= 0)
|
|
throw new IllegalArgumentException("No EGL configs");
|
|
|
|
EGLConfig[] configs = new EGLConfig[numConfigs];
|
|
_egl.eglChooseConfig(_egl_display, config_attrib_list, configs, numConfigs, num_config);
|
|
|
|
_egl_config = chooseEglConfig(configs);
|
|
int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
|
|
int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2,
|
|
EGL10.EGL_NONE };
|
|
_egl_context = _egl.eglCreateContext(_egl_display, _egl_config,
|
|
EGL10.EGL_NO_CONTEXT, attrib_list);
|
|
|
|
if (_egl_context == EGL10.EGL_NO_CONTEXT)
|
|
throw new Exception(String.format("Failed to create context: 0x%x",
|
|
_egl.eglGetError()));
|
|
}
|
|
|
|
// Callback from C++ peer instance
|
|
final protected EGLSurface initSurface() throws Exception {
|
|
_egl_surface = _egl.eglCreateWindowSurface(_egl_display, _egl_config,
|
|
_surface_holder, null);
|
|
|
|
if (_egl_surface == EGL10.EGL_NO_SURFACE)
|
|
throw new Exception(String.format(
|
|
"eglCreateWindowSurface failed: 0x%x", _egl.eglGetError()));
|
|
|
|
_egl.eglMakeCurrent(_egl_display, _egl_surface, _egl_surface,
|
|
_egl_context);
|
|
|
|
GL10 gl = (GL10)_egl_context.getGL();
|
|
|
|
Log.i(LOG_TAG, String.format("Using EGL %s (%s); GL %s/%s (%s)",
|
|
_egl.eglQueryString(_egl_display, EGL10.EGL_VERSION),
|
|
_egl.eglQueryString(_egl_display, EGL10.EGL_VENDOR),
|
|
gl.glGetString(GL10.GL_VERSION),
|
|
gl.glGetString(GL10.GL_RENDERER),
|
|
gl.glGetString(GL10.GL_VENDOR)));
|
|
|
|
return _egl_surface;
|
|
}
|
|
|
|
// Callback from C++ peer instance
|
|
final protected void deinitSurface() {
|
|
if (_egl_display != EGL10.EGL_NO_DISPLAY) {
|
|
_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
|
|
|
if (_egl_surface != EGL10.EGL_NO_SURFACE)
|
|
_egl.eglDestroySurface(_egl_display, _egl_surface);
|
|
}
|
|
|
|
_egl_surface = EGL10.EGL_NO_SURFACE;
|
|
}
|
|
|
|
final private void deinitEGL() {
|
|
if (_egl_display != EGL10.EGL_NO_DISPLAY) {
|
|
_egl.eglMakeCurrent(_egl_display, EGL10.EGL_NO_SURFACE,
|
|
EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT);
|
|
|
|
if (_egl_surface != EGL10.EGL_NO_SURFACE)
|
|
_egl.eglDestroySurface(_egl_display, _egl_surface);
|
|
|
|
if (_egl_context != EGL10.EGL_NO_CONTEXT)
|
|
_egl.eglDestroyContext(_egl_display, _egl_context);
|
|
|
|
_egl.eglTerminate(_egl_display);
|
|
}
|
|
|
|
_egl_surface = EGL10.EGL_NO_SURFACE;
|
|
_egl_context = EGL10.EGL_NO_CONTEXT;
|
|
_egl_config = null;
|
|
_egl_display = EGL10.EGL_NO_DISPLAY;
|
|
_egl = null;
|
|
}
|
|
|
|
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);
|
|
|
|
// ~50ms
|
|
int buffer_size_want = (_sample_rate * 2 * 2 / 20) & ~1023;
|
|
|
|
if (_buffer_size < buffer_size_want) {
|
|
Log.w(LOG_TAG, String.format(
|
|
"adjusting audio buffer size (was: %d)", _buffer_size));
|
|
|
|
_buffer_size = buffer_size_want;
|
|
}
|
|
|
|
Log.i(LOG_TAG, String.format("Using %d bytes buffer for %dHz audio",
|
|
_buffer_size, _sample_rate));
|
|
|
|
_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 initializing AudioTrack: %d",
|
|
_audio_track.getState()));
|
|
}
|
|
|
|
final private void deinitAudio() {
|
|
if (_audio_track != null)
|
|
_audio_track.stop();
|
|
|
|
_audio_track = null;
|
|
_buffer_size = 0;
|
|
_sample_rate = 0;
|
|
}
|
|
|
|
private static final int[] s_eglAttribs = {
|
|
EGL10.EGL_CONFIG_ID,
|
|
EGL10.EGL_BUFFER_SIZE,
|
|
EGL10.EGL_RED_SIZE,
|
|
EGL10.EGL_GREEN_SIZE,
|
|
EGL10.EGL_BLUE_SIZE,
|
|
EGL10.EGL_ALPHA_SIZE,
|
|
EGL10.EGL_CONFIG_CAVEAT,
|
|
EGL10.EGL_DEPTH_SIZE,
|
|
EGL10.EGL_LEVEL,
|
|
EGL10.EGL_MAX_PBUFFER_WIDTH,
|
|
EGL10.EGL_MAX_PBUFFER_HEIGHT,
|
|
EGL10.EGL_MAX_PBUFFER_PIXELS,
|
|
EGL10.EGL_NATIVE_RENDERABLE,
|
|
EGL10.EGL_NATIVE_VISUAL_ID,
|
|
EGL10.EGL_NATIVE_VISUAL_TYPE,
|
|
EGL10.EGL_SAMPLE_BUFFERS,
|
|
EGL10.EGL_SAMPLES,
|
|
EGL10.EGL_STENCIL_SIZE,
|
|
EGL10.EGL_SURFACE_TYPE,
|
|
EGL10.EGL_TRANSPARENT_TYPE,
|
|
EGL10.EGL_TRANSPARENT_RED_VALUE,
|
|
EGL10.EGL_TRANSPARENT_GREEN_VALUE,
|
|
EGL10.EGL_TRANSPARENT_BLUE_VALUE
|
|
};
|
|
|
|
final private class EglAttribs extends LinkedHashMap<Integer, Integer> {
|
|
public EglAttribs(EGLConfig config) {
|
|
super(s_eglAttribs.length);
|
|
|
|
int[] value = new int[1];
|
|
|
|
for (int i : s_eglAttribs) {
|
|
_egl.eglGetConfigAttrib(_egl_display, config, i, value);
|
|
|
|
put(i, value[0]);
|
|
}
|
|
}
|
|
|
|
private int weightBits(int attr, int size) {
|
|
final int value = get(attr);
|
|
|
|
int score = 0;
|
|
|
|
if (value == size || (size > 0 && value > size))
|
|
score += 10;
|
|
|
|
// penalize for wasted bits
|
|
score -= value - size;
|
|
|
|
return score;
|
|
}
|
|
|
|
public int weight() {
|
|
int score = 10000;
|
|
|
|
if (get(EGL10.EGL_CONFIG_CAVEAT) != EGL10.EGL_NONE)
|
|
score -= 1000;
|
|
|
|
// less MSAA is better
|
|
score -= get(EGL10.EGL_SAMPLES) * 100;
|
|
|
|
// Must be at least 565, but then smaller is better
|
|
score += weightBits(EGL10.EGL_RED_SIZE, 5);
|
|
score += weightBits(EGL10.EGL_GREEN_SIZE, 6);
|
|
score += weightBits(EGL10.EGL_BLUE_SIZE, 5);
|
|
score += weightBits(EGL10.EGL_ALPHA_SIZE, 0);
|
|
score += weightBits(EGL10.EGL_DEPTH_SIZE, 0);
|
|
score += weightBits(EGL10.EGL_STENCIL_SIZE, 0);
|
|
|
|
return score;
|
|
}
|
|
|
|
public String toString() {
|
|
String s;
|
|
|
|
if (get(EGL10.EGL_ALPHA_SIZE) > 0)
|
|
s = String.format("[%d] RGBA%d%d%d%d",
|
|
get(EGL10.EGL_CONFIG_ID),
|
|
get(EGL10.EGL_RED_SIZE),
|
|
get(EGL10.EGL_GREEN_SIZE),
|
|
get(EGL10.EGL_BLUE_SIZE),
|
|
get(EGL10.EGL_ALPHA_SIZE));
|
|
else
|
|
s = String.format("[%d] RGB%d%d%d",
|
|
get(EGL10.EGL_CONFIG_ID),
|
|
get(EGL10.EGL_RED_SIZE),
|
|
get(EGL10.EGL_GREEN_SIZE),
|
|
get(EGL10.EGL_BLUE_SIZE));
|
|
|
|
if (get(EGL10.EGL_DEPTH_SIZE) > 0)
|
|
s += String.format(" D%d", get(EGL10.EGL_DEPTH_SIZE));
|
|
|
|
if (get(EGL10.EGL_STENCIL_SIZE) > 0)
|
|
s += String.format(" S%d", get(EGL10.EGL_STENCIL_SIZE));
|
|
|
|
if (get(EGL10.EGL_SAMPLES) > 0)
|
|
s += String.format(" MSAAx%d", get(EGL10.EGL_SAMPLES));
|
|
|
|
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) > 0)
|
|
s += " W";
|
|
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PBUFFER_BIT) > 0)
|
|
s += " P";
|
|
if ((get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_PIXMAP_BIT) > 0)
|
|
s += " X";
|
|
|
|
switch (get(EGL10.EGL_CONFIG_CAVEAT)) {
|
|
case EGL10.EGL_NONE:
|
|
break;
|
|
|
|
case EGL10.EGL_SLOW_CONFIG:
|
|
s += " SLOW";
|
|
break;
|
|
|
|
case EGL10.EGL_NON_CONFORMANT_CONFIG:
|
|
s += " NON_CONFORMANT";
|
|
|
|
default:
|
|
s += String.format(" unknown CAVEAT 0x%x",
|
|
get(EGL10.EGL_CONFIG_CAVEAT));
|
|
}
|
|
|
|
return s;
|
|
}
|
|
};
|
|
|
|
final private EGLConfig chooseEglConfig(EGLConfig[] configs) {
|
|
EGLConfig res = configs[0];
|
|
int bestScore = -1;
|
|
|
|
Log.d(LOG_TAG, "EGL configs:");
|
|
|
|
for (EGLConfig config : configs) {
|
|
EglAttribs attr = new EglAttribs(config);
|
|
|
|
// must have
|
|
if ((attr.get(EGL10.EGL_SURFACE_TYPE) & EGL10.EGL_WINDOW_BIT) == 0)
|
|
continue;
|
|
|
|
int score = attr.weight();
|
|
|
|
Log.d(LOG_TAG, String.format("%s (%d)", attr.toString(), score));
|
|
|
|
if (score > bestScore) {
|
|
res = config;
|
|
bestScore = score;
|
|
}
|
|
}
|
|
|
|
if (bestScore < 0)
|
|
Log.e(LOG_TAG,
|
|
"Unable to find an acceptable EGL config, expect badness.");
|
|
|
|
Log.d(LOG_TAG, String.format("Chosen EGL config: %s",
|
|
new EglAttribs(res).toString()));
|
|
|
|
return res;
|
|
}
|
|
|
|
static {
|
|
// For grabbing with gdb...
|
|
final boolean sleep_for_debugger = false;
|
|
if (sleep_for_debugger) {
|
|
try {
|
|
Thread.sleep(20 * 1000);
|
|
} catch (InterruptedException e) {
|
|
}
|
|
}
|
|
|
|
System.loadLibrary("residualvm");
|
|
}
|
|
}
|