From 28ca4cd79560ee1ce1cdba9d0f67bbfeeb9b5186 Mon Sep 17 00:00:00 2001
From: Gabriel Machado <97042217+GabrielBRDeveloper@users.noreply.github.com>
Date: Thu, 1 Feb 2024 11:46:15 -0400
Subject: [PATCH] Logger (#397)

* Initial commit

* add shader-jit option

* add translate to word "graphics' for ptbr

* Native logger

* Bonk

* fix

---------

Co-authored-by: gabriel <gabriel>
Co-authored-by: wheremyfoodat <44909372+wheremyfoodat@users.noreply.github.com>
---
 include/logger.hpp                            |  10 +-
 src/jni_driver.cpp                            |   7 ++
 src/pandroid/app/src/main/AndroidManifest.xml |   2 +
 .../com/panda3ds/pandroid/AlberDriver.java    |   2 +
 .../panda3ds/pandroid/app/GameActivity.java   |   6 +
 .../pandroid/app/PandroidApplication.java     |   6 +
 .../app/base/BasePreferenceFragment.java      |   9 ++
 .../pandroid/app/main/SettingsFragment.java   |   2 +
 .../app/preferences/DeveloperPreferences.java |  49 +++++++++
 .../pandroid/app/services/LoggerService.java  | 104 ++++++++++++++++++
 .../pandroid/data/config/GlobalConfig.java    |   4 +-
 .../pandroid/lang/PipeStreamTask.java         |  40 +++++++
 .../java/com/panda3ds/pandroid/lang/Task.java |   2 +
 .../panda3ds/pandroid/utils/FileUtils.java    |  19 ++++
 .../pandroid/utils/PerformanceMonitor.java    |  64 +++++++++++
 .../pandroid/view/PandaGlRenderer.java        |  12 +-
 .../pandroid/view/utils/PerformanceView.java  |  64 +++++++++++
 .../src/main/res/values-pt-rBR/strings.xml    |  11 +-
 .../app/src/main/res/values/strings.xml       |   9 ++
 .../main/res/xml/developer_preferences.xml    |  29 +++++
 .../src/main/res/xml/start_preferences.xml    |   7 ++
 21 files changed, 454 insertions(+), 4 deletions(-)
 create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java
 create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java
 create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java
 create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java
 create mode 100644 src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java
 create mode 100644 src/pandroid/app/src/main/res/xml/developer_preferences.xml

diff --git a/include/logger.hpp b/include/logger.hpp
index 82d90410..e021a685 100644
--- a/include/logger.hpp
+++ b/include/logger.hpp
@@ -2,6 +2,10 @@
 #include <cstdarg>
 #include <fstream>
 
+#ifdef __ANDROID__
+#include <android/log.h>
+#endif
+
 namespace Log {
 	// Our logger class
 	template <bool enabled>
@@ -12,7 +16,11 @@ namespace Log {
 
 			std::va_list args;
 			va_start(args, fmt);
+#ifdef __ANDROID__
+			__android_log_vprint(ANDROID_LOG_DEFAULT, "Panda3DS", fmt, args);
+#else
 			std::vprintf(fmt, args);
+#endif
 			va_end(args);
 		}
 	};
@@ -81,4 +89,4 @@ namespace Log {
 #else
 #define MAKE_LOG_FUNCTION(functionName, logger) MAKE_LOG_FUNCTION_USER(functionName, logger)
 #endif
-}
+}
\ No newline at end of file
diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp
index 6eeb727a..d962f23e 100644
--- a/src/jni_driver.cpp
+++ b/src/jni_driver.cpp
@@ -35,6 +35,13 @@ JNIEnv* jniEnv() {
 
 extern "C" {
 
+#define MAKE_SETTING(functionName, type, settingName) \
+AlberFunction(void, functionName) (JNIEnv* env, jobject obj, type value) { emulator->getConfig().settingName = value; }
+
+MAKE_SETTING(setShaderJitEnabled, jboolean, shaderJitEnabled)
+
+#undef MAKE_SETTING
+
 AlberFunction(void, Setup)(JNIEnv* env, jobject obj) { env->GetJavaVM(&jvm); }
 AlberFunction(void, Pause)(JNIEnv* env, jobject obj) { emulator->pause(); }
 AlberFunction(void, Resume)(JNIEnv* env, jobject obj) { emulator->resume(); }
diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml
index b6276493..c66d37af 100644
--- a/src/pandroid/app/src/main/AndroidManifest.xml
+++ b/src/pandroid/app/src/main/AndroidManifest.xml
@@ -48,5 +48,7 @@
 
         <activity android:name=".app.preferences.InputMapActivity"
             android:configChanges="density|orientation|screenSize"/>
+
+        <service android:name=".app.services.LoggerService" android:process=":logger_service"/>
     </application>
 </manifest>
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java
index 5cff703c..00b7842b 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/AlberDriver.java
@@ -22,5 +22,7 @@ public class AlberDriver {
 	public static native void LoadLuaScript(String script);
 	public static native byte[] GetSmdh();
 
+	public static native void setShaderJitEnabled(boolean enable);
+
 	static { System.loadLibrary("Alber"); }
 }
\ No newline at end of file
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java
index aced6faa..f7050e99 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/GameActivity.java
@@ -21,6 +21,7 @@ import com.panda3ds.pandroid.input.InputMap;
 import com.panda3ds.pandroid.utils.Constants;
 import com.panda3ds.pandroid.view.PandaGlSurfaceView;
 import com.panda3ds.pandroid.view.PandaLayoutController;
+import com.panda3ds.pandroid.view.utils.PerformanceView;
 
 public class GameActivity extends BaseActivity {
 	private final DrawerFragment drawerFragment = new DrawerFragment();
@@ -56,6 +57,11 @@ public class GameActivity extends BaseActivity {
 		((CheckBox) findViewById(R.id.hide_screen_controller)).setChecked(GlobalConfig.get(GlobalConfig.KEY_SCREEN_GAMEPAD_VISIBLE));
 
 		getSupportFragmentManager().beginTransaction().replace(R.id.drawer_fragment, drawerFragment).commitNow();
+
+		if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) {
+			PerformanceView view = new PerformanceView(this);
+			((FrameLayout) findViewById(R.id.panda_gl_frame)).addView(view, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+		}
 	}
 
 	@Override
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java
index 02fbbbcc..b0cdc935 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/PandroidApplication.java
@@ -2,11 +2,13 @@ package com.panda3ds.pandroid.app;
 
 import android.app.Application;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 
 import com.panda3ds.pandroid.AlberDriver;
 import com.panda3ds.pandroid.R;
+import com.panda3ds.pandroid.app.services.LoggerService;
 import com.panda3ds.pandroid.data.config.GlobalConfig;
 import com.panda3ds.pandroid.input.InputMap;
 import com.panda3ds.pandroid.utils.GameUtils;
@@ -24,6 +26,10 @@ public class PandroidApplication extends Application {
 		GameUtils.initialize();
 		InputMap.initialize();
 		AlberDriver.Setup();
+
+		if (GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE)) {
+			startService(new Intent(this, LoggerService.class));
+		}
 	}
 
 	public static int getThemeId() {
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java
index 3cd28f4b..9482df1d 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/base/BasePreferenceFragment.java
@@ -1,8 +1,13 @@
 package com.panda3ds.pandroid.app.base;
 
 import android.annotation.SuppressLint;
+
+import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatActivity;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.SwitchPreference;
+
 import com.panda3ds.pandroid.lang.Function;
 
 
@@ -15,4 +20,8 @@ public abstract class BasePreferenceFragment extends PreferenceFragmentCompat {
 			return false;
 		});
 	}
+
+	protected void setActivityTitle(@StringRes int titleId) {
+		((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(titleId);
+	}
 }
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java
index b3bebf8f..bfe33a2b 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/main/SettingsFragment.java
@@ -8,6 +8,7 @@ import com.panda3ds.pandroid.R;
 import com.panda3ds.pandroid.app.PreferenceActivity;
 import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
 import com.panda3ds.pandroid.app.preferences.AppearancePreferences;
+import com.panda3ds.pandroid.app.preferences.DeveloperPreferences;
 import com.panda3ds.pandroid.app.preferences.InputPreferences;
 
 public class SettingsFragment extends BasePreferenceFragment {
@@ -16,5 +17,6 @@ public class SettingsFragment extends BasePreferenceFragment {
         setPreferencesFromResource(R.xml.start_preferences, rootKey);
         setItemClick("input", (item) -> PreferenceActivity.launch(requireContext(), InputPreferences.class));
         setItemClick("appearance", (item)-> PreferenceActivity.launch(requireContext(), AppearancePreferences.class));
+        setItemClick("developer", (item)-> PreferenceActivity.launch(requireContext(), DeveloperPreferences.class));
     }
 }
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java
new file mode 100644
index 00000000..f131f0a0
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/preferences/DeveloperPreferences.java
@@ -0,0 +1,49 @@
+package com.panda3ds.pandroid.app.preferences;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.preference.SwitchPreference;
+
+import com.panda3ds.pandroid.R;
+import com.panda3ds.pandroid.app.PandroidApplication;
+import com.panda3ds.pandroid.app.base.BasePreferenceFragment;
+import com.panda3ds.pandroid.app.services.LoggerService;
+import com.panda3ds.pandroid.data.config.GlobalConfig;
+
+public class DeveloperPreferences extends BasePreferenceFragment {
+    @Override
+    public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) {
+        setPreferencesFromResource(R.xml.developer_preferences, rootKey);
+        setActivityTitle(R.string.developer_options);
+
+        setItemClick("performanceMonitor", pref -> GlobalConfig.set(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY, ((SwitchPreference) pref).isChecked()));
+        setItemClick("shaderJit", pref -> GlobalConfig.set(GlobalConfig.KEY_SHADER_JIT, ((SwitchPreference) pref).isChecked()));
+        setItemClick("loggerService", pref -> {
+            boolean checked = ((SwitchPreference) pref).isChecked();
+            Context ctx = PandroidApplication.getAppContext();
+            if (checked) {
+                ctx.startService(new Intent(ctx, LoggerService.class));
+            } else {
+                ctx.stopService(new Intent(ctx, LoggerService.class));
+            }
+            GlobalConfig.set(GlobalConfig.KEY_LOGGER_SERVICE, checked);
+        });
+
+        refresh();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refresh();
+    }
+
+    private void refresh() {
+        ((SwitchPreference) findPreference("performanceMonitor")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY));
+        ((SwitchPreference) findPreference("loggerService")).setChecked(GlobalConfig.get(GlobalConfig.KEY_LOGGER_SERVICE));
+        ((SwitchPreference) findPreference("shaderJit")).setChecked(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
+    }
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java
new file mode 100644
index 00000000..e44f3503
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/app/services/LoggerService.java
@@ -0,0 +1,104 @@
+package com.panda3ds.pandroid.app.services;
+
+import android.app.Service;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.os.Build;
+import android.os.IBinder;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.panda3ds.pandroid.lang.PipeStreamTask;
+import com.panda3ds.pandroid.lang.Task;
+import com.panda3ds.pandroid.utils.Constants;
+import com.panda3ds.pandroid.utils.FileUtils;
+
+import java.io.OutputStream;
+import java.util.Arrays;
+
+public class LoggerService extends Service {
+    private static final long MAX_LOG_SIZE = 1024 * 1024 * 4; // 4MB
+
+    private PipeStreamTask errorTask;
+    private PipeStreamTask outputTask;
+    private Process logcat;
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        try {
+            Runtime.getRuntime().exec(new String[]{"logcat", "-c"}).waitFor();
+            logcat = Runtime.getRuntime().exec(new String[]{"logcat"});
+            String logPath = getExternalMediaDirs()[0].getAbsolutePath();
+            FileUtils.createDir(logPath, "logs");
+            logPath = logPath + "/logs";
+
+            if (FileUtils.exists(logPath + "/last.txt")) {
+                FileUtils.delete(logPath + "/last.txt");
+            }
+
+            if (FileUtils.exists(logPath + "/current.txt")) {
+                FileUtils.rename(logPath + "/current.txt", "last.txt");
+            }
+
+            OutputStream stream = FileUtils.getOutputStream(logPath + "/current.txt");
+            errorTask = new PipeStreamTask(logcat.getErrorStream(), stream, MAX_LOG_SIZE);
+            outputTask = new PipeStreamTask(logcat.getInputStream(), stream, MAX_LOG_SIZE);
+
+            errorTask.start();
+            outputTask.start();
+
+            Log.i(Constants.LOG_TAG, "Started logger service");
+            logDeviceInfo();
+        } catch (Exception e) {
+            stopSelf();
+            Log.e(Constants.LOG_TAG, "Failed to start logger service");
+        }
+    }
+
+    private void logDeviceInfo() {
+        Log.i(Constants.LOG_TAG, "----------------------");
+        Log.i(Constants.LOG_TAG, "Android SDK: " + Build.VERSION.SDK_INT);
+        Log.i(Constants.LOG_TAG, "Device: " + Build.DEVICE);
+        Log.i(Constants.LOG_TAG, "Model: " + Build.MANUFACTURER + " " + Build.MODEL);
+        Log.i(Constants.LOG_TAG, "ABIs: " + Arrays.toString(Build.SUPPORTED_ABIS));
+        try {
+            PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0);
+            Log.i(Constants.LOG_TAG, "");
+            Log.i(Constants.LOG_TAG, "Package: " + info.packageName);
+            Log.i(Constants.LOG_TAG, "Install location: " + info.installLocation);
+            Log.i(Constants.LOG_TAG, "App version: " + info.versionName + " (" + info.versionCode + ")");
+        } catch (Exception e) {
+            Log.e(Constants.LOG_TAG, "Error obtaining package info: " + e);
+        }
+        Log.i(Constants.LOG_TAG, "----------------------");
+    }
+
+    @Override
+    public void onTaskRemoved(Intent rootIntent) {
+        stopSelf();
+        //This is a time for app save save log file
+        try {
+            Thread.sleep(1000);
+        } catch (Exception e) {}
+        super.onTaskRemoved(rootIntent);
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(Constants.LOG_TAG, "Logger service terminating");
+        errorTask.close();
+        outputTask.close();
+        try {
+            logcat.destroy();
+        } catch (Throwable t) {}
+        super.onDestroy();
+    }
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java
index d6dbe3b8..bff1f9e0 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/data/config/GlobalConfig.java
@@ -5,7 +5,6 @@ import com.panda3ds.pandroid.data.GsonConfigParser;
 import com.panda3ds.pandroid.utils.Constants;
 
 import java.io.Serializable;
-import java.util.HashMap;
 import java.util.Map;
 
 public class GlobalConfig {
@@ -19,6 +18,9 @@ public class GlobalConfig {
 
     public static DataModel data;
 
+    public static final Key<Boolean> KEY_SHADER_JIT = new Key<>("emu.shader_jit", false);
+    public static final Key<Boolean> KEY_SHOW_PERFORMANCE_OVERLAY = new Key<>("dev.performanceOverlay", false);
+    public static final Key<Boolean> KEY_LOGGER_SERVICE = new Key<>("dev.loggerService", false);
     public static final Key<Integer> KEY_APP_THEME = new Key<>("app.theme", THEME_ANDROID);
     public static final Key<Boolean> KEY_SCREEN_GAMEPAD_VISIBLE = new Key<>("app.screen_gamepad.visible", true);
 
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java
new file mode 100644
index 00000000..e4bbda98
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/PipeStreamTask.java
@@ -0,0 +1,40 @@
+package com.panda3ds.pandroid.lang;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class PipeStreamTask extends Task {
+    private final InputStream input;
+    private final OutputStream output;
+    private final long limit;
+    private long size;
+
+    public PipeStreamTask(InputStream input, OutputStream output, long limit) {
+        this.input = input;
+        this.output = output;
+        this.limit = limit;
+    }
+
+    @Override
+    public void run() {
+        super.run();
+        int data;
+        try {
+            while ((data = input.read()) != -1) {
+                output.write(data);
+                if (++size > limit) {
+                    break;
+                }
+            }
+        } catch (Exception e) {}
+        close();
+    }
+
+    public void close() {
+        try {
+            output.flush();
+            output.close();
+            input.close();
+        } catch (Exception e) {}
+    }
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java
index 7745883d..8de344b4 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/lang/Task.java
@@ -5,6 +5,8 @@ public class Task extends Thread {
         super(runnable);
     }
 
+    protected Task() {}
+
     public void runSync() {
         start();
         waitFinish();
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
index 45faf5a4..1746f1c9 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/FileUtils.java
@@ -70,6 +70,25 @@ public class FileUtils {
         return parseFile(path).exists();
     }
 
+    public static void rename(String path, String newName){
+        parseFile(path).renameTo(newName);
+    }
+
+    public static void delete(String path) {
+        DocumentFile file = parseFile(path);
+
+        if (file.exists()) {
+            if (file.isDirectory()) {
+                String[] children = listFiles(path);
+                for (String child : children) {
+                    delete(path + "/" + child);
+                }
+            }
+
+            file.delete();
+        }
+    }
+
     public static boolean createDir(String path, String name) {
         DocumentFile folder = parseFile(path);
         if (folder.findFile(name) != null) {
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java
new file mode 100644
index 00000000..23adbf13
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/utils/PerformanceMonitor.java
@@ -0,0 +1,64 @@
+package com.panda3ds.pandroid.utils;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.Debug;
+import android.os.Process;
+
+import com.panda3ds.pandroid.app.PandroidApplication;
+import com.panda3ds.pandroid.data.config.GlobalConfig;
+
+public class PerformanceMonitor {
+    private static int fps = 1;
+    private static String backend = "";
+    private static int frames = 0;
+    private static long lastUpdate = 0;
+    private static long totalMemory = 1;
+    private static long availableMemory = 0;
+
+    public static void initialize(String backendName) {
+        fps = 1;
+        backend = backendName;
+    }
+
+    public static void runFrame() {
+        if (GlobalConfig.get(GlobalConfig.KEY_SHOW_PERFORMANCE_OVERLAY)) {
+            frames++;
+            if (System.currentTimeMillis() - lastUpdate > 1000) {
+                lastUpdate = System.currentTimeMillis();
+                fps = frames;
+                frames = 0;
+                try {
+                    Context ctx = PandroidApplication.getAppContext();
+                    ActivityManager manager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
+                    ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo();
+                    manager.getMemoryInfo(info);
+                    totalMemory = info.totalMem;
+                    availableMemory = info.availMem;
+                } catch (Exception e) {}
+            }
+        }
+    }
+
+    public static long getUsedMemory() {
+        return Math.max(1, totalMemory - availableMemory);
+    }
+
+    public static long getTotalMemory() {
+        return totalMemory;
+    }
+
+    public static long getAvailableMemory() {
+        return availableMemory;
+    }
+
+    public static int getFps() {
+        return fps;
+    }
+
+    public static String getBackend() {
+        return backend;
+    }
+
+    public static void destroy() {}
+}
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
index 52e609a3..c39b36b3 100644
--- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/PandaGlRenderer.java
@@ -7,8 +7,10 @@ import android.graphics.Rect;
 import android.opengl.GLSurfaceView;
 import android.util.Log;
 import com.panda3ds.pandroid.AlberDriver;
+import com.panda3ds.pandroid.data.config.GlobalConfig;
 import com.panda3ds.pandroid.utils.Constants;
 import com.panda3ds.pandroid.utils.GameUtils;
+import com.panda3ds.pandroid.utils.PerformanceMonitor;
 import com.panda3ds.pandroid.view.renderer.ConsoleRenderer;
 import com.panda3ds.pandroid.view.renderer.layout.ConsoleLayout;
 import com.panda3ds.pandroid.view.renderer.layout.DefaultScreenLayout;
@@ -38,9 +40,12 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
 		if (screenTexture != 0) {
 			glDeleteTextures(1, new int[] {screenTexture}, 0);
 		}
-		if (screenFbo != 0) {
+		
+        if (screenFbo != 0) {
 			glDeleteFramebuffers(1, new int[] {screenFbo}, 0);
 		}
+
+		PerformanceMonitor.destroy();
 		super.finalize();
 	}
 
@@ -78,6 +83,7 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
 		glBindFramebuffer(GL_FRAMEBUFFER, 0);
 
 		AlberDriver.Initialize();
+		AlberDriver.setShaderJitEnabled(GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT));
 		AlberDriver.LoadRom(romPath);
 
 		// Load the SMDH
@@ -92,6 +98,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
 			GameUtils.removeGame(game);
 			GameUtils.addGame(GameMetadata.applySMDH(game, smdh));
 		}
+    
+		PerformanceMonitor.initialize(getBackendName());
 	}
 
 	public void onDrawFrame(GL10 unused) {
@@ -114,6 +122,8 @@ public class PandaGlRenderer implements GLSurfaceView.Renderer, ConsoleRenderer
 				screenHeight - bottomScreen.bottom, GL_COLOR_BUFFER_BIT, GL_LINEAR
 			);
 		}
+
+		PerformanceMonitor.runFrame();
 	}
 
 	public void onSurfaceChanged(GL10 unused, int width, int height) {
diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java
new file mode 100644
index 00000000..e4d7be15
--- /dev/null
+++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/view/utils/PerformanceView.java
@@ -0,0 +1,64 @@
+package com.panda3ds.pandroid.view.utils;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.text.Html;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.AppCompatTextView;
+
+import com.panda3ds.pandroid.data.config.GlobalConfig;
+import com.panda3ds.pandroid.utils.PerformanceMonitor;
+
+public class PerformanceView extends AppCompatTextView {
+    private boolean running = false;
+
+    public PerformanceView(@NonNull Context context) {
+        this(context, null);
+    }
+
+    public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs) {
+        this(context, attrs,0);
+    }
+
+    public PerformanceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        int padding = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 10, getResources().getDisplayMetrics());
+        setPadding(padding,padding,padding,padding);
+        setTextColor(Color.WHITE);
+        setShadowLayer(padding,0,0,Color.BLACK);
+    }
+
+    public void refresh(){
+        running = isShown();
+        if (!running) {
+            return;
+        }
+
+        String debug = "";
+
+        // Calculate total memory in MB and the current memory usage
+        int memoryTotalMb = (int) Math.round(PerformanceMonitor.getTotalMemory() / (1024.0 * 1024.0));
+        int memoryUsageMb = (int) Math.round(PerformanceMonitor.getUsedMemory() / (1024.0 * 1024.0));
+
+        debug += "<b>FPS: </b>" + PerformanceMonitor.getFps() + "<br>";
+        debug += "<b>RAM: </b>" + Math.round(((float) memoryUsageMb / memoryTotalMb) * 100) + "%  (" + memoryUsageMb + "MB/" + memoryTotalMb + "MB)<br>";
+        debug += "<b>BACKEND: </b>" + PerformanceMonitor.getBackend() + (GlobalConfig.get(GlobalConfig.KEY_SHADER_JIT) ? " + JIT" : "") + "<br>";
+        setText(Html.fromHtml(debug, Html.FROM_HTML_MODE_COMPACT));
+        postDelayed(this::refresh, 250);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        
+        if (!running) {
+            refresh();
+        }
+    }
+}
diff --git a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
index 065b9e4f..1198d66b 100644
--- a/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
+++ b/src/pandroid/app/src/main/res/values-pt-rBR/strings.xml
@@ -45,4 +45,13 @@
     <string name="open_file">Abrir arquivo</string>
     <string name="create_new">Criar novo</string>
     <string name="running_ff">Executando \"%s\" ...</string>
