Use EGL from native code to initialize and run OpenGL on Android.

Should be more robust, allows initializing desktop GL if available, and lets us take control of the render loop.
This commit is contained in:
Henrik Rydgard 2015-12-13 22:25:58 +01:00
parent c2eb6a2fcb
commit 04f8bffa29
16 changed files with 379 additions and 576 deletions

View File

@ -44,6 +44,9 @@ void cInterfaceEGL::DetectMode()
EGL_RED_SIZE, 8, EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_STENCIL_SIZE, 8,
EGL_RENDERABLE_TYPE, renderable_type, EGL_RENDERABLE_TYPE, renderable_type,
EGL_NONE EGL_NONE
}; };
@ -95,7 +98,6 @@ void cInterfaceEGL::DetectMode()
} }
// Create rendering window. // Create rendering window.
// Call browser: Core.cpp:EmuThread() > main.cpp:Video_Initialize()
bool cInterfaceEGL::Create(void *window_handle, bool core) bool cInterfaceEGL::Create(void *window_handle, bool core)
{ {
const char *s; const char *s;
@ -128,6 +130,9 @@ bool cInterfaceEGL::Create(void *window_handle, bool core)
EGL_RED_SIZE, 8, EGL_RED_SIZE, 8,
EGL_GREEN_SIZE, 8, EGL_GREEN_SIZE, 8,
EGL_BLUE_SIZE, 8, EGL_BLUE_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_STENCIL_SIZE, 8,
EGL_NONE }; EGL_NONE };
EGLint ctx_attribs[] = { EGLint ctx_attribs[] = {
@ -154,8 +159,7 @@ bool cInterfaceEGL::Create(void *window_handle, bool core)
break; break;
} }
if (!eglChooseConfig( egl_dpy, attribs, &config, 1, &num_configs)) if (!eglChooseConfig( egl_dpy, attribs, &config, 1, &num_configs)) {
{
INFO_LOG(G3D, "Error: couldn't get an EGL visual config\n"); INFO_LOG(G3D, "Error: couldn't get an EGL visual config\n");
exit(1); exit(1);
} }
@ -181,15 +185,13 @@ bool cInterfaceEGL::Create(void *window_handle, bool core)
INFO_LOG(G3D, "EGL_CLIENT_APIS = %s\n", s); INFO_LOG(G3D, "EGL_CLIENT_APIS = %s\n", s);
egl_ctx = eglCreateContext(egl_dpy, config, EGL_NO_CONTEXT, ctx_attribs ); egl_ctx = eglCreateContext(egl_dpy, config, EGL_NO_CONTEXT, ctx_attribs );
if (!egl_ctx) if (!egl_ctx) {
{
INFO_LOG(G3D, "Error: eglCreateContext failed\n"); INFO_LOG(G3D, "Error: eglCreateContext failed\n");
exit(1); exit(1);
} }
egl_surf = eglCreateWindowSurface(egl_dpy, config, native_window, nullptr); egl_surf = eglCreateWindowSurface(egl_dpy, config, native_window, nullptr);
if (!egl_surf) if (!egl_surf) {
{
INFO_LOG(G3D, "Error: eglCreateWindowSurface failed\n"); INFO_LOG(G3D, "Error: eglCreateWindowSurface failed\n");
exit(1); exit(1);
} }
@ -207,7 +209,6 @@ bool cInterfaceEGL::ClearCurrent()
return eglMakeCurrent(egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); return eglMakeCurrent(egl_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
} }
// Close backend
void cInterfaceEGL::Shutdown() void cInterfaceEGL::Shutdown()
{ {
ShutdownPlatform(); ShutdownPlatform();

View File

@ -20,6 +20,8 @@ protected:
virtual EGLDisplay OpenDisplay() = 0; virtual EGLDisplay OpenDisplay() = 0;
virtual EGLNativeWindowType InitializePlatform(EGLNativeWindowType host_window, EGLConfig config) = 0; virtual EGLNativeWindowType InitializePlatform(EGLNativeWindowType host_window, EGLConfig config) = 0;
virtual void ShutdownPlatform() = 0; virtual void ShutdownPlatform() = 0;
virtual void SetInternalResolution(int internalWidth, int internalHeight) {}
public: public:
void SwapInterval(int Interval); void SwapInterval(int Interval);
void Swap(); void Swap();

View File

@ -11,9 +11,9 @@ EGLDisplay cInterfaceEGLAndroid::OpenDisplay()
EGLNativeWindowType cInterfaceEGLAndroid::InitializePlatform(EGLNativeWindowType host_window, EGLConfig config) EGLNativeWindowType cInterfaceEGLAndroid::InitializePlatform(EGLNativeWindowType host_window, EGLConfig config)
{ {
EGLint format; EGLint format = 0;
eglGetConfigAttrib(egl_dpy, config, EGL_NATIVE_VISUAL_ID, &format); eglGetConfigAttrib(egl_dpy, config, EGL_NATIVE_VISUAL_ID, &format);
ANativeWindow_setBuffersGeometry(host_window, 0, 0, format); ANativeWindow_setBuffersGeometry(host_window, internalWidth_, internalHeight_, format);
const int width = ANativeWindow_getWidth(host_window); const int width = ANativeWindow_getWidth(host_window);
const int height = ANativeWindow_getHeight(host_window); const int height = ANativeWindow_getHeight(host_window);

View File

@ -6,10 +6,19 @@
#include "Common/GL/GLInterface/EGL.h" #include "Common/GL/GLInterface/EGL.h"
class cInterfaceEGLAndroid : public cInterfaceEGL class cInterfaceEGLAndroid : public cInterfaceEGL {
{ public:
cInterfaceEGLAndroid() : internalWidth_(0), internalHeight_(0) {}
protected: protected:
EGLDisplay OpenDisplay() override; EGLDisplay OpenDisplay() override;
EGLNativeWindowType InitializePlatform(EGLNativeWindowType host_window, EGLConfig config) override; EGLNativeWindowType InitializePlatform(EGLNativeWindowType host_window, EGLConfig config) override;
void ShutdownPlatform() override; void ShutdownPlatform() override;
void SetInternalResolution(int internalWidth, int internalHeight) override {
internalWidth_ = internalWidth;
internalHeight_ = internalHeight;
}
private:
int internalWidth_;
int internalHeight_;
}; };

View File

@ -43,8 +43,5 @@ public:
virtual bool PeekMessages() { return false; } virtual bool PeekMessages() { return false; }
}; };
extern cInterfaceBase *GLInterface;
// This function has to be defined along the Host_ functions from Core/Host.h.
// Current canonical implementation: DolphinWX/GLInterface/GLInterface.cpp.
cInterfaceBase* HostGL_CreateGLInterface(); cInterfaceBase* HostGL_CreateGLInterface();

View File

@ -637,6 +637,7 @@ UI::EventReturn GameSettingsScreen::OnHardwareTransform(UI::EventParams &e) {
} }
UI::EventReturn GameSettingsScreen::OnScreenRotation(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnScreenRotation(UI::EventParams &e) {
ILOG("New display rotation: %d", g_Config.iScreenRotation);
System_SendMessage("rotate", ""); System_SendMessage("rotate", "");
return UI::EVENT_DONE; return UI::EVENT_DONE;
} }

View File

@ -210,7 +210,8 @@ void QtHost::ShutdownSound() { }
std::string NativeQueryConfig(std::string query) { std::string NativeQueryConfig(std::string query) {
char temp[128]; char temp[128];
if (query == "screenRotation") { if (query == "screenRotation") {
sprintf(temp, "%i", g_Config.iScreenRotation); ILOG("g_Config.screenRotation = %d", g_Config.iScreenRotation);
snprintf(temp, sizeof(temp), "%d", g_Config.iScreenRotation);
return std::string(temp); return std::string(temp);
} else if (query == "immersiveMode") { } else if (query == "immersiveMode") {
return std::string(g_Config.bImmersiveMode ? "1" : "0"); return std::string(g_Config.bImmersiveMode ? "1" : "0");
@ -224,7 +225,7 @@ std::string NativeQueryConfig(std::string query) {
} }
int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 1; int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 1;
sprintf(temp, "%i", std::min(scale, max_res)); snprintf(temp, sizeof(temp), "%d", std::min(scale, max_res));
return std::string(temp); return std::string(temp);
} else if (query == "force44khz") { } else if (query == "force44khz") {
return std::string("0"); return std::string("0");

View File

@ -99,8 +99,14 @@ ARCH_FILES := \
ArmEmitterTest.cpp ArmEmitterTest.cpp
endif endif
EGL_FILES := \
$(SRC)/Common/GL/GLInterface/EGL.cpp \
$(SRC)/Common/GL/GLInterface/EGLAndroid.cpp \
$(SRC)/Common/GL/GLInterface/GLInterface.cpp
EXEC_AND_LIB_FILES := \ EXEC_AND_LIB_FILES := \
$(ARCH_FILES) \ $(ARCH_FILES) \
$(EGL_FILES) \
TestRunner.cpp \ TestRunner.cpp \
$(SRC)/Core/MIPS/MIPS.cpp.arm \ $(SRC)/Core/MIPS/MIPS.cpp.arm \
$(SRC)/Core/MIPS/MIPSAnalyst.cpp \ $(SRC)/Core/MIPS/MIPSAnalyst.cpp \

View File

@ -15,7 +15,7 @@ LOCAL_C_INCLUDES := \
$(LOCAL_PATH) $(LOCAL_PATH)
LOCAL_STATIC_LIBRARIES := native libzip LOCAL_STATIC_LIBRARIES := native libzip
LOCAL_LDLIBS := -lz -lGLESv2 -lOpenSLES -lEGL -ldl -llog LOCAL_LDLIBS := -lz -landroid -lGLESv2 -lOpenSLES -lEGL -ldl -llog
# ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) # ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a) ifeq ($(findstring armeabi-v7a,$(TARGET_ARCH_ABI)),armeabi-v7a)

View File

@ -6,10 +6,12 @@
#include <jni.h> #include <jni.h>
#include <android/log.h> #include <android/log.h>
#include <android/native_window_jni.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
#include <GLES2/gl2ext.h> #include <GLES2/gl2ext.h>
#include <EGL/EGL.h>
#include <queue> #include <queue>
#include "base/basictypes.h" #include "base/basictypes.h"
@ -27,6 +29,8 @@
#include "android/jni/native_audio.h" #include "android/jni/native_audio.h"
#include "gfx/gl_common.h" #include "gfx/gl_common.h"
#include "Common/GL/GLInterfaceBase.h"
#include "app-android.h" #include "app-android.h"
static JNIEnv *jniEnvUI; static JNIEnv *jniEnvUI;
@ -66,6 +70,8 @@ static int display_yres;
static jmethodID postCommand; static jmethodID postCommand;
static jobject nativeActivity; static jobject nativeActivity;
static volatile bool exitRenderLoop;
bool renderLoopRunning;
// Android implementation of callbacks to the Java part of the app // Android implementation of callbacks to the Java part of the app
void SystemToast(const char *text) { void SystemToast(const char *text) {
@ -313,18 +319,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
ILOG("NativeApp.shutdown() -- end"); ILOG("NativeApp.shutdown() -- end");
} }
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { extern "C" void Java_org_ppsspp_ppsspp_NativeApp_displayResize(JNIEnv *, jclass, jint w, jint h, jint dpi, jfloat refreshRate) {
ILOG("NativeApp.displayInit()");
if (!renderer_inited) {
NativeInitGraphics();
renderer_inited = true;
} else {
NativeDeviceLost(); // ???
ILOG("displayInit: NativeDeviceLost completed.");
}
}
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayResize(JNIEnv *, jobject clazz, jint w, jint h, jint dpi, jfloat refreshRate) {
ILOG("NativeApp.displayResize(%i x %i, dpi=%i, refresh=%0.2f)", w, h, dpi, refreshRate); ILOG("NativeApp.displayResize(%i x %i, dpi=%i, refresh=%0.2f)", w, h, dpi, refreshRate);
g_dpi = dpi; g_dpi = dpi;
@ -341,69 +336,8 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayResize(JNIEnv *, jo
NativeResized(); NativeResized();
} }
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
static bool hasSetThreadName = false;
if (!hasSetThreadName) {
hasSetThreadName = true;
setCurrentThreadName("AndroidRender");
}
if (renderer_inited) {
// TODO: Look into if these locks are a perf loss
{
lock_guard guard(input_state.lock);
input_state.pad_lstick_x = left_joystick_x_async;
input_state.pad_lstick_y = left_joystick_y_async;
input_state.pad_rstick_x = right_joystick_x_async;
input_state.pad_rstick_y = right_joystick_y_async;
UpdateInputState(&input_state);
}
NativeUpdate(input_state);
{
lock_guard guard(input_state.lock);
EndInputState(&input_state);
}
NativeRender();
time_update();
} else {
ELOG("BAD: Ended up in nativeRender even though app has quit.%s", "");
// Shouldn't really get here. Let's draw magenta.
glDepthMask(GL_TRUE);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glClearColor(1.0, 0.0, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
lock_guard guard(frameCommandLock);
if (!nativeActivity) {
while (!frameCommands.empty())
frameCommands.pop();
return;
}
while (!frameCommands.empty()) {
FrameCommand frameCmd;
frameCmd = frameCommands.front();
frameCommands.pop();
DLOG("frameCommand %s %s", frameCmd.command.c_str(), frameCmd.params.c_str());
jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
jstring param = env->NewStringUTF(frameCmd.params.c_str());
env->CallVoidMethod(nativeActivity, postCommand, cmd, param);
env->DeleteLocalRef(cmd);
env->DeleteLocalRef(param);
}
}
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayShutdown(JNIEnv *env, jobject obj) { extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayShutdown(JNIEnv *env, jobject obj) {
if (renderer_inited) { if (renderer_inited) {
NativeDeviceLost();
ILOG("NativeDeviceLost completed.");
NativeShutdownGraphics();
renderer_inited = false; renderer_inited = false;
NativeMessageReceived("recreateviews", ""); NativeMessageReceived("recreateviews", "");
} }
@ -538,7 +472,7 @@ extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEn
return retvalX || retvalY || retvalZ; return retvalX || retvalY || retvalZ;
} }
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env, jclass, jstring message, jstring param) { extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env, jclass, jstring message, jstring param) {
std::string msg = GetJavaString(env, message); std::string msg = GetJavaString(env, message);
std::string prm = GetJavaString(env, param); std::string prm = GetJavaString(env, param);
@ -547,3 +481,99 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env, jclass
} }
NativeMessageReceived(msg.c_str(), prm.c_str()); NativeMessageReceived(msg.c_str(), prm.c_str());
} }
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv *env, jobject obj) {
if (!renderLoopRunning) {
ELOG("Render loop already exited");
return;
}
exitRenderLoop = true;
while (renderLoopRunning) {
sleep_ms(10);
}
}
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) {
ANativeWindow *wnd = ANativeWindow_fromSurface(env, _surf);
WLOG("runEGLRenderLoop");
if (wnd == nullptr) {
ELOG("Error: Surface is null.");
return;
}
cInterfaceBase *gl = HostGL_CreateGLInterface();
if (!gl) {
ELOG("ERROR: Failed to create GL interface");
return;
}
gl->SetMode(MODE_DETECT);
gl->Create(wnd);
gl->MakeCurrent();
if (!renderer_inited) {
NativeInitGraphics();
renderer_inited = true;
}
exitRenderLoop = false;
renderLoopRunning = true;
while (!exitRenderLoop) {
static bool hasSetThreadName = false;
if (!hasSetThreadName) {
hasSetThreadName = true;
setCurrentThreadName("AndroidRender");
}
// TODO: Look into if these locks are a perf loss
{
lock_guard guard(input_state.lock);
input_state.pad_lstick_x = left_joystick_x_async;
input_state.pad_lstick_y = left_joystick_y_async;
input_state.pad_rstick_x = right_joystick_x_async;
input_state.pad_rstick_y = right_joystick_y_async;
UpdateInputState(&input_state);
}
NativeUpdate(input_state);
{
lock_guard guard(input_state.lock);
EndInputState(&input_state);
}
NativeRender();
time_update();
gl->Swap();
lock_guard guard(frameCommandLock);
while (!frameCommands.empty()) {
FrameCommand frameCmd;
frameCmd = frameCommands.front();
frameCommands.pop();
WLOG("frameCommand! '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
jstring param = env->NewStringUTF(frameCmd.params.c_str());
env->CallVoidMethod(nativeActivity, postCommand, cmd, param);
env->DeleteLocalRef(cmd);
env->DeleteLocalRef(param);
}
}
NativeDeviceLost();
ILOG("NativeDeviceLost completed.");
NativeShutdownGraphics();
renderer_inited = false;
delete gl;
ANativeWindow_release(wnd);
renderLoopRunning = false;
WLOG("Render loop exited;");
}

View File

@ -37,6 +37,8 @@ import android.view.InputEvent;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.HapticFeedbackConstants; import android.view.HapticFeedbackConstants;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.View; import android.view.View;
import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.View.OnSystemUiVisibilityChangeListener;
import android.view.Window; import android.view.Window;
@ -46,57 +48,54 @@ import android.widget.EditText;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.Toast; import android.widget.Toast;
public class NativeActivity extends Activity { public class NativeActivity extends Activity implements SurfaceHolder.Callback {
// Remember to loadLibrary your JNI .so in a static {} block // Remember to loadLibrary your JNI .so in a static {} block
// Adjust these as necessary // Adjust these as necessary
private static String TAG = "NativeActivity"; private static String TAG = "NativeActivity";
// Allows us to skip a lot of initialization on secondary calls to onCreate. // Allows us to skip a lot of initialization on secondary calls to onCreate.
private static boolean initialized = false; private static boolean initialized = false;
// Graphics and audio interfaces // Graphics and audio interfaces
private NativeGLView mGLSurfaceView; private NativeSurfaceView mSurfaceView;
protected NativeRenderer nativeRenderer; private Surface mSurface;
private Thread mRenderLoopThread;
private String shortcutParam = ""; private String shortcutParam = "";
public static String runCommand; public static String runCommand;
public static String commandParameter; public static String commandParameter;
public static String installID; public static String installID;
// Remember settings for best audio latency // Remember settings for best audio latency
private int optimalFramesPerBuffer; private int optimalFramesPerBuffer;
private int optimalSampleRate; private int optimalSampleRate;
// audioFocusChangeListener to listen to changes in audio state // audioFocusChangeListener to listen to changes in audio state
private AudioFocusChangeListener audioFocusChangeListener; private AudioFocusChangeListener audioFocusChangeListener;
private AudioManager audioManager; private AudioManager audioManager;
private Vibrator vibrator; private Vibrator vibrator;
private boolean isXperiaPlay; private boolean isXperiaPlay;
// Allow for multiple connected gamepads but just consider them the same for now. // Allow for multiple connected gamepads but just consider them the same for now.
// Actually this is not entirely true, see the code. // Actually this is not entirely true, see the code.
InputDeviceState inputPlayerA; InputDeviceState inputPlayerA;
InputDeviceState inputPlayerB; InputDeviceState inputPlayerB;
InputDeviceState inputPlayerC; InputDeviceState inputPlayerC;
String inputPlayerADesc; String inputPlayerADesc;
// Functions for the app activity to override to change behaviour. // Functions for the app activity to override to change behaviour.
public native void registerCallbacks(); public native void registerCallbacks();
public native void unregisterCallbacks(); public native void unregisterCallbacks();
public boolean useLowProfileButtons() { public boolean useLowProfileButtons() {
return true; return true;
} }
NativeRenderer getRenderer() {
return nativeRenderer;
}
@TargetApi(17) @TargetApi(17)
private void detectOptimalAudioSettings() { private void detectOptimalAudioSettings() {
try { try {
@ -110,8 +109,8 @@ public class NativeActivity extends Activity {
// Ignore, if we can't parse it it's bogus and zero is a fine value (means we couldn't detect it). // Ignore, if we can't parse it it's bogus and zero is a fine value (means we couldn't detect it).
} }
} }
String getApplicationLibraryDir(ApplicationInfo application) { String getApplicationLibraryDir(ApplicationInfo application) {
String libdir = null; String libdir = null;
try { try {
// Starting from Android 2.3, nativeLibraryDir is available: // Starting from Android 2.3, nativeLibraryDir is available:
@ -159,28 +158,28 @@ public class NativeActivity extends Activity {
size.y = d.getHeight(); size.y = d.getHeight();
} }
} }
public void setShortcutParam(String shortcutParam) { public void setShortcutParam(String shortcutParam) {
this.shortcutParam = ((shortcutParam == null) ? "" : shortcutParam); this.shortcutParam = ((shortcutParam == null) ? "" : shortcutParam);
} }
public void Initialize() { public void Initialize() {
// Initialize audio classes. Do this here since detectOptimalAudioSettings() // Initialize audio classes. Do this here since detectOptimalAudioSettings()
// needs audioManager // needs audioManager
this.audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE); this.audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
this.audioFocusChangeListener = new AudioFocusChangeListener(); this.audioFocusChangeListener = new AudioFocusChangeListener();
if (Build.VERSION.SDK_INT >= 17) { if (Build.VERSION.SDK_INT >= 17) {
// Get the optimal buffer sz // Get the optimal buffer sz
detectOptimalAudioSettings(); detectOptimalAudioSettings();
} }
// isLandscape is used to trigger GetAppInfo currently, we // isLandscape is used to trigger GetAppInfo currently, we
boolean landscape = NativeApp.isLandscape(); boolean landscape = NativeApp.isLandscape();
Log.d(TAG, "Landscape: " + landscape); Log.d(TAG, "Landscape: " + landscape);
// Get system information // Get system information
ApplicationInfo appInfo = null; ApplicationInfo appInfo = null;
PackageManager packMgmr = getPackageManager(); PackageManager packMgmr = getPackageManager();
String packageName = getPackageName(); String packageName = getPackageName();
try { try {
@ -205,16 +204,16 @@ public class NativeActivity extends Activity {
} }
isXperiaPlay = IsXperiaPlay(); isXperiaPlay = IsXperiaPlay();
String libraryDir = getApplicationLibraryDir(appInfo); String libraryDir = getApplicationLibraryDir(appInfo);
File sdcard = Environment.getExternalStorageDirectory(); File sdcard = Environment.getExternalStorageDirectory();
String externalStorageDir = sdcard.getAbsolutePath(); String externalStorageDir = sdcard.getAbsolutePath();
String dataDir = this.getFilesDir().getAbsolutePath(); String dataDir = this.getFilesDir().getAbsolutePath();
String apkFilePath = appInfo.sourceDir; String apkFilePath = appInfo.sourceDir;
String model = Build.MANUFACTURER + ":" + Build.MODEL; String model = Build.MANUFACTURER + ":" + Build.MODEL;
String languageRegion = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry(); String languageRegion = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry();
Point displaySize = new Point(); Point displaySize = new Point();
GetScreenSize(displaySize); GetScreenSize(displaySize);
@ -223,24 +222,6 @@ public class NativeActivity extends Activity {
NativeApp.sendMessage("cacheDir", getCacheDir().getAbsolutePath()); NativeApp.sendMessage("cacheDir", getCacheDir().getAbsolutePath());
// OK, config should be initialized, we can query for screen rotation.
if (Build.VERSION.SDK_INT >= 9) {
updateScreenRotation();
}
// Detect OpenGL support.
// We don't currently use this detection for anything but good to have in the log.
if (!detectOpenGLES20()) {
Log.i(TAG, "OpenGL ES 2.0 NOT detected. Things will likely go badly.");
} else {
if (detectOpenGLES30()) {
Log.i(TAG, "OpenGL ES 3.0 detected.");
}
else {
Log.i(TAG, "OpenGL ES 2.0 detected.");
}
}
vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE); vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= 11) { if (Build.VERSION.SDK_INT >= 11) {
checkForVibrator(); checkForVibrator();
@ -258,6 +239,7 @@ public class NativeActivity extends Activity {
Log.e(TAG, "Invalid rotation: " + rotString); Log.e(TAG, "Invalid rotation: " + rotString);
return; return;
} }
Log.i(TAG, "Requested rotation: " + rot + " ('" + rotString + "')");
switch (rot) { switch (rot) {
case 0: case 0:
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
@ -276,7 +258,7 @@ public class NativeActivity extends Activity {
break; break;
} }
} }
private boolean useImmersive() { private boolean useImmersive() {
String immersive = NativeApp.queryConfig("immersiveMode"); String immersive = NativeApp.queryConfig("immersiveMode");
return immersive.equals("1") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; return immersive.equals("1") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
@ -298,7 +280,7 @@ public class NativeActivity extends Activity {
Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring"); Log.e(TAG, "updateSystemUiVisibility: decor view not yet created, ignoring");
} }
} }
// Need API 11 to check for existence of a vibrator? Zany. // Need API 11 to check for existence of a vibrator? Zany.
@TargetApi(11) @TargetApi(11)
public void checkForVibrator() { public void checkForVibrator() {
@ -315,9 +297,37 @@ public class NativeActivity extends Activity {
sz.y = 0; sz.y = 0;
} }
private Runnable mEmulationRunner = new Runnable()
{
@Override
public void run()
{
// Bit of a hack - loop until onSurfaceCreated succeeds.
try {
while (mSurface == null)
{
Thread.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.i(TAG, "Starting the render loop: " + mSurface);
// Start emulation using the provided Surface.
runEGLRenderLoop(mSurface);
Log.i(TAG, "Left the render loop: " + mSurface);
}
};
public native void runEGLRenderLoop(Surface surface);
// Tells the render loop thread to exit, so we can restart it.
public native void exitEGLRenderLoop();
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
registerCallbacks(); registerCallbacks();
installID = Installation.id(this); installID = Installation.id(this);
@ -325,55 +335,32 @@ public class NativeActivity extends Activity {
Initialize(); Initialize();
initialized = true; initialized = true;
} }
// OK, config should be initialized, we can query for screen rotation.
if (Build.VERSION.SDK_INT >= 9) {
updateScreenRotation();
}
// Keep the screen bright - very annoying if it goes dark when tilting away // Keep the screen bright - very annoying if it goes dark when tilting away
Window window = this.getWindow(); Window window = this.getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setVolumeControlStream(AudioManager.STREAM_MUSIC); setVolumeControlStream(AudioManager.STREAM_MUSIC);
gainAudioFocus(this.audioManager, this.audioFocusChangeListener); gainAudioFocus(this.audioManager, this.audioFocusChangeListener);
NativeApp.audioInit(); NativeApp.audioInit();
mGLSurfaceView = new NativeGLView(this); mSurfaceView = new NativeSurfaceView(this);
nativeRenderer = new NativeRenderer(this); mSurfaceView.getHolder().addCallback(this);
Point sz = new Point(); Point sz = new Point();
getDesiredBackbufferSize(sz); getDesiredBackbufferSize(sz);
if (sz.x > 0) { if (sz.x > 0) {
Log.i(TAG, "Requesting fixed size buffer: " + sz.x + "x" + sz.y); Log.i(TAG, "Requesting fixed size buffer: " + sz.x + "x" + sz.y);
// Auto-calculates new DPI and forwards to the correct call on mGLSurfaceView.getHolder() // Auto-calculates new DPI and forwards to the correct call on mGLSurfaceView.getHolder()
nativeRenderer.setFixedSize(sz.x, sz.y, mGLSurfaceView); mSurfaceView.setFixedSize(sz.x, sz.y);
} }
mGLSurfaceView.setEGLContextClientVersion(2);
setContentView(mSurfaceView);
// Setup the GLSurface and ask android for the correct
// Number of bits for r, g, b, a, depth and stencil components
// The PSP only has 16-bit Z so that should be enough.
// Might want to change this for other apps (24-bit might be useful).
// Actually, we might be able to do without both stencil and depth in
// the back buffer, but that would kill non-buffered rendering.
// It appears some gingerbread devices blow up if you use a config chooser at all ???? (Xperia Play)
//if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// On some (especially older devices), things blow up later (EGL_BAD_MATCH) if we don't set the format here,
// if we specify that we want destination alpha in the config chooser, which we do.
// http://grokbase.com/t/gg/android-developers/11bj40jm4w/fall-back
// Needed to avoid banding on Ouya?
if (Build.MANUFACTURER == "OUYA") {
mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBX_8888);
mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser());
} else {
// Many devices require that we set a config chooser, despite the documentation
// explicitly stating: "If no setEGLConfigChooser method is called, then by default the view will choose an RGB_888 surface with a depth buffer depth of at least 16 bits."
// On these devices, I get these crashes: http://stackoverflow.com/questions/14167319/android-opengl-demo-no-config-chosen
// So let's try it...
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 8);
}
mGLSurfaceView.setRenderer(nativeRenderer);
setContentView(mGLSurfaceView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
updateSystemUiVisibility(); updateSystemUiVisibility();
@ -381,8 +368,46 @@ public class NativeActivity extends Activity {
setupSystemUiCallback(); setupSystemUiCallback();
} }
} }
mRenderLoopThread = new Thread(mEmulationRunner);
mRenderLoopThread.start();
} }
@Override
public void surfaceCreated(SurfaceHolder holder)
{
Log.d(TAG, "Surface created.");
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
Log.w(TAG, "Surface changed. Resolution: " + width + "x" + height);
mSurface = holder.getSurface();
if (mRenderLoopThread == null || !mRenderLoopThread.isAlive()) {
mRenderLoopThread = new Thread(mEmulationRunner);
mRenderLoopThread.start();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
mSurface = null;
Log.w(TAG, "Surface destroyed.");
if (mRenderLoopThread != null && mRenderLoopThread.isAlive()) {
// This will wait until the thread has exited.
exitEGLRenderLoop();
try {
mRenderLoopThread.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@TargetApi(19) @TargetApi(19)
void setupSystemUiCallback() { void setupSystemUiCallback() {
getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() {
@ -394,27 +419,27 @@ public class NativeActivity extends Activity {
} }
}); });
} }
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); exitEGLRenderLoop();
super.onStop();
Log.i(TAG, "onStop - do nothing special"); Log.i(TAG, "onStop - do nothing special");
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
Log.i(TAG, "onDestroy"); Log.i(TAG, "onDestroy");
mGLSurfaceView.onDestroy(); mSurfaceView.onDestroy();
nativeRenderer.onDestroyed();
NativeApp.audioShutdown(); NativeApp.audioShutdown();
// Probably vain attempt to help the garbage collector... // Probably vain attempt to help the garbage collector...
mGLSurfaceView = null; mSurfaceView = null;
audioFocusChangeListener = null; audioFocusChangeListener = null;
audioManager = null; audioManager = null;
unregisterCallbacks(); unregisterCallbacks();
} }
private boolean detectOpenGLES20() { private boolean detectOpenGLES20() {
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ConfigurationInfo info = am.getDeviceConfigurationInfo(); ConfigurationInfo info = am.getDeviceConfigurationInfo();
@ -426,16 +451,16 @@ public class NativeActivity extends Activity {
ConfigurationInfo info = am.getDeviceConfigurationInfo(); ConfigurationInfo info = am.getDeviceConfigurationInfo();
return info.reqGlEsVersion >= 0x30000; return info.reqGlEsVersion >= 0x30000;
} }
@Override @Override
protected void onPause() { protected void onPause() {
super.onPause(); super.onPause();
Log.i(TAG, "onPause"); Log.i(TAG, "onPause");
loseAudioFocus(this.audioManager, this.audioFocusChangeListener); loseAudioFocus(this.audioManager, this.audioFocusChangeListener);
NativeApp.pause(); NativeApp.pause();
mGLSurfaceView.onPause(); mSurfaceView.onPause();
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
@ -445,30 +470,30 @@ public class NativeActivity extends Activity {
// OK, config should be initialized, we can query for screen rotation. // OK, config should be initialized, we can query for screen rotation.
if (Build.VERSION.SDK_INT >= 9) { if (Build.VERSION.SDK_INT >= 9) {
updateScreenRotation(); updateScreenRotation();
} }
Log.i(TAG, "onResume"); Log.i(TAG, "onResume");
if (mGLSurfaceView != null) { if (mSurfaceView != null) {
mGLSurfaceView.onResume(); mSurfaceView.onResume();
} else { } else {
Log.e(TAG, "mGLSurfaceView really shouldn't be null in onResume"); Log.e(TAG, "mGLSurfaceView really shouldn't be null in onResume");
} }
gainAudioFocus(this.audioManager, this.audioFocusChangeListener); gainAudioFocus(this.audioManager, this.audioFocusChangeListener);
NativeApp.resume(); NativeApp.resume();
} }
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
updateSystemUiVisibility(); updateSystemUiVisibility();
} }
Point sz = new Point(); Point sz = new Point();
getDesiredBackbufferSize(sz); getDesiredBackbufferSize(sz);
if (sz.x > 0) { if (sz.x > 0) {
mGLSurfaceView.getHolder().setFixedSize(sz.x/2, sz.y/2); mSurfaceView.getHolder().setFixedSize(sz.x/2, sz.y/2);
} }
} }
@ -480,7 +505,7 @@ public class NativeActivity extends Activity {
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
} }
} }
//keep this static so we can call this even if we don't //keep this static so we can call this even if we don't
//instantiate NativeAudioPlayer //instantiate NativeAudioPlayer
public static void loseAudioFocus(AudioManager audioManager,AudioFocusChangeListener focusChangeListener){ public static void loseAudioFocus(AudioManager audioManager,AudioFocusChangeListener focusChangeListener){
@ -488,7 +513,7 @@ public class NativeActivity extends Activity {
audioManager.abandonAudioFocus(focusChangeListener); audioManager.abandonAudioFocus(focusChangeListener);
} }
} }
// We simply grab the first input device to produce an event and ignore all others that are connected. // We simply grab the first input device to produce an event and ignore all others that are connected.
@TargetApi(Build.VERSION_CODES.GINGERBREAD) @TargetApi(Build.VERSION_CODES.GINGERBREAD)
private InputDeviceState getInputDeviceState(InputEvent event) { private InputDeviceState getInputDeviceState(InputEvent event) {
@ -579,11 +604,11 @@ public class NativeActivity extends Activity {
} }
} }
} }
// Let's go through the old path (onKeyUp, onKeyDown). // Let's go through the old path (onKeyUp, onKeyDown).
return super.dispatchKeyEvent(event); return super.dispatchKeyEvent(event);
} }
@TargetApi(16) @TargetApi(16)
static public String getInputDesc(InputDevice input) { static public String getInputDesc(InputDevice input) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
@ -648,7 +673,7 @@ public class NativeActivity extends Activity {
case KeyEvent.KEYCODE_SEARCH: case KeyEvent.KEYCODE_SEARCH:
NativeApp.keyDown(0, keyCode, repeat); NativeApp.keyDown(0, keyCode, repeat);
return true; return true;
case KeyEvent.KEYCODE_DPAD_UP: case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT: case KeyEvent.KEYCODE_DPAD_LEFT:
@ -703,12 +728,12 @@ public class NativeActivity extends Activity {
} }
} }
@TargetApi(11) @TargetApi(11)
private AlertDialog.Builder createDialogBuilderWithTheme() { private AlertDialog.Builder createDialogBuilderWithTheme() {
return new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK); return new AlertDialog.Builder(this, AlertDialog.THEME_HOLO_DARK);
} }
// The return value is sent elsewhere. TODO in java, in SendMessage in C++. // The return value is sent elsewhere. TODO in java, in SendMessage in C++.
public void inputBox(String title, String defaultText, String defaultAction) { public void inputBox(String title, String defaultText, String defaultAction) {
final FrameLayout fl = new FrameLayout(this); final FrameLayout fl = new FrameLayout(this);
@ -722,7 +747,7 @@ public class NativeActivity extends Activity {
input.setInputType(InputType.TYPE_CLASS_TEXT); input.setInputType(InputType.TYPE_CLASS_TEXT);
input.setText(defaultText); input.setText(defaultText);
input.selectAll(); input.selectAll();
AlertDialog.Builder bld = null; AlertDialog.Builder bld = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB)
bld = new AlertDialog.Builder(this); bld = new AlertDialog.Builder(this);
@ -733,22 +758,24 @@ public class NativeActivity extends Activity {
.setView(fl) .setView(fl)
.setTitle(title) .setTitle(title)
.setPositiveButton(defaultAction, new DialogInterface.OnClickListener(){ .setPositiveButton(defaultAction, new DialogInterface.OnClickListener(){
public void onClick(DialogInterface d, int which) { @Override
public void onClick(DialogInterface d, int which) {
NativeApp.sendMessage("inputbox_completed", input.getText().toString()); NativeApp.sendMessage("inputbox_completed", input.getText().toString());
d.dismiss(); d.dismiss();
} }
}) })
.setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ .setNegativeButton("Cancel", new DialogInterface.OnClickListener(){
public void onClick(DialogInterface d, int which) { @Override
public void onClick(DialogInterface d, int which) {
NativeApp.sendMessage("inputbox_failed", ""); NativeApp.sendMessage("inputbox_failed", "");
d.cancel(); d.cancel();
} }
}).create(); }).create();
dlg.setCancelable(true); dlg.setCancelable(true);
dlg.show(); dlg.show();
} }
public boolean processCommand(String command, String params) { public boolean processCommand(String command, String params) {
if (command.equals("launchBrowser")) { if (command.equals("launchBrowser")) {
try { try {
@ -825,18 +852,18 @@ public class NativeActivity extends Activity {
toast.show(); toast.show();
Log.i(TAG, params); Log.i(TAG, params);
return true; return true;
} else if (command.equals("showKeyboard") && mGLSurfaceView != null) { } else if (command.equals("showKeyboard") && mSurfaceView != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// No idea what the point of the ApplicationWindowToken is or if it // No idea what the point of the ApplicationWindowToken is or if it
// matters where we get it from... // matters where we get it from...
inputMethodManager.toggleSoftInputFromWindow( inputMethodManager.toggleSoftInputFromWindow(
mGLSurfaceView.getApplicationWindowToken(), mSurfaceView.getApplicationWindowToken(),
InputMethodManager.SHOW_FORCED, 0); InputMethodManager.SHOW_FORCED, 0);
return true; return true;
} else if (command.equals("hideKeyboard") && mGLSurfaceView != null) { } else if (command.equals("hideKeyboard") && mSurfaceView != null) {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.toggleSoftInputFromWindow( inputMethodManager.toggleSoftInputFromWindow(
mGLSurfaceView.getApplicationWindowToken(), mSurfaceView.getApplicationWindowToken(),
InputMethodManager.SHOW_FORCED, 0); InputMethodManager.SHOW_FORCED, 0);
return true; return true;
} else if (command.equals("inputbox")) { } else if (command.equals("inputbox")) {
@ -850,7 +877,7 @@ public class NativeActivity extends Activity {
Log.i(TAG, "Launching inputbox: " + title + " " + defString); Log.i(TAG, "Launching inputbox: " + title + " " + defString);
inputBox(title, defString, "OK"); inputBox(title, defString, "OK");
return true; return true;
} else if (command.equals("vibrate") && mGLSurfaceView != null) { } else if (command.equals("vibrate") && mSurfaceView != null) {
int milliseconds = -1; int milliseconds = -1;
if (params != "") { if (params != "") {
try { try {
@ -867,13 +894,13 @@ public class NativeActivity extends Activity {
// permission. // permission.
switch (milliseconds) { switch (milliseconds) {
case -1: case -1:
mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); mSurfaceView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
break; break;
case -2: case -2:
mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); mSurfaceView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
break; break;
case -3: case -3:
mGLSurfaceView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); mSurfaceView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
break; break;
default: default:
if (vibrator != null) { if (vibrator != null) {
@ -887,12 +914,13 @@ public class NativeActivity extends Activity {
} else if (command.equals("rotate")) { } else if (command.equals("rotate")) {
if (Build.VERSION.SDK_INT >= 9) { if (Build.VERSION.SDK_INT >= 9) {
updateScreenRotation(); updateScreenRotation();
} }
} else if (command.equals("immersive")) { } else if (command.equals("immersive")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
updateSystemUiVisibility(); updateSystemUiVisibility();
} }
} else if (command.equals("recreate")) { } else if (command.equals("recreate")) {
exitEGLRenderLoop();
recreate(); recreate();
} }
return false; return false;

View File

@ -1,24 +1,25 @@
package org.ppsspp.ppsspp; package org.ppsspp.ppsspp;
// Note that the display* methods are in NativeRenderer.java // Note that the display* methods are in NativeRenderer.java
public class NativeApp { public class NativeApp {
public final static int DEVICE_ID_DEFAULT = 0; public final static int DEVICE_ID_DEFAULT = 0;
public final static int DEVICE_ID_KEYBOARD = 1; public final static int DEVICE_ID_KEYBOARD = 1;
public final static int DEVICE_ID_MOUSE = 2; public final static int DEVICE_ID_MOUSE = 2;
public final static int DEVICE_ID_PAD_0 = 10; public final static int DEVICE_ID_PAD_0 = 10;
public final static int DEVICE_TYPE_MOBILE = 0; public final static int DEVICE_TYPE_MOBILE = 0;
public final static int DEVICE_TYPE_TV = 1; public final static int DEVICE_TYPE_TV = 1;
public final static int DEVICE_TYPE_DESKTOP = 2; public final static int DEVICE_TYPE_DESKTOP = 2;
public static native void init(String model, int deviceType, int xres, int yres, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String shortcutParam, String installID, int androidVersion); public static native void init(String model, int deviceType, int xres, int yres, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String shortcutParam, String installID, int androidVersion);
public static native void audioInit(); public static native void audioInit();
public static native void audioShutdown(); public static native void audioShutdown();
public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate); public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate);
public static native void displayResize(int width, int height, int scaled_dpi, float refreshRate);
public static native boolean isLandscape(); public static native boolean isLandscape();
public static native boolean isAtTopLevel(); public static native boolean isAtTopLevel();
@ -35,7 +36,7 @@ public class NativeApp {
public static native void beginJoystickEvent(); public static native void beginJoystickEvent();
public static native void joystickAxis(int deviceId, int axis, float value); public static native void joystickAxis(int deviceId, int axis, float value);
public static native void endJoystickEvent(); public static native void endJoystickEvent();
public static native boolean mouseWheelEvent(float x, float y); public static native boolean mouseWheelEvent(float x, float y);
// will only be called between init() and shutdown() // will only be called between init() and shutdown()
@ -47,7 +48,6 @@ public class NativeApp {
public static native boolean accelerometer(float x, float y, float z); public static native boolean accelerometer(float x, float y, float z);
public static native void sendMessage(String msg, String arg); public static native void sendMessage(String msg, String arg);
public static native String queryConfig(String queryName); public static native String queryConfig(String queryName);
} }

View File

@ -1,205 +0,0 @@
package org.ppsspp.ppsspp;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLDisplay;
import android.opengl.GLSurfaceView.EGLConfigChooser;
import android.util.Log;
public class NativeEGLConfigChooser implements EGLConfigChooser {
private static final String TAG = "NativeEGLConfigChooser";
private static final int EGL_OPENGL_ES2_BIT = 4;
private class ConfigAttribs {
EGLConfig config;
public int red;
public int green;
public int blue;
public int alpha;
public int stencil;
public int depth;
public int samples;
public void Log() {
Log.i(TAG, "EGLConfig: red=" + red + " green=" + green + " blue=" + blue + " alpha=" + alpha + " depth=" + depth + " stencil=" + stencil + " samples=" + samples);
}
}
int getEglConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attr) {
int[] value = new int[1];
try {
if (egl.eglGetConfigAttrib(display, config, attr, value))
return value[0];
else
return -1;
} catch (IllegalArgumentException e) {
if (config == null) {
Log.e(TAG, "Called getEglConfigAttrib with null config. Bad developer.");
} else {
Log.e(TAG, "Illegal argument to getEglConfigAttrib: attr=" + attr);
}
return -1;
}
}
ConfigAttribs[] getConfigAttribs(EGL10 egl, EGLDisplay display, EGLConfig[] configs) {
ConfigAttribs[] attr = new ConfigAttribs[configs.length];
for (int i = 0; i < configs.length; i++) {
ConfigAttribs cfg = new ConfigAttribs();
cfg.config = configs[i];
cfg.red = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_RED_SIZE);
cfg.green = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_GREEN_SIZE);
cfg.blue = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_BLUE_SIZE);
cfg.alpha = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_ALPHA_SIZE);
cfg.depth = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_DEPTH_SIZE);
cfg.stencil = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_STENCIL_SIZE);
cfg.samples = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_SAMPLES);
attr[i] = cfg;
}
return attr;
}
public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) {
// The absolute minimum. We will do our best to choose a better config though.
int[] configSpec = {
EGL10.EGL_RED_SIZE, 5,
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_DEPTH_SIZE, 16,
EGL10.EGL_STENCIL_SIZE, 0,
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
int[] num_config = new int[1];
if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed when counting");
}
int numConfigs = num_config[0];
Log.i(TAG, "There are " + numConfigs + " egl configs");
if (numConfigs <= 0) {
throw new IllegalArgumentException("No configs match configSpec");
}
EGLConfig[] eglConfigs = new EGLConfig[numConfigs];
if (!egl.eglChooseConfig(display, configSpec, eglConfigs, numConfigs, num_config)) {
throw new IllegalArgumentException("eglChooseConfig failed when retrieving");
}
ConfigAttribs [] configs = getConfigAttribs(egl, display, eglConfigs);
ConfigAttribs chosen = null;
// Log them all.
for (int i = 0; i < configs.length; i++) {
configs[i].Log();
}
// We now ignore destination alpha as a workaround for the Mali issue
// where we get badly composited if we use it.
// First, find our ideal configuration. Prefer depth.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 24) {
chosen = c;
break;
}
}
if (chosen == null) {
// Then, prefer one with 20-bit depth (Tegra 3)
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 20) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Second, accept one with 16-bit depth.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 16) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Third, accept one with no stencil.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.depth >= 16) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Third, accept one with alpha but with stencil, 24-bit depth.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 8 && c.stencil >= 8 && c.depth >= 24) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Third, accept one with alpha but with stencil, 16-bit depth.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 8 && c.stencil >= 8 && c.depth >= 16) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Fourth, accept one with 16-bit color but depth and stencil required.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red >= 5 && c.green >= 6 && c.blue >= 5 && c.depth >= 16 && c.stencil >= 8) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Fifth, accept one with 16-bit color but depth required.
for (int i = 0; i < configs.length; i++) {
ConfigAttribs c = configs[i];
if (c.red >= 5 && c.green >= 6 && c.blue >= 5 && c.depth >= 16) {
chosen = c;
break;
}
}
}
if (chosen == null) {
// Final, accept the first one in the list.
if (configs.length > 0)
chosen = configs[0];
}
if (chosen == null) {
throw new IllegalArgumentException("Failed to find a valid EGL config");
}
Log.i(TAG, "Final chosen config: ");
chosen.Log();
return chosen.config;
}
}

