* 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>
This commit is contained in:
Gabriel Machado 2024-02-01 11:46:15 -04:00 committed by GitHub
parent e6d012d05d
commit 28ca4cd795
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 454 additions and 4 deletions

View file

@ -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
}
}

View file

@ -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(); }

View file

@ -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>

View file

@ -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"); }
}

View file

@ -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

View file

@ -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() {

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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));
}
}

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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) {}
}
}

View file

@ -5,6 +5,8 @@ public class Task extends Thread {
super(runnable);
}
protected Task() {}
public void runSync() {
start();
waitFinish();

View file

@ -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) {

View file

@ -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() {}
}

View file

@ -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) {

View file

@ -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();
}
}
}

View file

@ -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>
<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>

View file

@ -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>

View file

@ -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>

View file

@ -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>