Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/android/jni/app-android.cpp
5661 views
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 "ppsspp_config.h"
8
9
#if PPSSPP_PLATFORM(ANDROID)
10
11
#include <cstdlib>
12
#include <cstdint>
13
14
#include <sstream>
15
#include <queue>
16
#include <mutex>
17
#include <thread>
18
#include <atomic>
19
20
#ifndef _MSC_VER
21
22
#include <jni.h>
23
#include <android/native_window_jni.h>
24
#include <android/log.h>
25
26
#include "Common/Render/Text/draw_text_android.h"
27
28
#elif !defined(JNIEXPORT)
29
// Just for better highlighting in MSVC if opening this file.
30
// Not having types makes it get confused and say everything is wrong.
31
struct JavaVM;
32
typedef void *jmethodID;
33
typedef void *jfieldID;
34
35
typedef uint8_t jboolean;
36
typedef int8_t jbyte;
37
typedef int16_t jshort;
38
typedef int32_t jint;
39
typedef int64_t jlong;
40
typedef jint jsize;
41
typedef float jfloat;
42
typedef double jdouble;
43
44
class _jobject {};
45
class _jclass : public _jobject {};
46
typedef _jobject *jobject;
47
typedef _jclass *jclass;
48
typedef jobject jstring;
49
typedef jobject jbyteArray;
50
typedef jobject jintArray;
51
typedef jobject jfloatArray;
52
53
struct JNIEnv {};
54
55
#define JNIEXPORT
56
#define JNICALL
57
// Just a random value to make MSVC highlighting happy.
58
#define JNI_VERSION_1_6 16
59
#endif
60
61
#include "Common/Log.h"
62
#include "Common/LogReporting.h"
63
64
#include "Common/Net/Resolve.h"
65
#include "Common/Net/URL.h"
66
#include "android/jni/AndroidAudio.h"
67
#include "Common/GPU/OpenGL/GLCommon.h"
68
#include "Common/GPU/OpenGL/GLFeatures.h"
69
70
#include "Common/System/Display.h"
71
#include "Common/System/NativeApp.h"
72
#include "Common/System/System.h"
73
#include "Common/System/OSD.h"
74
#include "Common/System/Request.h"
75
#include "Common/Thread/ThreadUtil.h"
76
#include "Common/File/Path.h"
77
#include "Common/File/DirListing.h"
78
#include "Common/File/VFS/VFS.h"
79
#include "Common/File/VFS/DirectoryReader.h"
80
#include "Common/File/VFS/ZipFileReader.h"
81
#include "Common/File/AndroidStorage.h"
82
#include "Common/Input/InputState.h"
83
#include "Common/Input/KeyCodes.h"
84
#include "Common/Profiler/Profiler.h"
85
#include "Common/Math/math_util.h"
86
#include "Common/Data/Text/Parsers.h"
87
#include "Common/VR/PPSSPPVR.h"
88
#include "Common/GPU/Vulkan/VulkanLoader.h"
89
90
#include "Common/GraphicsContext.h"
91
#include "Common/StringUtils.h"
92
#include "Common/TimeUtil.h"
93
94
#include "AndroidGraphicsContext.h"
95
#include "AndroidVulkanContext.h"
96
#include "AndroidJavaGLContext.h"
97
98
#include "Core/Config.h"
99
#include "Core/ConfigValues.h"
100
#include "Core/Loaders.h"
101
#include "Core/FileLoaders/LocalFileLoader.h"
102
#include "Core/KeyMap.h"
103
#include "Core/System.h"
104
#include "Core/HLE/sceUsbCam.h"
105
#include "Core/HLE/sceUsbGps.h"
106
#include "Common/CPUDetect.h"
107
#include "UI/GameInfoCache.h"
108
109
#include "app-android.h"
110
111
bool useCPUThread = true;
112
113
enum class EmuThreadState {
114
DISABLED,
115
START_REQUESTED,
116
RUNNING,
117
QUIT_REQUESTED,
118
STOPPED,
119
};
120
121
// OpenGL emu thread
122
static std::thread emuThread;
123
static std::atomic<int> emuThreadState((int)EmuThreadState::DISABLED);
124
125
AndroidAudioState *g_audioState;
126
127
struct FrameCommand {
128
FrameCommand() {}
129
FrameCommand(std::string cmd, std::string prm) : command(cmd), params(prm) {}
130
131
std::string command;
132
std::string params;
133
};
134
135
static std::mutex frameCommandLock;
136
static std::queue<FrameCommand> frameCommands;
137
138
static std::string systemName;
139
static std::string langRegion;
140
static std::string mogaVersion;
141
static std::string boardName;
142
143
std::string g_externalDir; // Original external dir (root of Android storage).
144
std::string g_extFilesDir; // App private external dir.
145
std::string g_nativeLibDir; // App native library dir
146
147
static std::vector<std::string> g_additionalStorageDirs;
148
149
static int optimalFramesPerBuffer = 0;
150
static int optimalSampleRate = 0;
151
static int sampleRate = 0;
152
static int framesPerBuffer = 0;
153
static int androidVersion;
154
static int deviceType;
155
156
// This is the ACTUAL display size, not the hardware scaled display size.
157
static int display_xres;
158
static int display_yres;
159
static int display_dpi;
160
static float display_scale_x; // Scale factor due to backbuffer scaling
161
static float display_scale_y;
162
163
static int backbuffer_format; // Android PixelFormat enum
164
165
static int desiredBackbufferSizeX;
166
static int desiredBackbufferSizeY;
167
168
// Cache the class loader so we can use it from native threads. Required for TextAndroid.
169
extern JavaVM *gJvm;
170
171
static jobject gClassLoader;
172
static jmethodID gFindClassMethod;
173
174
static float g_safeInsetLeft = 0.0;
175
static float g_safeInsetRight = 0.0;
176
static float g_safeInsetTop = 0.0;
177
static float g_safeInsetBottom = 0.0;
178
179
static jmethodID postCommand;
180
static jmethodID getDebugString;
181
static jmethodID getNativeCrashHistory;
182
183
static jobject ppssppActivity;
184
185
static std::atomic<bool> exitRenderLoop;
186
static std::atomic<bool> renderLoopRunning;
187
static bool renderer_inited = false;
188
189
static bool sustainedPerfSupported = false;
190
191
static std::map<SystemPermission, PermissionStatus> permissions;
192
193
static AndroidGraphicsContext *graphicsContext;
194
195
#define MessageBox(a, b, c, d) __android_log_print(ANDROID_LOG_INFO, APP_NAME, "%s %s", (b), (c));
196
197
#if PPSSPP_ARCH(ARMV7)
198
// Old Android workaround
199
extern "C" {
200
int utimensat(int fd, const char *path, const struct timespec times[2]) {
201
return -1;
202
}
203
}
204
#endif
205
206
static void ProcessFrameCommands(JNIEnv *env);
207
208
JNIEnv* getEnv() {
209
JNIEnv *env;
210
int status = gJvm->GetEnv((void**)&env, JNI_VERSION_1_6);
211
_assert_msg_(status >= 0, "'%s': Can only call getEnv if you've attached the thread already!", GetCurrentThreadName());
212
return env;
213
}
214
215
jclass findClass(const char* name) {
216
return static_cast<jclass>(getEnv()->CallObjectMethod(gClassLoader, gFindClassMethod, getEnv()->NewStringUTF(name)));
217
}
218
219
void Android_AttachThreadToJNI() {
220
JNIEnv *env;
221
int status = gJvm->GetEnv((void **)&env, JNI_VERSION_1_6);
222
if (status < 0) {
223
DEBUG_LOG(Log::System, "Attaching thread '%s' (not already attached) to JNI.", GetCurrentThreadName());
224
JavaVMAttachArgs args{};
225
args.version = JNI_VERSION_1_6;
226
args.name = GetCurrentThreadName();
227
status = gJvm->AttachCurrentThread(&env, &args);
228
229
if (status < 0) {
230
// bad, but what can we do other than report..
231
ERROR_LOG(Log::System, "Failed to attach thread %s to JNI.", GetCurrentThreadName());
232
}
233
} else {
234
WARN_LOG(Log::System, "Thread %s was already attached to JNI.", GetCurrentThreadName());
235
}
236
}
237
238
void Android_DetachThreadFromJNI() {
239
if (gJvm->DetachCurrentThread() == JNI_OK) {
240
DEBUG_LOG(Log::System, "Detached thread from JNI: '%s'", GetCurrentThreadName());
241
} else {
242
WARN_LOG(Log::System, "Failed to detach thread '%s' from JNI - never attached?", GetCurrentThreadName());
243
}
244
}
245
246
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) {
247
INFO_LOG(Log::System, "JNI_OnLoad");
248
gJvm = pjvm; // cache the JavaVM pointer
249
auto env = getEnv();
250
//replace with one of your classes in the line below
251
auto randomClass = env->FindClass("org/ppsspp/ppsspp/PpssppActivity");
252
jclass classClass = env->GetObjectClass(randomClass);
253
auto classLoaderClass = env->FindClass("java/lang/ClassLoader");
254
auto getClassLoaderMethod = env->GetMethodID(classClass, "getClassLoader",
255
"()Ljava/lang/ClassLoader;");
256
gClassLoader = env->NewGlobalRef(env->CallObjectMethod(randomClass, getClassLoaderMethod));
257
gFindClassMethod = env->GetMethodID(classLoaderClass, "findClass",
258
"(Ljava/lang/String;)Ljava/lang/Class;");
259
260
RegisterAttachDetach(&Android_AttachThreadToJNI, &Android_DetachThreadFromJNI);
261
262
TimeInit();
263
return JNI_VERSION_1_6;
264
}
265
266
// Only used in OpenGL mode.
267
static void EmuThreadFunc() {
268
SetCurrentThreadName("Entering EmuThread");
269
270
// Name the thread in the JVM, because why not (might result in better debug output in Play Console).
271
// TODO: Do something clever with getEnv() and stored names from SetCurrentThreadName?
272
JNIEnv *env;
273
JavaVMAttachArgs args{};
274
args.version = JNI_VERSION_1_6;
275
args.name = "EmuThread";
276
gJvm->AttachCurrentThread(&env, &args);
277
278
INFO_LOG(Log::System, "Entering emu thread");
279
280
// Wait for render loop to get started.
281
INFO_LOG(Log::System, "Runloop: Waiting for displayInit...");
282
while (!graphicsContext || graphicsContext->GetState() == GraphicsContextState::PENDING) {
283
sleep_ms(5, "graphics-poll");
284
}
285
286
// Check the state of the graphics context before we try to feed it into NativeInitGraphics.
287
if (graphicsContext->GetState() != GraphicsContextState::INITIALIZED) {
288
ERROR_LOG(Log::G3D, "Failed to initialize the graphics context! %d", (int)graphicsContext->GetState());
289
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
290
gJvm->DetachCurrentThread();
291
return;
292
}
293
294
if (!NativeInitGraphics(graphicsContext)) {
295
_assert_msg_(false, "NativeInitGraphics failed, might as well bail");
296
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
297
gJvm->DetachCurrentThread();
298
return;
299
}
300
301
INFO_LOG(Log::System, "Graphics initialized. Entering loop.");
302
303
// There's no real requirement that NativeInit happen on this thread.
304
// We just call the update/render loop here.
305
emuThreadState = (int)EmuThreadState::RUNNING;
306
while (emuThreadState != (int)EmuThreadState::QUIT_REQUESTED) {
307
NativeFrame(graphicsContext);
308
309
std::lock_guard<std::mutex> guard(frameCommandLock);
310
if (!ppssppActivity) {
311
ERROR_LOG(Log::System, "No activity, clearing commands");
312
while (!frameCommands.empty())
313
frameCommands.pop();
314
return;
315
}
316
// Still under lock here.
317
ProcessFrameCommands(env);
318
}
319
320
INFO_LOG(Log::System, "emuThreadState was set to QUIT_REQUESTED, left EmuThreadFunc loop. Setting state to STOPPED.");
321
emuThreadState = (int)EmuThreadState::STOPPED;
322
323
NativeShutdownGraphics();
324
325
gJvm->DetachCurrentThread();
326
INFO_LOG(Log::System, "Leaving EmuThread");
327
}
328
329
static void EmuThreadStart() {
330
INFO_LOG(Log::System, "EmuThreadStart");
331
emuThreadState = (int)EmuThreadState::START_REQUESTED;
332
emuThread = std::thread(&EmuThreadFunc);
333
}
334
335
// Call EmuThreadStop first, then keep running the GPU (or eat commands)
336
// as long as emuThreadState isn't STOPPED and/or there are still things queued up.
337
// Only after that, call EmuThreadJoin.
338
static void EmuThreadStop(const char *caller) {
339
INFO_LOG(Log::System, "EmuThreadStop - stopping (%s)...", caller);
340
emuThreadState = (int)EmuThreadState::QUIT_REQUESTED;
341
}
342
343
static void EmuThreadJoin() {
344
emuThread.join();
345
emuThread = std::thread();
346
INFO_LOG(Log::System, "EmuThreadJoin - joined");
347
}
348
349
static void PushCommand(std::string_view cmd, std::string_view param) {
350
std::lock_guard<std::mutex> guard(frameCommandLock);
351
frameCommands.emplace(std::string(cmd), std::string(param));
352
}
353
354
// Android implementation of callbacks to the Java part of the app
355
void System_Toast(std::string_view text) {
356
PushCommand("toast", std::string(text));
357
}
358
359
void System_ShowKeyboard() {
360
PushCommand("showKeyboard", "");
361
}
362
363
void System_Vibrate(int length_ms) {
364
char temp[32];
365
snprintf(temp, sizeof(temp), "%d", length_ms);
366
PushCommand("vibrate", temp);
367
}
368
369
void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) {
370
switch (urlType) {
371
case LaunchUrlType::BROWSER_URL: PushCommand("launchBrowser", url); break;
372
case LaunchUrlType::MARKET_URL: PushCommand("launchMarket", url); break;
373
case LaunchUrlType::EMAIL_ADDRESS: PushCommand("launchEmail", url); break;
374
}
375
}
376
377
std::string System_GetProperty(SystemProperty prop) {
378
switch (prop) {
379
case SYSPROP_NAME:
380
return systemName;
381
case SYSPROP_LANGREGION: // "en_US"
382
return langRegion;
383
case SYSPROP_MOGA_VERSION:
384
return mogaVersion;
385
case SYSPROP_BOARDNAME:
386
return boardName;
387
case SYSPROP_BUILD_VERSION:
388
return PPSSPP_GIT_VERSION;
389
default:
390
return "";
391
}
392
}
393
394
std::vector<std::string> System_GetPropertyStringVec(SystemProperty prop) {
395
switch (prop) {
396
case SYSPROP_ADDITIONAL_STORAGE_DIRS:
397
return g_additionalStorageDirs;
398
399
case SYSPROP_TEMP_DIRS:
400
default:
401
return {};
402
}
403
}
404
405
int64_t System_GetPropertyInt(SystemProperty prop) {
406
switch (prop) {
407
case SYSPROP_SYSTEMVERSION:
408
return androidVersion;
409
case SYSPROP_DEVICE_TYPE:
410
return deviceType;
411
case SYSPROP_DISPLAY_XRES:
412
return display_xres;
413
case SYSPROP_DISPLAY_YRES:
414
return display_yres;
415
case SYSPROP_DISPLAY_DPI:
416
return display_dpi;
417
case SYSPROP_AUDIO_SAMPLE_RATE:
418
return sampleRate;
419
case SYSPROP_AUDIO_FRAMES_PER_BUFFER:
420
return framesPerBuffer;
421
case SYSPROP_AUDIO_OPTIMAL_SAMPLE_RATE:
422
return optimalSampleRate;
423
case SYSPROP_AUDIO_OPTIMAL_FRAMES_PER_BUFFER:
424
return optimalFramesPerBuffer;
425
default:
426
return -1;
427
}
428
}
429
430
float System_GetPropertyFloat(SystemProperty prop) {
431
switch (prop) {
432
case SYSPROP_DISPLAY_REFRESH_RATE:
433
return g_display.display_hz;
434
case SYSPROP_DISPLAY_SAFE_INSET_LEFT:
435
return g_safeInsetLeft * display_scale_x * g_display.dpi_scale_x;
436
case SYSPROP_DISPLAY_SAFE_INSET_RIGHT:
437
return g_safeInsetRight * display_scale_x * g_display.dpi_scale_x;
438
case SYSPROP_DISPLAY_SAFE_INSET_TOP:
439
return g_safeInsetTop * display_scale_y * g_display.dpi_scale_y;
440
case SYSPROP_DISPLAY_SAFE_INSET_BOTTOM:
441
return g_safeInsetBottom * display_scale_y * g_display.dpi_scale_y;
442
default:
443
return -1;
444
}
445
}
446
447
bool System_GetPropertyBool(SystemProperty prop) {
448
switch (prop) {
449
case SYSPROP_SUPPORTS_PERMISSIONS:
450
if (androidVersion < 23) {
451
// 6.0 Marshmallow introduced run time permissions.
452
return false;
453
} else {
454
// It gets a bit complicated here. If scoped storage enforcement is on,
455
// we also don't need to request permissions. We'll have the access we request
456
// on a per-folder basis.
457
return !System_GetPropertyBool(SYSPROP_ANDROID_SCOPED_STORAGE);
458
}
459
case SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE:
460
return sustainedPerfSupported; // 7.0 introduced sustained performance mode as an optional feature.
461
case SYSPROP_HAS_TEXT_INPUT_DIALOG:
462
return androidVersion >= 11; // honeycomb
463
case SYSPROP_HAS_TEXT_CLIPBOARD:
464
return true;
465
case SYSPROP_HAS_OPEN_DIRECTORY:
466
return false; // We have this implemented but it may or may not work depending on if a file explorer is installed.
467
case SYSPROP_HAS_ADDITIONAL_STORAGE:
468
return !g_additionalStorageDirs.empty();
469
case SYSPROP_HAS_BACK_BUTTON:
470
return true;
471
case SYSPROP_HAS_IMAGE_BROWSER:
472
return deviceType != DEVICE_TYPE_VR;
473
case SYSPROP_HAS_FILE_BROWSER:
474
// It's only really needed with scoped storage, but why not make it available
475
// as far back as possible - works just fine.
476
return (androidVersion >= 19) && (deviceType != DEVICE_TYPE_VR); // when ACTION_OPEN_DOCUMENT was added
477
case SYSPROP_HAS_FOLDER_BROWSER:
478
// Uses OPEN_DOCUMENT_TREE to let you select a folder.
479
// Doesn't actually mean it's usable though, in many early versions of Android
480
// this dialog is complete garbage and only lets you select subfolders of the Downloads folder.
481
return (androidVersion >= 21) && (deviceType != DEVICE_TYPE_VR); // when ACTION_OPEN_DOCUMENT_TREE was added
482
case SYSPROP_SUPPORTS_OPEN_FILE_IN_EDITOR:
483
return false; // Update if we add support in FileUtil.cpp: OpenFileInEditor
484
case SYSPROP_SUPPORTS_SHARE_TEXT:
485
return true;
486
case SYSPROP_APP_GOLD:
487
#ifdef GOLD
488
return true;
489
#else
490
return false;
491
#endif
492
case SYSPROP_USE_IAP:
493
// Not yet implemented, we use a separate gold app on Android
494
return false;
495
case SYSPROP_USE_APP_STORE:
496
return true;
497
case SYSPROP_CAN_JIT:
498
return true;
499
case SYSPROP_ANDROID_SCOPED_STORAGE:
500
// We turn this on for Android 30+ (11) now that when we target Android 11+.
501
// Along with adding:
502
// android:preserveLegacyExternalStorage="true"
503
// To the already requested:
504
// android:requestLegacyExternalStorage="true"
505
//
506
// This will cause Android 11+ to still behave like Android 10 until the app
507
// is manually uninstalled. We can detect this state with
508
// Android_IsExternalStoragePreservedLegacy(), but most of the app will just see
509
// that scoped storage enforcement is disabled in this case.
510
if (androidVersion >= 30) {
511
// Here we do a check to see if we ended up in the preserveLegacyExternalStorage path.
512
// That won't last if the user uninstalls/reinstalls though, but would preserve the user
513
// experience for simple upgrades so maybe let's support it.
514
return !Android_IsExternalStoragePreservedLegacy();
515
} else {
516
return false;
517
}
518
case SYSPROP_HAS_KEYBOARD:
519
return deviceType != DEVICE_TYPE_VR;
520
case SYSPROP_HAS_ACCELEROMETER:
521
return deviceType == DEVICE_TYPE_MOBILE;
522
case SYSPROP_CAN_CREATE_SHORTCUT:
523
return false; // We can't create shortcuts directly from game code, but we can from the Android UI.
524
#ifndef HTTPS_NOT_AVAILABLE
525
case SYSPROP_SUPPORTS_HTTPS:
526
return !g_Config.bDisableHTTPS;
527
#endif
528
default:
529
return false;
530
}
531
}
532
533
std::string Android_GetInputDeviceDebugString() {
534
if (!ppssppActivity) {
535
return "(N/A)";
536
}
537
auto env = getEnv();
538
539
jstring jparam = env->NewStringUTF("InputDevice");
540
jstring jstr = (jstring)env->CallObjectMethod(ppssppActivity, getDebugString, jparam);
541
if (!jstr) {
542
env->DeleteLocalRef(jparam);
543
return "(N/A)";
544
}
545
546
const char *charArray = env->GetStringUTFChars(jstr, nullptr);
547
std::string retVal = charArray;
548
env->ReleaseStringUTFChars(jstr, charArray);
549
env->DeleteLocalRef(jstr);
550
env->DeleteLocalRef(jparam);
551
return retVal;
552
}
553
554
std::vector<std::string> Android_GetNativeCrashHistory(int maxEntries) {
555
std::vector<std::string> crashHistory;
556
if (!ppssppActivity || !getNativeCrashHistory) {
557
return crashHistory;
558
}
559
auto env = getEnv();
560
jobject jlist = env->CallObjectMethod(ppssppActivity, getNativeCrashHistory, maxEntries);
561
if (!jlist) {
562
return crashHistory;
563
}
564
jclass arrayListClass = env->GetObjectClass(jlist);
565
jmethodID sizeMethod = env->GetMethodID(arrayListClass, "size", "()I");
566
jmethodID getMethod = env->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
567
jint listSize = env->CallIntMethod(jlist, sizeMethod);
568
for (jint i = 0; i < listSize; i++) {
569
jstring jstr = (jstring)env->CallObjectMethod(jlist, getMethod, i);
570
if (jstr) {
571
const char *charArray = env->GetStringUTFChars(jstr, nullptr);
572
crashHistory.emplace_back(charArray);
573
env->ReleaseStringUTFChars(jstr, charArray);
574
env->DeleteLocalRef(jstr);
575
}
576
}
577
env->DeleteLocalRef(arrayListClass);
578
env->DeleteLocalRef(jlist);
579
return crashHistory;
580
}
581
582
std::string GetJavaString(JNIEnv *env, jstring jstr) {
583
if (!jstr)
584
return "";
585
const char *str = env->GetStringUTFChars(jstr, nullptr);
586
std::string cpp_string = std::string(str);
587
env->ReleaseStringUTFChars(jstr, str);
588
return cpp_string;
589
}
590
591
extern "C" void Java_org_ppsspp_ppsspp_PpssppActivity_registerCallbacks(JNIEnv *env, jobject obj) {
592
ppssppActivity = env->NewGlobalRef(obj);
593
TextDrawerAndroid::SetActivity(ppssppActivity);
594
postCommand = env->GetMethodID(env->GetObjectClass(obj), "postCommand", "(Ljava/lang/String;Ljava/lang/String;)V");
595
getDebugString = env->GetMethodID(env->GetObjectClass(obj), "getDebugString", "(Ljava/lang/String;)Ljava/lang/String;");
596
getNativeCrashHistory = env->GetMethodID(env->GetObjectClass(obj), "getNativeCrashHistory", "(I)Ljava/util/ArrayList;");
597
598
_dbg_assert_(postCommand);
599
_dbg_assert_(getDebugString);
600
// It's OK if getNativeCrashHistory is missing.
601
602
Android_RegisterStorageCallbacks(env, obj);
603
Android_StorageSetActivity(ppssppActivity);
604
}
605
606
extern "C" void Java_org_ppsspp_ppsspp_PpssppActivity_unregisterCallbacks(JNIEnv *env, jobject obj) {
607
Android_StorageSetActivity(nullptr);
608
env->DeleteGlobalRef(ppssppActivity);
609
ppssppActivity = nullptr;
610
}
611
612
// This is now only used as a trigger for GetAppInfo as a function to all before Init.
613
// On Android we don't use any of the values it returns.
614
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isLandscape(JNIEnv *env, jclass) {
615
std::string app_name, app_nice_name, version;
616
bool landscape;
617
NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
618
return landscape;
619
}
620
621
// Allow the app to intercept the back button.
622
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_isAtTopLevel(JNIEnv *env, jclass) {
623
return NativeIsAtTopLevel();
624
}
625
626
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioConfig
627
(JNIEnv *env, jclass, jint optimalFPB, jint optimalSR) {
628
optimalFramesPerBuffer = optimalFPB;
629
optimalSampleRate = optimalSR;
630
}
631
632
// Easy way for the Java side to ask the C++ side for configuration options, such as
633
// the rotation lock which must be controlled from Java on Android.
634
static std::string QueryConfig(std::string_view query) {
635
char temp[128];
636
if (query == "screenRotation") {
637
INFO_LOG(Log::G3D, "g_Config.screenRotation = %d", g_Config.iScreenRotation);
638
snprintf(temp, sizeof(temp), "%d", g_Config.iScreenRotation);
639
return temp;
640
} else if (query == "immersiveMode") {
641
return g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation()).bImmersiveMode ? "1" : "0";
642
} else if (query == "sustainedPerformanceMode") {
643
return g_Config.bSustainedPerformanceMode ? "1" : "0";
644
} else if (query == "androidJavaGL") {
645
// If we're using Vulkan, we say no... need C++ to use Vulkan.
646
if (GetGPUBackend() == GPUBackend::VULKAN) {
647
return "false";
648
}
649
// Otherwise, some devices prefer the Java init so play it safe.
650
return "true";
651
} else {
652
return "";
653
}
654
}
655
656
extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig
657
(JNIEnv *env, jclass, jstring jquery) {
658
std::string query = GetJavaString(env, jquery);
659
std::string result = QueryConfig(query);
660
jstring jresult = env->NewStringUTF(result.c_str());
661
return jresult;
662
}
663
664
static void parse_args(std::vector<std::string> &args, const std::string value) {
665
// Simple argument parser so we can take args from extra params.
666
const char *p = value.c_str();
667
668
while (*p != '\0') {
669
while (isspace(*p)) {
670
p++;
671
}
672
if (*p == '\0') {
673
break;
674
}
675
676
bool done = false;
677
bool quote = false;
678
std::string arg;
679
680
while (!done) {
681
size_t sz = strcspn(p, "\"\\ \r\n\t");
682
arg += std::string(p, sz);
683
p += sz;
684
685
switch (*p) {
686
case '"':
687
quote = !quote;
688
p++;
689
break;
690
691
case '\\':
692
p++;
693
arg += std::string(p, 1);
694
p++;
695
break;
696
697
case '\0':
698
done = true;
699
break;
700
701
default:
702
// If it's not the above, it's whitespace.
703
if (!quote) {
704
done = true;
705
} else {
706
sz = strspn(p, " \r\n\t");
707
arg += std::string(p, sz);
708
p += sz;
709
}
710
break;
711
}
712
}
713
714
args.push_back(arg);
715
716
while (isspace(*p)) {
717
p++;
718
}
719
}
720
}
721
722
// Need to use raw Android logging before NativeInit.
723
#define EARLY_LOG(...) __android_log_print(ANDROID_LOG_INFO, "PPSSPP", __VA_ARGS__)
724
725
static bool bFirstResume = false;
726
727
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
728
(JNIEnv * env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
729
jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jNativeLibDir, jstring jadditionalStorageDirs, jstring jcacheDir, jstring jshortcutParam,
730
jint jAndroidVersion, jstring jboard) {
731
SetCurrentThreadName("androidInit");
732
733
// Makes sure we get early permission grants.
734
ProcessFrameCommands(env);
735
736
EARLY_LOG("NativeApp.init() -- begin");
737
PROFILE_INIT();
738
739
renderer_inited = false;
740
exitRenderLoop = false;
741
androidVersion = jAndroidVersion;
742
deviceType = jdeviceType;
743
744
Path apkPath(GetJavaString(env, japkpath));
745
g_VFS.Register("", ZipFileReader::Create(apkPath, "assets/"));
746
747
systemName = GetJavaString(env, jmodel);
748
langRegion = GetJavaString(env, jlangRegion);
749
750
EARLY_LOG("NativeApp.init(): device name: '%s'", systemName.c_str());
751
752
std::string externalStorageDir = GetJavaString(env, jexternalStorageDir);
753
std::string additionalStorageDirsString = GetJavaString(env, jadditionalStorageDirs);
754
std::string externalFilesDir = GetJavaString(env, jexternalFilesDir);
755
std::string nativeLibDir = GetJavaString(env, jNativeLibDir);
756
757
g_externalDir = externalStorageDir;
758
g_extFilesDir = externalFilesDir;
759
g_nativeLibDir = nativeLibDir;
760
761
if (!additionalStorageDirsString.empty()) {
762
SplitString(additionalStorageDirsString, ':', g_additionalStorageDirs);
763
for (auto &str : g_additionalStorageDirs) {
764
EARLY_LOG("Additional storage: %s", str.c_str());
765
}
766
}
767
768
std::string user_data_path = GetJavaString(env, jdataDir);
769
if (!user_data_path.empty())
770
user_data_path += "/";
771
std::string shortcut_param = GetJavaString(env, jshortcutParam);
772
std::string cacheDir = GetJavaString(env, jcacheDir);
773
std::string buildBoard = GetJavaString(env, jboard);
774
boardName = buildBoard;
775
EARLY_LOG("NativeApp.init(): External storage path: %s", externalStorageDir.c_str());
776
EARLY_LOG("NativeApp.init(): Launch shortcut parameter: %s", shortcut_param.c_str());
777
778
std::string app_name;
779
std::string app_nice_name;
780
std::string version;
781
bool landscape;
782
783
// Unfortunately, on the Samsung Galaxy S7, this isn't in /proc/cpuinfo.
784
// We also can't read it from __system_property_get.
785
if (buildBoard == "universal8890") {
786
cpu_info.sQuirks.bExynos8890DifferingCachelineSizes = true;
787
}
788
789
NativeGetAppInfo(&app_name, &app_nice_name, &landscape, &version);
790
791
// If shortcut_param is not empty, pass it as additional arguments to the NativeInit() method.
792
// NativeInit() is expected to treat extra argument as boot_filename, which in turn will start game immediately.
793
// NOTE: Will only work if ppsspp started from Activity.onCreate(). Won't work if ppsspp app start from onResume().
794
795
std::vector<const char *> args;
796
std::vector<std::string> temp;
797
args.push_back(app_name.c_str());
798
if (!shortcut_param.empty()) {
799
EARLY_LOG("NativeInit shortcut param %s", shortcut_param.c_str());
800
parse_args(temp, shortcut_param);
801
for (const auto &arg : temp) {
802
args.push_back(arg.c_str());
803
}
804
}
805
806
// TODO: We should be able to do the Vulkan init in parallel with NativeInit.
807
NativeInit((int)args.size(), &args[0], user_data_path.c_str(), externalStorageDir.c_str(), cacheDir.c_str());
808
809
bFirstResume = true;
810
811
// In debug mode, don't allow creating software Vulkan devices (reject by VulkanMaybeAvailable).
812
// Needed for #16931.
813
#ifdef NDEBUG
814
if (!VulkanMayBeAvailable()) {
815
// If VulkanLoader decided on no viable backend, let's force Vulkan off in release builds at least.
816
g_Config.iGPUBackend = 0;
817
}
818
#endif
819
820
// No need to use EARLY_LOG anymore.
821
822
retry:
823
switch (g_Config.iGPUBackend) {
824
case (int)GPUBackend::OPENGL:
825
useCPUThread = true;
826
INFO_LOG(Log::System, "NativeApp.init() -- creating OpenGL context (JavaGL)");
827
graphicsContext = new AndroidJavaEGLGraphicsContext();
828
INFO_LOG(Log::System, "NativeApp.init() - launching emu thread");
829
EmuThreadStart();
830
break;
831
case (int)GPUBackend::VULKAN:
832
{
833
INFO_LOG(Log::System, "NativeApp.init() -- creating Vulkan context");
834
useCPUThread = false;
835
// The Vulkan render manager manages its own thread.
836
// We create and destroy the Vulkan graphics context in the app main thread though.
837
AndroidVulkanContext *ctx = new AndroidVulkanContext();
838
if (!ctx->InitAPI()) {
839
INFO_LOG(Log::System, "Failed to initialize Vulkan, switching to OpenGL");
840
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
841
SetGPUBackend(GPUBackend::OPENGL);
842
goto retry;
843
} else {
844
graphicsContext = ctx;
845
}
846
break;
847
}
848
default:
849
ERROR_LOG(Log::System, "NativeApp.init(): iGPUBackend %d not supported. Switching to OpenGL.", (int)g_Config.iGPUBackend);
850
g_Config.iGPUBackend = (int)GPUBackend::OPENGL;
851
goto retry;
852
}
853
854
if (IsVREnabled()) {
855
Version gitVer(PPSSPP_GIT_VERSION);
856
InitVROnAndroid(gJvm, ppssppActivity, systemName.c_str(), gitVer.ToInteger(), "PPSSPP");
857
SetVRCallbacks(NativeAxis, NativeKey, NativeTouch);
858
}
859
}
860
861
AudioBackend *System_CreateAudioBackend() {
862
// Use legacy mechanisms.
863
return nullptr;
864
}
865
866
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioInit(JNIEnv *, jclass) {
867
sampleRate = optimalSampleRate;
868
if (optimalSampleRate == 0) {
869
sampleRate = 44100;
870
}
871
if (optimalFramesPerBuffer > 0) {
872
framesPerBuffer = optimalFramesPerBuffer;
873
} else {
874
framesPerBuffer = 512;
875
}
876
877
// Some devices have totally bonkers buffer sizes like 8192. They will have terrible latency anyway, so to avoid having to
878
// create extra smart buffering code, we'll just let their regular mixer deal with it, missing the fast path (as if they had one...)
879
if (framesPerBuffer > 512) {
880
framesPerBuffer = 512;
881
sampleRate = 44100;
882
}
883
884
INFO_LOG(Log::Audio, "NativeApp.audioInit() -- Using OpenSL audio! frames/buffer: %i optimal sr: %i actual sr: %i", optimalFramesPerBuffer, optimalSampleRate, sampleRate);
885
if (!g_audioState) {
886
g_audioState = AndroidAudio_Init(&NativeMix, framesPerBuffer, sampleRate);
887
} else {
888
ERROR_LOG(Log::Audio, "Audio state already initialized");
889
}
890
}
891
892
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioShutdown(JNIEnv *, jclass) {
893
if (g_audioState) {
894
AndroidAudio_Shutdown(g_audioState);
895
g_audioState = nullptr;
896
} else {
897
ERROR_LOG(Log::Audio, "Audio state already shutdown!");
898
}
899
}
900
901
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1SetSampleRate(JNIEnv *, jclass, jint sampleRate) {
902
AndroidAudio_Recording_SetSampleRate(g_audioState, sampleRate);
903
}
904
905
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Start(JNIEnv *, jclass) {
906
AndroidAudio_Recording_Start(g_audioState);
907
}
908
909
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_audioRecording_1Stop(JNIEnv *, jclass) {
910
AndroidAudio_Recording_Stop(g_audioState);
911
}
912
913
bool System_AudioRecordingIsAvailable() {
914
return true;
915
}
916
917
bool System_AudioRecordingState() {
918
return AndroidAudio_Recording_State(g_audioState);
919
}
920
921
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_resume(JNIEnv *, jclass) {
922
INFO_LOG(Log::System, "NativeApp.resume() - begin");
923
AndroidAudio_Resume(g_audioState);
924
925
System_PostUIMessage(UIMessage::APP_RESUMED, bFirstResume ? "first" : "");
926
927
bFirstResume = false;
928
INFO_LOG(Log::System, "NativeApp.resume() - end");
929
}
930
931
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_pause(JNIEnv *, jclass) {
932
INFO_LOG(Log::System, "NativeApp.pause() - begin");
933
AndroidAudio_Pause(g_audioState);
934
INFO_LOG(Log::System, "NativeApp.pause() - end");
935
}
936
937
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) {
938
INFO_LOG(Log::System, "NativeApp.shutdown() -- begin");
939
940
if (renderer_inited && useCPUThread && graphicsContext) {
941
// Only used in Java EGL path.
942
943
EmuThreadStop("shutdown");
944
// NOTE: We know that the GLSurfaceView render thread is stopped here, since we now
945
// correctly call GLSurfaceView.onPause/onResume. However, there may still be queued frames.
946
// We can't join until we've cleared the queue by calling ThreadFrame.
947
948
// Now we know that more frames won't be coming in.
949
950
INFO_LOG(Log::System, "BeginAndroidShutdown");
951
graphicsContext->BeginAndroidShutdown(); // Makes sure we don't actually perform draws.
952
953
// Now, it could be that we had some frames queued up. Get through them.
954
// We're on the render thread, so this is synchronous.
955
graphicsContext->ThreadFrameUntilCondition([]() -> bool {
956
return emuThreadState == (int)EmuThreadState::STOPPED;
957
});
958
graphicsContext->ThreadEnd();
959
960
EmuThreadJoin();
961
962
INFO_LOG(Log::System, "ThreadEnd called.");
963
graphicsContext->ShutdownFromRenderThread();
964
INFO_LOG(Log::System, "Graphics context now shut down from NativeApp_shutdown");
965
}
966
967
{
968
if (graphicsContext) {
969
INFO_LOG(Log::G3D, "Shutting down renderer");
970
graphicsContext->Shutdown();
971
delete graphicsContext;
972
graphicsContext = nullptr;
973
renderer_inited = false;
974
} else {
975
INFO_LOG(Log::G3D, "Not shutting down renderer - not initialized");
976
}
977
978
NativeShutdown();
979
g_VFS.Clear();
980
}
981
982
{
983
std::lock_guard<std::mutex> guard(frameCommandLock);
984
while (!frameCommands.empty())
985
frameCommands.pop();
986
}
987
INFO_LOG(Log::System, "NativeApp.shutdown() -- end");
988
}
989
990
// JavaEGL. This doesn't get called on the Vulkan path.
991
// This gets called from onSurfaceCreated.
992
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) {
993
_assert_(useCPUThread);
994
995
INFO_LOG(Log::G3D, "NativeApp.displayInit()");
996
bool firstStart = !renderer_inited;
997
998
// We should be running on the render thread here.
999
std::string errorMessage;
1000
if (renderer_inited) {
1001
// Would be really nice if we could get something on the GL thread immediately when shutting down,
1002
// but the only mechanism for handling lost devices seems to be that onSurfaceCreated is called again,
1003
// which ends up calling displayInit.
1004
1005
INFO_LOG(Log::G3D, "NativeApp.displayInit() restoring");
1006
EmuThreadStop("displayInit");
1007
graphicsContext->BeginAndroidShutdown();
1008
INFO_LOG(Log::G3D, "BeginAndroidShutdown. Looping until emu thread done...");
1009
// Skipping GL calls here because the old context is lost.
1010
graphicsContext->ThreadFrameUntilCondition([]() -> bool {
1011
return emuThreadState == (int)EmuThreadState::STOPPED;
1012
});
1013
INFO_LOG(Log::G3D, "Joining emu thread");
1014
EmuThreadJoin();
1015
1016
graphicsContext->ThreadEnd();
1017
graphicsContext->ShutdownFromRenderThread();
1018
1019
INFO_LOG(Log::G3D, "Shut down both threads. Now let's bring it up again!");
1020
1021
if (!graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
1022
System_Toast("Graphics initialization failed. Quitting.");
1023
return false;
1024
}
1025
1026
graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
1027
g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
1028
}, nullptr);
1029
1030
EmuThreadStart();
1031
1032
graphicsContext->ThreadStart();
1033
1034
INFO_LOG(Log::G3D, "Restored.");
1035
} else {
1036
INFO_LOG(Log::G3D, "NativeApp.displayInit() first time");
1037
if (!graphicsContext || !graphicsContext->InitFromRenderThread(nullptr, 0, 0, 0, 0)) {
1038
System_Toast("Graphics initialization failed. Quitting.");
1039
return false;
1040
}
1041
1042
graphicsContext->GetDrawContext()->SetErrorCallback([](const char *shortDesc, const char *details, void *userdata) {
1043
g_OSD.Show(OSDType::MESSAGE_ERROR, details, 5.0);
1044
}, nullptr);
1045
1046
graphicsContext->ThreadStart();
1047
renderer_inited = true;
1048
}
1049
1050
System_PostUIMessage(UIMessage::RECREATE_VIEWS);
1051
1052
if (IsVREnabled()) {
1053
EnterVR(firstStart);
1054
}
1055
return true;
1056
}
1057
1058
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv *, jclass, jint pixel_xres, jint pixel_yres, jint format) {
1059
INFO_LOG(Log::System, "NativeApp.backbufferResize(%d x %d)", pixel_xres, pixel_yres);
1060
1061
int old_w = g_display.pixel_xres;
1062
int old_h = g_display.pixel_yres;
1063
1064
// pixel_*res is the backbuffer resolution.
1065
backbuffer_format = format;
1066
1067
if (IsVREnabled()) {
1068
GetVRResolutionPerEye(&pixel_xres, &pixel_yres);
1069
}
1070
1071
// Compute display scale factor. Always < 1.0f (well, as long as we use buffers sized smaller than the screen...)
1072
display_scale_x = (float)pixel_xres / (float)display_xres;
1073
display_scale_y = (float)pixel_yres / (float)display_yres;
1074
1075
float dpi_x = (1.0f / display_scale_x) * (240.0f / (float)display_dpi);
1076
float dpi_y = (1.0f / display_scale_y) * (240.0f / (float)display_dpi);
1077
1078
bool new_size = g_display.Recalculate(pixel_xres, pixel_yres, dpi_x, dpi_y, UIScaleFactorToMultiplier(g_Config.iUIScaleFactor));
1079
1080
INFO_LOG(Log::G3D, "RecalcDPI: display_xres=%d display_yres=%d pixel_xres=%d pixel_yres=%d", display_xres, display_yres, g_display.pixel_xres, g_display.pixel_yres);
1081
INFO_LOG(Log::G3D, "RecalcDPI: g_dpi=%d scaled_dpi_x=%f scaled_dpi_y=%f display_scale_x=%f display_scale_y=%f g_dpi_scale_x=%f g_dpi_scale_y=%f dp_xres=%d dp_yres=%d",
1082
display_dpi, dpi_x, dpi_y, display_scale_x, display_scale_y, g_display.dpi_scale_x, g_display.dpi_scale_y, g_display.dp_xres, g_display.dp_yres);
1083
1084
if (new_size) {
1085
INFO_LOG(Log::G3D, "Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h);
1086
NativeResized();
1087
} else {
1088
INFO_LOG(Log::G3D, "NativeApp::backbufferResize: Size didn't change.");
1089
}
1090
}
1091
1092
void System_Notify(SystemNotification notification) {
1093
switch (notification) {
1094
case SystemNotification::ROTATE_UPDATED:
1095
PushCommand("rotate", "");
1096
break;
1097
case SystemNotification::FORCE_RECREATE_ACTIVITY:
1098
PushCommand("recreate", "");
1099
break;
1100
case SystemNotification::IMMERSIVE_MODE_CHANGE:
1101
PushCommand("immersive", "");
1102
break;
1103
case SystemNotification::SUSTAINED_PERF_CHANGE:
1104
PushCommand("sustainedPerfMode", "");
1105
break;
1106
case SystemNotification::TEST_JAVA_EXCEPTION:
1107
PushCommand("testException", "This is a test exception");
1108
break;
1109
default:
1110
break;
1111
}
1112
}
1113
1114
bool System_MakeRequest(SystemRequestType type, int requestId, const std::string &param1, const std::string &param2, int64_t param3, int64_t param4) {
1115
switch (type) {
1116
case SystemRequestType::EXIT_APP:
1117
PushCommand("finish", "");
1118
return true;
1119
case SystemRequestType::RESTART_APP:
1120
PushCommand("graphics_restart", param1);
1121
return true;
1122
case SystemRequestType::RECREATE_ACTIVITY:
1123
PushCommand("recreate", param1);
1124
return true;
1125
case SystemRequestType::COPY_TO_CLIPBOARD:
1126
PushCommand("copy_to_clipboard", param1);
1127
return true;
1128
case SystemRequestType::INPUT_TEXT_MODAL:
1129
{
1130
std::string serialized = StringFromFormat("%d:@:%s:@:%s", requestId, param1.c_str(), param2.c_str());
1131
PushCommand("inputbox", serialized);
1132
return true;
1133
}
1134
case SystemRequestType::BROWSE_FOR_IMAGE:
1135
PushCommand("browse_image", StringFromFormat("%d", requestId));
1136
return true;
1137
case SystemRequestType::BROWSE_FOR_FILE:
1138
{
1139
BrowseFileType fileType = (BrowseFileType)param3;
1140
std::string params = StringFromFormat("%d", requestId);
1141
switch (fileType) {
1142
case BrowseFileType::SOUND_EFFECT:
1143
PushCommand("browse_file_audio", params);
1144
break;
1145
case BrowseFileType::ZIP:
1146
PushCommand("browse_file_zip", params);
1147
break;
1148
default:
1149
PushCommand("browse_file", params);
1150
break;
1151
}
1152
return true;
1153
}
1154
case SystemRequestType::BROWSE_FOR_FOLDER:
1155
PushCommand("browse_folder", StringFromFormat("%d", requestId));
1156
return true;
1157
1158
case SystemRequestType::CAMERA_COMMAND:
1159
PushCommand("camera_command", param1);
1160
return true;
1161
case SystemRequestType::GPS_COMMAND:
1162
PushCommand("gps_command", param1);
1163
return true;
1164
case SystemRequestType::INFRARED_COMMAND:
1165
PushCommand("infrared_command", param1);
1166
return true;
1167
case SystemRequestType::MICROPHONE_COMMAND:
1168
PushCommand("microphone_command", param1);
1169
return true;
1170
case SystemRequestType::SHARE_TEXT:
1171
PushCommand("share_text", param1);
1172
return true;
1173
case SystemRequestType::SET_KEEP_SCREEN_BRIGHT:
1174
PushCommand("set_keep_screen_bright", param3 ? "on" : "off");
1175
return true;
1176
case SystemRequestType::SHOW_FILE_IN_FOLDER:
1177
PushCommand("show_folder", param1);
1178
return true;
1179
default:
1180
return false;
1181
}
1182
}
1183
1184
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendRequestResult(JNIEnv *env, jclass, jint jrequestID, jboolean result, jstring jvalue, jint jintValue) {
1185
std::string value = jvalue ? GetJavaString(env, jvalue) : "(no value)";
1186
INFO_LOG(Log::System, "Received result of request %d from Java: %d: %d '%s'", jrequestID, (int)result, jintValue, value.c_str());
1187
if (result) {
1188
g_requestManager.PostSystemSuccess(jrequestID, value.c_str());
1189
} else {
1190
g_requestManager.PostSystemFailure(jrequestID);
1191
}
1192
}
1193
1194
extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) {
1195
// This doesn't get called on the Vulkan path.
1196
_assert_(useCPUThread);
1197
1198
static bool hasSetThreadName = false;
1199
if (!hasSetThreadName) {
1200
hasSetThreadName = true;
1201
SetCurrentThreadName("AndroidRender");
1202
}
1203
1204
if (IsVREnabled() && !StartVRRender())
1205
return;
1206
1207
// This is the "GPU thread". Call ThreadFrame.
1208
if (!graphicsContext || !graphicsContext->ThreadFrame(true)) {
1209
return;
1210
}
1211
1212
if (IsVREnabled()) {
1213
UpdateVRInput(g_Config.bHapticFeedback, g_display.dpi_scale_x, g_display.dpi_scale_y);
1214
FinishVRRender();
1215
}
1216
}
1217
1218
void System_AskForPermission(SystemPermission permission) {
1219
switch (permission) {
1220
case SYSTEM_PERMISSION_STORAGE:
1221
PushCommand("ask_permission", "storage");
1222
break;
1223
}
1224
}
1225
1226
PermissionStatus System_GetPermissionStatus(SystemPermission permission) {
1227
if (androidVersion < 23) {
1228
return PERMISSION_STATUS_GRANTED;
1229
} else {
1230
return permissions[permission];
1231
}
1232
}
1233
1234
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch
1235
(JNIEnv *, jclass, float x, float y, int code, int pointerId) {
1236
if (!renderer_inited)
1237
return;
1238
TouchInput touch{};
1239
touch.id = pointerId;
1240
touch.x = x * display_scale_x * g_display.dpi_scale_x;
1241
touch.y = y * display_scale_y * g_display.dpi_scale_y;
1242
touch.flags = (TouchInputFlags)code;
1243
NativeTouch(touch);
1244
}
1245
1246
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyDown(JNIEnv *, jclass, jint deviceId, jint key, jboolean isRepeat) {
1247
if (!renderer_inited) {
1248
return false; // could probably return true here too..
1249
}
1250
if (key == 0 && deviceId >= DEVICE_ID_PAD_0 && deviceId <= DEVICE_ID_PAD_9) {
1251
// Ignore keycode 0 from pads. Stadia controllers seem to produce them when pressing L2/R2 for some reason, confusing things.
1252
return true; // need to eat the key so it doesn't go through legacy path
1253
}
1254
1255
KeyInput keyInput;
1256
keyInput.deviceId = (InputDeviceID)deviceId;
1257
keyInput.keyCode = (InputKeyCode)key;
1258
keyInput.flags = KeyInputFlags::DOWN;
1259
if (isRepeat) {
1260
keyInput.flags |= KeyInputFlags::IS_REPEAT;
1261
}
1262
return NativeKey(keyInput);
1263
}
1264
1265
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyUp(JNIEnv *, jclass, jint deviceId, jint key) {
1266
if (!renderer_inited) {
1267
return false; // could probably return true here too..
1268
}
1269
if (key == 0 && deviceId >= DEVICE_ID_PAD_0 && deviceId <= DEVICE_ID_PAD_9) {
1270
// Ignore keycode 0 from pads. Stadia controllers seem to produce them when pressing L2/R2 for some reason, confusing things.
1271
return true; // need to eat the key so it doesn't go through legacy path
1272
}
1273
1274
KeyInput keyInput;
1275
keyInput.deviceId = (InputDeviceID)deviceId;
1276
keyInput.keyCode = (InputKeyCode)key;
1277
keyInput.flags = KeyInputFlags::UP;
1278
return NativeKey(keyInput);
1279
}
1280
1281
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_keyChar(JNIEnv *, jclass, jint deviceId, jint unicodeChar) {
1282
if (!renderer_inited) {
1283
return false; // could probably return true here too..
1284
}
1285
1286
KeyInput keyInput;
1287
keyInput.deviceId = (InputDeviceID)deviceId;
1288
keyInput.unicodeChar = unicodeChar;
1289
keyInput.flags = KeyInputFlags::CHAR;
1290
return NativeKey(keyInput);
1291
}
1292
1293
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_joystickAxis(
1294
JNIEnv *env, jclass, jint deviceId, jintArray axisIds, jfloatArray values, jint count) {
1295
if (!renderer_inited)
1296
return;
1297
1298
AxisInput *axis = new AxisInput[count];
1299
_dbg_assert_(count <= env->GetArrayLength(axisIds));
1300
_dbg_assert_(count <= env->GetArrayLength(values));
1301
jint *axisIdBuffer = env->GetIntArrayElements(axisIds, nullptr);
1302
jfloat *valueBuffer = env->GetFloatArrayElements(values, nullptr);
1303
1304
// These are dirty-filtered on the Java side.
1305
for (int i = 0; i < count; i++) {
1306
axis[i].deviceId = (InputDeviceID)(int)deviceId;
1307
axis[i].axisId = (InputAxis)(int)axisIdBuffer[i];
1308
axis[i].value = valueBuffer[i];
1309
}
1310
NativeAxis(axis, count);
1311
delete[] axis;
1312
env->ReleaseIntArrayElements(axisIds, axisIdBuffer, JNI_ABORT); // ABORT just means we don't want changes copied back!
1313
env->ReleaseFloatArrayElements(values, valueBuffer, JNI_ABORT); // ABORT just means we don't want changes copied back!
1314
}
1315
1316
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_mouse(
1317
JNIEnv *env, jclass, jfloat x, jfloat y, int button, int action) {
1318
if (!renderer_inited)
1319
return;
1320
TouchInput input{};
1321
1322
static float last_x = 0.0f;
1323
static float last_y = 0.0f;
1324
1325
if (x == -1.0f) {
1326
x = last_x;
1327
} else {
1328
last_x = x;
1329
}
1330
if (y == -1.0f) {
1331
y = last_y;
1332
} else {
1333
last_y = y;
1334
}
1335
1336
x *= g_display.dpi_scale_x;
1337
y *= g_display.dpi_scale_y;
1338
1339
if (button == 0) {
1340
// It's a pure mouse move.
1341
input.flags = TouchInputFlags::MOUSE | TouchInputFlags::MOVE;
1342
input.x = x;
1343
input.y = y;
1344
input.id = 0;
1345
} else {
1346
input.buttons = button;
1347
input.x = x;
1348
input.y = y;
1349
switch (action) {
1350
case 1:
1351
input.flags = TouchInputFlags::MOUSE | TouchInputFlags::DOWN;
1352
break;
1353
case 2:
1354
input.flags = TouchInputFlags::MOUSE | TouchInputFlags::UP;
1355
break;
1356
}
1357
input.id = 0;
1358
}
1359
INFO_LOG(Log::System, "New-style mouse event: %f %f %d %d -> x: %f y: %f buttons: %d flags: %04x", x, y, button, action, input.x, input.y, input.buttons, input.flags);
1360
NativeTouch(input);
1361
1362
// Also send mouse button key events, for binding.
1363
if (button) {
1364
KeyInput keyInput{};
1365
keyInput.deviceId = DEVICE_ID_MOUSE;
1366
switch (button) {
1367
case 1: keyInput.keyCode = NKCODE_EXT_MOUSEBUTTON_1; break;
1368
case 2: keyInput.keyCode = NKCODE_EXT_MOUSEBUTTON_2; break;
1369
case 3: keyInput.keyCode = NKCODE_EXT_MOUSEBUTTON_3; break;
1370
default: WARN_LOG(Log::System, "Unexpected mouse button %d", button);
1371
}
1372
keyInput.flags = action == 1 ? KeyInputFlags::DOWN : KeyInputFlags::UP;
1373
if (keyInput.keyCode != 0) {
1374
NativeKey(keyInput);
1375
}
1376
}
1377
}
1378
1379
extern "C" jboolean Java_org_ppsspp_ppsspp_NativeApp_mouseWheelEvent(
1380
JNIEnv *env, jclass, jfloat x, jfloat y) {
1381
if (!renderer_inited)
1382
return false;
1383
// TODO: Mousewheel should probably be an axis instead.
1384
int wheelDelta = y * 30.0f;
1385
if (wheelDelta > 500) wheelDelta = 500;
1386
if (wheelDelta < -500) wheelDelta = -500;
1387
1388
KeyInput key;
1389
key.deviceId = DEVICE_ID_MOUSE;
1390
if (wheelDelta < 0) {
1391
key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN;
1392
wheelDelta = -wheelDelta;
1393
} else {
1394
key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP;
1395
}
1396
// There's no separate keyup event for mousewheel events,
1397
// so we release it with a slight delay.
1398
key.flags = (KeyInputFlags)((u32)KeyInputFlags::DOWN | (u32)KeyInputFlags::HAS_WHEEL_DELTA | (wheelDelta << 16));
1399
NativeKey(key);
1400
return true;
1401
}
1402
1403
extern "C" void Java_org_ppsspp_ppsspp_NativeApp_mouseDelta(
1404
JNIEnv * env, jclass, jfloat x, jfloat y) {
1405
if (!renderer_inited)
1406
return;
1407
NativeMouseDelta(x, y);
1408
}
1409
1410
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_accelerometer(JNIEnv *, jclass, float x, float y, float z) {
1411
if (!renderer_inited)
1412
return;
1413
NativeAccelerometer(x, y, z);
1414
}
1415
1416
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNIEnv *env, jclass, jstring jmessage, jstring jparam) {
1417
std::string msg = GetJavaString(env, jmessage);
1418
std::string prm = GetJavaString(env, jparam);
1419
1420
// A bit ugly, see InputDeviceState.java.
1421
static InputDeviceID nextInputDeviceID = DEVICE_ID_ANY;
1422
1423
// Some messages are caught by app-android. TODO: Should be all.
1424
if (msg == "moga") {
1425
mogaVersion = prm;
1426
} else if (msg == "permission_pending") {
1427
INFO_LOG(Log::System, "STORAGE PERMISSION: PENDING");
1428
// TODO: Add support for other permissions
1429
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
1430
// Don't need to send along, nothing else is listening.
1431
} else if (msg == "permission_denied") {
1432
INFO_LOG(Log::System, "STORAGE PERMISSION: DENIED");
1433
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
1434
// Don't need to send along, nothing else is listening.
1435
} else if (msg == "permission_granted") {
1436
INFO_LOG(Log::System, "STORAGE PERMISSION: GRANTED");
1437
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
1438
// Send along.
1439
System_PostUIMessage(UIMessage::PERMISSION_GRANTED, prm);
1440
} else if (msg == "sustained_perf_supported") {
1441
sustainedPerfSupported = true;
1442
} else if (msg == "safe_insets") {
1443
// INFO_LOG(Log::System, "Got insets: %s", prm.c_str());
1444
// We don't bother with supporting exact rectangular regions. Safe insets are good enough.
1445
int left, right, top, bottom;
1446
if (4 == sscanf(prm.c_str(), "%d:%d:%d:%d", &left, &right, &top, &bottom)) {
1447
g_safeInsetLeft = (float)left;
1448
g_safeInsetRight = (float)right;
1449
g_safeInsetTop = (float)top;
1450
g_safeInsetBottom = (float)bottom;
1451
}
1452
} else if (msg == "inputDeviceConnectedID") {
1453
nextInputDeviceID = (InputDeviceID)parseLong(prm);
1454
} else if (msg == "inputDeviceConnected") {
1455
KeyMap::NotifyPadConnected(nextInputDeviceID, prm);
1456
} else if (msg == "inputDeviceDisconnectedID") {
1457
InputDeviceID disconnectedID = (InputDeviceID)parseLong(prm);
1458
KeyMap::NotifyPadDisconnected(disconnectedID);
1459
} else if (msg == "core_powerSaving") {
1460
// Forward.
1461
System_PostUIMessage(UIMessage::POWER_SAVING, prm);
1462
} else if (msg == "exception") {
1463
g_OSD.Show(OSDType::MESSAGE_ERROR, std::string("Java Exception"), prm, 10.0f);
1464
} else if (msg == "shortcutParam") {
1465
if (prm.empty()) {
1466
WARN_LOG(Log::System, "shortcutParam empty");
1467
return;
1468
}
1469
INFO_LOG(Log::System, "shortcutParam received: %s", prm.c_str());
1470
1471
prm = StripQuotes(prm);
1472
// NOTE: The parameter can be a file:// URL, which we need to take care of here. Similar to in NativeApp.cpp, search for file://
1473
if (startsWith(prm, "file:///")) {
1474
std::string param = prm;
1475
prm = UriDecode(prm.substr(7));
1476
INFO_LOG(Log::IO, "Decoding '%s' to '%s'", param.c_str(), prm.c_str());
1477
}
1478
System_PostUIMessage(UIMessage::REQUEST_GAME_BOOT, StripQuotes(prm));
1479
} else {
1480
ERROR_LOG(Log::System, "Got unexpected message from Java, ignoring: %s / %s", msg.c_str(), prm.c_str());
1481
}
1482
}
1483
1484
void correctRatio(int &sz_x, int &sz_y, float scale) {
1485
float x = (float)sz_x;
1486
float y = (float)sz_y;
1487
float ratio = x / y;
1488
INFO_LOG(Log::G3D, "CorrectRatio: Considering size: %0.2f/%0.2f=%0.2f for scale %f", x, y, ratio, scale);
1489
float targetRatio;
1490
1491
// Try to get the longest dimension to match scale*PSP resolution.
1492
if (x >= y) {
1493
targetRatio = 480.0f / 272.0f;
1494
x = 480.f * scale;
1495
y = 272.f * scale;
1496
} else {
1497
targetRatio = 272.0f / 480.0f;
1498
x = 272.0f * scale;
1499
y = 480.0f * scale;
1500
}
1501
1502
float correction = targetRatio / ratio;
1503
INFO_LOG(Log::G3D, "Target ratio: %0.2f ratio: %0.2f correction: %0.2f", targetRatio, ratio, correction);
1504
if (ratio < targetRatio) {
1505
y *= correction;
1506
} else {
1507
x /= correction;
1508
}
1509
1510
sz_x = x;
1511
sz_y = y;
1512
INFO_LOG(Log::G3D, "Corrected ratio: %dx%d", sz_x, sz_y);
1513
}
1514
1515
void getDesiredBackbufferSize(int &sz_x, int &sz_y) {
1516
sz_x = display_xres;
1517
sz_y = display_yres;
1518
1519
int scale = g_Config.iAndroidHwScale;
1520
// Override hw scale for TV type devices.
1521
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV)
1522
scale = 0;
1523
1524
if (scale == 1) {
1525
// If g_Config.iInternalResolution is also set to Auto (1), we fall back to "Device resolution" (0). It works out.
1526
scale = g_Config.iInternalResolution;
1527
} else if (scale >= 2) {
1528
scale -= 1;
1529
}
1530
1531
int max_res = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 1;
1532
1533
scale = std::min(scale, max_res);
1534
1535
if (scale > 0) {
1536
correctRatio(sz_x, sz_y, scale);
1537
} else {
1538
sz_x = 0;
1539
sz_y = 0;
1540
}
1541
}
1542
1543
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setDisplayParameters(JNIEnv *, jclass, jint xres, jint yres, jint dpi, jfloat refreshRate) {
1544
INFO_LOG(Log::G3D, "NativeApp.setDisplayParameters(%d x %d, dpi=%d, refresh=%0.2f)", xres, yres, dpi, refreshRate);
1545
1546
if (IsVREnabled()) {
1547
int width, height;
1548
GetVRResolutionPerEye(&width, &height);
1549
xres = width;
1550
yres = height * 272 / 480;
1551
dpi = 320;
1552
}
1553
1554
// Hard parameters for the display. Actual DPI recalculation happens in BackbufferResize.
1555
display_xres = xres;
1556
display_yres = yres;
1557
display_dpi = dpi;
1558
g_display.display_hz = refreshRate;
1559
}
1560
1561
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_computeDesiredBackbufferDimensions(JNIEnv *, jclass) {
1562
getDesiredBackbufferSize(desiredBackbufferSizeX, desiredBackbufferSizeY);
1563
}
1564
1565
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferWidth(JNIEnv *, jclass) {
1566
return desiredBackbufferSizeX;
1567
}
1568
1569
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHeight(JNIEnv *, jclass) {
1570
return desiredBackbufferSizeY;
1571
}
1572
1573
extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDisplayFramerateMode(JNIEnv *, jclass) {
1574
return g_Config.iDisplayFramerateMode;
1575
}
1576
1577
std::vector<std::string> System_GetCameraDeviceList() {
1578
jclass cameraClass = findClass("org/ppsspp/ppsspp/CameraHelper");
1579
jmethodID deviceListMethod = getEnv()->GetStaticMethodID(cameraClass, "getDeviceList", "()Ljava/util/ArrayList;");
1580
jobject deviceListObject = getEnv()->CallStaticObjectMethod(cameraClass, deviceListMethod);
1581
jclass arrayListClass = getEnv()->FindClass("java/util/ArrayList");
1582
jmethodID arrayListSize = getEnv()->GetMethodID(arrayListClass, "size", "()I");
1583
jmethodID arrayListGet = getEnv()->GetMethodID(arrayListClass, "get", "(I)Ljava/lang/Object;");
1584
1585
jint arrayListObjectLen = getEnv()->CallIntMethod(deviceListObject, arrayListSize);
1586
std::vector<std::string> deviceListVector;
1587
1588
for (int i = 0; i < arrayListObjectLen; i++) {
1589
jstring dev = static_cast<jstring>(getEnv()->CallObjectMethod(deviceListObject, arrayListGet, i));
1590
const char *cdev = getEnv()->GetStringUTFChars(dev, nullptr);
1591
if (!cdev) {
1592
getEnv()->DeleteLocalRef(dev);
1593
continue;
1594
}
1595
deviceListVector.emplace_back(cdev);
1596
getEnv()->ReleaseStringUTFChars(dev, cdev);
1597
getEnv()->DeleteLocalRef(dev);
1598
}
1599
return deviceListVector;
1600
}
1601
1602
extern "C" jint Java_org_ppsspp_ppsspp_NativeApp_getSelectedCamera(JNIEnv *, jclass) {
1603
int cameraId = 0;
1604
sscanf(g_Config.sCameraDevice.c_str(), "%d:", &cameraId);
1605
return cameraId;
1606
}
1607
1608
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setGpsDataAndroid(JNIEnv *, jclass,
1609
jlong time, jfloat hdop, jfloat latitude, jfloat longitude, jfloat altitude, jfloat speed, jfloat bearing) {
1610
GPS::setGpsData(time, hdop, latitude, longitude, altitude, speed, bearing);
1611
}
1612
1613
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_setSatInfoAndroid(JNIEnv *, jclass,
1614
jshort index, jshort id, jshort elevation, jshort azimuth, jshort snr, jshort good) {
1615
GPS::setSatInfo(index, id, elevation, azimuth, snr, good);
1616
}
1617
1618
extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_pushCameraImageAndroid(JNIEnv *env, jclass, jbyteArray image) {
1619
if (image) {
1620
jlong size = env->GetArrayLength(image);
1621
jbyte* buffer = env->GetByteArrayElements(image, nullptr);
1622
Camera::pushCameraImage(size, (unsigned char *)buffer);
1623
env->ReleaseByteArrayElements(image, buffer, JNI_ABORT);
1624
}
1625
}
1626
1627
// Call this under frameCommandLock.
1628
static void ProcessFrameCommands(JNIEnv *env) {
1629
while (!frameCommands.empty()) {
1630
FrameCommand frameCmd;
1631
frameCmd = frameCommands.front();
1632
frameCommands.pop();
1633
1634
DEBUG_LOG(Log::System, "frameCommand '%s' '%s'", frameCmd.command.c_str(), frameCmd.params.c_str());
1635
1636
jstring cmd = env->NewStringUTF(frameCmd.command.c_str());
1637
jstring param = env->NewStringUTF(frameCmd.params.c_str());
1638
env->CallVoidMethod(ppssppActivity, postCommand, cmd, param);
1639
env->DeleteLocalRef(cmd);
1640
env->DeleteLocalRef(param);
1641
}
1642
}
1643
1644
std::thread g_renderLoopThread;
1645
1646
static void VulkanEmuThread(ANativeWindow *wnd);
1647
1648
// This runs in Vulkan mode only.
1649
// This handles the entire lifecycle of the Vulkan context, init and exit.
1650
extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_PpssppActivity_runVulkanRenderLoop(JNIEnv * env, jobject obj, jobject _surf) {
1651
_assert_(!useCPUThread);
1652
1653
if (!graphicsContext) {
1654
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
1655
return false;
1656
}
1657
1658
if (g_renderLoopThread.joinable()) {
1659
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Already running");
1660
return false;
1661
}
1662
1663
ANativeWindow *wnd = _surf ? ANativeWindow_fromSurface(env, _surf) : nullptr;
1664
1665
if (!wnd) {
1666
// This shouldn't ever happen.
1667
ERROR_LOG(Log::G3D, "Error: Surface is null.");
1668
renderLoopRunning = false;
1669
return false;
1670
}
1671
1672
g_renderLoopThread = std::thread(VulkanEmuThread, wnd);
1673
return true;
1674
}
1675
1676
extern "C" void JNICALL Java_org_ppsspp_ppsspp_PpssppActivity_requestExitVulkanRenderLoop(JNIEnv * env, jobject obj) {
1677
if (!renderLoopRunning) {
1678
ERROR_LOG(Log::System, "Render loop already exited");
1679
return;
1680
}
1681
_assert_(g_renderLoopThread.joinable());
1682
exitRenderLoop = true;
1683
g_renderLoopThread.join();
1684
g_renderLoopThread = std::thread();
1685
}
1686
1687
// TODO: Merge with the Win32 EmuThread and so on, and the Java EmuThread?
1688
static void VulkanEmuThread(ANativeWindow *wnd) {
1689
SetCurrentThreadName("EmuThread");
1690
1691
AndroidJNIThreadContext ctx;
1692
JNIEnv *env = getEnv();
1693
1694
if (!graphicsContext) {
1695
ERROR_LOG(Log::G3D, "runVulkanRenderLoop: Tried to enter without a created graphics context.");
1696
renderLoopRunning = false;
1697
exitRenderLoop = false;
1698
return;
1699
}
1700
1701
if (exitRenderLoop) {
1702
WARN_LOG(Log::G3D, "runVulkanRenderLoop: ExitRenderLoop requested at start, skipping the whole thing.");
1703
renderLoopRunning = false;
1704
exitRenderLoop = false;
1705
return;
1706
}
1707
1708
// This is up here to prevent race conditions, in case we pause during init.
1709
renderLoopRunning = true;
1710
1711
WARN_LOG(Log::G3D, "runVulkanRenderLoop. display_xres=%d display_yres=%d desiredBackbufferSizeX=%d desiredBackbufferSizeY=%d",
1712
display_xres, display_yres, desiredBackbufferSizeX, desiredBackbufferSizeY);
1713
1714
if (!graphicsContext->InitFromRenderThread(wnd, desiredBackbufferSizeX, desiredBackbufferSizeY, backbuffer_format, androidVersion)) {
1715
// On Android, if we get here, really no point in continuing.
1716
// The UI is supposed to render on any device both on OpenGL and Vulkan. If either of those don't work
1717
// on a device, we blacklist it. Hopefully we should have already failed in InitAPI anyway and reverted to GL back then.
1718
ERROR_LOG(Log::G3D, "Failed to initialize graphics context.");
1719
System_Toast("Failed to initialize graphics context.");
1720
1721
delete graphicsContext;
1722
graphicsContext = nullptr;
1723
renderLoopRunning = false;
1724
return;
1725
}
1726
1727
if (!exitRenderLoop) {
1728
if (!NativeInitGraphics(graphicsContext)) {
1729
ERROR_LOG(Log::G3D, "Failed to initialize graphics.");
1730
// Gonna be in a weird state here..
1731
}
1732
graphicsContext->ThreadStart();
1733
renderer_inited = true;
1734
1735
while (!exitRenderLoop) {
1736
{
1737
NativeFrame(graphicsContext);
1738
}
1739
{
1740
std::lock_guard<std::mutex> guard(frameCommandLock);
1741
ProcessFrameCommands(env);
1742
}
1743
}
1744
INFO_LOG(Log::G3D, "Leaving Vulkan main loop.");
1745
} else {
1746
INFO_LOG(Log::G3D, "Not entering main loop.");
1747
}
1748
1749
NativeShutdownGraphics();
1750
1751
renderer_inited = false;
1752
graphicsContext->ThreadEnd();
1753
1754
// Shut the graphics context down to the same state it was in when we entered the render thread.
1755
INFO_LOG(Log::G3D, "Shutting down graphics context...");
1756
graphicsContext->ShutdownFromRenderThread();
1757
renderLoopRunning = false;
1758
exitRenderLoop = false;
1759
1760
WARN_LOG(Log::G3D, "Render loop function exited.");
1761
}
1762
1763
extern "C" JNIEXPORT jobjectArray JNICALL
1764
Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameInfo(JNIEnv * env, jclass, jobject activity, jstring jpath) {
1765
1766
jobject activityRef = nullptr;
1767
1768
bool teardownThreadManager = false;
1769
// Maybe we should just check ppssppActivity instead.
1770
if (!g_threadManager.IsInitialized()) {
1771
teardownThreadManager = true;
1772
g_threadManager.Init(1, 1);
1773
g_logManager.SetOutputsEnabled(LogOutput::Stdio);
1774
g_logManager.SetAllLogLevels(LogLevel::LDEBUG);
1775
activityRef = env->NewGlobalRef(activity);
1776
Android_StorageSetActivity(activityRef);
1777
Android_RegisterStorageCallbacks(env, activityRef);
1778
INFO_LOG(Log::System, "No thread manager - initializing one");
1779
}
1780
1781
Path path = Path(GetJavaString(env, jpath));
1782
INFO_LOG(Log::System, "queryGameInfo(%s)", path.c_str());
1783
1784
std::string gameName;
1785
jbyteArray gameIcon = nullptr;
1786
1787
GameInfoCache *cache = new GameInfoCache();
1788
std::shared_ptr<GameInfo> info = cache->GetInfo(nullptr, path, GameInfoFlags::PARAM_SFO | GameInfoFlags::ICON);
1789
1790
if (info) {
1791
INFO_LOG(Log::System, "GetInfo successful, waiting");
1792
1793
// Wait for both name and icon
1794
int attempts = 1000;
1795
while ((!info->Ready(GameInfoFlags::PARAM_SFO) || !info->Ready(GameInfoFlags::ICON)) && attempts > 0) {
1796
sleep_ms(1, "info-icon-poll");
1797
attempts--;
1798
}
1799
INFO_LOG(Log::System, "Done waiting");
1800
1801
if (info->fileType != IdentifiedFileType::UNKNOWN) {
1802
// Get the game title
1803
gameName = info->GetTitle();
1804
if (gameName.length() > strlen("The ") && startsWithNoCase(gameName, "The ")) {
1805
gameName = gameName.substr(strlen("The "));
1806
}
1807
INFO_LOG(Log::System, "Got name: '%s'", gameName.c_str());
1808
1809
// Get the game icon if available
1810
if (info->Ready(GameInfoFlags::ICON) && !info->icon.data.empty()) {
1811
INFO_LOG(Log::System, "Got icon");
1812
gameIcon = env->NewByteArray((jsize)info->icon.data.size());
1813
env->SetByteArrayRegion(gameIcon, 0, (jsize)info->icon.data.size(), (const jbyte *)info->icon.data.data());
1814
}
1815
} else {
1816
INFO_LOG(Log::System, "Failed to query game info");
1817
}
1818
} else {
1819
INFO_LOG(Log::System, "No info from cache");
1820
}
1821
delete cache;
1822
1823
if (teardownThreadManager) {
1824
g_logManager.SetOutputsEnabled((LogOutput)0);
1825
Android_UnregisterStorageCallbacks(env);
1826
Android_StorageSetActivity(nullptr);
1827
g_threadManager.Teardown();
1828
}
1829
1830
// Construct a Java Object[] with two entries: name (String), icon (byte[])
1831
jobjectArray result = env->NewObjectArray(2, env->FindClass("java/lang/Object"), nullptr);
1832
1833
jstring jname = env->NewStringUTF(gameName.c_str());
1834
env->SetObjectArrayElement(result, 0, jname);
1835
env->DeleteLocalRef(jname);
1836
1837
if (gameIcon != nullptr) {
1838
env->SetObjectArrayElement(result, 1, gameIcon);
1839
env->DeleteLocalRef(gameIcon);
1840
}
1841
if (activityRef) {
1842
env->DeleteGlobalRef(activityRef);
1843
}
1844
return result;
1845
}
1846
1847
#endif
1848
1849