View File

@ -1,94 +0,0 @@
package org.ppsspp.ppsspp;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.graphics.Point;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Display;
public class NativeRenderer implements GLSurfaceView.Renderer {
private static String TAG = "NativeRenderer";
private NativeActivity mActivity;
private boolean isDark = false;
private int dpi;
private float refreshRate;
private double dpi_scale_x;
private double dpi_scale_y;
int last_width, last_height;
NativeRenderer(NativeActivity act) {
mActivity = act;
DisplayMetrics metrics = new DisplayMetrics();
Display display = act.getWindowManager().getDefaultDisplay();
display.getMetrics(metrics);
dpi = metrics.densityDpi;
refreshRate = display.getRefreshRate();
}
double getDpiScaleX() {
return dpi_scale_x;
}
double getDpiScaleY() {
return dpi_scale_y;
}
public void setDark(boolean d) {
isDark = d;
}
public void setFixedSize(int xres, int yres, GLSurfaceView surfaceView) {
Log.i(TAG, "Setting surface to fixed size " + xres + "x" + yres);
surfaceView.getHolder().setFixedSize(xres, yres);
}
public void onDrawFrame(GL10 unused /*use GLES20*/) {
if (isDark) {
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT);
} else {
displayRender();
}
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
// Log.i(TAG, "onSurfaceCreated - EGL context is new or was lost");
// Actually, it seems that it is here we should recreate lost GL objects.
displayInit();
}
public void onSurfaceChanged(GL10 unused, int width, int height) {
Point sz = new Point();
mActivity.GetScreenSize(sz);
double actualW = sz.x;
double actualH = sz.y;
dpi_scale_x = ((double)width / (double)actualW);
dpi_scale_y = ((double)height / (double)actualH);
Log.i(TAG, "onSurfaceChanged: " + dpi_scale_x + "x" + dpi_scale_y + " (width=" + width + ", actualW=" + actualW);
int scaled_dpi = (int)((double)dpi * dpi_scale_x);
displayResize(width, height, scaled_dpi, refreshRate);
last_width = width;
last_height = height;
}
// Not override, it's custom.
public void onDestroyed() {
displayShutdown();
}
// NATIVE METHODS
// Note: This also means "device lost" and you should reload
// all buffered objects.
public native void displayInit();
public native void displayResize(int w, int h, int dpi, float refreshRate);
public native void displayRender();
public native void displayShutdown();
}

