From c5dd439e53585ef7673473c141b2ba5daff03eff Mon Sep 17 00:00:00 2001 From: SachinVin Date: Tue, 30 Mar 2021 02:00:02 +0530 Subject: [PATCH] android: progress bar for disk shaders --- .../DiskShaderCacheProgress.java | 136 ++++++++++++++++++ src/android/app/src/main/jni/id_cache.cpp | 22 +++ src/android/app/src/main/jni/id_cache.h | 3 + src/android/app/src/main/jni/native.cpp | 34 ++++- .../main/res/layout/dialog_progress_bar.xml | 26 ++++ 5 files changed, 216 insertions(+), 5 deletions(-) create mode 100644 src/android/app/src/main/java/org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress.java create mode 100644 src/android/app/src/main/res/layout/dialog_progress_bar.xml diff --git a/src/android/app/src/main/java/org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress.java b/src/android/app/src/main/java/org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress.java new file mode 100644 index 000000000..84ed4dac0 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress.java @@ -0,0 +1,136 @@ +package org.citra.citra_emu.disk_shader_cache; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Bundle; +import android.os.Handler; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.SeekBar; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; + +import org.citra.citra_emu.CitraApplication; +import org.citra.citra_emu.NativeLibrary; +import org.citra.citra_emu.R; +import org.citra.citra_emu.activities.EmulationActivity; + +import java.util.Objects; + +public class DiskShaderCacheProgress { + + // Equivalent to VideoCore::LoadCallbackStage + public enum LoadCallbackStage { + Prepare, + Decompile, + Build, + Complete, + } + + private static final Object finishLock = new Object(); + private static ProgressDialogFragment fragment; + + public static class ProgressDialogFragment extends DialogFragment { + + private final Handler updateHandler = new Handler(); + + ProgressBar progressBar; + TextView progressText; + AlertDialog dialog; + + static ProgressDialogFragment newInstance(String title, String message) { + ProgressDialogFragment frag = new ProgressDialogFragment(); + Bundle args = new Bundle(); + args.putString("title", title); + args.putString("message", message); + frag.setArguments(args); + return frag; + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final Activity emulationActivity = Objects.requireNonNull(getActivity()); + + final String title = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("title")); + final String message = Objects.requireNonNull(Objects.requireNonNull(getArguments()).getString("message")); + + LayoutInflater inflater = LayoutInflater.from(emulationActivity); + View view = inflater.inflate(R.layout.dialog_progress_bar, null); + + progressBar = view.findViewById(R.id.progress_bar); + progressText = view.findViewById(R.id.progress_text); + progressText.setText(""); + + setCancelable(false); + setRetainInstance(true); + + AlertDialog.Builder builder = new AlertDialog.Builder(emulationActivity); + builder.setTitle(title); + builder.setMessage(message); + builder.setView(view); + builder.setNegativeButton(R.string.abort_button, (dialog, whichButton) -> { + + }); + + dialog = builder.create(); + dialog.create(); + + synchronized (finishLock) { + finishLock.notifyAll(); + } + + return dialog; + } + + private void onUpdateProgress(LoadCallbackStage stage, int progress, int max) { + updateHandler.post(()->{ + progressBar.setProgress(progress); + progressBar.setMax(max); + progressText.setText(String.format("%d/%d", progress, max)); + if (stage == LoadCallbackStage.Build){ + dialog.setMessage("Building shaders"); + } + }); + } + } + + private static void prepareDialog() { + NativeLibrary.sEmulationActivity.get().runOnUiThread(() -> { + final EmulationActivity emulationActivity = NativeLibrary.sEmulationActivity.get(); + fragment = ProgressDialogFragment.newInstance("Loading...", "Preparing shaders"); + fragment.show(emulationActivity.getSupportFragmentManager(), "diskShaders"); + }); + + synchronized (finishLock) { + try { + finishLock.wait(); + } catch (Exception ignored) { + } + } + } + + public static void loadProgress(LoadCallbackStage stage, int progress, int max) { + switch (stage) { + case Prepare: + prepareDialog(); + break; + case Decompile: + case Build: + fragment.onUpdateProgress(stage, progress, max); + break; + case Complete: + // Workaround for when dialog is dismissed when the app is in the background + fragment.dismissAllowingStateLoss(); + break; + } + } +} diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index d716640b6..be69f5874 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -21,6 +21,8 @@ static JavaVM* s_java_vm; static jclass s_native_library_class; static jclass s_core_error_class; static jclass s_savestate_info_class; +static jclass s_disk_cache_progress_class; +static jclass s_load_callback_stage_class; static jmethodID s_on_core_error; static jmethodID s_display_alert_msg; static jmethodID s_display_alert_prompt; @@ -30,6 +32,7 @@ static jmethodID s_landscape_screen_layout; static jmethodID s_exit_emulation_activity; static jmethodID s_request_camera_permission; static jmethodID s_request_mic_permission; +static jmethodID s_disk_cache_load_progress; namespace IDCache { @@ -64,6 +67,14 @@ jclass GetSavestateInfoClass() { return s_savestate_info_class; } +jclass GetDiskCacheProgressClass() { + return s_disk_cache_progress_class; +} + +jclass GetDiskCacheLoadCallbackStageClass() { + return s_load_callback_stage_class; +} + jmethodID GetOnCoreError() { return s_on_core_error; } @@ -100,6 +111,10 @@ jmethodID GetRequestMicPermission() { return s_request_mic_permission; } +jmethodID GetDiskCacheLoadProgress() { + return s_disk_cache_load_progress; +} + } // namespace IDCache #ifdef __cplusplus @@ -130,6 +145,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$SavestateInfo"))); s_core_error_class = reinterpret_cast( env->NewGlobalRef(env->FindClass("org/citra/citra_emu/NativeLibrary$CoreError"))); + s_disk_cache_progress_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress"))); + s_load_callback_stage_class = reinterpret_cast( + env->NewGlobalRef(env->FindClass("org/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); + s_on_core_error = env->GetStaticMethodID( s_native_library_class, "OnCoreError", "(Lorg/citra/citra_emu/NativeLibrary$CoreError;Ljava/lang/String;)Z"); @@ -149,6 +169,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { env->GetStaticMethodID(s_native_library_class, "RequestCameraPermission", "()Z"); s_request_mic_permission = env->GetStaticMethodID(s_native_library_class, "RequestMicPermission", "()Z"); + s_disk_cache_load_progress = + env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(Lorg/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage;II)V"); MiiSelector::InitJNI(env); SoftwareKeyboard::InitJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index fc4987b46..88aa224f4 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -14,6 +14,8 @@ JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); jclass GetCoreErrorClass(); jclass GetSavestateInfoClass(); +jclass GetDiskCacheProgressClass(); +jclass GetDiskCacheLoadCallbackStageClass(); jmethodID GetOnCoreError(); jmethodID GetDisplayAlertMsg(); jmethodID GetDisplayAlertPrompt(); @@ -23,6 +25,7 @@ jmethodID GetLandscapeScreenLayout(); jmethodID GetExitEmulationActivity(); jmethodID GetRequestCameraPermission(); jmethodID GetRequestMicPermission(); +jmethodID GetDiskCacheLoadProgress(); } // namespace IDCache diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index c9401620f..0a0484b01 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -119,6 +119,29 @@ static bool HandleCoreError(Core::System::ResultStatus result, const std::string env->NewStringUTF(details.c_str())) != JNI_FALSE; } +static jobject ToJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage) { + static const std::map LoadCallbackStageMap{ + {VideoCore::LoadCallbackStage::Prepare, "Prepare"}, + {VideoCore::LoadCallbackStage::Decompile, "Decompile"}, + {VideoCore::LoadCallbackStage::Build, "Build"}, + {VideoCore::LoadCallbackStage::Complete, "Complete"}, + }; + + const auto name = LoadCallbackStageMap.at(stage); + + JNIEnv* env = IDCache::GetEnvForThread(); + + const jclass load_callback_stage_class = IDCache::GetDiskCacheLoadCallbackStageClass(); + return env->GetStaticObjectField( + load_callback_stage_class, env->GetStaticFieldID(load_callback_stage_class, name, + "Lorg/citra/citra_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage;")); +} + +static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), IDCache::GetDiskCacheLoadProgress(), ToJavaLoadCallbackStage(stage), (jint) progress, (jint) max); +} + static Camera::NDK::Factory* g_ndk_factory{}; static void TryShutdown() { @@ -190,22 +213,23 @@ static Core::System::ResultStatus RunCitra(const std::string& filepath) { is_running = true; pause_emulation = false; + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + std::unique_ptr cpu_context{window->CreateSharedContext()}; if (Settings::values.use_asynchronous_gpu_emulation) { cpu_context->MakeCurrent(); } system.Renderer().Rasterizer()->LoadDiskResources( - !is_running, [](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) { - if(value%10 == 0 || value + 1 == total){ - LOG_INFO(Frontend, "Shader cache Stage {}: {}/{}", stage, value + 1, total); - } - }); + !is_running, &LoadDiskCacheProgress + ); if (Settings::values.use_asynchronous_gpu_emulation) { cpu_context->DoneCurrent(); } + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + SCOPE_EXIT({ TryShutdown(); }); // Audio stretching on Android is only useful with lower framerates, disable it when fullspeed diff --git a/src/android/app/src/main/res/layout/dialog_progress_bar.xml b/src/android/app/src/main/res/layout/dialog_progress_bar.xml new file mode 100644 index 000000000..a81157a29 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file