diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a80b06a..4aa4456a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -393,7 +393,7 @@ else() endif() if(ANDROID) - target_link_libraries(Alber PRIVATE log) + target_link_libraries(Alber PRIVATE EGL log) endif() if(ENABLE_LTO OR ENABLE_USER_BUILD) diff --git a/src/jni_driver.cpp b/src/jni_driver.cpp index c2a00d7b..74ac0732 100644 --- a/src/jni_driver.cpp +++ b/src/jni_driver.cpp @@ -1,11 +1,46 @@ #include +#include #include +#include +#include "renderer_gl/renderer_gl.hpp" #include "emulator.hpp" -std::unique_ptr emulator; +std::unique_ptr emulator = nullptr; +RendererGL* renderer = nullptr; +bool romLoaded = false; extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Initialize(JNIEnv* env, jobject obj) { - __android_log_print(ANDROID_LOG_INFO, "Panda3DS", "Initializing Alber Driver"); emulator = std::make_unique(); - __android_log_print(ANDROID_LOG_INFO, "Panda3DS", "Done"); + if (emulator->getRendererType() != RendererType::OpenGL) { + throw std::runtime_error("Renderer is not OpenGL"); + } + renderer = static_cast(emulator->getRenderer()); + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES Before %d.%d", GLVersion.major, GLVersion.minor); + if (!gladLoadGLES2Loader(reinterpret_cast(eglGetProcAddress))) { + throw std::runtime_error("OpenGL ES init failed"); + } + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "OpenGL ES %d.%d", GLVersion.major, GLVersion.minor); + emulator->initGraphicsContext(nullptr); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_RunFrame(JNIEnv* env, jobject obj, jint fbo) { + renderer->setFBO(fbo); + renderer->resetStateManager(); + emulator->runFrame(); +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_Finalize(JNIEnv* env, jobject obj) { + emulator = nullptr; + renderer = nullptr; +} + +extern "C" JNIEXPORT jboolean JNICALL Java_com_panda3ds_pandroid_AlberDriver_HasRomLoaded(JNIEnv* env, jobject obj) { + return romLoaded; +} + +extern "C" JNIEXPORT void JNICALL Java_com_panda3ds_pandroid_AlberDriver_LoadRom(JNIEnv* env, jobject obj, jstring path) { + const char* pathStr = env->GetStringUTFChars(path, nullptr); + __android_log_print(ANDROID_LOG_INFO, "AlberDriver", "Loading ROM %s", pathStr); + romLoaded = emulator->loadROM(pathStr); + env->ReleaseStringUTFChars(path, pathStr); } \ No newline at end of file diff --git a/src/pandroid/app/src/main/AndroidManifest.xml b/src/pandroid/app/src/main/AndroidManifest.xml index 8047484a..0effd35f 100644 --- a/src/pandroid/app/src/main/AndroidManifest.xml +++ b/src/pandroid/app/src/main/AndroidManifest.xml @@ -22,5 +22,5 @@ - + \ No newline at end of file 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 55bcd47e..1021e1d2 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 @@ -2,7 +2,15 @@ package com.panda3ds.pandroid; public class AlberDriver { + AlberDriver() { + super(); + } + public static native void Initialize(); + public static native void RunFrame(int fbo); + public static native boolean HasRomLoaded(); + public static native void LoadRom(String path); + public static native void Finalize(); static { System.loadLibrary("Alber"); diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java index e2ab412b..b7b3726d 100644 --- a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/MainActivity.java @@ -1,16 +1,67 @@ package com.panda3ds.pandroid; import androidx.appcompat.app.AppCompatActivity; +import static android.provider.Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION; +import android.content.Intent; +import android.os.Build; import android.os.Bundle; +import android.view.WindowInsets; +import android.view.View; +import android.os.Environment; +import android.widget.Toast; +import android.widget.FrameLayout; +import com.panda3ds.pandroid.PathUtils; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; public class MainActivity extends AppCompatActivity { + PandaGlSurfaceView glView; + + private static final int PICK_3DS_ROM = 2; + + private void openFile() { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + startActivityForResult(intent, PICK_3DS_ROM); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); + if (!Environment.isExternalStorageManager()) { + Intent intent = new Intent(ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); + startActivity(intent); + } + + glView = new PandaGlSurfaceView(this); + setContentView(glView); + FloatingActionButton fab = new FloatingActionButton(this); + fab.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + openFile(); + } + }); + FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(200, 200); + addContentView(fab, params); + } - AlberDriver.Initialize(); + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == PICK_3DS_ROM) { + if (resultCode == RESULT_OK) { + String path = PathUtils.getPath(getApplicationContext(), data.getData()); + Toast.makeText(getApplicationContext(), "pandroid opening " + path, Toast.LENGTH_LONG).show(); + glView.queueEvent(new Runnable() { + @Override + public void run() { + AlberDriver.LoadRom(path); + } + }); + } + } } } \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlRenderer.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlRenderer.java new file mode 100644 index 00000000..cb6f90da --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlRenderer.java @@ -0,0 +1,79 @@ +package com.panda3ds.pandroid; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import static android.opengl.GLES32.*; + +import android.content.res.Resources; +import android.opengl.GLSurfaceView; +import android.util.DisplayMetrics; +import android.util.Log; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.FloatBuffer; + +public class PandaGlRenderer implements GLSurfaceView.Renderer { + int screenWidth, screenHeight; + int screenTexture; + public int screenFbo; + + PandaGlRenderer() { + super(); + } + + @Override + protected void finalize() throws Throwable { + if (screenTexture != 0) { + glDeleteTextures(1, new int[]{screenTexture}, 0); + } + if (screenFbo != 0) { + glDeleteFramebuffers(1, new int[]{screenFbo}, 0); + } + super.finalize(); + } + + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + Log.i("pandroid", glGetString(GL_EXTENSIONS)); + Log.w("pandroid", glGetString(GL_VERSION)); + screenWidth = Resources.getSystem().getDisplayMetrics().widthPixels; + screenHeight = Resources.getSystem().getDisplayMetrics().heightPixels; + + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + + int[] generateBuffer = new int[1]; + glGenTextures(1, generateBuffer, 0); + screenTexture = generateBuffer[0]; + glBindTexture(GL_TEXTURE_2D, screenTexture); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, screenWidth, screenHeight); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindTexture(GL_TEXTURE_2D, 0); + + glGenFramebuffers(1, generateBuffer, 0); + screenFbo = generateBuffer[0]; + glBindFramebuffer(GL_FRAMEBUFFER, screenFbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + AlberDriver.Initialize(); + } + + public void onDrawFrame(GL10 unused) { + if (AlberDriver.HasRomLoaded()) { + AlberDriver.RunFrame(screenFbo); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_READ_FRAMEBUFFER, screenFbo); + glBlitFramebuffer(0, 0, 400, 480, 0, 0, screenWidth, screenHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR); + } + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + glViewport(0, 0, width, height); + screenWidth = width; + screenHeight = height; + glDisable(GL_SCISSOR_TEST); + } +} diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlSurfaceView.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlSurfaceView.java new file mode 100644 index 00000000..657b327c --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PandaGlSurfaceView.java @@ -0,0 +1,19 @@ +package com.panda3ds.pandroid; + +import android.app.Activity; +import android.content.Context; +import android.opengl.GLSurfaceView; +import android.util.DisplayMetrics; + +import com.panda3ds.pandroid.PandaGlRenderer; + +public class PandaGlSurfaceView extends GLSurfaceView { + final PandaGlRenderer renderer; + + public PandaGlSurfaceView(Context context) { + super(context); + setEGLContextClientVersion(3); + renderer = new PandaGlRenderer(); + setRenderer(renderer); + } +} \ No newline at end of file diff --git a/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PathUtils.java b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PathUtils.java new file mode 100644 index 00000000..740223eb --- /dev/null +++ b/src/pandroid/app/src/main/java/com/panda3ds/pandroid/PathUtils.java @@ -0,0 +1,125 @@ +package com.panda3ds.pandroid; + +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class PathUtils { + + public static String getPath(final Context context, final Uri uri) { + + // DocumentProvider + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { + + if (isExternalStorageDocument(uri)) {// ExternalStorageProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + String storageDefinition; + + + if("primary".equalsIgnoreCase(type)){ + + return Environment.getExternalStorageDirectory() + "/" + split[1]; + + } else { + + if(Environment.isExternalStorageRemovable()){ + storageDefinition = "EXTERNAL_STORAGE"; + + } else{ + storageDefinition = "SECONDARY_STORAGE"; + } + + return System.getenv(storageDefinition) + "/" + split[1]; + } + + } else if (isDownloadsDocument(uri)) {// DownloadsProvider + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + + } else if (isMediaDocument(uri)) {// MediaProvider + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + + } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore (and general) + + // Return the remote address + if (isGooglePhotosUri(uri)) + return uri.getLastPathSegment(); + + return getDataColumn(context, uri, null, null); + + } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File + return uri.getPath(); + } + + return null; + } + + public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } +} \ No newline at end of file