View File

@ -5,6 +5,7 @@ package org.ppsspp.ppsspp;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.app.Activity; import android.app.Activity;
import android.graphics.Point;
import android.hardware.Sensor; import android.hardware.Sensor;
import android.hardware.SensorEvent; import android.hardware.SensorEvent;
import android.hardware.SensorEventListener; import android.hardware.SensorEventListener;
@ -12,48 +13,55 @@ import android.hardware.SensorManager;
import android.opengl.GLSurfaceView; import android.opengl.GLSurfaceView;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.util.DisplayMetrics;
// import android.os.Build; // import android.os.Build;
// import android.util.Log; // import android.util.Log;
import android.util.Log; import android.util.Log;
import android.view.Display;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.SurfaceView;
import com.bda.controller.*; import com.bda.controller.*;
public class NativeGLView extends GLSurfaceView implements SensorEventListener, ControllerListener { public class NativeSurfaceView extends SurfaceView implements SensorEventListener, ControllerListener {
private static String TAG = "NativeGLView"; private static String TAG = "NativeGLView";
private SensorManager mSensorManager; private SensorManager mSensorManager;
private Sensor mAccelerometer; private Sensor mAccelerometer;
private NativeActivity mActivity; private NativeActivity mActivity;
// Moga controller // Moga controller
private Controller mController = null; private Controller mController = null;
private boolean isMogaPro = false; private boolean isMogaPro = false;
private int dpi;
public NativeGLView(NativeActivity activity) { private float refreshRate;
private double dpi_scale_x;
private double dpi_scale_y;
int last_width, last_height;
public int fixedW = 0;
public int fixedH = 0;
public NativeSurfaceView(NativeActivity activity) {
super(activity); super(activity);
DisplayMetrics metrics = new DisplayMetrics();
Display display = activity.getWindowManager().getDefaultDisplay();
display.getMetrics(metrics);
dpi = metrics.densityDpi;
refreshRate = display.getRefreshRate();
mActivity = activity; mActivity = activity;
/*// TODO: This would be nice.
if (Build.VERSION.SDK_INT >= 11) {
try {
Method method_setPreserveEGLContextOnPause = GLSurfaceView.class.getMethod(
"setPreserveEGLContextOnPause", new Class[] { Boolean.class });
Log.i(TAG, "Invoking setPreserveEGLContextOnPause");
method_setPreserveEGLContextOnPause.invoke(this, true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}*/
mSensorManager = (SensorManager)activity.getSystemService(Activity.SENSOR_SERVICE); mSensorManager = (SensorManager)activity.getSystemService(Activity.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
mController = Controller.getInstance(activity); mController = Controller.getInstance(activity);
onResize(display.getWidth(), display.getHeight());
try { try {
MogaHack.init(mController, activity); MogaHack.init(mController, activity);
Log.i(TAG, "MOGA initialized"); Log.i(TAG, "MOGA initialized");
@ -63,23 +71,43 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
} }
} }
void onResize(int width, int height) {
Point sz = new Point();
mActivity.GetScreenSize(sz);
double actualW = sz.x;
double actualH = sz.y;
dpi_scale_x = (width / actualW);
dpi_scale_y = (height / actualH);
Log.i(TAG, "onSurfaceChanged: " + dpi_scale_x + "x" + dpi_scale_y + " (width=" + width + ", actualW=" + actualW);
int scaled_dpi = (int)(dpi * dpi_scale_x);
NativeApp.displayResize(width, height, scaled_dpi, refreshRate);
last_width = width;
last_height = height;
}
void setFixedSize(int w, int h) {
fixedW = w;
fixedH = h;
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private int getToolType(final MotionEvent ev, int pointer) { private int getToolType(final MotionEvent ev, int pointer) {
return ev.getToolType(pointer); return ev.getToolType(pointer);
} }
@Override
public boolean onTouchEvent(final MotionEvent ev) { public boolean onTouchEvent(final MotionEvent ev) {
boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;
int numTouchesHandled = 0; int numTouchesHandled = 0;
float scaleX = (float)mActivity.getRenderer().getDpiScaleX(); float scaleX = (float)this.dpi_scale_x;
float scaleY = (float)mActivity.getRenderer().getDpiScaleY(); float scaleY = (float)this.dpi_scale_y;
for (int i = 0; i < ev.getPointerCount(); i++) { for (int i = 0; i < ev.getPointerCount(); i++) {
int pid = ev.getPointerId(i); int pid = ev.getPointerId(i);
int code = 0; int code = 0;
final int action = ev.getActionMasked(); final int action = ev.getActionMasked();
// These code bits are now the same as the constants in input_state.h. // These code bits are now the same as the constants in input_state.h.
switch (action) { switch (action) {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
@ -98,7 +126,7 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
default: default:
break; break;
} }
if (code != 0) { if (code != 0) {
if (canReadToolType) { if (canReadToolType) {
int tool = getToolType(ev, i); int tool = getToolType(ev, i);
@ -112,9 +140,11 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
} }
// Sensor management // Sensor management
@Override
public void onAccuracyChanged(Sensor sensor, int arg1) { public void onAccuracyChanged(Sensor sensor, int arg1) {
} }
@Override
public void onSensorChanged(SensorEvent event) { public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
return; return;
@ -122,41 +152,37 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
// Can also look at event.timestamp for accuracy magic // Can also look at event.timestamp for accuracy magic
NativeApp.accelerometer(event.values[0], event.values[1], event.values[2]); NativeApp.accelerometer(event.values[0], event.values[1], event.values[2]);
} }
@Override
public void onPause() { public void onPause() {
super.onPause();
mSensorManager.unregisterListener(this); mSensorManager.unregisterListener(this);
if (mController != null) { if (mController != null) {
mController.onPause(); mController.onPause();
} }
} }
@Override
public void onResume() { public void onResume() {
super.onResume();
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME);
if (mController != null) { if (mController != null) {
mController.onResume(); mController.onResume();
// According to the docs, the Moga's state can be inconsistent here. // According to the docs, the Moga's state can be inconsistent here.
// We should do a one time poll. TODO // We should do a one time poll. TODO
} }
} }
public void onDestroy() { public void onDestroy() {
if (mController != null) { if (mController != null) {
mController.exit(); mController.exit();
} }
} }
// MOGA Controller - from ControllerListener // MOGA Controller - from ControllerListener
@Override @Override
public void onKeyEvent(KeyEvent event) { public void onKeyEvent(KeyEvent event) {
// The Moga left stick doubles as a D-pad. This creates mapping conflicts so let's turn it off. // The Moga left stick doubles as a D-pad. This creates mapping conflicts so let's turn it off.
// Unfortunately this breaks menu navigation in PPSSPP currently but meh. // Unfortunately this breaks menu navigation in PPSSPP currently but meh.
// This is different on Moga Pro though. // This is different on Moga Pro though.
if (!isMogaPro) { if (!isMogaPro) {
switch (event.getKeyCode()) { switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_DOWN: case KeyEvent.KEYCODE_DPAD_DOWN:
@ -216,7 +242,7 @@ public class NativeGLView extends GLSurfaceView implements SensorEventListener,
break; break;
} }
break; break;
case StateEvent.STATE_POWER_LOW: case StateEvent.STATE_POWER_LOW:
switch (state.getAction()) { switch (state.getAction()) {
case StateEvent.ACTION_TRUE: case StateEvent.ACTION_TRUE:

View File

@ -11,7 +11,7 @@ public class PpssppActivity extends NativeActivity {
private static final String TAG = "PpssppActivity"; private static final String TAG = "PpssppActivity";
// Key used by shortcut. // Key used by shortcut.
public static final String SHORTCUT_EXTRA_KEY = "org.ppsspp.ppsspp.Shortcuts"; public static final String SHORTCUT_EXTRA_KEY = "org.ppsspp.ppsspp.Shortcuts";
private static boolean m_hasUnsupportedABI = false; private static boolean m_hasUnsupportedABI = false;
private static boolean m_hasNoNativeBinary = false; private static boolean m_hasNoNativeBinary = false;
@ -47,15 +47,15 @@ public class PpssppActivity extends NativeActivity {
} }
Looper.loop(); Looper.loop();
} }
}.start(); }.start();
try { try {
Thread.sleep(3000); Thread.sleep(3000);
} catch (InterruptedException e) { } catch (InterruptedException e) {
e.printStackTrace(); e.printStackTrace();
} }
System.exit(-1); System.exit(-1);
return; return;
} }
@ -114,7 +114,7 @@ public class PpssppActivity extends NativeActivity {
sz.y = 0; sz.y = 0;
return; return;
} }
correctRatio(sz, (float)scale); correctRatio(sz, scale);
} }
// called by the C++ code through JNI. Dispatch anything we can't directly handle // called by the C++ code through JNI. Dispatch anything we can't directly handle
@ -123,6 +123,7 @@ public class PpssppActivity extends NativeActivity {
final String cmd = command; final String cmd = command;
final String param = parameter; final String param = parameter;
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override
public void run() { public void run() {
processCommand(cmd, param); processCommand(cmd, param);
} }