-</resources>
\ No newline at end of file
+    <string name="developer_options">Opções de desenvolvedor</string>
+    <string name="pref_developer_summary">Depuração, mostrar fps, etc.</string>
+    <string name="pref_performance_monitor_title">Monitor de desempenho</string>
+    <string name="pref_performance_monitor_summary">Mostrar um overlay com fps, memoria, etc.</string>
+    <string name="pref_logger_service_title">Depuração</string>
+    <string name="pref_logger_service_summary">Grave os registros para um arquivo.</string>
+    <string name="pref_shader_jit_title">Shader Jit</string>
+    <string name="pref_shader_jit_summary">Usar recompilador de shaders.</string>
+    <string name="graphics">Gráficos</string>
+</resources>
diff --git a/src/pandroid/app/src/main/res/values/strings.xml b/src/pandroid/app/src/main/res/values/strings.xml
index e0de62e1..4c64439c 100644
--- a/src/pandroid/app/src/main/res/values/strings.xml
+++ b/src/pandroid/app/src/main/res/values/strings.xml
@@ -46,4 +46,13 @@
     <string name="open_file">Open file</string>
     <string name="create_new">Create new</string>
     <string name="running_ff">Running \"%s\" ...</string>
+    <string name="developer_options">Developer options</string>
+    <string name="pref_developer_summary">Logger, FPS Counter, etc.</string>
+    <string name="pref_performance_monitor_title">Performance monitor</string>
+    <string name="pref_performance_monitor_summary">Show overlay with fps, memory, etc.</string>
+    <string name="pref_logger_service_title">Logger</string>
+    <string name="pref_logger_service_summary">Store application logs to file.</string>
+    <string name="pref_shader_jit_title">Shader JIT</string>
+    <string name="pref_shader_jit_summary">Use shader recompiler.</string>
+    <string name="graphics">Graphics</string>
 </resources>
