1 // This is generic code that is included in all Android apps that use the
2 // Native framework by Henrik Rydgard (https://github.com/hrydgard/native).
3
4 // It calls a set of methods defined in NativeApp.h. These should be implemented
5 // by your game or app.
6
7 #include <cstdlib>
8 #include <cstdint>
9
10 #include <sstream>
11 #include <queue>
12 #include <mutex>
13 #include <thread>
14 #include <atomic>
15
16 #include <android/log.h>
17
18 #ifndef _MSC_VER
19 #include <jni.h>
20 #include <android/native_window_jni.h>
21 #include <android/log.h>
22
23 #elif !defined(JNIEXPORT)
24 // Just for better highlighting in MSVC if opening this file.
25 // Not having types makes it get confused and say everything is wrong.
26 struct JavaVM;
27 typedef void *jmethodID;
28 typedef void *jfieldID;
29
30 typedef uint8_t jboolean;
31 typedef int8_t jbyte;
32 typedef int16_t jshort;
33 typedef int32_t jint;
34 typedef int64_t jlong;
35 typedef jint jsize;
36 typedef float jfloat;
37 typedef double jdouble;
38
39 class _jobject {};
40 class _jclass : public _jobject {};
41 typedef _jobject *jobject;
42 typedef _jclass *jclass;
43 typedef jobject jstring;
44 typedef jobject jbyteArray;
45
46 struct JNIEnv {};
47
48 #define JNIEXPORT
49 #define JNICALL
50 // Just a random value to make MSVC highlighting happy.
51 #define JNI_VERSION_1_6 16
52 #endif
53
54 #include "Common/Net/Resolve.h"
55 #include "android/jni/AndroidAudio.h"
56 #include "Common/GPU/OpenGL/GLCommon.h"
57 #include "Common/GPU/OpenGL/GLFeatures.h"
58
59 #include "Common/System/Display.h"
60 #include "Common/System/NativeApp.h"
61 #include "Common/System/System.h"
62 #include "Common/Thread/ThreadUtil.h"
63 #include "Common/File/Path.h"
64 #include "Common/File/DirListing.h"
65 #include "Common/File/VFS/VFS.h"
66 #include "Common/File/VFS/AssetReader.h"
67 #include "Common/File/AndroidStorage.h"
68 #include "Common/Input/InputState.h"
69 #include "Common/Input/KeyCodes.h"
70 #include "Common/Profiler/Profiler.h"
71 #include "Common/Math/math_util.h"
72 #include "Common/Data/Text/Parsers.h"
73
74 #include "Common/Log.h"
75 #include "Common/GraphicsContext.h"
76 #include "Common/StringUtils.h"
77 #include "Common/TimeUtil.h"
78
79 #include "AndroidGraphicsContext.h"
80 #include "AndroidVulkanContext.h"
81 #include "AndroidEGLContext.h"
82 #include "AndroidJavaGLContext.h"
83
84 #include "Core/Config.h"
85 #include "Core/ConfigValues.h"
86 #include "Core/Loaders.h"
87 #include "Core/FileLoaders/LocalFileLoader.h"
88 #include "Core/System.h"
89 #include "Core/HLE/sceUsbCam.h"
90 #include "Core/HLE/sceUsbGps.h"
91 #include "Core/Host.h"
92 #include "Common/CPUDetect.h"
93 #include "Common/Log.h"
94 #include "UI/GameInfoCache.h"
95
96 #include "app-android.h"
97
98 bool useCPUThread = true;
99
100 enum class EmuThreadState {
101 DISABLED,
102 START_REQUESTED,
103 RUNNING,
104 QUIT_REQUESTED,
105 STOPPED,
106 };
107
108 static std::thread emuThread;
109 static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
110
111 void UpdateRunLoopAndroid(JNIEnv *env);
112
113 AndroidAudioState *g_audioState;
114
115 struct FrameCommand {
FrameCommandFrameCommand116 FrameCommand() {}
FrameCommandFrameCommand117 FrameCommand(std::string cmd, std::string prm) : command(cmd), params(prm) {}
118
119 std::string command;
120 std::string params;
121 };
122
123 static std::mutex frameCommandLock;
124 static std::queue<FrameCommand> frameCommands;
125
126 std::string systemName;
127 std::string langRegion;
128 std::string mogaVersion;
129 std::string boardName;
130
131 std::string g_externalDir; // Original external dir (root of Android storage).
132 std::string g_extFilesDir; // App private external dir.
133
134 std::vector<std::string> g_additionalStorageDirs;
135
136 static float left_joystick_x_async;
137 static float left_joystick_y_async;
138 static float right_joystick_x_async;
139 static float right_joystick_y_async;
140 static float hat_joystick_x_async;
141 static float hat_joystick_y_async;
142
143 static int optimalFramesPerBuffer = 0;
144 static int optimalSampleRate = 0;
145 static int sampleRate = 0;
146 static int framesPerBuffer = 0;
147 static int androidVersion;
148 static int deviceType;
149
150 // Should only be used for display detection during startup (for config defaults etc)
151 // This is the ACTUAL display size, not the hardware scaled display size.
152 // Exposed so it can be displayed on the touchscreen test.
153 int display_xres;
154 int display_yres;
155 static int display_dpi_x;
156 static int display_dpi_y;
157 static int backbuffer_format; // Android PixelFormat enum
158
159 static int desiredBackbufferSizeX;
160 static int desiredBackbufferSizeY;
161
162 // Cache the class loader so we can use it from native threads. Required for TextAndroid.
163 JavaVM* gJvm = nullptr;
164 static jobject gClassLoader;
165 static jmethodID gFindClassMethod;
166
167 static float g_safeInsetLeft = 0.0;
168 static float g_safeInsetRight = 0.0;
169 static float g_safeInsetTop = 0.0;
170 static float g_safeInsetBottom = 0.0;
171
172 static jmethodID postCommand;
173 static jmethodID getDebugString;
174
175 static jobject nativeActivity;
176 static volatile bool exitRenderLoop;
177 static bool renderLoopRunning;
178 static int inputBoxSequence = 1;
179 std::map<int, std::function<void(bool, const std::string &)>> inputBoxCallbacks;
180
181 static float dp_xscale = 1.0f;
182 static float dp_yscale = 1.0f;
183
184 static bool renderer_inited = false;
185 static bool sustainedPerfSupported = false;
186 static std::mutex renderLock;
187
188 // See NativeQueryConfig("androidJavaGL") to change this value.
189 static bool javaGL = true;
190
191 static std::string library_path;
192 static std::map<SystemPermission, PermissionStatus> permissions;
193
194 AndroidGraphicsContext *graphicsContext;
195
196 #ifndef LOG_APP_NAME
197 #define LOG_APP_NAME "PPSSPP"
198 #endif
199
200 #ifdef _DEBUG
201 #define DLOG(...) __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, __VA_ARGS__);
202 #else
203 #define DLOG(...)
204 #endif
205
206 #define ILOG(...) __android_log_print(ANDROID_LOG_INFO, LOG_APP_NAME, __VA_ARGS__);
207 #define WLOG(...) __android_log_print(ANDROID_LOG_WARN, LOG_APP_NAME, __VA_ARGS__);
208 #define ELOG(...) __android_log_print(ANDROID_LOG_ERROR, LOG_APP_NAME, __VA_ARGS__);
209 #define FLOG(...) __android_log_print(ANDROID_LOG_FATAL, LOG_APP_NAME, __VA_ARGS__);
210
211 #define MessageBox(a, b, c, d) __android_log_print(ANDROID_LOG_INFO, APP_NAME, "%s %s", (b), (c));
212
Log(const LogMessage & message)213 void AndroidLogger::Log(const LogMessage &message) {
214 // Log with simplified headers as Android already provides timestamp etc.
215 switch (message.level) {
216 case LogTypes::LVERBOSE:
217 case LogTypes::LDEBUG:
218 case LogTypes::LINFO:
219 ILOG("[%s] %s", message.log, message.msg.c_str());
220 break;
221 case LogTypes::LERROR:
222 ELOG("[%s] %s", message.log, message.msg.c_str());
223 break;
224 case LogTypes::LWARNING:
225 WLOG("[%s] %s", message.log, message.msg.c_str());
226 break;
227 case LogTypes::LNOTICE:
228 default:
229 ILOG("[%s] !!! %s", message.log, message.msg.c_str());
230 break;
231 }
232 }
233
getEnv()234 JNIEnv* getEnv() {
235 JNIEnv *env;
236 int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
237 if(status < 0) {
238 status = gJvm->AttachCurrentThread(&env, NULL);
239 if(status < 0) {
240 return nullptr;
241 }
242 }
243 return env;
244 }
245
findClass(const char * name)246 jclass findClass(const char* name) {
247 return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
248 }
249
JNI_OnLoad(JavaVM * pjvm,void * reserved)250 JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
251 INFO_LOG(SYSTEM, "JNI_OnLoad");
252 gJvm = pjvm; // cache the JavaVM pointer
253 auto env = getEnv();
254 //replace with one of your classes in the line below
255 auto randomClass = env->FindClass("org/ppsspp/ppsspp/NativeActivity");
256 jclass classClass = env->GetObjectClass(randomClass);
257 auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
258 auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
259 "()Ljava/lang/ClassLoader;");
260 gClassLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
261 gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
262 "(Ljava/lang/String;)Ljava/lang/Class;");
263 return JNI_VERSION_1_6;
264 }
265
EmuThreadFunc()266 static void EmuThreadFunc() {
267 JNIEnv *env;
268 gJvm->AttachCurrentThread(&env, nullptr);
269
270 SetCurrentThreadName("Emu");
271 INFO_LOG(SYSTEM, "Entering emu thread");
272
273 // Wait for render loop to get started.
274 if (!graphicsContext || !graphicsContext->Initialized()) {
275 INFO_LOG(SYSTEM, "Runloop: Waiting for displayInit...");
276 while (!graphicsContext || !graphicsContext->Initialized()) {
277 sleep_ms(20);
278 }
279 } else {
280 INFO_LOG(SYSTEM, "Runloop: Graphics context available! %p", graphicsContext);
281 }
282 NativeInitGraphics(graphicsContext);
283
284 INFO_LOG(SYSTEM, "Graphics initialized. Entering loop.");
285
286 // There's no real requirement that NativeInit happen on this thread.
287 // We just call the update/render loop here.
288 emuThreadState = (int)EmuThreadState::RUNNING;
289 while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
290 UpdateRunLoopAndroid(env);
291 }
292 INFO_LOG(SYSTEM, "QUIT_REQUESTED found, left loop. Setting state to STOPPED.");
293 emuThreadState = (int)EmuThreadState::STOPPED;
294
295 NativeShutdownGraphics();
296
297 // Also ask the main thread to stop, so it doesn't hang waiting for a new frame.
298 graphicsContext->StopThread();
299
300 gJvm->DetachCurrentThread();
301 INFO_LOG(SYSTEM, "Leaving emu thread");
302 }
303
EmuThreadStart()304 static void EmuThreadStart() {
305 INFO_LOG(SYSTEM, "EmuThreadStart");
306 emuThreadState = (int)EmuThreadState::START_REQUESTED;
307 emuThread = std::thread(&EmuThreadFunc);
308 }
309
310 // Call EmuThreadStop first, then keep running the GPU (or eat commands)
311 // as long as emuThreadState isn't STOPPED and/or there are still things queued up.
312 // Only after that, call EmuThreadJoin.
EmuThreadStop(const char * caller)313 static void EmuThreadStop(const char *caller) {
314 INFO_LOG(SYSTEM, "EmuThreadStop - stopping (%s)...", caller);
315 emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
316 }
317
EmuThreadJoin()318 static void EmuThreadJoin() {
319 emuThread.join();
320 emuThread = std::thread();
321 INFO_LOG(SYSTEM, "EmuThreadJoin - joined");
322 }
323
324 static void ProcessFrameCommands(JNIEnv *env);
325
PushCommand(std::string cmd,std::string param)326 void PushCommand(std::string cmd, std::string param) {
327 std::lock_guard<std::mutex> guard(frameCommandLock);
328 frameCommands.push(FrameCommand(cmd, param));
329 }
330
331 // Android implementation of callbacks to the Java part of the app
SystemToast(const char * text)332 void SystemToast(const char *text) {
333 PushCommand("toast", text);
334 }
335
ShowKeyboard()336 void ShowKeyboard() {
337 PushCommand("showKeyboard", "");
338 }
339
Vibrate(int length_ms)340 void Vibrate(int length_ms) {
341 char temp[32];
342 sprintf(temp, "%i", length_ms);
343 PushCommand("vibrate", temp);
344 }
345
OpenDirectory(const char * path)346 void OpenDirectory(const char *path) {
347 // Unsupported
348 }
349
LaunchBrowser(const char * url)350 void LaunchBrowser(const char *url) {
351 PushCommand("launchBrowser", url);
352 }
353
LaunchMarket(const char * url)354 void LaunchMarket(const char *url) {
355 PushCommand("launchMarket", url);
356 }
357
LaunchEmail(const char * email_address)358 void LaunchEmail(const char *email_address) {
359 PushCommand("launchEmail", email_address);
360 }
361
System_SendMessage(const char * command,const char * parameter)362 void System_SendMessage(const char *command, const char *parameter) {
363 PushCommand(command, parameter);
364 }
365
System_GetProperty(SystemProperty prop)366 std::string System_GetProperty(SystemProperty prop) {
367 switch (prop) {
368 case SYSPROP_NAME:
369 return systemName;
370 case SYSPROP_LANGREGION: // "en_US"
371 return langRegion;
372 case SYSPROP_MOGA_VERSION:
373 return mogaVersion;
374 case SYSPROP_BOARDNAME:
375 return boardName;
376 default:
377 return "";
378 }
379 }
380
System_GetPropertyStringVec(SystemProperty prop)381 std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
382 switch (prop) {
383 case SYSPROP_ADDITIONAL_STORAGE_DIRS:
384 return g_additionalStorageDirs;
385
386 case SYSPROP_TEMP_DIRS:
387 default:
388 return std::vector<std::string>();
389 }
390 }
391
System_GetPropertyInt(SystemProperty prop)392 int System_GetPropertyInt(SystemProperty prop) {
393 switch (prop) {
394 case SYSPROP_SYSTEMVERSION:
395 return androidVersion;
396 case SYSPROP_DEVICE_TYPE:
397 return deviceType;
398 case SYSPROP_DISPLAY_XRES:
399 return display_xres;
400 case SYSPROP_DISPLAY_YRES:
401 return display_yres;
402 case SYSPROP_AUDIO_SAMPLE_RATE:
403 return sampleRate;
404 case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
405 return framesPerBuffer;
406 case SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE:
407 return optimalSampleRate;
408 case SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER:
409 return optimalFramesPerBuffer;
410 default:
411 return -1;
412 }
413 }
414
System_GetPropertyFloat(SystemProperty prop)415 float System_GetPropertyFloat(SystemProperty prop) {
416 switch (prop) {
417 case SYSPROP_DISPLAY_REFRESH_RATE:
418 return display_hz;
419 case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
420 return g_safeInsetLeft;
421 case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
422 return g_safeInsetRight;
423 case SYSPROP_DISPLAY_SAFE_INSET_TOP:
424 return g_safeInsetTop;
425 case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
426 return g_safeInsetBottom;
427 default:
428 return -1;
429 }
430 }
431
System_GetPropertyBool(SystemProperty prop)432 bool System_GetPropertyBool(SystemProperty prop) {
433 switch (prop) {
434 case SYSPROP_SUPPORTS_PERMISSIONS:
435 if (androidVersion < 23) {
436 // 6.0 Marshmallow introduced run time permissions.
437 return false;
438 } else {
439 // It gets a bit complicated here. If scoped storage enforcement is on,
440 // we also don't need to request permissions. We'll have the access we request
441 // on a per-folder basis.
442 return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE);
443 }
444 case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE:
445 return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature.
446 case SYSPROP_HAS_ADDITIONAL_STORAGE:
447 return !g_additionalStorageDirs.empty();
448 case SYSPROP_HAS_BACK_BUTTON:
449 return true;
450 case SYSPROP_HAS_IMAGE_BROWSER:
451 return true;
452 case SYSPROP_HAS_FILE_BROWSER:
453 // It's only really needed with scoped storage, but why not make it available
454 // as far back as possible - works just fine.
455 return androidVersion >= 19; // when ACTION_OPEN_DOCUMENT was added
456 case SYSPROP_HAS_FOLDER_BROWSER:
457 // Uses OPEN_DOCUMENT_TREE to let you select a folder.
458 // Doesn't actually mean it's usable though, in many early versions of Android
459 // this dialog is complete garbage and only lets you select subfolders of the Downloads folder.
460 return androidVersion >= 21; // when ACTION_OPEN_DOCUMENT_TREE was added
461 case SYSPROP_APP_GOLD:
462 #ifdef GOLD
463 return true;
464 #else
465 return false;
466 #endif
467 case SYSPROP_CAN_JIT:
468 return true;
469 case SYSPROP_ANDROID_SCOPED_STORAGE:
470 // We turn this on for Android 30+ (11) now that when we target Android 11+.
471 // Along with adding:
472 // android:preserveLegacyExternalStorage="true"
473 // To the already requested:
474 // android:requestLegacyExternalStorage="true"
475 //
476 // This will cause Android 11+ to still behave like Android 10 until the app
477 // is manually uninstalled. We can detect this state with
478 // Android_IsExternalStoragePreservedLegacy(), but most of the app will just see
479 // that scoped storage enforcement is disabled in this case.
480 if (androidVersion >= 30) {
481 // Here we do a check to see if we ended up in the preserveLegacyExternalStorage path.
482 // That won't last if the user uninstalls/reinstalls though, but would preserve the user
483 // experience for simple upgrades so maybe let's support it.
484 return !Android_IsExternalStoragePreservedLegacy();
485 } else {
486 return false;
487 }
488 default:
489 return false;
490 }
491 }
492
Android_GetInputDeviceDebugString()493 std::string Android_GetInputDeviceDebugString() {
494 if (!nativeActivity) {
495 return "(N/A)";
496 }
497 auto env = getEnv();
498 jstring param = env->NewStringUTF("InputDevice");
499
500 jstring str = (jstring)env->CallObjectMethod(nativeActivity, getDebugString, param);
501 if (!str) {
502 return "(N/A)";
503 }
504
505 const char *charArray = env->GetStringUTFChars(str, 0);
506 std::string retVal = charArray;
507 env->DeleteLocalRef(str);
508 return retVal;
509 }
510
GetJavaString(JNIEnv * env,jstring jstr)511 std::string GetJavaString(JNIEnv *env, jstring jstr) {
512 if (!jstr)
513 return "";
514 const char *str = env->GetStringUTFChars(jstr, 0);
515 std::string cpp_string = std::string(str);
516 env->ReleaseStringUTFChars(jstr, str);
517 return cpp_string;
518 }
519
Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv * env,jobject obj)520 extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_registerCallbacks(JNIEnv *env, jobject obj) {
521 nativeActivity = env->NewGlobalRef(obj);
522 postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V");
523 getDebugString = env->GetMethodID(env->GetObjectClass(obj), "getDebugString", "(Ljava/lang/String;)Ljava/lang/String;");
524 _dbg_assert_(postCommand);
525 _dbg_assert_(getDebugString);
526
527 Android_RegisterStorageCallbacks(env, obj);
528 Android_StorageSetNativeActivity(nativeActivity);
529 }
530
Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv * env,jobject obj)531 extern "C" void Java_org_ppsspp_ppsspp_NativeActivity_unregisterCallbacks(JNIEnv *env, jobject obj) {
532 Android_StorageSetNativeActivity(nullptr);
533 env->DeleteGlobalRef(nativeActivity);
534 nativeActivity = nullptr;
535 }
536
537 // This is now only used as a trigger for GetAppInfo as a function to all before Init.
538 // On Android we don't use any of the values it returns.
Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv * env,jclass)539 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv *env, jclass) {
540 std::string app_name, app_nice_name, version;
541 bool landscape;
542 NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
543 return landscape;
544 }
545
546 // Allow the app to intercept the back button.
Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv * env,jclass)547 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv *env, jclass) {
548 return NativeIsAtTopLevel();
549 }
550
Java_org_ppsspp_ppsspp_NativeApp_audioConfig(JNIEnv * env,jclass,jint optimalFPB,jint optimalSR)551 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioConfig
552 (JNIEnv *env, jclass, jint optimalFPB, jint optimalSR) {
553 optimalFramesPerBuffer = optimalFPB;
554 optimalSampleRate = optimalSR;
555 }
556
Java_org_ppsspp_ppsspp_NativeApp_queryConfig(JNIEnv * env,jclass,jstring jquery)557 extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig
558 (JNIEnv *env, jclass, jstring jquery) {
559 std::string query = GetJavaString(env, jquery);
560 std::string result = NativeQueryConfig(query);
561 jstring jresult = env->NewStringUTF(result.c_str());
562 return jresult;
563 }
564
parse_args(std::vector<std::string> & args,const std::string value)565 static void parse_args(std::vector<std::string> &args, const std::string value) {
566 // Simple argument parser so we can take args from extra params.
567 const char *p = value.c_str();
568
569 while (*p != '\0') {
570 while (isspace(*p)) {
571 p++;
572 }
573 if (*p == '\0') {
574 break;
575 }
576
577 bool done = false;
578 bool quote = false;
579 std::string arg;
580
581 while (!done) {
582 size_t sz = strcspn(p, "\"\\ \r\n\t");
583 arg += std::string(p, sz);
584 p += sz;
585
586 switch (*p) {
587 case '"':
588 quote = !quote;
589 p++;
590 break;
591
592 case '\\':
593 p++;
594 arg += std::string(p, 1);
595 p++;
596 break;
597
598 case '\0':
599 done = true;
600 break;
601
602 default:
603 // If it's not the above, it's whitespace.
604 if (!quote) {
605 done = true;
606 } else {
607 sz = strspn(p, " \r\n\t");
608 arg += std::string(p, sz);
609 p += sz;
610 }
611 break;
612 }
613 }
614
615 args.push_back(arg);
616
617 while (isspace(*p)) {
618 p++;
619 }
620 }
621 }
622
623 // Need to use raw Android logging before NativeInit.
624 #define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__)
625
Java_org_ppsspp_ppsspp_NativeApp_init(JNIEnv * env,jclass,jstring jmodel,jint jdeviceType,jstring jlangRegion,jstring japkpath,jstring jdataDir,jstring jexternalStorageDir,jstring jexternalFilesDir,jstring jadditionalStorageDirs,jstring jlibraryDir,jstring jcacheDir,jstring jshortcutParam,jint jAndroidVersion,jstring jboard)626 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
627 (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
628 jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jadditionalStorageDirs, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam,
629 jint jAndroidVersion, jstring jboard) {
630 SetCurrentThreadName("androidInit");
631
632 // Makes sure we get early permission grants.
633 ProcessFrameCommands(env);
634
635 EARLY_LOG("NativeApp.init() -- begin");
636 PROFILE_INIT();
637
638 std::lock_guard<std::mutex> guard(renderLock);
639 renderer_inited = false;
640 androidVersion = jAndroidVersion;
641 deviceType = jdeviceType;
642
643 left_joystick_x_async = 0;
644 left_joystick_y_async = 0;
645 right_joystick_x_async = 0;
646 right_joystick_y_async = 0;
647 hat_joystick_x_async = 0;
648 hat_joystick_y_async = 0;
649
650 std::string apkPath = GetJavaString(env, japkpath);
651 VFSRegister("", new ZipAssetReader(apkPath.c_str(), "assets/"));
652
653 systemName = GetJavaString(env, jmodel);
654 langRegion = GetJavaString(env, jlangRegion);
655
656 EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str());
657
658 std::string externalStorageDir = GetJavaString(env, jexternalStorageDir);
659 std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs);
660 std::string externalFilesDir = GetJavaString(env, jexternalFilesDir);
661
662 g_externalDir = externalStorageDir;
663 g_extFilesDir = externalFilesDir;
664
665 if (!additionalStorageDirsString.empty()) {
666 SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs);
667 for (auto &str : g_additionalStorageDirs) {
668 EARLY_LOG("Additional storage: %s", str.c_str());
669 }
670 }
671
672 std::string user_data_path = GetJavaString(env, jdataDir);
673 if (user_data_path.size() > 0)
674 user_data_path += "/";
675 library_path = GetJavaString(env, jlibraryDir) + "/";
676 std::string shortcut_param = GetJavaString(env, jshortcutParam);
677 std::string cacheDir = GetJavaString(env, jcacheDir);
678 std::string buildBoard = GetJavaString(env, jboard);
679 boardName = buildBoard;
680 EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str());
681 EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());
682
683 std::string app_name;
684 std::string app_nice_name;
685 std::string version;
686 bool landscape;
687
688 // Unfortunately, on the Samsung Galaxy S7, this isn't in /proc/cpuinfo.
689 // We also can't read it from __system_property_get.
690 if (buildBoard == "universal8890") {
691 cpu_info.sQuirks.bExynos8890DifferingCachelineSizes = true;
692 }
693
694 NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
695
696 // If shortcut_param is not empty, pass it as additional arguments to the NativeInit() method.
697 // NativeInit() is expected to treat extra argument as boot_filename, which in turn will start game immediately.
698 // NOTE: Will only work if ppsspp started from Activity.onCreate(). Won't work if ppsspp app start from onResume().
699
700 std::vector<const char *> args;
701 std::vector<std::string> temp;
702 args.push_back(app_name.c_str());
703 if (!shortcut_param.empty()) {
704 parse_args(temp, shortcut_param);
705 for (const auto &arg : temp) {
706 args.push_back(arg.c_str());
707 }
708 }
709
710 NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str());
711
712 // No need to use EARLY_LOG anymore.
713
714 retry:
715 // Now that we've loaded config, set javaGL.
716 javaGL = NativeQueryConfig("androidJavaGL") == "true";
717
718 switch (g_Config.iGPUBackend) {
719 case (int)GPUBackend::OPENGL:
720 useCPUThread = true;
721 if (javaGL) {
722 INFO_LOG(SYSTEM, "NativeApp.init() -- creating OpenGL context (JavaGL)");
723 graphicsContext = new AndroidJavaEGLGraphicsContext();
724 } else {
725 graphicsContext = new AndroidEGLGraphicsContext();
726 }
727 break;
728 case (int)GPUBackend::VULKAN:
729 {
730 INFO_LOG(SYSTEM, "NativeApp.init() -- creating Vulkan context");
731 useCPUThread = false; // The Vulkan render manager manages its own thread.
732 // We create and destroy the Vulkan graphics context in the "EGL" thread.
733 AndroidVulkanContext *ctx = new AndroidVulkanContext();
734 if (!ctx->InitAPI()) {
735 INFO_LOG(SYSTEM, "Failed to initialize Vulkan, switching to OpenGL");
736 g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
737 SetGPUBackend(GPUBackend::OPENGL);
738 goto retry;
739 } else {
740 graphicsContext = ctx;
741 }
742 break;
743 }
744 default:
745 ERROR_LOG(SYSTEM, "NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend);
746 g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
747 goto retry;
748 }
749
750 if (useCPUThread) {
751 INFO_LOG(SYSTEM, "NativeApp.init() - launching emu thread");
752 EmuThreadStart();
753 }
754 }
755
Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *,jclass)756 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) {
757 sampleRate = optimalSampleRate;
758 if (optimalSampleRate == 0) {
759 sampleRate = 44100;
760 }
761 if (optimalFramesPerBuffer > 0) {
762 framesPerBuffer = optimalFramesPerBuffer;
763 } else {
764 framesPerBuffer = 512;
765 }
766
767 // Some devices have totally bonkers buffer sizes like 8192. They will have terrible latency anyway, so to avoid having to
768 // create extra smart buffering code, we'll just let their regular mixer deal with it, missing the fast path (as if they had one...)
769 if (framesPerBuffer > 512) {
770 framesPerBuffer = 512;
771 sampleRate = 44100;
772 }
773
774 INFO_LOG(AUDIO, "NativeApp.audioInit() -- Using OpenSL audio! frames/buffer: %i optimal sr: %i actual sr: %i", optimalFramesPerBuffer, optimalSampleRate, sampleRate);
775 if (!g_audioState) {
776 g_audioState = AndroidAudio_Init(&NativeMix, framesPerBuffer, sampleRate);
777 } else {
778 ERROR_LOG(AUDIO, "Audio state already initialized");
779 }
780 }
781
Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *,jclass)782 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *, jclass) {
783 if (g_audioState) {
784 AndroidAudio_Shutdown(g_audioState);
785 g_audioState = nullptr;
786 } else {
787 ERROR_LOG(AUDIO, "Audio state already shutdown!");
788 }
789 }
790
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *,jclass,jint sampleRate)791 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *, jclass, jint sampleRate) {
792 AndroidAudio_Recording_SetSampleRate(g_audioState, sampleRate);
793 }
794
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *,jclass)795 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *, jclass) {
796 AndroidAudio_Recording_Start(g_audioState);
797 }
798
Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *,jclass)799 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *, jclass) {
800 AndroidAudio_Recording_Stop(g_audioState);
801 }
802
audioRecording_Available()803 bool audioRecording_Available() {
804 return true;
805 }
806
audioRecording_State()807 bool audioRecording_State() {
808 return AndroidAudio_Recording_State(g_audioState);
809 }
810
Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *,jclass)811 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) {
812 INFO_LOG(SYSTEM, "NativeApp.resume() - resuming audio");
813 AndroidAudio_Resume(g_audioState);
814
815 NativeMessageReceived("app_resumed", "");
816 }
817
Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *,jclass)818 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
819 INFO_LOG(SYSTEM, "NativeApp.pause() - pausing audio");
820 AndroidAudio_Pause(g_audioState);
821 }
822
Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *,jclass)823 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
824 if (renderer_inited && useCPUThread && graphicsContext) {
825 // Only used in Java EGL path.
826 EmuThreadStop("shutdown");
827 INFO_LOG(SYSTEM, "BeginAndroidShutdown");
828 graphicsContext->BeginAndroidShutdown();
829 // Skipping GL calls, the old context is gone.
830 while (graphicsContext->ThreadFrame()) {
831 INFO_LOG(SYSTEM, "graphicsContext->ThreadFrame executed to clear buffers");
832 }
833 INFO_LOG(SYSTEM, "Joining emuthread");
834 EmuThreadJoin();
835 INFO_LOG(SYSTEM, "Joined emuthread");
836
837 graphicsContext->ThreadEnd();
838 graphicsContext->ShutdownFromRenderThread();
839 INFO_LOG(SYSTEM, "Graphics context now shut down from NativeApp_shutdown");
840 }
841
842 INFO_LOG(SYSTEM, "NativeApp.shutdown() -- begin");
843 if (renderer_inited) {
844 INFO_LOG(G3D, "Shutting down renderer");
845 // This will be from the wrong thread? :/
846 graphicsContext->Shutdown();
847 delete graphicsContext;
848 graphicsContext = nullptr;
849 renderer_inited = false;
850 } else {
851 INFO_LOG(G3D, "Not shutting down renderer - not initialized");
852 }
853
854 {
855 std::lock_guard<std::mutex> guard(renderLock);
856 inputBoxCallbacks.clear();
857 NativeShutdown();
858 VFSShutdown();
859 }
860
861 std::lock_guard<std::mutex> guard(frameCommandLock);
862 while (frameCommands.size())
863 frameCommands.pop();
864 INFO_LOG(SYSTEM, "NativeApp.shutdown() -- end");
865 }
866
867 // JavaEGL
Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env,jobject obj)868 extern "C" bool Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) {
869 // We should be running on the render thread here.
870 std::string errorMessage;
871 if (renderer_inited) {
872 // Would be really nice if we could get something on the GL thread immediately when shutting down.
873 INFO_LOG(G3D, "NativeApp.displayInit() restoring");
874 if (useCPUThread) {
875 EmuThreadStop("displayInit");
876 graphicsContext->BeginAndroidShutdown();
877 INFO_LOG(G3D, "BeginAndroidShutdown. Looping until emu thread done...");
878 // Skipping GL calls here because the old context is lost.
879 while (graphicsContext->ThreadFrame()) {
880 continue;
881 }
882 INFO_LOG(G3D, "Joining emu thread");
883 EmuThreadJoin();
884 } else {
885 NativeShutdownGraphics();
886 }
887 graphicsContext->ThreadEnd();
888 graphicsContext->ShutdownFromRenderThread();
889
890 INFO_LOG(G3D, "Shut down both threads. Now let's bring it up again!");
891
892 if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
893 SystemToast("Graphics initialization failed. Quitting.");
894 return false;
895 }
896
897 graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
898 host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback");
899 }, nullptr);
900
901 if (useCPUThread) {
902 EmuThreadStart();
903 } else {
904 if (!NativeInitGraphics(graphicsContext)) {
905 // Gonna be in a weird state here, not good.
906 SystemToast("Failed to initialize graphics.");
907 return false;
908 }
909 }
910
911 graphicsContext->ThreadStart();
912 INFO_LOG(G3D, "Restored.");
913 } else {
914 INFO_LOG(G3D, "NativeApp.displayInit() first time");
915 if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
916 SystemToast("Graphics initialization failed. Quitting.");
917 return false;
918 }
919
920 graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
921 host->NotifyUserMessage(details, 5.0, 0xFFFFFFFF, "error_callback");
922 }, nullptr);
923
924 graphicsContext->ThreadStart();
925 renderer_inited = true;
926 }
927 NativeMessageReceived("recreateviews", "");
928 return true;
929 }
930
recalculateDpi()931 static void recalculateDpi() {
932 g_dpi = display_dpi_x;
933 g_dpi_scale_x = 240.0f / display_dpi_x;
934 g_dpi_scale_y = 240.0f / display_dpi_y;
935 g_dpi_scale_real_x = g_dpi_scale_x;
936 g_dpi_scale_real_y = g_dpi_scale_y;
937
938 dp_xres = display_xres * g_dpi_scale_x;
939 dp_yres = display_yres * g_dpi_scale_y;
940
941 // Touch scaling is from display pixels to dp pixels.
942 // Wait, doesn't even make sense... this is equal to g_dpi_scale_x. TODO: Figure out what's going on!
943 dp_xscale = (float)dp_xres / (float)display_xres;
944 dp_yscale = (float)dp_yres / (float)display_yres;
945
946 pixel_in_dps_x = (float)pixel_xres / dp_xres;
947 pixel_in_dps_y = (float)pixel_yres / dp_yres;
948
949 INFO_LOG(G3D, "RecalcDPI: display_xres=%d display_yres=%d", display_xres, display_yres);
950 INFO_LOG(G3D, "RecalcDPI: g_dpi=%f g_dpi_scale_x=%f g_dpi_scale_y=%f", g_dpi, g_dpi_scale_x, g_dpi_scale_y);
951 INFO_LOG(G3D, "RecalcDPI: dp_xscale=%f dp_yscale=%f", dp_xscale, dp_yscale);
952 INFO_LOG(G3D, "RecalcDPI: dp_xres=%d dp_yres=%d", dp_xres, dp_yres);
953 INFO_LOG(G3D, "RecalcDPI: pixel_xres=%d pixel_yres=%d", pixel_xres, pixel_yres);
954 }
955
Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *,jclass,jint bufw,jint bufh,jint format)956 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint bufw, jint bufh, jint format) {
957 INFO_LOG(SYSTEM, "NativeApp.backbufferResize(%d x %d)", bufw, bufh);
958
959 bool new_size = pixel_xres != bufw || pixel_yres != bufh;
960 int old_w = pixel_xres;
961 int old_h = pixel_yres;
962 // pixel_*res is the backbuffer resolution.
963 pixel_xres = bufw;
964 pixel_yres = bufh;
965 backbuffer_format = format;
966
967 recalculateDpi();
968
969 if (new_size) {
970 INFO_LOG(G3D, "Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h);
971 NativeResized();
972 } else {
973 INFO_LOG(G3D, "NativeApp::backbufferResize: Size didn't change.");
974 }
975 }
976
System_InputBoxGetString(const std::string & title,const std::string & defaultValue,std::function<void (bool,const std::string &)> cb)977 void System_InputBoxGetString(const std::string &title, const std::string &defaultValue, std::function<void(bool, const std::string &)> cb) {
978 int seq = inputBoxSequence++;
979 inputBoxCallbacks[seq] = cb;
980
981 std::string serialized = StringFromFormat("%d:@:%s:@:%s", seq, title.c_str(), defaultValue.c_str());
982 System_SendMessage("inputbox", serialized.c_str());
983 }
984
Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv * env,jclass,jstring jseqID,jboolean result,jstring jvalue)985 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendInputBox(JNIEnv *env, jclass, jstring jseqID, jboolean result, jstring jvalue) {
986 std::string seqID = GetJavaString(env, jseqID);
987 std::string value = GetJavaString(env, jvalue);
988
989 static std::string lastSeqID = "";
990 if (lastSeqID == seqID) {
991 // We send this on dismiss, so twice in many cases.
992 DEBUG_LOG(SYSTEM, "Ignoring duplicate sendInputBox");
993 return;
994 }
995 lastSeqID = seqID;
996
997 int seq = 0;
998 if (!TryParse(seqID, &seq)) {
999 ERROR_LOG(SYSTEM, "Invalid inputbox seqID value: %s", seqID.c_str());
1000 return;
1001 }
1002
1003 auto entry = inputBoxCallbacks.find(seq);
1004 if (entry == inputBoxCallbacks.end()) {
1005 ERROR_LOG(SYSTEM, "Did not find inputbox callback for %s, shutdown?", seqID.c_str());
1006 return;
1007 }
1008
1009 NativeInputBoxReceived(entry->second, result, value);
1010 }
1011
LockedNativeUpdateRender()1012 void LockedNativeUpdateRender() {
1013 std::lock_guard<std::mutex> renderGuard(renderLock);
1014 NativeUpdate();
1015 NativeRender(graphicsContext);
1016 }
1017
UpdateRunLoopAndroid(JNIEnv * env)1018 void UpdateRunLoopAndroid(JNIEnv *env) {
1019 LockedNativeUpdateRender();
1020
1021 std::lock_guard<std::mutex> guard(frameCommandLock);
1022 if (!nativeActivity) {
1023 while (!frameCommands.empty())
1024 frameCommands.pop();
1025 return;
1026 }
1027 // Still under lock here.
1028 ProcessFrameCommands(env);
1029 }
1030
Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv * env,jobject obj)1031 extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
1032 static bool hasSetThreadName = false;
1033 if (!hasSetThreadName) {
1034 hasSetThreadName = true;
1035 SetCurrentThreadName("AndroidRender");
1036 }
1037
1038 if (useCPUThread) {
1039 // This is the "GPU thread".
1040 if (graphicsContext)
1041 graphicsContext->ThreadFrame();
1042 } else {
1043 UpdateRunLoopAndroid(env);
1044 }
1045 }
1046
System_AskForPermission(SystemPermission permission)1047 void System_AskForPermission(SystemPermission permission) {
1048 switch (permission) {
1049 case SYSTEM_PERMISSION_STORAGE:
1050 PushCommand("ask_permission", "storage");
1051 break;
1052 }
1053 }
1054
System_GetPermissionStatus(SystemPermission permission)1055 PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
1056 if (androidVersion < 23) {
1057 return PERMISSION_STATUS_GRANTED;
1058 } else {
1059 return permissions[permission];
1060 }
1061 }
1062
Java_org_ppsspp_ppsspp_NativeApp_touch(JNIEnv *,jclass,float x,float y,int code,int pointerId)1063 extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch
1064 (JNIEnv *, jclass, float x, float y, int code, int pointerId) {
1065
1066 float scaledX = x * dp_xscale;
1067 float scaledY = y * dp_yscale;
1068
1069 TouchInput touch;
1070 touch.id = pointerId;
1071 touch.x = scaledX;
1072 touch.y = scaledY;
1073 touch.flags = code;
1074
1075 bool retval = NativeTouch(touch);
1076 return retval;
1077 }
1078
Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *,jclass,jint deviceId,jint key,jboolean isRepeat)1079 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *, jclass, jint deviceId, jint key, jboolean isRepeat) {
1080 KeyInput keyInput;
1081 keyInput.deviceId = deviceId;
1082 keyInput.keyCode = key;
1083 keyInput.flags = KEY_DOWN;
1084 if (isRepeat) {
1085 keyInput.flags |= KEY_IS_REPEAT;
1086 }
1087 return NativeKey(keyInput);
1088 }
1089
Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *,jclass,jint deviceId,jint key)1090 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *, jclass, jint deviceId, jint key) {
1091 KeyInput keyInput;
1092 keyInput.deviceId = deviceId;
1093 keyInput.keyCode = key;
1094 keyInput.flags = KEY_UP;
1095 return NativeKey(keyInput);
1096 }
1097
Java_org_ppsspp_ppsspp_NativeApp_beginJoystickEvent(JNIEnv * env,jclass)1098 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_beginJoystickEvent(
1099 JNIEnv *env, jclass) {
1100 // mutex lock?
1101 }
1102
Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(JNIEnv * env,jclass,jint deviceId,jint axisId,jfloat value)1103 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
1104 JNIEnv *env, jclass, jint deviceId, jint axisId, jfloat value) {
1105 if (!renderer_inited)
1106 return false;
1107 switch (axisId) {
1108 case JOYSTICK_AXIS_X:
1109 left_joystick_x_async = value;
1110 break;
1111 case JOYSTICK_AXIS_Y:
1112 left_joystick_y_async = -value;
1113 break;
1114 case JOYSTICK_AXIS_Z:
1115 right_joystick_x_async = value;
1116 break;
1117 case JOYSTICK_AXIS_RZ:
1118 right_joystick_y_async = -value;
1119 break;
1120 case JOYSTICK_AXIS_HAT_X:
1121 hat_joystick_x_async = value;
1122 break;
1123 case JOYSTICK_AXIS_HAT_Y:
1124 hat_joystick_y_async = -value;
1125 break;
1126 }
1127
1128 AxisInput axis;
1129 axis.axisId = axisId;
1130 axis.deviceId = deviceId;
1131 axis.value = value;
1132
1133 return NativeAxis(axis);
1134 }
1135
Java_org_ppsspp_ppsspp_NativeApp_endJoystickEvent(JNIEnv * env,jclass)1136 extern "C" void Java_org_ppsspp_ppsspp_NativeApp_endJoystickEvent(
1137 JNIEnv *env, jclass) {
1138 // mutex unlock?
1139 }
1140
1141
Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(JNIEnv * env,jclass,jint stick,jfloat x,jfloat y)1142 extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
1143 JNIEnv *env, jclass, jint stick, jfloat x, jfloat y) {
1144 // TODO: Support mousewheel for android
1145 return true;
1146 }
1147
Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *,jclass,float x,float y,float z)1148 extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *, jclass, float x, float y, float z) {
1149 if (!renderer_inited)
1150 return false;
1151
1152 AxisInput axis;
1153 axis.deviceId = DEVICE_ID_ACCELEROMETER;
1154 axis.flags = 0;
1155
1156 axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_X;
1157 axis.value = x;
1158 bool retvalX = NativeAxis(axis);
1159
1160 axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Y;
1161 axis.value = y;
1162 bool retvalY = NativeAxis(axis);
1163
1164 axis.axisId = JOYSTICK_AXIS_ACCELEROMETER_Z;
1165 axis.value = z;
1166 bool retvalZ = NativeAxis(axis);
1167
1168 return retvalX || retvalY || retvalZ;
1169 }
1170
Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv * env,jclass,jstring message,jstring param)1171 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessage(JNIEnv *env, jclass, jstring message, jstring param) {
1172 std::string msg = GetJavaString(env, message);
1173 std::string prm = GetJavaString(env, param);
1174
1175 // Some messages are caught by app-android.
1176 if (msg == "moga") {
1177 mogaVersion = prm;
1178 } else if (msg == "permission_pending") {
1179 INFO_LOG(SYSTEM, "STORAGE PERMISSION: PENDING");
1180 // TODO: Add support for other permissions
1181 permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
1182 } else if (msg == "permission_denied") {
1183 INFO_LOG(SYSTEM, "STORAGE PERMISSION: DENIED");
1184 permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
1185 } else if (msg == "permission_granted") {
1186 INFO_LOG(SYSTEM, "STORAGE PERMISSION: GRANTED");
1187 permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
1188 } else if (msg == "sustained_perf_supported") {
1189 sustainedPerfSupported = true;
1190 } else if (msg == "safe_insets") {
1191 INFO_LOG(SYSTEM, "Got insets: %s", prm.c_str());
1192 // We don't bother with supporting exact rectangular regions. Safe insets are good enough.
1193 int left, right, top, bottom;
1194 if (4 == sscanf(prm.c_str(), "%d:%d:%d:%d", &left, &right, &top, &bottom)) {
1195 g_safeInsetLeft = (float)left * g_dpi_scale_x;
1196 g_safeInsetRight = (float)right * g_dpi_scale_x;
1197 g_safeInsetTop = (float)top * g_dpi_scale_y;
1198 g_safeInsetBottom = (float)bottom * g_dpi_scale_y;
1199 }
1200 }
1201
1202 // Ensures that the receiver can handle it on a sensible thread.
1203 NativeMessageReceived(msg.c_str(), prm.c_str());
1204 }
1205
Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv * env,jobject obj)1206 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeActivity_exitEGLRenderLoop(JNIEnv *env, jobject obj) {
1207 if (!renderLoopRunning) {
1208 ERROR_LOG(SYSTEM, "Render loop already exited");
1209 return;
1210 }
1211 exitRenderLoop = true;
1212 while (renderLoopRunning) {
1213 sleep_ms(10);
1214 }
1215 }
1216
correctRatio(int & sz_x,int & sz_y,float scale)1217 void correctRatio(int &sz_x, int &sz_y, float scale) {
1218 float x = (float)sz_x;
1219 float y = (float)sz_y;
1220 float ratio = x / y;
1221 INFO_LOG(G3D, "CorrectRatio: Considering size: %0.2f/%0.2f=%0.2f for scale %f", x, y, ratio, scale);
1222 float targetRatio;
1223
1224 // Try to get the longest dimension to match scale*PSP resolution.
1225 if (x >= y) {
1226 targetRatio = 480.0f / 272.0f;
1227 x = 480.f * scale;
1228 y = 272.f * scale;
1229 } else {
1230 targetRatio = 272.0f / 480.0f;
1231 x = 272.0f * scale;
1232 y = 480.0f * scale;
1233 }
1234
1235 float correction = targetRatio / ratio;
1236 INFO_LOG(G3D, "Target ratio: %0.2f ratio: %0.2f correction: %0.2f", targetRatio, ratio, correction);
1237 if (ratio < targetRatio) {
1238 y *= correction;
1239 } else {
1240 x /= correction;
1241 }
1242
1243 sz_x = x;
1244 sz_y = y;
1245 INFO_LOG(G3D, "Corrected ratio: %dx%d", sz_x, sz_y);
1246 }
1247
getDesiredBackbufferSize(int & sz_x,int & sz_y)1248 void getDesiredBackbufferSize(int &sz_x, int &sz_y) {
1249 sz_x = display_xres;
1250 sz_y = display_yres;
1251 std::string config = NativeQueryConfig("hwScale");
1252 int scale;
1253 if (1 == sscanf(config.c_str(), "%d", &scale) && scale > 0) {
1254 correctRatio(sz_x, sz_y, scale);
1255 } else {
1256 sz_x = 0;
1257 sz_y = 0;
1258 }
1259 }
1260
Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *,jclass,jint xres,jint yres,jint dpi,jfloat refreshRate)1261 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *, jclass, jint xres, jint yres, jint dpi, jfloat refreshRate) {
1262 INFO_LOG(G3D, "NativeApp.setDisplayParameters(%d x %d, dpi=%d, refresh=%0.2f)", xres, yres, dpi, refreshRate);
1263 bool changed = false;
1264 changed = changed || display_xres != xres || display_yres != yres;
1265 changed = changed || display_dpi_x != dpi || display_dpi_y != dpi;
1266 changed = changed || display_hz != refreshRate;
1267
1268 if (changed) {
1269 display_xres = xres;
1270 display_yres = yres;
1271 display_dpi_x = dpi;
1272 display_dpi_y = dpi;
1273 display_hz = refreshRate;
1274
1275 recalculateDpi();
1276 NativeResized();
1277 }
1278 }
1279
Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions()1280 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions() {
1281 getDesiredBackbufferSize(desiredBackbufferSizeX, desiredBackbufferSizeY);
1282 }
1283
Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *,jclass)1284 extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *, jclass) {
1285 return desiredBackbufferSizeX;
1286 }
1287
Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *,jclass)1288 extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *, jclass) {
1289 return desiredBackbufferSizeY;
1290 }
1291
__cameraGetDeviceList()1292 std::vector<std::string> __cameraGetDeviceList() {
1293 jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper");
1294 jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;");
1295 jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod);
1296 jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList");
1297 jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I");
1298 jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
1299
1300 jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize);
1301 std::vector<std::string> deviceListVector;
1302
1303 for (int i=0; i < arrayListObjectLen; i++) {
1304 jstring dev = static_cast<jstring>(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i));
1305 const char* cdev = getEnv()->GetStringUTFChars(dev, nullptr);
1306 deviceListVector.push_back(cdev);
1307 getEnv()->ReleaseStringUTFChars(dev, cdev);
1308 getEnv()->DeleteLocalRef(dev);
1309 }
1310 return deviceListVector;
1311 }
1312
Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *,jclass)1313 extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) {
1314 int cameraId = 0;
1315 sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId);
1316 return cameraId;
1317 }
1318
Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *,jclass,jlong time,jfloat hdop,jfloat latitude,jfloat longitude,jfloat altitude,jfloat speed,jfloat bearing)1319 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass,
1320 jlong time, jfloat hdop, jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing) {
1321 GPS::setGpsData(time, hdop, latitude, longitude, altitude, speed, bearing);
1322 }
1323
Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *,jclass,jshort index,jshort id,jshort elevation,jshort azimuth,jshort snr,jshort good)1324 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *, jclass,
1325 jshort index, jshort id, jshort elevation, jshort azimuth, jshort snr, jshort good) {
1326 GPS::setSatInfo(index, id, elevation, azimuth, snr, good);
1327 }
1328
Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv * env,jclass,jbyteArray image)1329 extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass,
1330 jbyteArray image) {
1331
1332 if (image != NULL) {
1333 jlong size = env->GetArrayLength(image);
1334 jbyte* buffer = env->GetByteArrayElements(image, NULL);
1335 Camera::pushCameraImage(size, (unsigned char *)buffer);
1336 env->ReleaseByteArrayElements(image, buffer, JNI_ABORT);
1337 }
1338 }
1339
1340 // Call this under frameCommandLock.
ProcessFrameCommands(JNIEnv * env)1341 static void ProcessFrameCommands(JNIEnv *env) {
1342 while (!frameCommands.empty()) {
1343 FrameCommand frameCmd;
1344 frameCmd = frameCommands.front();
1345 frameCommands.pop();
1346
1347 INFO_LOG(SYSTEM, "frameCommand '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
1348
1349 jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
1350 jstring param = env->NewStringUTF(frameCmd.params.c_str());
1351 env->CallVoidMethod(nativeActivity, postCommand, cmd, param);
1352 env->DeleteLocalRef(cmd);
1353 env->DeleteLocalRef(param);
1354 }
1355 }
1356
Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv * env,jobject obj,jobject _surf)1357 extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(JNIEnv *env, jobject obj, jobject _surf) {
1358 if (!graphicsContext) {
1359 ERROR_LOG(G3D, "runEGLRenderLoop: Tried to enter without a created graphics context.");
1360 return false;
1361 }
1362
1363 // Needed for Vulkan, even if we're not using the old EGL path.
1364
1365 exitRenderLoop = false;
1366 // This is up here to prevent race conditions, in case we pause during init.
1367 renderLoopRunning = true;
1368
1369 ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;
1370
1371 WARN_LOG(G3D, "runEGLRenderLoop. display_xres=%d display_yres=%d", display_xres, display_yres);
1372
1373 if (wnd == nullptr) {
1374 ERROR_LOG(G3D, "Error: Surface is null.");
1375 renderLoopRunning = false;
1376 return false;
1377 }
1378
1379 auto tryInit = [&]() {
1380 if (graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
1381 return true;
1382 } else {
1383 ERROR_LOG(G3D, "Failed to initialize graphics context.");
1384 SystemToast("Failed to initialize graphics context.");
1385 return false;
1386 }
1387 };
1388
1389 bool initSuccess = tryInit();
1390 if (!initSuccess) {
1391 if (!exitRenderLoop && g_Config.iGPUBackend == (int)GPUBackend::VULKAN) {
1392 INFO_LOG(G3D, "Trying again, this time with OpenGL.");
1393 SetGPUBackend(GPUBackend::OPENGL);
1394 g_Config.iGPUBackend = (int)GetGPUBackend();
1395
1396 // If we were still supporting EGL for GL, we'd retry here:
1397 //initSuccess = tryInit();
1398 }
1399
1400 if (!initSuccess) {
1401 delete graphicsContext;
1402 graphicsContext = nullptr;
1403 renderLoopRunning = false;
1404 return false;
1405 }
1406 }
1407
1408 if (!exitRenderLoop) {
1409 if (!useCPUThread) {
1410 if (!NativeInitGraphics(graphicsContext)) {
1411 ERROR_LOG(G3D, "Failed to initialize graphics.");
1412 // Gonna be in a weird state here..
1413 }
1414 }
1415 graphicsContext->ThreadStart();
1416 renderer_inited = true;
1417 }
1418
1419 if (!exitRenderLoop) {
1420 static bool hasSetThreadName = false;
1421 if (!hasSetThreadName) {
1422 hasSetThreadName = true;
1423 SetCurrentThreadName("AndroidRender");
1424 }
1425 }
1426
1427 if (useCPUThread) {
1428 ERROR_LOG(SYSTEM, "Running graphics loop");
1429 while (!exitRenderLoop) {
1430 // This is the "GPU thread".
1431 graphicsContext->ThreadFrame();
1432 graphicsContext->SwapBuffers();
1433 }
1434 } else {
1435 while (!exitRenderLoop) {
1436 LockedNativeUpdateRender();
1437 graphicsContext->SwapBuffers();
1438
1439 ProcessFrameCommands(env);
1440 }
1441 }
1442
1443 INFO_LOG(G3D, "Leaving EGL/Vulkan render loop.");
1444
1445 if (useCPUThread) {
1446 EmuThreadStop("exitrenderloop");
1447 while (graphicsContext->ThreadFrame()) {
1448 continue;
1449 }
1450 EmuThreadJoin();
1451 } else {
1452 NativeShutdownGraphics();
1453 }
1454 renderer_inited = false;
1455 graphicsContext->ThreadEnd();
1456
1457 // Shut the graphics context down to the same state it was in when we entered the render thread.
1458 INFO_LOG(G3D, "Shutting down graphics context from render thread...");
1459 graphicsContext->ShutdownFromRenderThread();
1460 renderLoopRunning = false;
1461 WARN_LOG(G3D, "Render loop function exited.");
1462 return true;
1463 }
1464
1465 // NOTE: This is defunct and not working, due to how the Android storage functions currently require
1466 // a PpssppActivity specifically and we don't have one here.
Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv * env,jclass,jstring jpath)1467 extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv *env, jclass, jstring jpath) {
1468 bool teardownThreadManager = false;
1469 if (!g_threadManager.IsInitialized()) {
1470 INFO_LOG(SYSTEM, "No thread manager - initializing one");
1471 // Need a thread manager.
1472 teardownThreadManager = true;
1473 g_threadManager.Init(1, 1);
1474 }
1475
1476 Path path = Path(GetJavaString(env, jpath));
1477
1478 INFO_LOG(SYSTEM, "queryGameName(%s)", path.c_str());
1479
1480 std::string result = "";
1481
1482 GameInfoCache *cache = new GameInfoCache();
1483 std::shared_ptr<GameInfo> info = cache->GetInfo(nullptr, path, 0);
1484 // Wait until it's done: this is synchronous, unfortunately.
1485 if (info) {
1486 INFO_LOG(SYSTEM, "GetInfo successful, waiting");
1487 cache->WaitUntilDone(info);
1488 INFO_LOG(SYSTEM, "Done waiting");
1489 if (info->fileType != IdentifiedFileType::UNKNOWN) {
1490 result = info->GetTitle();
1491
1492 // Pretty arbitrary, but the home screen will often truncate titles.
1493 // Let's remove "The " from names since it's common in English titles.
1494 if (result.length() > strlen("The ") && startsWithNoCase(result, "The ")) {
1495 result = result.substr(strlen("The "));
1496 }
1497
1498 INFO_LOG(SYSTEM, "queryGameName: Got '%s'", result.c_str());
1499 } else {
1500 INFO_LOG(SYSTEM, "queryGameName: Filetype unknown");
1501 }
1502 } else {
1503 INFO_LOG(SYSTEM, "No info from cache");
1504 }
1505 delete cache;
1506
1507 if (teardownThreadManager) {
1508 g_threadManager.Teardown();
1509 }
1510
1511 return env->NewStringUTF(result.c_str());
1512 }
1513