From 4006468f7368d0afe51be11a466e5b33c62e362d Mon Sep 17 00:00:00 2001 From: bunnei Date: Tue, 11 Apr 2023 22:03:19 -0700 Subject: [PATCH] android: video_core: Add support for disk shader cache. (#64) --- .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 1 + .../DiskShaderCacheProgress.kt | 46 ++++++++ .../ShaderProgressViewModel.kt | 31 ++++++ .../ui/ShaderProgressDialogFragment.kt | 101 ++++++++++++++++++ .../settings/ui/SettingsFragmentPresenter.kt | 12 +++ .../features/settings/utils/SettingsFile.kt | 1 + src/android/app/src/main/jni/config.cpp | 4 - src/android/app/src/main/jni/id_cache.cpp | 27 +++++ src/android/app/src/main/jni/id_cache.h | 5 + src/android/app/src/main/jni/native.cpp | 18 ++++ .../main/res/layout/dialog_progress_bar.xml | 9 ++ .../app/src/main/res/values/strings.xml | 7 ++ 12 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index 3ab685b9b..1e654777a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -42,6 +42,7 @@ object NativeLibrary { const val Player8Device = 7 const val ConsoleDevice = 8 + @JvmField var sEmulationActivity = WeakReference(null) init { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt new file mode 100644 index 000000000..9b665c7a0 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress.kt @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.disk_shader_cache + +import org.yuzu.yuzu_emu.NativeLibrary +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.disk_shader_cache.ui.ShaderProgressDialogFragment + +object DiskShaderCacheProgress { + val finishLock = Object() + private lateinit var fragment: ShaderProgressDialogFragment + + private fun prepareDialog() { + val emulationActivity = NativeLibrary.sEmulationActivity.get()!! + emulationActivity.runOnUiThread { + fragment = ShaderProgressDialogFragment.newInstance( + emulationActivity.getString(R.string.loading), + emulationActivity.getString(R.string.preparing_shaders) + ) + fragment.show(emulationActivity.supportFragmentManager, ShaderProgressDialogFragment.TAG) + } + synchronized(finishLock) { finishLock.wait() } + } + + @JvmStatic + fun loadProgress(stage: Int, progress: Int, max: Int) { + val emulationActivity = NativeLibrary.sEmulationActivity.get() + ?: error("[DiskShaderCacheProgress] EmulationActivity not present") + + when (LoadCallbackStage.values()[stage]) { + LoadCallbackStage.Prepare -> prepareDialog() + LoadCallbackStage.Build -> fragment.onUpdateProgress( + emulationActivity.getString(R.string.building_shaders), + progress, + max + ) + LoadCallbackStage.Complete -> fragment.dismiss() + } + } + + // Equivalent to VideoCore::LoadCallbackStage + enum class LoadCallbackStage { + Prepare, Build, Complete + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt new file mode 100644 index 000000000..bf6f0366d --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ShaderProgressViewModel.kt @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.disk_shader_cache + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class ShaderProgressViewModel : ViewModel() { + private val _progress = MutableLiveData(0) + val progress: LiveData get() = _progress + + private val _max = MutableLiveData(0) + val max: LiveData get() = _max + + private val _message = MutableLiveData("") + val message: LiveData get() = _message + + fun setProgress(progress: Int) { + _progress.postValue(progress) + } + + fun setMax(max: Int) { + _max.postValue(max) + } + + fun setMessage(msg: String) { + _message.postValue(msg) + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt new file mode 100644 index 000000000..2c68c9ac3 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/disk_shader_cache/ui/ShaderProgressDialogFragment.kt @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.yuzu.yuzu_emu.disk_shader_cache.ui + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.lifecycle.ViewModelProvider +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding +import org.yuzu.yuzu_emu.disk_shader_cache.DiskShaderCacheProgress +import org.yuzu.yuzu_emu.disk_shader_cache.ShaderProgressViewModel + +class ShaderProgressDialogFragment : DialogFragment() { + private var _binding: DialogProgressBarBinding? = null + private val binding get() = _binding!! + + private lateinit var alertDialog: AlertDialog + + private lateinit var shaderProgressViewModel: ShaderProgressViewModel + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + _binding = DialogProgressBarBinding.inflate(layoutInflater) + shaderProgressViewModel = + ViewModelProvider(requireActivity())[ShaderProgressViewModel::class.java] + + val title = requireArguments().getString(TITLE) + val message = requireArguments().getString(MESSAGE) + + isCancelable = false + alertDialog = MaterialAlertDialogBuilder(requireActivity()) + .setView(binding.root) + .setTitle(title) + .setMessage(message) + .create() + return alertDialog + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + shaderProgressViewModel.progress.observe(viewLifecycleOwner) { progress -> + binding.progressBar.progress = progress + setUpdateText() + } + shaderProgressViewModel.max.observe(viewLifecycleOwner) { max -> + binding.progressBar.max = max + setUpdateText() + } + shaderProgressViewModel.message.observe(viewLifecycleOwner) { msg -> + alertDialog.setMessage(msg) + } + synchronized(DiskShaderCacheProgress.finishLock) { DiskShaderCacheProgress.finishLock.notifyAll() } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } + + fun onUpdateProgress(msg: String, progress: Int, max: Int) { + shaderProgressViewModel.setProgress(progress) + shaderProgressViewModel.setMax(max) + shaderProgressViewModel.setMessage(msg) + } + + private fun setUpdateText() { + binding.progressText.text = String.format( + "%d/%d", + shaderProgressViewModel.progress.value, + shaderProgressViewModel.max.value + ) + } + + companion object { + const val TAG = "ProgressDialogFragment" + const val TITLE = "title" + const val MESSAGE = "message" + + fun newInstance(title: String, message: String): ShaderProgressDialogFragment { + val frag = ShaderProgressDialogFragment() + val args = Bundle() + args.putString(TITLE, title) + args.putString(MESSAGE, message) + frag.arguments = args + return frag + } + } +} diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index f04b81335..a137d1c3a 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -196,6 +196,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) val rendererResolution = rendererSection.getSetting(SettingsFile.KEY_RENDERER_RESOLUTION) val rendererAspectRatio = rendererSection.getSetting(SettingsFile.KEY_RENDERER_ASPECT_RATIO) + val rendererUseDiskShaderCache = + rendererSection.getSetting(SettingsFile.KEY_RENDERER_USE_DISK_SHADER_CACHE) val rendererForceMaxClocks = rendererSection.getSetting(SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK) val rendererAsynchronousShaders = @@ -250,6 +252,16 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) 0 ) ) + add( + SwitchSetting( + SettingsFile.KEY_RENDERER_USE_DISK_SHADER_CACHE, + Settings.SECTION_RENDERER, + rendererUseDiskShaderCache, + R.string.use_disk_shader_cache, + R.string.use_disk_shader_cache_description, + true + ) + ) add( SwitchSetting( SettingsFile.KEY_RENDERER_FORCE_MAX_CLOCK, diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt index 37ae39fe6..c518d9ba0 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/utils/SettingsFile.kt @@ -36,6 +36,7 @@ object SettingsFile { const val KEY_RENDERER_RESOLUTION = "resolution_setup" const val KEY_RENDERER_ASPECT_RATIO = "aspect_ratio" const val KEY_RENDERER_ACCURACY = "gpu_accuracy" + const val KEY_RENDERER_USE_DISK_SHADER_CACHE = "use_disk_shader_cache" const val KEY_RENDERER_ASYNCHRONOUS_SHADERS = "use_asynchronous_shaders" const val KEY_RENDERER_FORCE_MAX_CLOCK = "force_max_clock" const val KEY_RENDERER_USE_SPEED_LIMIT = "use_speed_limit" diff --git a/src/android/app/src/main/jni/config.cpp b/src/android/app/src/main/jni/config.cpp index d882688f1..0e86dea9e 100644 --- a/src/android/app/src/main/jni/config.cpp +++ b/src/android/app/src/main/jni/config.cpp @@ -226,10 +226,6 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.bg_green); ReadSetting("Renderer", Settings::values.bg_blue); - // Disable shader cache by default on Android - Settings::values.use_disk_shader_cache = - config->GetBoolean("Renderer", "use_disk_shader_cache", false); - // Enable force_max_clock by default on Android Settings::values.renderer_force_max_clock = config->GetBoolean("Renderer", "force_max_clock", true); diff --git a/src/android/app/src/main/jni/id_cache.cpp b/src/android/app/src/main/jni/id_cache.cpp index 6291c8652..7edadb94a 100644 --- a/src/android/app/src/main/jni/id_cache.cpp +++ b/src/android/app/src/main/jni/id_cache.cpp @@ -3,13 +3,18 @@ #include +#include "common/assert.h" #include "common/fs/fs_android.h" #include "jni/applets/software_keyboard.h" #include "jni/id_cache.h" +#include "video_core/rasterizer_interface.h" static JavaVM* s_java_vm; static jclass s_native_library_class; +static jclass s_disk_cache_progress_class; +static jclass s_load_callback_stage_class; static jmethodID s_exit_emulation_activity; +static jmethodID s_disk_cache_load_progress; static constexpr jint JNI_VERSION = JNI_VERSION_1_6; @@ -38,10 +43,22 @@ jclass GetNativeLibraryClass() { return s_native_library_class; } +jclass GetDiskCacheProgressClass() { + return s_disk_cache_progress_class; +} + +jclass GetDiskCacheLoadCallbackStageClass() { + return s_load_callback_stage_class; +} + jmethodID GetExitEmulationActivity() { return s_exit_emulation_activity; } +jmethodID GetDiskCacheLoadProgress() { + return s_disk_cache_load_progress; +} + } // namespace IDCache #ifdef __cplusplus @@ -58,8 +75,16 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { // Initialize Java classes const jclass native_library_class = env->FindClass("org/yuzu/yuzu_emu/NativeLibrary"); s_native_library_class = reinterpret_cast(env->NewGlobalRef(native_library_class)); + s_disk_cache_progress_class = reinterpret_cast(env->NewGlobalRef( + env->FindClass("org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress"))); + s_load_callback_stage_class = reinterpret_cast(env->NewGlobalRef(env->FindClass( + "org/yuzu/yuzu_emu/disk_shader_cache/DiskShaderCacheProgress$LoadCallbackStage"))); + + // Initialize methods s_exit_emulation_activity = env->GetStaticMethodID(s_native_library_class, "exitEmulationActivity", "(I)V"); + s_disk_cache_load_progress = + env->GetStaticMethodID(s_disk_cache_progress_class, "loadProgress", "(III)V"); // Initialize Android Storage Common::FS::Android::RegisterCallbacks(env, s_native_library_class); @@ -79,6 +104,8 @@ void JNI_OnUnload(JavaVM* vm, void* reserved) { // UnInitialize Android Storage Common::FS::Android::UnRegisterCallbacks(); env->DeleteGlobalRef(s_native_library_class); + env->DeleteGlobalRef(s_disk_cache_progress_class); + env->DeleteGlobalRef(s_load_callback_stage_class); // UnInitialze applets SoftwareKeyboard::CleanupJNI(env); diff --git a/src/android/app/src/main/jni/id_cache.h b/src/android/app/src/main/jni/id_cache.h index 2fe07169d..9337cd254 100644 --- a/src/android/app/src/main/jni/id_cache.h +++ b/src/android/app/src/main/jni/id_cache.h @@ -2,10 +2,15 @@ #include +#include "video_core/rasterizer_interface.h" + namespace IDCache { JNIEnv* GetEnvForThread(); jclass GetNativeLibraryClass(); +jclass GetDiskCacheProgressClass(); +jclass GetDiskCacheLoadCallbackStageClass(); jmethodID GetExitEmulationActivity(); +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 55736bce2..b10c55a45 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -51,6 +51,7 @@ #include "jni/emu_window/emu_window.h" #include "jni/id_cache.h" #include "video_core/rasterizer_interface.h" +#include "video_core/renderer_base.h" namespace { @@ -229,6 +230,15 @@ public: m_is_running = true; } + // Load the disk shader cache. + if (Settings::values.use_disk_shader_cache.GetValue()) { + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); + m_system.Renderer().ReadRasterizer()->LoadDiskResources( + m_system.GetApplicationProcessProgramID(), std::stop_token{}, + LoadDiskCacheProgress); + LoadDiskCacheProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); + } + void(m_system.Run()); if (m_system.DebuggerEnabled()) { @@ -295,6 +305,14 @@ private: return entry; } +private: + static void LoadDiskCacheProgress(VideoCore::LoadCallbackStage stage, int progress, int max) { + JNIEnv* env = IDCache::GetEnvForThread(); + env->CallStaticVoidMethod(IDCache::GetDiskCacheProgressClass(), + IDCache::GetDiskCacheLoadProgress(), static_cast(stage), + static_cast(progress), static_cast(max)); + } + private: static EmulationSession s_instance; 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 index 1dbfd4f7b..d17711a65 100644 --- a/src/android/app/src/main/res/layout/dialog_progress_bar.xml +++ b/src/android/app/src/main/res/layout/dialog_progress_bar.xml @@ -12,4 +12,13 @@ android:layout_margin="24dp" app:trackCornerRadius="4dp" /> + + diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 70bff5749..45a9694d4 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -35,6 +35,8 @@ Compiles shaders asynchronously, which will reduce stutter but may introduce glitches. Enable graphics debugging When checked, the graphics API enters a slower debugging mode. + Use disk shader cache + Reduce stuttering by storing and loading generated shaders to disk. Volume @@ -45,6 +47,7 @@ Saved settings Saved settings for %1$s Error saving %1$s.ini: %2$s + Loading... Settings @@ -183,4 +186,8 @@ Home Screenshot + + Preparing shaders + Building shaders +