diff --git a/src/pandroid/app/src/main/res/xml/developer_preferences.xml b/src/pandroid/app/src/main/res/xml/developer_preferences.xml
new file mode 100644
index 00000000..96ce8906
--- /dev/null
+++ b/src/pandroid/app/src/main/res/xml/developer_preferences.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <SwitchPreference
+        android:key="performanceMonitor"
+        app:title="@string/pref_performance_monitor_title"
+        app:summary="@string/pref_performance_monitor_summary"
+        app:iconSpaceReserved="false"/>
+
+    <SwitchPreference
+        android:key="loggerService"
+        app:iconSpaceReserved="false"
+        app:title="@string/pref_logger_service_title"
+        android:summary="@string/pref_logger_service_summary"/>
+
+    <PreferenceCategory
+        app:iconSpaceReserved="false"
+        app:title="@string/graphics">
+
+        <SwitchPreference
+            app:key="shaderJit"
+            app:title="@string/pref_shader_jit_title"
+            app:summary="@string/pref_shader_jit_summary"
+            app:iconSpaceReserved="false"/>
+
+    </PreferenceCategory>
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/src/pandroid/app/src/main/res/xml/start_preferences.xml b/src/pandroid/app/src/main/res/xml/start_preferences.xml
index 878aa920..5eeb1954 100644
--- a/src/pandroid/app/src/main/res/xml/start_preferences.xml
+++ b/src/pandroid/app/src/main/res/xml/start_preferences.xml
@@ -23,4 +23,11 @@
         app:summary="@string/pref_appearance_summary"
         app:layout="@layout/preference_start_item"/>
 
+    <Preference
+        app:key="developer"
+        app:icon="@drawable/ic_code"
+        app:title="@string/developer_options"
+        app:summary="@string/pref_developer_summary"
+        app:layout="@layout/preference_start_item"/>
+
 </PreferenceScreen>
\ No newline at end of file